Skip to content

Commit 27f456f

Browse files
author
Karl Ranna
authored
Verify CEX assets (#111)
* rename initBinance$ -> initCEX$ * return markets from initCEX$ * fixup! rename initBinance$ -> initCEX$ * verify CEX assets * fixup! verify CEX assets
1 parent 4e5caba commit 27f456f

19 files changed

+202
-66
lines changed

src/arby.spec.ts

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { Observable } from 'rxjs';
22
import { TestScheduler } from 'rxjs/testing';
33
import { startArby } from '../src/arby';
44
import { Config } from '../src/config';
5+
import { InitCEXResponse } from './centralized/ccxt/init';
56
import { getLoggers } from './test-utils';
6-
import { Exchange } from 'ccxt';
77

88
let testScheduler: TestScheduler;
99

@@ -14,11 +14,16 @@ type AssertStartArbyParams = {
1414
getTrade$: string;
1515
shutdown$: string;
1616
cleanup$: string;
17-
initBinance$: string;
17+
initCEX$: string;
1818
};
19+
verifyMarkets?: () => boolean;
1920
};
2021

21-
const assertStartArby = ({ expected, inputEvents }: AssertStartArbyParams) => {
22+
const assertStartArby = ({
23+
expected,
24+
inputEvents,
25+
verifyMarkets,
26+
}: AssertStartArbyParams) => {
2227
testScheduler.run(helpers => {
2328
const { cold, expectObservable } = helpers;
2429
const config$ = cold(inputEvents.config$) as Observable<Config>;
@@ -29,9 +34,9 @@ const assertStartArby = ({ expected, inputEvents }: AssertStartArbyParams) => {
2934
const cleanup$ = () => {
3035
return cold(inputEvents.cleanup$);
3136
};
32-
const initBinance$ = () => {
33-
return (cold(inputEvents.initBinance$) as unknown) as Observable<
34-
Exchange
37+
const initCEX$ = () => {
38+
return (cold(inputEvents.initCEX$) as unknown) as Observable<
39+
InitCEXResponse
3540
>;
3641
};
3742
const arby$ = startArby({
@@ -40,9 +45,10 @@ const assertStartArby = ({ expected, inputEvents }: AssertStartArbyParams) => {
4045
shutdown$,
4146
trade$: getTrade$,
4247
cleanup$,
43-
initBinance$,
48+
initCEX$,
49+
verifyMarkets: verifyMarkets ? verifyMarkets : () => true,
4450
});
45-
expectObservable(arby$).toBe(expected);
51+
expectObservable(arby$).toBe(expected, undefined, { message: 'error' });
4652
});
4753
};
4854

@@ -56,7 +62,7 @@ describe('startArby', () => {
5662
it('waits for valid configuration before starting', () => {
5763
const inputEvents = {
5864
config$: '1000ms a',
59-
initBinance$: '1s a',
65+
initCEX$: '1s a',
6066
getTrade$: 'b',
6167
shutdown$: '',
6268
cleanup$: '',
@@ -68,10 +74,28 @@ describe('startArby', () => {
6874
});
6975
});
7076

77+
it('errors when verifyMarkets fails', () => {
78+
const inputEvents = {
79+
config$: '1000ms a',
80+
initCEX$: '1s a',
81+
getTrade$: 'b',
82+
shutdown$: '',
83+
cleanup$: '',
84+
};
85+
const expected = '2s #';
86+
assertStartArby({
87+
inputEvents,
88+
expected,
89+
verifyMarkets: () => {
90+
throw { message: 'error' };
91+
},
92+
});
93+
});
94+
7195
it('performs cleanup when shutting down gracefully', () => {
7296
const inputEvents = {
7397
config$: 'a',
74-
initBinance$: '1s a',
98+
initCEX$: '1s a',
7599
getTrade$: '500ms b',
76100
shutdown$: '10s c',
77101
cleanup$: '2s a',
@@ -86,7 +110,7 @@ describe('startArby', () => {
86110
it('performs cleanup when getTrade$ errors', () => {
87111
const inputEvents = {
88112
config$: 'a',
89-
initBinance$: '1s a',
113+
initCEX$: '1s a',
90114
getTrade$: '500ms #',
91115
shutdown$: '10s c',
92116
cleanup$: '2s a',

src/arby.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import { Exchange } from 'ccxt';
21
import { concat, Observable } from 'rxjs';
32
import { catchError, mergeMap, takeUntil } from 'rxjs/operators';
43
import { getExchange } from './centralized/ccxt/exchange';
5-
import { initBinance$, InitBinanceParams } from './centralized/ccxt/init';
4+
import {
5+
initCEX$,
6+
InitCEXparams,
7+
InitCEXResponse,
8+
} from './centralized/ccxt/init';
69
import { loadMarkets$ } from './centralized/ccxt/load-markets';
710
import { getCentralizedExchangePrice$ } from './centralized/exchange-price';
811
import { getCentralizedExchangeOrder$ } from './centralized/order';
@@ -12,10 +15,12 @@ import { Logger, Loggers } from './logger';
1215
import { catchOpenDEXerror } from './opendex/catch-error';
1316
import { getOpenDEXcomplete$ } from './opendex/complete';
1417
import { removeOpenDEXorders$ } from './opendex/remove-orders';
18+
import { getArbyStore } from './store';
1519
import { getCleanup$, GetCleanupParams } from './trade/cleanup';
1620
import { getNewTrade$, GetTradeParams } from './trade/trade';
1721
import { getStartShutdown$ } from './utils';
18-
import { getArbyStore } from './store';
22+
import { Dictionary, Market } from 'ccxt';
23+
import { verifyMarkets } from './centralized/verify-markets';
1924

2025
type StartArbyParams = {
2126
config$: Observable<Config>;
@@ -32,11 +37,12 @@ type StartArbyParams = {
3237
config,
3338
removeOpenDEXorders$,
3439
}: GetCleanupParams) => Observable<unknown>;
35-
initBinance$: ({
40+
initCEX$: ({
3641
getExchange,
3742
config,
3843
loadMarkets$,
39-
}: InitBinanceParams) => Observable<Exchange>;
44+
}: InitCEXparams) => Observable<InitCEXResponse>;
45+
verifyMarkets: (config: Config, CEXmarkets: Dictionary<Market>) => boolean;
4046
};
4147

4248
const logConfig = (config: Config, logger: Logger) => {
@@ -79,21 +85,23 @@ export const startArby = ({
7985
shutdown$,
8086
trade$,
8187
cleanup$,
82-
initBinance$,
88+
initCEX$,
89+
verifyMarkets,
8390
}: StartArbyParams): Observable<any> => {
8491
const store = getArbyStore();
8592
return config$.pipe(
8693
mergeMap(config => {
87-
const CEX$ = initBinance$({
94+
const CEX$ = initCEX$({
8895
config,
8996
loadMarkets$,
9097
getExchange,
9198
});
9299
return CEX$.pipe(
93-
mergeMap(CEX => {
100+
mergeMap(({ markets: CEXmarkets, exchange: CEX }) => {
94101
const loggers = getLoggers(config);
95102
loggers.global.info('Starting. Hello, Arby.');
96103
logConfig(config, loggers.global);
104+
verifyMarkets(config, CEXmarkets);
97105
const tradeComplete$ = trade$({
98106
config,
99107
loggers,
@@ -145,7 +153,8 @@ if (!module.parent) {
145153
getLoggers,
146154
shutdown$: getStartShutdown$(),
147155
cleanup$: getCleanup$,
148-
initBinance$,
156+
initCEX$,
157+
verifyMarkets,
149158
}).subscribe({
150159
error: error => {
151160
if (error.message) {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`quantityAboveMinimum throws for unknown asset KILRAU 1`] = `"Could not retrieve minimum order quantity for KILRAU"`;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`verifyMarkets throws when BTC/USD trading pair is inactive 1`] = `"The configured trading pair BTC/USD does not exist on Binance"`;
4+
5+
exports[`verifyMarkets throws when BTC/USD trading pair not exist 1`] = `"The configured trading pair BTC/USD does not exist on Binance"`;

src/centralized/ccxt/init.spec.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Dictionary, Exchange, Market } from 'ccxt';
22
import { Observable, of } from 'rxjs';
33
import { TestScheduler } from 'rxjs/testing';
44
import { testConfig } from '../../test-utils';
5-
import { initBinance$ } from './init';
5+
import { initCEX$ } from './init';
66

77
let testScheduler: TestScheduler;
88

@@ -21,12 +21,14 @@ const assertInitBinance = (
2121
};
2222
const config = testConfig();
2323
const getExchange = () => ('a' as unknown) as Exchange;
24-
const centralizedExchangeOrder$ = initBinance$({
24+
const centralizedExchangeOrder$ = initCEX$({
2525
config,
2626
loadMarkets$,
2727
getExchange,
2828
});
29-
expectObservable(centralizedExchangeOrder$).toBe(expected);
29+
expectObservable(centralizedExchangeOrder$).toBe(expected, {
30+
a: { exchange: 'a', markets: 'a' },
31+
});
3032
});
3133
};
3234

@@ -54,7 +56,7 @@ it('initializes once and loads markets', done => {
5456
const getExchange = jest.fn(() => {
5557
return (null as unknown) as Exchange;
5658
});
57-
const CEX = initBinance$({
59+
const CEX = initCEX$({
5860
loadMarkets$,
5961
config: testConfig(),
6062
getExchange,

src/centralized/ccxt/init.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,33 @@
11
import { Dictionary, Exchange, Market } from 'ccxt';
22
import { Observable } from 'rxjs';
3-
import { mapTo } from 'rxjs/operators';
3+
import { map } from 'rxjs/operators';
44
import { Config } from '../../config';
55

6-
type InitBinanceParams = {
6+
type InitCEXparams = {
77
config: Config;
88
loadMarkets$: (exchange: Exchange) => Observable<Dictionary<Market>>;
99
getExchange: (config: Config) => Exchange;
1010
};
1111

12-
const initBinance$ = ({
12+
type InitCEXResponse = {
13+
markets: Dictionary<Market>;
14+
exchange: Exchange;
15+
};
16+
17+
const initCEX$ = ({
1318
getExchange,
1419
config,
1520
loadMarkets$,
16-
}: InitBinanceParams): Observable<Exchange> => {
21+
}: InitCEXparams): Observable<InitCEXResponse> => {
1722
const exchange = getExchange(config);
18-
return loadMarkets$(exchange).pipe(mapTo(exchange));
23+
return loadMarkets$(exchange).pipe(
24+
map(markets => {
25+
return {
26+
markets,
27+
exchange,
28+
};
29+
})
30+
);
1931
};
2032

21-
export { initBinance$, InitBinanceParams };
33+
export { initCEX$, InitCEXparams, InitCEXResponse };

src/centralized/ccxt/load-markets.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Exchange, Market, Dictionary } from 'ccxt';
2-
import { from, Observable, defer } from 'rxjs';
1+
import { Dictionary, Exchange, Market } from 'ccxt';
2+
import { defer, from, Observable } from 'rxjs';
33

44
const loadMarkets$ = (exchange: Exchange): Observable<Dictionary<Market>> => {
55
return defer(() => from(exchange.loadMarkets()));

src/centralized/derive-order-quantity.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import BigNumber from 'bignumber.js';
2-
import { OrderSide, Asset } from '../constants';
2+
import { OrderSide } from '../constants';
33
import { deriveCEXorderQuantity } from './derive-order-quantity';
44
import { CEXorder } from './order-builder';
55
import { testConfig } from '../test-utils';
@@ -15,8 +15,8 @@ describe('deriveCEXorderQuantity', () => {
1515
quantity: new BigNumber('0.00104227'),
1616
side: OrderSide.BUY,
1717
};
18-
const CEX_BASEASSET: Asset = 'BTC';
19-
const CEX_QUOTEASSET: Asset = 'USDT';
18+
const CEX_BASEASSET = 'BTC';
19+
const CEX_QUOTEASSET = 'USDT';
2020
const config = {
2121
...testConfig(),
2222
CEX_BASEASSET,
@@ -32,8 +32,8 @@ describe('deriveCEXorderQuantity', () => {
3232
quantity: new BigNumber('10.94381840'),
3333
side: OrderSide.BUY,
3434
};
35-
const CEX_BASEASSET: Asset = 'ETH';
36-
const CEX_QUOTEASSET: Asset = 'BTC';
35+
const CEX_BASEASSET = 'ETH';
36+
const CEX_QUOTEASSET = 'BTC';
3737
const config = {
3838
...testConfig(),
3939
CEX_BASEASSET,

src/centralized/minimum-order-quantity-filter.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,12 @@ describe('quantityAboveMinimum', () => {
3737
);
3838
expect(quantityAboveMinimum('USDT')(quantity)).toEqual(false);
3939
});
40+
41+
it('throws for unknown asset KILRAU', () => {
42+
expect.assertions(1);
43+
const quantity = new BigNumber('1');
44+
expect(() => {
45+
quantityAboveMinimum('KILRAU')(quantity);
46+
}).toThrowErrorMatchingSnapshot();
47+
});
4048
});

src/centralized/minimum-order-quantity-filter.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
import BigNumber from 'bignumber.js';
2-
import { Asset } from '../constants';
2+
import { errors } from '../opendex/errors';
33

44
type MinimumCEXquantities = {
5-
BTC: BigNumber;
6-
ETH: BigNumber;
7-
DAI: BigNumber;
8-
USDT: BigNumber;
5+
[key: string]: BigNumber;
96
};
107

118
const MINIMUM_ORDER_SIZE: MinimumCEXquantities = {
@@ -15,9 +12,17 @@ const MINIMUM_ORDER_SIZE: MinimumCEXquantities = {
1512
USDT: new BigNumber('15'),
1613
};
1714

18-
const quantityAboveMinimum = (asset: Asset) => {
15+
const getMinimumOrderSize = (asset: string): BigNumber => {
16+
const minimumOrderSize = MINIMUM_ORDER_SIZE[asset];
17+
if (!minimumOrderSize) {
18+
throw errors.CEX_INVALID_MINIMUM_ORDER_QUANTITY(asset);
19+
}
20+
return minimumOrderSize;
21+
};
22+
23+
const quantityAboveMinimum = (asset: string) => {
1924
return (quantity: BigNumber): boolean => {
20-
return quantity.isGreaterThanOrEqualTo(MINIMUM_ORDER_SIZE[asset]);
25+
return quantity.isGreaterThanOrEqualTo(getMinimumOrderSize(asset));
2126
};
2227
};
2328

0 commit comments

Comments
 (0)