@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.cjs
CHANGED
|
@@ -233,14 +233,14 @@ const getReadySubject = functoolsKit.singleshot(() => {
|
|
|
233
233
|
return new functoolsKit.Subject();
|
|
234
234
|
});
|
|
235
235
|
|
|
236
|
-
const main$
|
|
236
|
+
const main$j = () => {
|
|
237
237
|
const entrySubject = getEntrySubject();
|
|
238
238
|
entrySubject.subscribe(async (path) => {
|
|
239
239
|
console.log("Running", path);
|
|
240
240
|
await getReadySubject().next();
|
|
241
241
|
});
|
|
242
242
|
};
|
|
243
|
-
main$
|
|
243
|
+
main$j();
|
|
244
244
|
|
|
245
245
|
const ERROR_HANDLER_INSTALLED = Symbol.for("error-handler-installed");
|
|
246
246
|
function dumpStackTrace(error) {
|
|
@@ -891,6 +891,10 @@ const getArgs = functoolsKit.singleshot(() => {
|
|
|
891
891
|
type: "boolean",
|
|
892
892
|
default: false,
|
|
893
893
|
},
|
|
894
|
+
simulator: {
|
|
895
|
+
type: "boolean",
|
|
896
|
+
default: false,
|
|
897
|
+
},
|
|
894
898
|
commit: {
|
|
895
899
|
type: "string",
|
|
896
900
|
default: "",
|
|
@@ -2337,6 +2341,13 @@ const STOP_BOT_FN = functoolsKit.singleshot(async () => {
|
|
|
2337
2341
|
const { stopBot } = await getTelegram();
|
|
2338
2342
|
stopBot();
|
|
2339
2343
|
});
|
|
2344
|
+
/**
|
|
2345
|
+
* Throttle window for the per-tick order_continue decision channel: at most one
|
|
2346
|
+
* Telegram notification per signalId per 15 minutes via functools-kit ttl. The
|
|
2347
|
+
* terminal order_stop is NOT throttled (fires once per signal) and clears the
|
|
2348
|
+
* throttle slot for its signalId.
|
|
2349
|
+
*/
|
|
2350
|
+
const ORDER_CHECK_NOTIFY_TTL = 15 * 60 * 1000;
|
|
2340
2351
|
class TelegramLogicService {
|
|
2341
2352
|
constructor() {
|
|
2342
2353
|
this.loggerService = inject(TYPES.loggerService);
|
|
@@ -2485,11 +2496,24 @@ class TelegramLogicService {
|
|
|
2485
2496
|
markdown,
|
|
2486
2497
|
});
|
|
2487
2498
|
};
|
|
2488
|
-
this.
|
|
2489
|
-
this.loggerService.log("telegramLogicService
|
|
2499
|
+
this.notifyOrderFillOpen = functoolsKit.trycatch(async (event) => {
|
|
2500
|
+
this.loggerService.log("telegramLogicService notifyOrderFillOpen", {
|
|
2501
|
+
event,
|
|
2502
|
+
});
|
|
2503
|
+
const markdown = await this.telegramTemplateService.getOrderFillOpenMarkdown(event);
|
|
2504
|
+
if (!markdown) {
|
|
2505
|
+
return;
|
|
2506
|
+
}
|
|
2507
|
+
await this.telegramWebService.publishNotify({
|
|
2508
|
+
symbol: event.symbol,
|
|
2509
|
+
markdown,
|
|
2510
|
+
});
|
|
2511
|
+
});
|
|
2512
|
+
this.notifyOrderFillClose = functoolsKit.trycatch(async (event) => {
|
|
2513
|
+
this.loggerService.log("telegramLogicService notifyOrderFillClose", {
|
|
2490
2514
|
event,
|
|
2491
2515
|
});
|
|
2492
|
-
const markdown = await this.telegramTemplateService.
|
|
2516
|
+
const markdown = await this.telegramTemplateService.getOrderFillCloseMarkdown(event);
|
|
2493
2517
|
if (!markdown) {
|
|
2494
2518
|
return;
|
|
2495
2519
|
}
|
|
@@ -2498,11 +2522,64 @@ class TelegramLogicService {
|
|
|
2498
2522
|
markdown,
|
|
2499
2523
|
});
|
|
2500
2524
|
});
|
|
2501
|
-
this.
|
|
2502
|
-
this.loggerService.log("telegramLogicService
|
|
2525
|
+
this.notifyOrderRejectOpen = functoolsKit.trycatch(async (event) => {
|
|
2526
|
+
this.loggerService.log("telegramLogicService notifyOrderRejectOpen", {
|
|
2503
2527
|
event,
|
|
2504
2528
|
});
|
|
2505
|
-
const markdown = await this.telegramTemplateService.
|
|
2529
|
+
const markdown = await this.telegramTemplateService.getOrderRejectOpenMarkdown(event);
|
|
2530
|
+
if (!markdown) {
|
|
2531
|
+
return;
|
|
2532
|
+
}
|
|
2533
|
+
await this.telegramWebService.publishNotify({
|
|
2534
|
+
symbol: event.symbol,
|
|
2535
|
+
markdown,
|
|
2536
|
+
});
|
|
2537
|
+
});
|
|
2538
|
+
this.notifyOrderRejectClose = functoolsKit.trycatch(async (event) => {
|
|
2539
|
+
this.loggerService.log("telegramLogicService notifyOrderRejectClose", {
|
|
2540
|
+
event,
|
|
2541
|
+
});
|
|
2542
|
+
const markdown = await this.telegramTemplateService.getOrderRejectCloseMarkdown(event);
|
|
2543
|
+
if (!markdown) {
|
|
2544
|
+
return;
|
|
2545
|
+
}
|
|
2546
|
+
await this.telegramWebService.publishNotify({
|
|
2547
|
+
symbol: event.symbol,
|
|
2548
|
+
markdown,
|
|
2549
|
+
});
|
|
2550
|
+
});
|
|
2551
|
+
/**
|
|
2552
|
+
* Post-verdict order-check CONTINUE (order_continue). Fires every live tick the
|
|
2553
|
+
* order survives the check, so it is throttled via functools-kit ttl:
|
|
2554
|
+
* one message per signalId per ORDER_CHECK_NOTIFY_TTL (15 minutes).
|
|
2555
|
+
*/
|
|
2556
|
+
this.notifyOrderContinue = functoolsKit.ttl(functoolsKit.trycatch(async (event) => {
|
|
2557
|
+
this.loggerService.log("telegramLogicService notifyOrderContinue", {
|
|
2558
|
+
event,
|
|
2559
|
+
});
|
|
2560
|
+
const markdown = await this.telegramTemplateService.getOrderContinueMarkdown(event);
|
|
2561
|
+
if (!markdown) {
|
|
2562
|
+
return;
|
|
2563
|
+
}
|
|
2564
|
+
await this.telegramWebService.publishNotify({
|
|
2565
|
+
symbol: event.symbol,
|
|
2566
|
+
markdown,
|
|
2567
|
+
});
|
|
2568
|
+
}), {
|
|
2569
|
+
key: ([event]) => event.signalId,
|
|
2570
|
+
timeout: ORDER_CHECK_NOTIFY_TTL,
|
|
2571
|
+
});
|
|
2572
|
+
/**
|
|
2573
|
+
* Post-verdict order-check STOP (order_stop). Terminal — fires once per signal,
|
|
2574
|
+
* so no throttle; drops the continue throttle slot for this signalId
|
|
2575
|
+
* (the signal leaves monitoring, the ttl entry would only leak otherwise).
|
|
2576
|
+
*/
|
|
2577
|
+
this.notifyOrderStop = functoolsKit.trycatch(async (event) => {
|
|
2578
|
+
this.loggerService.log("telegramLogicService notifyOrderStop", {
|
|
2579
|
+
event,
|
|
2580
|
+
});
|
|
2581
|
+
this.notifyOrderContinue.clear(event.signalId);
|
|
2582
|
+
const markdown = await this.telegramTemplateService.getOrderStopMarkdown(event);
|
|
2506
2583
|
if (!markdown) {
|
|
2507
2584
|
return;
|
|
2508
2585
|
}
|
|
@@ -2607,26 +2684,51 @@ class TelegramLogicService {
|
|
|
2607
2684
|
return;
|
|
2608
2685
|
}
|
|
2609
2686
|
});
|
|
2610
|
-
|
|
2687
|
+
// Broker-CONFIRMED fills only (post-verdict orderFillSubject): a rejected or
|
|
2688
|
+
// transient gate attempt never reaches this channel, so "Order Filled" is
|
|
2689
|
+
// guaranteed truthful. The pre-verdict syncSubject is NOT consumed here.
|
|
2690
|
+
const unOrderFill = BacktestKit.listenOrderFill(async (event) => {
|
|
2611
2691
|
if (event.action === "signal-open") {
|
|
2612
|
-
// type "
|
|
2613
|
-
//
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2692
|
+
// Both open outcomes notify: type "active" — the order opened at once
|
|
2693
|
+
// (immediate fill, no schedule phase), type "schedule" — the resting
|
|
2694
|
+
// order was placed on the exchange. Either way the broker confirmed a
|
|
2695
|
+
// real order; the template's Order field carries the type.
|
|
2696
|
+
await this.notifyOrderFillOpen(event);
|
|
2697
|
+
return;
|
|
2698
|
+
}
|
|
2699
|
+
if (event.action === "signal-close") {
|
|
2700
|
+
await this.notifyOrderFillClose(event);
|
|
2701
|
+
return;
|
|
2702
|
+
}
|
|
2703
|
+
});
|
|
2704
|
+
// Terminal broker rejection (post-verdict orderRejectSubject): exactly one
|
|
2705
|
+
// event per dropped order attempt — the engine consumes the rejected signal
|
|
2706
|
+
// id, so this cannot repeat per-tick for the same signal. Dispatched by
|
|
2707
|
+
// action to the dedicated open/close notifications (single responsibility).
|
|
2708
|
+
const unOrderReject = BacktestKit.listenOrderReject(async (event) => {
|
|
2709
|
+
if (event.action === "signal-open") {
|
|
2710
|
+
await this.notifyOrderRejectOpen(event);
|
|
2618
2711
|
return;
|
|
2619
2712
|
}
|
|
2620
2713
|
if (event.action === "signal-close") {
|
|
2621
|
-
await this.
|
|
2714
|
+
await this.notifyOrderRejectClose(event);
|
|
2622
2715
|
return;
|
|
2623
2716
|
}
|
|
2624
2717
|
});
|
|
2718
|
+
// Post-verdict check decisions: CONTINUE fires every surviving tick and is
|
|
2719
|
+
// throttled to one message per signalId per 15 minutes via ttl; terminal
|
|
2720
|
+
// STOP fires once per signal, unthrottled.
|
|
2721
|
+
const unOrderContinue = BacktestKit.listenOrderContinue(async (event) => {
|
|
2722
|
+
await this.notifyOrderContinue(event);
|
|
2723
|
+
});
|
|
2724
|
+
const unOrderStop = BacktestKit.listenOrderStop(async (event) => {
|
|
2725
|
+
await this.notifyOrderStop(event);
|
|
2726
|
+
});
|
|
2625
2727
|
const unSignalNotify = BacktestKit.listenSignalNotify(async (event) => {
|
|
2626
2728
|
await this.notifySignalInfo(event);
|
|
2627
2729
|
});
|
|
2628
2730
|
const unConnect = () => this.connect.clear();
|
|
2629
|
-
const unListen = functoolsKit.compose(() => unRisk(), () => unSignal(), () => unCommit(), () =>
|
|
2731
|
+
const unListen = functoolsKit.compose(() => unRisk(), () => unSignal(), () => unCommit(), () => unOrderFill(), () => unOrderReject(), () => unOrderContinue(), () => unOrderStop(), () => this.notifyOrderContinue.clear(), () => unSignalNotify(), () => unConnect());
|
|
2630
2732
|
return () => {
|
|
2631
2733
|
STOP_BOT_FN();
|
|
2632
2734
|
unListen();
|
|
@@ -2779,25 +2881,65 @@ class TelegramTemplateService {
|
|
|
2779
2881
|
}
|
|
2780
2882
|
return await RENDER_TEMPLATE_FN("average-buy.mustache", event, this);
|
|
2781
2883
|
};
|
|
2782
|
-
this.
|
|
2783
|
-
this.loggerService.log("telegramTemplateService
|
|
2884
|
+
this.getOrderFillOpenMarkdown = async (event) => {
|
|
2885
|
+
this.loggerService.log("telegramTemplateService getOrderFillOpenMarkdown", {
|
|
2886
|
+
event,
|
|
2887
|
+
});
|
|
2888
|
+
const adapter = await this.getTelegramAdapter();
|
|
2889
|
+
if (adapter?.getOrderFillOpenMarkdown) {
|
|
2890
|
+
return await adapter.getOrderFillOpenMarkdown(event);
|
|
2891
|
+
}
|
|
2892
|
+
return await RENDER_TEMPLATE_FN("order-fill-open.mustache", event, this);
|
|
2893
|
+
};
|
|
2894
|
+
this.getOrderFillCloseMarkdown = async (event) => {
|
|
2895
|
+
this.loggerService.log("telegramTemplateService getOrderFillCloseMarkdown", {
|
|
2896
|
+
event,
|
|
2897
|
+
});
|
|
2898
|
+
const adapter = await this.getTelegramAdapter();
|
|
2899
|
+
if (adapter?.getOrderFillCloseMarkdown) {
|
|
2900
|
+
return await adapter.getOrderFillCloseMarkdown(event);
|
|
2901
|
+
}
|
|
2902
|
+
return await RENDER_TEMPLATE_FN("order-fill-close.mustache", event, this);
|
|
2903
|
+
};
|
|
2904
|
+
this.getOrderRejectOpenMarkdown = async (event) => {
|
|
2905
|
+
this.loggerService.log("telegramTemplateService getOrderRejectOpenMarkdown", {
|
|
2906
|
+
event,
|
|
2907
|
+
});
|
|
2908
|
+
const adapter = await this.getTelegramAdapter();
|
|
2909
|
+
if (adapter?.getOrderRejectOpenMarkdown) {
|
|
2910
|
+
return await adapter.getOrderRejectOpenMarkdown(event);
|
|
2911
|
+
}
|
|
2912
|
+
return await RENDER_TEMPLATE_FN("order-reject-open.mustache", event, this);
|
|
2913
|
+
};
|
|
2914
|
+
this.getOrderRejectCloseMarkdown = async (event) => {
|
|
2915
|
+
this.loggerService.log("telegramTemplateService getOrderRejectCloseMarkdown", {
|
|
2916
|
+
event,
|
|
2917
|
+
});
|
|
2918
|
+
const adapter = await this.getTelegramAdapter();
|
|
2919
|
+
if (adapter?.getOrderRejectCloseMarkdown) {
|
|
2920
|
+
return await adapter.getOrderRejectCloseMarkdown(event);
|
|
2921
|
+
}
|
|
2922
|
+
return await RENDER_TEMPLATE_FN("order-reject-close.mustache", event, this);
|
|
2923
|
+
};
|
|
2924
|
+
this.getOrderContinueMarkdown = async (event) => {
|
|
2925
|
+
this.loggerService.log("telegramTemplateService getOrderContinueMarkdown", {
|
|
2784
2926
|
event,
|
|
2785
2927
|
});
|
|
2786
2928
|
const adapter = await this.getTelegramAdapter();
|
|
2787
|
-
if (adapter?.
|
|
2788
|
-
return await adapter.
|
|
2929
|
+
if (adapter?.getOrderContinueMarkdown) {
|
|
2930
|
+
return await adapter.getOrderContinueMarkdown(event);
|
|
2789
2931
|
}
|
|
2790
|
-
return await RENDER_TEMPLATE_FN("
|
|
2932
|
+
return await RENDER_TEMPLATE_FN("order-continue.mustache", event, this);
|
|
2791
2933
|
};
|
|
2792
|
-
this.
|
|
2793
|
-
this.loggerService.log("telegramTemplateService
|
|
2934
|
+
this.getOrderStopMarkdown = async (event) => {
|
|
2935
|
+
this.loggerService.log("telegramTemplateService getOrderStopMarkdown", {
|
|
2794
2936
|
event,
|
|
2795
2937
|
});
|
|
2796
2938
|
const adapter = await this.getTelegramAdapter();
|
|
2797
|
-
if (adapter?.
|
|
2798
|
-
return await adapter.
|
|
2939
|
+
if (adapter?.getOrderStopMarkdown) {
|
|
2940
|
+
return await adapter.getOrderStopMarkdown(event);
|
|
2799
2941
|
}
|
|
2800
|
-
return await RENDER_TEMPLATE_FN("
|
|
2942
|
+
return await RENDER_TEMPLATE_FN("order-stop.mustache", event, this);
|
|
2801
2943
|
};
|
|
2802
2944
|
this.getCancelScheduledMarkdown = async (event) => {
|
|
2803
2945
|
this.loggerService.log("telegramTemplateService getCancelScheduledMarkdown", {
|
|
@@ -3534,14 +3676,14 @@ const cli = {
|
|
|
3534
3676
|
};
|
|
3535
3677
|
init();
|
|
3536
3678
|
|
|
3537
|
-
const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
|
|
3679
|
+
const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "simulator", "flush", "init", "docker", "help", "version"];
|
|
3538
3680
|
const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
3539
3681
|
const HELP_TEXT$1 = `
|
|
3540
3682
|
Example:
|
|
3541
3683
|
|
|
3542
3684
|
node ${ENTRY_PATH$1} --help
|
|
3543
3685
|
`.trimStart();
|
|
3544
|
-
const main$
|
|
3686
|
+
const main$i = async () => {
|
|
3545
3687
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3546
3688
|
return;
|
|
3547
3689
|
}
|
|
@@ -3549,14 +3691,14 @@ const main$h = async () => {
|
|
|
3549
3691
|
if (MODES.some((mode) => values[mode])) {
|
|
3550
3692
|
return;
|
|
3551
3693
|
}
|
|
3552
|
-
process.stdout.write(`@backtest-kit/cli ${"16.
|
|
3694
|
+
process.stdout.write(`@backtest-kit/cli ${"16.5.0"}\n`);
|
|
3553
3695
|
process.stdout.write("\n");
|
|
3554
3696
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
3555
3697
|
process.stdout.write("\n");
|
|
3556
3698
|
process.stdout.write(HELP_TEXT$1);
|
|
3557
3699
|
process.exit(0);
|
|
3558
3700
|
};
|
|
3559
|
-
main$
|
|
3701
|
+
main$i();
|
|
3560
3702
|
|
|
3561
3703
|
const notifyShutdown = functoolsKit.singleshot(async () => {
|
|
3562
3704
|
console.log("Graceful shutdown initiated.");
|
|
@@ -3572,7 +3714,7 @@ const flush = async (entryPoint) => {
|
|
|
3572
3714
|
console.log(`Removed: ${target}`);
|
|
3573
3715
|
}
|
|
3574
3716
|
};
|
|
3575
|
-
const main$
|
|
3717
|
+
const main$h = async () => {
|
|
3576
3718
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3577
3719
|
return;
|
|
3578
3720
|
}
|
|
@@ -3589,7 +3731,7 @@ const main$g = async () => {
|
|
|
3589
3731
|
}
|
|
3590
3732
|
process.exit(0);
|
|
3591
3733
|
};
|
|
3592
|
-
main$
|
|
3734
|
+
main$h();
|
|
3593
3735
|
|
|
3594
3736
|
const BEFORE_EXIT_FN$6 = functoolsKit.singleshot(async () => {
|
|
3595
3737
|
process.off("SIGINT", BEFORE_EXIT_FN$6);
|
|
@@ -3612,7 +3754,7 @@ const BEFORE_EXIT_FN$6 = functoolsKit.singleshot(async () => {
|
|
|
3612
3754
|
const listenGracefulShutdown$6 = functoolsKit.singleshot(() => {
|
|
3613
3755
|
process.on("SIGINT", BEFORE_EXIT_FN$6);
|
|
3614
3756
|
});
|
|
3615
|
-
const main$
|
|
3757
|
+
const main$g = async () => {
|
|
3616
3758
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3617
3759
|
return;
|
|
3618
3760
|
}
|
|
@@ -3630,7 +3772,7 @@ const main$f = async () => {
|
|
|
3630
3772
|
await cli.backtestMainService.connect();
|
|
3631
3773
|
listenGracefulShutdown$6();
|
|
3632
3774
|
};
|
|
3633
|
-
main$
|
|
3775
|
+
main$g();
|
|
3634
3776
|
|
|
3635
3777
|
const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
|
|
3636
3778
|
process.off("SIGINT", BEFORE_EXIT_FN$5);
|
|
@@ -3649,7 +3791,7 @@ const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
|
|
|
3649
3791
|
const listenGracefulShutdown$5 = functoolsKit.singleshot(() => {
|
|
3650
3792
|
process.on("SIGINT", BEFORE_EXIT_FN$5);
|
|
3651
3793
|
});
|
|
3652
|
-
const main$
|
|
3794
|
+
const main$f = async () => {
|
|
3653
3795
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3654
3796
|
return;
|
|
3655
3797
|
}
|
|
@@ -3668,7 +3810,7 @@ const main$e = async () => {
|
|
|
3668
3810
|
listenGracefulShutdown$5();
|
|
3669
3811
|
await cli.walkerMainService.connect();
|
|
3670
3812
|
};
|
|
3671
|
-
main$
|
|
3813
|
+
main$f();
|
|
3672
3814
|
|
|
3673
3815
|
const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
|
|
3674
3816
|
process.off("SIGINT", BEFORE_EXIT_FN$4);
|
|
@@ -3690,7 +3832,7 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
|
|
|
3690
3832
|
const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
|
|
3691
3833
|
process.on("SIGINT", BEFORE_EXIT_FN$4);
|
|
3692
3834
|
});
|
|
3693
|
-
const main$
|
|
3835
|
+
const main$e = async () => {
|
|
3694
3836
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3695
3837
|
return;
|
|
3696
3838
|
}
|
|
@@ -3704,7 +3846,7 @@ const main$d = async () => {
|
|
|
3704
3846
|
await cli.paperMainService.connect();
|
|
3705
3847
|
listenGracefulShutdown$4();
|
|
3706
3848
|
};
|
|
3707
|
-
main$
|
|
3849
|
+
main$e();
|
|
3708
3850
|
|
|
3709
3851
|
const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
|
|
3710
3852
|
process.off("SIGINT", BEFORE_EXIT_FN$3);
|
|
@@ -3726,7 +3868,7 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
|
|
|
3726
3868
|
const listenGracefulShutdown$3 = functoolsKit.singleshot(() => {
|
|
3727
3869
|
process.on("SIGINT", BEFORE_EXIT_FN$3);
|
|
3728
3870
|
});
|
|
3729
|
-
const main$
|
|
3871
|
+
const main$d = async () => {
|
|
3730
3872
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3731
3873
|
return;
|
|
3732
3874
|
}
|
|
@@ -3740,7 +3882,7 @@ const main$c = async () => {
|
|
|
3740
3882
|
await cli.liveMainService.connect();
|
|
3741
3883
|
listenGracefulShutdown$3();
|
|
3742
3884
|
};
|
|
3743
|
-
main$
|
|
3885
|
+
main$d();
|
|
3744
3886
|
|
|
3745
3887
|
const stopBacktestList$1 = async () => {
|
|
3746
3888
|
for (const item of await BacktestKit.Backtest.list()) {
|
|
@@ -3804,7 +3946,7 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
|
|
|
3804
3946
|
const listenGracefulShutdown$2 = functoolsKit.singleshot(() => {
|
|
3805
3947
|
process.on("SIGINT", BEFORE_EXIT_FN$2);
|
|
3806
3948
|
});
|
|
3807
|
-
const main$
|
|
3949
|
+
const main$c = async () => {
|
|
3808
3950
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3809
3951
|
return;
|
|
3810
3952
|
}
|
|
@@ -3860,7 +4002,7 @@ const main$b = async () => {
|
|
|
3860
4002
|
listenGracefulShutdown$2();
|
|
3861
4003
|
await cli.resolveService.attachEntry(path.resolve(cwd, entryPoint));
|
|
3862
4004
|
};
|
|
3863
|
-
main$
|
|
4005
|
+
main$c();
|
|
3864
4006
|
|
|
3865
4007
|
const MODE_MODULE = {
|
|
3866
4008
|
backtest: "backtest.module",
|
|
@@ -3963,7 +4105,7 @@ const createGracefulShutdown = (mode) => {
|
|
|
3963
4105
|
process.on("SIGINT", handler);
|
|
3964
4106
|
});
|
|
3965
4107
|
};
|
|
3966
|
-
const main$
|
|
4108
|
+
const main$b = async () => {
|
|
3967
4109
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3968
4110
|
return;
|
|
3969
4111
|
}
|
|
@@ -4039,7 +4181,7 @@ const main$a = async () => {
|
|
|
4039
4181
|
}
|
|
4040
4182
|
await getEntrySubject().next(absolutePath);
|
|
4041
4183
|
};
|
|
4042
|
-
main$
|
|
4184
|
+
main$b();
|
|
4043
4185
|
|
|
4044
4186
|
const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
|
|
4045
4187
|
process.off("SIGINT", BEFORE_EXIT_FN$1);
|
|
@@ -4050,7 +4192,7 @@ const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
|
|
|
4050
4192
|
const listenGracefulShutdown$1 = functoolsKit.singleshot(() => {
|
|
4051
4193
|
process.on("SIGINT", BEFORE_EXIT_FN$1);
|
|
4052
4194
|
});
|
|
4053
|
-
const main$
|
|
4195
|
+
const main$a = async () => {
|
|
4054
4196
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
4055
4197
|
return;
|
|
4056
4198
|
}
|
|
@@ -4060,7 +4202,7 @@ const main$9 = async () => {
|
|
|
4060
4202
|
}
|
|
4061
4203
|
listenGracefulShutdown$1();
|
|
4062
4204
|
};
|
|
4063
|
-
main$
|
|
4205
|
+
main$a();
|
|
4064
4206
|
|
|
4065
4207
|
const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
|
|
4066
4208
|
process.off("SIGINT", BEFORE_EXIT_FN);
|
|
@@ -4071,7 +4213,7 @@ const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
|
|
|
4071
4213
|
const listenGracefulShutdown = functoolsKit.singleshot(() => {
|
|
4072
4214
|
process.on("SIGINT", BEFORE_EXIT_FN);
|
|
4073
4215
|
});
|
|
4074
|
-
const main$
|
|
4216
|
+
const main$9 = async () => {
|
|
4075
4217
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
4076
4218
|
return;
|
|
4077
4219
|
}
|
|
@@ -4081,7 +4223,7 @@ const main$8 = async () => {
|
|
|
4081
4223
|
}
|
|
4082
4224
|
listenGracefulShutdown();
|
|
4083
4225
|
};
|
|
4084
|
-
main$
|
|
4226
|
+
main$9();
|
|
4085
4227
|
|
|
4086
4228
|
const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
4087
4229
|
const keys = Object.keys(schema);
|
|
@@ -4103,7 +4245,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
|
4103
4245
|
}
|
|
4104
4246
|
return rows;
|
|
4105
4247
|
};
|
|
4106
|
-
const main$
|
|
4248
|
+
const main$8 = async () => {
|
|
4107
4249
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
4108
4250
|
return;
|
|
4109
4251
|
}
|
|
@@ -4203,9 +4345,9 @@ const main$7 = async () => {
|
|
|
4203
4345
|
console.log(await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema));
|
|
4204
4346
|
process.exit(0);
|
|
4205
4347
|
};
|
|
4206
|
-
main$
|
|
4348
|
+
main$8();
|
|
4207
4349
|
|
|
4208
|
-
const main$
|
|
4350
|
+
const main$7 = async () => {
|
|
4209
4351
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
4210
4352
|
return;
|
|
4211
4353
|
}
|
|
@@ -4268,9 +4410,9 @@ const main$6 = async () => {
|
|
|
4268
4410
|
};
|
|
4269
4411
|
process.on("SIGINT", beforeExit);
|
|
4270
4412
|
};
|
|
4271
|
-
main$
|
|
4413
|
+
main$7();
|
|
4272
4414
|
|
|
4273
|
-
const main$
|
|
4415
|
+
const main$6 = async () => {
|
|
4274
4416
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
4275
4417
|
return;
|
|
4276
4418
|
}
|
|
@@ -4351,9 +4493,9 @@ const main$5 = async () => {
|
|
|
4351
4493
|
console.log(JSON.stringify(candles, null, 2));
|
|
4352
4494
|
process.exit(0);
|
|
4353
4495
|
};
|
|
4354
|
-
main$
|
|
4496
|
+
main$6();
|
|
4355
4497
|
|
|
4356
|
-
const main$
|
|
4498
|
+
const main$5 = async () => {
|
|
4357
4499
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
4358
4500
|
return;
|
|
4359
4501
|
}
|
|
@@ -4467,6 +4609,236 @@ const main$4 = async () => {
|
|
|
4467
4609
|
}
|
|
4468
4610
|
process.exit(0);
|
|
4469
4611
|
};
|
|
4612
|
+
main$5();
|
|
4613
|
+
|
|
4614
|
+
const SIMULATOR_NAME = "cli_simulator";
|
|
4615
|
+
const IDEA_DIRECTIONS = ["LONG", "SHORT", "NEUTRAL"];
|
|
4616
|
+
const validateIdea = (idea, line) => {
|
|
4617
|
+
if (typeof idea !== "object" || idea === null) {
|
|
4618
|
+
return `line ${line}: not an object`;
|
|
4619
|
+
}
|
|
4620
|
+
if (typeof idea.id !== "number") {
|
|
4621
|
+
return `line ${line}: "id" must be a number, got ${typeof idea.id}`;
|
|
4622
|
+
}
|
|
4623
|
+
if (typeof idea.ts !== "number") {
|
|
4624
|
+
return `line ${line}: "ts" must be a number (unix ms), got ${typeof idea.ts}`;
|
|
4625
|
+
}
|
|
4626
|
+
if (typeof idea.symbol !== "string" || !idea.symbol) {
|
|
4627
|
+
return `line ${line}: "symbol" must be a non-empty string`;
|
|
4628
|
+
}
|
|
4629
|
+
if (!IDEA_DIRECTIONS.includes(idea.direction)) {
|
|
4630
|
+
return `line ${line}: "direction" must be one of ${IDEA_DIRECTIONS.join("|")}, got ${JSON.stringify(idea.direction)}`;
|
|
4631
|
+
}
|
|
4632
|
+
if (typeof idea.author !== "string" || !idea.author) {
|
|
4633
|
+
return `line ${line}: "author" must be a non-empty string`;
|
|
4634
|
+
}
|
|
4635
|
+
return null;
|
|
4636
|
+
};
|
|
4637
|
+
const toMarkdown = (result) => {
|
|
4638
|
+
const lines = [];
|
|
4639
|
+
lines.push(`# Simulator Report — ${result.symbol}`);
|
|
4640
|
+
lines.push("");
|
|
4641
|
+
lines.push(`| Metric | Value |`);
|
|
4642
|
+
lines.push(`| --- | --- |`);
|
|
4643
|
+
lines.push(`| Ideas (total / directional) | ${result.ideasTotal} / ${result.ideasDirectional} |`);
|
|
4644
|
+
lines.push(`| Profiles (truncated) | ${result.profileCount} (${result.truncatedCount}) |`);
|
|
4645
|
+
lines.push(`| Authors allowed / banned | ${result.allowedAuthors.length} / ${result.bannedAuthors.length} |`);
|
|
4646
|
+
lines.push(`| Grid points | ${result.reports.length} |`);
|
|
4647
|
+
lines.push(`| Hold minutes avg / p95 / p99 | ${result.avgHoldMinutes.toFixed(0)} / ${result.p95HoldMinutes} / ${result.p99HoldMinutes} |`);
|
|
4648
|
+
lines.push("");
|
|
4649
|
+
lines.push(`## Ranking winners`);
|
|
4650
|
+
lines.push("");
|
|
4651
|
+
lines.push(`| Criterion | Stop% | Trailing% | Hold | Aligned | Track | HitRate | Trades | PNL% | WinRate | Sharpe | Sortino |`);
|
|
4652
|
+
lines.push(`| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |`);
|
|
4653
|
+
for (const best of result.best) {
|
|
4654
|
+
if (!best.report) {
|
|
4655
|
+
lines.push(`| ${best.criterion} | — | — | — | — | — | — | — | — | — | — | — |`);
|
|
4656
|
+
continue;
|
|
4657
|
+
}
|
|
4658
|
+
const { point } = best.report;
|
|
4659
|
+
lines.push(`| ${best.criterion} | ${point.hardStopPercent} | ${point.trailingTakePercent} | ${point.holdMinutes / 60}h | ${point.minIdeasAligned} | ${point.minAuthorTrack} | ${point.minAuthorHitRate} | ` +
|
|
4660
|
+
`${best.report.trades} | ${best.report.totalPnlPercent.toFixed(2)}% | ${(best.report.winRate * 100).toFixed(0)}% | ${best.report.sharpe.toFixed(2)} | ${best.report.sortino.toFixed(2)} |`);
|
|
4661
|
+
}
|
|
4662
|
+
lines.push("");
|
|
4663
|
+
lines.push(`## Allowed authors (production whitelist)`);
|
|
4664
|
+
lines.push("");
|
|
4665
|
+
lines.push(`| Author | Ideas | Hits | HitRate |`);
|
|
4666
|
+
lines.push(`| --- | --- | --- | --- |`);
|
|
4667
|
+
for (const stat of result.authorStats.filter(({ banned }) => !banned)) {
|
|
4668
|
+
lines.push(`| ${stat.author} | ${stat.ideas} | ${stat.hits} | ${(stat.hitRate * 100).toFixed(0)}% |`);
|
|
4669
|
+
}
|
|
4670
|
+
return lines.join("\n");
|
|
4671
|
+
};
|
|
4672
|
+
const main$4 = async () => {
|
|
4673
|
+
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
4674
|
+
return;
|
|
4675
|
+
}
|
|
4676
|
+
const { values, positionals } = getArgs();
|
|
4677
|
+
if (!values.simulator) {
|
|
4678
|
+
return;
|
|
4679
|
+
}
|
|
4680
|
+
const [ideasPath = null] = positionals.filter((value) => value.endsWith(".jsonl"));
|
|
4681
|
+
if (!ideasPath) {
|
|
4682
|
+
console.error("Error: positional path to an ideas .jsonl file is required");
|
|
4683
|
+
process.exit(1);
|
|
4684
|
+
}
|
|
4685
|
+
let ideas = [];
|
|
4686
|
+
{
|
|
4687
|
+
let content;
|
|
4688
|
+
try {
|
|
4689
|
+
content = await fs$1.readFile(path.resolve(ideasPath), "utf-8");
|
|
4690
|
+
}
|
|
4691
|
+
catch (error) {
|
|
4692
|
+
console.error(`Error: cannot read ideas file: ${ideasPath}`);
|
|
4693
|
+
process.exit(1);
|
|
4694
|
+
}
|
|
4695
|
+
const rows = content.split("\n").filter(Boolean);
|
|
4696
|
+
if (!rows.length) {
|
|
4697
|
+
console.error(`Error: ideas file is empty: ${ideasPath}`);
|
|
4698
|
+
process.exit(1);
|
|
4699
|
+
}
|
|
4700
|
+
for (let i = 0; i < rows.length; i++) {
|
|
4701
|
+
let idea;
|
|
4702
|
+
try {
|
|
4703
|
+
idea = JSON.parse(rows[i]);
|
|
4704
|
+
}
|
|
4705
|
+
catch {
|
|
4706
|
+
console.error(`Error: invalid JSON in ideas file, line ${i + 1}`);
|
|
4707
|
+
process.exit(1);
|
|
4708
|
+
}
|
|
4709
|
+
const problem = validateIdea(idea, i + 1);
|
|
4710
|
+
if (problem) {
|
|
4711
|
+
console.error(`Error: ideas file does not match the idea structure — ${problem}`);
|
|
4712
|
+
console.error(`Expected shape: { "id": number, "ts": number, "symbol": string, "direction": "LONG"|"SHORT"|"NEUTRAL", "author": string }`);
|
|
4713
|
+
process.exit(1);
|
|
4714
|
+
}
|
|
4715
|
+
ideas.push(idea);
|
|
4716
|
+
}
|
|
4717
|
+
}
|
|
4718
|
+
{
|
|
4719
|
+
const cwd = process.cwd();
|
|
4720
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
4721
|
+
}
|
|
4722
|
+
await cli.configConnectionService.loadConfig("setup.config");
|
|
4723
|
+
{
|
|
4724
|
+
const loader = await cli.configConnectionService.loadConfig("loader.config");
|
|
4725
|
+
try {
|
|
4726
|
+
if (typeof loader === "function") {
|
|
4727
|
+
await loader();
|
|
4728
|
+
}
|
|
4729
|
+
if (typeof loader?.loader === "function") {
|
|
4730
|
+
await loader.loader();
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
catch (error) {
|
|
4734
|
+
console.error("Module loader failed", error);
|
|
4735
|
+
process.exit(-1);
|
|
4736
|
+
}
|
|
4737
|
+
}
|
|
4738
|
+
await cli.moduleConnectionService.loadModule("simulator.module");
|
|
4739
|
+
{
|
|
4740
|
+
await cli.exchangeSchemaService.addSchema();
|
|
4741
|
+
await cli.symbolSchemaService.addSchema();
|
|
4742
|
+
}
|
|
4743
|
+
const [defaultExchangeName = null] = await BacktestKit.listExchangeSchema();
|
|
4744
|
+
const exchangeName = values.exchange || defaultExchangeName?.exchangeName;
|
|
4745
|
+
const symbol = values.symbol || "BTCUSDT";
|
|
4746
|
+
BacktestKit.addSimulatorSchema({
|
|
4747
|
+
simulatorName: SIMULATOR_NAME,
|
|
4748
|
+
exchangeName,
|
|
4749
|
+
gridAxes: {
|
|
4750
|
+
hardStopPercent: [1, 1.5, 2, 2.5, 3, 4, 5, 7],
|
|
4751
|
+
trailingTakePercent: [0.5, 1, 1.5, 2, 3, 4],
|
|
4752
|
+
holdMinutes: [24 * 60, 2 * 24 * 60, 3 * 24 * 60],
|
|
4753
|
+
minIdeasAligned: [1, 2, 3],
|
|
4754
|
+
minAuthorTrack: [2, 3, 5],
|
|
4755
|
+
minAuthorHitRate: [0.5, 0.6],
|
|
4756
|
+
minWeightAligned: [0, 0.6, 1.2],
|
|
4757
|
+
profitLockPercent: [0, 1.5, 2.5],
|
|
4758
|
+
},
|
|
4759
|
+
callbacks: {
|
|
4760
|
+
onProgress: (symbol, stage, processed, total) => {
|
|
4761
|
+
if (values.verbose) {
|
|
4762
|
+
console.log("onProgress", { symbol, stage, processed, total });
|
|
4763
|
+
}
|
|
4764
|
+
},
|
|
4765
|
+
onIdeas: (symbol, ideasTotal, ideasDirectional) => {
|
|
4766
|
+
if (values.verbose) {
|
|
4767
|
+
console.log("onIdeas", { symbol, ideasTotal, ideasDirectional });
|
|
4768
|
+
}
|
|
4769
|
+
},
|
|
4770
|
+
onProfiles: (symbol, profiles, truncatedCount) => {
|
|
4771
|
+
if (values.verbose) {
|
|
4772
|
+
console.log("onProfiles", { symbol, profiles: profiles.length, truncatedCount });
|
|
4773
|
+
}
|
|
4774
|
+
},
|
|
4775
|
+
onAuthorsTrained: (symbol, stats, bannedIdeas) => {
|
|
4776
|
+
if (values.verbose) {
|
|
4777
|
+
console.log("onAuthorsTrained", {
|
|
4778
|
+
symbol,
|
|
4779
|
+
authors: stats.length,
|
|
4780
|
+
banned: stats.filter(({ banned }) => banned).length,
|
|
4781
|
+
bannedIdeas,
|
|
4782
|
+
});
|
|
4783
|
+
}
|
|
4784
|
+
},
|
|
4785
|
+
onGridPoint: (symbol, report) => {
|
|
4786
|
+
if (values.verbose) {
|
|
4787
|
+
console.log("onGridPoint", {
|
|
4788
|
+
symbol,
|
|
4789
|
+
point: report.point,
|
|
4790
|
+
trades: report.trades,
|
|
4791
|
+
pnl: +report.totalPnlPercent.toFixed(2),
|
|
4792
|
+
sharpe: +report.sharpe.toFixed(2),
|
|
4793
|
+
});
|
|
4794
|
+
}
|
|
4795
|
+
},
|
|
4796
|
+
onRanking: (symbol, criterion, _sorted, best) => {
|
|
4797
|
+
if (values.verbose) {
|
|
4798
|
+
console.log("onRanking", {
|
|
4799
|
+
symbol,
|
|
4800
|
+
criterion,
|
|
4801
|
+
point: best.report?.point ?? null,
|
|
4802
|
+
pnl: best.report ? +best.report.totalPnlPercent.toFixed(2) : null,
|
|
4803
|
+
});
|
|
4804
|
+
}
|
|
4805
|
+
},
|
|
4806
|
+
onDone: (symbol, result) => {
|
|
4807
|
+
if (values.verbose) {
|
|
4808
|
+
console.log("onDone", {
|
|
4809
|
+
symbol,
|
|
4810
|
+
reports: result.reports.length,
|
|
4811
|
+
allowedAuthors: result.allowedAuthors.length,
|
|
4812
|
+
bannedAuthors: result.bannedAuthors.length,
|
|
4813
|
+
});
|
|
4814
|
+
}
|
|
4815
|
+
},
|
|
4816
|
+
},
|
|
4817
|
+
});
|
|
4818
|
+
const result = await BacktestKit.Simulator.run({
|
|
4819
|
+
symbol,
|
|
4820
|
+
simulatorName: SIMULATOR_NAME,
|
|
4821
|
+
ideas,
|
|
4822
|
+
});
|
|
4823
|
+
const dumpName = values.output || `simulator_${symbol}_${Date.now()}`;
|
|
4824
|
+
const dumpDir = path.join(process.cwd(), "dump");
|
|
4825
|
+
if (values.json) {
|
|
4826
|
+
const filePath = path.resolve(dumpDir, `${dumpName}.json`);
|
|
4827
|
+
await fs$1.mkdir(dumpDir, { recursive: true });
|
|
4828
|
+
await fs$1.writeFile(filePath, JSON.stringify(result, null, 2), "utf-8");
|
|
4829
|
+
console.log(`Saved: ${filePath}`);
|
|
4830
|
+
process.exit(0);
|
|
4831
|
+
}
|
|
4832
|
+
if (values.markdown) {
|
|
4833
|
+
const filePath = path.resolve(dumpDir, `${dumpName}.md`);
|
|
4834
|
+
await fs$1.mkdir(dumpDir, { recursive: true });
|
|
4835
|
+
await fs$1.writeFile(filePath, toMarkdown(result), "utf-8");
|
|
4836
|
+
console.log(`Saved: ${filePath}`);
|
|
4837
|
+
process.exit(0);
|
|
4838
|
+
}
|
|
4839
|
+
console.log(toMarkdown(result));
|
|
4840
|
+
process.exit(0);
|
|
4841
|
+
};
|
|
4470
4842
|
main$4();
|
|
4471
4843
|
|
|
4472
4844
|
const __filename$2 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
@@ -4665,6 +5037,7 @@ Modes:
|
|
|
4665
5037
|
--dump Fetch and save raw OHLCV candles
|
|
4666
5038
|
--pnldebug Simulate PnL per minute for a given entry price and direction
|
|
4667
5039
|
--brokerdebug Fire a single broker commit against the live broker adapter
|
|
5040
|
+
--simulator <ideas.jsonl> Sweep exit/entry parameters over crowd ideas, train author whitelist
|
|
4668
5041
|
--flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
|
|
4669
5042
|
--init Scaffold a new project in the current directory
|
|
4670
5043
|
--docker Scaffold a Docker workspace for running strategies in a container
|
|
@@ -4785,6 +5158,33 @@ Broker debug flags (--brokerdebug):
|
|
|
4785
5158
|
Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
|
|
4786
5159
|
the selected broker commit with synthetic payload values derived from current price.
|
|
4787
5160
|
|
|
5161
|
+
Simulator flags (--simulator):
|
|
5162
|
+
|
|
5163
|
+
--symbol <string> Trading pair to simulate (default: BTCUSDT)
|
|
5164
|
+
--exchange <string> Exchange name (default: first registered)
|
|
5165
|
+
--output <string> Output file base name (default: simulator_{SYMBOL}_{TIMESTAMP})
|
|
5166
|
+
--json Save full ISimulatorResult to ./dump/<output>.json
|
|
5167
|
+
--markdown Save summary report to ./dump/<output>.md
|
|
5168
|
+
--verbose Log every simulator lifecycle callback to the console
|
|
5169
|
+
|
|
5170
|
+
Positional: path to an ideas .jsonl file — one idea per line with the exact shape
|
|
5171
|
+
{ "id": number, "ts": number, "symbol": string, "direction": "LONG"|"SHORT"|"NEUTRAL",
|
|
5172
|
+
"author": string }. The file is validated BEFORE any work; a line that does not
|
|
5173
|
+
match the structure aborts the run with an error.
|
|
5174
|
+
|
|
5175
|
+
Runs the backtest-kit Simulator entity: one 5-day candle pass per idea, flood
|
|
5176
|
+
dedupe (one idea per author per direction per 8h), default-ban author filter
|
|
5177
|
+
(ban thresholds are grid axes), grid evaluation with time-based Sharpe/Sortino,
|
|
5178
|
+
four ranking winners. The report includes the production author whitelist.
|
|
5179
|
+
Ideas of other symbols are filtered out — one shared feed serves any --symbol.
|
|
5180
|
+
|
|
5181
|
+
No output flag → print the Markdown summary to stdout. With --verbose every
|
|
5182
|
+
simulator lifecycle callback (onProgress, onIdeas, onProfiles, onAuthorsTrained,
|
|
5183
|
+
onGridPoint, onRanking, onDone) is logged to the console as it fires.
|
|
5184
|
+
|
|
5185
|
+
Module file ./modules/simulator.module is loaded automatically if it exists
|
|
5186
|
+
(register your exchange there); without it CCXT Binance is used by default.
|
|
5187
|
+
|
|
4788
5188
|
Flush flags (--flush):
|
|
4789
5189
|
|
|
4790
5190
|
One or more positional entry points. For each entry point the following
|
|
@@ -4818,6 +5218,7 @@ Module hooks (loaded automatically by each mode):
|
|
|
4818
5218
|
modules/dump.module --dump Exchange schema for candle dumps
|
|
4819
5219
|
modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
|
|
4820
5220
|
modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
|
|
5221
|
+
modules/simulator.module --simulator Exchange schema for crowd-ideas simulation
|
|
4821
5222
|
|
|
4822
5223
|
--flush has no associated module. It only removes dump subdirectories.
|
|
4823
5224
|
|
|
@@ -4846,6 +5247,8 @@ Examples:
|
|
|
4846
5247
|
node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
|
|
4847
5248
|
node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
|
|
4848
5249
|
node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
|
|
5250
|
+
node ${ENTRY_PATH} --simulator --symbol BTCUSDT ./assets/tv-ideas.normalized.jsonl
|
|
5251
|
+
node ${ENTRY_PATH} --simulator --symbol BTCUSDT --json --output jun_2026_sweep ./assets/tv-ideas.normalized.jsonl
|
|
4849
5252
|
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
|
|
4850
5253
|
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
|
|
4851
5254
|
node ${ENTRY_PATH} --init --output my-trading-bot
|
|
@@ -4859,7 +5262,7 @@ const main$1 = async () => {
|
|
|
4859
5262
|
if (!values.help) {
|
|
4860
5263
|
return;
|
|
4861
5264
|
}
|
|
4862
|
-
process.stdout.write(`@backtest-kit/cli ${"16.
|
|
5265
|
+
process.stdout.write(`@backtest-kit/cli ${"16.5.0"}\n\n`);
|
|
4863
5266
|
process.stdout.write(HELP_TEXT);
|
|
4864
5267
|
process.exit(0);
|
|
4865
5268
|
};
|
|
@@ -4873,7 +5276,7 @@ const main = async () => {
|
|
|
4873
5276
|
if (!values.version) {
|
|
4874
5277
|
return;
|
|
4875
5278
|
}
|
|
4876
|
-
process.stdout.write(`@backtest-kit/cli ${"16.
|
|
5279
|
+
process.stdout.write(`@backtest-kit/cli ${"16.5.0"}\n`);
|
|
4877
5280
|
process.exit(0);
|
|
4878
5281
|
};
|
|
4879
5282
|
main();
|