Skip to content

Commit 286337f

Browse files
author
Karl Ranna
authored
Merge pull request #82 from ExchangeUnion/fix/order-throttle-followups
fix: order throttling follow-up fixes
2 parents ab8aebd + 10b7467 commit 286337f

File tree

9 files changed

+276
-132
lines changed

9 files changed

+276
-132
lines changed

src/centralized/exchange-price.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import BigNumber from 'bignumber.js';
22
import { Observable, throwError } from 'rxjs';
3-
import { catchError, timeout, share } from 'rxjs/operators';
3+
import {
4+
catchError,
5+
timeout,
6+
share,
7+
distinctUntilChanged,
8+
} from 'rxjs/operators';
49
import WebSocket from 'ws';
510
import { Config } from '../config';
611
import { Logger } from '../logger';
@@ -55,6 +60,7 @@ const getCentralizedExchangePrice$ = ({
5560
catchError(() => {
5661
return throwError(errors.CENTRALIZED_EXCHANGE_PRICE_FEED_ERROR);
5762
}),
63+
distinctUntilChanged((a, b) => a.isEqualTo(b)),
5864
share()
5965
);
6066
};

src/centralized/order-builder.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ describe('getCentralizedExchangeOrder$', () => {
134134
expectedAssetToTradeOnCEX,
135135
store
136136
);
137-
expect(store.resetLastOrderUpdatePrice).toHaveBeenCalledTimes(2);
137+
expect(store.resetLastOrderUpdatePrice).toHaveBeenCalledTimes(4);
138138
});
139139

140140
it('accumulates buy and sell orders for BTCUSDT', () => {
@@ -186,6 +186,6 @@ describe('getCentralizedExchangeOrder$', () => {
186186
expectedAssetToTradeOnCEX,
187187
store
188188
);
189-
expect(store.resetLastOrderUpdatePrice).toHaveBeenCalledTimes(2);
189+
expect(store.resetLastOrderUpdatePrice).toHaveBeenCalledTimes(4);
190190
});
191191
});

src/centralized/order-builder.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import BigNumber from 'bignumber.js';
22
import { merge, Observable, of } from 'rxjs';
3-
import { filter, map, repeat, take, tap, mergeMap } from 'rxjs/operators';
3+
import { filter, map, mergeMap, repeat, take } from 'rxjs/operators';
44
import { Config } from '../config';
5-
import { OrderSide, Asset } from '../constants';
5+
import { Asset, OrderSide } from '../constants';
66
import { Logger } from '../logger';
77
import {
88
GetOpenDEXswapSuccessParams,
@@ -82,10 +82,12 @@ const getOrderBuilder$ = ({
8282
// accumulate OpenDEX order fills when receiving
8383
// quote asset
8484
accumulateOrderFillsForBaseAssetReceived(config),
85-
tap((quantity: BigNumber) => {
85+
mergeMap((quantity: BigNumber) => {
8686
logger.info(
8787
`Swap success. Accumulated ${assetToTradeOnCEX} quantity: ${quantity.toFixed()}`
8888
);
89+
store.resetLastOrderUpdatePrice();
90+
return of(quantity);
8991
}),
9092
// filter based on minimum CEX order quantity
9193
filter(quantityAboveMinimum(assetToTradeOnCEX)),

src/opendex/complete.spec.ts

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -75,23 +75,4 @@ describe('getOpenDEXcomplete$', () => {
7575
};
7676
assertGetOpenDEXcomplete(inputEvents, expected, inputValues);
7777
});
78-
79-
test('filters by shouldCreateOpenDEXorders', () => {
80-
const inputEvents = {
81-
tradeInfo$: 'a ^ 1000ms a 1500ms a 500ms b',
82-
getOpenDEXorders$: '1s a|',
83-
};
84-
const inputValues = {
85-
tradeInfo$: {
86-
a: {
87-
price: new BigNumber('10000'),
88-
},
89-
b: {
90-
price: new BigNumber('10010.1'),
91-
},
92-
},
93-
};
94-
const expected = '2s a 2001ms a';
95-
assertGetOpenDEXcomplete(inputEvents, expected, inputValues);
96-
});
9778
});

src/opendex/complete.ts

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { BigNumber } from 'bignumber.js';
22
import { Exchange } from 'ccxt';
3-
import { empty, Observable, of } from 'rxjs';
4-
import { exhaustMap, mergeMap, take } from 'rxjs/operators';
3+
import { Observable } from 'rxjs';
4+
import { exhaustMap } from 'rxjs/operators';
55
import { getCentralizedExchangeAssets$ } from '../centralized/assets';
66
import { Config } from '../config';
77
import { Loggers } from '../logger';
@@ -38,6 +38,7 @@ type GetOpenDEXcompleteParams = {
3838
tradeInfoToOpenDEXorders,
3939
getXudClient$,
4040
createXudOrder$,
41+
store,
4142
}: CreateOpenDEXordersParams) => Observable<boolean>;
4243
centralizedExchangePrice$: Observable<BigNumber>;
4344
store: ArbyStore;
@@ -76,30 +77,16 @@ const getOpenDEXcomplete$ = ({
7677
// is already in progress
7778
exhaustMap((tradeInfo: TradeInfo) => {
7879
const getTradeInfo = () => tradeInfo;
79-
return store.selectState('lastOrderUpdatePrice').pipe(
80-
take(1),
81-
mergeMap((lastPriceUpdate: BigNumber) => {
82-
if (shouldCreateOpenDEXorders(tradeInfo.price, lastPriceUpdate)) {
83-
// create orders based on latest trade info
84-
return createOpenDEXorders$({
85-
config,
86-
logger: loggers.opendex,
87-
getTradeInfo,
88-
getXudClient$,
89-
createXudOrder$,
90-
tradeInfoToOpenDEXorders,
91-
}).pipe(
92-
mergeMap(() => {
93-
// store the last price update
94-
store.updateLastOrderUpdatePrice(tradeInfo.price);
95-
return of(true);
96-
})
97-
);
98-
}
99-
// do nothing in case orders do not need updating
100-
return empty();
101-
})
102-
);
80+
return createOpenDEXorders$({
81+
config,
82+
logger: loggers.opendex,
83+
getTradeInfo,
84+
getXudClient$,
85+
createXudOrder$,
86+
tradeInfoToOpenDEXorders,
87+
store,
88+
shouldCreateOpenDEXorders,
89+
});
10390
})
10491
);
10592
};

src/opendex/create-orders.spec.ts

Lines changed: 106 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import { Observable } from 'rxjs';
33
import { TestScheduler } from 'rxjs/testing';
44
import { XudClient } from '../proto/xudrpc_grpc_pb';
55
import { PlaceOrderResponse } from '../proto/xudrpc_pb';
6-
import { getLoggers, testConfig } from '../test-utils';
6+
import { getArbyStore, ArbyStore } from '../store';
7+
import { getLoggers, testConfig, TestError } from '../test-utils';
78
import { TradeInfo } from '../trade/info';
89
import { createOpenDEXorders$, OpenDEXorders } from './create-orders';
9-
import { TestError } from '../test-utils';
10+
import BigNumber from 'bignumber.js';
1011

1112
let testScheduler: TestScheduler;
1213
const testSchedulerSetup = () => {
@@ -21,32 +22,40 @@ type CreateOpenDEXordersInputEvents = {
2122
replaceXudOrder$: string;
2223
};
2324

24-
const assertCreateOpenDEXorders = (
25-
inputEvents: CreateOpenDEXordersInputEvents,
26-
expected: string,
25+
const assertCreateOpenDEXorders = ({
26+
expected,
27+
inputEvents,
28+
inputErrors,
29+
store,
30+
shouldCreateOpenDEXorders,
31+
}: {
32+
inputEvents: CreateOpenDEXordersInputEvents;
33+
expected: string;
2734
inputErrors?: {
2835
xudOrder$?: TestError;
2936
replaceXudOrder$?: TestError;
30-
}
31-
) => {
37+
};
38+
store?: ArbyStore;
39+
shouldCreateOpenDEXorders?: () => boolean;
40+
}) => {
3241
testScheduler.run(helpers => {
3342
const { cold, expectObservable } = helpers;
3443
const getTradeInfo = (): TradeInfo => {
3544
return ('mock trade info' as unknown) as TradeInfo;
3645
};
3746
const createXudOrder$ = (createOrderParams: any) => {
3847
if (createOrderParams.replaceOrderId) {
39-
return cold(
48+
return (cold(
4049
inputEvents.replaceXudOrder$,
41-
{},
50+
{ a: 'order-response' },
4251
inputErrors?.replaceXudOrder$
43-
) as Observable<PlaceOrderResponse>;
52+
) as unknown) as Observable<PlaceOrderResponse>;
4453
} else {
45-
return cold(
54+
return (cold(
4655
inputEvents.xudOrder$,
47-
{},
56+
{ a: 'order-response' },
4857
inputErrors?.xudOrder$
49-
) as Observable<PlaceOrderResponse>;
58+
) as unknown) as Observable<PlaceOrderResponse>;
5059
}
5160
};
5261
const getXudClient$ = () => {
@@ -62,6 +71,10 @@ const assertCreateOpenDEXorders = (
6271
tradeInfoToOpenDEXorders,
6372
logger: getLoggers().global,
6473
config: testConfig(),
74+
store: store ? store : getArbyStore(),
75+
shouldCreateOpenDEXorders: shouldCreateOpenDEXorders
76+
? shouldCreateOpenDEXorders
77+
: () => true,
6578
});
6679
expectObservable(createOrders$).toBe(expected, {
6780
a: true,
@@ -79,17 +92,86 @@ describe('createOpenDEXorders$', () => {
7992
xudOrder$: '',
8093
};
8194
const expected = '2s (a|)';
82-
assertCreateOpenDEXorders(inputEvents, expected);
95+
const store = {
96+
...getArbyStore(),
97+
...{ updateLastSellOrderUpdatePrice: jest.fn() },
98+
...{ updateLastBuyOrderUpdatePrice: jest.fn() },
99+
};
100+
assertCreateOpenDEXorders({ inputEvents, expected, store });
101+
expect(store.updateLastSellOrderUpdatePrice).toHaveBeenCalledTimes(1);
102+
expect(store.updateLastBuyOrderUpdatePrice).toHaveBeenCalledTimes(1);
103+
});
104+
105+
it('filters by shouldCreateOpenDEXorders', () => {
106+
const inputEvents = {
107+
xudClient$: '1s a',
108+
replaceXudOrder$: '1s (a|)',
109+
xudOrder$: '',
110+
};
111+
// it returns true immediately without attempting to create orders
112+
const expected = '1s (a|)';
113+
const arbyStore = getArbyStore();
114+
const lastBuyOrderUpdatePrice = new BigNumber('123');
115+
const lastSellOrderUpdatePrice = new BigNumber('321');
116+
arbyStore.updateLastBuyOrderUpdatePrice(lastBuyOrderUpdatePrice);
117+
arbyStore.updateLastSellOrderUpdatePrice(lastSellOrderUpdatePrice);
118+
const store = {
119+
...arbyStore,
120+
...{ updateLastSellOrderUpdatePrice: jest.fn() },
121+
...{ updateLastBuyOrderUpdatePrice: jest.fn() },
122+
};
123+
const stateChangesSpy = jest.spyOn(store, 'stateChanges');
124+
const shouldCreateOpenDEXorders = jest.fn(() => false);
125+
assertCreateOpenDEXorders({
126+
inputEvents,
127+
expected,
128+
store,
129+
shouldCreateOpenDEXorders,
130+
});
131+
expect(store.updateLastSellOrderUpdatePrice).toHaveBeenCalledTimes(0);
132+
expect(store.updateLastBuyOrderUpdatePrice).toHaveBeenCalledTimes(0);
133+
expect(stateChangesSpy).toHaveBeenCalledTimes(1);
134+
expect(shouldCreateOpenDEXorders).toHaveBeenCalledTimes(2);
135+
expect(shouldCreateOpenDEXorders).toHaveBeenCalledWith(
136+
undefined,
137+
new BigNumber(lastBuyOrderUpdatePrice)
138+
);
139+
expect(shouldCreateOpenDEXorders).toHaveBeenCalledWith(
140+
undefined,
141+
new BigNumber(lastSellOrderUpdatePrice)
142+
);
83143
});
84144

85-
it('throws if unknown error for repace order', () => {
145+
it('will not update lastOrderUpdatePrice when orders not created', () => {
146+
const inputEvents = {
147+
xudClient$: '1s a',
148+
replaceXudOrder$: '1s (b|)',
149+
xudOrder$: '',
150+
};
151+
const expected = '2s (a|)';
152+
const store = {
153+
...getArbyStore(),
154+
...{ updateLastSellOrderUpdatePrice: jest.fn() },
155+
...{ updateLastBuyOrderUpdatePrice: jest.fn() },
156+
};
157+
assertCreateOpenDEXorders({ inputEvents, expected, store });
158+
expect(store.updateLastSellOrderUpdatePrice).toHaveBeenCalledTimes(0);
159+
expect(store.updateLastBuyOrderUpdatePrice).toHaveBeenCalledTimes(0);
160+
});
161+
162+
it('throws if unknown error for replace order', () => {
86163
const inputEvents = {
87164
xudClient$: '1s a',
88165
replaceXudOrder$: '1s #',
89166
xudOrder$: '',
90167
};
91168
const expected = '2s #';
92-
assertCreateOpenDEXorders(inputEvents, expected);
169+
const store = {
170+
...getArbyStore(),
171+
...{ updateLastOrderUpdatePrice: jest.fn() },
172+
};
173+
assertCreateOpenDEXorders({ inputEvents, expected, store });
174+
expect(store.updateLastOrderUpdatePrice).toHaveBeenCalledTimes(0);
93175
});
94176

95177
it('retries without replaceOrderId if grpc.NOT_FOUND error', () => {
@@ -105,6 +187,13 @@ describe('createOpenDEXorders$', () => {
105187
},
106188
};
107189
const expected = '3s (a|)';
108-
assertCreateOpenDEXorders(inputEvents, expected, inputErrors);
190+
const store = {
191+
...getArbyStore(),
192+
...{ updateLastSellOrderUpdatePrice: jest.fn() },
193+
...{ updateLastBuyOrderUpdatePrice: jest.fn() },
194+
};
195+
assertCreateOpenDEXorders({ inputEvents, expected, inputErrors, store });
196+
expect(store.updateLastSellOrderUpdatePrice).toHaveBeenCalledTimes(1);
197+
expect(store.updateLastBuyOrderUpdatePrice).toHaveBeenCalledTimes(1);
109198
});
110199
});

0 commit comments

Comments
 (0)