@quantform/core 0.7.12 → 0.7.17
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/lib/backtest/index.d.ts +7 -0
- package/lib/backtest/index.d.ts.map +1 -0
- package/lib/backtest/index.js +22 -0
- package/lib/backtest/use-backtest-options.d.ts +15 -0
- package/lib/backtest/use-backtest-options.d.ts.map +1 -0
- package/lib/backtest/use-backtest-options.js +20 -0
- package/lib/backtest/use-backtest-query-buffer.d.ts +15 -0
- package/lib/backtest/use-backtest-query-buffer.d.ts.map +1 -0
- package/lib/backtest/use-backtest-query-buffer.js +52 -0
- package/lib/backtest/use-backtest-query-cursor.d.ts +30 -0
- package/lib/backtest/use-backtest-query-cursor.d.ts.map +1 -0
- package/lib/backtest/use-backtest-query-cursor.js +43 -0
- package/lib/backtest/use-backtest-scheduler.d.ts +28 -0
- package/lib/backtest/use-backtest-scheduler.d.ts.map +1 -0
- package/lib/backtest/use-backtest-scheduler.js +77 -0
- package/lib/backtest/use-backtest-storage.d.ts +20 -0
- package/lib/backtest/use-backtest-storage.d.ts.map +1 -0
- package/lib/backtest/use-backtest-storage.js +63 -0
- package/lib/backtest/use-backtest-sync.d.ts +3 -0
- package/lib/backtest/use-backtest-sync.d.ts.map +1 -0
- package/lib/backtest/use-backtest-sync.js +16 -0
- package/lib/backtest/use-backtest.d.ts +23 -0
- package/lib/backtest/use-backtest.d.ts.map +1 -0
- package/lib/backtest/use-backtest.js +14 -0
- package/lib/backtest/when-backtest-finished.d.ts +2 -0
- package/lib/backtest/when-backtest-finished.d.ts.map +1 -0
- package/lib/backtest/when-backtest-finished.js +15 -0
- package/lib/cli/index.js +0 -9
- package/lib/cli/replay.js +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -0
- package/lib/uri.d.ts +11 -0
- package/lib/uri.d.ts.map +1 -0
- package/lib/uri.js +15 -0
- package/lib/use-timestamp.d.ts.map +1 -1
- package/lib/use-timestamp.js +2 -2
- package/package.json +1 -1
- package/src/backtest/index.ts +6 -0
- package/src/backtest/use-backtest-options.ts +23 -0
- package/src/backtest/use-backtest-query-buffer.ts +50 -0
- package/src/backtest/use-backtest-query-cursor.ts +40 -0
- package/src/backtest/use-backtest-scheduler.ts +98 -0
- package/src/backtest/use-backtest-storage.ts +79 -0
- package/src/backtest/use-backtest-sync.ts +19 -0
- package/src/backtest/use-backtest.ts +26 -0
- package/src/backtest/when-backtest-finished.ts +20 -0
- package/src/cli/index.ts +0 -10
- package/src/cli/replay.ts +1 -1
- package/src/index.ts +2 -0
- package/src/uri.ts +22 -0
- package/src/use-timestamp.ts +3 -2
- package/lib/cli/pull.d.ts +0 -2
- package/lib/cli/pull.d.ts.map +0 -1
- package/lib/cli/pull.js +0 -28
- package/src/cli/pull.ts +0 -15
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './use-backtest';
|
|
2
|
+
export * from './use-backtest-options';
|
|
3
|
+
export * from './use-backtest-scheduler';
|
|
4
|
+
export * from './use-backtest-storage';
|
|
5
|
+
export * from './use-backtest-sync';
|
|
6
|
+
export * from './when-backtest-finished';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backtest/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./use-backtest"), exports);
|
|
18
|
+
__exportStar(require("./use-backtest-options"), exports);
|
|
19
|
+
__exportStar(require("./use-backtest-scheduler"), exports);
|
|
20
|
+
__exportStar(require("./use-backtest-storage"), exports);
|
|
21
|
+
__exportStar(require("./use-backtest-sync"), exports);
|
|
22
|
+
__exportStar(require("./when-backtest-finished"), exports);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Dependency } from '../module';
|
|
2
|
+
type BacktestOptions = {
|
|
3
|
+
from: number;
|
|
4
|
+
to: number;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
*/
|
|
9
|
+
export declare function backtestOptions(options: BacktestOptions): Dependency;
|
|
10
|
+
/**
|
|
11
|
+
* Will return current backtest execution options.
|
|
12
|
+
*/
|
|
13
|
+
export declare const useBacktestOptions: () => BacktestOptions;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=use-backtest-options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-backtest-options.d.ts","sourceRoot":"","sources":["../../src/backtest/use-backtest-options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAc,MAAM,aAAa,CAAC;AAIrD,KAAK,eAAe,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,eAAe,GAAG,UAAU,CAKpE;AAED;;GAEG;AACH,eAAO,MAAM,kBAAkB,uBAAoD,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useBacktestOptions = exports.backtestOptions = void 0;
|
|
4
|
+
const module_1 = require("../module");
|
|
5
|
+
const injectionToken = Symbol('backtest-options');
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
*/
|
|
9
|
+
function backtestOptions(options) {
|
|
10
|
+
return {
|
|
11
|
+
provide: injectionToken,
|
|
12
|
+
useValue: options
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
exports.backtestOptions = backtestOptions;
|
|
16
|
+
/**
|
|
17
|
+
* Will return current backtest execution options.
|
|
18
|
+
*/
|
|
19
|
+
const useBacktestOptions = () => (0, module_1.useContext)(injectionToken);
|
|
20
|
+
exports.useBacktestOptions = useBacktestOptions;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BacktestStorage } from './use-backtest';
|
|
2
|
+
export declare const useBacktestQueryBuffer: <T>(storage: BacktestStorage<T>) => {
|
|
3
|
+
size(): number;
|
|
4
|
+
peek(): {
|
|
5
|
+
timestamp: number;
|
|
6
|
+
payload: T;
|
|
7
|
+
};
|
|
8
|
+
dequeue(): {
|
|
9
|
+
timestamp: number;
|
|
10
|
+
payload: T;
|
|
11
|
+
};
|
|
12
|
+
completed(): boolean;
|
|
13
|
+
fetchNextPage(): Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=use-backtest-query-buffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-backtest-query-buffer.d.ts","sourceRoot":"","sources":["../../src/backtest/use-backtest-query-buffer.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,eAAO,MAAM,sBAAsB;;;mBAGJ,MAAM;;;;mBAAN,MAAM;;;;;CAwCnC,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.useBacktestQueryBuffer = void 0;
|
|
13
|
+
const replay_1 = require("../replay");
|
|
14
|
+
const storage_1 = require("../storage");
|
|
15
|
+
const with_memo_1 = require("../with-memo");
|
|
16
|
+
exports.useBacktestQueryBuffer = (0, with_memo_1.withMemo)((storage) => {
|
|
17
|
+
const { from, to } = (0, replay_1.useReplayOptions)();
|
|
18
|
+
let page = [];
|
|
19
|
+
let index = 0;
|
|
20
|
+
let completed = false;
|
|
21
|
+
let count = 0;
|
|
22
|
+
return {
|
|
23
|
+
size() {
|
|
24
|
+
return page.length - index;
|
|
25
|
+
},
|
|
26
|
+
peek() {
|
|
27
|
+
return page[index];
|
|
28
|
+
},
|
|
29
|
+
dequeue() {
|
|
30
|
+
return page[index++];
|
|
31
|
+
},
|
|
32
|
+
completed() {
|
|
33
|
+
return completed;
|
|
34
|
+
},
|
|
35
|
+
fetchNextPage() {
|
|
36
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
if (completed) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
index = 0;
|
|
41
|
+
page = yield storage.query({
|
|
42
|
+
where: { timestamp: (0, storage_1.between)(from, to) },
|
|
43
|
+
limit: 10000,
|
|
44
|
+
offset: count,
|
|
45
|
+
orderBy: 'ASC'
|
|
46
|
+
});
|
|
47
|
+
count += page.length;
|
|
48
|
+
completed = page.length === 0;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BacktestStorage } from './use-backtest';
|
|
2
|
+
export declare const useBacktestQueryCursor: () => {
|
|
3
|
+
get<T>(query: BacktestStorage<T>): {
|
|
4
|
+
size(): number;
|
|
5
|
+
peek(): {
|
|
6
|
+
timestamp: number;
|
|
7
|
+
payload: T;
|
|
8
|
+
};
|
|
9
|
+
dequeue(): {
|
|
10
|
+
timestamp: number;
|
|
11
|
+
payload: T;
|
|
12
|
+
};
|
|
13
|
+
completed(): boolean;
|
|
14
|
+
fetchNextPage(): Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
cursor(): Promise<{
|
|
17
|
+
size(): number;
|
|
18
|
+
peek(): {
|
|
19
|
+
timestamp: number;
|
|
20
|
+
payload: any;
|
|
21
|
+
};
|
|
22
|
+
dequeue(): {
|
|
23
|
+
timestamp: number;
|
|
24
|
+
payload: any;
|
|
25
|
+
};
|
|
26
|
+
completed(): boolean;
|
|
27
|
+
fetchNextPage(): Promise<void>;
|
|
28
|
+
} | undefined>;
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=use-backtest-query-cursor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-backtest-query-cursor.d.ts","sourceRoot":"","sources":["../../src/backtest/use-backtest-query-cursor.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGjD,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCjC,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.useBacktestQueryCursor = void 0;
|
|
13
|
+
const with_memo_1 = require("../with-memo");
|
|
14
|
+
const use_backtest_query_buffer_1 = require("./use-backtest-query-buffer");
|
|
15
|
+
exports.useBacktestQueryCursor = (0, with_memo_1.withMemo)(() => {
|
|
16
|
+
const cursors = Array.of();
|
|
17
|
+
return {
|
|
18
|
+
get(query) {
|
|
19
|
+
const buffer = (0, use_backtest_query_buffer_1.useBacktestQueryBuffer)(query);
|
|
20
|
+
cursors.push(buffer);
|
|
21
|
+
return buffer;
|
|
22
|
+
},
|
|
23
|
+
cursor() {
|
|
24
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
+
let current;
|
|
26
|
+
for (const cursor of cursors) {
|
|
27
|
+
if (cursor.completed()) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (cursor.size() == 0) {
|
|
31
|
+
yield cursor.fetchNextPage();
|
|
32
|
+
}
|
|
33
|
+
if (cursor.peek()) {
|
|
34
|
+
if (!current || current.peek().timestamp > cursor.peek().timestamp) {
|
|
35
|
+
current = cursor;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return current;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Observable } from 'rxjs';
|
|
2
|
+
import { BacktestStorage } from './use-backtest';
|
|
3
|
+
export declare const useBacktestScheduler: () => {
|
|
4
|
+
stream: Observable<[{
|
|
5
|
+
size(): number;
|
|
6
|
+
peek(): {
|
|
7
|
+
timestamp: number;
|
|
8
|
+
payload: any;
|
|
9
|
+
};
|
|
10
|
+
dequeue(): {
|
|
11
|
+
timestamp: number;
|
|
12
|
+
payload: any;
|
|
13
|
+
};
|
|
14
|
+
completed(): boolean;
|
|
15
|
+
fetchNextPage(): Promise<void>;
|
|
16
|
+
}, {
|
|
17
|
+
timestamp: number;
|
|
18
|
+
payload: any;
|
|
19
|
+
}]>;
|
|
20
|
+
timestamp(): number;
|
|
21
|
+
stop(): void;
|
|
22
|
+
tryContinue: () => void;
|
|
23
|
+
watch<T>(query: BacktestStorage<T>): Observable<{
|
|
24
|
+
timestamp: number;
|
|
25
|
+
payload: T;
|
|
26
|
+
}>;
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=use-backtest-scheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-backtest-scheduler.d.ts","sourceRoot":"","sources":["../../src/backtest/use-backtest-scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,UAAU,EAAW,MAAM,MAAM,CAAC;AAI/D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAKjD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;mBASiC,MAAM;iBAAW,GAAG;;;;;;mBA+DrB,MAAM;;;CAgBrE,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.useBacktestScheduler = void 0;
|
|
13
|
+
const rxjs_1 = require("rxjs");
|
|
14
|
+
const with_memo_1 = require("../with-memo");
|
|
15
|
+
const use_backtest_options_1 = require("./use-backtest-options");
|
|
16
|
+
const use_backtest_query_cursor_1 = require("./use-backtest-query-cursor");
|
|
17
|
+
exports.useBacktestScheduler = (0, with_memo_1.withMemo)(() => {
|
|
18
|
+
const { from } = (0, use_backtest_options_1.useBacktestOptions)();
|
|
19
|
+
const { get, cursor } = (0, use_backtest_query_cursor_1.useBacktestQueryCursor)();
|
|
20
|
+
let timestamp = from;
|
|
21
|
+
let stopAcquire = 1;
|
|
22
|
+
let processing = false;
|
|
23
|
+
const stream$ = new rxjs_1.Subject();
|
|
24
|
+
const processNext = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
25
|
+
const storage = yield cursor();
|
|
26
|
+
if (!storage || !storage.peek()) {
|
|
27
|
+
stream$.complete();
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const sample = storage.dequeue();
|
|
31
|
+
timestamp = sample.timestamp;
|
|
32
|
+
stream$.next([storage, sample]);
|
|
33
|
+
return true;
|
|
34
|
+
});
|
|
35
|
+
const next = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
36
|
+
if (processing) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
processing = true;
|
|
40
|
+
while (stopAcquire === 0) {
|
|
41
|
+
if (!(yield processNext())) {
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
yield new Promise(it => setImmediate(it));
|
|
45
|
+
}
|
|
46
|
+
processing = false;
|
|
47
|
+
});
|
|
48
|
+
const tryContinue = () => {
|
|
49
|
+
if (stopAcquire === 0) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
stopAcquire--;
|
|
53
|
+
if (stopAcquire === 0) {
|
|
54
|
+
next();
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
return {
|
|
58
|
+
stream: stream$.asObservable(),
|
|
59
|
+
timestamp() {
|
|
60
|
+
return timestamp;
|
|
61
|
+
},
|
|
62
|
+
stop() {
|
|
63
|
+
stopAcquire++;
|
|
64
|
+
},
|
|
65
|
+
tryContinue,
|
|
66
|
+
watch(query) {
|
|
67
|
+
const storage = get(query);
|
|
68
|
+
return (0, rxjs_1.defer)(() => {
|
|
69
|
+
tryContinue();
|
|
70
|
+
return stream$.pipe((0, rxjs_1.filter)(([cur]) => cur === storage), (0, rxjs_1.map)(([, it]) => ({
|
|
71
|
+
timestamp: it.timestamp,
|
|
72
|
+
payload: it.payload
|
|
73
|
+
})));
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { InferQueryObject, Query, QueryObject, QueryObjectType } from '../storage';
|
|
2
|
+
import { Uri } from '../uri';
|
|
3
|
+
import { BacktestStorage } from './use-backtest';
|
|
4
|
+
export type BacktestStorageQuery<V> = {
|
|
5
|
+
sync: <T extends QueryObjectType<K>, K extends QueryObject>(query: Query<InferQueryObject<T>> & {
|
|
6
|
+
where: {
|
|
7
|
+
timestamp: {
|
|
8
|
+
min: number;
|
|
9
|
+
max: number;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
}, storage: {
|
|
13
|
+
save: (objects: {
|
|
14
|
+
timestamp: number;
|
|
15
|
+
payload: V;
|
|
16
|
+
}[]) => Promise<void>;
|
|
17
|
+
}) => Promise<void>;
|
|
18
|
+
};
|
|
19
|
+
export declare function useBacktestStorage<V, P extends Record<string, string | number>>(uri: Uri<P>, { sync }: BacktestStorageQuery<V>): BacktestStorage<V>;
|
|
20
|
+
//# sourceMappingURL=use-backtest-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-backtest-storage.d.ts","sourceRoot":"","sources":["../../src/backtest/use-backtest-storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,gBAAgB,EAChB,KAAK,EACL,WAAW,EACX,eAAe,EAGhB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,MAAM,MAAM,oBAAoB,CAAC,CAAC,IAAI;IACpC,IAAI,EAAE,CAAC,CAAC,SAAS,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,WAAW,EACxD,KAAK,EAAE,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,GAAG;QAClC,KAAK,EAAE;YAAE,SAAS,EAAE;gBAAE,GAAG,EAAE,MAAM,CAAC;gBAAC,GAAG,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,CAAC;KACpD,EACD,OAAO,EAAE;QAAE,IAAI,EAAE,CAAC,OAAO,EAAE;YAAE,SAAS,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,CAAC,CAAA;SAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,KAC/E,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB,CAAC;AASF,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAC7E,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,EACX,EAAE,IAAI,EAAE,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAChC,eAAe,CAAC,CAAC,CAAC,CA8CpB"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.useBacktestStorage = void 0;
|
|
13
|
+
const storage_1 = require("../storage");
|
|
14
|
+
const storageIndexObject = storage_1.Storage.createObject('index://range', {
|
|
15
|
+
timestamp: 'number',
|
|
16
|
+
uri: 'string',
|
|
17
|
+
min: 'number',
|
|
18
|
+
max: 'number'
|
|
19
|
+
});
|
|
20
|
+
function useBacktestStorage(uri, { sync }) {
|
|
21
|
+
const storage = (0, storage_1.useStorage)(['backtest']);
|
|
22
|
+
const storageObjectKey = uri.query;
|
|
23
|
+
const storageObject = storage_1.Storage.createObject(storageObjectKey, {
|
|
24
|
+
timestamp: 'number',
|
|
25
|
+
payload: 'string'
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
query(query) {
|
|
29
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
const [index] = yield storage.query(storageIndexObject, {
|
|
31
|
+
limit: 1,
|
|
32
|
+
where: { uri: (0, storage_1.eq)(storageObjectKey) }
|
|
33
|
+
});
|
|
34
|
+
const { min, max } = query.where.timestamp;
|
|
35
|
+
if (!index || min < index.min || max > index.max) {
|
|
36
|
+
yield sync(query, {
|
|
37
|
+
save(objects) {
|
|
38
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
39
|
+
yield storage.save(storageObject, objects.map(it => ({
|
|
40
|
+
timestamp: it.timestamp,
|
|
41
|
+
payload: JSON.stringify(it.payload)
|
|
42
|
+
})));
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
yield storage.save(storageIndexObject, [
|
|
47
|
+
{
|
|
48
|
+
timestamp: 0,
|
|
49
|
+
max,
|
|
50
|
+
min,
|
|
51
|
+
uri: storageObjectKey
|
|
52
|
+
}
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
return (yield storage.query(storageObject, query)).map(it => ({
|
|
56
|
+
timestamp: it.timestamp,
|
|
57
|
+
payload: JSON.parse(it.payload)
|
|
58
|
+
}));
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
exports.useBacktestStorage = useBacktestStorage;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-backtest-sync.d.ts","sourceRoot":"","sources":["../../src/backtest/use-backtest-sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,UAAU,EAAE,MAAM,MAAM,CAAC;AAM5C,wBAAgB,eAAe,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAYtE"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useBacktestSync = void 0;
|
|
4
|
+
const rxjs_1 = require("rxjs");
|
|
5
|
+
const use_execution_mode_1 = require("../use-execution-mode");
|
|
6
|
+
const use_backtest_scheduler_1 = require("./use-backtest-scheduler");
|
|
7
|
+
function useBacktestSync(input) {
|
|
8
|
+
const { isReplay } = (0, use_execution_mode_1.useExecutionMode)();
|
|
9
|
+
if (!isReplay) {
|
|
10
|
+
return input;
|
|
11
|
+
}
|
|
12
|
+
const { stop, tryContinue } = (0, use_backtest_scheduler_1.useBacktestScheduler)();
|
|
13
|
+
stop();
|
|
14
|
+
return input.pipe((0, rxjs_1.finalize)(() => tryContinue()));
|
|
15
|
+
}
|
|
16
|
+
exports.useBacktestSync = useBacktestSync;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Observable } from 'rxjs';
|
|
2
|
+
import { Query, QueryObject } from '../storage';
|
|
3
|
+
export interface BacktestStorage<V> {
|
|
4
|
+
query(query: Query<QueryObject> & {
|
|
5
|
+
where: {
|
|
6
|
+
timestamp: {
|
|
7
|
+
min: number;
|
|
8
|
+
max: number;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
}): Promise<{
|
|
12
|
+
timestamp: number;
|
|
13
|
+
payload: V;
|
|
14
|
+
}[]>;
|
|
15
|
+
}
|
|
16
|
+
export declare function useBacktest<T>(input: Observable<{
|
|
17
|
+
timestamp: number;
|
|
18
|
+
payload: T;
|
|
19
|
+
}>, query: BacktestStorage<T>): Observable<{
|
|
20
|
+
timestamp: number;
|
|
21
|
+
payload: T;
|
|
22
|
+
}>;
|
|
23
|
+
//# sourceMappingURL=use-backtest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-backtest.d.ts","sourceRoot":"","sources":["../../src/backtest/use-backtest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAElC,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAKlD,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,KAAK,CACH,KAAK,EAAE,KAAK,CAAC,WAAW,CAAC,GAAG;QAAE,KAAK,EAAE;YAAE,SAAS,EAAE;gBAAE,GAAG,EAAE,MAAM,CAAC;gBAAC,GAAG,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,CAAA;KAAE,GACjF,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC,CAAA;KAAE,EAAE,CAAC,CAAC;CACjD;AAED,wBAAgB,WAAW,CAAC,CAAC,EAC3B,KAAK,EAAE,UAAU,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,CAAC,CAAA;CAAE,CAAC,EACpD,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC;eADM,MAAM;aAAW,CAAC;GAWlD"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useBacktest = void 0;
|
|
4
|
+
const use_execution_mode_1 = require("../use-execution-mode");
|
|
5
|
+
const use_backtest_scheduler_1 = require("./use-backtest-scheduler");
|
|
6
|
+
function useBacktest(input, query) {
|
|
7
|
+
const { isReplay } = (0, use_execution_mode_1.useExecutionMode)();
|
|
8
|
+
if (isReplay) {
|
|
9
|
+
const { watch } = (0, use_backtest_scheduler_1.useBacktestScheduler)();
|
|
10
|
+
return watch(query);
|
|
11
|
+
}
|
|
12
|
+
return input;
|
|
13
|
+
}
|
|
14
|
+
exports.useBacktest = useBacktest;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"when-backtest-finished.d.ts","sourceRoot":"","sources":["../../src/backtest/when-backtest-finished.ts"],"names":[],"mappings":"AAMA,wBAAgB,oBAAoB,uCAanC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.whenBacktestFinished = void 0;
|
|
4
|
+
const rxjs_1 = require("rxjs");
|
|
5
|
+
const use_execution_mode_1 = require("../use-execution-mode");
|
|
6
|
+
const use_backtest_scheduler_1 = require("./use-backtest-scheduler");
|
|
7
|
+
function whenBacktestFinished() {
|
|
8
|
+
const { isReplay } = (0, use_execution_mode_1.useExecutionMode)();
|
|
9
|
+
if (!isReplay) {
|
|
10
|
+
return new rxjs_1.Subject().asObservable();
|
|
11
|
+
}
|
|
12
|
+
const { stream } = (0, use_backtest_scheduler_1.useBacktestScheduler)();
|
|
13
|
+
return (0, rxjs_1.from)(stream).pipe((0, rxjs_1.last)(), (0, rxjs_1.map)(() => true));
|
|
14
|
+
}
|
|
15
|
+
exports.whenBacktestFinished = whenBacktestFinished;
|
package/lib/cli/index.js
CHANGED
|
@@ -19,7 +19,6 @@ const node_watch_1 = __importDefault(require("node-watch"));
|
|
|
19
19
|
const build_1 = __importDefault(require("../cli/build"));
|
|
20
20
|
const live_1 = __importDefault(require("../cli/live"));
|
|
21
21
|
const paper_1 = __importDefault(require("../cli/paper"));
|
|
22
|
-
const pull_1 = __importDefault(require("../cli/pull"));
|
|
23
22
|
const replay_1 = __importDefault(require("../cli/replay"));
|
|
24
23
|
commander_1.program
|
|
25
24
|
.command('build')
|
|
@@ -52,14 +51,6 @@ commander_1.program
|
|
|
52
51
|
.option('-t, --to <to>', 'date to')
|
|
53
52
|
.option('-w', 'watch mode')
|
|
54
53
|
.action(replay_1.default);
|
|
55
|
-
commander_1.program
|
|
56
|
-
.command('pull')
|
|
57
|
-
.description('pulls instrument historical data to storage')
|
|
58
|
-
.argument('<name>', 'strategy to execute')
|
|
59
|
-
.argument('<instrument>', 'instrument to import')
|
|
60
|
-
.option('-f, --from <from>', 'date from')
|
|
61
|
-
.option('-t, --to <to>', 'date to')
|
|
62
|
-
.action(pull_1.default);
|
|
63
54
|
commander_1.program.name('quantform').description('quantform tools');
|
|
64
55
|
if (process.argv.length < 3) {
|
|
65
56
|
commander_1.program.help();
|
package/lib/cli/replay.js
CHANGED
|
@@ -24,7 +24,7 @@ function default_1(name, options) {
|
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
const from = options.from ? new Date(options.from).getTime() : 0;
|
|
27
|
-
const to = options.to ? new Date(options.to).getTime() :
|
|
27
|
+
const to = options.to ? new Date(options.to).getTime() : 1893452400; // 01-01-2030;
|
|
28
28
|
const script = new script_1.Script(name, [
|
|
29
29
|
session_1.useSession.options({ id: (_a = options.id) !== null && _a !== void 0 ? _a : Date.now().toString() }),
|
|
30
30
|
(0, replay_1.replayOptions)({ from, to }),
|
package/lib/index.d.ts
CHANGED
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,WAAW,CAAC;AAC1B,cAAc,yBAAyB,CAAC;AACxC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,WAAW,CAAC;AAC1B,cAAc,yBAAyB,CAAC;AACxC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC"}
|
package/lib/index.js
CHANGED
|
@@ -34,3 +34,5 @@ __exportStar(require("./with-memo"), exports);
|
|
|
34
34
|
__exportStar(require("./session"), exports);
|
|
35
35
|
__exportStar(require("./strategy"), exports);
|
|
36
36
|
__exportStar(require("./operators"), exports);
|
|
37
|
+
__exportStar(require("./backtest"), exports);
|
|
38
|
+
__exportStar(require("./uri"), exports);
|
package/lib/uri.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type Uri<T> = {
|
|
2
|
+
base: `${string}://${string}`;
|
|
3
|
+
params: T;
|
|
4
|
+
query: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function uri<T extends Record<string, string | number>>(base: `${string}://${string}`, params: T): {
|
|
7
|
+
base: `${string}://${string}`;
|
|
8
|
+
params: T;
|
|
9
|
+
query: string;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=uri.d.ts.map
|
package/lib/uri.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uri.d.ts","sourceRoot":"","sources":["../src/uri.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI;IACnB,IAAI,EAAE,GAAG,MAAM,MAAM,MAAM,EAAE,CAAC;IAC9B,MAAM,EAAE,CAAC,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,wBAAgB,GAAG,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAC3D,IAAI,EAAE,GAAG,MAAM,MAAM,MAAM,EAAE,EAC7B,MAAM,EAAE,CAAC;;;;EAaV"}
|
package/lib/uri.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.uri = void 0;
|
|
4
|
+
function uri(base, params) {
|
|
5
|
+
const search = new URLSearchParams();
|
|
6
|
+
for (const [k, v] of Object.entries(params)) {
|
|
7
|
+
search.set(k, String(v));
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
base,
|
|
11
|
+
params,
|
|
12
|
+
query: `${base}?${search.toString()}`
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
exports.uri = uri;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-timestamp.d.ts","sourceRoot":"","sources":["../src/use-timestamp.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-timestamp.d.ts","sourceRoot":"","sources":["../src/use-timestamp.ts"],"names":[],"mappings":"AAIA,wBAAgB,YAAY,WAQ3B"}
|
package/lib/use-timestamp.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.useTimestamp = void 0;
|
|
4
|
-
const replay_1 = require("./replay");
|
|
5
4
|
const use_execution_mode_1 = require("./use-execution-mode");
|
|
5
|
+
const backtest_1 = require("./backtest");
|
|
6
6
|
function useTimestamp() {
|
|
7
7
|
const { isReplay } = (0, use_execution_mode_1.useExecutionMode)();
|
|
8
8
|
if (isReplay) {
|
|
9
|
-
return (0,
|
|
9
|
+
return (0, backtest_1.useBacktestScheduler)().timestamp();
|
|
10
10
|
}
|
|
11
11
|
return Date.now();
|
|
12
12
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Dependency, useContext } from '@lib/module';
|
|
2
|
+
|
|
3
|
+
const injectionToken = Symbol('backtest-options');
|
|
4
|
+
|
|
5
|
+
type BacktestOptions = {
|
|
6
|
+
from: number;
|
|
7
|
+
to: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
*/
|
|
13
|
+
export function backtestOptions(options: BacktestOptions): Dependency {
|
|
14
|
+
return {
|
|
15
|
+
provide: injectionToken,
|
|
16
|
+
useValue: options
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Will return current backtest execution options.
|
|
22
|
+
*/
|
|
23
|
+
export const useBacktestOptions = () => useContext<BacktestOptions>(injectionToken);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useReplayOptions } from '@lib/replay';
|
|
2
|
+
import { between } from '@lib/storage';
|
|
3
|
+
import { withMemo } from '@lib/with-memo';
|
|
4
|
+
|
|
5
|
+
import { BacktestStorage } from './use-backtest';
|
|
6
|
+
|
|
7
|
+
export const useBacktestQueryBuffer = withMemo(<T>(storage: BacktestStorage<T>) => {
|
|
8
|
+
const { from, to } = useReplayOptions();
|
|
9
|
+
|
|
10
|
+
let page: Array<{ timestamp: number; payload: T }> = [];
|
|
11
|
+
let index = 0;
|
|
12
|
+
let completed = false;
|
|
13
|
+
let count = 0;
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
size() {
|
|
17
|
+
return page.length - index;
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
peek() {
|
|
21
|
+
return page[index];
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
dequeue() {
|
|
25
|
+
return page[index++];
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
completed() {
|
|
29
|
+
return completed;
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
async fetchNextPage() {
|
|
33
|
+
if (completed) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
index = 0;
|
|
38
|
+
|
|
39
|
+
page = await storage.query({
|
|
40
|
+
where: { timestamp: between(from, to) },
|
|
41
|
+
limit: 10000,
|
|
42
|
+
offset: count,
|
|
43
|
+
orderBy: 'ASC'
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
count += page.length;
|
|
47
|
+
completed = page.length === 0;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { withMemo } from '@lib/with-memo';
|
|
2
|
+
|
|
3
|
+
import { BacktestStorage } from './use-backtest';
|
|
4
|
+
import { useBacktestQueryBuffer } from './use-backtest-query-buffer';
|
|
5
|
+
|
|
6
|
+
export const useBacktestQueryCursor = withMemo(() => {
|
|
7
|
+
const cursors = Array.of<ReturnType<typeof useBacktestQueryBuffer<any>>>();
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
get<T>(query: BacktestStorage<T>) {
|
|
11
|
+
const buffer = useBacktestQueryBuffer<T>(query);
|
|
12
|
+
|
|
13
|
+
cursors.push(buffer);
|
|
14
|
+
|
|
15
|
+
return buffer;
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
async cursor() {
|
|
19
|
+
let current: ReturnType<typeof useBacktestQueryBuffer<any>> | undefined;
|
|
20
|
+
|
|
21
|
+
for (const cursor of cursors) {
|
|
22
|
+
if (cursor.completed()) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (cursor.size() == 0) {
|
|
27
|
+
await cursor.fetchNextPage();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (cursor.peek()) {
|
|
31
|
+
if (!current || current.peek().timestamp > cursor.peek().timestamp) {
|
|
32
|
+
current = cursor;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return current;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { defer, filter, map, Observable, Subject } from 'rxjs';
|
|
2
|
+
|
|
3
|
+
import { withMemo } from '@lib/with-memo';
|
|
4
|
+
|
|
5
|
+
import { BacktestStorage } from './use-backtest';
|
|
6
|
+
import { useBacktestOptions } from './use-backtest-options';
|
|
7
|
+
import { useBacktestQueryBuffer } from './use-backtest-query-buffer';
|
|
8
|
+
import { useBacktestQueryCursor } from './use-backtest-query-cursor';
|
|
9
|
+
|
|
10
|
+
export const useBacktestScheduler = withMemo(() => {
|
|
11
|
+
const { from } = useBacktestOptions();
|
|
12
|
+
const { get, cursor } = useBacktestQueryCursor();
|
|
13
|
+
|
|
14
|
+
let timestamp = from;
|
|
15
|
+
let stopAcquire = 1;
|
|
16
|
+
let processing = false;
|
|
17
|
+
|
|
18
|
+
const stream$ = new Subject<
|
|
19
|
+
[ReturnType<typeof useBacktestQueryBuffer<any>>, { timestamp: number; payload: any }]
|
|
20
|
+
>();
|
|
21
|
+
|
|
22
|
+
const processNext = async () => {
|
|
23
|
+
const storage = await cursor();
|
|
24
|
+
|
|
25
|
+
if (!storage || !storage.peek()) {
|
|
26
|
+
stream$.complete();
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const sample = storage.dequeue();
|
|
31
|
+
|
|
32
|
+
timestamp = sample.timestamp;
|
|
33
|
+
|
|
34
|
+
stream$.next([storage, sample]);
|
|
35
|
+
|
|
36
|
+
return true;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const next = async () => {
|
|
40
|
+
if (processing) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
processing = true;
|
|
45
|
+
|
|
46
|
+
while (stopAcquire === 0) {
|
|
47
|
+
if (!(await processNext())) {
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await new Promise(it => setImmediate(it));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
processing = false;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const tryContinue = () => {
|
|
58
|
+
if (stopAcquire === 0) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
stopAcquire--;
|
|
63
|
+
|
|
64
|
+
if (stopAcquire === 0) {
|
|
65
|
+
next();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
stream: stream$.asObservable(),
|
|
71
|
+
|
|
72
|
+
timestamp() {
|
|
73
|
+
return timestamp;
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
stop() {
|
|
77
|
+
stopAcquire++;
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
tryContinue,
|
|
81
|
+
|
|
82
|
+
watch<T>(query: BacktestStorage<T>): Observable<{ timestamp: number; payload: T }> {
|
|
83
|
+
const storage = get<T>(query);
|
|
84
|
+
|
|
85
|
+
return defer(() => {
|
|
86
|
+
tryContinue();
|
|
87
|
+
|
|
88
|
+
return stream$.pipe(
|
|
89
|
+
filter(([cur]) => cur === storage),
|
|
90
|
+
map(([, it]) => ({
|
|
91
|
+
timestamp: it.timestamp,
|
|
92
|
+
payload: it.payload as T
|
|
93
|
+
}))
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
eq,
|
|
3
|
+
InferQueryObject,
|
|
4
|
+
Query,
|
|
5
|
+
QueryObject,
|
|
6
|
+
QueryObjectType,
|
|
7
|
+
Storage,
|
|
8
|
+
useStorage
|
|
9
|
+
} from '@lib/storage';
|
|
10
|
+
import { Uri } from '@lib/uri';
|
|
11
|
+
|
|
12
|
+
import { BacktestStorage } from './use-backtest';
|
|
13
|
+
|
|
14
|
+
export type BacktestStorageQuery<V> = {
|
|
15
|
+
sync: <T extends QueryObjectType<K>, K extends QueryObject>(
|
|
16
|
+
query: Query<InferQueryObject<T>> & {
|
|
17
|
+
where: { timestamp: { min: number; max: number } };
|
|
18
|
+
},
|
|
19
|
+
storage: { save: (objects: { timestamp: number; payload: V }[]) => Promise<void> }
|
|
20
|
+
) => Promise<void>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const storageIndexObject = Storage.createObject('index://range', {
|
|
24
|
+
timestamp: 'number',
|
|
25
|
+
uri: 'string',
|
|
26
|
+
min: 'number',
|
|
27
|
+
max: 'number'
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export function useBacktestStorage<V, P extends Record<string, string | number>>(
|
|
31
|
+
uri: Uri<P>,
|
|
32
|
+
{ sync }: BacktestStorageQuery<V>
|
|
33
|
+
): BacktestStorage<V> {
|
|
34
|
+
const storage = useStorage(['backtest']);
|
|
35
|
+
const storageObjectKey = uri.query;
|
|
36
|
+
const storageObject = Storage.createObject(storageObjectKey, {
|
|
37
|
+
timestamp: 'number',
|
|
38
|
+
payload: 'string'
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
async query(query) {
|
|
43
|
+
const [index] = await storage.query(storageIndexObject, {
|
|
44
|
+
limit: 1,
|
|
45
|
+
where: { uri: eq(storageObjectKey) }
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const { min, max } = query.where.timestamp;
|
|
49
|
+
|
|
50
|
+
if (!index || min < index.min || max > index.max) {
|
|
51
|
+
await sync(query, {
|
|
52
|
+
async save(objects) {
|
|
53
|
+
await storage.save(
|
|
54
|
+
storageObject,
|
|
55
|
+
objects.map(it => ({
|
|
56
|
+
timestamp: it.timestamp,
|
|
57
|
+
payload: JSON.stringify(it.payload)
|
|
58
|
+
}))
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await storage.save(storageIndexObject, [
|
|
64
|
+
{
|
|
65
|
+
timestamp: 0,
|
|
66
|
+
max,
|
|
67
|
+
min,
|
|
68
|
+
uri: storageObjectKey
|
|
69
|
+
}
|
|
70
|
+
]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (await storage.query(storageObject, query)).map(it => ({
|
|
74
|
+
timestamp: it.timestamp,
|
|
75
|
+
payload: JSON.parse(it.payload) as V
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { finalize, Observable } from 'rxjs';
|
|
2
|
+
|
|
3
|
+
import { useExecutionMode } from '@lib/use-execution-mode';
|
|
4
|
+
|
|
5
|
+
import { useBacktestScheduler } from './use-backtest-scheduler';
|
|
6
|
+
|
|
7
|
+
export function useBacktestSync<T>(input: Observable<T>): Observable<T> {
|
|
8
|
+
const { isReplay } = useExecutionMode();
|
|
9
|
+
|
|
10
|
+
if (!isReplay) {
|
|
11
|
+
return input;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { stop, tryContinue } = useBacktestScheduler();
|
|
15
|
+
|
|
16
|
+
stop();
|
|
17
|
+
|
|
18
|
+
return input.pipe(finalize(() => tryContinue()));
|
|
19
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Observable } from 'rxjs';
|
|
2
|
+
|
|
3
|
+
import { Query, QueryObject } from '@lib/storage';
|
|
4
|
+
import { useExecutionMode } from '@lib/use-execution-mode';
|
|
5
|
+
|
|
6
|
+
import { useBacktestScheduler } from './use-backtest-scheduler';
|
|
7
|
+
|
|
8
|
+
export interface BacktestStorage<V> {
|
|
9
|
+
query(
|
|
10
|
+
query: Query<QueryObject> & { where: { timestamp: { min: number; max: number } } }
|
|
11
|
+
): Promise<{ timestamp: number; payload: V }[]>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function useBacktest<T>(
|
|
15
|
+
input: Observable<{ timestamp: number; payload: T }>,
|
|
16
|
+
query: BacktestStorage<T>
|
|
17
|
+
) {
|
|
18
|
+
const { isReplay } = useExecutionMode();
|
|
19
|
+
|
|
20
|
+
if (isReplay) {
|
|
21
|
+
const { watch } = useBacktestScheduler();
|
|
22
|
+
return watch<T>(query);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return input;
|
|
26
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { from, last, map, Subject } from 'rxjs';
|
|
2
|
+
|
|
3
|
+
import { useExecutionMode } from '@lib/use-execution-mode';
|
|
4
|
+
|
|
5
|
+
import { useBacktestScheduler } from './use-backtest-scheduler';
|
|
6
|
+
|
|
7
|
+
export function whenBacktestFinished() {
|
|
8
|
+
const { isReplay } = useExecutionMode();
|
|
9
|
+
|
|
10
|
+
if (!isReplay) {
|
|
11
|
+
return new Subject<boolean>().asObservable();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { stream } = useBacktestScheduler();
|
|
15
|
+
|
|
16
|
+
return from(stream).pipe(
|
|
17
|
+
last(),
|
|
18
|
+
map(() => true)
|
|
19
|
+
);
|
|
20
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -7,7 +7,6 @@ import watch from 'node-watch';
|
|
|
7
7
|
import build from '@lib/cli/build';
|
|
8
8
|
import live from '@lib/cli/live';
|
|
9
9
|
import paper from '@lib/cli/paper';
|
|
10
|
-
import pull from '@lib/cli/pull';
|
|
11
10
|
import replay from '@lib/cli/replay';
|
|
12
11
|
|
|
13
12
|
program
|
|
@@ -45,15 +44,6 @@ program
|
|
|
45
44
|
.option('-w', 'watch mode')
|
|
46
45
|
.action(replay);
|
|
47
46
|
|
|
48
|
-
program
|
|
49
|
-
.command('pull')
|
|
50
|
-
.description('pulls instrument historical data to storage')
|
|
51
|
-
.argument('<name>', 'strategy to execute')
|
|
52
|
-
.argument('<instrument>', 'instrument to import')
|
|
53
|
-
.option('-f, --from <from>', 'date from')
|
|
54
|
-
.option('-t, --to <to>', 'date to')
|
|
55
|
-
.action(pull);
|
|
56
|
-
|
|
57
47
|
program.name('quantform').description('quantform tools');
|
|
58
48
|
|
|
59
49
|
if (process.argv.length < 3) {
|
package/src/cli/replay.ts
CHANGED
|
@@ -14,7 +14,7 @@ export default async function (
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const from = options.from ? new Date(options.from).getTime() : 0;
|
|
17
|
-
const to = options.to ? new Date(options.to).getTime() :
|
|
17
|
+
const to = options.to ? new Date(options.to).getTime() : 1893452400; // 01-01-2030;
|
|
18
18
|
|
|
19
19
|
const script = new Script(name, [
|
|
20
20
|
useSession.options({ id: options.id ?? Date.now().toString() }),
|
package/src/index.ts
CHANGED
package/src/uri.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type Uri<T> = {
|
|
2
|
+
base: `${string}://${string}`;
|
|
3
|
+
params: T;
|
|
4
|
+
query: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function uri<T extends Record<string, string | number>>(
|
|
8
|
+
base: `${string}://${string}`,
|
|
9
|
+
params: T
|
|
10
|
+
) {
|
|
11
|
+
const search = new URLSearchParams();
|
|
12
|
+
|
|
13
|
+
for (const [k, v] of Object.entries(params)) {
|
|
14
|
+
search.set(k, String(v));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
base,
|
|
19
|
+
params,
|
|
20
|
+
query: `${base}?${search.toString()}`
|
|
21
|
+
};
|
|
22
|
+
}
|
package/src/use-timestamp.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { useReplayManager } from '@lib/replay';
|
|
2
1
|
import { useExecutionMode } from '@lib/use-execution-mode';
|
|
3
2
|
|
|
3
|
+
import { useBacktestScheduler } from './backtest';
|
|
4
|
+
|
|
4
5
|
export function useTimestamp() {
|
|
5
6
|
const { isReplay } = useExecutionMode();
|
|
6
7
|
|
|
7
8
|
if (isReplay) {
|
|
8
|
-
return
|
|
9
|
+
return useBacktestScheduler().timestamp();
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
return Date.now();
|
package/lib/cli/pull.d.ts
DELETED
package/lib/cli/pull.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/cli/pull.ts"],"names":[],"mappings":"AAKA,yBAA+B,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,iBAS5E"}
|
package/lib/cli/pull.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
const build_1 = __importDefault(require("../cli/build"));
|
|
16
|
-
const use_execution_mode_1 = require("../use-execution-mode");
|
|
17
|
-
const script_1 = require("./internal/script");
|
|
18
|
-
function default_1(name, instrument, options) {
|
|
19
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
20
|
-
if (yield (0, build_1.default)()) {
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
const script = new script_1.Script(name, [use_execution_mode_1.useExecutionMode.idleOptions()]);
|
|
24
|
-
//const output = await script.run();
|
|
25
|
-
//console.log(output);
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
exports.default = default_1;
|
package/src/cli/pull.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import build from '@lib/cli/build';
|
|
2
|
-
import { useExecutionMode } from '@lib/use-execution-mode';
|
|
3
|
-
|
|
4
|
-
import { Script } from './internal/script';
|
|
5
|
-
|
|
6
|
-
export default async function (name: string, instrument: string, options: any) {
|
|
7
|
-
if (await build()) {
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const script = new Script(name, [useExecutionMode.idleOptions()]);
|
|
12
|
-
//const output = await script.run();
|
|
13
|
-
|
|
14
|
-
//console.log(output);
|
|
15
|
-
}
|