@quantform/core 0.3.247 → 0.3.248

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.
@@ -4,8 +4,15 @@ import { Logger } from '../shared';
4
4
  import {
5
5
  AdapterAccountCommand,
6
6
  AdapterAwakeCommand,
7
- AdapterDisposeCommand
7
+ AdapterDisposeCommand,
8
+ AdapterFeedCommand,
9
+ AdapterHistoryQuery,
10
+ AdapterOrderCancelCommand,
11
+ AdapterOrderOpenCommand,
12
+ AdapterSubscribeCommand
8
13
  } from './adapter.event';
14
+ import { InstrumentSelector, Order, Candle } from '../domain';
15
+ import { Feed } from './../storage';
9
16
 
10
17
  /**
11
18
  * Manages instances of all adapters provided in session descriptor.
@@ -14,21 +21,26 @@ import {
14
21
  export class AdapterAggregate {
15
22
  private readonly adapter: Record<string, Adapter> = {};
16
23
 
17
- constructor(private readonly store: Store, adapters: Adapter[]) {
24
+ constructor(adapters: Adapter[], private readonly store: Store) {
18
25
  adapters.forEach(it => (this.adapter[it.name] = it));
19
26
  }
20
27
 
28
+ /**
29
+ * Returns adapter by name.
30
+ * @param adapterName adapter name.
31
+ * @returns
32
+ */
33
+ get(adapterName: string): Adapter {
34
+ return this.adapter[adapterName];
35
+ }
36
+
21
37
  /**
22
38
  * Sets up all adapters.
23
- * @param usePrivateScope use private api (api keys needed).
24
39
  */
25
- async awake(usePrivateScope = true): Promise<void> {
40
+ async awake(): Promise<void> {
26
41
  for (const adapter in this.adapter) {
27
42
  await this.dispatch(adapter, new AdapterAwakeCommand());
28
-
29
- if (usePrivateScope) {
30
- await this.dispatch(adapter, new AdapterAccountCommand());
31
- }
43
+ await this.dispatch(adapter, new AdapterAccountCommand());
32
44
  }
33
45
  }
34
46
 
@@ -41,17 +53,102 @@ export class AdapterAggregate {
41
53
  }
42
54
  }
43
55
 
56
+ /**
57
+ * Subscribe to collection of instruments.
58
+ * @param selectors
59
+ */
60
+ async subscribe(selectors: InstrumentSelector[]): Promise<void> {
61
+ const grouped = selectors
62
+ .filter(it => it != null)
63
+ .reduce((aggregate, it) => {
64
+ const adapter = it.base.adapter;
65
+
66
+ if (aggregate[adapter]) {
67
+ aggregate[adapter].push(it);
68
+ } else {
69
+ aggregate[adapter] = [it];
70
+ }
71
+
72
+ return aggregate;
73
+ }, {});
74
+
75
+ for (const adapterName in grouped) {
76
+ await this.dispatch(adapterName, new AdapterSubscribeCommand(grouped[adapterName]));
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Opens new order.
82
+ * @param order an order to open.
83
+ */
84
+ open(order: Order): Promise<void> {
85
+ return this.dispatch<AdapterOrderOpenCommand, void>(
86
+ order.instrument.base.adapter,
87
+ new AdapterOrderOpenCommand(order)
88
+ );
89
+ }
90
+
91
+ /**
92
+ * Cancels specific order.
93
+ */
94
+ cancel(order: Order): Promise<void> {
95
+ return this.dispatch(
96
+ order.instrument.base.adapter,
97
+ new AdapterOrderCancelCommand(order)
98
+ );
99
+ }
100
+
101
+ /**
102
+ *
103
+ * @param selector Returns collection of candles for specific history.
104
+ * @param timeframe
105
+ * @param length
106
+ * @returns
107
+ */
108
+ history(
109
+ selector: InstrumentSelector,
110
+ timeframe: number,
111
+ length: number
112
+ ): Promise<Candle[]> {
113
+ return this.dispatch<AdapterHistoryQuery, Candle[]>(
114
+ selector.base.adapter,
115
+ new AdapterHistoryQuery(selector, timeframe, length)
116
+ );
117
+ }
118
+
119
+ /**
120
+ * Feeds a storage with historical instrument data.
121
+ * @param selector
122
+ * @param from
123
+ * @param to
124
+ * @param destination
125
+ * @param callback
126
+ * @returns
127
+ */
128
+ feed(
129
+ selector: InstrumentSelector,
130
+ from: number,
131
+ to: number,
132
+ destination: Feed,
133
+ callback: (timestamp: number) => void
134
+ ): Promise<void> {
135
+ return this.dispatch(
136
+ selector.base.adapter,
137
+ new AdapterFeedCommand(selector, from, to, destination, callback)
138
+ );
139
+ }
140
+
44
141
  /**
45
142
  * Routes and executes command to a specific adapter.
46
143
  * @param adapterName name of adapter
47
- * @param event
144
+ * @param command
48
145
  * @returns
49
146
  */
50
- dispatch<TEvent extends { type: string }, TResponse>(
147
+ private dispatch<TCommand extends { type: string }, TResponse>(
51
148
  adapterName: string,
52
- event: TEvent
149
+ command: TCommand
53
150
  ): Promise<TResponse> {
54
- const adapter = this.adapter[adapterName];
151
+ const adapter = this.get(adapterName);
55
152
 
56
153
  if (!adapter) {
57
154
  throw new Error(
@@ -60,7 +157,7 @@ export class AdapterAggregate {
60
157
  }
61
158
 
62
159
  try {
63
- return adapter.dispatch(event, new AdapterContext(adapter, this.store));
160
+ return adapter.dispatch(command, new AdapterContext(adapter, this.store));
64
161
  } catch (e) {
65
162
  Logger.error(e);
66
163
  }
package/src/bin.ts CHANGED
@@ -27,7 +27,6 @@ export function backtest(
27
27
  listener
28
28
  );
29
29
  const aggregate = new AdapterAggregate(
30
- store,
31
30
  descriptor.adapter.map(
32
31
  it =>
33
32
  new PaperAdapter(
@@ -35,7 +34,8 @@ export function backtest(
35
34
  store,
36
35
  descriptor.options.backtester
37
36
  )
38
- )
37
+ ),
38
+ store
39
39
  );
40
40
 
41
41
  return [new Session(store, aggregate, descriptor), streamer];
@@ -51,8 +51,8 @@ export function paper(descriptor: SessionDescriptor): Session {
51
51
  const store = new Store();
52
52
 
53
53
  const aggregate = new AdapterAggregate(
54
- store,
55
- descriptor.adapter.map(it => new PaperAdapter(it, store, descriptor.options.paper))
54
+ descriptor.adapter.map(it => new PaperAdapter(it, store, descriptor.options.paper)),
55
+ store
56
56
  );
57
57
 
58
58
  return new Session(store, aggregate, descriptor);
@@ -65,7 +65,7 @@ export function paper(descriptor: SessionDescriptor): Session {
65
65
  */
66
66
  export function live(descriptor: SessionDescriptor): Session {
67
67
  const store = new Store();
68
- const aggregate = new AdapterAggregate(store, descriptor.adapter);
68
+ const aggregate = new AdapterAggregate(descriptor.adapter, store);
69
69
 
70
70
  return new Session(store, aggregate, descriptor);
71
71
  }
package/src/ipc.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { AdapterFeedCommand } from './adapter';
2
1
  import { Session, SessionDescriptor } from './session';
3
2
  import { instrumentOf } from './domain';
4
3
  import { Topic, event, handler } from './shared/topic';
@@ -56,6 +55,16 @@ export class IpcPaperCommand implements IpcCommand {
56
55
  @event
57
56
  export class IpcBacktestCommand implements IpcCommand {
58
57
  type = 'backtest';
58
+
59
+ /**
60
+ * Start date of the feed in unix timestamp.
61
+ */
62
+ from?: number;
63
+
64
+ /**
65
+ * Due date of the feed in unix timestamp.
66
+ */
67
+ to?: number;
59
68
  }
60
69
 
61
70
  /**
@@ -122,7 +131,7 @@ class IpcHandler extends Topic<{ type: string }, IpcSessionAccessor> {
122
131
 
123
132
  accessor.session = live(this.descriptor);
124
133
 
125
- this.notify({
134
+ this.emit({
126
135
  type: 'live:started',
127
136
  session: accessor.session.descriptor?.id
128
137
  });
@@ -141,7 +150,7 @@ class IpcHandler extends Topic<{ type: string }, IpcSessionAccessor> {
141
150
 
142
151
  accessor.session = paper(this.descriptor);
143
152
 
144
- this.notify({
153
+ this.emit({
145
154
  type: 'paper:started',
146
155
  session: accessor.session.descriptor?.id
147
156
  });
@@ -154,10 +163,17 @@ class IpcHandler extends Topic<{ type: string }, IpcSessionAccessor> {
154
163
  */
155
164
  @handler(IpcBacktestCommand)
156
165
  onBacktestMode(command: IpcBacktestCommand, accessor: IpcSessionAccessor) {
166
+ if (command.from) {
167
+ this.descriptor.options.backtester.from = command.from;
168
+ }
169
+ if (command.to) {
170
+ this.descriptor.options.backtester.to = command.to;
171
+ }
172
+
157
173
  return new Promise<void>(async resolve => {
158
174
  const [session, streamer] = backtest(this.descriptor, {
159
175
  onBacktestStarted: (streamer: BacktesterStreamer) => {
160
- this.notify({
176
+ this.emit({
161
177
  type: 'backtest:started',
162
178
  session: session.descriptor?.id,
163
179
  timestamp: streamer.timestamp,
@@ -166,7 +182,7 @@ class IpcHandler extends Topic<{ type: string }, IpcSessionAccessor> {
166
182
  });
167
183
  },
168
184
  onBacktestUpdated: (streamer: BacktesterStreamer) => {
169
- this.notify({
185
+ this.emit({
170
186
  type: 'backtest:updated',
171
187
  session: session.descriptor?.id,
172
188
  timestamp: streamer.timestamp,
@@ -177,7 +193,7 @@ class IpcHandler extends Topic<{ type: string }, IpcSessionAccessor> {
177
193
  onBacktestCompleted: async (streamer: BacktesterStreamer) => {
178
194
  await accessor.session.dispose();
179
195
 
180
- this.notify({
196
+ this.emit({
181
197
  type: 'backtest:completed',
182
198
  session: session.descriptor?.id,
183
199
  timestamp: streamer.timestamp,
@@ -206,26 +222,23 @@ class IpcHandler extends Topic<{ type: string }, IpcSessionAccessor> {
206
222
 
207
223
  await accessor.session.awake(undefined);
208
224
 
209
- this.notify({ type: 'feed:started' });
210
-
211
- await accessor.session.aggregate.dispatch(
212
- instrument.base.adapter,
213
- new AdapterFeedCommand(
214
- instrument,
215
- command.from,
216
- command.to,
217
- this.descriptor.feed,
218
- timestamp =>
219
- this.notify({
220
- type: 'feed:updated',
221
- timestamp,
222
- from: command.from,
223
- to: command.to
224
- })
225
- )
225
+ this.emit({ type: 'feed:started' });
226
+
227
+ await accessor.session.aggregate.feed(
228
+ instrument,
229
+ command.from,
230
+ command.to,
231
+ this.descriptor.feed,
232
+ timestamp =>
233
+ this.emit({
234
+ type: 'feed:updated',
235
+ timestamp,
236
+ from: command.from,
237
+ to: command.to
238
+ })
226
239
  );
227
240
 
228
- this.notify({ type: 'feed:completed' });
241
+ this.emit({ type: 'feed:completed' });
229
242
 
230
243
  await accessor.session.dispose();
231
244
  }
@@ -239,7 +252,7 @@ class IpcHandler extends Topic<{ type: string }, IpcSessionAccessor> {
239
252
 
240
253
  await accessor.session.awake(undefined);
241
254
 
242
- this.notify({ type: 'task:started', taskName: query.taskName });
255
+ this.emit({ type: 'task:started', taskName: query.taskName });
243
256
 
244
257
  let result = undefined;
245
258
 
@@ -249,7 +262,7 @@ class IpcHandler extends Topic<{ type: string }, IpcSessionAccessor> {
249
262
  result = e;
250
263
  }
251
264
 
252
- this.notify({ type: 'task:completed', taskName: query.taskName, result });
265
+ this.emit({ type: 'task:completed', taskName: query.taskName, result });
253
266
 
254
267
  await accessor.session.dispose();
255
268
  }
@@ -268,7 +281,7 @@ class IpcHandler extends Topic<{ type: string }, IpcSessionAccessor> {
268
281
  /**
269
282
  * Sends a message to parent process.
270
283
  */
271
- private notify(message: any) {
284
+ private emit(message: any) {
272
285
  if (process.send) {
273
286
  process.send(message);
274
287
  }
@@ -26,12 +26,6 @@ import { AdapterAggregate } from '../adapter/adapter-aggregate';
26
26
  import { Worker, now } from '../shared';
27
27
  import { Trade } from '../domain/trade';
28
28
  import { SessionDescriptor } from './session-descriptor';
29
- import {
30
- AdapterHistoryQuery,
31
- AdapterOrderCancelCommand,
32
- AdapterOrderOpenCommand,
33
- AdapterSubscribeCommand
34
- } from '../adapter';
35
29
 
36
30
  type Optional<T, K extends keyof T> = Omit<T, K> & Partial<T>;
37
31
 
@@ -65,7 +59,7 @@ export class Session {
65
59
  this.initialized = true;
66
60
 
67
61
  // awake all adapters and synchronize trading accounts with store.
68
- await this.aggregate.awake(this.descriptor != null);
62
+ await this.aggregate.awake();
69
63
 
70
64
  if (describe) {
71
65
  this.subscription = describe(this).subscribe();
@@ -147,24 +141,8 @@ export class Session {
147
141
  * Subscribes to specific instrument. Usually forces adapter to subscribe
148
142
  * for orderbook and ticker streams.
149
143
  */
150
- async subscribe(instrument: Array<InstrumentSelector>): Promise<void> {
151
- const grouped = instrument
152
- .filter(it => it != null)
153
- .reduce((aggregate, it) => {
154
- const adapter = it.base.adapter;
155
-
156
- if (aggregate[adapter]) {
157
- aggregate[adapter].push(it);
158
- } else {
159
- aggregate[adapter] = [it];
160
- }
161
-
162
- return aggregate;
163
- }, {});
164
-
165
- for (const group in grouped) {
166
- this.aggregate.dispatch(group, new AdapterSubscribeCommand(grouped[group]));
167
- }
144
+ subscribe(instrument: Array<InstrumentSelector>): Promise<void> {
145
+ return this.aggregate.subscribe(instrument);
168
146
  }
169
147
 
170
148
  /**
@@ -173,24 +151,14 @@ export class Session {
173
151
  * session.open(Order.buyMarket(instrument, 100));
174
152
  */
175
153
  async open(...orders: Order[]): Promise<void> {
176
- await Promise.all(
177
- orders.map(it =>
178
- this.aggregate.dispatch<AdapterOrderOpenCommand, void>(
179
- it.instrument.base.adapter,
180
- new AdapterOrderOpenCommand(it)
181
- )
182
- )
183
- );
154
+ await Promise.all(orders.map(it => this.aggregate.open(it)));
184
155
  }
185
156
 
186
157
  /**
187
158
  * Cancels specific order.
188
159
  */
189
160
  cancel(order: Order): Promise<void> {
190
- return this.aggregate.dispatch(
191
- order.instrument.base.adapter,
192
- new AdapterOrderCancelCommand(order)
193
- );
161
+ return this.aggregate.cancel(order);
194
162
  }
195
163
 
196
164
  /**
@@ -372,14 +340,7 @@ export class Session {
372
340
  return this.store.changes$.pipe(
373
341
  startWith(this.store.snapshot.universe.instrument[selector.toString()]),
374
342
  filter(it => it instanceof Instrument && it.toString() == selector.toString()),
375
- switchMap(() =>
376
- from(
377
- this.aggregate.dispatch<AdapterHistoryQuery, Candle[]>(
378
- selector.base.adapter,
379
- new AdapterHistoryQuery(selector, timeframe, length)
380
- )
381
- )
382
- ),
343
+ switchMap(() => from(this.aggregate.history(selector, timeframe, length))),
383
344
  take(1),
384
345
  shareReplay(),
385
346
  mergeMap(it => it)