@quantform/core 0.3.222 → 0.3.230
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 +3 -3
- package/dist/adapter/adapter-aggregate.js.map +1 -1
- package/dist/adapter/adapter.d.ts +4 -4
- package/dist/adapter/adapter.event.d.ts +1 -1
- package/dist/adapter/adapter.event.js +3 -2
- package/dist/adapter/adapter.event.js.map +1 -1
- package/dist/adapter/adapter.js +3 -3
- package/dist/adapter/backtester/backtester-adapter.d.ts +2 -2
- package/dist/adapter/backtester/backtester-adapter.js +3 -5
- package/dist/adapter/backtester/backtester-adapter.js.map +1 -1
- package/dist/adapter/backtester/backtester-cursor.d.ts +2 -2
- package/dist/adapter/backtester/backtester-cursor.js +5 -1
- package/dist/adapter/backtester/backtester-cursor.js.map +1 -1
- package/dist/adapter/backtester/backtester-cursor.spec.js +5 -5
- package/dist/adapter/backtester/backtester-cursor.spec.js.map +1 -1
- package/dist/adapter/backtester/backtester-streamer.d.ts +1 -1
- package/dist/{common/decimals.spec.d.ts → adapter/backtester/backtester-streamer.spec.d.ts} +0 -0
- package/dist/adapter/backtester/backtester-streamer.spec.js +45 -0
- package/dist/adapter/backtester/backtester-streamer.spec.js.map +1 -0
- package/dist/adapter/paper/{model/paper-model.d.ts → executor/paper-executor.d.ts} +1 -1
- package/dist/adapter/paper/{model/paper-model.js → executor/paper-executor.js} +4 -4
- package/dist/adapter/paper/executor/paper-executor.js.map +1 -0
- package/dist/adapter/paper/{model/paper-margin-model.d.ts → executor/paper-margin-executor.d.ts} +2 -2
- package/dist/adapter/paper/{model/paper-margin-model.js → executor/paper-margin-executor.js} +11 -11
- package/dist/adapter/paper/executor/paper-margin-executor.js.map +1 -0
- package/dist/adapter/paper/{model/paper-spot-model.d.ts → executor/paper-spot-executor.d.ts} +3 -3
- package/dist/adapter/paper/{model/paper-spot-model.js → executor/paper-spot-executor.js} +7 -7
- package/dist/adapter/paper/executor/paper-spot-executor.js.map +1 -0
- package/dist/adapter/paper/index.d.ts +2 -2
- package/dist/adapter/paper/index.js +2 -2
- package/dist/adapter/paper/index.js.map +1 -1
- package/dist/adapter/paper/paper-adapter.d.ts +3 -3
- package/dist/adapter/paper/paper-adapter.js +6 -6
- package/dist/adapter/paper/paper-adapter.js.map +1 -1
- package/dist/adapter/paper/paper-adapter.spec.js +2 -2
- package/dist/adapter/paper/paper-adapter.spec.js.map +1 -1
- package/dist/bin.d.ts +2 -2
- package/dist/bin.js +6 -4
- package/dist/bin.js.map +1 -1
- package/dist/domain/asset.js +1 -1
- package/dist/domain/balance.d.ts +1 -1
- package/dist/domain/balance.js +1 -3
- package/dist/domain/balance.js.map +1 -1
- package/dist/domain/candle-builder.d.ts +1 -1
- package/dist/domain/candle.d.ts +1 -1
- package/dist/domain/candle.spec.js +2 -2
- package/dist/domain/{commision.d.ts → commission.d.ts} +2 -2
- package/dist/domain/{commision.js → commission.js} +7 -7
- package/dist/domain/commission.js.map +1 -0
- package/dist/domain/component.d.ts +1 -1
- package/dist/domain/index.d.ts +1 -1
- package/dist/domain/index.js +1 -1
- package/dist/domain/index.js.map +1 -1
- package/dist/domain/instrument.d.ts +3 -3
- package/dist/domain/order.d.ts +1 -1
- package/dist/domain/orderbook.d.ts +1 -1
- package/dist/domain/position.d.ts +1 -1
- package/dist/domain/position.js +3 -3
- package/dist/domain/trade.d.ts +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/indicator/truerange.spec.js +6 -6
- package/dist/session/index.d.ts +1 -0
- package/dist/session/index.js +1 -0
- package/dist/session/index.js.map +1 -1
- package/dist/session/session-descriptor.d.ts +2 -3
- package/dist/session/session-statement.d.ts +5 -0
- package/dist/session/session-statement.js +87 -0
- package/dist/session/session-statement.js.map +1 -0
- package/dist/session/session.d.ts +3 -3
- package/dist/session/session.js +14 -27
- package/dist/session/session.js.map +1 -1
- package/dist/session/session.spec.js +3 -3
- package/dist/session/session.spec.js.map +1 -1
- package/dist/{common → shared}/datetime.d.ts +0 -0
- package/dist/{common → shared}/datetime.js +0 -0
- package/dist/{common → shared}/datetime.js.map +1 -1
- package/dist/{common → shared}/decimals.d.ts +0 -0
- package/dist/{common → shared}/decimals.js +0 -0
- package/dist/{common → shared}/decimals.js.map +1 -1
- package/dist/{common/policy.spec.d.ts → shared/decimals.spec.d.ts} +0 -0
- package/dist/{common → shared}/decimals.spec.js +0 -0
- package/dist/{common → shared}/decimals.spec.js.map +1 -1
- package/dist/{common → shared}/index.d.ts +0 -0
- package/dist/{common → shared}/index.js +0 -0
- package/dist/{common → shared}/index.js.map +1 -1
- package/dist/{common → shared}/io.d.ts +0 -0
- package/dist/{common → shared}/io.js +0 -0
- package/dist/shared/io.js.map +1 -0
- package/dist/{common → shared}/logger.d.ts +0 -0
- package/dist/{common → shared}/logger.js +0 -0
- package/dist/{common → shared}/logger.js.map +1 -1
- package/dist/{common → shared}/policy.d.ts +0 -0
- package/dist/{common → shared}/policy.js +0 -0
- package/dist/{common → shared}/policy.js.map +1 -1
- package/dist/{common/topic.spec.d.ts → shared/policy.spec.d.ts} +0 -0
- package/dist/{common → shared}/policy.spec.js +0 -0
- package/dist/{common → shared}/policy.spec.js.map +1 -1
- package/dist/{common → shared}/topic.d.ts +0 -0
- package/dist/{common → shared}/topic.js +0 -0
- package/dist/{common → shared}/topic.js.map +1 -1
- package/dist/{common/worker.spec.d.ts → shared/topic.spec.d.ts} +0 -0
- package/dist/{common → shared}/topic.spec.js +0 -0
- package/dist/{common → shared}/topic.spec.js.map +1 -1
- package/dist/{common → shared}/worker.d.ts +0 -0
- package/dist/{common → shared}/worker.js +0 -0
- package/dist/{common → shared}/worker.js.map +1 -1
- package/dist/shared/worker.spec.d.ts +1 -0
- package/dist/{common → shared}/worker.spec.js +0 -0
- package/dist/{common → shared}/worker.spec.js.map +1 -1
- package/dist/storage/feed.d.ts +7 -4
- package/dist/storage/feed.js +21 -0
- package/dist/storage/feed.js.map +1 -1
- package/dist/storage/index.d.ts +1 -2
- package/dist/storage/index.js +1 -2
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/measurement.d.ts +7 -9
- package/dist/storage/measurement.js +25 -0
- package/dist/storage/measurement.js.map +1 -1
- package/dist/storage/storage.d.ts +23 -0
- package/dist/storage/storage.js +48 -0
- package/dist/storage/storage.js.map +1 -0
- package/dist/store/event/store-balance.event.d.ts +1 -1
- package/dist/store/event/store-balance.event.js +1 -1
- package/dist/store/event/store-balance.event.spec.js +3 -3
- package/dist/store/event/store-balance.event.spec.js.map +1 -1
- package/dist/store/event/store-candle.event.d.ts +2 -2
- package/dist/store/event/store-candle.event.js +31 -3
- package/dist/store/event/store-candle.event.js.map +1 -1
- package/dist/store/event/store-candle.event.spec.d.ts +1 -0
- package/dist/store/event/store-candle.event.spec.js +24 -0
- package/dist/store/event/store-candle.event.spec.js.map +1 -0
- package/dist/store/event/store-instrument.event.d.ts +4 -4
- package/dist/store/event/store-instrument.event.js +3 -3
- package/dist/store/event/store-instrument.event.js.map +1 -1
- package/dist/store/event/store-instrument.event.spec.js +3 -3
- package/dist/store/event/store-instrument.event.spec.js.map +1 -1
- package/dist/store/event/store-order.event.d.ts +1 -1
- package/dist/store/event/store-order.event.js +1 -1
- package/dist/store/event/store-order.event.spec.js +2 -2
- package/dist/store/event/store-orderbook.event.d.ts +1 -1
- package/dist/store/event/store-orderbook.event.js +1 -1
- package/dist/store/event/store-position.event.d.ts +1 -1
- package/dist/store/event/store-position.event.js +1 -1
- package/dist/store/event/store-trade.event.d.ts +1 -1
- package/dist/store/event/store-trade.event.js +1 -1
- package/dist/store/event/store-trade.event.spec.js +6 -6
- package/dist/store/event/store-trade.event.spec.js.map +1 -1
- package/dist/store/event/store.event.d.ts +1 -1
- package/dist/store/store.d.ts +2 -2
- package/dist/store/store.js +1 -1
- package/dist/store/store.state.d.ts +1 -1
- package/dist/store/store.state.js.map +1 -1
- package/dist/tests/backtester-adapter.spec.js +7 -7
- package/dist/tests/backtester-adapter.spec.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/jestconfig.unit.json +2 -1
- package/package.json +1 -1
- package/src/adapter/adapter-aggregate.ts +4 -2
- package/src/adapter/adapter.event.ts +2 -2
- package/src/adapter/adapter.ts +4 -4
- package/src/adapter/backtester/backtester-adapter.ts +4 -6
- package/src/adapter/backtester/backtester-cursor.spec.ts +8 -8
- package/src/adapter/backtester/backtester-cursor.ts +7 -3
- package/src/adapter/backtester/backtester-streamer.spec.ts +53 -0
- package/src/adapter/backtester/backtester-streamer.ts +1 -1
- package/src/adapter/paper/{model/paper-model.ts → executor/paper-executor.ts} +1 -1
- package/src/adapter/paper/{model/paper-margin-model.ts → executor/paper-margin-executor.ts} +4 -4
- package/src/adapter/paper/{model/paper-spot-model.ts → executor/paper-spot-executor.ts} +5 -5
- package/src/adapter/paper/index.ts +2 -2
- package/src/adapter/paper/paper-adapter.spec.ts +4 -4
- package/src/adapter/paper/paper-adapter.ts +8 -8
- package/src/bin.ts +8 -6
- package/src/domain/asset.ts +2 -2
- package/src/domain/balance.ts +3 -5
- package/src/domain/candle-builder.ts +1 -1
- package/src/domain/candle.spec.ts +1 -1
- package/src/domain/candle.ts +1 -1
- package/src/domain/{commision.ts → commission.ts} +3 -3
- package/src/domain/component.ts +1 -1
- package/src/domain/index.ts +1 -1
- package/src/domain/instrument.ts +3 -3
- package/src/domain/order.ts +1 -1
- package/src/domain/orderbook.ts +1 -1
- package/src/domain/position.ts +2 -2
- package/src/domain/trade.ts +1 -1
- package/src/index.ts +1 -7
- package/src/indicator/truerange.spec.ts +1 -1
- package/src/session/index.ts +1 -0
- package/src/session/session-descriptor.ts +29 -7
- package/src/session/session-statement.ts +119 -0
- package/src/session/session.spec.ts +6 -4
- package/src/session/session.ts +67 -33
- package/src/{common → shared}/datetime.ts +0 -0
- package/src/{common → shared}/decimals.spec.ts +0 -0
- package/src/{common → shared}/decimals.ts +0 -0
- package/src/{common → shared}/index.ts +0 -0
- package/src/{common → shared}/io.ts +0 -0
- package/src/{common → shared}/logger.ts +0 -0
- package/src/{common → shared}/policy.spec.ts +0 -0
- package/src/{common → shared}/policy.ts +0 -0
- package/src/{common → shared}/topic.spec.ts +0 -0
- package/src/{common → shared}/topic.ts +0 -0
- package/src/{common → shared}/worker.spec.ts +0 -0
- package/src/{common → shared}/worker.ts +0 -0
- package/src/storage/feed.ts +32 -7
- package/src/storage/index.ts +1 -2
- package/src/storage/measurement.ts +28 -14
- package/src/storage/storage.ts +76 -0
- package/src/store/event/store-balance.event.spec.ts +3 -3
- package/src/store/event/store-balance.event.ts +2 -2
- package/src/store/event/store-candle.event.spec.ts +32 -0
- package/src/store/event/store-candle.event.ts +26 -2
- package/src/store/event/store-instrument.event.spec.ts +3 -3
- package/src/store/event/store-instrument.event.ts +5 -5
- package/src/store/event/store-order.event.spec.ts +1 -1
- package/src/store/event/store-order.event.ts +2 -2
- package/src/store/event/store-orderbook.event.ts +2 -2
- package/src/store/event/store-position.event.ts +2 -2
- package/src/store/event/store-trade.event.spec.ts +2 -2
- package/src/store/event/store-trade.event.ts +2 -2
- package/src/store/event/store.event.ts +1 -1
- package/src/store/store.state.ts +2 -1
- package/src/store/store.ts +1 -1
- package/src/tests/backtester-adapter.spec.ts +10 -10
- package/dist/adapter/paper/model/paper-margin-model.js.map +0 -1
- package/dist/adapter/paper/model/paper-model.js.map +0 -1
- package/dist/adapter/paper/model/paper-spot-model.js.map +0 -1
- package/dist/behaviour/behaviour.d.ts +0 -6
- package/dist/behaviour/behaviour.js +0 -3
- package/dist/behaviour/behaviour.js.map +0 -1
- package/dist/behaviour/combined-behaviour.d.ts +0 -14
- package/dist/behaviour/combined-behaviour.js +0 -26
- package/dist/behaviour/combined-behaviour.js.map +0 -1
- package/dist/behaviour/index.d.ts +0 -3
- package/dist/behaviour/index.js +0 -16
- package/dist/behaviour/index.js.map +0 -1
- package/dist/behaviour/statement/benchmark-statement.behaviour.d.ts +0 -15
- package/dist/behaviour/statement/benchmark-statement.behaviour.js +0 -41
- package/dist/behaviour/statement/benchmark-statement.behaviour.js.map +0 -1
- package/dist/behaviour/statement/equity-statement.behaviour.d.ts +0 -15
- package/dist/behaviour/statement/equity-statement.behaviour.js +0 -54
- package/dist/behaviour/statement/equity-statement.behaviour.js.map +0 -1
- package/dist/behaviour/statement/index.d.ts +0 -1
- package/dist/behaviour/statement/index.js +0 -14
- package/dist/behaviour/statement/index.js.map +0 -1
- package/dist/behaviour/statement/period-statement.behaviour.d.ts +0 -9
- package/dist/behaviour/statement/period-statement.behaviour.js +0 -20
- package/dist/behaviour/statement/period-statement.behaviour.js.map +0 -1
- package/dist/behaviour/statement/statement.behaviour.d.ts +0 -7
- package/dist/behaviour/statement/statement.behaviour.js +0 -19
- package/dist/behaviour/statement/statement.behaviour.js.map +0 -1
- package/dist/common/io.js.map +0 -1
- package/dist/domain/commision.js.map +0 -1
- package/dist/storage/feed.interceptor.d.ts +0 -14
- package/dist/storage/feed.interceptor.js +0 -52
- package/dist/storage/feed.interceptor.js.map +0 -1
- package/dist/storage/in-memory.feed.d.ts +0 -10
- package/dist/storage/in-memory.feed.js +0 -30
- package/dist/storage/in-memory.feed.js.map +0 -1
- package/src/behaviour/behaviour.ts +0 -7
- package/src/behaviour/combined-behaviour.ts +0 -23
- package/src/behaviour/index.ts +0 -3
- package/src/behaviour/statement/benchmark-statement.behaviour.ts +0 -53
- package/src/behaviour/statement/equity-statement.behaviour.ts +0 -71
- package/src/behaviour/statement/index.ts +0 -1
- package/src/behaviour/statement/period-statement.behaviour.ts +0 -26
- package/src/behaviour/statement/statement.behaviour.ts +0 -19
- package/src/storage/feed.interceptor.ts +0 -92
- package/src/storage/in-memory.feed.ts +0 -38
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { Session } from '.';
|
|
2
|
+
import { InstrumentSelector } from '../domain';
|
|
3
|
+
import { combineLatest, finalize, map, take, tap } from 'rxjs';
|
|
4
|
+
import { drawdown } from '../indicator';
|
|
5
|
+
import { floor, precision } from '../shared';
|
|
6
|
+
|
|
7
|
+
export function period() {
|
|
8
|
+
return (session: Session) => {
|
|
9
|
+
const period = session.useStatement('period');
|
|
10
|
+
|
|
11
|
+
return session.store.changes$.pipe(
|
|
12
|
+
finalize(() => (period.to = new Date(session.timestamp).toISOString())),
|
|
13
|
+
take(1),
|
|
14
|
+
tap(it => (period.from = new Date(it.timestamp).toISOString()))
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function benchmark(instrument: InstrumentSelector) {
|
|
20
|
+
return (session: Session) => {
|
|
21
|
+
const statement = session.useStatement('benchmark');
|
|
22
|
+
|
|
23
|
+
let balance: number, entry: number, exit: number;
|
|
24
|
+
let dd = 0;
|
|
25
|
+
|
|
26
|
+
return combineLatest([
|
|
27
|
+
session.orderbook(instrument),
|
|
28
|
+
session.balance(instrument.quote),
|
|
29
|
+
session.orderbook(instrument).pipe(
|
|
30
|
+
drawdown(it => it.midRate),
|
|
31
|
+
tap(it => (dd = it))
|
|
32
|
+
)
|
|
33
|
+
]).pipe(
|
|
34
|
+
map(([orderbook, quote]) => {
|
|
35
|
+
const price = orderbook.midRate;
|
|
36
|
+
|
|
37
|
+
if (!balance) {
|
|
38
|
+
balance = quote.total;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!entry) {
|
|
42
|
+
entry = price;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
exit = price;
|
|
46
|
+
}),
|
|
47
|
+
finalize(() => {
|
|
48
|
+
const pnl = exit / entry - 1;
|
|
49
|
+
|
|
50
|
+
statement['benchmark_entry'] = entry;
|
|
51
|
+
statement['benchmark_exit'] = exit;
|
|
52
|
+
statement['benchmark_pnl'] = floor(balance * pnl, precision(entry));
|
|
53
|
+
statement['benchmark_pnl_pp'] = floor(pnl * 100, 2);
|
|
54
|
+
statement['benchmark_drawdown_pp'] = floor(dd * 100, 2);
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function equity(instrument: InstrumentSelector) {
|
|
61
|
+
return (session: Session) => {
|
|
62
|
+
const statement = session.useStatement('benchmark');
|
|
63
|
+
|
|
64
|
+
let balance: number, min: number, max: number;
|
|
65
|
+
let dd = 0;
|
|
66
|
+
let equity = 0;
|
|
67
|
+
|
|
68
|
+
return combineLatest([
|
|
69
|
+
session.orderbook(instrument),
|
|
70
|
+
session.balance(instrument.base),
|
|
71
|
+
session.balance(instrument.quote),
|
|
72
|
+
session.positions(instrument)
|
|
73
|
+
]).pipe(
|
|
74
|
+
tap(([, , quote]) => {
|
|
75
|
+
if (!balance) {
|
|
76
|
+
balance = quote.total;
|
|
77
|
+
}
|
|
78
|
+
}),
|
|
79
|
+
map(([orderbook, base, quote, positions]) =>
|
|
80
|
+
orderbook.instrument.quote.fixed(
|
|
81
|
+
quote.total +
|
|
82
|
+
base.total * orderbook.bestBidRate +
|
|
83
|
+
positions.reduce(
|
|
84
|
+
(agg, position) => agg + position.calculatePnL(orderbook.bestBidRate),
|
|
85
|
+
0
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
),
|
|
89
|
+
tap(it => {
|
|
90
|
+
if (!min) {
|
|
91
|
+
min = it;
|
|
92
|
+
} else {
|
|
93
|
+
min = Math.min(min, it);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!max) {
|
|
97
|
+
max = it;
|
|
98
|
+
} else {
|
|
99
|
+
max = Math.max(max, it);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
equity = it;
|
|
103
|
+
}),
|
|
104
|
+
drawdown(it => it),
|
|
105
|
+
tap(it => (dd = it)),
|
|
106
|
+
finalize(() => {
|
|
107
|
+
const pnl = equity / balance - 1;
|
|
108
|
+
const scale = precision(equity);
|
|
109
|
+
|
|
110
|
+
statement['equity'] = equity;
|
|
111
|
+
statement['equity_min'] = min;
|
|
112
|
+
statement['equity_max'] = max;
|
|
113
|
+
statement['equity_pnl'] = floor(balance * pnl, scale);
|
|
114
|
+
statement['equity_pnl_pp'] = floor(pnl * 100, 2);
|
|
115
|
+
statement['equity_drawdown_pp'] = floor(dd * 100, 2);
|
|
116
|
+
})
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { InstrumentPatchEvent } from '../store/event';
|
|
2
|
-
import { Asset,
|
|
3
|
-
import { now } from '../
|
|
2
|
+
import { Asset, Commission } from '../domain';
|
|
3
|
+
import { now } from '../shared';
|
|
4
4
|
import { SessionDescriptor } from './session-descriptor';
|
|
5
5
|
import { paper } from '../bin';
|
|
6
|
+
import { Session } from './session';
|
|
7
|
+
import { of } from 'rxjs';
|
|
6
8
|
|
|
7
9
|
describe('session tests', () => {
|
|
8
10
|
const descriptor: SessionDescriptor = {
|
|
@@ -29,14 +31,14 @@ describe('session tests', () => {
|
|
|
29
31
|
now(),
|
|
30
32
|
new Asset('de30', 'cex', 2),
|
|
31
33
|
new Asset('usd', 'cex', 2),
|
|
32
|
-
new
|
|
34
|
+
new Commission(0, 0),
|
|
33
35
|
''
|
|
34
36
|
),
|
|
35
37
|
new InstrumentPatchEvent(
|
|
36
38
|
now(),
|
|
37
39
|
new Asset('wig20', 'cex', 2),
|
|
38
40
|
new Asset('pln', 'cex', 2),
|
|
39
|
-
new
|
|
41
|
+
new Commission(0, 0),
|
|
40
42
|
''
|
|
41
43
|
)
|
|
42
44
|
);
|
package/src/session/session.ts
CHANGED
|
@@ -22,9 +22,8 @@ import {
|
|
|
22
22
|
} from '../domain';
|
|
23
23
|
import { Store } from '../store';
|
|
24
24
|
import { concat, from, Observable, Subject, Subscription } from 'rxjs';
|
|
25
|
-
import { Behaviour, CombinedBehaviour, FunctionBehaviour } from '../behaviour';
|
|
26
25
|
import { AdapterAggregate } from '../adapter/adapter-aggregate';
|
|
27
|
-
import { Worker } from '../
|
|
26
|
+
import { Worker, now } from '../shared';
|
|
28
27
|
import { Trade } from '../domain/trade';
|
|
29
28
|
import { SessionDescriptor } from './session-descriptor';
|
|
30
29
|
import {
|
|
@@ -39,18 +38,24 @@ type Optional<T, K extends keyof T> = Omit<T, K> & Partial<T>;
|
|
|
39
38
|
export class Session {
|
|
40
39
|
private initialized = false;
|
|
41
40
|
private subscription: Subscription;
|
|
42
|
-
private behaviour: Behaviour;
|
|
43
41
|
private worker = new Worker();
|
|
44
42
|
|
|
45
43
|
get timestamp(): number {
|
|
46
44
|
return this.store.snapshot.timestamp;
|
|
47
45
|
}
|
|
48
46
|
|
|
47
|
+
readonly statement: Record<string, Record<string, any>> = {};
|
|
48
|
+
|
|
49
49
|
constructor(
|
|
50
50
|
readonly store: Store,
|
|
51
51
|
readonly aggregate: AdapterAggregate,
|
|
52
52
|
readonly descriptor?: SessionDescriptor
|
|
53
|
-
) {
|
|
53
|
+
) {
|
|
54
|
+
// generate session id based on time if not provided.
|
|
55
|
+
if (descriptor && !descriptor.id) {
|
|
56
|
+
descriptor.id = now();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
54
59
|
|
|
55
60
|
async awake(): Promise<void> {
|
|
56
61
|
if (this.initialized) {
|
|
@@ -59,18 +64,11 @@ export class Session {
|
|
|
59
64
|
|
|
60
65
|
this.initialized = true;
|
|
61
66
|
|
|
67
|
+
// awake all adapters and synchronize trading accounts with store.
|
|
62
68
|
await this.aggregate.awake(this.descriptor != null);
|
|
63
69
|
|
|
64
|
-
if (this.descriptor
|
|
65
|
-
|
|
66
|
-
this.behaviour = new FunctionBehaviour(this.descriptor.behaviour);
|
|
67
|
-
} else {
|
|
68
|
-
this.behaviour = Array.isArray(this.descriptor.behaviour)
|
|
69
|
-
? new CombinedBehaviour(this.descriptor.behaviour)
|
|
70
|
-
: this.descriptor.behaviour;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
this.subscription = this.behaviour.describe(this).subscribe();
|
|
70
|
+
if (this.descriptor.describe) {
|
|
71
|
+
this.subscription = this.descriptor.describe(this).subscribe();
|
|
74
72
|
}
|
|
75
73
|
}
|
|
76
74
|
|
|
@@ -89,24 +87,29 @@ export class Session {
|
|
|
89
87
|
await this.worker.wait();
|
|
90
88
|
}
|
|
91
89
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
this.behaviour.statement(output);
|
|
90
|
+
useStatement(section: string): Record<string, any> {
|
|
91
|
+
return this.statement[section] ?? (this.statement[section] = {});
|
|
98
92
|
}
|
|
99
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Returns last stored measurement and setter for it in session.
|
|
96
|
+
* For example you can save and restore variables in same session between runs.
|
|
97
|
+
* Example usage:
|
|
98
|
+
* const [order$, setOrder] = session.measurement<Order>('order');
|
|
99
|
+
*
|
|
100
|
+
* order.pipe(tap(it => console.log(`your last order was: ${it}`)));
|
|
101
|
+
*
|
|
102
|
+
* setOrder(order);
|
|
103
|
+
*/
|
|
100
104
|
useMeasure<T extends { timestamp: number }>(
|
|
101
|
-
params: {
|
|
105
|
+
params: { kind: string; timestamp?: number },
|
|
102
106
|
defaultValue: T = undefined
|
|
103
107
|
): [Observable<T>, (value: Optional<T, 'timestamp'>) => void] {
|
|
104
108
|
const stored$ = from(
|
|
105
109
|
this.descriptor.measurement.query(this.descriptor.id, {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
direction: 'BACKWARD'
|
|
110
|
+
to: params.timestamp ?? this.timestamp,
|
|
111
|
+
kind: params.kind,
|
|
112
|
+
count: 1
|
|
110
113
|
})
|
|
111
114
|
).pipe(
|
|
112
115
|
map(it =>
|
|
@@ -119,7 +122,7 @@ export class Session {
|
|
|
119
122
|
|
|
120
123
|
const setter = (value: T) => {
|
|
121
124
|
const timestamp = value.timestamp ?? this.timestamp;
|
|
122
|
-
const measure = { timestamp,
|
|
125
|
+
const measure = { timestamp, kind: params.kind, payload: value };
|
|
123
126
|
|
|
124
127
|
this.worker.enqueue(() =>
|
|
125
128
|
this.descriptor.measurement.save(this.descriptor.id, [measure])
|
|
@@ -131,17 +134,19 @@ export class Session {
|
|
|
131
134
|
return [concat(stored$, subject$.asObservable()), setter];
|
|
132
135
|
}
|
|
133
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Return values for patch provided in optimization file.
|
|
139
|
+
* Example usage:
|
|
140
|
+
* const orderSize = session.useOptimizer('order.size');
|
|
141
|
+
*/
|
|
134
142
|
useOptimizer(path: string): any {
|
|
135
|
-
const session = this;
|
|
136
|
-
const [order$, setOrderMeasure] = session.useMeasure(
|
|
137
|
-
{ key: 'ordered' },
|
|
138
|
-
{ limt: 10, timestamp: 0 }
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
setOrderMeasure({ limt: 0, timestamp: 0 });
|
|
142
143
|
return undefined;
|
|
143
144
|
}
|
|
144
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Subscribes to specific instrument. Usually forces adapter to subscribe
|
|
148
|
+
* for orderbook and ticker streams.
|
|
149
|
+
*/
|
|
145
150
|
async subscribe(instrument: Array<InstrumentSelector>): Promise<void> {
|
|
146
151
|
const grouped = instrument
|
|
147
152
|
.filter(it => it != null)
|
|
@@ -162,6 +167,11 @@ export class Session {
|
|
|
162
167
|
}
|
|
163
168
|
}
|
|
164
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Opens collection of orders.
|
|
172
|
+
* Example:
|
|
173
|
+
* session.open(Order.buyMarket(instrument, 100));
|
|
174
|
+
*/
|
|
165
175
|
async open(...orders: Order[]): Promise<void> {
|
|
166
176
|
await Promise.all(
|
|
167
177
|
orders.map(it =>
|
|
@@ -173,6 +183,9 @@ export class Session {
|
|
|
173
183
|
);
|
|
174
184
|
}
|
|
175
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Cancels specific order.
|
|
188
|
+
*/
|
|
176
189
|
cancel(order: Order): Promise<void> {
|
|
177
190
|
return this.aggregate.dispatch(
|
|
178
191
|
order.instrument.base.exchange,
|
|
@@ -180,6 +193,10 @@ export class Session {
|
|
|
180
193
|
);
|
|
181
194
|
}
|
|
182
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Subscribes to specific instrument changes.
|
|
198
|
+
* When adapter awake then it will fetch collection of all available instruments.
|
|
199
|
+
*/
|
|
183
200
|
instrument(selector: InstrumentSelector): Observable<Instrument> {
|
|
184
201
|
this.subscribe([selector]);
|
|
185
202
|
|
|
@@ -189,6 +206,10 @@ export class Session {
|
|
|
189
206
|
);
|
|
190
207
|
}
|
|
191
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Subscribes to instruments changes.
|
|
211
|
+
* When adapter awake then it will fetch collection of all available instruments.
|
|
212
|
+
*/
|
|
192
213
|
instruments(): Observable<Instrument[]> {
|
|
193
214
|
return this.store.changes$.pipe(
|
|
194
215
|
filter(it => it instanceof Instrument),
|
|
@@ -199,6 +220,9 @@ export class Session {
|
|
|
199
220
|
);
|
|
200
221
|
}
|
|
201
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Subscribes to trade/ticker changes.
|
|
225
|
+
*/
|
|
202
226
|
trade(selector?: InstrumentSelector): Observable<Trade> {
|
|
203
227
|
this.subscribe([selector]);
|
|
204
228
|
|
|
@@ -212,6 +236,10 @@ export class Session {
|
|
|
212
236
|
);
|
|
213
237
|
}
|
|
214
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Subscribes to orderbook changes.
|
|
241
|
+
* Right now you can access only best bid and best ask.
|
|
242
|
+
*/
|
|
215
243
|
orderbook(selector?: InstrumentSelector): Observable<Orderbook> {
|
|
216
244
|
this.subscribe([selector]);
|
|
217
245
|
|
|
@@ -225,6 +253,9 @@ export class Session {
|
|
|
225
253
|
);
|
|
226
254
|
}
|
|
227
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Subscribes to position on leveraged market.
|
|
258
|
+
*/
|
|
228
259
|
position(selector?: InstrumentSelector): Observable<Position> {
|
|
229
260
|
this.subscribe([selector]);
|
|
230
261
|
|
|
@@ -238,6 +269,9 @@ export class Session {
|
|
|
238
269
|
);
|
|
239
270
|
}
|
|
240
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Subscribes to positions on leveraged markets.
|
|
274
|
+
*/
|
|
241
275
|
positions(selector: InstrumentSelector): Observable<Position[]> {
|
|
242
276
|
this.subscribe([selector]);
|
|
243
277
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/storage/feed.ts
CHANGED
|
@@ -1,13 +1,38 @@
|
|
|
1
1
|
import { StoreEvent } from '../store/event';
|
|
2
2
|
import { InstrumentSelector } from '../domain';
|
|
3
|
-
import {
|
|
3
|
+
import { Storage, StorageQueryOptions } from './storage';
|
|
4
4
|
|
|
5
|
-
export
|
|
6
|
-
|
|
5
|
+
export class Feed {
|
|
6
|
+
constructor(private readonly storage: Storage) {}
|
|
7
|
+
|
|
8
|
+
index(): Promise<Array<string>> {
|
|
9
|
+
return this.storage.index();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
save(instrument: InstrumentSelector, events: StoreEvent[]): Promise<void> {
|
|
13
|
+
return this.storage.save(
|
|
14
|
+
instrument.toString(),
|
|
15
|
+
events.map(it => ({
|
|
16
|
+
timestamp: it.timestamp,
|
|
17
|
+
kind: it.type,
|
|
18
|
+
json: JSON.stringify(it, (key, value) =>
|
|
19
|
+
key != 'timestamp' && key != 'type' && key != 'instrument' ? value : undefined
|
|
20
|
+
)
|
|
21
|
+
}))
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async query(
|
|
7
26
|
instrument: InstrumentSelector,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
27
|
+
options: StorageQueryOptions
|
|
28
|
+
): Promise<StoreEvent[]> {
|
|
29
|
+
const rows = await this.storage.query(instrument.toString(), options);
|
|
11
30
|
|
|
12
|
-
|
|
31
|
+
return rows.map(it => ({
|
|
32
|
+
timestamp: it.timestamp,
|
|
33
|
+
type: it.kind,
|
|
34
|
+
instrument,
|
|
35
|
+
...JSON.parse(it.json)
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
13
38
|
}
|
package/src/storage/index.ts
CHANGED
|
@@ -1,23 +1,37 @@
|
|
|
1
|
-
import { timestamp } from '../
|
|
1
|
+
import { timestamp } from '../shared';
|
|
2
|
+
import { Storage, StorageQueryOptions } from './storage';
|
|
2
3
|
|
|
3
4
|
export interface Measure {
|
|
4
5
|
timestamp: timestamp;
|
|
5
|
-
|
|
6
|
+
kind: string;
|
|
6
7
|
payload: any;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
export
|
|
10
|
-
|
|
10
|
+
export class Measurement {
|
|
11
|
+
constructor(private readonly storage: Storage) {}
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
timestamp: timestamp;
|
|
16
|
-
type?: string;
|
|
17
|
-
limit: number;
|
|
18
|
-
direction: 'FORWARD' | 'BACKWARD';
|
|
19
|
-
}
|
|
20
|
-
): Promise<Measure[]>;
|
|
13
|
+
async index(): Promise<Array<number>> {
|
|
14
|
+
return (await this.storage.index()).map(it => Number(it));
|
|
15
|
+
}
|
|
21
16
|
|
|
22
|
-
save(session: number, measurements: Measure[]): Promise<void
|
|
17
|
+
save(session: number, measurements: Measure[]): Promise<void> {
|
|
18
|
+
return this.storage.save(
|
|
19
|
+
session.toString(),
|
|
20
|
+
measurements.map(it => ({
|
|
21
|
+
timestamp: it.timestamp,
|
|
22
|
+
kind: it.kind,
|
|
23
|
+
json: JSON.stringify(it.payload)
|
|
24
|
+
}))
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async query(session: number, options: StorageQueryOptions): Promise<Measure[]> {
|
|
29
|
+
const rows = await this.storage.query(session.toString(), options);
|
|
30
|
+
|
|
31
|
+
return rows.map(it => ({
|
|
32
|
+
timestamp: it.timestamp,
|
|
33
|
+
kind: it.kind,
|
|
34
|
+
payload: JSON.parse(it.json)
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
23
37
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export type StorageDocument = {
|
|
2
|
+
timestamp: number;
|
|
3
|
+
kind: string;
|
|
4
|
+
json: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type StorageQueryOptions = {
|
|
8
|
+
from?: number;
|
|
9
|
+
to?: number;
|
|
10
|
+
kind?: string;
|
|
11
|
+
count: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export interface Storage {
|
|
15
|
+
index(): Promise<Array<string>>;
|
|
16
|
+
|
|
17
|
+
save(library: string, documents: StorageDocument[]): Promise<void>;
|
|
18
|
+
|
|
19
|
+
query(library: string, options: StorageQueryOptions): Promise<StorageDocument[]>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class InMemoryStorage implements Storage {
|
|
23
|
+
private tables: Record<string, StorageDocument[]> = {};
|
|
24
|
+
|
|
25
|
+
async index(): Promise<Array<string>> {
|
|
26
|
+
return Object.keys(this.tables);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async query(library: string, options: StorageQueryOptions): Promise<StorageDocument[]> {
|
|
30
|
+
if (!this.tables[library]) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let query = this.tables[library];
|
|
35
|
+
|
|
36
|
+
if (options.from) {
|
|
37
|
+
query = query.filter(it => it.timestamp > options.from);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (options.to) {
|
|
41
|
+
query = query.filter(it => it.timestamp < options.to);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (options.kind) {
|
|
45
|
+
query = query.filter(it => it.kind == options.kind);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (options.from == undefined && options.to) {
|
|
49
|
+
query = query.reverse();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (options.count) {
|
|
53
|
+
query = query.slice(0, options.count);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return query;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async save(library: string, documents: StorageDocument[]): Promise<void> {
|
|
60
|
+
if (!this.tables[library]) {
|
|
61
|
+
this.tables[library] = [];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const buffer = this.tables[library];
|
|
65
|
+
|
|
66
|
+
for (const document of documents) {
|
|
67
|
+
buffer.push(document);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
buffer.sort((lhs, rhs) => lhs.timestamp - rhs.timestamp);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
clear() {
|
|
74
|
+
this.tables = {};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Asset,
|
|
1
|
+
import { Asset, Commission } from '../../domain';
|
|
2
2
|
import { BalancePatchEvent } from '.';
|
|
3
3
|
import { InstrumentPatchEvent } from './store-instrument.event';
|
|
4
|
-
import { now } from '../../
|
|
4
|
+
import { now } from '../../shared';
|
|
5
5
|
import { Store } from '..';
|
|
6
6
|
|
|
7
7
|
describe('balance event tests', () => {
|
|
@@ -15,7 +15,7 @@ describe('balance event tests', () => {
|
|
|
15
15
|
store.changes$.subscribe(it => (component = it));
|
|
16
16
|
|
|
17
17
|
store.dispatch(
|
|
18
|
-
new InstrumentPatchEvent(timestamp, base, quote, new
|
|
18
|
+
new InstrumentPatchEvent(timestamp, base, quote, new Commission(0, 0), '')
|
|
19
19
|
);
|
|
20
20
|
store.dispatch(new BalancePatchEvent(base, 100, 0, timestamp));
|
|
21
21
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { event } from '../../
|
|
2
|
-
import { timestamp } from '../../
|
|
1
|
+
import { event } from '../../shared/topic';
|
|
2
|
+
import { timestamp } from '../../shared';
|
|
3
3
|
import { AssetSelector } from '../../domain/asset';
|
|
4
4
|
import { Balance } from '../../domain/balance';
|
|
5
5
|
import { State } from '../store.state';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Asset, Instrument } from '../../domain';
|
|
2
|
+
import { CandleEvent, CandleEventHandler } from '.';
|
|
3
|
+
import { State } from '../store.state';
|
|
4
|
+
import { now } from '../../shared';
|
|
5
|
+
|
|
6
|
+
const instrument = new Instrument(
|
|
7
|
+
new Asset('btc', 'binance', 8),
|
|
8
|
+
new Asset('usdt', 'binance', 2),
|
|
9
|
+
'binance:btc-usdt'
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
describe('candle patch event tests', () => {
|
|
13
|
+
test('should patch trade object', () => {
|
|
14
|
+
const timestamp = now();
|
|
15
|
+
const state = new State();
|
|
16
|
+
|
|
17
|
+
state.universe.instrument[instrument.toString()] = instrument;
|
|
18
|
+
state.subscription.instrument[instrument.toString()] = instrument;
|
|
19
|
+
|
|
20
|
+
const event = new CandleEvent(instrument, 1, 1, 1, 1, 1, 1, timestamp);
|
|
21
|
+
|
|
22
|
+
CandleEventHandler(event, state);
|
|
23
|
+
|
|
24
|
+
const trade = state.trade[instrument.toString()];
|
|
25
|
+
|
|
26
|
+
expect(trade.timestamp).toEqual(timestamp);
|
|
27
|
+
expect(trade.instrument.toString()).toEqual(instrument.toString());
|
|
28
|
+
expect(trade.rate).toEqual(1);
|
|
29
|
+
expect(trade.quantity).toEqual(1);
|
|
30
|
+
expect(state.timestamp).toEqual(timestamp);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { TradePatchEventHandler } from './store-trade.event';
|
|
2
|
-
import {
|
|
2
|
+
import { OrderbookPatchEventHandler } from './store-orderbook.event';
|
|
3
|
+
import { timestamp } from '../../shared/datetime';
|
|
3
4
|
import { InstrumentSelector } from '../../domain';
|
|
4
5
|
import { State } from '../store.state';
|
|
5
6
|
import { StoreEvent } from './store.event';
|
|
7
|
+
import { event } from '../../shared/topic';
|
|
6
8
|
|
|
9
|
+
@event
|
|
7
10
|
export class CandleEvent implements StoreEvent {
|
|
8
11
|
type = 'candle';
|
|
9
12
|
|
|
@@ -20,7 +23,10 @@ export class CandleEvent implements StoreEvent {
|
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
export function CandleEventHandler(event: CandleEvent, state: State) {
|
|
23
|
-
|
|
26
|
+
const instrument = state.universe.instrument[event.instrument.toString()];
|
|
27
|
+
|
|
28
|
+
// patch trade object
|
|
29
|
+
const trade = TradePatchEventHandler(
|
|
24
30
|
{
|
|
25
31
|
type: 'trade-patch',
|
|
26
32
|
instrument: event.instrument,
|
|
@@ -30,4 +36,22 @@ export function CandleEventHandler(event: CandleEvent, state: State) {
|
|
|
30
36
|
},
|
|
31
37
|
state
|
|
32
38
|
);
|
|
39
|
+
|
|
40
|
+
// patch orderbook by assuming candle close price is mid orderbook price
|
|
41
|
+
const orderbook = OrderbookPatchEventHandler(
|
|
42
|
+
{
|
|
43
|
+
type: 'orderbook-patch',
|
|
44
|
+
instrument: event.instrument,
|
|
45
|
+
bestAskQuantity: event.volume * 0.5,
|
|
46
|
+
bestAskRate: event.close + instrument.quote.tickSize,
|
|
47
|
+
bestBidQuantity: event.volume * 0.5,
|
|
48
|
+
bestBidRate: event.close - instrument.quote.tickSize,
|
|
49
|
+
timestamp: event.timestamp
|
|
50
|
+
},
|
|
51
|
+
state
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
state.timestamp = event.timestamp;
|
|
55
|
+
|
|
56
|
+
return [trade, orderbook];
|
|
33
57
|
}
|