@quantform/core 0.3.235 → 0.3.242

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 (63) hide show
  1. package/dist/adapter/adapter-aggregate.js +5 -5
  2. package/dist/adapter/adapter-aggregate.js.map +1 -1
  3. package/dist/adapter/backtester/backtester-adapter.d.ts +2 -3
  4. package/dist/adapter/backtester/backtester-adapter.js.map +1 -1
  5. package/dist/adapter/backtester/backtester-streamer.d.ts +6 -0
  6. package/dist/adapter/backtester/backtester-streamer.js +10 -7
  7. package/dist/adapter/backtester/backtester-streamer.js.map +1 -1
  8. package/dist/adapter/backtester/backtester-streamer.spec.js +9 -7
  9. package/dist/adapter/backtester/backtester-streamer.spec.js.map +1 -1
  10. package/dist/adapter/paper/paper-adapter.js +2 -2
  11. package/dist/adapter/paper/paper-adapter.js.map +1 -1
  12. package/dist/bin.d.ts +0 -1
  13. package/dist/bin.js +1 -7
  14. package/dist/bin.js.map +1 -1
  15. package/dist/domain/asset.d.ts +3 -3
  16. package/dist/domain/asset.js +8 -8
  17. package/dist/domain/asset.js.map +1 -1
  18. package/dist/domain/asset.spec.js +4 -4
  19. package/dist/domain/asset.spec.js.map +1 -1
  20. package/dist/domain/instrument.d.ts +1 -1
  21. package/dist/domain/instrument.js +6 -6
  22. package/dist/domain/instrument.js.map +1 -1
  23. package/dist/domain/instrument.spec.js +7 -7
  24. package/dist/domain/instrument.spec.js.map +1 -1
  25. package/dist/domain/orderbook.d.ts +0 -1
  26. package/dist/ipc.d.ts +10 -5
  27. package/dist/ipc.js +81 -38
  28. package/dist/ipc.js.map +1 -1
  29. package/dist/ipc.spec.js +45 -1
  30. package/dist/ipc.spec.js.map +1 -1
  31. package/dist/session/session.d.ts +1 -1
  32. package/dist/session/session.js +9 -9
  33. package/dist/session/session.js.map +1 -1
  34. package/dist/shared/index.d.ts +1 -0
  35. package/dist/shared/index.js +1 -0
  36. package/dist/shared/index.js.map +1 -1
  37. package/dist/shared/task.d.ts +6 -0
  38. package/dist/shared/task.js +25 -0
  39. package/dist/shared/task.js.map +1 -0
  40. package/dist/store/event/store-instrument.event.js +1 -1
  41. package/dist/store/event/store-instrument.event.js.map +1 -1
  42. package/dist/tests/backtester-adapter.spec.js +7 -5
  43. package/dist/tests/backtester-adapter.spec.js.map +1 -1
  44. package/dist/tsconfig.tsbuildinfo +1 -1
  45. package/package.json +1 -1
  46. package/src/adapter/adapter-aggregate.ts +5 -5
  47. package/src/adapter/backtester/backtester-adapter.ts +2 -3
  48. package/src/adapter/backtester/backtester-streamer.spec.ts +9 -7
  49. package/src/adapter/backtester/backtester-streamer.ts +36 -7
  50. package/src/adapter/paper/paper-adapter.ts +2 -2
  51. package/src/bin.ts +0 -11
  52. package/src/domain/asset.spec.ts +4 -4
  53. package/src/domain/asset.ts +9 -9
  54. package/src/domain/instrument.spec.ts +7 -7
  55. package/src/domain/instrument.ts +6 -6
  56. package/src/domain/orderbook.ts +1 -1
  57. package/src/ipc.spec.ts +59 -4
  58. package/src/ipc.ts +93 -41
  59. package/src/session/session.ts +9 -9
  60. package/src/shared/index.ts +1 -0
  61. package/src/shared/task.ts +30 -0
  62. package/src/store/event/store-instrument.event.ts +1 -1
  63. package/src/tests/backtester-adapter.spec.ts +7 -5
@@ -43,13 +43,13 @@ export class PaperAdapter extends Adapter {
43
43
  @handler(AdapterAccountCommand)
44
44
  onAccount(event: AdapterAccountCommand, context: AdapterContext) {
45
45
  let subscribed = Object.values(this.store.snapshot.subscription.asset).filter(
46
- it => it.exchange == this.name
46
+ it => it.adapter == this.name
47
47
  );
48
48
 
49
49
  for (const balance in this.options.balance) {
50
50
  const asset = assetOf(balance);
51
51
 
52
- if (asset.exchange != this.name) {
52
+ if (asset.adapter != this.name) {
53
53
  continue;
54
54
  }
55
55
 
package/src/bin.ts CHANGED
@@ -59,14 +59,3 @@ export function live(descriptor: SessionDescriptor): Session {
59
59
 
60
60
  return new Session(store, aggregate, descriptor);
61
61
  }
62
-
63
- /**
64
- * Starts a new idle session.
65
- * @param descriptor session descriptor.
66
- */
67
- export function idle(descriptor: SessionDescriptor): Session {
68
- const store = new Store();
69
- const aggregate = new AdapterAggregate(store, descriptor.adapter);
70
-
71
- return new Session(store, aggregate);
72
- }
@@ -5,7 +5,7 @@ describe('asset tests', () => {
5
5
  const sut = new Asset('abc', 'xyz', 4);
6
6
 
7
7
  expect(sut.name).toEqual('abc');
8
- expect(sut.exchange).toEqual('xyz');
8
+ expect(sut.adapter).toEqual('xyz');
9
9
  expect(sut.scale).toEqual(4);
10
10
  expect(sut.tickSize).toEqual(0.0001);
11
11
  expect(sut.fixed(1.1234567)).toEqual(1.1234);
@@ -20,7 +20,7 @@ describe('asset selector tests', () => {
20
20
  const sut = assetOf('xyz:abc');
21
21
 
22
22
  expect(sut.name).toEqual('abc');
23
- expect(sut.exchange).toEqual('xyz');
23
+ expect(sut.adapter).toEqual('xyz');
24
24
  expect(sut.toString()).toEqual('xyz:abc');
25
25
  });
26
26
 
@@ -28,7 +28,7 @@ describe('asset selector tests', () => {
28
28
  const sut = assetOf('XYZ:ABC');
29
29
 
30
30
  expect(sut.name).toEqual('abc');
31
- expect(sut.exchange).toEqual('xyz');
31
+ expect(sut.adapter).toEqual('xyz');
32
32
  expect(sut.toString()).toEqual('xyz:abc');
33
33
  });
34
34
 
@@ -56,7 +56,7 @@ describe('asset selector tests', () => {
56
56
  expect(fn).toThrow(Error);
57
57
  });
58
58
 
59
- test('should throw invalid format message for missing exchange name', () => {
59
+ test('should throw invalid format message for missing adapter name', () => {
60
60
  const fn = () => {
61
61
  assetOf(':abc');
62
62
  };
@@ -7,13 +7,13 @@ export class AssetSelector {
7
7
  private readonly id: string;
8
8
 
9
9
  readonly name: string;
10
- readonly exchange: string;
10
+ readonly adapter: string;
11
11
 
12
- constructor(name: string, exchange: string) {
12
+ constructor(name: string, adapter: string) {
13
13
  this.name = name.toLowerCase();
14
- this.exchange = exchange.toLowerCase();
14
+ this.adapter = adapter.toLowerCase();
15
15
 
16
- this.id = `${this.exchange}:${this.name}`;
16
+ this.id = `${this.adapter}:${this.name}`;
17
17
  }
18
18
 
19
19
  /**
@@ -35,13 +35,13 @@ export function assetOf(asset: string): AssetSelector {
35
35
  }
36
36
 
37
37
  const assetName = section[1];
38
- const exchangeName = section[0];
38
+ const adapterName = section[0];
39
39
 
40
- if (assetName.length == 0 || exchangeName.length == 0) {
40
+ if (assetName.length == 0 || adapterName.length == 0) {
41
41
  throw Error('invalid asset format');
42
42
  }
43
43
 
44
- return new AssetSelector(assetName, exchangeName);
44
+ return new AssetSelector(assetName, adapterName);
45
45
  }
46
46
 
47
47
  /**
@@ -51,8 +51,8 @@ export function assetOf(asset: string): AssetSelector {
51
51
  export class Asset extends AssetSelector {
52
52
  readonly tickSize: number;
53
53
 
54
- constructor(name: string, exchange: string, public readonly scale: number) {
55
- super(name, exchange);
54
+ constructor(name: string, adapter: string, public readonly scale: number) {
55
+ super(name, adapter);
56
56
 
57
57
  this.tickSize = 1.0 / Math.pow(10, this.scale);
58
58
  }
@@ -10,9 +10,9 @@ describe('instrument tests', () => {
10
10
  );
11
11
 
12
12
  expect(sut.base.name).toEqual('abc');
13
- expect(sut.base.exchange).toEqual('xyz');
13
+ expect(sut.base.adapter).toEqual('xyz');
14
14
  expect(sut.quote.name).toEqual('def');
15
- expect(sut.quote.exchange).toEqual('xyz');
15
+ expect(sut.quote.adapter).toEqual('xyz');
16
16
  expect(sut.toString()).toEqual('xyz:abc-def');
17
17
  });
18
18
  });
@@ -22,9 +22,9 @@ describe('instrument selector tests', () => {
22
22
  const sut = instrumentOf('xyz:abc-def');
23
23
 
24
24
  expect(sut.base.name).toEqual('abc');
25
- expect(sut.base.exchange).toEqual('xyz');
25
+ expect(sut.base.adapter).toEqual('xyz');
26
26
  expect(sut.quote.name).toEqual('def');
27
- expect(sut.quote.exchange).toEqual('xyz');
27
+ expect(sut.quote.adapter).toEqual('xyz');
28
28
  expect(sut.toString()).toEqual('xyz:abc-def');
29
29
  });
30
30
 
@@ -32,9 +32,9 @@ describe('instrument selector tests', () => {
32
32
  const sut = instrumentOf('XYZ:ABC-DEF');
33
33
 
34
34
  expect(sut.base.name).toEqual('abc');
35
- expect(sut.base.exchange).toEqual('xyz');
35
+ expect(sut.base.adapter).toEqual('xyz');
36
36
  expect(sut.quote.name).toEqual('def');
37
- expect(sut.quote.exchange).toEqual('xyz');
37
+ expect(sut.quote.adapter).toEqual('xyz');
38
38
  expect(sut.toString()).toEqual('xyz:abc-def');
39
39
  });
40
40
 
@@ -62,7 +62,7 @@ describe('instrument selector tests', () => {
62
62
  expect(fn).toThrow(Error);
63
63
  });
64
64
 
65
- test('should throw invalid format message for missing exchange name', () => {
65
+ test('should throw invalid format message for missing adapter name', () => {
66
66
  const fn = () => {
67
67
  assetOf(':abc-def');
68
68
  };
@@ -9,9 +9,9 @@ export class InstrumentSelector {
9
9
  readonly base: AssetSelector;
10
10
  readonly quote: AssetSelector;
11
11
 
12
- constructor(base: string, quote: string, exchange: string) {
13
- this.base = new AssetSelector(base.toLowerCase(), exchange.toLowerCase());
14
- this.quote = new AssetSelector(quote.toLowerCase(), exchange.toLowerCase());
12
+ constructor(base: string, quote: string, adapter: string) {
13
+ this.base = new AssetSelector(base.toLowerCase(), adapter.toLowerCase());
14
+ this.quote = new AssetSelector(quote.toLowerCase(), adapter.toLowerCase());
15
15
 
16
16
  this.id = `${this.base.toString()}-${this.quote.name}`;
17
17
  }
@@ -31,10 +31,10 @@ export class Instrument extends InstrumentSelector implements Component {
31
31
  leverage?: number = null;
32
32
 
33
33
  constructor(readonly base: Asset, readonly quote: Asset, readonly raw: string) {
34
- super(base.name, quote.name, base.exchange);
34
+ super(base.name, quote.name, base.adapter);
35
35
 
36
- if (base.exchange != quote.exchange) {
37
- throw new Error('Exchange mismatch!');
36
+ if (base.adapter != quote.adapter) {
37
+ throw new Error('Adapter mismatch!');
38
38
  }
39
39
  }
40
40
  }
@@ -11,7 +11,7 @@ export class Orderbook implements Component {
11
11
  bestAskQuantity: number;
12
12
  bestBidRate: number;
13
13
  bestBidQuantity: number;
14
- az;
14
+
15
15
  get midRate(): number {
16
16
  return this.instrument.quote.fixed((this.bestAskRate + this.bestBidRate) / 2);
17
17
  }
package/src/ipc.spec.ts CHANGED
@@ -3,14 +3,18 @@ import {
3
3
  Adapter,
4
4
  AdapterFeedCommand,
5
5
  AdapterAwakeCommand,
6
- AdapterAccountCommand
6
+ AdapterAccountCommand,
7
+ AdapterSubscribeCommand,
8
+ AdapterDisposeCommand
7
9
  } from './adapter';
8
10
  import { PaperAdapter, PaperSpotExecutor } from './adapter/paper';
9
11
  import { PaperExecutor } from './adapter/paper/executor/paper-executor';
10
- import { IpcFeedCommand, run } from './ipc';
12
+ import { run } from './ipc';
11
13
  import { Feed, InMemoryStorage } from './storage';
12
14
  import { instrumentOf } from './domain';
13
- import { handler } from './shared';
15
+ import { handler, task } from './shared';
16
+ import { EventEmitter } from 'events';
17
+ import { from, of, take, tap } from 'rxjs';
14
18
 
15
19
  class DefaultAdapter extends Adapter {
16
20
  name = 'default';
@@ -26,6 +30,12 @@ class DefaultAdapter extends Adapter {
26
30
  @handler(AdapterAwakeCommand)
27
31
  onAwake(command: AdapterAwakeCommand) {}
28
32
 
33
+ @handler(AdapterDisposeCommand)
34
+ onDispose(command: AdapterDisposeCommand) {}
35
+
36
+ @handler(AdapterSubscribeCommand)
37
+ onSubscribe(command: AdapterSubscribeCommand) {}
38
+
29
39
  @handler(AdapterAccountCommand)
30
40
  onAccount(command: AdapterAccountCommand) {}
31
41
 
@@ -51,6 +61,51 @@ describe('ipc feed tests', () => {
51
61
  command
52
62
  );
53
63
 
54
- expect(session.descriptor).toBeUndefined();
64
+ //expect(session.descriptor).toBeUndefined();
65
+ });
66
+
67
+ test('should dispatch session started event', done => {
68
+ const command = {
69
+ type: 'paper',
70
+ balance: { 'default:usd': 100 }
71
+ };
72
+
73
+ const ipcSub = new EventEmitter();
74
+
75
+ ipcSub.on('message', (message: any) => {
76
+ expect(message.type).toBe('paper:started');
77
+ done();
78
+ });
79
+
80
+ run(
81
+ {
82
+ adapter: [new DefaultAdapter()],
83
+ describe: (session: Session) => session.trade(instrumentOf('default:btc-usdt')),
84
+ ipcSub
85
+ },
86
+ command
87
+ );
88
+ });
89
+
90
+ test('should execute user task', done => {
91
+ task('hello-world', session => {
92
+ return of(1).pipe(
93
+ take(1),
94
+ tap(() => done())
95
+ );
96
+ });
97
+
98
+ const command = {
99
+ type: 'task',
100
+ taskName: 'hello-world'
101
+ };
102
+
103
+ run(
104
+ {
105
+ adapter: [new DefaultAdapter()],
106
+ describe: (session: Session) => session.trade(instrumentOf('default:btc-usdt'))
107
+ },
108
+ command
109
+ );
55
110
  });
56
111
  });
package/src/ipc.ts CHANGED
@@ -2,8 +2,10 @@ import { AdapterFeedCommand } from './adapter';
2
2
  import { Session, SessionDescriptor } from './session';
3
3
  import { instrumentOf } from './domain';
4
4
  import { Topic, event, handler } from './shared/topic';
5
- import { Logger } from './shared';
6
- import { backtest, idle, live, paper } from './bin';
5
+ import { runTask, Logger } from './shared';
6
+ import { backtest, live, paper } from './bin';
7
+ import { BacktesterStreamer } from './adapter/backtester';
8
+ import { EventEmitter } from 'events';
7
9
  import minimist = require('minimist');
8
10
 
9
11
  /**
@@ -72,12 +74,6 @@ export class IpcBacktestCommand implements IpcCommand {
72
74
  balance: { [key: string]: number };
73
75
  }
74
76
 
75
- @event
76
- export class IpcUniverseQuery implements IpcCommand {
77
- type = 'universe';
78
- exchange: string;
79
- }
80
-
81
77
  /**
82
78
  * Feeds specific session descriptor with instrument data.
83
79
  */
@@ -101,6 +97,19 @@ export class IpcFeedCommand implements IpcCommand {
101
97
  to: number;
102
98
  }
103
99
 
100
+ /**
101
+ * Executes user task defined in quantform.ts file.
102
+ */
103
+ @event
104
+ export class IpcTaskCommand implements IpcCommand {
105
+ type = 'task';
106
+
107
+ /**
108
+ * Name of the task to execute.
109
+ */
110
+ taskName: string;
111
+ }
112
+
104
113
  /**
105
114
  * Stores current session instance.
106
115
  */
@@ -108,11 +117,13 @@ class IpcSessionAccessor {
108
117
  session: Session;
109
118
  }
110
119
 
120
+ export declare type SessionRunDescriptor = SessionDescriptor & { ipcSub?: EventEmitter };
121
+
111
122
  /**
112
123
  * Inter process communication handler.
113
124
  */
114
125
  class IpcHandler extends Topic<{ type: string }, IpcSessionAccessor> {
115
- constructor(private readonly descriptor: SessionDescriptor) {
126
+ constructor(private readonly descriptor: SessionRunDescriptor) {
116
127
  super();
117
128
  }
118
129
 
@@ -127,6 +138,11 @@ class IpcHandler extends Topic<{ type: string }, IpcSessionAccessor> {
127
138
 
128
139
  accessor.session = live(this.descriptor);
129
140
 
141
+ this.notify({
142
+ type: 'live:started',
143
+ session: accessor.session.descriptor?.id
144
+ });
145
+
130
146
  await accessor.session.awake();
131
147
  }
132
148
 
@@ -143,6 +159,11 @@ class IpcHandler extends Topic<{ type: string }, IpcSessionAccessor> {
143
159
  balance: command.balance
144
160
  });
145
161
 
162
+ this.notify({
163
+ type: 'paper:started',
164
+ session: accessor.session.descriptor?.id
165
+ });
166
+
146
167
  await accessor.session.awake();
147
168
  }
148
169
 
@@ -156,55 +177,62 @@ class IpcHandler extends Topic<{ type: string }, IpcSessionAccessor> {
156
177
  from: command.from,
157
178
  to: command.to,
158
179
  balance: command.balance,
159
- progress: timestamp =>
160
- this.notify({
161
- type: 'backtest:updated',
162
- timestamp,
163
- from: command.from,
164
- to: command.to
165
- }),
166
- completed: async () => {
167
- const statement = {};
168
-
169
- await accessor.session.dispose();
170
-
171
- this.notify({ type: 'backtest:completed', statement });
172
-
173
- resolve();
180
+ listener: {
181
+ onBacktestStarted: (streamer: BacktesterStreamer) => {
182
+ this.notify({
183
+ type: 'backtest:started',
184
+ session: session.descriptor?.id,
185
+ timestamp: streamer.timestamp,
186
+ from: command.from,
187
+ to: command.to
188
+ });
189
+ },
190
+ onBacktestUpdated: (streamer: BacktesterStreamer) => {
191
+ this.notify({
192
+ type: 'backtest:updated',
193
+ session: session.descriptor?.id,
194
+ timestamp: streamer.timestamp,
195
+ from: command.from,
196
+ to: command.to
197
+ });
198
+ },
199
+ onBacktestCompleted: async (streamer: BacktesterStreamer) => {
200
+ await accessor.session.dispose();
201
+
202
+ this.notify({
203
+ type: 'backtest:completed',
204
+ session: session.descriptor?.id,
205
+ timestamp: streamer.timestamp,
206
+ from: command.from,
207
+ to: command.to
208
+ });
209
+
210
+ resolve();
211
+ }
174
212
  }
175
213
  });
176
214
 
177
215
  accessor.session = session;
178
216
 
179
- this.notify({ type: 'backtest:started' });
180
-
181
217
  await accessor.session.awake();
182
218
  await streamer.tryContinue().catch(it => Logger.error(it));
183
219
  });
184
220
  }
185
221
 
186
- /**
187
- * @see IpcUniverseQuery
188
- */
189
- @handler(IpcUniverseQuery)
190
- onUniverse(query: IpcUniverseQuery, accessor: IpcSessionAccessor) {
191
- accessor.session = accessor.session ?? idle(this.descriptor);
192
- }
193
-
194
222
  /**
195
223
  * @see IpcFeedCommand
196
224
  */
197
225
  @handler(IpcFeedCommand)
198
226
  async onFeed(command: IpcFeedCommand, accessor: IpcSessionAccessor) {
199
- accessor.session = accessor.session ?? idle(this.descriptor);
227
+ accessor.session = accessor.session ?? live(this.descriptor);
200
228
  const instrument = instrumentOf(command.instrument);
201
229
 
202
- await accessor.session.awake();
230
+ await accessor.session.awake(true);
203
231
 
204
232
  this.notify({ type: 'feed:started' });
205
233
 
206
234
  await accessor.session.aggregate.dispatch(
207
- instrument.base.exchange,
235
+ instrument.base.adapter,
208
236
  new AdapterFeedCommand(
209
237
  instrument,
210
238
  command.from,
@@ -225,15 +253,39 @@ class IpcHandler extends Topic<{ type: string }, IpcSessionAccessor> {
225
253
  await accessor.session.dispose();
226
254
  }
227
255
 
256
+ /**
257
+ * @see IpcTaskCommand
258
+ */
259
+ @handler(IpcTaskCommand)
260
+ async onTask(query: IpcTaskCommand, accessor: IpcSessionAccessor) {
261
+ accessor.session = accessor.session ?? live(this.descriptor);
262
+
263
+ await accessor.session.awake(true);
264
+
265
+ this.notify({ type: 'task:started', taskName: query.taskName });
266
+
267
+ let result = undefined;
268
+
269
+ try {
270
+ result = await runTask(query.taskName, accessor.session);
271
+ } catch (e) {
272
+ result = e;
273
+ }
274
+
275
+ this.notify({ type: 'task:completed', taskName: query.taskName, result });
276
+
277
+ await accessor.session.dispose();
278
+ }
279
+
228
280
  /**
229
281
  * Sends a message to parent process.
230
282
  */
231
283
  private notify(message: any) {
232
- if (!process.send) {
233
- return;
284
+ if (process.send) {
285
+ process.send(message);
234
286
  }
235
287
 
236
- process.send(message);
288
+ this.descriptor.ipcSub?.emit('message', message);
237
289
  }
238
290
  }
239
291
 
@@ -244,7 +296,7 @@ class IpcHandler extends Topic<{ type: string }, IpcSessionAccessor> {
244
296
  * @returns new session.
245
297
  */
246
298
  export async function run(
247
- descriptor: SessionDescriptor,
299
+ descriptor: SessionRunDescriptor,
248
300
  ...commands: IpcCommand[]
249
301
  ): Promise<Session> {
250
302
  const handler = new IpcHandler(descriptor);
@@ -57,7 +57,7 @@ export class Session {
57
57
  }
58
58
  }
59
59
 
60
- async awake(): Promise<void> {
60
+ async awake(idle: boolean = false): Promise<void> {
61
61
  if (this.initialized) {
62
62
  return;
63
63
  }
@@ -67,7 +67,7 @@ export class Session {
67
67
  // awake all adapters and synchronize trading accounts with store.
68
68
  await this.aggregate.awake(this.descriptor != null);
69
69
 
70
- if (this.descriptor?.describe) {
70
+ if (!idle && this.descriptor?.describe) {
71
71
  this.subscription = this.descriptor.describe(this).subscribe();
72
72
  }
73
73
  }
@@ -151,12 +151,12 @@ export class Session {
151
151
  const grouped = instrument
152
152
  .filter(it => it != null)
153
153
  .reduce((aggregate, it) => {
154
- const exchange = it.base.exchange;
154
+ const adapter = it.base.adapter;
155
155
 
156
- if (aggregate[exchange]) {
157
- aggregate[exchange].push(it);
156
+ if (aggregate[adapter]) {
157
+ aggregate[adapter].push(it);
158
158
  } else {
159
- aggregate[exchange] = [it];
159
+ aggregate[adapter] = [it];
160
160
  }
161
161
 
162
162
  return aggregate;
@@ -176,7 +176,7 @@ export class Session {
176
176
  await Promise.all(
177
177
  orders.map(it =>
178
178
  this.aggregate.dispatch<AdapterOrderOpenCommand, void>(
179
- it.instrument.base.exchange,
179
+ it.instrument.base.adapter,
180
180
  new AdapterOrderOpenCommand(it)
181
181
  )
182
182
  )
@@ -188,7 +188,7 @@ export class Session {
188
188
  */
189
189
  cancel(order: Order): Promise<void> {
190
190
  return this.aggregate.dispatch(
191
- order.instrument.base.exchange,
191
+ order.instrument.base.adapter,
192
192
  new AdapterOrderCancelCommand(order)
193
193
  );
194
194
  }
@@ -375,7 +375,7 @@ export class Session {
375
375
  switchMap(() =>
376
376
  from(
377
377
  this.aggregate.dispatch<AdapterHistoryQuery, Candle[]>(
378
- selector.base.exchange,
378
+ selector.base.adapter,
379
379
  new AdapterHistoryQuery(selector, timeframe, length)
380
380
  )
381
381
  )
@@ -5,3 +5,4 @@ export * from './io';
5
5
  export * from './topic';
6
6
  export * from './policy';
7
7
  export * from './worker';
8
+ export * from './task';
@@ -0,0 +1,30 @@
1
+ import { lastValueFrom, Observable } from 'rxjs';
2
+ import { Session } from './../session';
3
+
4
+ declare type Task = (session: Session) => Observable<any> | Promise<any> | any;
5
+
6
+ const tasks: Record<string, Task> = {};
7
+
8
+ export function task(name: string, fn: Task) {
9
+ tasks[name] = fn;
10
+ }
11
+
12
+ export async function runTask(name: string, session: Session): Promise<void> {
13
+ const task = tasks[name];
14
+
15
+ if (!task) {
16
+ throw new Error('Unknown task: ' + name);
17
+ }
18
+
19
+ const result = tasks[name](session);
20
+
21
+ if (result instanceof Observable) {
22
+ return lastValueFrom(result);
23
+ }
24
+
25
+ if (result instanceof Promise) {
26
+ return await result;
27
+ }
28
+
29
+ return result;
30
+ }
@@ -23,7 +23,7 @@ export function InstrumentPatchEventHandler(event: InstrumentPatchEvent, state:
23
23
  const selector = new InstrumentSelector(
24
24
  event.base.name,
25
25
  event.quote.name,
26
- event.base.exchange
26
+ event.base.adapter
27
27
  );
28
28
 
29
29
  let instrument = state.universe.instrument[selector.toString()];
@@ -69,12 +69,14 @@ describe('backtester adapter tests', () => {
69
69
  },
70
70
  from: 0,
71
71
  to: 100,
72
- completed: () => {
73
- expect(store.snapshot.timestamp).toEqual(1);
74
- expect(store.snapshot.trade[instrument.toString()].rate).toEqual(100);
75
- expect(store.snapshot.trade[instrument.toString()].quantity).toEqual(10);
72
+ listener: {
73
+ onBacktestCompleted: () => {
74
+ expect(store.snapshot.timestamp).toEqual(1);
75
+ expect(store.snapshot.trade[instrument.toString()].rate).toEqual(100);
76
+ expect(store.snapshot.trade[instrument.toString()].quantity).toEqual(10);
76
77
 
77
- done();
78
+ done();
79
+ }
78
80
  }
79
81
  });
80
82