@quantform/core 0.5.15 → 0.5.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/dist/adapter/adapter-aggregate.d.ts +5 -4
  2. package/dist/adapter/adapter-aggregate.js +4 -4
  3. package/dist/adapter/adapter-aggregate.js.map +1 -1
  4. package/dist/adapter/adapter.d.ts +4 -15
  5. package/dist/adapter/adapter.js.map +1 -1
  6. package/dist/adapter/backtester/backtester-adapter.d.ts +3 -3
  7. package/dist/adapter/backtester/backtester-adapter.js +4 -4
  8. package/dist/adapter/backtester/backtester-adapter.js.map +1 -1
  9. package/dist/adapter/backtester/backtester-adapter.spec.js +7 -6
  10. package/dist/adapter/backtester/backtester-adapter.spec.js.map +1 -1
  11. package/dist/adapter/backtester/backtester-cursor.d.ts +4 -4
  12. package/dist/adapter/backtester/backtester-cursor.js.map +1 -1
  13. package/dist/adapter/backtester/backtester-cursor.spec.js +19 -17
  14. package/dist/adapter/backtester/backtester-cursor.spec.js.map +1 -1
  15. package/dist/adapter/backtester/backtester-streamer.d.ts +0 -1
  16. package/dist/adapter/backtester/backtester-streamer.js +4 -14
  17. package/dist/adapter/backtester/backtester-streamer.js.map +1 -1
  18. package/dist/adapter/backtester/backtester-streamer.spec.js +12 -11
  19. package/dist/adapter/backtester/backtester-streamer.spec.js.map +1 -1
  20. package/dist/adapter/paper/engine/paper-engine.js +20 -19
  21. package/dist/adapter/paper/engine/paper-engine.js.map +1 -1
  22. package/dist/adapter/paper/engine/paper-engine.spec.js +19 -19
  23. package/dist/adapter/paper/engine/paper-engine.spec.js.map +1 -1
  24. package/dist/adapter/paper/paper-adapter.d.ts +4 -3
  25. package/dist/adapter/paper/paper-adapter.js +8 -7
  26. package/dist/adapter/paper/paper-adapter.js.map +1 -1
  27. package/dist/adapter/paper/paper-adapter.spec.js +5 -4
  28. package/dist/adapter/paper/paper-adapter.spec.js.map +1 -1
  29. package/dist/cli/pull.js +5 -10
  30. package/dist/cli/pull.js.map +1 -1
  31. package/dist/domain/asset.d.ts +4 -4
  32. package/dist/domain/asset.js +4 -7
  33. package/dist/domain/asset.js.map +1 -1
  34. package/dist/domain/asset.spec.js +4 -4
  35. package/dist/domain/asset.spec.js.map +1 -1
  36. package/dist/domain/balance.d.ts +9 -9
  37. package/dist/domain/balance.js +16 -15
  38. package/dist/domain/balance.js.map +1 -1
  39. package/dist/domain/balance.spec.js +37 -36
  40. package/dist/domain/balance.spec.js.map +1 -1
  41. package/dist/domain/candle.d.ts +8 -8
  42. package/dist/domain/candle.js +3 -2
  43. package/dist/domain/candle.js.map +1 -1
  44. package/dist/domain/candle.operator.d.ts +3 -2
  45. package/dist/domain/candle.operator.js.map +1 -1
  46. package/dist/domain/candle.operator.spec.js +43 -42
  47. package/dist/domain/candle.operator.spec.js.map +1 -1
  48. package/dist/domain/candle.spec.js +11 -11
  49. package/dist/domain/candle.spec.js.map +1 -1
  50. package/dist/domain/commission.d.ts +10 -9
  51. package/dist/domain/commission.js +5 -5
  52. package/dist/domain/commission.js.map +1 -1
  53. package/dist/domain/commission.spec.js +13 -12
  54. package/dist/domain/commission.spec.js.map +1 -1
  55. package/dist/domain/error.d.ts +2 -1
  56. package/dist/domain/error.js +1 -1
  57. package/dist/domain/error.js.map +1 -1
  58. package/dist/domain/order.d.ts +13 -13
  59. package/dist/domain/order.js +10 -9
  60. package/dist/domain/order.js.map +1 -1
  61. package/dist/domain/order.operator.spec.js +6 -5
  62. package/dist/domain/order.operator.spec.js.map +1 -1
  63. package/dist/domain/order.spec.js +11 -10
  64. package/dist/domain/order.spec.js.map +1 -1
  65. package/dist/domain/orderbook.d.ts +18 -6
  66. package/dist/domain/orderbook.js +5 -4
  67. package/dist/domain/orderbook.js.map +1 -1
  68. package/dist/domain/position.d.ts +7 -7
  69. package/dist/domain/position.js +2 -2
  70. package/dist/domain/position.js.map +1 -1
  71. package/dist/domain/position.operator.d.ts +3 -2
  72. package/dist/domain/position.operator.js +3 -3
  73. package/dist/domain/position.operator.js.map +1 -1
  74. package/dist/domain/position.operator.spec.js +4 -3
  75. package/dist/domain/position.operator.spec.js.map +1 -1
  76. package/dist/domain/position.spec.js +10 -9
  77. package/dist/domain/position.spec.js.map +1 -1
  78. package/dist/domain/session.js +1 -1
  79. package/dist/domain/session.js.map +1 -1
  80. package/dist/domain/session.spec.js +1 -1
  81. package/dist/domain/session.spec.js.map +1 -1
  82. package/dist/domain/trade.d.ts +3 -3
  83. package/dist/indicator/atr.d.ts +2 -1
  84. package/dist/indicator/atr.js +3 -3
  85. package/dist/indicator/atr.js.map +1 -1
  86. package/dist/indicator/cross.d.ts +3 -2
  87. package/dist/indicator/cross.js +13 -13
  88. package/dist/indicator/cross.js.map +1 -1
  89. package/dist/indicator/cross.spec.js +22 -21
  90. package/dist/indicator/cross.spec.js.map +1 -1
  91. package/dist/indicator/donchian.d.ts +3 -2
  92. package/dist/indicator/donchian.js.map +1 -1
  93. package/dist/indicator/drawdown.d.ts +2 -1
  94. package/dist/indicator/drawdown.js +3 -2
  95. package/dist/indicator/drawdown.js.map +1 -1
  96. package/dist/indicator/ema.d.ts +2 -1
  97. package/dist/indicator/ema.js +3 -2
  98. package/dist/indicator/ema.js.map +1 -1
  99. package/dist/indicator/ema.spec.js +3 -2
  100. package/dist/indicator/ema.spec.js.map +1 -1
  101. package/dist/indicator/envelope.d.ts +4 -3
  102. package/dist/indicator/envelope.js +4 -4
  103. package/dist/indicator/envelope.js.map +1 -1
  104. package/dist/indicator/index.d.ts +1 -1
  105. package/dist/indicator/index.js +1 -1
  106. package/dist/indicator/index.js.map +1 -1
  107. package/dist/indicator/macd.d.ts +2 -1
  108. package/dist/indicator/macd.js +1 -1
  109. package/dist/indicator/macd.js.map +1 -1
  110. package/dist/indicator/min-max.d.ts +4 -3
  111. package/dist/indicator/min-max.js +5 -4
  112. package/dist/indicator/min-max.js.map +1 -1
  113. package/dist/indicator/rma.d.ts +2 -1
  114. package/dist/indicator/rma.js +3 -2
  115. package/dist/indicator/rma.js.map +1 -1
  116. package/dist/indicator/sma.d.ts +2 -1
  117. package/dist/indicator/sma.js +4 -6
  118. package/dist/indicator/sma.js.map +1 -1
  119. package/dist/indicator/sma.spec.js +3 -2
  120. package/dist/indicator/sma.spec.js.map +1 -1
  121. package/dist/indicator/swma.d.ts +2 -1
  122. package/dist/indicator/swma.js +5 -1
  123. package/dist/indicator/swma.js.map +1 -1
  124. package/dist/indicator/tma.d.ts +2 -1
  125. package/dist/indicator/tma.js.map +1 -1
  126. package/dist/indicator/tma.spec.js +3 -2
  127. package/dist/indicator/tma.spec.js.map +1 -1
  128. package/dist/indicator/trailing.d.ts +8 -7
  129. package/dist/indicator/trailing.js +12 -11
  130. package/dist/indicator/trailing.js.map +1 -1
  131. package/dist/indicator/trailing.spec.js +15 -14
  132. package/dist/indicator/trailing.spec.js.map +1 -1
  133. package/dist/indicator/true-range.d.ts +4 -0
  134. package/dist/indicator/true-range.js +20 -0
  135. package/dist/indicator/true-range.js.map +1 -0
  136. package/dist/indicator/{truerange.spec.d.ts → true-range.spec.d.ts} +0 -0
  137. package/dist/indicator/true-range.spec.js +28 -0
  138. package/dist/indicator/true-range.spec.js.map +1 -0
  139. package/dist/indicator/window.d.ts +1 -1
  140. package/dist/indicator/window.js +1 -1
  141. package/dist/indicator/window.js.map +1 -1
  142. package/dist/indicator/wma.d.ts +2 -1
  143. package/dist/indicator/wma.js +6 -5
  144. package/dist/indicator/wma.js.map +1 -1
  145. package/dist/indicator/wma.spec.js +3 -2
  146. package/dist/indicator/wma.spec.js.map +1 -1
  147. package/dist/shared/collections.d.ts +16 -0
  148. package/dist/shared/collections.js +83 -1
  149. package/dist/shared/collections.js.map +1 -1
  150. package/dist/shared/collections.spec.d.ts +1 -0
  151. package/dist/shared/collections.spec.js +28 -0
  152. package/dist/shared/collections.spec.js.map +1 -0
  153. package/dist/shared/decimals.d.ts +15 -6
  154. package/dist/shared/decimals.js +21 -34
  155. package/dist/shared/decimals.js.map +1 -1
  156. package/dist/shared/decimals.spec.js +17 -22
  157. package/dist/shared/decimals.spec.js.map +1 -1
  158. package/dist/shared/index.d.ts +1 -0
  159. package/dist/shared/index.js +1 -0
  160. package/dist/shared/index.js.map +1 -1
  161. package/dist/shared/pipe.d.ts +4 -0
  162. package/dist/shared/pipe.js +9 -0
  163. package/dist/shared/pipe.js.map +1 -0
  164. package/dist/storage/feed.d.ts +8 -4
  165. package/dist/storage/feed.js +55 -18
  166. package/dist/storage/feed.js.map +1 -1
  167. package/dist/store/store-balance.event.d.ts +6 -6
  168. package/dist/store/store-balance.event.js +2 -2
  169. package/dist/store/store-balance.event.js.map +1 -1
  170. package/dist/store/store-balance.event.spec.js +4 -4
  171. package/dist/store/store-balance.event.spec.js.map +1 -1
  172. package/dist/store/store-instrument.event.js.map +1 -1
  173. package/dist/store/store-instrument.event.spec.js +1 -1
  174. package/dist/store/store-instrument.event.spec.js.map +1 -1
  175. package/dist/store/store-order.event.d.ts +3 -3
  176. package/dist/store/store-order.event.js.map +1 -1
  177. package/dist/store/store-order.event.spec.js +1 -1
  178. package/dist/store/store-order.event.spec.js.map +1 -1
  179. package/dist/store/store-orderbook.event.d.ts +4 -6
  180. package/dist/store/store-orderbook.event.js +13 -13
  181. package/dist/store/store-orderbook.event.js.map +1 -1
  182. package/dist/store/store-orderbook.event.spec.d.ts +1 -0
  183. package/dist/store/store-orderbook.event.spec.js +28 -0
  184. package/dist/store/store-orderbook.event.spec.js.map +1 -0
  185. package/dist/store/store-position.event.d.ts +4 -4
  186. package/dist/store/store-position.event.js +23 -11
  187. package/dist/store/store-position.event.js.map +1 -1
  188. package/dist/store/store-trade.event.d.ts +4 -4
  189. package/dist/store/store-trade.event.js +2 -2
  190. package/dist/store/store-trade.event.js.map +1 -1
  191. package/dist/store/store-trade.event.spec.js +8 -8
  192. package/dist/store/store-trade.event.spec.js.map +1 -1
  193. package/dist/store/store.spec.js +15 -15
  194. package/dist/store/store.spec.js.map +1 -1
  195. package/dist/tsconfig.tsbuildinfo +1 -1
  196. package/jestconfig.json +1 -0
  197. package/package.json +3 -1
  198. package/src/adapter/adapter-aggregate.ts +16 -7
  199. package/src/adapter/adapter.ts +15 -18
  200. package/src/adapter/backtester/backtester-adapter.spec.ts +18 -9
  201. package/src/adapter/backtester/backtester-adapter.ts +14 -5
  202. package/src/adapter/backtester/backtester-cursor.spec.ts +20 -18
  203. package/src/adapter/backtester/backtester-cursor.ts +5 -5
  204. package/src/adapter/backtester/backtester-streamer.spec.ts +14 -13
  205. package/src/adapter/backtester/backtester-streamer.ts +5 -22
  206. package/src/adapter/paper/engine/paper-engine.spec.ts +30 -23
  207. package/src/adapter/paper/engine/paper-engine.ts +28 -21
  208. package/src/adapter/paper/paper-adapter.spec.ts +15 -6
  209. package/src/adapter/paper/paper-adapter.ts +20 -8
  210. package/src/cli/pull.ts +6 -5
  211. package/src/domain/asset.spec.ts +4 -4
  212. package/src/domain/asset.ts +7 -14
  213. package/src/domain/balance.spec.ts +37 -36
  214. package/src/domain/balance.ts +29 -29
  215. package/src/domain/candle.operator.spec.ts +43 -42
  216. package/src/domain/candle.operator.ts +4 -3
  217. package/src/domain/candle.spec.ts +13 -12
  218. package/src/domain/candle.ts +9 -9
  219. package/src/domain/commission.spec.ts +13 -12
  220. package/src/domain/commission.ts +13 -11
  221. package/src/domain/error.ts +5 -3
  222. package/src/domain/order.operator.spec.ts +6 -5
  223. package/src/domain/order.spec.ts +11 -10
  224. package/src/domain/order.ts +25 -22
  225. package/src/domain/orderbook.ts +14 -8
  226. package/src/domain/position.operator.spec.ts +5 -4
  227. package/src/domain/position.operator.ts +8 -5
  228. package/src/domain/position.spec.ts +10 -9
  229. package/src/domain/position.ts +8 -8
  230. package/src/domain/session.spec.ts +3 -3
  231. package/src/domain/session.ts +1 -3
  232. package/src/domain/trade.ts +3 -3
  233. package/src/indicator/atr.ts +6 -5
  234. package/src/indicator/cross.spec.ts +32 -47
  235. package/src/indicator/cross.ts +18 -10
  236. package/src/indicator/donchian.ts +5 -4
  237. package/src/indicator/drawdown.ts +7 -5
  238. package/src/indicator/ema.spec.ts +4 -3
  239. package/src/indicator/ema.ts +7 -6
  240. package/src/indicator/envelope.ts +11 -6
  241. package/src/indicator/index.ts +1 -1
  242. package/src/indicator/macd.ts +5 -4
  243. package/src/indicator/min-max.ts +10 -7
  244. package/src/indicator/rma.ts +7 -6
  245. package/src/indicator/sma.spec.ts +4 -3
  246. package/src/indicator/sma.ts +9 -8
  247. package/src/indicator/swma.ts +8 -4
  248. package/src/indicator/tma.spec.ts +4 -3
  249. package/src/indicator/tma.ts +4 -3
  250. package/src/indicator/trailing.spec.ts +29 -16
  251. package/src/indicator/trailing.ts +22 -16
  252. package/src/indicator/true-range.spec.ts +32 -0
  253. package/src/indicator/true-range.ts +32 -0
  254. package/src/indicator/window.ts +3 -3
  255. package/src/indicator/wma.spec.ts +4 -3
  256. package/src/indicator/wma.ts +8 -7
  257. package/src/shared/collections.spec.ts +30 -0
  258. package/src/shared/collections.ts +106 -0
  259. package/src/shared/decimals.spec.ts +18 -24
  260. package/src/shared/decimals.ts +22 -55
  261. package/src/shared/index.ts +1 -0
  262. package/src/shared/pipe.ts +12 -0
  263. package/src/storage/feed.ts +89 -34
  264. package/src/store/store-balance.event.spec.ts +5 -5
  265. package/src/store/store-balance.event.ts +6 -6
  266. package/src/store/store-instrument.event.spec.ts +2 -2
  267. package/src/store/store-instrument.event.ts +2 -2
  268. package/src/store/store-order.event.spec.ts +2 -2
  269. package/src/store/store-order.event.ts +2 -2
  270. package/src/store/store-orderbook.event.spec.ts +37 -0
  271. package/src/store/store-orderbook.event.ts +13 -13
  272. package/src/store/store-position.event.ts +27 -15
  273. package/src/store/store-trade.event.spec.ts +9 -9
  274. package/src/store/store-trade.event.ts +5 -5
  275. package/src/store/store.spec.ts +16 -16
  276. package/dist/indicator/truerange.d.ts +0 -3
  277. package/dist/indicator/truerange.js +0 -19
  278. package/dist/indicator/truerange.js.map +0 -1
  279. package/dist/indicator/truerange.spec.js +0 -28
  280. package/dist/indicator/truerange.spec.js.map +0 -1
  281. package/src/indicator/truerange.spec.ts +0 -32
  282. package/src/indicator/truerange.ts +0 -31
@@ -1,6 +1,6 @@
1
1
  import { Candle, InstrumentSelector, Order } from '../domain';
2
2
  import { now, timestamp } from '../shared';
3
- import { Cache, Feed } from '../storage';
3
+ import { Cache, StorageEvent } from '../storage';
4
4
  import { Store } from '../store';
5
5
  import { PaperAdapter } from './paper';
6
6
  import { PaperEngine } from './paper/engine/paper-engine';
@@ -13,26 +13,14 @@ export const DefaultTimeProvider = {
13
13
  timestamp: () => now()
14
14
  };
15
15
 
16
+ export type FeedAsyncCallback = (timestamp: number, chunk: StorageEvent[]) => void;
17
+
16
18
  export type AdapterFactory = (
17
19
  timeProvider: AdapterTimeProvider,
18
20
  store: Store,
19
21
  cache: Cache
20
22
  ) => Adapter;
21
23
 
22
- export type HistoryQuery = {
23
- instrument: InstrumentSelector;
24
- timeframe: number;
25
- length: number;
26
- };
27
-
28
- export type FeedQuery = {
29
- instrument: InstrumentSelector;
30
- from: timestamp;
31
- to: timestamp;
32
- destination: Feed;
33
- callback: (timestamp: number) => void;
34
- };
35
-
36
24
  /**
37
25
  * Base adapter class, you should derive your own adapter from this class.
38
26
  * @abstract
@@ -78,9 +66,18 @@ export abstract class Adapter {
78
66
  */
79
67
  abstract cancel(order: Order): Promise<void>;
80
68
 
81
- abstract history(query: HistoryQuery): Promise<Candle[]>;
82
-
83
- abstract feed(query: FeedQuery): Promise<void>;
69
+ abstract history(
70
+ instrument: InstrumentSelector,
71
+ timeframe: number,
72
+ length: number
73
+ ): Promise<Candle[]>;
74
+
75
+ abstract feed(
76
+ instrument: InstrumentSelector,
77
+ from: timestamp,
78
+ to: timestamp,
79
+ callback: FeedAsyncCallback
80
+ ): Promise<void>;
84
81
 
85
82
  abstract createPaperEngine(adapter: PaperAdapter): PaperEngine;
86
83
  }
@@ -6,14 +6,14 @@ import {
6
6
  InstrumentSelector,
7
7
  Order
8
8
  } from '../../domain';
9
+ import { d, timestamp } from '../../shared';
9
10
  import { Cache, Feed, InMemoryStorage } from '../../storage';
10
- import { InstrumentPatchEvent, Store } from '../../store';
11
+ import { InstrumentPatchEvent, Store, TradePatchEvent } from '../../store';
11
12
  import {
12
13
  Adapter,
13
14
  AdapterTimeProvider,
14
15
  DefaultTimeProvider,
15
- FeedQuery,
16
- HistoryQuery
16
+ FeedAsyncCallback
17
17
  } from '../adapter';
18
18
  import { PaperEngine } from '../paper/engine/paper-engine';
19
19
  import { PaperAdapter } from '../paper/paper-adapter';
@@ -39,10 +39,19 @@ class DefaultAdapter extends Adapter {
39
39
  cancel(order: Order): Promise<void> {
40
40
  throw new Error('Method not implemented.');
41
41
  }
42
- history(query: HistoryQuery): Promise<Candle[]> {
42
+ history(
43
+ instrument: InstrumentSelector,
44
+ timeframe: number,
45
+ length: number
46
+ ): Promise<Candle[]> {
43
47
  throw new Error('Method not implemented.');
44
48
  }
45
- feed(query: FeedQuery): Promise<void> {
49
+ feed(
50
+ instrument: InstrumentSelector,
51
+ from: timestamp,
52
+ to: timestamp,
53
+ callback: FeedAsyncCallback
54
+ ): Promise<void> {
46
55
  throw new Error('Method not implemented.');
47
56
  }
48
57
  name = 'default';
@@ -65,7 +74,7 @@ class DefaultAdapter extends Adapter {
65
74
  this.timestamp(),
66
75
  base,
67
76
  quote,
68
- new Commission(0.1, 0.1),
77
+ new Commission(d(0.1), d(0.1)),
69
78
  'btc-usdt'
70
79
  )
71
80
  );
@@ -104,15 +113,15 @@ describe('BacktesterAdapter', () => {
104
113
  {
105
114
  onBacktestCompleted: () => {
106
115
  expect(store.snapshot.timestamp).toEqual(1);
107
- expect(store.snapshot.trade.get(instrument.id).rate).toEqual(100);
108
- expect(store.snapshot.trade.get(instrument.id).quantity).toEqual(10);
116
+ expect(store.snapshot.trade.get(instrument.id).rate).toEqual(d(100));
117
+ expect(store.snapshot.trade.get(instrument.id).quantity).toEqual(d(10));
109
118
 
110
119
  done();
111
120
  }
112
121
  }
113
122
  );
114
123
 
115
- feed.save(instrument, [new Candle(1, 100, 100, 100, 100, 10)]);
124
+ feed.save([new TradePatchEvent(instrument, d(100), d(10), 1)]);
116
125
 
117
126
  const sut = new BacktesterAdapter(adapter, streamer, store);
118
127
 
@@ -2,7 +2,7 @@ import { Candle, InstrumentSelector, Order } from '../../domain';
2
2
  import { timestamp } from '../../shared';
3
3
  import { InstrumentSubscriptionEvent, Store } from '../../store';
4
4
  import { Adapter } from '..';
5
- import { AdapterFactory, FeedQuery, HistoryQuery } from '../adapter';
5
+ import { AdapterFactory, FeedAsyncCallback } from '../adapter';
6
6
  import { PaperAdapter, PaperOptions } from '../paper';
7
7
  import { PaperEngine } from '../paper/engine/paper-engine';
8
8
  import { BacktesterStreamer } from './backtester-streamer';
@@ -69,18 +69,27 @@ export class BacktesterAdapter extends Adapter {
69
69
  return this.decoratedAdapter.cancel(order);
70
70
  }
71
71
 
72
- async history(query: HistoryQuery): Promise<Candle[]> {
72
+ async history(
73
+ instrument: InstrumentSelector,
74
+ timeframe: number,
75
+ length: number
76
+ ): Promise<Candle[]> {
73
77
  this.streamer.stop();
74
78
 
75
- const response = await this.decoratedAdapter.history(query);
79
+ const response = await this.decoratedAdapter.history(instrument, timeframe, length);
76
80
 
77
81
  this.streamer.tryContinue();
78
82
 
79
83
  return response;
80
84
  }
81
85
 
82
- feed(query: FeedQuery): Promise<void> {
83
- return this.decoratedAdapter.feed(query);
86
+ feed(
87
+ instrument: InstrumentSelector,
88
+ from: timestamp,
89
+ to: timestamp,
90
+ callback: FeedAsyncCallback
91
+ ): Promise<void> {
92
+ return this.decoratedAdapter.feed(instrument, from, to, callback);
84
93
  }
85
94
 
86
95
  createPaperEngine(adapter: PaperAdapter): PaperEngine {
@@ -1,5 +1,7 @@
1
- import { Candle, instrumentOf } from '../../domain';
1
+ import { instrumentOf } from '../../domain';
2
+ import { d } from '../../shared';
2
3
  import { Feed, InMemoryStorage } from '../../storage';
4
+ import { TradePatchEvent } from '../../store';
3
5
  import { BacktesterCursor } from './backtester-cursor';
4
6
 
5
7
  describe('BacktesterCursor', () => {
@@ -8,35 +10,35 @@ describe('BacktesterCursor', () => {
8
10
  const feed = new Feed(new InMemoryStorage());
9
11
  const cursor = new BacktesterCursor(instrument, feed);
10
12
 
11
- feed.save(instrument, [
12
- new Candle(1, 1, 1, 1, 1),
13
- new Candle(2, 2, 2, 2, 2),
14
- new Candle(3, 3, 3, 3, 3),
15
- new Candle(4, 4, 4, 4, 4),
16
- new Candle(5, 5, 5, 5, 5),
17
- new Candle(6, 6, 6, 6, 6),
18
- new Candle(7, 7, 7, 7, 7),
19
- new Candle(8, 8, 8, 8, 8)
13
+ feed.save([
14
+ new TradePatchEvent(instrument, d(1), d(1), 1),
15
+ new TradePatchEvent(instrument, d(2), d(2), 2),
16
+ new TradePatchEvent(instrument, d(3), d(3), 3),
17
+ new TradePatchEvent(instrument, d(4), d(4), 4),
18
+ new TradePatchEvent(instrument, d(5), d(5), 5),
19
+ new TradePatchEvent(instrument, d(6), d(6), 6),
20
+ new TradePatchEvent(instrument, d(7), d(7), 7),
21
+ new TradePatchEvent(instrument, d(8), d(8), 8)
20
22
  ]);
21
23
 
22
24
  await cursor.fetchNextPage(0, 4);
23
25
 
24
- expect(cursor.dequeue().open).toEqual(1);
25
- expect(cursor.dequeue().open).toEqual(2);
26
- expect(cursor.dequeue().open).toEqual(3);
26
+ expect(cursor.dequeue()).toEqual(new TradePatchEvent(instrument, d(1), d(1), 1));
27
+ expect(cursor.dequeue()).toEqual(new TradePatchEvent(instrument, d(2), d(2), 2));
28
+ expect(cursor.dequeue()).toEqual(new TradePatchEvent(instrument, d(3), d(3), 3));
27
29
  expect(cursor.peek()).toEqual(undefined);
28
30
 
29
31
  await cursor.fetchNextPage(3, 7);
30
32
 
31
- expect(cursor.dequeue().open).toEqual(4);
32
- expect(cursor.dequeue().open).toEqual(5);
33
- expect(cursor.dequeue().open).toEqual(6);
33
+ expect(cursor.dequeue()).toEqual(new TradePatchEvent(instrument, d(4), d(4), 4));
34
+ expect(cursor.dequeue()).toEqual(new TradePatchEvent(instrument, d(5), d(5), 5));
35
+ expect(cursor.dequeue()).toEqual(new TradePatchEvent(instrument, d(6), d(6), 6));
34
36
  expect(cursor.peek()).toEqual(undefined);
35
37
 
36
38
  await cursor.fetchNextPage(6, 10);
37
39
 
38
- expect(cursor.dequeue().open).toEqual(7);
39
- expect(cursor.dequeue().open).toEqual(8);
40
+ expect(cursor.dequeue()).toEqual(new TradePatchEvent(instrument, d(7), d(7), 7));
41
+ expect(cursor.dequeue()).toEqual(new TradePatchEvent(instrument, d(8), d(8), 8));
40
42
  expect(cursor.peek()).toEqual(undefined);
41
43
  });
42
44
  });
@@ -1,10 +1,10 @@
1
- import { Candle, InstrumentSelector } from '../../domain';
1
+ import { InstrumentSelector } from '../../domain';
2
2
  import { timestamp } from '../../shared';
3
- import { Feed } from '../../storage';
3
+ import { Feed, StorageEvent } from '../../storage';
4
4
  import { backtestPageNotEmpty } from '../error';
5
5
 
6
6
  export class BacktesterCursor {
7
- private page = new Array<Candle>();
7
+ private page = new Array<StorageEvent>();
8
8
  private pageIndex = 0;
9
9
  completed = false;
10
10
 
@@ -14,7 +14,7 @@ export class BacktesterCursor {
14
14
 
15
15
  constructor(readonly instrument: InstrumentSelector, private readonly feed: Feed) {}
16
16
 
17
- peek(): Candle {
17
+ peek(): StorageEvent {
18
18
  if (!this.page) {
19
19
  return undefined;
20
20
  }
@@ -22,7 +22,7 @@ export class BacktesterCursor {
22
22
  return this.page[this.pageIndex];
23
23
  }
24
24
 
25
- dequeue(): Candle {
25
+ dequeue(): StorageEvent {
26
26
  return this.page[this.pageIndex++];
27
27
  }
28
28
 
@@ -1,6 +1,7 @@
1
- import { Asset, Candle, Instrument } from '../../domain';
1
+ import { Asset, Instrument } from '../../domain';
2
+ import { d } from '../../shared';
2
3
  import { Feed, InMemoryStorage } from '../../storage';
3
- import { Store } from '../../store';
4
+ import { Store, TradePatchEvent } from '../../store';
4
5
  import { BacktesterStreamer } from './backtester-streamer';
5
6
 
6
7
  describe('BacktesterStreamer', () => {
@@ -29,8 +30,8 @@ describe('BacktesterStreamer', () => {
29
30
  const trade = store.snapshot.trade.get(instrument.id);
30
31
 
31
32
  expect(trade.timestamp).toEqual(8);
32
- expect(trade.rate).toEqual(8);
33
- expect(trade.quantity).toEqual(8);
33
+ expect(trade.rate).toEqual(d(8));
34
+ expect(trade.quantity).toEqual(d(8));
34
35
  expect(store.snapshot.timestamp).toEqual(8);
35
36
 
36
37
  done();
@@ -39,15 +40,15 @@ describe('BacktesterStreamer', () => {
39
40
  );
40
41
 
41
42
  feed
42
- .save(instrument, [
43
- new Candle(1, 1, 1, 1, 1, 1),
44
- new Candle(2, 2, 2, 2, 2, 2),
45
- new Candle(3, 3, 3, 3, 3, 3),
46
- new Candle(4, 4, 4, 4, 4, 4),
47
- new Candle(5, 5, 5, 5, 5, 5),
48
- new Candle(6, 6, 6, 6, 6, 6),
49
- new Candle(7, 7, 7, 7, 7, 7),
50
- new Candle(8, 8, 8, 8, 8, 8)
43
+ .save([
44
+ new TradePatchEvent(instrument, d(1), d(1), 1),
45
+ new TradePatchEvent(instrument, d(2), d(2), 2),
46
+ new TradePatchEvent(instrument, d(3), d(3), 3),
47
+ new TradePatchEvent(instrument, d(4), d(4), 4),
48
+ new TradePatchEvent(instrument, d(5), d(5), 5),
49
+ new TradePatchEvent(instrument, d(6), d(6), 6),
50
+ new TradePatchEvent(instrument, d(7), d(7), 7),
51
+ new TradePatchEvent(instrument, d(8), d(8), 8)
51
52
  ])
52
53
  .then(() => {
53
54
  streamer.subscribe(instrument);
@@ -1,7 +1,7 @@
1
1
  import { InstrumentSelector } from '../../domain';
2
2
  import { timestamp } from '../../shared';
3
3
  import { Feed } from '../../storage';
4
- import { OrderbookPatchEvent, Store, TradePatchEvent } from '../../store';
4
+ import { Store } from '../../store';
5
5
  import { AdapterTimeProvider } from '../adapter';
6
6
  import { BacktesterCursor } from './backtester-cursor';
7
7
 
@@ -118,37 +118,20 @@ export class BacktesterStreamer {
118
118
  return false;
119
119
  }
120
120
 
121
- const candle = cursor.peek();
122
- const instrument = cursor.instrument;
123
- const volume = candle.volume ?? 0;
121
+ const event = cursor.peek();
124
122
 
125
- this.timestamp = candle.timestamp;
123
+ this.timestamp = event.timestamp;
126
124
  this.sequence++;
127
125
 
128
- this.dispatch(candle.timestamp, instrument, candle.open, volume);
129
- this.dispatch(candle.timestamp, instrument, candle.high, volume);
130
- this.dispatch(candle.timestamp, instrument, candle.low, volume);
131
- this.dispatch(candle.timestamp, instrument, candle.close, volume);
126
+ this.store.dispatch(event);
132
127
 
133
- if (cursor.dequeue().timestamp != candle.timestamp) {
128
+ if (cursor.dequeue().timestamp != event.timestamp) {
134
129
  throw new Error('invalid event to consume');
135
130
  }
136
131
 
137
132
  return true;
138
133
  }
139
134
 
140
- private dispatch(
141
- timestamp: number,
142
- instrument: InstrumentSelector,
143
- rate: number,
144
- volume: number
145
- ) {
146
- this.store.dispatch(
147
- new TradePatchEvent(instrument, rate, volume, timestamp),
148
- new OrderbookPatchEvent(instrument, rate, volume, rate, volume, timestamp)
149
- );
150
- }
151
-
152
135
  private async current(from: timestamp, to: timestamp): Promise<BacktesterCursor> {
153
136
  for (const cursor of Object.values(this.cursor)) {
154
137
  if (cursor.size == 0 && !cursor.completed) {
@@ -1,5 +1,5 @@
1
1
  import { Asset, commissionPercentOf, Instrument, Order } from '../../../domain';
2
- import { now } from '../../../shared';
2
+ import { d, now } from '../../../shared';
3
3
  import {
4
4
  BalancePatchEvent,
5
5
  InstrumentPatchEvent,
@@ -17,57 +17,57 @@ describe('PaperEngine', () => {
17
17
  );
18
18
 
19
19
  const commission = commissionPercentOf({
20
- maker: 0.1,
21
- taker: 0.1
20
+ maker: d(0.1),
21
+ taker: d(0.1)
22
22
  });
23
23
 
24
24
  test('should open a new buy market order', () => {
25
25
  const store = new Store();
26
26
  const engine = new PaperEngine(store);
27
- const order = Order.market(instrument, 1.0);
27
+ const order = Order.market(instrument, d(1.0));
28
28
 
29
29
  store.dispatch(
30
30
  new InstrumentPatchEvent(now(), instrument.base, instrument.quote, commission, ''),
31
- new BalancePatchEvent(instrument.base, 1, 0, now()),
32
- new BalancePatchEvent(instrument.quote, 1000, 0, now())
31
+ new BalancePatchEvent(instrument.base, d(1), d.Zero, now()),
32
+ new BalancePatchEvent(instrument.quote, d(1000), d.Zero, now())
33
33
  );
34
34
 
35
35
  engine.open(order);
36
36
 
37
37
  expect(store.snapshot.order.asReadonlyArray().length).toEqual(1);
38
38
  expect(store.snapshot.order.get(instrument.id).get(order.id)).toEqual(order);
39
- expect(store.snapshot.balance.get(instrument.quote.id).free).toEqual(0);
40
- expect(store.snapshot.balance.get(instrument.quote.id).locked).toEqual(1000);
39
+ expect(store.snapshot.balance.get(instrument.quote.id).free).toEqual(d.Zero);
40
+ expect(store.snapshot.balance.get(instrument.quote.id).locked).toEqual(d(1000));
41
41
  });
42
42
 
43
43
  test('should open a new sell market order', () => {
44
44
  const store = new Store();
45
45
  const engine = new PaperEngine(store);
46
- const order = Order.market(instrument, -0.6);
46
+ const order = Order.market(instrument, d(-0.6));
47
47
 
48
48
  store.dispatch(
49
49
  new InstrumentPatchEvent(now(), instrument.base, instrument.quote, commission, ''),
50
- new BalancePatchEvent(instrument.base, 1, 0, now()),
51
- new BalancePatchEvent(instrument.quote, 1000, 0, now())
50
+ new BalancePatchEvent(instrument.base, d(1), d.Zero, now()),
51
+ new BalancePatchEvent(instrument.quote, d(1000), d.Zero, now())
52
52
  );
53
53
 
54
54
  engine.open(order);
55
55
 
56
56
  expect(store.snapshot.order.asReadonlyArray().length).toEqual(1);
57
57
  expect(store.snapshot.order.get(instrument.id).get(order.id)).toEqual(order);
58
- expect(store.snapshot.balance.get(instrument.base.id).free).toEqual(0.4);
59
- expect(store.snapshot.balance.get(instrument.base.id).locked).toEqual(0.6);
58
+ expect(store.snapshot.balance.get(instrument.base.id).free).toEqual(d(0.4));
59
+ expect(store.snapshot.balance.get(instrument.base.id).locked).toEqual(d(0.6));
60
60
  });
61
61
 
62
62
  test('should open and fill a new sell limit order', () => {
63
63
  const store = new Store();
64
64
  const engine = new PaperEngine(store);
65
- const order = Order.limit(instrument, -0.6, 100);
65
+ const order = Order.limit(instrument, d(-0.6), d(100));
66
66
 
67
67
  store.dispatch(
68
68
  new InstrumentPatchEvent(now(), instrument.base, instrument.quote, commission, ''),
69
- new BalancePatchEvent(instrument.base, 1, 0, now()),
70
- new BalancePatchEvent(instrument.quote, 1000, 0, now()),
69
+ new BalancePatchEvent(instrument.base, d(1), d.Zero, now()),
70
+ new BalancePatchEvent(instrument.quote, d(1000), d.Zero, now()),
71
71
  new InstrumentSubscriptionEvent(now(), instrument, true)
72
72
  );
73
73
 
@@ -77,16 +77,23 @@ describe('PaperEngine', () => {
77
77
  expect(store.snapshot.order.get(instrument.id).get(order.id).state).toEqual(
78
78
  'PENDING'
79
79
  );
80
- expect(store.snapshot.balance.get(instrument.base.id).free).toEqual(0.4);
81
- expect(store.snapshot.balance.get(instrument.base.id).locked).toEqual(0.6);
80
+ expect(store.snapshot.balance.get(instrument.base.id).free).toEqual(d(0.4));
81
+ expect(store.snapshot.balance.get(instrument.base.id).locked).toEqual(d(0.6));
82
82
 
83
- store.dispatch(new OrderbookPatchEvent(instrument, 102, 1, 101, 1, now()));
83
+ store.dispatch(
84
+ new OrderbookPatchEvent(
85
+ instrument,
86
+ { rate: d(102), quantity: d(1), next: undefined },
87
+ { rate: d(101), quantity: d(1), next: undefined },
88
+ now()
89
+ )
90
+ );
84
91
 
85
92
  expect(store.snapshot.order.asReadonlyArray().length).toEqual(1);
86
93
  expect(store.snapshot.order.get(instrument.id).get(order.id).state).toEqual('FILLED');
87
- expect(store.snapshot.balance.get(instrument.base.id).free).toEqual(0.4);
88
- expect(store.snapshot.balance.get(instrument.base.id).locked).toEqual(0);
89
- expect(store.snapshot.balance.get(instrument.quote.id).free).toEqual(1060.53);
90
- expect(store.snapshot.balance.get(instrument.quote.id).locked).toEqual(0);
94
+ expect(store.snapshot.balance.get(instrument.base.id).free).toEqual(d(0.4));
95
+ expect(store.snapshot.balance.get(instrument.base.id).locked).toEqual(d.Zero);
96
+ expect(store.snapshot.balance.get(instrument.quote.id).free).toEqual(d(1060.53));
97
+ expect(store.snapshot.balance.get(instrument.quote.id).locked).toEqual(d.Zero);
91
98
  });
92
99
  });
@@ -1,6 +1,7 @@
1
1
  import { tap } from 'rxjs';
2
2
 
3
3
  import { Order, Orderbook, Trade } from '../../../domain';
4
+ import { d, decimal } from '../../../shared';
4
5
  import {
5
6
  BalanceLockOrderEvent,
6
7
  BalanceTransactEvent,
@@ -68,16 +69,16 @@ export class PaperEngine {
68
69
  }
69
70
 
70
71
  if (it.type == 'LIMIT') {
71
- if (it.quantity > 0 && it.rate > orderbook.bestAskRate) {
72
- this.completeOrder(it, orderbook.bestAskRate);
73
- } else if (it.quantity < 0 && it.rate < orderbook.bestBidRate) {
74
- this.completeOrder(it, orderbook.bestBidRate);
72
+ if (it.quantity.greaterThan(0) && it.rate.greaterThan(orderbook.asks.rate)) {
73
+ this.completeOrder(it, orderbook.asks.rate);
74
+ } else if (it.quantity.lessThan(0) && it.rate.lessThan(orderbook.bids.rate)) {
75
+ this.completeOrder(it, orderbook.bids.rate);
75
76
  }
76
77
  } else if (it.type == 'MARKET') {
77
- if (it.quantity > 0) {
78
- this.completeOrder(it, orderbook.bestAskRate);
79
- } else if (it.quantity < 0) {
80
- this.completeOrder(it, orderbook.bestBidRate);
78
+ if (it.quantity.greaterThan(0)) {
79
+ this.completeOrder(it, orderbook.asks.rate);
80
+ } else if (it.quantity.lessThan(0)) {
81
+ this.completeOrder(it, orderbook.bids.rate);
81
82
  }
82
83
  }
83
84
  });
@@ -93,9 +94,9 @@ export class PaperEngine {
93
94
  }
94
95
 
95
96
  if (it.type == 'LIMIT') {
96
- if (it.quantity > 0 && it.rate > trade.rate) {
97
+ if (it.quantity.greaterThan(0) && it.rate.greaterThan(trade.rate)) {
97
98
  this.completeOrder(it, trade.rate);
98
- } else if (it.quantity < 0 && it.rate < trade.rate) {
99
+ } else if (it.quantity.lessThan(0) && it.rate.lessThan(trade.rate)) {
99
100
  this.completeOrder(it, trade.rate);
100
101
  }
101
102
  } else if (it.type == 'MARKET') {
@@ -104,24 +105,30 @@ export class PaperEngine {
104
105
  });
105
106
  }
106
107
 
107
- private completeOrder(order: Order, averageExecutionRate: number) {
108
+ private completeOrder(order: Order, averageExecutionRate: decimal) {
108
109
  const { timestamp } = this.store.snapshot;
109
110
 
110
111
  const instrument = this.store.snapshot.universe.instrument.get(order.instrument.id);
111
112
  const transacted = {
112
- base: 0,
113
- quote: 0
113
+ base: d.Zero,
114
+ quote: d.Zero
114
115
  };
115
116
 
116
- const qty = Math.abs(order.quantity);
117
+ const qty = order.quantity.abs();
117
118
 
118
- if (order.quantity > 0) {
119
- transacted.base += instrument.base.floor(instrument.commission.applyMakerFee(qty));
120
- transacted.quote -= instrument.quote.floor(averageExecutionRate * qty);
121
- } else if (order.quantity < 0) {
122
- transacted.base -= instrument.base.floor(qty);
123
- transacted.quote += instrument.quote.floor(
124
- instrument.commission.applyMakerFee(averageExecutionRate * qty)
119
+ if (order.quantity.greaterThan(0)) {
120
+ transacted.base = transacted.base.plus(
121
+ instrument.base.floor(instrument.commission.applyMakerFee(qty))
122
+ );
123
+ transacted.quote = transacted.quote.minus(
124
+ instrument.quote.floor(averageExecutionRate.mul(qty))
125
+ );
126
+ } else if (order.quantity.lessThan(0)) {
127
+ transacted.base = transacted.base.minus(instrument.base.floor(qty));
128
+ transacted.quote = transacted.quote.plus(
129
+ instrument.quote.floor(
130
+ instrument.commission.applyMakerFee(averageExecutionRate.mul(qty))
131
+ )
125
132
  );
126
133
  }
127
134
 
@@ -1,10 +1,10 @@
1
+ import { d, timestamp } from '../../shared';
1
2
  import { InstrumentPatchEvent, Store } from '../../store';
2
3
  import {
3
4
  Adapter,
4
5
  AdapterTimeProvider,
5
6
  DefaultTimeProvider,
6
- FeedQuery,
7
- HistoryQuery
7
+ FeedAsyncCallback
8
8
  } from '../adapter';
9
9
  import {
10
10
  Asset,
@@ -33,10 +33,19 @@ class DefaultAdapter extends Adapter {
33
33
  cancel(order: Order): Promise<void> {
34
34
  throw new Error('Method not implemented.');
35
35
  }
36
- history(query: HistoryQuery): Promise<Candle[]> {
36
+ history(
37
+ instrument: InstrumentSelector,
38
+ timeframe: number,
39
+ length: number
40
+ ): Promise<Candle[]> {
37
41
  throw new Error('Method not implemented.');
38
42
  }
39
- feed(query: FeedQuery): Promise<void> {
43
+ feed(
44
+ instrument: InstrumentSelector,
45
+ from: timestamp,
46
+ to: timestamp,
47
+ callback: FeedAsyncCallback
48
+ ): Promise<void> {
40
49
  throw new Error('Method not implemented.');
41
50
  }
42
51
  name = 'default';
@@ -55,7 +64,7 @@ class DefaultAdapter extends Adapter {
55
64
  this.timestamp(),
56
65
  new Asset('a', this.name, 8),
57
66
  new Asset('b', this.name, 4),
58
- new Commission(0.1, 0.1),
67
+ new Commission(d(0.1), d(0.1)),
59
68
  'a-b'
60
69
  )
61
70
  );
@@ -96,7 +105,7 @@ describe('PaperAdapter', () => {
96
105
  await sut.awake();
97
106
  await sut.account();
98
107
 
99
- const order = Order.market(instrumentOf('default:a-b'), 1.0);
108
+ const order = Order.market(instrumentOf('default:a-b'), d(1.0));
100
109
 
101
110
  await sut.open(order);
102
111
 
@@ -1,7 +1,8 @@
1
1
  import { assetOf, Candle, InstrumentSelector, Order } from '../../domain';
2
+ import { d, timestamp } from '../../shared';
2
3
  import { BalancePatchEvent, Store } from '../../store';
3
4
  import { Adapter } from '..';
4
- import { AdapterFactory, FeedQuery, HistoryQuery } from '../adapter';
5
+ import { AdapterFactory, FeedAsyncCallback } from '../adapter';
5
6
  import { PaperEngine } from './engine/paper-engine';
6
7
 
7
8
  export interface PaperOptions {
@@ -56,15 +57,17 @@ export class PaperAdapter extends Adapter {
56
57
  continue;
57
58
  }
58
59
 
59
- const free = this.options.balance[balance];
60
+ const free = d(this.options.balance[balance]);
60
61
 
61
62
  subscribed = subscribed.filter(it => it.id != asset.id);
62
63
 
63
- this.store.dispatch(new BalancePatchEvent(asset, free, 0, this.timestamp()));
64
+ this.store.dispatch(new BalancePatchEvent(asset, free, d.Zero, this.timestamp()));
64
65
  }
65
66
 
66
67
  for (const missingAsset of subscribed) {
67
- this.store.dispatch(new BalancePatchEvent(missingAsset, 0, 0, this.timestamp()));
68
+ this.store.dispatch(
69
+ new BalancePatchEvent(missingAsset, d.Zero, d.Zero, this.timestamp())
70
+ );
68
71
  }
69
72
  }
70
73
 
@@ -76,12 +79,21 @@ export class PaperAdapter extends Adapter {
76
79
  this.engine.cancel(order);
77
80
  }
78
81
 
79
- history(query: HistoryQuery): Promise<Candle[]> {
80
- return this.decoratedAdapter.history(query);
82
+ history(
83
+ instrument: InstrumentSelector,
84
+ timeframe: number,
85
+ length: number
86
+ ): Promise<Candle[]> {
87
+ return this.decoratedAdapter.history(instrument, timeframe, length);
81
88
  }
82
89
 
83
- feed(query: FeedQuery): Promise<void> {
84
- return this.decoratedAdapter.feed(query);
90
+ feed(
91
+ instrument: InstrumentSelector,
92
+ from: timestamp,
93
+ to: timestamp,
94
+ callback: FeedAsyncCallback
95
+ ): Promise<void> {
96
+ return this.decoratedAdapter.feed(instrument, from, to, callback);
85
97
  }
86
98
 
87
99
  createPaperEngine(adapter: PaperAdapter): PaperEngine {