@backtest-kit/cli 16.3.0 → 16.5.0
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/README.md +31 -2
- package/build/index.cjs +461 -58
- package/build/index.mjs +466 -63
- package/config/notification.config.mjs +4 -0
- package/docker/package.json +6 -6
- package/package.json +13 -13
- package/template/order-continue.mustache +17 -0
- package/template/{signal-close.mustache → order-fill-close.mustache} +1 -1
- package/template/{signal-open.mustache → order-fill-open.mustache} +2 -1
- package/template/order-reject-close.mustache +17 -0
- package/template/order-reject-open.mustache +16 -0
- package/template/order-stop.mustache +18 -0
- package/template/project/package.mustache +5 -5
- package/types.d.ts +33 -7
package/build/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as BacktestKit from 'backtest-kit';
|
|
3
|
-
import { setConfig, listenExit, emitters, Notification, Cron, Recent, Storage, Markdown, Report, Dump, State, Memory, MarkdownWriter, ReportWriter, PersistSignalAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistPartialAdapter, PersistBreakevenAdapter, PersistCandleAdapter, PersistStorageAdapter, PersistNotificationAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistIntervalAdapter, PersistMemoryAdapter, PersistRecentAdapter, PersistStateAdapter, PersistSessionAdapter, PersistStrategyAdapter, Log, StorageLive, StorageBacktest, NotificationLive, NotificationBacktest, RecentLive, RecentBacktest, Broker, SessionLive, SessionBacktest, MemoryLive, MemoryBacktest, StateLive, StateBacktest, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, shutdown, listenSignal, listStrategySchema, overrideExchangeSchema, Backtest, System, Cache, Interval, alignToInterval, addWalkerSchema, overrideWalkerSchema, Walker, listenDoneWalker, Live, getCandles, checkCandles, warmCandles, listenRisk, listenStrategyCommit,
|
|
4
|
-
import { singleshot, sleep, getErrorMessage, errorData, BehaviorSubject, Subject, str, compose, createAwaiter, execpool, queued, randomString, TIMEOUT_SYMBOL, typo, retry, trycatch, memoize, isObject } from 'functools-kit';
|
|
3
|
+
import { setConfig, listenExit, emitters, Notification, Cron, Recent, Storage, Markdown, Report, Dump, State, Memory, MarkdownWriter, ReportWriter, PersistSignalAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistPartialAdapter, PersistBreakevenAdapter, PersistCandleAdapter, PersistStorageAdapter, PersistNotificationAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistIntervalAdapter, PersistMemoryAdapter, PersistRecentAdapter, PersistStateAdapter, PersistSessionAdapter, PersistStrategyAdapter, Log, StorageLive, StorageBacktest, NotificationLive, NotificationBacktest, RecentLive, RecentBacktest, Broker, SessionLive, SessionBacktest, MemoryLive, MemoryBacktest, StateLive, StateBacktest, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, shutdown, listenSignal, listStrategySchema, overrideExchangeSchema, Backtest, System, Cache, Interval, alignToInterval, addWalkerSchema, overrideWalkerSchema, Walker, listenDoneWalker, Live, getCandles, checkCandles, warmCandles, listenRisk, listenStrategyCommit, listenOrderFill, listenOrderReject, listenOrderContinue, listenOrderStop, listenSignalNotify, Exchange, addSimulatorSchema, Simulator } from 'backtest-kit';
|
|
4
|
+
import { singleshot, sleep, getErrorMessage, errorData, BehaviorSubject, Subject, str, compose, createAwaiter, execpool, queued, randomString, TIMEOUT_SYMBOL, typo, retry, trycatch, ttl, memoize, isObject } from 'functools-kit';
|
|
5
5
|
import { exec, spawn } from 'child_process';
|
|
6
6
|
import fs, { constants, realpathSync } from 'fs';
|
|
7
7
|
import * as stackTrace from 'stack-trace';
|
|
@@ -28,7 +28,7 @@ import Module, { createRequire } from 'module';
|
|
|
28
28
|
import * as BacktestKitGraph from '@backtest-kit/graph';
|
|
29
29
|
import * as BacktestKitOllama from '@backtest-kit/ollama';
|
|
30
30
|
import * as BacktestKitPinets from '@backtest-kit/pinets';
|
|
31
|
-
import { run as run$1, Code, toMarkdown } from '@backtest-kit/pinets';
|
|
31
|
+
import { run as run$1, Code, toMarkdown as toMarkdown$1 } from '@backtest-kit/pinets';
|
|
32
32
|
import * as BacktestKitSignals from '@backtest-kit/signals';
|
|
33
33
|
import open from 'open';
|
|
34
34
|
|
|
@@ -208,14 +208,14 @@ const getReadySubject = singleshot(() => {
|
|
|
208
208
|
return new Subject();
|
|
209
209
|
});
|
|
210
210
|
|
|
211
|
-
const main$
|
|
211
|
+
const main$j = () => {
|
|
212
212
|
const entrySubject = getEntrySubject();
|
|
213
213
|
entrySubject.subscribe(async (path) => {
|
|
214
214
|
console.log("Running", path);
|
|
215
215
|
await getReadySubject().next();
|
|
216
216
|
});
|
|
217
217
|
};
|
|
218
|
-
main$
|
|
218
|
+
main$j();
|
|
219
219
|
|
|
220
220
|
const ERROR_HANDLER_INSTALLED = Symbol.for("error-handler-installed");
|
|
221
221
|
function dumpStackTrace(error) {
|
|
@@ -866,6 +866,10 @@ const getArgs = singleshot(() => {
|
|
|
866
866
|
type: "boolean",
|
|
867
867
|
default: false,
|
|
868
868
|
},
|
|
869
|
+
simulator: {
|
|
870
|
+
type: "boolean",
|
|
871
|
+
default: false,
|
|
872
|
+
},
|
|
869
873
|
commit: {
|
|
870
874
|
type: "string",
|
|
871
875
|
default: "",
|
|
@@ -2312,6 +2316,13 @@ const STOP_BOT_FN = singleshot(async () => {
|
|
|
2312
2316
|
const { stopBot } = await getTelegram();
|
|
2313
2317
|
stopBot();
|
|
2314
2318
|
});
|
|
2319
|
+
/**
|
|
2320
|
+
* Throttle window for the per-tick order_continue decision channel: at most one
|
|
2321
|
+
* Telegram notification per signalId per 15 minutes via functools-kit ttl. The
|
|
2322
|
+
* terminal order_stop is NOT throttled (fires once per signal) and clears the
|
|
2323
|
+
* throttle slot for its signalId.
|
|
2324
|
+
*/
|
|
2325
|
+
const ORDER_CHECK_NOTIFY_TTL = 15 * 60 * 1000;
|
|
2315
2326
|
class TelegramLogicService {
|
|
2316
2327
|
constructor() {
|
|
2317
2328
|
this.loggerService = inject(TYPES.loggerService);
|
|
@@ -2460,11 +2471,24 @@ class TelegramLogicService {
|
|
|
2460
2471
|
markdown,
|
|
2461
2472
|
});
|
|
2462
2473
|
};
|
|
2463
|
-
this.
|
|
2464
|
-
this.loggerService.log("telegramLogicService
|
|
2474
|
+
this.notifyOrderFillOpen = trycatch(async (event) => {
|
|
2475
|
+
this.loggerService.log("telegramLogicService notifyOrderFillOpen", {
|
|
2476
|
+
event,
|
|
2477
|
+
});
|
|
2478
|
+
const markdown = await this.telegramTemplateService.getOrderFillOpenMarkdown(event);
|
|
2479
|
+
if (!markdown) {
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2482
|
+
await this.telegramWebService.publishNotify({
|
|
2483
|
+
symbol: event.symbol,
|
|
2484
|
+
markdown,
|
|
2485
|
+
});
|
|
2486
|
+
});
|
|
2487
|
+
this.notifyOrderFillClose = trycatch(async (event) => {
|
|
2488
|
+
this.loggerService.log("telegramLogicService notifyOrderFillClose", {
|
|
2465
2489
|
event,
|
|
2466
2490
|
});
|
|
2467
|
-
const markdown = await this.telegramTemplateService.
|
|
2491
|
+
const markdown = await this.telegramTemplateService.getOrderFillCloseMarkdown(event);
|
|
2468
2492
|
if (!markdown) {
|
|
2469
2493
|
return;
|
|
2470
2494
|
}
|
|
@@ -2473,11 +2497,64 @@ class TelegramLogicService {
|
|
|
2473
2497
|
markdown,
|
|
2474
2498
|
});
|
|
2475
2499
|
});
|
|
2476
|
-
this.
|
|
2477
|
-
this.loggerService.log("telegramLogicService
|
|
2500
|
+
this.notifyOrderRejectOpen = trycatch(async (event) => {
|
|
2501
|
+
this.loggerService.log("telegramLogicService notifyOrderRejectOpen", {
|
|
2478
2502
|
event,
|
|
2479
2503
|
});
|
|
2480
|
-
const markdown = await this.telegramTemplateService.
|
|
2504
|
+
const markdown = await this.telegramTemplateService.getOrderRejectOpenMarkdown(event);
|
|
2505
|
+
if (!markdown) {
|
|
2506
|
+
return;
|
|
2507
|
+
}
|
|
2508
|
+
await this.telegramWebService.publishNotify({
|
|
2509
|
+
symbol: event.symbol,
|
|
2510
|
+
markdown,
|
|
2511
|
+
});
|
|
2512
|
+
});
|
|
2513
|
+
this.notifyOrderRejectClose = trycatch(async (event) => {
|
|
2514
|
+
this.loggerService.log("telegramLogicService notifyOrderRejectClose", {
|
|
2515
|
+
event,
|
|
2516
|
+
});
|
|
2517
|
+
const markdown = await this.telegramTemplateService.getOrderRejectCloseMarkdown(event);
|
|
2518
|
+
if (!markdown) {
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2521
|
+
await this.telegramWebService.publishNotify({
|
|
2522
|
+
symbol: event.symbol,
|
|
2523
|
+
markdown,
|
|
2524
|
+
});
|
|
2525
|
+
});
|
|
2526
|
+
/**
|
|
2527
|
+
* Post-verdict order-check CONTINUE (order_continue). Fires every live tick the
|
|
2528
|
+
* order survives the check, so it is throttled via functools-kit ttl:
|
|
2529
|
+
* one message per signalId per ORDER_CHECK_NOTIFY_TTL (15 minutes).
|
|
2530
|
+
*/
|
|
2531
|
+
this.notifyOrderContinue = ttl(trycatch(async (event) => {
|
|
2532
|
+
this.loggerService.log("telegramLogicService notifyOrderContinue", {
|
|
2533
|
+
event,
|
|
2534
|
+
});
|
|
2535
|
+
const markdown = await this.telegramTemplateService.getOrderContinueMarkdown(event);
|
|
2536
|
+
if (!markdown) {
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
await this.telegramWebService.publishNotify({
|
|
2540
|
+
symbol: event.symbol,
|
|
2541
|
+
markdown,
|
|
2542
|
+
});
|
|
2543
|
+
}), {
|
|
2544
|
+
key: ([event]) => event.signalId,
|
|
2545
|
+
timeout: ORDER_CHECK_NOTIFY_TTL,
|
|
2546
|
+
});
|
|
2547
|
+
/**
|
|
2548
|
+
* Post-verdict order-check STOP (order_stop). Terminal — fires once per signal,
|
|
2549
|
+
* so no throttle; drops the continue throttle slot for this signalId
|
|
2550
|
+
* (the signal leaves monitoring, the ttl entry would only leak otherwise).
|
|
2551
|
+
*/
|
|
2552
|
+
this.notifyOrderStop = trycatch(async (event) => {
|
|
2553
|
+
this.loggerService.log("telegramLogicService notifyOrderStop", {
|
|
2554
|
+
event,
|
|
2555
|
+
});
|
|
2556
|
+
this.notifyOrderContinue.clear(event.signalId);
|
|
2557
|
+
const markdown = await this.telegramTemplateService.getOrderStopMarkdown(event);
|
|
2481
2558
|
if (!markdown) {
|
|
2482
2559
|
return;
|
|
2483
2560
|
}
|
|
@@ -2582,26 +2659,51 @@ class TelegramLogicService {
|
|
|
2582
2659
|
return;
|
|
2583
2660
|
}
|
|
2584
2661
|
});
|
|
2585
|
-
|
|
2662
|
+
// Broker-CONFIRMED fills only (post-verdict orderFillSubject): a rejected or
|
|
2663
|
+
// transient gate attempt never reaches this channel, so "Order Filled" is
|
|
2664
|
+
// guaranteed truthful. The pre-verdict syncSubject is NOT consumed here.
|
|
2665
|
+
const unOrderFill = listenOrderFill(async (event) => {
|
|
2586
2666
|
if (event.action === "signal-open") {
|
|
2587
|
-
// type "
|
|
2588
|
-
//
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2667
|
+
// Both open outcomes notify: type "active" — the order opened at once
|
|
2668
|
+
// (immediate fill, no schedule phase), type "schedule" — the resting
|
|
2669
|
+
// order was placed on the exchange. Either way the broker confirmed a
|
|
2670
|
+
// real order; the template's Order field carries the type.
|
|
2671
|
+
await this.notifyOrderFillOpen(event);
|
|
2672
|
+
return;
|
|
2673
|
+
}
|
|
2674
|
+
if (event.action === "signal-close") {
|
|
2675
|
+
await this.notifyOrderFillClose(event);
|
|
2676
|
+
return;
|
|
2677
|
+
}
|
|
2678
|
+
});
|
|
2679
|
+
// Terminal broker rejection (post-verdict orderRejectSubject): exactly one
|
|
2680
|
+
// event per dropped order attempt — the engine consumes the rejected signal
|
|
2681
|
+
// id, so this cannot repeat per-tick for the same signal. Dispatched by
|
|
2682
|
+
// action to the dedicated open/close notifications (single responsibility).
|
|
2683
|
+
const unOrderReject = listenOrderReject(async (event) => {
|
|
2684
|
+
if (event.action === "signal-open") {
|
|
2685
|
+
await this.notifyOrderRejectOpen(event);
|
|
2593
2686
|
return;
|
|
2594
2687
|
}
|
|
2595
2688
|
if (event.action === "signal-close") {
|
|
2596
|
-
await this.
|
|
2689
|
+
await this.notifyOrderRejectClose(event);
|
|
2597
2690
|
return;
|
|
2598
2691
|
}
|
|
2599
2692
|
});
|
|
2693
|
+
// Post-verdict check decisions: CONTINUE fires every surviving tick and is
|
|
2694
|
+
// throttled to one message per signalId per 15 minutes via ttl; terminal
|
|
2695
|
+
// STOP fires once per signal, unthrottled.
|
|
2696
|
+
const unOrderContinue = listenOrderContinue(async (event) => {
|
|
2697
|
+
await this.notifyOrderContinue(event);
|
|
2698
|
+
});
|
|
2699
|
+
const unOrderStop = listenOrderStop(async (event) => {
|
|
2700
|
+
await this.notifyOrderStop(event);
|
|
2701
|
+
});
|
|
2600
2702
|
const unSignalNotify = listenSignalNotify(async (event) => {
|
|
2601
2703
|
await this.notifySignalInfo(event);
|
|
2602
2704
|
});
|
|
2603
2705
|
const unConnect = () => this.connect.clear();
|
|
2604
|
-
const unListen = compose(() => unRisk(), () => unSignal(), () => unCommit(), () =>
|
|
2706
|
+
const unListen = compose(() => unRisk(), () => unSignal(), () => unCommit(), () => unOrderFill(), () => unOrderReject(), () => unOrderContinue(), () => unOrderStop(), () => this.notifyOrderContinue.clear(), () => unSignalNotify(), () => unConnect());
|
|
2605
2707
|
return () => {
|
|
2606
2708
|
STOP_BOT_FN();
|
|
2607
2709
|
unListen();
|
|
@@ -2754,25 +2856,65 @@ class TelegramTemplateService {
|
|
|
2754
2856
|
}
|
|
2755
2857
|
return await RENDER_TEMPLATE_FN("average-buy.mustache", event, this);
|
|
2756
2858
|
};
|
|
2757
|
-
this.
|
|
2758
|
-
this.loggerService.log("telegramTemplateService
|
|
2859
|
+
this.getOrderFillOpenMarkdown = async (event) => {
|
|
2860
|
+
this.loggerService.log("telegramTemplateService getOrderFillOpenMarkdown", {
|
|
2861
|
+
event,
|
|
2862
|
+
});
|
|
2863
|
+
const adapter = await this.getTelegramAdapter();
|
|
2864
|
+
if (adapter?.getOrderFillOpenMarkdown) {
|
|
2865
|
+
return await adapter.getOrderFillOpenMarkdown(event);
|
|
2866
|
+
}
|
|
2867
|
+
return await RENDER_TEMPLATE_FN("order-fill-open.mustache", event, this);
|
|
2868
|
+
};
|
|
2869
|
+
this.getOrderFillCloseMarkdown = async (event) => {
|
|
2870
|
+
this.loggerService.log("telegramTemplateService getOrderFillCloseMarkdown", {
|
|
2871
|
+
event,
|
|
2872
|
+
});
|
|
2873
|
+
const adapter = await this.getTelegramAdapter();
|
|
2874
|
+
if (adapter?.getOrderFillCloseMarkdown) {
|
|
2875
|
+
return await adapter.getOrderFillCloseMarkdown(event);
|
|
2876
|
+
}
|
|
2877
|
+
return await RENDER_TEMPLATE_FN("order-fill-close.mustache", event, this);
|
|
2878
|
+
};
|
|
2879
|
+
this.getOrderRejectOpenMarkdown = async (event) => {
|
|
2880
|
+
this.loggerService.log("telegramTemplateService getOrderRejectOpenMarkdown", {
|
|
2881
|
+
event,
|
|
2882
|
+
});
|
|
2883
|
+
const adapter = await this.getTelegramAdapter();
|
|
2884
|
+
if (adapter?.getOrderRejectOpenMarkdown) {
|
|
2885
|
+
return await adapter.getOrderRejectOpenMarkdown(event);
|
|
2886
|
+
}
|
|
2887
|
+
return await RENDER_TEMPLATE_FN("order-reject-open.mustache", event, this);
|
|
2888
|
+
};
|
|
2889
|
+
this.getOrderRejectCloseMarkdown = async (event) => {
|
|
2890
|
+
this.loggerService.log("telegramTemplateService getOrderRejectCloseMarkdown", {
|
|
2891
|
+
event,
|
|
2892
|
+
});
|
|
2893
|
+
const adapter = await this.getTelegramAdapter();
|
|
2894
|
+
if (adapter?.getOrderRejectCloseMarkdown) {
|
|
2895
|
+
return await adapter.getOrderRejectCloseMarkdown(event);
|
|
2896
|
+
}
|
|
2897
|
+
return await RENDER_TEMPLATE_FN("order-reject-close.mustache", event, this);
|
|
2898
|
+
};
|
|
2899
|
+
this.getOrderContinueMarkdown = async (event) => {
|
|
2900
|
+
this.loggerService.log("telegramTemplateService getOrderContinueMarkdown", {
|
|
2759
2901
|
event,
|
|
2760
2902
|
});
|
|
2761
2903
|
const adapter = await this.getTelegramAdapter();
|
|
2762
|
-
if (adapter?.
|
|
2763
|
-
return await adapter.
|
|
2904
|
+
if (adapter?.getOrderContinueMarkdown) {
|
|
2905
|
+
return await adapter.getOrderContinueMarkdown(event);
|
|
2764
2906
|
}
|
|
2765
|
-
return await RENDER_TEMPLATE_FN("
|
|
2907
|
+
return await RENDER_TEMPLATE_FN("order-continue.mustache", event, this);
|
|
2766
2908
|
};
|
|
2767
|
-
this.
|
|
2768
|
-
this.loggerService.log("telegramTemplateService
|
|
2909
|
+
this.getOrderStopMarkdown = async (event) => {
|
|
2910
|
+
this.loggerService.log("telegramTemplateService getOrderStopMarkdown", {
|
|
2769
2911
|
event,
|
|
2770
2912
|
});
|
|
2771
2913
|
const adapter = await this.getTelegramAdapter();
|
|
2772
|
-
if (adapter?.
|
|
2773
|
-
return await adapter.
|
|
2914
|
+
if (adapter?.getOrderStopMarkdown) {
|
|
2915
|
+
return await adapter.getOrderStopMarkdown(event);
|
|
2774
2916
|
}
|
|
2775
|
-
return await RENDER_TEMPLATE_FN("
|
|
2917
|
+
return await RENDER_TEMPLATE_FN("order-stop.mustache", event, this);
|
|
2776
2918
|
};
|
|
2777
2919
|
this.getCancelScheduledMarkdown = async (event) => {
|
|
2778
2920
|
this.loggerService.log("telegramTemplateService getCancelScheduledMarkdown", {
|
|
@@ -3502,14 +3644,14 @@ const cli = {
|
|
|
3502
3644
|
};
|
|
3503
3645
|
init();
|
|
3504
3646
|
|
|
3505
|
-
const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
|
|
3647
|
+
const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "simulator", "flush", "init", "docker", "help", "version"];
|
|
3506
3648
|
const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
3507
3649
|
const HELP_TEXT$1 = `
|
|
3508
3650
|
Example:
|
|
3509
3651
|
|
|
3510
3652
|
node ${ENTRY_PATH$1} --help
|
|
3511
3653
|
`.trimStart();
|
|
3512
|
-
const main$
|
|
3654
|
+
const main$i = async () => {
|
|
3513
3655
|
if (!getEntry(import.meta.url)) {
|
|
3514
3656
|
return;
|
|
3515
3657
|
}
|
|
@@ -3517,14 +3659,14 @@ const main$h = async () => {
|
|
|
3517
3659
|
if (MODES.some((mode) => values[mode])) {
|
|
3518
3660
|
return;
|
|
3519
3661
|
}
|
|
3520
|
-
process.stdout.write(`@backtest-kit/cli ${"16.
|
|
3662
|
+
process.stdout.write(`@backtest-kit/cli ${"16.5.0"}\n`);
|
|
3521
3663
|
process.stdout.write("\n");
|
|
3522
3664
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
3523
3665
|
process.stdout.write("\n");
|
|
3524
3666
|
process.stdout.write(HELP_TEXT$1);
|
|
3525
3667
|
process.exit(0);
|
|
3526
3668
|
};
|
|
3527
|
-
main$
|
|
3669
|
+
main$i();
|
|
3528
3670
|
|
|
3529
3671
|
const notifyShutdown = singleshot(async () => {
|
|
3530
3672
|
console.log("Graceful shutdown initiated.");
|
|
@@ -3540,7 +3682,7 @@ const flush = async (entryPoint) => {
|
|
|
3540
3682
|
console.log(`Removed: ${target}`);
|
|
3541
3683
|
}
|
|
3542
3684
|
};
|
|
3543
|
-
const main$
|
|
3685
|
+
const main$h = async () => {
|
|
3544
3686
|
if (!getEntry(import.meta.url)) {
|
|
3545
3687
|
return;
|
|
3546
3688
|
}
|
|
@@ -3557,7 +3699,7 @@ const main$g = async () => {
|
|
|
3557
3699
|
}
|
|
3558
3700
|
process.exit(0);
|
|
3559
3701
|
};
|
|
3560
|
-
main$
|
|
3702
|
+
main$h();
|
|
3561
3703
|
|
|
3562
3704
|
const BEFORE_EXIT_FN$6 = singleshot(async () => {
|
|
3563
3705
|
process.off("SIGINT", BEFORE_EXIT_FN$6);
|
|
@@ -3580,7 +3722,7 @@ const BEFORE_EXIT_FN$6 = singleshot(async () => {
|
|
|
3580
3722
|
const listenGracefulShutdown$6 = singleshot(() => {
|
|
3581
3723
|
process.on("SIGINT", BEFORE_EXIT_FN$6);
|
|
3582
3724
|
});
|
|
3583
|
-
const main$
|
|
3725
|
+
const main$g = async () => {
|
|
3584
3726
|
if (!getEntry(import.meta.url)) {
|
|
3585
3727
|
return;
|
|
3586
3728
|
}
|
|
@@ -3598,7 +3740,7 @@ const main$f = async () => {
|
|
|
3598
3740
|
await cli.backtestMainService.connect();
|
|
3599
3741
|
listenGracefulShutdown$6();
|
|
3600
3742
|
};
|
|
3601
|
-
main$
|
|
3743
|
+
main$g();
|
|
3602
3744
|
|
|
3603
3745
|
const BEFORE_EXIT_FN$5 = singleshot(async () => {
|
|
3604
3746
|
process.off("SIGINT", BEFORE_EXIT_FN$5);
|
|
@@ -3617,7 +3759,7 @@ const BEFORE_EXIT_FN$5 = singleshot(async () => {
|
|
|
3617
3759
|
const listenGracefulShutdown$5 = singleshot(() => {
|
|
3618
3760
|
process.on("SIGINT", BEFORE_EXIT_FN$5);
|
|
3619
3761
|
});
|
|
3620
|
-
const main$
|
|
3762
|
+
const main$f = async () => {
|
|
3621
3763
|
if (!getEntry(import.meta.url)) {
|
|
3622
3764
|
return;
|
|
3623
3765
|
}
|
|
@@ -3636,7 +3778,7 @@ const main$e = async () => {
|
|
|
3636
3778
|
listenGracefulShutdown$5();
|
|
3637
3779
|
await cli.walkerMainService.connect();
|
|
3638
3780
|
};
|
|
3639
|
-
main$
|
|
3781
|
+
main$f();
|
|
3640
3782
|
|
|
3641
3783
|
const BEFORE_EXIT_FN$4 = singleshot(async () => {
|
|
3642
3784
|
process.off("SIGINT", BEFORE_EXIT_FN$4);
|
|
@@ -3658,7 +3800,7 @@ const BEFORE_EXIT_FN$4 = singleshot(async () => {
|
|
|
3658
3800
|
const listenGracefulShutdown$4 = singleshot(() => {
|
|
3659
3801
|
process.on("SIGINT", BEFORE_EXIT_FN$4);
|
|
3660
3802
|
});
|
|
3661
|
-
const main$
|
|
3803
|
+
const main$e = async () => {
|
|
3662
3804
|
if (!getEntry(import.meta.url)) {
|
|
3663
3805
|
return;
|
|
3664
3806
|
}
|
|
@@ -3672,7 +3814,7 @@ const main$d = async () => {
|
|
|
3672
3814
|
await cli.paperMainService.connect();
|
|
3673
3815
|
listenGracefulShutdown$4();
|
|
3674
3816
|
};
|
|
3675
|
-
main$
|
|
3817
|
+
main$e();
|
|
3676
3818
|
|
|
3677
3819
|
const BEFORE_EXIT_FN$3 = singleshot(async () => {
|
|
3678
3820
|
process.off("SIGINT", BEFORE_EXIT_FN$3);
|
|
@@ -3694,7 +3836,7 @@ const BEFORE_EXIT_FN$3 = singleshot(async () => {
|
|
|
3694
3836
|
const listenGracefulShutdown$3 = singleshot(() => {
|
|
3695
3837
|
process.on("SIGINT", BEFORE_EXIT_FN$3);
|
|
3696
3838
|
});
|
|
3697
|
-
const main$
|
|
3839
|
+
const main$d = async () => {
|
|
3698
3840
|
if (!getEntry(import.meta.url)) {
|
|
3699
3841
|
return;
|
|
3700
3842
|
}
|
|
@@ -3708,7 +3850,7 @@ const main$c = async () => {
|
|
|
3708
3850
|
await cli.liveMainService.connect();
|
|
3709
3851
|
listenGracefulShutdown$3();
|
|
3710
3852
|
};
|
|
3711
|
-
main$
|
|
3853
|
+
main$d();
|
|
3712
3854
|
|
|
3713
3855
|
const stopBacktestList$1 = async () => {
|
|
3714
3856
|
for (const item of await Backtest.list()) {
|
|
@@ -3772,7 +3914,7 @@ const BEFORE_EXIT_FN$2 = singleshot(async () => {
|
|
|
3772
3914
|
const listenGracefulShutdown$2 = singleshot(() => {
|
|
3773
3915
|
process.on("SIGINT", BEFORE_EXIT_FN$2);
|
|
3774
3916
|
});
|
|
3775
|
-
const main$
|
|
3917
|
+
const main$c = async () => {
|
|
3776
3918
|
if (!getEntry(import.meta.url)) {
|
|
3777
3919
|
return;
|
|
3778
3920
|
}
|
|
@@ -3828,7 +3970,7 @@ const main$b = async () => {
|
|
|
3828
3970
|
listenGracefulShutdown$2();
|
|
3829
3971
|
await cli.resolveService.attachEntry(path.resolve(cwd, entryPoint));
|
|
3830
3972
|
};
|
|
3831
|
-
main$
|
|
3973
|
+
main$c();
|
|
3832
3974
|
|
|
3833
3975
|
const MODE_MODULE = {
|
|
3834
3976
|
backtest: "backtest.module",
|
|
@@ -3931,7 +4073,7 @@ const createGracefulShutdown = (mode) => {
|
|
|
3931
4073
|
process.on("SIGINT", handler);
|
|
3932
4074
|
});
|
|
3933
4075
|
};
|
|
3934
|
-
const main$
|
|
4076
|
+
const main$b = async () => {
|
|
3935
4077
|
if (!getEntry(import.meta.url)) {
|
|
3936
4078
|
return;
|
|
3937
4079
|
}
|
|
@@ -4007,7 +4149,7 @@ const main$a = async () => {
|
|
|
4007
4149
|
}
|
|
4008
4150
|
await getEntrySubject().next(absolutePath);
|
|
4009
4151
|
};
|
|
4010
|
-
main$
|
|
4152
|
+
main$b();
|
|
4011
4153
|
|
|
4012
4154
|
const BEFORE_EXIT_FN$1 = singleshot(async () => {
|
|
4013
4155
|
process.off("SIGINT", BEFORE_EXIT_FN$1);
|
|
@@ -4018,7 +4160,7 @@ const BEFORE_EXIT_FN$1 = singleshot(async () => {
|
|
|
4018
4160
|
const listenGracefulShutdown$1 = singleshot(() => {
|
|
4019
4161
|
process.on("SIGINT", BEFORE_EXIT_FN$1);
|
|
4020
4162
|
});
|
|
4021
|
-
const main$
|
|
4163
|
+
const main$a = async () => {
|
|
4022
4164
|
if (!getEntry(import.meta.url)) {
|
|
4023
4165
|
return;
|
|
4024
4166
|
}
|
|
@@ -4028,7 +4170,7 @@ const main$9 = async () => {
|
|
|
4028
4170
|
}
|
|
4029
4171
|
listenGracefulShutdown$1();
|
|
4030
4172
|
};
|
|
4031
|
-
main$
|
|
4173
|
+
main$a();
|
|
4032
4174
|
|
|
4033
4175
|
const BEFORE_EXIT_FN = singleshot(async () => {
|
|
4034
4176
|
process.off("SIGINT", BEFORE_EXIT_FN);
|
|
@@ -4039,7 +4181,7 @@ const BEFORE_EXIT_FN = singleshot(async () => {
|
|
|
4039
4181
|
const listenGracefulShutdown = singleshot(() => {
|
|
4040
4182
|
process.on("SIGINT", BEFORE_EXIT_FN);
|
|
4041
4183
|
});
|
|
4042
|
-
const main$
|
|
4184
|
+
const main$9 = async () => {
|
|
4043
4185
|
if (!getEntry(import.meta.url)) {
|
|
4044
4186
|
return;
|
|
4045
4187
|
}
|
|
@@ -4049,7 +4191,7 @@ const main$8 = async () => {
|
|
|
4049
4191
|
}
|
|
4050
4192
|
listenGracefulShutdown();
|
|
4051
4193
|
};
|
|
4052
|
-
main$
|
|
4194
|
+
main$9();
|
|
4053
4195
|
|
|
4054
4196
|
const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
4055
4197
|
const keys = Object.keys(schema);
|
|
@@ -4071,7 +4213,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
|
4071
4213
|
}
|
|
4072
4214
|
return rows;
|
|
4073
4215
|
};
|
|
4074
|
-
const main$
|
|
4216
|
+
const main$8 = async () => {
|
|
4075
4217
|
if (!getEntry(import.meta.url)) {
|
|
4076
4218
|
return;
|
|
4077
4219
|
}
|
|
@@ -4163,17 +4305,17 @@ const main$7 = async () => {
|
|
|
4163
4305
|
if (values.markdown) {
|
|
4164
4306
|
const filePath = resolve(dumpDir, `${dumpName}.md`);
|
|
4165
4307
|
await mkdir(dumpDir, { recursive: true });
|
|
4166
|
-
await writeFile(filePath, await toMarkdown(signalId, plots, signalSchema), "utf-8");
|
|
4308
|
+
await writeFile(filePath, await toMarkdown$1(signalId, plots, signalSchema), "utf-8");
|
|
4167
4309
|
console.log(`Saved: ${filePath}`);
|
|
4168
4310
|
process.exit(0);
|
|
4169
4311
|
return;
|
|
4170
4312
|
}
|
|
4171
|
-
console.log(await toMarkdown(signalId, plots, signalSchema));
|
|
4313
|
+
console.log(await toMarkdown$1(signalId, plots, signalSchema));
|
|
4172
4314
|
process.exit(0);
|
|
4173
4315
|
};
|
|
4174
|
-
main$
|
|
4316
|
+
main$8();
|
|
4175
4317
|
|
|
4176
|
-
const main$
|
|
4318
|
+
const main$7 = async () => {
|
|
4177
4319
|
if (!getEntry(import.meta.url)) {
|
|
4178
4320
|
return;
|
|
4179
4321
|
}
|
|
@@ -4236,9 +4378,9 @@ const main$6 = async () => {
|
|
|
4236
4378
|
};
|
|
4237
4379
|
process.on("SIGINT", beforeExit);
|
|
4238
4380
|
};
|
|
4239
|
-
main$
|
|
4381
|
+
main$7();
|
|
4240
4382
|
|
|
4241
|
-
const main$
|
|
4383
|
+
const main$6 = async () => {
|
|
4242
4384
|
if (!getEntry(import.meta.url)) {
|
|
4243
4385
|
return;
|
|
4244
4386
|
}
|
|
@@ -4319,9 +4461,9 @@ const main$5 = async () => {
|
|
|
4319
4461
|
console.log(JSON.stringify(candles, null, 2));
|
|
4320
4462
|
process.exit(0);
|
|
4321
4463
|
};
|
|
4322
|
-
main$
|
|
4464
|
+
main$6();
|
|
4323
4465
|
|
|
4324
|
-
const main$
|
|
4466
|
+
const main$5 = async () => {
|
|
4325
4467
|
if (!getEntry(import.meta.url)) {
|
|
4326
4468
|
return;
|
|
4327
4469
|
}
|
|
@@ -4435,6 +4577,236 @@ const main$4 = async () => {
|
|
|
4435
4577
|
}
|
|
4436
4578
|
process.exit(0);
|
|
4437
4579
|
};
|
|
4580
|
+
main$5();
|
|
4581
|
+
|
|
4582
|
+
const SIMULATOR_NAME = "cli_simulator";
|
|
4583
|
+
const IDEA_DIRECTIONS = ["LONG", "SHORT", "NEUTRAL"];
|
|
4584
|
+
const validateIdea = (idea, line) => {
|
|
4585
|
+
if (typeof idea !== "object" || idea === null) {
|
|
4586
|
+
return `line ${line}: not an object`;
|
|
4587
|
+
}
|
|
4588
|
+
if (typeof idea.id !== "number") {
|
|
4589
|
+
return `line ${line}: "id" must be a number, got ${typeof idea.id}`;
|
|
4590
|
+
}
|
|
4591
|
+
if (typeof idea.ts !== "number") {
|
|
4592
|
+
return `line ${line}: "ts" must be a number (unix ms), got ${typeof idea.ts}`;
|
|
4593
|
+
}
|
|
4594
|
+
if (typeof idea.symbol !== "string" || !idea.symbol) {
|
|
4595
|
+
return `line ${line}: "symbol" must be a non-empty string`;
|
|
4596
|
+
}
|
|
4597
|
+
if (!IDEA_DIRECTIONS.includes(idea.direction)) {
|
|
4598
|
+
return `line ${line}: "direction" must be one of ${IDEA_DIRECTIONS.join("|")}, got ${JSON.stringify(idea.direction)}`;
|
|
4599
|
+
}
|
|
4600
|
+
if (typeof idea.author !== "string" || !idea.author) {
|
|
4601
|
+
return `line ${line}: "author" must be a non-empty string`;
|
|
4602
|
+
}
|
|
4603
|
+
return null;
|
|
4604
|
+
};
|
|
4605
|
+
const toMarkdown = (result) => {
|
|
4606
|
+
const lines = [];
|
|
4607
|
+
lines.push(`# Simulator Report — ${result.symbol}`);
|
|
4608
|
+
lines.push("");
|
|
4609
|
+
lines.push(`| Metric | Value |`);
|
|
4610
|
+
lines.push(`| --- | --- |`);
|
|
4611
|
+
lines.push(`| Ideas (total / directional) | ${result.ideasTotal} / ${result.ideasDirectional} |`);
|
|
4612
|
+
lines.push(`| Profiles (truncated) | ${result.profileCount} (${result.truncatedCount}) |`);
|
|
4613
|
+
lines.push(`| Authors allowed / banned | ${result.allowedAuthors.length} / ${result.bannedAuthors.length} |`);
|
|
4614
|
+
lines.push(`| Grid points | ${result.reports.length} |`);
|
|
4615
|
+
lines.push(`| Hold minutes avg / p95 / p99 | ${result.avgHoldMinutes.toFixed(0)} / ${result.p95HoldMinutes} / ${result.p99HoldMinutes} |`);
|
|
4616
|
+
lines.push("");
|
|
4617
|
+
lines.push(`## Ranking winners`);
|
|
4618
|
+
lines.push("");
|
|
4619
|
+
lines.push(`| Criterion | Stop% | Trailing% | Hold | Aligned | Track | HitRate | Trades | PNL% | WinRate | Sharpe | Sortino |`);
|
|
4620
|
+
lines.push(`| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |`);
|
|
4621
|
+
for (const best of result.best) {
|
|
4622
|
+
if (!best.report) {
|
|
4623
|
+
lines.push(`| ${best.criterion} | — | — | — | — | — | — | — | — | — | — | — |`);
|
|
4624
|
+
continue;
|
|
4625
|
+
}
|
|
4626
|
+
const { point } = best.report;
|
|
4627
|
+
lines.push(`| ${best.criterion} | ${point.hardStopPercent} | ${point.trailingTakePercent} | ${point.holdMinutes / 60}h | ${point.minIdeasAligned} | ${point.minAuthorTrack} | ${point.minAuthorHitRate} | ` +
|
|
4628
|
+
`${best.report.trades} | ${best.report.totalPnlPercent.toFixed(2)}% | ${(best.report.winRate * 100).toFixed(0)}% | ${best.report.sharpe.toFixed(2)} | ${best.report.sortino.toFixed(2)} |`);
|
|
4629
|
+
}
|
|
4630
|
+
lines.push("");
|
|
4631
|
+
lines.push(`## Allowed authors (production whitelist)`);
|
|
4632
|
+
lines.push("");
|
|
4633
|
+
lines.push(`| Author | Ideas | Hits | HitRate |`);
|
|
4634
|
+
lines.push(`| --- | --- | --- | --- |`);
|
|
4635
|
+
for (const stat of result.authorStats.filter(({ banned }) => !banned)) {
|
|
4636
|
+
lines.push(`| ${stat.author} | ${stat.ideas} | ${stat.hits} | ${(stat.hitRate * 100).toFixed(0)}% |`);
|
|
4637
|
+
}
|
|
4638
|
+
return lines.join("\n");
|
|
4639
|
+
};
|
|
4640
|
+
const main$4 = async () => {
|
|
4641
|
+
if (!getEntry(import.meta.url)) {
|
|
4642
|
+
return;
|
|
4643
|
+
}
|
|
4644
|
+
const { values, positionals } = getArgs();
|
|
4645
|
+
if (!values.simulator) {
|
|
4646
|
+
return;
|
|
4647
|
+
}
|
|
4648
|
+
const [ideasPath = null] = positionals.filter((value) => value.endsWith(".jsonl"));
|
|
4649
|
+
if (!ideasPath) {
|
|
4650
|
+
console.error("Error: positional path to an ideas .jsonl file is required");
|
|
4651
|
+
process.exit(1);
|
|
4652
|
+
}
|
|
4653
|
+
let ideas = [];
|
|
4654
|
+
{
|
|
4655
|
+
let content;
|
|
4656
|
+
try {
|
|
4657
|
+
content = await readFile(resolve(ideasPath), "utf-8");
|
|
4658
|
+
}
|
|
4659
|
+
catch (error) {
|
|
4660
|
+
console.error(`Error: cannot read ideas file: ${ideasPath}`);
|
|
4661
|
+
process.exit(1);
|
|
4662
|
+
}
|
|
4663
|
+
const rows = content.split("\n").filter(Boolean);
|
|
4664
|
+
if (!rows.length) {
|
|
4665
|
+
console.error(`Error: ideas file is empty: ${ideasPath}`);
|
|
4666
|
+
process.exit(1);
|
|
4667
|
+
}
|
|
4668
|
+
for (let i = 0; i < rows.length; i++) {
|
|
4669
|
+
let idea;
|
|
4670
|
+
try {
|
|
4671
|
+
idea = JSON.parse(rows[i]);
|
|
4672
|
+
}
|
|
4673
|
+
catch {
|
|
4674
|
+
console.error(`Error: invalid JSON in ideas file, line ${i + 1}`);
|
|
4675
|
+
process.exit(1);
|
|
4676
|
+
}
|
|
4677
|
+
const problem = validateIdea(idea, i + 1);
|
|
4678
|
+
if (problem) {
|
|
4679
|
+
console.error(`Error: ideas file does not match the idea structure — ${problem}`);
|
|
4680
|
+
console.error(`Expected shape: { "id": number, "ts": number, "symbol": string, "direction": "LONG"|"SHORT"|"NEUTRAL", "author": string }`);
|
|
4681
|
+
process.exit(1);
|
|
4682
|
+
}
|
|
4683
|
+
ideas.push(idea);
|
|
4684
|
+
}
|
|
4685
|
+
}
|
|
4686
|
+
{
|
|
4687
|
+
const cwd = process.cwd();
|
|
4688
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
4689
|
+
}
|
|
4690
|
+
await cli.configConnectionService.loadConfig("setup.config");
|
|
4691
|
+
{
|
|
4692
|
+
const loader = await cli.configConnectionService.loadConfig("loader.config");
|
|
4693
|
+
try {
|
|
4694
|
+
if (typeof loader === "function") {
|
|
4695
|
+
await loader();
|
|
4696
|
+
}
|
|
4697
|
+
if (typeof loader?.loader === "function") {
|
|
4698
|
+
await loader.loader();
|
|
4699
|
+
}
|
|
4700
|
+
}
|
|
4701
|
+
catch (error) {
|
|
4702
|
+
console.error("Module loader failed", error);
|
|
4703
|
+
process.exit(-1);
|
|
4704
|
+
}
|
|
4705
|
+
}
|
|
4706
|
+
await cli.moduleConnectionService.loadModule("simulator.module");
|
|
4707
|
+
{
|
|
4708
|
+
await cli.exchangeSchemaService.addSchema();
|
|
4709
|
+
await cli.symbolSchemaService.addSchema();
|
|
4710
|
+
}
|
|
4711
|
+
const [defaultExchangeName = null] = await listExchangeSchema();
|
|
4712
|
+
const exchangeName = values.exchange || defaultExchangeName?.exchangeName;
|
|
4713
|
+
const symbol = values.symbol || "BTCUSDT";
|
|
4714
|
+
addSimulatorSchema({
|
|
4715
|
+
simulatorName: SIMULATOR_NAME,
|
|
4716
|
+
exchangeName,
|
|
4717
|
+
gridAxes: {
|
|
4718
|
+
hardStopPercent: [1, 1.5, 2, 2.5, 3, 4, 5, 7],
|
|
4719
|
+
trailingTakePercent: [0.5, 1, 1.5, 2, 3, 4],
|
|
4720
|
+
holdMinutes: [24 * 60, 2 * 24 * 60, 3 * 24 * 60],
|
|
4721
|
+
minIdeasAligned: [1, 2, 3],
|
|
4722
|
+
minAuthorTrack: [2, 3, 5],
|
|
4723
|
+
minAuthorHitRate: [0.5, 0.6],
|
|
4724
|
+
minWeightAligned: [0, 0.6, 1.2],
|
|
4725
|
+
profitLockPercent: [0, 1.5, 2.5],
|
|
4726
|
+
},
|
|
4727
|
+
callbacks: {
|
|
4728
|
+
onProgress: (symbol, stage, processed, total) => {
|
|
4729
|
+
if (values.verbose) {
|
|
4730
|
+
console.log("onProgress", { symbol, stage, processed, total });
|
|
4731
|
+
}
|
|
4732
|
+
},
|
|
4733
|
+
onIdeas: (symbol, ideasTotal, ideasDirectional) => {
|
|
4734
|
+
if (values.verbose) {
|
|
4735
|
+
console.log("onIdeas", { symbol, ideasTotal, ideasDirectional });
|
|
4736
|
+
}
|
|
4737
|
+
},
|
|
4738
|
+
onProfiles: (symbol, profiles, truncatedCount) => {
|
|
4739
|
+
if (values.verbose) {
|
|
4740
|
+
console.log("onProfiles", { symbol, profiles: profiles.length, truncatedCount });
|
|
4741
|
+
}
|
|
4742
|
+
},
|
|
4743
|
+
onAuthorsTrained: (symbol, stats, bannedIdeas) => {
|
|
4744
|
+
if (values.verbose) {
|
|
4745
|
+
console.log("onAuthorsTrained", {
|
|
4746
|
+
symbol,
|
|
4747
|
+
authors: stats.length,
|
|
4748
|
+
banned: stats.filter(({ banned }) => banned).length,
|
|
4749
|
+
bannedIdeas,
|
|
4750
|
+
});
|
|
4751
|
+
}
|
|
4752
|
+
},
|
|
4753
|
+
onGridPoint: (symbol, report) => {
|
|
4754
|
+
if (values.verbose) {
|
|
4755
|
+
console.log("onGridPoint", {
|
|
4756
|
+
symbol,
|
|
4757
|
+
point: report.point,
|
|
4758
|
+
trades: report.trades,
|
|
4759
|
+
pnl: +report.totalPnlPercent.toFixed(2),
|
|
4760
|
+
sharpe: +report.sharpe.toFixed(2),
|
|
4761
|
+
});
|
|
4762
|
+
}
|
|
4763
|
+
},
|
|
4764
|
+
onRanking: (symbol, criterion, _sorted, best) => {
|
|
4765
|
+
if (values.verbose) {
|
|
4766
|
+
console.log("onRanking", {
|
|
4767
|
+
symbol,
|
|
4768
|
+
criterion,
|
|
4769
|
+
point: best.report?.point ?? null,
|
|
4770
|
+
pnl: best.report ? +best.report.totalPnlPercent.toFixed(2) : null,
|
|
4771
|
+
});
|
|
4772
|
+
}
|
|
4773
|
+
},
|
|
4774
|
+
onDone: (symbol, result) => {
|
|
4775
|
+
if (values.verbose) {
|
|
4776
|
+
console.log("onDone", {
|
|
4777
|
+
symbol,
|
|
4778
|
+
reports: result.reports.length,
|
|
4779
|
+
allowedAuthors: result.allowedAuthors.length,
|
|
4780
|
+
bannedAuthors: result.bannedAuthors.length,
|
|
4781
|
+
});
|
|
4782
|
+
}
|
|
4783
|
+
},
|
|
4784
|
+
},
|
|
4785
|
+
});
|
|
4786
|
+
const result = await Simulator.run({
|
|
4787
|
+
symbol,
|
|
4788
|
+
simulatorName: SIMULATOR_NAME,
|
|
4789
|
+
ideas,
|
|
4790
|
+
});
|
|
4791
|
+
const dumpName = values.output || `simulator_${symbol}_${Date.now()}`;
|
|
4792
|
+
const dumpDir = join(process.cwd(), "dump");
|
|
4793
|
+
if (values.json) {
|
|
4794
|
+
const filePath = resolve(dumpDir, `${dumpName}.json`);
|
|
4795
|
+
await mkdir(dumpDir, { recursive: true });
|
|
4796
|
+
await writeFile(filePath, JSON.stringify(result, null, 2), "utf-8");
|
|
4797
|
+
console.log(`Saved: ${filePath}`);
|
|
4798
|
+
process.exit(0);
|
|
4799
|
+
}
|
|
4800
|
+
if (values.markdown) {
|
|
4801
|
+
const filePath = resolve(dumpDir, `${dumpName}.md`);
|
|
4802
|
+
await mkdir(dumpDir, { recursive: true });
|
|
4803
|
+
await writeFile(filePath, toMarkdown(result), "utf-8");
|
|
4804
|
+
console.log(`Saved: ${filePath}`);
|
|
4805
|
+
process.exit(0);
|
|
4806
|
+
}
|
|
4807
|
+
console.log(toMarkdown(result));
|
|
4808
|
+
process.exit(0);
|
|
4809
|
+
};
|
|
4438
4810
|
main$4();
|
|
4439
4811
|
|
|
4440
4812
|
const __filename$1 = fileURLToPath(import.meta.url);
|
|
@@ -4633,6 +5005,7 @@ Modes:
|
|
|
4633
5005
|
--dump Fetch and save raw OHLCV candles
|
|
4634
5006
|
--pnldebug Simulate PnL per minute for a given entry price and direction
|
|
4635
5007
|
--brokerdebug Fire a single broker commit against the live broker adapter
|
|
5008
|
+
--simulator <ideas.jsonl> Sweep exit/entry parameters over crowd ideas, train author whitelist
|
|
4636
5009
|
--flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
|
|
4637
5010
|
--init Scaffold a new project in the current directory
|
|
4638
5011
|
--docker Scaffold a Docker workspace for running strategies in a container
|
|
@@ -4753,6 +5126,33 @@ Broker debug flags (--brokerdebug):
|
|
|
4753
5126
|
Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
|
|
4754
5127
|
the selected broker commit with synthetic payload values derived from current price.
|
|
4755
5128
|
|
|
5129
|
+
Simulator flags (--simulator):
|
|
5130
|
+
|
|
5131
|
+
--symbol <string> Trading pair to simulate (default: BTCUSDT)
|
|
5132
|
+
--exchange <string> Exchange name (default: first registered)
|
|
5133
|
+
--output <string> Output file base name (default: simulator_{SYMBOL}_{TIMESTAMP})
|
|
5134
|
+
--json Save full ISimulatorResult to ./dump/<output>.json
|
|
5135
|
+
--markdown Save summary report to ./dump/<output>.md
|
|
5136
|
+
--verbose Log every simulator lifecycle callback to the console
|
|
5137
|
+
|
|
5138
|
+
Positional: path to an ideas .jsonl file — one idea per line with the exact shape
|
|
5139
|
+
{ "id": number, "ts": number, "symbol": string, "direction": "LONG"|"SHORT"|"NEUTRAL",
|
|
5140
|
+
"author": string }. The file is validated BEFORE any work; a line that does not
|
|
5141
|
+
match the structure aborts the run with an error.
|
|
5142
|
+
|
|
5143
|
+
Runs the backtest-kit Simulator entity: one 5-day candle pass per idea, flood
|
|
5144
|
+
dedupe (one idea per author per direction per 8h), default-ban author filter
|
|
5145
|
+
(ban thresholds are grid axes), grid evaluation with time-based Sharpe/Sortino,
|
|
5146
|
+
four ranking winners. The report includes the production author whitelist.
|
|
5147
|
+
Ideas of other symbols are filtered out — one shared feed serves any --symbol.
|
|
5148
|
+
|
|
5149
|
+
No output flag → print the Markdown summary to stdout. With --verbose every
|
|
5150
|
+
simulator lifecycle callback (onProgress, onIdeas, onProfiles, onAuthorsTrained,
|
|
5151
|
+
onGridPoint, onRanking, onDone) is logged to the console as it fires.
|
|
5152
|
+
|
|
5153
|
+
Module file ./modules/simulator.module is loaded automatically if it exists
|
|
5154
|
+
(register your exchange there); without it CCXT Binance is used by default.
|
|
5155
|
+
|
|
4756
5156
|
Flush flags (--flush):
|
|
4757
5157
|
|
|
4758
5158
|
One or more positional entry points. For each entry point the following
|
|
@@ -4786,6 +5186,7 @@ Module hooks (loaded automatically by each mode):
|
|
|
4786
5186
|
modules/dump.module --dump Exchange schema for candle dumps
|
|
4787
5187
|
modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
|
|
4788
5188
|
modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
|
|
5189
|
+
modules/simulator.module --simulator Exchange schema for crowd-ideas simulation
|
|
4789
5190
|
|
|
4790
5191
|
--flush has no associated module. It only removes dump subdirectories.
|
|
4791
5192
|
|
|
@@ -4814,6 +5215,8 @@ Examples:
|
|
|
4814
5215
|
node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
|
|
4815
5216
|
node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
|
|
4816
5217
|
node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
|
|
5218
|
+
node ${ENTRY_PATH} --simulator --symbol BTCUSDT ./assets/tv-ideas.normalized.jsonl
|
|
5219
|
+
node ${ENTRY_PATH} --simulator --symbol BTCUSDT --json --output jun_2026_sweep ./assets/tv-ideas.normalized.jsonl
|
|
4817
5220
|
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
|
|
4818
5221
|
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
|
|
4819
5222
|
node ${ENTRY_PATH} --init --output my-trading-bot
|
|
@@ -4827,7 +5230,7 @@ const main$1 = async () => {
|
|
|
4827
5230
|
if (!values.help) {
|
|
4828
5231
|
return;
|
|
4829
5232
|
}
|
|
4830
|
-
process.stdout.write(`@backtest-kit/cli ${"16.
|
|
5233
|
+
process.stdout.write(`@backtest-kit/cli ${"16.5.0"}\n\n`);
|
|
4831
5234
|
process.stdout.write(HELP_TEXT);
|
|
4832
5235
|
process.exit(0);
|
|
4833
5236
|
};
|
|
@@ -4841,7 +5244,7 @@ const main = async () => {
|
|
|
4841
5244
|
if (!values.version) {
|
|
4842
5245
|
return;
|
|
4843
5246
|
}
|
|
4844
|
-
process.stdout.write(`@backtest-kit/cli ${"16.
|
|
5247
|
+
process.stdout.write(`@backtest-kit/cli ${"16.5.0"}\n`);
|
|
4845
5248
|
process.exit(0);
|
|
4846
5249
|
};
|
|
4847
5250
|
main();
|