@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.
- package/dist/adapter/adapter-aggregate.d.ts +11 -5
- package/dist/adapter/adapter-aggregate.js +38 -8
- package/dist/adapter/adapter-aggregate.js.map +1 -1
- package/dist/bin.js +3 -3
- package/dist/bin.js.map +1 -1
- package/dist/ipc.d.ts +2 -0
- package/dist/ipc.js +18 -13
- package/dist/ipc.js.map +1 -1
- package/dist/session/session.js +6 -21
- package/dist/session/session.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/adapter/adapter-aggregate.ts +110 -13
- package/src/bin.ts +5 -5
- package/src/ipc.ts +40 -27
- package/src/session/session.ts +6 -45
|
@@ -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
|
|
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(
|
|
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
|
|
144
|
+
* @param command
|
|
48
145
|
* @returns
|
|
49
146
|
*/
|
|
50
|
-
dispatch<
|
|
147
|
+
private dispatch<TCommand extends { type: string }, TResponse>(
|
|
51
148
|
adapterName: string,
|
|
52
|
-
|
|
149
|
+
command: TCommand
|
|
53
150
|
): Promise<TResponse> {
|
|
54
|
-
const adapter = this.
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
210
|
-
|
|
211
|
-
await accessor.session.aggregate.
|
|
212
|
-
instrument
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
this.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
284
|
+
private emit(message: any) {
|
|
272
285
|
if (process.send) {
|
|
273
286
|
process.send(message);
|
|
274
287
|
}
|
package/src/session/session.ts
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
151
|
-
|
|
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.
|
|
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)
|