Skip to content
This repository has been archived by the owner on Jan 20, 2020. It is now read-only.

CCXT Historical Data Retrieval Methods + Tests #60

Merged
merged 7 commits into from
Oct 22, 2017
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gdax-trading-toolkit",
"version": "0.1.18",
"version": "0.1.19",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let us manage versioning; we can then batch a bunch of PRs together in a release.

"description": "A trading toolkit for building advanced trading bots on the GDAX platform",
"main": "build/src/index.js",
"types": "build/src/index.d.ts",
Expand Down Expand Up @@ -50,7 +50,7 @@
"@types/ws": "0.0.37",
"bignumber.js": "4.0.2",
"bintrees": "1.0.1",
"ccxt": "^1.6.72",
"ccxt": "^1.9.186",
"commander": "2.9.0",
"crypto": "0.0.3",
"gdax": "0.3.1",
Expand Down
30 changes: 28 additions & 2 deletions src/exchanges/ccxt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
**********************************************************************************************************************/

import * as ccxt from 'ccxt';
import { CCXTMarket, CCXTOrderbook } from 'ccxt';
import { CCXTHistTrade, CCXTMarket, CCXTOHLCV, CCXTOrderbook } from 'ccxt';
import { Product, PublicExchangeAPI, Ticker } from '../PublicExchangeAPI';
import { AuthenticatedExchangeAPI, Balances } from '../AuthenticatedExchangeAPI';
import { CryptoAddress, ExchangeTransferAPI, TransferRequest, TransferResult, WithdrawalRequest } from '../ExchangeTransferAPI';
import { ExchangeAuthConfig } from '../AuthConfig';
import { Big, BigJS } from '../../lib/types';
import { BookBuilder } from '../../lib/BookBuilder';
import { PlaceOrderMessage } from '../../core/Messages';
import { PlaceOrderMessage, TradeMessage } from '../../core/Messages';
import { Level3Order, LiveOrder } from '../../lib/Orderbook';
import { Logger } from '../../utils/Logger';

Expand Down Expand Up @@ -337,4 +337,30 @@ export default class CCXTExchangeWrapper implements PublicExchangeAPI, Authentic
transfer(cur: string, amount: BigJS, from: string, to: string, options: any): Promise<TransferResult> {
throw new Error('Not implemented yet');
}

/**
* Attempts to fetch historical trade data from the exchange and return it in
*/
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(),
}));
}

async fetchOHLCV(symbol: string, params?: {}): Promise<CCXTOHLCV[] | null> {
if (!this.instance.hasFetchOHLCV) {
return null;
} else {
const sourceSymbol = await this.getSourceSymbol(symbol);
return await this.instance.fetchOHLCV(sourceSymbol, params);
}
}
}
51 changes: 51 additions & 0 deletions test/exchanges/CCXTExchangeTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**********************************************************************************************************************
* @license *
* Copyright 2017 Coinbase, Inc. *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on*
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
* License for the specific language governing permissions and limitations under the License. *
**********************************************************************************************************************/

const assert = require('assert');

import { ExchangeAuthConfig } from '../../src/exchanges/AuthConfig';
import CCXTExchangeWrapper from '../../src/exchanges/ccxt';
import { NullLogger } from '../../src/utils/Logger';

describe('CCXT Exchange Wrapper', () => {
const exchangeId = 'bitmex';
const productId = 'BTC-USD';
let wrapper: CCXTExchangeWrapper;

before(async () => {
const auth: ExchangeAuthConfig = { key: null, secret: null };
wrapper = CCXTExchangeWrapper.createExchange(exchangeId, auth, NullLogger);
});

it('is able to fetch historical trade data from an exchange', async () => {
const data = await wrapper.fetchHistTrades(productId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the tests, but it looks like these requests are hitting the internet. This leads to slow and flaky tests (what if bitmex is down / delist the product?). We use the nock library for mocking out HTTP requests, and you can see examples of its use in some of the other tests here.

Copy link

@kroitor kroitor Oct 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delist the product

That's why we don't hardcode pairs and fetch them online where possible... Outdated product lists can be painful to maintain in the long term. Though we still have to keep unique product id ←→ common symbol mapping to remain consistent across exchanges... Did you know that CoinMarketCap lists BTM for two different coins (Bytom (HitBtc) vs Bitmark (Poloniex), ccxt/ccxt#348, ccxt/ccxt#350, etc...) ? There's plenty of that across exchanges...

Just my 2 cents.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. Then there's BCH and BCC..

For the GTT, which is an abstraction layer, I think it's ok to maintain a canonical list of coin names and map them behind the interfaces.
So for example, if GTT users define and expect BCC to represent 'Bitcoin cash' then mappings to BCH should happen behind the scenes where appropriate. Then users only have to learn one set of ticker names and the GTT does most of the work. ccxt, being pretty close to the apis needs to work with this more carefully.

Copy link

@kroitor kroitor Oct 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CjS77 we do the BCC → BCH mapping on the fly and the user doesn't have to know/learn it. We use BCH everywhere in our API because BCH is most widely used for Bitcoin Cash, while BCC also stands for Bitconnect sometimes, that causes confusion. If you notice BCC somewhere in CCXT let us know, but you should expect unified symbols from CCXT as written here: https://github.com/ccxt-dev/ccxt/wiki/Manual#naming-consistency ;) The unification mapping is overrideable per exchange, of course.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, so we're advocating the same thing. I was just using BCH/BCC as an example and wasn't favoring one ticker over the other.

assert(data.length && data.length > 0);
});

it('is able to fetch historical OHLCV candlestick data from an exchange', async () => {
const data = await wrapper.fetchOHLCV(productId);
assert.notEqual(data, null);
assert(data.length && data.length > 0);
});

it('is able to fetch a ticker', async () => {
const data = await wrapper.loadTicker(productId);
assert(data.bid > data.ask);
});

it('is able to load an orderbook image', async () => {
const data = await wrapper.loadOrderbook(productId);
assert(data.numAsks > 0 && data.numBids > 0);
});
});
21 changes: 20 additions & 1 deletion types/ccxt.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,25 @@ declare module 'ccxt' {
datetime: string;
}

export interface CCXTHistTrade {
info: {}; // the original decoded JSON as is
id: string; // string trade id
timestamp: number; // Unix timestamp in milliseconds
datetime: string; // ISO8601 datetime with milliseconds;
symbol: string; // symbol in CCXT format
order?: string; // string order id or undefined/None/null
type?: 'market' | 'limit'; // order type, 'market', 'limit' or undefined/None/null
side: 'buy' | 'sell';
price: number; // float price in quote currency
amount: number; // amount of base currency
}

// timestamp, open, high, low, close, volume
export type CCXTOHLCV = [number, number, number, number, number, number];

export class Exchange {
readonly rateLimit: number;
readonly hasFetchOHLCV: boolean;
public verbose: boolean;
public substituteCommonCurrencyCodes: boolean;
public hasFetchTickers: boolean;
Expand Down Expand Up @@ -54,7 +71,9 @@ declare module 'ccxt' {

fetchTicker(market: string): Promise<any>;

fetchTrades(symbol: string, params?: any): Promise<any>;
fetchTrades(symbol: string, params?: {}): Promise<CCXTHistTrade[]>;

fetchOHLCV?(symbol: string, params?: {}): Promise<CCXTOHLCV[]>;

cancelOrder(id: string): Promise<any>;

Expand Down