@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.
- package/dist/adapter/adapter-aggregate.js +5 -5
- package/dist/adapter/adapter-aggregate.js.map +1 -1
- package/dist/adapter/backtester/backtester-adapter.d.ts +2 -3
- package/dist/adapter/backtester/backtester-adapter.js.map +1 -1
- package/dist/adapter/backtester/backtester-streamer.d.ts +6 -0
- package/dist/adapter/backtester/backtester-streamer.js +10 -7
- package/dist/adapter/backtester/backtester-streamer.js.map +1 -1
- package/dist/adapter/backtester/backtester-streamer.spec.js +9 -7
- package/dist/adapter/backtester/backtester-streamer.spec.js.map +1 -1
- package/dist/adapter/paper/paper-adapter.js +2 -2
- package/dist/adapter/paper/paper-adapter.js.map +1 -1
- package/dist/bin.d.ts +0 -1
- package/dist/bin.js +1 -7
- package/dist/bin.js.map +1 -1
- package/dist/domain/asset.d.ts +3 -3
- package/dist/domain/asset.js +8 -8
- package/dist/domain/asset.js.map +1 -1
- package/dist/domain/asset.spec.js +4 -4
- package/dist/domain/asset.spec.js.map +1 -1
- package/dist/domain/instrument.d.ts +1 -1
- package/dist/domain/instrument.js +6 -6
- package/dist/domain/instrument.js.map +1 -1
- package/dist/domain/instrument.spec.js +7 -7
- package/dist/domain/instrument.spec.js.map +1 -1
- package/dist/domain/orderbook.d.ts +0 -1
- package/dist/ipc.d.ts +10 -5
- package/dist/ipc.js +81 -38
- package/dist/ipc.js.map +1 -1
- package/dist/ipc.spec.js +45 -1
- package/dist/ipc.spec.js.map +1 -1
- package/dist/session/session.d.ts +1 -1
- package/dist/session/session.js +9 -9
- package/dist/session/session.js.map +1 -1
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/task.d.ts +6 -0
- package/dist/shared/task.js +25 -0
- package/dist/shared/task.js.map +1 -0
- package/dist/store/event/store-instrument.event.js +1 -1
- package/dist/store/event/store-instrument.event.js.map +1 -1
- package/dist/tests/backtester-adapter.spec.js +7 -5
- package/dist/tests/backtester-adapter.spec.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/adapter/adapter-aggregate.ts +5 -5
- package/src/adapter/backtester/backtester-adapter.ts +2 -3
- package/src/adapter/backtester/backtester-streamer.spec.ts +9 -7
- package/src/adapter/backtester/backtester-streamer.ts +36 -7
- package/src/adapter/paper/paper-adapter.ts +2 -2
- package/src/bin.ts +0 -11
- package/src/domain/asset.spec.ts +4 -4
- package/src/domain/asset.ts +9 -9
- package/src/domain/instrument.spec.ts +7 -7
- package/src/domain/instrument.ts +6 -6
- package/src/domain/orderbook.ts +1 -1
- package/src/ipc.spec.ts +59 -4
- package/src/ipc.ts +93 -41
- package/src/session/session.ts +9 -9
- package/src/shared/index.ts +1 -0
- package/src/shared/task.ts +30 -0
- package/src/store/event/store-instrument.event.ts +1 -1
- 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.
|
|
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.
|
|
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
|
-
}
|
package/src/domain/asset.spec.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
|
59
|
+
test('should throw invalid format message for missing adapter name', () => {
|
|
60
60
|
const fn = () => {
|
|
61
61
|
assetOf(':abc');
|
|
62
62
|
};
|
package/src/domain/asset.ts
CHANGED
|
@@ -7,13 +7,13 @@ export class AssetSelector {
|
|
|
7
7
|
private readonly id: string;
|
|
8
8
|
|
|
9
9
|
readonly name: string;
|
|
10
|
-
readonly
|
|
10
|
+
readonly adapter: string;
|
|
11
11
|
|
|
12
|
-
constructor(name: string,
|
|
12
|
+
constructor(name: string, adapter: string) {
|
|
13
13
|
this.name = name.toLowerCase();
|
|
14
|
-
this.
|
|
14
|
+
this.adapter = adapter.toLowerCase();
|
|
15
15
|
|
|
16
|
-
this.id = `${this.
|
|
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
|
|
38
|
+
const adapterName = section[0];
|
|
39
39
|
|
|
40
|
-
if (assetName.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,
|
|
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,
|
|
55
|
-
super(name,
|
|
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.
|
|
13
|
+
expect(sut.base.adapter).toEqual('xyz');
|
|
14
14
|
expect(sut.quote.name).toEqual('def');
|
|
15
|
-
expect(sut.quote.
|
|
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.
|
|
25
|
+
expect(sut.base.adapter).toEqual('xyz');
|
|
26
26
|
expect(sut.quote.name).toEqual('def');
|
|
27
|
-
expect(sut.quote.
|
|
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.
|
|
35
|
+
expect(sut.base.adapter).toEqual('xyz');
|
|
36
36
|
expect(sut.quote.name).toEqual('def');
|
|
37
|
-
expect(sut.quote.
|
|
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
|
|
65
|
+
test('should throw invalid format message for missing adapter name', () => {
|
|
66
66
|
const fn = () => {
|
|
67
67
|
assetOf(':abc-def');
|
|
68
68
|
};
|
package/src/domain/instrument.ts
CHANGED
|
@@ -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,
|
|
13
|
-
this.base = new AssetSelector(base.toLowerCase(),
|
|
14
|
-
this.quote = new AssetSelector(quote.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.
|
|
34
|
+
super(base.name, quote.name, base.adapter);
|
|
35
35
|
|
|
36
|
-
if (base.
|
|
37
|
-
throw new Error('
|
|
36
|
+
if (base.adapter != quote.adapter) {
|
|
37
|
+
throw new Error('Adapter mismatch!');
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
}
|
package/src/domain/orderbook.ts
CHANGED
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 {
|
|
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,
|
|
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:
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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 ??
|
|
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.
|
|
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 (
|
|
233
|
-
|
|
284
|
+
if (process.send) {
|
|
285
|
+
process.send(message);
|
|
234
286
|
}
|
|
235
287
|
|
|
236
|
-
|
|
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:
|
|
299
|
+
descriptor: SessionRunDescriptor,
|
|
248
300
|
...commands: IpcCommand[]
|
|
249
301
|
): Promise<Session> {
|
|
250
302
|
const handler = new IpcHandler(descriptor);
|
package/src/session/session.ts
CHANGED
|
@@ -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
|
|
154
|
+
const adapter = it.base.adapter;
|
|
155
155
|
|
|
156
|
-
if (aggregate[
|
|
157
|
-
aggregate[
|
|
156
|
+
if (aggregate[adapter]) {
|
|
157
|
+
aggregate[adapter].push(it);
|
|
158
158
|
} else {
|
|
159
|
-
aggregate[
|
|
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.
|
|
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.
|
|
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.
|
|
378
|
+
selector.base.adapter,
|
|
379
379
|
new AdapterHistoryQuery(selector, timeframe, length)
|
|
380
380
|
)
|
|
381
381
|
)
|
package/src/shared/index.ts
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
78
|
+
done();
|
|
79
|
+
}
|
|
78
80
|
}
|
|
79
81
|
});
|
|
80
82
|
|