Skip to content

Commit

Permalink
Exchanges should use Error API
Browse files Browse the repository at this point in the history
  • Loading branch information
Cayle Sharrock committed Nov 11, 2017
1 parent 42eb5bd commit 5e73f88
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 87 deletions.
8 changes: 4 additions & 4 deletions src/exchanges/PublicExchangeAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,22 @@ export interface PublicExchangeAPI {
readonly owner: string;

/**
* Load the list of supported products on this exchange
* Load the list of supported products on this exchange. Resolves with a list of products, or otherwise rejects the Promise with an HTTPError
*/
loadProducts(): Promise<Product[]>;

/**
* Load the mid-market price from the exchange's ticker
* Load the mid-market price from the exchange's ticker. Resolves with the latest midmarket price, or else rejects with an HTTPError
*/
loadMidMarketPrice(gdaxProduct: string): Promise<BigJS>;

/**
* Load the order book from the REST API and return an aggregated book as a #{../core/BookBuilder} object
* Load the order book from the REST API and resolves as an aggregated book as a #{../core/BookBuilder} object, otherwise the promise is rejected with an HTTPError
*/
loadOrderbook(gdaxProduct: string): Promise<BookBuilder>;

/**
* Load the ticker for the configured product from the REST API
* Load the ticker for the configured product from the REST API, Resolves with the latest ticker object, or else rejects with an HTTPError
*/
loadTicker(gdaxProduct: string): Promise<Ticker>;
}
Expand Down
29 changes: 18 additions & 11 deletions src/exchanges/bitfinex/BitfinexExchangeAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { PlaceOrderMessage } from '../../core/Messages';
import { LiveOrder } from '../../lib/Orderbook';
import request = require('superagent');
import Response = request.Response;
import { extractResponse, GTTError, HTTPError } from '../../lib/errors';

const API_V1 = 'https://api.bitfinex.com/v1';

Expand Down Expand Up @@ -110,7 +111,7 @@ export class BitfinexExchangeAPI implements PublicExchangeAPI, AuthenticatedExch
.accept('application/json')
.then((res: Response) => {
if (res.status !== 200) {
throw new Error('loadProducts did not get the expected response from the server. ' + res.body);
return Promise.reject(new HTTPError('Error loading products from Bitfinex', extractResponse(res)));
}
const bfProducts: BitfinexProduct[] = res.body;
const products: Product[] = bfProducts.map((prod: BitfinexProduct) => {
Expand All @@ -127,7 +128,9 @@ export class BitfinexExchangeAPI implements PublicExchangeAPI, AuthenticatedExch
sourceData: prod
};
});
return products;
return Promise.resolve(products);
}).catch((err: Error) => {
return Promise.reject(new GTTError('Error loading products from Bitfinex', err));
});
}

Expand All @@ -144,9 +147,11 @@ export class BitfinexExchangeAPI implements PublicExchangeAPI, AuthenticatedExch
.accept('application/json')
.then((res: Response) => {
if (res.status !== 200) {
throw new Error('loadOrderbook did not get the expected response from the server. ' + res.body);
return Promise.reject(new HTTPError('Error loading order book from Bitfinex', extractResponse(res)));
}
return this.convertBitfinexBookToGdaxBook(res.body as BitfinexOrderbook);
return Promise.resolve(this.convertBitfinexBookToGdaxBook(res.body as BitfinexOrderbook));
}).catch((err: Error) => {
return Promise.reject(new GTTError(`Error loading ${gdaxProduct} order book from Bitfinex`, err));
});
}

Expand All @@ -156,24 +161,26 @@ export class BitfinexExchangeAPI implements PublicExchangeAPI, AuthenticatedExch
.accept('application/json')
.then((res: Response) => {
if (res.status !== 200) {
throw new Error('loadTicker did not get the expected response from the server. ' + res.body);
return Promise.reject(new HTTPError('Error loading ticker from Bitfinex', extractResponse(res)));
}
const ticker: any = res.body;
return {
return Promise.resolve({
productId: gdaxProduct,
ask: ticker.ask ? Big(ticker.ask) : null,
bid: ticker.bid ? Big(ticker.bid) : null,
price: Big(ticker.last_price || 0),
volume: Big(ticker.volume || 0),
time: new Date(+ticker.timestamp * 1000)
};
});
}).catch((err: Error) => {
return Promise.reject(new GTTError(`Error loading ${gdaxProduct} ticker from Bitfinex`, err));
});
}

checkAuth(): Promise<ExchangeAuthConfig> {
return new Promise((resolve, reject) => {
if (this.auth === null) {
return reject(new Error('You cannot make authenticated requests if a ExchangeAuthConfig object was not provided to the BitfinexExchangeAPI constructor'));
return reject(new GTTError('You cannot make authenticated requests if a ExchangeAuthConfig object was not provided to the BitfinexExchangeAPI constructor'));
}
return resolve(this.auth);
});
Expand Down Expand Up @@ -267,7 +274,7 @@ export class BitfinexExchangeAPI implements PublicExchangeAPI, AuthenticatedExch
// -------------------------- Transfer methods -------------------------------------------------

requestCryptoAddress(cur: string): Promise<CryptoAddress> {
return undefined;
return Promise.reject(new GTTError('Not implemented'));
}

requestTransfer(req: TransferRequest): Promise<TransferResult> {
Expand Down Expand Up @@ -300,11 +307,11 @@ export class BitfinexExchangeAPI implements PublicExchangeAPI, AuthenticatedExch
}

requestWithdrawal(req: WithdrawalRequest): Promise<TransferResult> {
return undefined;
return Promise.reject(new GTTError('Not implemented'));
}

transfer(cur: string, amount: BigJS, from: string, to: string, options: any): Promise<TransferResult> {
return undefined;
return Promise.reject(new GTTError('Not implemented'));
}

// -------------------------- Helper methods -------------------------------------------------
Expand Down
68 changes: 33 additions & 35 deletions src/exchanges/ccxt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { BookBuilder } from '../../lib/BookBuilder';
import { PlaceOrderMessage, TradeMessage } from '../../core/Messages';
import { Level3Order, LiveOrder } from '../../lib/Orderbook';
import { Logger } from '../../utils/Logger';
import { GTTError, HTTPError } from '../../lib/errors';

type ExchangeDefinition = [string, new (opts: any) => ccxt.Exchange];
// Supported exchanges, minus those with native support
Expand Down Expand Up @@ -164,7 +165,7 @@ export default class CCXTExchangeWrapper implements PublicExchangeAPI, Authentic
}
}
return Promise.resolve(null);
});
}).catch((err: Error) => rejectWithError(`Error loading symbols for ${gdaxProduct} on ${this.instance.name} (CCXT)`, err));
}

loadProducts(): Promise<Product[]> {
Expand All @@ -188,16 +189,13 @@ export default class CCXTExchangeWrapper implements PublicExchangeAPI, Authentic
result.push(product);
}
return Promise.resolve(result);
}).catch((err) => {
this.log('error', `Could not load products for ${this.owner}`, err);
return Promise.resolve([]);
});
}).catch((err: Error) => rejectWithError(`Error loading products on ${this.instance.name} (CCXT)`, err));
}

loadMidMarketPrice(gdaxProduct: string): Promise<BigJS> {
return this.loadTicker(gdaxProduct).then((t: Ticker) => {
if (!(t && t.ask && t.bid)) {
return Promise.resolve(null);
return Promise.reject(new HTTPError(`Error loading ticker for ${gdaxProduct} from ${this.instance.name} (CCXT)`, { status: 200, body: t}));
}
return Promise.resolve(t.bid.plus(t.ask).div(2));
});
Expand Down Expand Up @@ -225,10 +223,7 @@ export default class CCXTExchangeWrapper implements PublicExchangeAPI, Authentic
addSide('buy', ccxtBook.bids);
addSide('sell', ccxtBook.asks);
return Promise.resolve(book);
}).catch((err) => {
this.log('error', `Could not load orderbook for ${gdaxProduct} on ${this.owner}`, err);
return Promise.resolve(null);
});
}).catch((err: Error) => rejectWithError(`Error loading order book for ${gdaxProduct} on ${this.instance.name} (CCXT)`, err));
}

loadTicker(gdaxProduct: string): Promise<Ticker> {
Expand All @@ -247,10 +242,7 @@ export default class CCXTExchangeWrapper implements PublicExchangeAPI, Authentic
volume: Big(ticker.baseVolume)
};
return Promise.resolve(t);
}).catch((err) => {
this.log('error', `Could not load ticker for ${gdaxProduct} on ${this.owner}`, err);
return Promise.resolve(null);
});
}).catch((err: Error) => rejectWithError(`Error loading ticker for ${gdaxProduct} on ${this.instance.name} (CCXT)`, err));
}

placeOrder(order: PlaceOrderMessage): Promise<LiveOrder> {
Expand All @@ -271,10 +263,7 @@ export default class CCXTExchangeWrapper implements PublicExchangeAPI, Authentic
status: 'active'
};
return Promise.resolve(result);
}).catch((err) => {
this.log('error', `Could not place order on ${this.owner}`, {error: err, order: order});
return Promise.resolve(null);
});
}).catch((err: Error) => rejectWithError(`Error placing order for ${order.productId} on ${this.instance.name} (CCXT)`, err));
});

}
Expand Down Expand Up @@ -316,10 +305,7 @@ export default class CCXTExchangeWrapper implements PublicExchangeAPI, Authentic
};
}
return Promise.resolve(result);
}).catch((err) => {
this.log('error', `Could not load balances from ${this.owner}`, err);
return Promise.resolve(null);
});
}).catch((err: Error) => rejectWithError(`Error loading balances on ${this.instance.name} (CCXT)`, err));
}

requestCryptoAddress(cur: string): Promise<CryptoAddress> {
Expand All @@ -343,24 +329,36 @@ export default class CCXTExchangeWrapper implements PublicExchangeAPI, Authentic
*/
async fetchHistTrades(symbol: string, params?: {}): Promise<TradeMessage[]> {
const sourceSymbol = await this.getSourceSymbol(symbol);
const rawTrades: CCXTHistTrade[] = await this.instance.fetchTrades(sourceSymbol, params);
return rawTrades.map(({ info, id, timestamp, datetime, symbol: _symbol, order, type, side, price, amount }) => ({
type: 'trade' as 'trade',
time: new Date(timestamp),
productId: _symbol,
side,
tradeId: id,
price: price.toString(),
size: amount.toString(),
}));
try {
const rawTrades: CCXTHistTrade[] = await this.instance.fetchTrades(sourceSymbol, params);
return rawTrades.map(({ info, id, timestamp, datetime, symbol: _symbol, order, type, side, price, amount }) => ({
type: 'trade' as 'trade',
time: new Date(timestamp),
productId: _symbol,
side,
tradeId: id,
price: price.toString(),
size: amount.toString(),
}));
} catch (err) {
return rejectWithError(`Error trade history for ${symbol} on ${this.instance.name} (CCXT)`, err);
}
}

async fetchOHLCV(symbol: string, params?: {}): Promise<CCXTOHLCV[] | null> {
if (!this.instance.hasFetchOHLCV) {
return null;
} else {
const sourceSymbol = await this.getSourceSymbol(symbol);
return Promise.reject(new GTTError(`${this.instance.name} does not support candles`));
}
const sourceSymbol = await this.getSourceSymbol(symbol);
try {
return await this.instance.fetchOHLCV(sourceSymbol, params);
} catch (err) {
return rejectWithError(`Error loading candles for ${symbol} on ${this.instance.name} (CCXT)`, err);
}
}
}

function rejectWithError(msg: string, error: any): Promise<never> {
const err = new GTTError(`${error.constructor.name}: ${msg}`, error);
return Promise.reject(err);
}
Loading

0 comments on commit 5e73f88

Please sign in to comment.