@quantform/core 0.3.220 → 0.3.228
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.event.js +2 -1
- package/dist/adapter/adapter.event.js.map +1 -1
- package/dist/adapter/backtester/backtester-cursor.d.ts +1 -1
- 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/session/session.d.ts +8 -4
- package/dist/session/session.js +16 -9
- package/dist/session/session.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-candle.event.d.ts +1 -1
- package/dist/store/event/store-candle.event.js +31 -3
- package/dist/store/event/store-candle.event.js.map +1 -1
- package/dist/store/store.d.ts +1 -1
- package/dist/tests/backtester-adapter.spec.js +2 -2
- package/dist/tests/backtester-adapter.spec.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/adapter/backtester/backtester-cursor.spec.ts +6 -6
- package/src/adapter/backtester/backtester-cursor.ts +6 -2
- package/src/session/session-descriptor.ts +3 -10
- package/src/session/session.ts +31 -14
- package/src/storage/feed.ts +32 -7
- package/src/storage/index.ts +1 -2
- package/src/storage/measurement.ts +28 -15
- package/src/storage/storage.ts +76 -0
- package/src/store/event/store-candle.event.ts +25 -1
- package/src/tests/backtester-adapter.spec.ts +3 -3
- 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/storage/feed.interceptor.ts +0 -92
- package/src/storage/in-memory.feed.ts +0 -38
package/src/session/session.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
filter,
|
|
4
4
|
map,
|
|
5
5
|
mergeMap,
|
|
6
|
+
share,
|
|
6
7
|
shareReplay,
|
|
7
8
|
startWith,
|
|
8
9
|
switchMap,
|
|
@@ -20,13 +21,12 @@ import {
|
|
|
20
21
|
OrderState
|
|
21
22
|
} from '../domain';
|
|
22
23
|
import { Store } from '../store';
|
|
23
|
-
import { from, Observable, Subscription } from 'rxjs';
|
|
24
|
+
import { concat, from, Observable, Subject, Subscription } from 'rxjs';
|
|
24
25
|
import { Behaviour, CombinedBehaviour, FunctionBehaviour } from '../behaviour';
|
|
25
26
|
import { AdapterAggregate } from '../adapter/adapter-aggregate';
|
|
26
27
|
import { Worker } from '../common';
|
|
27
28
|
import { Trade } from '../domain/trade';
|
|
28
29
|
import { SessionDescriptor } from './session-descriptor';
|
|
29
|
-
import { Measure } from '../storage/measurement';
|
|
30
30
|
import {
|
|
31
31
|
AdapterHistoryQuery,
|
|
32
32
|
AdapterOrderCancelCommand,
|
|
@@ -34,12 +34,18 @@ import {
|
|
|
34
34
|
AdapterSubscribeCommand
|
|
35
35
|
} from '../adapter';
|
|
36
36
|
|
|
37
|
+
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<T>;
|
|
38
|
+
|
|
37
39
|
export class Session {
|
|
38
40
|
private initialized = false;
|
|
39
41
|
private subscription: Subscription;
|
|
40
42
|
private behaviour: Behaviour;
|
|
41
43
|
private worker = new Worker();
|
|
42
44
|
|
|
45
|
+
get timestamp(): number {
|
|
46
|
+
return this.store.snapshot.timestamp;
|
|
47
|
+
}
|
|
48
|
+
|
|
43
49
|
constructor(
|
|
44
50
|
readonly store: Store,
|
|
45
51
|
readonly aggregate: AdapterAggregate,
|
|
@@ -91,30 +97,41 @@ export class Session {
|
|
|
91
97
|
this.behaviour.statement(output);
|
|
92
98
|
}
|
|
93
99
|
|
|
94
|
-
useMeasure<T extends
|
|
95
|
-
params: {
|
|
100
|
+
useMeasure<T extends { timestamp: number }>(
|
|
101
|
+
params: { kind: string; timestamp?: number },
|
|
96
102
|
defaultValue: T = undefined
|
|
97
|
-
) {
|
|
98
|
-
const
|
|
103
|
+
): [Observable<T>, (value: Optional<T, 'timestamp'>) => void] {
|
|
104
|
+
const stored$ = from(
|
|
99
105
|
this.descriptor.measurement.query(this.descriptor.id, {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
direction: 'BACKWARD'
|
|
106
|
+
to: params.timestamp ?? this.timestamp,
|
|
107
|
+
kind: params.kind,
|
|
108
|
+
count: 1
|
|
104
109
|
})
|
|
105
|
-
).pipe(
|
|
110
|
+
).pipe(
|
|
111
|
+
map(it =>
|
|
112
|
+
it.length ? { timestamp: it[0].timestamp, ...it[0].payload } : defaultValue
|
|
113
|
+
),
|
|
114
|
+
share()
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const subject$ = new Subject<T>();
|
|
106
118
|
|
|
107
119
|
const setter = (value: T) => {
|
|
120
|
+
const timestamp = value.timestamp ?? this.timestamp;
|
|
121
|
+
const measure = { timestamp, kind: params.kind, payload: value };
|
|
122
|
+
|
|
108
123
|
this.worker.enqueue(() =>
|
|
109
|
-
this.descriptor.measurement.save(this.descriptor.id, [
|
|
124
|
+
this.descriptor.measurement.save(this.descriptor.id, [measure])
|
|
110
125
|
);
|
|
126
|
+
|
|
127
|
+
subject$.next({ ...value, timestamp });
|
|
111
128
|
};
|
|
112
129
|
|
|
113
|
-
return [
|
|
130
|
+
return [concat(stored$, subject$.asObservable()), setter];
|
|
114
131
|
}
|
|
115
132
|
|
|
116
133
|
useOptimizer(path: string): any {
|
|
117
|
-
return
|
|
134
|
+
return undefined;
|
|
118
135
|
}
|
|
119
136
|
|
|
120
137
|
async subscribe(instrument: Array<InstrumentSelector>): Promise<void> {
|
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,24 +1,37 @@
|
|
|
1
1
|
import { timestamp } from '../common';
|
|
2
|
+
import { Storage, StorageQueryOptions } from './storage';
|
|
2
3
|
|
|
3
4
|
export interface Measure {
|
|
4
5
|
timestamp: timestamp;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
[key: string]: any;
|
|
6
|
+
kind: string;
|
|
7
|
+
payload: any;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export
|
|
11
|
-
|
|
10
|
+
export class Measurement {
|
|
11
|
+
constructor(private readonly storage: Storage) {}
|
|
12
|
+
|
|
13
|
+
async index(): Promise<Array<number>> {
|
|
14
|
+
return (await this.storage.index()).map(it => Number(it));
|
|
15
|
+
}
|
|
16
|
+
|
|
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
|
+
}
|
|
12
27
|
|
|
13
|
-
query(
|
|
14
|
-
session
|
|
15
|
-
options: {
|
|
16
|
-
timestamp: timestamp;
|
|
17
|
-
type?: string;
|
|
18
|
-
limit: number;
|
|
19
|
-
direction: 'FORWARD' | 'BACKWARD';
|
|
20
|
-
}
|
|
21
|
-
): Promise<Measure[]>;
|
|
28
|
+
async query(session: number, options: StorageQueryOptions): Promise<Measure[]> {
|
|
29
|
+
const rows = await this.storage.query(session.toString(), options);
|
|
22
30
|
|
|
23
|
-
|
|
31
|
+
return rows.map(it => ({
|
|
32
|
+
timestamp: it.timestamp,
|
|
33
|
+
kind: it.kind,
|
|
34
|
+
payload: JSON.parse(it.json)
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
24
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,9 +1,12 @@
|
|
|
1
1
|
import { TradePatchEventHandler } from './store-trade.event';
|
|
2
|
+
import { OrderbookPatchEventHandler } from './store-orderbook.event';
|
|
2
3
|
import { timestamp } from '../../common/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 '../../common/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
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AdapterAwakeCommand } from '../adapter';
|
|
2
2
|
import { AdapterSubscribeCommand } from '../adapter';
|
|
3
3
|
import { Asset, Commision, instrumentOf } from '../domain';
|
|
4
|
-
import {
|
|
4
|
+
import { InMemoryStorage, Feed } from '../storage';
|
|
5
5
|
import { Store } from '../store';
|
|
6
6
|
import { InstrumentPatchEvent, TradePatchEvent } from '../store/event';
|
|
7
7
|
import { Adapter, AdapterContext } from '../adapter/adapter';
|
|
@@ -43,7 +43,7 @@ class DefaultAdapter extends Adapter {
|
|
|
43
43
|
const instrument = instrumentOf('binance:btc-usdt');
|
|
44
44
|
const adapter = new DefaultAdapter();
|
|
45
45
|
const store = new Store();
|
|
46
|
-
const feed = new
|
|
46
|
+
const feed = new Feed(new InMemoryStorage());
|
|
47
47
|
|
|
48
48
|
describe('backtester adapter tests', () => {
|
|
49
49
|
test('should return proper adapter name and timestamp', () => {
|
|
@@ -78,7 +78,7 @@ describe('backtester adapter tests', () => {
|
|
|
78
78
|
}
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
-
feed.
|
|
81
|
+
feed.save(instrument, [new TradePatchEvent(instrument, 100, 10, 1)]);
|
|
82
82
|
|
|
83
83
|
const sut = new BacktesterAdapter(adapter, streamer);
|
|
84
84
|
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { InstrumentSelector } from '../domain';
|
|
2
|
-
import { StoreEvent } from '../store/event';
|
|
3
|
-
import { Feed } from './feed';
|
|
4
|
-
export declare abstract class FeedInterceptor implements Feed {
|
|
5
|
-
private readonly feed;
|
|
6
|
-
constructor(feed: Feed);
|
|
7
|
-
abstract intercept(instrument: InstrumentSelector, event: StoreEvent): StoreEvent | StoreEvent[];
|
|
8
|
-
read(instrument: InstrumentSelector, from: number, to: number): Promise<StoreEvent[]>;
|
|
9
|
-
write(instrument: InstrumentSelector, events: StoreEvent[]): Promise<void>;
|
|
10
|
-
}
|
|
11
|
-
export declare function fromCandle(feed: Feed, options: {
|
|
12
|
-
orderbook: boolean;
|
|
13
|
-
trade: boolean;
|
|
14
|
-
}): Feed;
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.fromCandle = exports.FeedInterceptor = void 0;
|
|
4
|
-
const event_1 = require("../store/event");
|
|
5
|
-
class FeedInterceptor {
|
|
6
|
-
constructor(feed) {
|
|
7
|
-
this.feed = feed;
|
|
8
|
-
}
|
|
9
|
-
async read(instrument, from, to) {
|
|
10
|
-
const output = new Array();
|
|
11
|
-
for (const event of await this.feed.read(instrument, from, to)) {
|
|
12
|
-
const intercepted = this.intercept(instrument, event);
|
|
13
|
-
if (!intercepted) {
|
|
14
|
-
continue;
|
|
15
|
-
}
|
|
16
|
-
if (Array.isArray(intercepted)) {
|
|
17
|
-
output.push(...intercepted);
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
output.push(intercepted);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return output;
|
|
24
|
-
}
|
|
25
|
-
write(instrument, events) {
|
|
26
|
-
return this.feed.write(instrument, events);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
exports.FeedInterceptor = FeedInterceptor;
|
|
30
|
-
class FeedCandleInterceptor extends FeedInterceptor {
|
|
31
|
-
constructor(feed, options) {
|
|
32
|
-
super(feed);
|
|
33
|
-
this.options = options;
|
|
34
|
-
}
|
|
35
|
-
intercept(instrument, event) {
|
|
36
|
-
const output = [];
|
|
37
|
-
if (event.type == 'candle') {
|
|
38
|
-
if (this.options.orderbook) {
|
|
39
|
-
output.push(new event_1.OrderbookPatchEvent(instrument, event.close, 0, event.close, 0, event.timestamp));
|
|
40
|
-
}
|
|
41
|
-
if (this.options.trade) {
|
|
42
|
-
output.push(new event_1.TradePatchEvent(event.instrument, event.close, 0, event.timestamp));
|
|
43
|
-
}
|
|
44
|
-
return output;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function fromCandle(feed, options) {
|
|
49
|
-
return new FeedCandleInterceptor(feed, options);
|
|
50
|
-
}
|
|
51
|
-
exports.fromCandle = fromCandle;
|
|
52
|
-
//# sourceMappingURL=feed.interceptor.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"feed.interceptor.js","sourceRoot":"","sources":["../../src/storage/feed.interceptor.ts"],"names":[],"mappings":";;;AACA,0CAAkF;AAGlF,MAAsB,eAAe;IACnC,YAA6B,IAAU;QAAV,SAAI,GAAJ,IAAI,CAAM;IAAG,CAAC;IAO3C,KAAK,CAAC,IAAI,CACR,UAA8B,EAC9B,IAAY,EACZ,EAAU;QAEV,MAAM,MAAM,GAAG,IAAI,KAAK,EAAc,CAAC;QAEvC,KAAK,MAAM,KAAK,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE;YAC9D,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;YAEtD,IAAI,CAAC,WAAW,EAAE;gBAChB,SAAS;aACV;YAED,IAAI,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE;gBAC9B,MAAM,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC;aAC7B;iBAAM;gBACL,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;aAC1B;SACF;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,UAA8B,EAAE,MAAoB;QACxD,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC7C,CAAC;CACF;AAnCD,0CAmCC;AAED,MAAM,qBAAsB,SAAQ,eAAe;IACjD,YACE,IAAU,EACO,OAGhB;QAED,KAAK,CAAC,IAAI,CAAC,CAAC;QALK,YAAO,GAAP,OAAO,CAGvB;IAGH,CAAC;IAED,SAAS,CACP,UAA8B,EAC9B,KAAuB;QAEvB,MAAM,MAAM,GAAG,EAAE,CAAC;QAElB,IAAI,KAAK,CAAC,IAAI,IAAI,QAAQ,EAAE;YAC1B,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE;gBAC1B,MAAM,CAAC,IAAI,CACT,IAAI,2BAAmB,CACrB,UAAU,EACV,KAAK,CAAC,KAAK,EACX,CAAC,EACD,KAAK,CAAC,KAAK,EACX,CAAC,EACD,KAAK,CAAC,SAAS,CAChB,CACF,CAAC;aACH;YAED,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE;gBACtB,MAAM,CAAC,IAAI,CACT,IAAI,uBAAe,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CACvE,CAAC;aACH;YAED,OAAO,MAAM,CAAC;SACf;IACH,CAAC;CACF;AAED,SAAgB,UAAU,CACxB,IAAU,EACV,OAGC;IAED,OAAO,IAAI,qBAAqB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAClD,CAAC;AARD,gCAQC"}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { timestamp } from '../common';
|
|
2
|
-
import { InstrumentSelector } from '../domain';
|
|
3
|
-
import { StoreEvent } from '../store/event';
|
|
4
|
-
import { Feed } from '.';
|
|
5
|
-
export declare class InMemoryFeed implements Feed {
|
|
6
|
-
private data;
|
|
7
|
-
read(instrument: InstrumentSelector, from: timestamp, to: timestamp): Promise<StoreEvent[]>;
|
|
8
|
-
write(instrument: InstrumentSelector, events: StoreEvent[]): Promise<void>;
|
|
9
|
-
clear(): void;
|
|
10
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.InMemoryFeed = void 0;
|
|
4
|
-
class InMemoryFeed {
|
|
5
|
-
constructor() {
|
|
6
|
-
this.data = {};
|
|
7
|
-
}
|
|
8
|
-
async read(instrument, from, to) {
|
|
9
|
-
if (!this.data[instrument.toString()]) {
|
|
10
|
-
return [];
|
|
11
|
-
}
|
|
12
|
-
return this.data[instrument.toString()]
|
|
13
|
-
.filter(it => it.timestamp > from && it.timestamp <= to)
|
|
14
|
-
.sort((lhs, rhs) => lhs.timestamp - rhs.timestamp);
|
|
15
|
-
}
|
|
16
|
-
async write(instrument, events) {
|
|
17
|
-
if (!this.data[instrument.toString()]) {
|
|
18
|
-
this.data[instrument.toString()] = [];
|
|
19
|
-
}
|
|
20
|
-
const buffer = this.data[instrument.toString()];
|
|
21
|
-
for (const event of events) {
|
|
22
|
-
buffer.push(event);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
clear() {
|
|
26
|
-
this.data = {};
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
exports.InMemoryFeed = InMemoryFeed;
|
|
30
|
-
//# sourceMappingURL=in-memory.feed.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"in-memory.feed.js","sourceRoot":"","sources":["../../src/storage/in-memory.feed.ts"],"names":[],"mappings":";;;AAKA,MAAa,YAAY;IAAzB;QACU,SAAI,GAAiC,EAAE,CAAC;IA+BlD,CAAC;IA7BC,KAAK,CAAC,IAAI,CACR,UAA8B,EAC9B,IAAe,EACf,EAAa;QAEb,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC,EAAE;YACrC,OAAO,EAAE,CAAC;SACX;QAED,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;aACpC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,SAAS,IAAI,EAAE,CAAC;aACvD,IAAI,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC;IACvD,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,UAA8B,EAAE,MAAoB;QAC9D,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC,EAAE;YACrC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC;SACvC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;QAEhD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE;YAC1B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;SACpB;IACH,CAAC;IAED,KAAK;QACH,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;IACjB,CAAC;CACF;AAhCD,oCAgCC"}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { InstrumentSelector } from '../domain';
|
|
2
|
-
import { StoreEvent, OrderbookPatchEvent, TradePatchEvent } from '../store/event';
|
|
3
|
-
import { Feed } from './feed';
|
|
4
|
-
|
|
5
|
-
export abstract class FeedInterceptor implements Feed {
|
|
6
|
-
constructor(private readonly feed: Feed) {}
|
|
7
|
-
|
|
8
|
-
abstract intercept(
|
|
9
|
-
instrument: InstrumentSelector,
|
|
10
|
-
event: StoreEvent
|
|
11
|
-
): StoreEvent | StoreEvent[];
|
|
12
|
-
|
|
13
|
-
async read(
|
|
14
|
-
instrument: InstrumentSelector,
|
|
15
|
-
from: number,
|
|
16
|
-
to: number
|
|
17
|
-
): Promise<StoreEvent[]> {
|
|
18
|
-
const output = new Array<StoreEvent>();
|
|
19
|
-
|
|
20
|
-
for (const event of await this.feed.read(instrument, from, to)) {
|
|
21
|
-
const intercepted = this.intercept(instrument, event);
|
|
22
|
-
|
|
23
|
-
if (!intercepted) {
|
|
24
|
-
continue;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (Array.isArray(intercepted)) {
|
|
28
|
-
output.push(...intercepted);
|
|
29
|
-
} else {
|
|
30
|
-
output.push(intercepted);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return output;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
write(instrument: InstrumentSelector, events: StoreEvent[]): Promise<void> {
|
|
38
|
-
return this.feed.write(instrument, events);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
class FeedCandleInterceptor extends FeedInterceptor {
|
|
43
|
-
constructor(
|
|
44
|
-
feed: Feed,
|
|
45
|
-
private readonly options: {
|
|
46
|
-
orderbook: boolean;
|
|
47
|
-
trade: boolean;
|
|
48
|
-
}
|
|
49
|
-
) {
|
|
50
|
-
super(feed);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
intercept(
|
|
54
|
-
instrument: InstrumentSelector,
|
|
55
|
-
event: StoreEvent & any
|
|
56
|
-
): StoreEvent | StoreEvent[] {
|
|
57
|
-
const output = [];
|
|
58
|
-
|
|
59
|
-
if (event.type == 'candle') {
|
|
60
|
-
if (this.options.orderbook) {
|
|
61
|
-
output.push(
|
|
62
|
-
new OrderbookPatchEvent(
|
|
63
|
-
instrument,
|
|
64
|
-
event.close, // + instrument.quote.tickSize,
|
|
65
|
-
0,
|
|
66
|
-
event.close, // - instrument.quote.tickSize,
|
|
67
|
-
0,
|
|
68
|
-
event.timestamp
|
|
69
|
-
)
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (this.options.trade) {
|
|
74
|
-
output.push(
|
|
75
|
-
new TradePatchEvent(event.instrument, event.close, 0, event.timestamp)
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return output;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function fromCandle(
|
|
85
|
-
feed: Feed,
|
|
86
|
-
options: {
|
|
87
|
-
orderbook: boolean;
|
|
88
|
-
trade: boolean;
|
|
89
|
-
}
|
|
90
|
-
): Feed {
|
|
91
|
-
return new FeedCandleInterceptor(feed, options);
|
|
92
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { timestamp } from '../common';
|
|
2
|
-
import { InstrumentSelector } from '../domain';
|
|
3
|
-
import { StoreEvent } from '../store/event';
|
|
4
|
-
import { Feed } from '.';
|
|
5
|
-
|
|
6
|
-
export class InMemoryFeed implements Feed {
|
|
7
|
-
private data: Record<string, StoreEvent[]> = {};
|
|
8
|
-
|
|
9
|
-
async read(
|
|
10
|
-
instrument: InstrumentSelector,
|
|
11
|
-
from: timestamp,
|
|
12
|
-
to: timestamp
|
|
13
|
-
): Promise<StoreEvent[]> {
|
|
14
|
-
if (!this.data[instrument.toString()]) {
|
|
15
|
-
return [];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return this.data[instrument.toString()]
|
|
19
|
-
.filter(it => it.timestamp > from && it.timestamp <= to)
|
|
20
|
-
.sort((lhs, rhs) => lhs.timestamp - rhs.timestamp);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async write(instrument: InstrumentSelector, events: StoreEvent[]): Promise<void> {
|
|
24
|
-
if (!this.data[instrument.toString()]) {
|
|
25
|
-
this.data[instrument.toString()] = [];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const buffer = this.data[instrument.toString()];
|
|
29
|
-
|
|
30
|
-
for (const event of events) {
|
|
31
|
-
buffer.push(event);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
clear() {
|
|
36
|
-
this.data = {};
|
|
37
|
-
}
|
|
38
|
-
}
|