@backtest-kit/cli 16.5.0 → 16.6.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 +39 -7
- package/build/index.cjs +727 -67
- package/build/index.mjs +732 -72
- package/docker/package.json +6 -6
- package/package.json +13 -13
- package/template/project/package.mustache +5 -5
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$l = () => {
|
|
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$l();
|
|
244
244
|
|
|
245
245
|
const ERROR_HANDLER_INSTALLED = Symbol.for("error-handler-installed");
|
|
246
246
|
function dumpStackTrace(error) {
|
|
@@ -895,6 +895,14 @@ const getArgs = functoolsKit.singleshot(() => {
|
|
|
895
895
|
type: "boolean",
|
|
896
896
|
default: false,
|
|
897
897
|
},
|
|
898
|
+
tune: {
|
|
899
|
+
type: "boolean",
|
|
900
|
+
default: false,
|
|
901
|
+
},
|
|
902
|
+
split: {
|
|
903
|
+
type: "string",
|
|
904
|
+
default: "",
|
|
905
|
+
},
|
|
898
906
|
commit: {
|
|
899
907
|
type: "string",
|
|
900
908
|
default: "",
|
|
@@ -2727,8 +2735,11 @@ class TelegramLogicService {
|
|
|
2727
2735
|
const unSignalNotify = BacktestKit.listenSignalNotify(async (event) => {
|
|
2728
2736
|
await this.notifySignalInfo(event);
|
|
2729
2737
|
});
|
|
2738
|
+
const unGc = functoolsKit.Source.fromInterval(90000).connect(() => {
|
|
2739
|
+
this.notifyOrderContinue.gc();
|
|
2740
|
+
});
|
|
2730
2741
|
const unConnect = () => this.connect.clear();
|
|
2731
|
-
const unListen = functoolsKit.compose(() => unRisk(), () => unSignal(), () => unCommit(), () => unOrderFill(), () => unOrderReject(), () => unOrderContinue(), () => unOrderStop(), () => this.notifyOrderContinue.clear(), () => unSignalNotify(), () => unConnect());
|
|
2742
|
+
const unListen = functoolsKit.compose(() => unRisk(), () => unSignal(), () => unCommit(), () => unOrderFill(), () => unOrderReject(), () => unOrderContinue(), () => unOrderStop(), () => this.notifyOrderContinue.clear(), () => unGc(), () => unSignalNotify(), () => unConnect());
|
|
2732
2743
|
return () => {
|
|
2733
2744
|
STOP_BOT_FN();
|
|
2734
2745
|
unListen();
|
|
@@ -3676,14 +3687,14 @@ const cli = {
|
|
|
3676
3687
|
};
|
|
3677
3688
|
init();
|
|
3678
3689
|
|
|
3679
|
-
const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "simulator", "flush", "init", "docker", "help", "version"];
|
|
3690
|
+
const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "simulator", "tune", "flush", "init", "docker", "help", "version"];
|
|
3680
3691
|
const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
3681
3692
|
const HELP_TEXT$1 = `
|
|
3682
3693
|
Example:
|
|
3683
3694
|
|
|
3684
3695
|
node ${ENTRY_PATH$1} --help
|
|
3685
3696
|
`.trimStart();
|
|
3686
|
-
const main$
|
|
3697
|
+
const main$k = async () => {
|
|
3687
3698
|
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)))) {
|
|
3688
3699
|
return;
|
|
3689
3700
|
}
|
|
@@ -3691,14 +3702,14 @@ const main$i = async () => {
|
|
|
3691
3702
|
if (MODES.some((mode) => values[mode])) {
|
|
3692
3703
|
return;
|
|
3693
3704
|
}
|
|
3694
|
-
process.stdout.write(`@backtest-kit/cli ${"16.
|
|
3705
|
+
process.stdout.write(`@backtest-kit/cli ${"16.6.0"}\n`);
|
|
3695
3706
|
process.stdout.write("\n");
|
|
3696
3707
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
3697
3708
|
process.stdout.write("\n");
|
|
3698
3709
|
process.stdout.write(HELP_TEXT$1);
|
|
3699
3710
|
process.exit(0);
|
|
3700
3711
|
};
|
|
3701
|
-
main$
|
|
3712
|
+
main$k();
|
|
3702
3713
|
|
|
3703
3714
|
const notifyShutdown = functoolsKit.singleshot(async () => {
|
|
3704
3715
|
console.log("Graceful shutdown initiated.");
|
|
@@ -3714,7 +3725,7 @@ const flush = async (entryPoint) => {
|
|
|
3714
3725
|
console.log(`Removed: ${target}`);
|
|
3715
3726
|
}
|
|
3716
3727
|
};
|
|
3717
|
-
const main$
|
|
3728
|
+
const main$j = async () => {
|
|
3718
3729
|
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)))) {
|
|
3719
3730
|
return;
|
|
3720
3731
|
}
|
|
@@ -3731,7 +3742,7 @@ const main$h = async () => {
|
|
|
3731
3742
|
}
|
|
3732
3743
|
process.exit(0);
|
|
3733
3744
|
};
|
|
3734
|
-
main$
|
|
3745
|
+
main$j();
|
|
3735
3746
|
|
|
3736
3747
|
const BEFORE_EXIT_FN$6 = functoolsKit.singleshot(async () => {
|
|
3737
3748
|
process.off("SIGINT", BEFORE_EXIT_FN$6);
|
|
@@ -3754,7 +3765,7 @@ const BEFORE_EXIT_FN$6 = functoolsKit.singleshot(async () => {
|
|
|
3754
3765
|
const listenGracefulShutdown$6 = functoolsKit.singleshot(() => {
|
|
3755
3766
|
process.on("SIGINT", BEFORE_EXIT_FN$6);
|
|
3756
3767
|
});
|
|
3757
|
-
const main$
|
|
3768
|
+
const main$i = async () => {
|
|
3758
3769
|
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)))) {
|
|
3759
3770
|
return;
|
|
3760
3771
|
}
|
|
@@ -3772,7 +3783,7 @@ const main$g = async () => {
|
|
|
3772
3783
|
await cli.backtestMainService.connect();
|
|
3773
3784
|
listenGracefulShutdown$6();
|
|
3774
3785
|
};
|
|
3775
|
-
main$
|
|
3786
|
+
main$i();
|
|
3776
3787
|
|
|
3777
3788
|
const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
|
|
3778
3789
|
process.off("SIGINT", BEFORE_EXIT_FN$5);
|
|
@@ -3791,7 +3802,7 @@ const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
|
|
|
3791
3802
|
const listenGracefulShutdown$5 = functoolsKit.singleshot(() => {
|
|
3792
3803
|
process.on("SIGINT", BEFORE_EXIT_FN$5);
|
|
3793
3804
|
});
|
|
3794
|
-
const main$
|
|
3805
|
+
const main$h = async () => {
|
|
3795
3806
|
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)))) {
|
|
3796
3807
|
return;
|
|
3797
3808
|
}
|
|
@@ -3810,7 +3821,7 @@ const main$f = async () => {
|
|
|
3810
3821
|
listenGracefulShutdown$5();
|
|
3811
3822
|
await cli.walkerMainService.connect();
|
|
3812
3823
|
};
|
|
3813
|
-
main$
|
|
3824
|
+
main$h();
|
|
3814
3825
|
|
|
3815
3826
|
const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
|
|
3816
3827
|
process.off("SIGINT", BEFORE_EXIT_FN$4);
|
|
@@ -3832,7 +3843,7 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
|
|
|
3832
3843
|
const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
|
|
3833
3844
|
process.on("SIGINT", BEFORE_EXIT_FN$4);
|
|
3834
3845
|
});
|
|
3835
|
-
const main$
|
|
3846
|
+
const main$g = async () => {
|
|
3836
3847
|
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)))) {
|
|
3837
3848
|
return;
|
|
3838
3849
|
}
|
|
@@ -3846,7 +3857,7 @@ const main$e = async () => {
|
|
|
3846
3857
|
await cli.paperMainService.connect();
|
|
3847
3858
|
listenGracefulShutdown$4();
|
|
3848
3859
|
};
|
|
3849
|
-
main$
|
|
3860
|
+
main$g();
|
|
3850
3861
|
|
|
3851
3862
|
const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
|
|
3852
3863
|
process.off("SIGINT", BEFORE_EXIT_FN$3);
|
|
@@ -3868,7 +3879,7 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
|
|
|
3868
3879
|
const listenGracefulShutdown$3 = functoolsKit.singleshot(() => {
|
|
3869
3880
|
process.on("SIGINT", BEFORE_EXIT_FN$3);
|
|
3870
3881
|
});
|
|
3871
|
-
const main$
|
|
3882
|
+
const main$f = async () => {
|
|
3872
3883
|
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)))) {
|
|
3873
3884
|
return;
|
|
3874
3885
|
}
|
|
@@ -3882,7 +3893,7 @@ const main$d = async () => {
|
|
|
3882
3893
|
await cli.liveMainService.connect();
|
|
3883
3894
|
listenGracefulShutdown$3();
|
|
3884
3895
|
};
|
|
3885
|
-
main$
|
|
3896
|
+
main$f();
|
|
3886
3897
|
|
|
3887
3898
|
const stopBacktestList$1 = async () => {
|
|
3888
3899
|
for (const item of await BacktestKit.Backtest.list()) {
|
|
@@ -3946,7 +3957,7 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
|
|
|
3946
3957
|
const listenGracefulShutdown$2 = functoolsKit.singleshot(() => {
|
|
3947
3958
|
process.on("SIGINT", BEFORE_EXIT_FN$2);
|
|
3948
3959
|
});
|
|
3949
|
-
const main$
|
|
3960
|
+
const main$e = async () => {
|
|
3950
3961
|
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)))) {
|
|
3951
3962
|
return;
|
|
3952
3963
|
}
|
|
@@ -4002,7 +4013,7 @@ const main$c = async () => {
|
|
|
4002
4013
|
listenGracefulShutdown$2();
|
|
4003
4014
|
await cli.resolveService.attachEntry(path.resolve(cwd, entryPoint));
|
|
4004
4015
|
};
|
|
4005
|
-
main$
|
|
4016
|
+
main$e();
|
|
4006
4017
|
|
|
4007
4018
|
const MODE_MODULE = {
|
|
4008
4019
|
backtest: "backtest.module",
|
|
@@ -4105,7 +4116,7 @@ const createGracefulShutdown = (mode) => {
|
|
|
4105
4116
|
process.on("SIGINT", handler);
|
|
4106
4117
|
});
|
|
4107
4118
|
};
|
|
4108
|
-
const main$
|
|
4119
|
+
const main$d = async () => {
|
|
4109
4120
|
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)))) {
|
|
4110
4121
|
return;
|
|
4111
4122
|
}
|
|
@@ -4181,7 +4192,7 @@ const main$b = async () => {
|
|
|
4181
4192
|
}
|
|
4182
4193
|
await getEntrySubject().next(absolutePath);
|
|
4183
4194
|
};
|
|
4184
|
-
main$
|
|
4195
|
+
main$d();
|
|
4185
4196
|
|
|
4186
4197
|
const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
|
|
4187
4198
|
process.off("SIGINT", BEFORE_EXIT_FN$1);
|
|
@@ -4192,7 +4203,7 @@ const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
|
|
|
4192
4203
|
const listenGracefulShutdown$1 = functoolsKit.singleshot(() => {
|
|
4193
4204
|
process.on("SIGINT", BEFORE_EXIT_FN$1);
|
|
4194
4205
|
});
|
|
4195
|
-
const main$
|
|
4206
|
+
const main$c = async () => {
|
|
4196
4207
|
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)))) {
|
|
4197
4208
|
return;
|
|
4198
4209
|
}
|
|
@@ -4202,7 +4213,7 @@ const main$a = async () => {
|
|
|
4202
4213
|
}
|
|
4203
4214
|
listenGracefulShutdown$1();
|
|
4204
4215
|
};
|
|
4205
|
-
main$
|
|
4216
|
+
main$c();
|
|
4206
4217
|
|
|
4207
4218
|
const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
|
|
4208
4219
|
process.off("SIGINT", BEFORE_EXIT_FN);
|
|
@@ -4213,7 +4224,7 @@ const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
|
|
|
4213
4224
|
const listenGracefulShutdown = functoolsKit.singleshot(() => {
|
|
4214
4225
|
process.on("SIGINT", BEFORE_EXIT_FN);
|
|
4215
4226
|
});
|
|
4216
|
-
const main$
|
|
4227
|
+
const main$b = async () => {
|
|
4217
4228
|
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)))) {
|
|
4218
4229
|
return;
|
|
4219
4230
|
}
|
|
@@ -4223,7 +4234,7 @@ const main$9 = async () => {
|
|
|
4223
4234
|
}
|
|
4224
4235
|
listenGracefulShutdown();
|
|
4225
4236
|
};
|
|
4226
|
-
main$
|
|
4237
|
+
main$b();
|
|
4227
4238
|
|
|
4228
4239
|
const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
4229
4240
|
const keys = Object.keys(schema);
|
|
@@ -4245,7 +4256,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
|
4245
4256
|
}
|
|
4246
4257
|
return rows;
|
|
4247
4258
|
};
|
|
4248
|
-
const main$
|
|
4259
|
+
const main$a = async () => {
|
|
4249
4260
|
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)))) {
|
|
4250
4261
|
return;
|
|
4251
4262
|
}
|
|
@@ -4345,9 +4356,9 @@ const main$8 = async () => {
|
|
|
4345
4356
|
console.log(await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema));
|
|
4346
4357
|
process.exit(0);
|
|
4347
4358
|
};
|
|
4348
|
-
main$
|
|
4359
|
+
main$a();
|
|
4349
4360
|
|
|
4350
|
-
const main$
|
|
4361
|
+
const main$9 = async () => {
|
|
4351
4362
|
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)))) {
|
|
4352
4363
|
return;
|
|
4353
4364
|
}
|
|
@@ -4410,9 +4421,9 @@ const main$7 = async () => {
|
|
|
4410
4421
|
};
|
|
4411
4422
|
process.on("SIGINT", beforeExit);
|
|
4412
4423
|
};
|
|
4413
|
-
main$
|
|
4424
|
+
main$9();
|
|
4414
4425
|
|
|
4415
|
-
const main$
|
|
4426
|
+
const main$8 = async () => {
|
|
4416
4427
|
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)))) {
|
|
4417
4428
|
return;
|
|
4418
4429
|
}
|
|
@@ -4493,9 +4504,9 @@ const main$6 = async () => {
|
|
|
4493
4504
|
console.log(JSON.stringify(candles, null, 2));
|
|
4494
4505
|
process.exit(0);
|
|
4495
4506
|
};
|
|
4496
|
-
main$
|
|
4507
|
+
main$8();
|
|
4497
4508
|
|
|
4498
|
-
const main$
|
|
4509
|
+
const main$7 = async () => {
|
|
4499
4510
|
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)))) {
|
|
4500
4511
|
return;
|
|
4501
4512
|
}
|
|
@@ -4609,11 +4620,314 @@ const main$5 = async () => {
|
|
|
4609
4620
|
}
|
|
4610
4621
|
process.exit(0);
|
|
4611
4622
|
};
|
|
4612
|
-
main$
|
|
4623
|
+
main$7();
|
|
4613
4624
|
|
|
4614
|
-
const
|
|
4615
|
-
|
|
4616
|
-
|
|
4625
|
+
const getBroker = functoolsKit.singleshot(() => {
|
|
4626
|
+
// Reaches into BrokerAdapter internals on purpose: the public commit*
|
|
4627
|
+
// methods skip when payload.backtest=true and require enable(), while
|
|
4628
|
+
// brokerdebug must fire exactly one commit at the raw adapter proxy.
|
|
4629
|
+
const broker = BacktestKit.Broker["getInstance"]();
|
|
4630
|
+
if (!broker) {
|
|
4631
|
+
throw new Error("Broker instance is not initialized.");
|
|
4632
|
+
}
|
|
4633
|
+
return broker;
|
|
4634
|
+
});
|
|
4635
|
+
/** Called when a new signal is opened (position entry confirmed). */
|
|
4636
|
+
const commitOrderOpen = functoolsKit.trycatch(async (payload) => {
|
|
4637
|
+
const broker = getBroker();
|
|
4638
|
+
return await broker.onOrderOpenCommit(payload);
|
|
4639
|
+
}, {
|
|
4640
|
+
fallback: (error) => {
|
|
4641
|
+
console.log(error);
|
|
4642
|
+
process.exit(-1);
|
|
4643
|
+
},
|
|
4644
|
+
});
|
|
4645
|
+
/** Called when a new signal is closed (take-profit, stop-loss, or manual close). */
|
|
4646
|
+
const commitOrderClose = functoolsKit.trycatch(async (payload) => {
|
|
4647
|
+
const broker = getBroker();
|
|
4648
|
+
return await broker.onOrderCloseCommit(payload);
|
|
4649
|
+
}, {
|
|
4650
|
+
fallback: (error) => {
|
|
4651
|
+
console.log(error);
|
|
4652
|
+
process.exit(-1);
|
|
4653
|
+
},
|
|
4654
|
+
});
|
|
4655
|
+
/** Called when a partial profit close is committed. */
|
|
4656
|
+
const commitPartialProfit = functoolsKit.trycatch(async (payload) => {
|
|
4657
|
+
const broker = getBroker();
|
|
4658
|
+
return await broker.onPartialProfitCommit(payload);
|
|
4659
|
+
}, {
|
|
4660
|
+
fallback: (error) => {
|
|
4661
|
+
console.log(error);
|
|
4662
|
+
process.exit(-1);
|
|
4663
|
+
},
|
|
4664
|
+
});
|
|
4665
|
+
/** Called when a partial loss close is committed. */
|
|
4666
|
+
const commitPartialLoss = functoolsKit.trycatch(async (payload) => {
|
|
4667
|
+
const broker = getBroker();
|
|
4668
|
+
return await broker.onPartialLossCommit(payload);
|
|
4669
|
+
}, {
|
|
4670
|
+
fallback: (error) => {
|
|
4671
|
+
console.log(error);
|
|
4672
|
+
process.exit(-1);
|
|
4673
|
+
},
|
|
4674
|
+
});
|
|
4675
|
+
/** Called when a trailing stop update is committed. */
|
|
4676
|
+
const commitTrailingStop = functoolsKit.trycatch(async (payload) => {
|
|
4677
|
+
const broker = getBroker();
|
|
4678
|
+
return await broker.onTrailingStopCommit(payload);
|
|
4679
|
+
}, {
|
|
4680
|
+
fallback: (error) => {
|
|
4681
|
+
console.log(error);
|
|
4682
|
+
process.exit(-1);
|
|
4683
|
+
},
|
|
4684
|
+
});
|
|
4685
|
+
/** Called when a trailing take-profit update is committed. */
|
|
4686
|
+
const commitTrailingTake = functoolsKit.trycatch(async (payload) => {
|
|
4687
|
+
const broker = getBroker();
|
|
4688
|
+
return await broker.onTrailingTakeCommit(payload);
|
|
4689
|
+
}, {
|
|
4690
|
+
fallback: (error) => {
|
|
4691
|
+
console.log(error);
|
|
4692
|
+
process.exit(-1);
|
|
4693
|
+
},
|
|
4694
|
+
});
|
|
4695
|
+
/** Called when a breakeven stop is committed (stop loss moved to entry price). */
|
|
4696
|
+
const commitBreakeven = functoolsKit.trycatch(async (payload) => {
|
|
4697
|
+
const broker = getBroker();
|
|
4698
|
+
return await broker.onBreakevenCommit(payload);
|
|
4699
|
+
}, {
|
|
4700
|
+
fallback: (error) => {
|
|
4701
|
+
console.log(error);
|
|
4702
|
+
process.exit(-1);
|
|
4703
|
+
},
|
|
4704
|
+
});
|
|
4705
|
+
/** Called when a DCA (average-buy) entry is committed. */
|
|
4706
|
+
const commitAverageBuy = functoolsKit.trycatch(async (payload) => {
|
|
4707
|
+
const broker = getBroker();
|
|
4708
|
+
return await broker.onAverageBuyCommit(payload);
|
|
4709
|
+
}, {
|
|
4710
|
+
fallback: (error) => {
|
|
4711
|
+
console.log(error);
|
|
4712
|
+
process.exit(-1);
|
|
4713
|
+
},
|
|
4714
|
+
});
|
|
4715
|
+
const COMMITS = [
|
|
4716
|
+
"signal-open",
|
|
4717
|
+
"signal-close",
|
|
4718
|
+
"partial-profit",
|
|
4719
|
+
"partial-loss",
|
|
4720
|
+
"average-buy",
|
|
4721
|
+
"trailing-stop",
|
|
4722
|
+
"trailing-take",
|
|
4723
|
+
"breakeven",
|
|
4724
|
+
];
|
|
4725
|
+
const ZERO_PNL = {
|
|
4726
|
+
pnlPercentage: 0,
|
|
4727
|
+
priceOpen: 0,
|
|
4728
|
+
priceClose: 0,
|
|
4729
|
+
pnlCost: 0,
|
|
4730
|
+
pnlEntries: 0,
|
|
4731
|
+
};
|
|
4732
|
+
const main$6 = async () => {
|
|
4733
|
+
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)))) {
|
|
4734
|
+
return;
|
|
4735
|
+
}
|
|
4736
|
+
const { values } = getArgs();
|
|
4737
|
+
if (!values.brokerdebug) {
|
|
4738
|
+
return;
|
|
4739
|
+
}
|
|
4740
|
+
{
|
|
4741
|
+
const cwd = process.cwd();
|
|
4742
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
4743
|
+
}
|
|
4744
|
+
await cli.configConnectionService.loadConfig("setup.config");
|
|
4745
|
+
{
|
|
4746
|
+
const loader = await cli.configConnectionService.loadConfig("loader.config");
|
|
4747
|
+
try {
|
|
4748
|
+
if (typeof loader === "function") {
|
|
4749
|
+
await loader();
|
|
4750
|
+
}
|
|
4751
|
+
if (typeof loader?.loader === "function") {
|
|
4752
|
+
await loader.loader();
|
|
4753
|
+
}
|
|
4754
|
+
}
|
|
4755
|
+
catch (error) {
|
|
4756
|
+
console.error("Module loader failed", error);
|
|
4757
|
+
process.exit(-1);
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4760
|
+
await cli.moduleConnectionService.loadModule("brokerdebug.module");
|
|
4761
|
+
const [defaultExchangeName = null] = await BacktestKit.listExchangeSchema();
|
|
4762
|
+
const exchangeName = values.exchange || defaultExchangeName?.exchangeName;
|
|
4763
|
+
const symbol = values.symbol || "BTCUSDT";
|
|
4764
|
+
const commit = values.commit || "signal-open";
|
|
4765
|
+
if (!COMMITS.includes(commit)) {
|
|
4766
|
+
console.error(`Unknown --commit value: ${commit}`);
|
|
4767
|
+
console.error(`Available: ${COMMITS.join(", ")}`);
|
|
4768
|
+
process.exit(1);
|
|
4769
|
+
}
|
|
4770
|
+
const candles = await BacktestKit.Exchange.getCandles(symbol, "1m", 5, { exchangeName });
|
|
4771
|
+
const lastCandle = candles[candles.length - 1];
|
|
4772
|
+
const currentPrice = lastCandle.close;
|
|
4773
|
+
const priceTakeProfit = currentPrice * 1.02;
|
|
4774
|
+
const priceStopLoss = currentPrice * 0.98;
|
|
4775
|
+
const context = {
|
|
4776
|
+
strategyName: "broker-test",
|
|
4777
|
+
exchangeName: exchangeName,
|
|
4778
|
+
};
|
|
4779
|
+
const signalId = `broker-debug-${Date.now()}`;
|
|
4780
|
+
const when = BacktestKit.alignToInterval(new Date(), "1m");
|
|
4781
|
+
{
|
|
4782
|
+
console.log(`symbol=${symbol} price=${currentPrice} commit=${commit}`);
|
|
4783
|
+
console.time(commit);
|
|
4784
|
+
}
|
|
4785
|
+
const run = async () => {
|
|
4786
|
+
if (commit === "signal-open") {
|
|
4787
|
+
await commitOrderOpen({
|
|
4788
|
+
type: "active",
|
|
4789
|
+
symbol,
|
|
4790
|
+
signalId,
|
|
4791
|
+
cost: 100,
|
|
4792
|
+
position: "long",
|
|
4793
|
+
priceOpen: currentPrice,
|
|
4794
|
+
priceTakeProfit,
|
|
4795
|
+
priceStopLoss,
|
|
4796
|
+
pnl: ZERO_PNL,
|
|
4797
|
+
peakProfit: ZERO_PNL,
|
|
4798
|
+
maxDrawdown: ZERO_PNL,
|
|
4799
|
+
attempt: 0,
|
|
4800
|
+
context,
|
|
4801
|
+
when,
|
|
4802
|
+
backtest: true,
|
|
4803
|
+
});
|
|
4804
|
+
}
|
|
4805
|
+
if (commit === "signal-close") {
|
|
4806
|
+
await commitOrderClose({
|
|
4807
|
+
symbol,
|
|
4808
|
+
signalId,
|
|
4809
|
+
cost: 100,
|
|
4810
|
+
position: "long",
|
|
4811
|
+
currentPrice,
|
|
4812
|
+
priceOpen: currentPrice,
|
|
4813
|
+
priceTakeProfit,
|
|
4814
|
+
priceStopLoss,
|
|
4815
|
+
totalEntries: 1,
|
|
4816
|
+
totalPartials: 0,
|
|
4817
|
+
attempt: 0,
|
|
4818
|
+
pnl: ZERO_PNL,
|
|
4819
|
+
peakProfit: ZERO_PNL,
|
|
4820
|
+
maxDrawdown: ZERO_PNL,
|
|
4821
|
+
context,
|
|
4822
|
+
when,
|
|
4823
|
+
backtest: true,
|
|
4824
|
+
});
|
|
4825
|
+
}
|
|
4826
|
+
if (commit === "partial-profit") {
|
|
4827
|
+
await commitPartialProfit({
|
|
4828
|
+
symbol,
|
|
4829
|
+
signalId,
|
|
4830
|
+
percentToClose: 50,
|
|
4831
|
+
cost: 50,
|
|
4832
|
+
currentPrice,
|
|
4833
|
+
position: "long",
|
|
4834
|
+
priceTakeProfit,
|
|
4835
|
+
priceStopLoss,
|
|
4836
|
+
context,
|
|
4837
|
+
when,
|
|
4838
|
+
backtest: true,
|
|
4839
|
+
});
|
|
4840
|
+
}
|
|
4841
|
+
if (commit === "partial-loss") {
|
|
4842
|
+
await commitPartialLoss({
|
|
4843
|
+
symbol,
|
|
4844
|
+
signalId,
|
|
4845
|
+
percentToClose: 50,
|
|
4846
|
+
cost: 50,
|
|
4847
|
+
currentPrice,
|
|
4848
|
+
position: "long",
|
|
4849
|
+
priceTakeProfit,
|
|
4850
|
+
priceStopLoss,
|
|
4851
|
+
context,
|
|
4852
|
+
when,
|
|
4853
|
+
backtest: true,
|
|
4854
|
+
});
|
|
4855
|
+
}
|
|
4856
|
+
if (commit === "average-buy") {
|
|
4857
|
+
await commitAverageBuy({
|
|
4858
|
+
symbol,
|
|
4859
|
+
signalId,
|
|
4860
|
+
currentPrice,
|
|
4861
|
+
cost: 100,
|
|
4862
|
+
position: "long",
|
|
4863
|
+
priceTakeProfit,
|
|
4864
|
+
priceStopLoss,
|
|
4865
|
+
context,
|
|
4866
|
+
when,
|
|
4867
|
+
backtest: true,
|
|
4868
|
+
});
|
|
4869
|
+
}
|
|
4870
|
+
if (commit === "trailing-stop") {
|
|
4871
|
+
await commitTrailingStop({
|
|
4872
|
+
symbol,
|
|
4873
|
+
signalId,
|
|
4874
|
+
percentShift: -50,
|
|
4875
|
+
currentPrice,
|
|
4876
|
+
newStopLossPrice: priceStopLoss + (currentPrice - priceStopLoss) * 0.5,
|
|
4877
|
+
takeProfitPrice: priceTakeProfit,
|
|
4878
|
+
position: "long",
|
|
4879
|
+
context,
|
|
4880
|
+
when,
|
|
4881
|
+
backtest: true,
|
|
4882
|
+
});
|
|
4883
|
+
}
|
|
4884
|
+
if (commit === "trailing-take") {
|
|
4885
|
+
await commitTrailingTake({
|
|
4886
|
+
symbol,
|
|
4887
|
+
signalId,
|
|
4888
|
+
percentShift: 50,
|
|
4889
|
+
currentPrice,
|
|
4890
|
+
newTakeProfitPrice: priceTakeProfit + (priceTakeProfit - currentPrice) * 0.5,
|
|
4891
|
+
takeProfitPrice: priceTakeProfit,
|
|
4892
|
+
position: "long",
|
|
4893
|
+
context,
|
|
4894
|
+
when,
|
|
4895
|
+
backtest: true,
|
|
4896
|
+
});
|
|
4897
|
+
}
|
|
4898
|
+
if (commit === "breakeven") {
|
|
4899
|
+
await commitBreakeven({
|
|
4900
|
+
symbol,
|
|
4901
|
+
signalId,
|
|
4902
|
+
currentPrice,
|
|
4903
|
+
newStopLossPrice: currentPrice,
|
|
4904
|
+
newTakeProfitPrice: priceTakeProfit,
|
|
4905
|
+
position: "long",
|
|
4906
|
+
context,
|
|
4907
|
+
when,
|
|
4908
|
+
backtest: true,
|
|
4909
|
+
});
|
|
4910
|
+
}
|
|
4911
|
+
};
|
|
4912
|
+
await BacktestKit.runInMockContext(run, {
|
|
4913
|
+
when,
|
|
4914
|
+
strategyName: context.strategyName,
|
|
4915
|
+
exchangeName: context.exchangeName,
|
|
4916
|
+
frameName: "",
|
|
4917
|
+
symbol,
|
|
4918
|
+
backtest: true,
|
|
4919
|
+
});
|
|
4920
|
+
{
|
|
4921
|
+
console.log(`${commit} OK`);
|
|
4922
|
+
console.timeEnd(commit);
|
|
4923
|
+
}
|
|
4924
|
+
process.exit(0);
|
|
4925
|
+
};
|
|
4926
|
+
main$6();
|
|
4927
|
+
|
|
4928
|
+
const SIMULATOR_NAME$1 = "cli_simulator";
|
|
4929
|
+
const IDEA_DIRECTIONS$1 = ["LONG", "SHORT", "NEUTRAL"];
|
|
4930
|
+
const validateIdea$1 = (idea, line) => {
|
|
4617
4931
|
if (typeof idea !== "object" || idea === null) {
|
|
4618
4932
|
return `line ${line}: not an object`;
|
|
4619
4933
|
}
|
|
@@ -4626,15 +4940,16 @@ const validateIdea = (idea, line) => {
|
|
|
4626
4940
|
if (typeof idea.symbol !== "string" || !idea.symbol) {
|
|
4627
4941
|
return `line ${line}: "symbol" must be a non-empty string`;
|
|
4628
4942
|
}
|
|
4629
|
-
if (!IDEA_DIRECTIONS.includes(idea.direction)) {
|
|
4630
|
-
return `line ${line}: "direction" must be one of ${IDEA_DIRECTIONS.join("|")}, got ${JSON.stringify(idea.direction)}`;
|
|
4943
|
+
if (!IDEA_DIRECTIONS$1.includes(idea.direction)) {
|
|
4944
|
+
return `line ${line}: "direction" must be one of ${IDEA_DIRECTIONS$1.join("|")}, got ${JSON.stringify(idea.direction)}`;
|
|
4631
4945
|
}
|
|
4632
4946
|
if (typeof idea.author !== "string" || !idea.author) {
|
|
4633
4947
|
return `line ${line}: "author" must be a non-empty string`;
|
|
4634
4948
|
}
|
|
4635
4949
|
return null;
|
|
4636
4950
|
};
|
|
4637
|
-
const toMarkdown = (result) => {
|
|
4951
|
+
const toMarkdown$1 = (result) => {
|
|
4952
|
+
const profitable = result.reports.filter(({ totalPnlPercent }) => totalPnlPercent > 0).length;
|
|
4638
4953
|
const lines = [];
|
|
4639
4954
|
lines.push(`# Simulator Report — ${result.symbol}`);
|
|
4640
4955
|
lines.push("");
|
|
@@ -4643,33 +4958,36 @@ const toMarkdown = (result) => {
|
|
|
4643
4958
|
lines.push(`| Ideas (total / directional) | ${result.ideasTotal} / ${result.ideasDirectional} |`);
|
|
4644
4959
|
lines.push(`| Profiles (truncated) | ${result.profileCount} (${result.truncatedCount}) |`);
|
|
4645
4960
|
lines.push(`| Authors allowed / banned | ${result.allowedAuthors.length} / ${result.bannedAuthors.length} |`);
|
|
4646
|
-
lines.push(`|
|
|
4961
|
+
lines.push(`| Profitable corridor | ${profitable} / ${result.reports.length} grid points |`);
|
|
4647
4962
|
lines.push(`| Hold minutes avg / p95 / p99 | ${result.avgHoldMinutes.toFixed(0)} / ${result.p95HoldMinutes} / ${result.p99HoldMinutes} |`);
|
|
4648
4963
|
lines.push("");
|
|
4649
4964
|
lines.push(`## Ranking winners`);
|
|
4650
4965
|
lines.push("");
|
|
4651
|
-
lines.push(`| Criterion | Stop% |
|
|
4652
|
-
lines.push(`| --- | --- | --- | --- | --- | --- | --- | --- | --- | ---
|
|
4966
|
+
lines.push(`| Criterion | Stop% | Hold | Track | HitRate | Trades | PNL% | WinRate | Sharpe | Sortino |`);
|
|
4967
|
+
lines.push(`| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |`);
|
|
4653
4968
|
for (const best of result.best) {
|
|
4654
4969
|
if (!best.report) {
|
|
4655
|
-
lines.push(`| ${best.criterion} | — | — | — | — | — | — | — | — | —
|
|
4970
|
+
lines.push(`| ${best.criterion} | — | — | — | — | — | — | — | — | — |`);
|
|
4656
4971
|
continue;
|
|
4657
4972
|
}
|
|
4658
4973
|
const { point } = best.report;
|
|
4659
|
-
lines.push(`| ${best.criterion} | ${point.hardStopPercent} | ${point.
|
|
4974
|
+
lines.push(`| ${best.criterion} | ${point.hardStopPercent} | ${point.holdMinutes / 60}h | ${point.minAuthorTrack} | ${point.minAuthorHitRate} | ` +
|
|
4660
4975
|
`${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
4976
|
}
|
|
4662
4977
|
lines.push("");
|
|
4663
|
-
lines.push(`## Allowed authors (
|
|
4978
|
+
lines.push(`## Allowed authors (whitelist of the sharpe winner rule)`);
|
|
4664
4979
|
lines.push("");
|
|
4665
4980
|
lines.push(`| Author | Ideas | Hits | HitRate |`);
|
|
4666
4981
|
lines.push(`| --- | --- | --- | --- |`);
|
|
4667
|
-
|
|
4982
|
+
// артефакт авторов живёт по-победительно в best[] — ран-левел
|
|
4983
|
+
// authorStats в ISimulatorResult больше нет
|
|
4984
|
+
const sharpeBest = result.best.find(({ criterion }) => criterion === "sharpe");
|
|
4985
|
+
for (const stat of (sharpeBest?.authorStats ?? []).filter(({ banned }) => !banned)) {
|
|
4668
4986
|
lines.push(`| ${stat.author} | ${stat.ideas} | ${stat.hits} | ${(stat.hitRate * 100).toFixed(0)}% |`);
|
|
4669
4987
|
}
|
|
4670
4988
|
return lines.join("\n");
|
|
4671
4989
|
};
|
|
4672
|
-
const main$
|
|
4990
|
+
const main$5 = async () => {
|
|
4673
4991
|
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
4992
|
return;
|
|
4675
4993
|
}
|
|
@@ -4706,7 +5024,7 @@ const main$4 = async () => {
|
|
|
4706
5024
|
console.error(`Error: invalid JSON in ideas file, line ${i + 1}`);
|
|
4707
5025
|
process.exit(1);
|
|
4708
5026
|
}
|
|
4709
|
-
const problem = validateIdea(idea, i + 1);
|
|
5027
|
+
const problem = validateIdea$1(idea, i + 1);
|
|
4710
5028
|
if (problem) {
|
|
4711
5029
|
console.error(`Error: ideas file does not match the idea structure — ${problem}`);
|
|
4712
5030
|
console.error(`Expected shape: { "id": number, "ts": number, "symbol": string, "direction": "LONG"|"SHORT"|"NEUTRAL", "author": string }`);
|
|
@@ -4742,20 +5060,42 @@ const main$4 = async () => {
|
|
|
4742
5060
|
}
|
|
4743
5061
|
const [defaultExchangeName = null] = await BacktestKit.listExchangeSchema();
|
|
4744
5062
|
const exchangeName = values.exchange || defaultExchangeName?.exchangeName;
|
|
5063
|
+
if (values.verbose) {
|
|
5064
|
+
BacktestKit.overrideExchangeSchema({
|
|
5065
|
+
exchangeName,
|
|
5066
|
+
callbacks: {
|
|
5067
|
+
onCandleData(symbol, interval, since) {
|
|
5068
|
+
console.log(`Received candle data for symbol: ${symbol}, interval: ${interval}, since: ${since.toUTCString()}`);
|
|
5069
|
+
},
|
|
5070
|
+
},
|
|
5071
|
+
});
|
|
5072
|
+
}
|
|
4745
5073
|
const symbol = values.symbol || "BTCUSDT";
|
|
5074
|
+
// Проба осуществимости, НЕ поиск заработка (подбор параметров — у
|
|
5075
|
+
// --tune): собирающая прибыль механика выключена, остаётся вопрос
|
|
5076
|
+
// "есть ли прибыльный коридор" по стопу x холду x правилу бана —
|
|
5077
|
+
// 48 точек, быстрый вердикт
|
|
4746
5078
|
BacktestKit.addSimulatorSchema({
|
|
4747
|
-
simulatorName: SIMULATOR_NAME,
|
|
5079
|
+
simulatorName: SIMULATOR_NAME$1,
|
|
4748
5080
|
exchangeName,
|
|
4749
5081
|
gridAxes: {
|
|
4750
|
-
|
|
4751
|
-
|
|
5082
|
+
// грубая шкала катастрофы: коридор должен быть широким, не точкой
|
|
5083
|
+
hardStopPercent: [2, 3, 5, 7],
|
|
5084
|
+
// инертен: проба не собирает прибыль, выход — по времени или стопу
|
|
5085
|
+
trailingTakePercent: [100],
|
|
4752
5086
|
holdMinutes: [24 * 60, 2 * 24 * 60, 3 * 24 * 60],
|
|
4753
|
-
|
|
4754
|
-
|
|
5087
|
+
// одного проверенного автора достаточно — консенсус не перебираем
|
|
5088
|
+
minIdeasAligned: [1],
|
|
5089
|
+
// правило бана — единственная перебираемая "умность" пробы
|
|
5090
|
+
minAuthorTrack: [3, 5],
|
|
4755
5091
|
minAuthorHitRate: [0.5, 0.6],
|
|
4756
|
-
|
|
4757
|
-
|
|
5092
|
+
minAuthorWilson: [0],
|
|
5093
|
+
minWeightAligned: [0],
|
|
5094
|
+
profitLockPercent: [0],
|
|
5095
|
+
authorMetric: ["close"],
|
|
5096
|
+
banCriteria: ["sharpe", "pnl"],
|
|
4758
5097
|
},
|
|
5098
|
+
reportOrder: "sharpe",
|
|
4759
5099
|
callbacks: {
|
|
4760
5100
|
onProgress: (symbol, stage, processed, total) => {
|
|
4761
5101
|
if (values.verbose) {
|
|
@@ -4817,7 +5157,7 @@ const main$4 = async () => {
|
|
|
4817
5157
|
});
|
|
4818
5158
|
const result = await BacktestKit.Simulator.run({
|
|
4819
5159
|
symbol,
|
|
4820
|
-
simulatorName: SIMULATOR_NAME,
|
|
5160
|
+
simulatorName: SIMULATOR_NAME$1,
|
|
4821
5161
|
ideas,
|
|
4822
5162
|
});
|
|
4823
5163
|
const dumpName = values.output || `simulator_${symbol}_${Date.now()}`;
|
|
@@ -4832,11 +5172,299 @@ const main$4 = async () => {
|
|
|
4832
5172
|
if (values.markdown) {
|
|
4833
5173
|
const filePath = path.resolve(dumpDir, `${dumpName}.md`);
|
|
4834
5174
|
await fs$1.mkdir(dumpDir, { recursive: true });
|
|
4835
|
-
await fs$1.writeFile(filePath, toMarkdown(result), "utf-8");
|
|
5175
|
+
await fs$1.writeFile(filePath, toMarkdown$1(result), "utf-8");
|
|
5176
|
+
console.log(`Saved: ${filePath}`);
|
|
5177
|
+
process.exit(0);
|
|
5178
|
+
}
|
|
5179
|
+
console.log(toMarkdown$1(result));
|
|
5180
|
+
process.exit(0);
|
|
5181
|
+
};
|
|
5182
|
+
main$5();
|
|
5183
|
+
|
|
5184
|
+
const SIMULATOR_NAME = "cli_tune";
|
|
5185
|
+
/** Доля времени фида, уходящая в обучение; хвост — один OOS-выстрел. */
|
|
5186
|
+
const DEFAULT_TRAIN_SPLIT = 0.7;
|
|
5187
|
+
const IDEA_DIRECTIONS = ["LONG", "SHORT", "NEUTRAL"];
|
|
5188
|
+
const validateIdea = (idea, line) => {
|
|
5189
|
+
if (typeof idea !== "object" || idea === null) {
|
|
5190
|
+
return `line ${line}: not an object`;
|
|
5191
|
+
}
|
|
5192
|
+
if (typeof idea.id !== "number") {
|
|
5193
|
+
return `line ${line}: "id" must be a number, got ${typeof idea.id}`;
|
|
5194
|
+
}
|
|
5195
|
+
if (typeof idea.ts !== "number") {
|
|
5196
|
+
return `line ${line}: "ts" must be a number (unix ms), got ${typeof idea.ts}`;
|
|
5197
|
+
}
|
|
5198
|
+
if (typeof idea.symbol !== "string" || !idea.symbol) {
|
|
5199
|
+
return `line ${line}: "symbol" must be a non-empty string`;
|
|
5200
|
+
}
|
|
5201
|
+
if (!IDEA_DIRECTIONS.includes(idea.direction)) {
|
|
5202
|
+
return `line ${line}: "direction" must be one of ${IDEA_DIRECTIONS.join("|")}, got ${JSON.stringify(idea.direction)}`;
|
|
5203
|
+
}
|
|
5204
|
+
if (typeof idea.author !== "string" || !idea.author) {
|
|
5205
|
+
return `line ${line}: "author" must be a non-empty string`;
|
|
5206
|
+
}
|
|
5207
|
+
return null;
|
|
5208
|
+
};
|
|
5209
|
+
const pointLabel = (point) => `H=${point.hardStopPercent} TT=${point.trailingTakePercent} hold=${point.holdMinutes / 60}h N=${point.minIdeasAligned} ` +
|
|
5210
|
+
`track=${point.minAuthorTrack} rate=${point.minAuthorHitRate} wilson=${point.minAuthorWilson} W=${point.minWeightAligned} ` +
|
|
5211
|
+
`lock=${point.profitLockPercent} metric=${point.authorMetric}`;
|
|
5212
|
+
const fmtRatio = (value) => Number.isFinite(value) ? value.toFixed(2) : "inf";
|
|
5213
|
+
const toMarkdown = (train, test, trainSplit) => {
|
|
5214
|
+
const lines = [];
|
|
5215
|
+
lines.push(`# Tune Report — ${train.symbol}`);
|
|
5216
|
+
lines.push("");
|
|
5217
|
+
lines.push(`Walk-forward: train on the first ${Math.round(trainSplit * 100)}% of the feed time range, ONE out-of-sample shot of the frozen sharpe winner on the tail.`);
|
|
5218
|
+
lines.push("");
|
|
5219
|
+
lines.push(`## Train (${train.ideasDirectional} directional ideas, ${train.reports.length} grid points)`);
|
|
5220
|
+
lines.push("");
|
|
5221
|
+
lines.push(`| Criterion | Point | Trades | PNL% | WinRate | DD% | Sharpe | Sortino |`);
|
|
5222
|
+
lines.push(`| --- | --- | --- | --- | --- | --- | --- | --- |`);
|
|
5223
|
+
for (const best of train.best) {
|
|
5224
|
+
if (!best.report) {
|
|
5225
|
+
lines.push(`| ${best.criterion} | — | — | — | — | — | — | — |`);
|
|
5226
|
+
continue;
|
|
5227
|
+
}
|
|
5228
|
+
lines.push(`| ${best.criterion} | ${pointLabel(best.report.point)} | ${best.report.trades} | ${best.report.totalPnlPercent.toFixed(2)}% | ` +
|
|
5229
|
+
`${(best.report.winRate * 100).toFixed(0)}% | ${best.report.maxSeriesDrawdownPercent.toFixed(2)} | ${fmtRatio(best.report.sharpe)} | ${fmtRatio(best.report.sortino)} |`);
|
|
5230
|
+
}
|
|
5231
|
+
lines.push("");
|
|
5232
|
+
lines.push(`## Out-of-sample (frozen sharpe winner, ${test.ideasDirectional} tail ideas)`);
|
|
5233
|
+
lines.push("");
|
|
5234
|
+
lines.push(`Frozen point: ${pointLabel(test.point)}`);
|
|
5235
|
+
lines.push("");
|
|
5236
|
+
lines.push(`| Metric | Value |`);
|
|
5237
|
+
lines.push(`| --- | --- |`);
|
|
5238
|
+
lines.push(`| Trades (skipped busy) | ${test.report.trades} (${test.report.skippedBusy}) |`);
|
|
5239
|
+
lines.push(`| PNL | ${test.report.totalPnlPercent.toFixed(2)}% |`);
|
|
5240
|
+
lines.push(`| Win rate | ${(test.report.winRate * 100).toFixed(0)}% |`);
|
|
5241
|
+
lines.push(`| Profit factor | ${fmtRatio(test.report.profitFactor)} |`);
|
|
5242
|
+
lines.push(`| Max series drawdown | ${test.report.maxSeriesDrawdownPercent.toFixed(2)}% |`);
|
|
5243
|
+
lines.push(`| Sharpe / Sortino | ${fmtRatio(test.report.sharpe)} / ${fmtRatio(test.report.sortino)} |`);
|
|
5244
|
+
lines.push(`| Recovery factor | ${fmtRatio(test.report.recoveryFactor)} |`);
|
|
5245
|
+
lines.push(`| Exits | ${Object.entries(test.report.exitReasons).map(([reason, count]) => `${reason}=${count}`).join(", ")} |`);
|
|
5246
|
+
lines.push("");
|
|
5247
|
+
lines.push(`## Test trades`);
|
|
5248
|
+
lines.push("");
|
|
5249
|
+
lines.push(`| Direction | Exit | PNL% | Hold | Entry (UTC) |`);
|
|
5250
|
+
lines.push(`| --- | --- | --- | --- | --- |`);
|
|
5251
|
+
for (const trade of test.trades) {
|
|
5252
|
+
lines.push(`| ${trade.direction} | ${trade.exitReason} | ${trade.pnlPercent.toFixed(2)} | ${trade.holdMinutesActual}m | ${new Date(trade.entryTimestamp).toISOString()} |`);
|
|
5253
|
+
}
|
|
5254
|
+
lines.push("");
|
|
5255
|
+
lines.push(`## Frozen author track record (train range, sharpe winner rule)`);
|
|
5256
|
+
lines.push("");
|
|
5257
|
+
lines.push(`Freeze these raw numbers for production \`Simulator.test\` — banned flags and vote weights are re-derived from them under the frozen point's rule; an author absent from the list is banned by default.`);
|
|
5258
|
+
lines.push("");
|
|
5259
|
+
lines.push(`| Author | Ideas | Hits | Banned |`);
|
|
5260
|
+
lines.push(`| --- | --- | --- | --- |`);
|
|
5261
|
+
for (const stat of test.authorStats) {
|
|
5262
|
+
lines.push(`| ${stat.author} | ${stat.ideas} | ${stat.hits} | ${stat.banned ? "yes" : ""} |`);
|
|
5263
|
+
}
|
|
5264
|
+
return lines.join("\n");
|
|
5265
|
+
};
|
|
5266
|
+
const main$4 = async () => {
|
|
5267
|
+
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)))) {
|
|
5268
|
+
return;
|
|
5269
|
+
}
|
|
5270
|
+
const { values, positionals } = getArgs();
|
|
5271
|
+
if (!values.tune) {
|
|
5272
|
+
return;
|
|
5273
|
+
}
|
|
5274
|
+
const [ideasPath = null] = positionals.filter((value) => value.endsWith(".jsonl"));
|
|
5275
|
+
if (!ideasPath) {
|
|
5276
|
+
console.error("Error: positional path to an ideas .jsonl file is required");
|
|
5277
|
+
process.exit(1);
|
|
5278
|
+
}
|
|
5279
|
+
let ideas = [];
|
|
5280
|
+
{
|
|
5281
|
+
let content;
|
|
5282
|
+
try {
|
|
5283
|
+
content = await fs$1.readFile(path.resolve(ideasPath), "utf-8");
|
|
5284
|
+
}
|
|
5285
|
+
catch (error) {
|
|
5286
|
+
console.error(`Error: cannot read ideas file: ${ideasPath}`);
|
|
5287
|
+
process.exit(1);
|
|
5288
|
+
}
|
|
5289
|
+
const rows = content.split("\n").filter(Boolean);
|
|
5290
|
+
if (!rows.length) {
|
|
5291
|
+
console.error(`Error: ideas file is empty: ${ideasPath}`);
|
|
5292
|
+
process.exit(1);
|
|
5293
|
+
}
|
|
5294
|
+
for (let i = 0; i < rows.length; i++) {
|
|
5295
|
+
let idea;
|
|
5296
|
+
try {
|
|
5297
|
+
idea = JSON.parse(rows[i]);
|
|
5298
|
+
}
|
|
5299
|
+
catch {
|
|
5300
|
+
console.error(`Error: invalid JSON in ideas file, line ${i + 1}`);
|
|
5301
|
+
process.exit(1);
|
|
5302
|
+
}
|
|
5303
|
+
const problem = validateIdea(idea, i + 1);
|
|
5304
|
+
if (problem) {
|
|
5305
|
+
console.error(`Error: ideas file does not match the idea structure — ${problem}`);
|
|
5306
|
+
console.error(`Expected shape: { "id": number, "ts": number, "symbol": string, "direction": "LONG"|"SHORT"|"NEUTRAL", "author": string }`);
|
|
5307
|
+
process.exit(1);
|
|
5308
|
+
}
|
|
5309
|
+
ideas.push(idea);
|
|
5310
|
+
}
|
|
5311
|
+
}
|
|
5312
|
+
const trainSplit = values.split
|
|
5313
|
+
? parseFloat(values.split)
|
|
5314
|
+
: DEFAULT_TRAIN_SPLIT;
|
|
5315
|
+
if (!Number.isFinite(trainSplit) || trainSplit <= 0 || trainSplit >= 1) {
|
|
5316
|
+
console.error(`Error: --split must be a fraction between 0 and 1, got ${values.split}`);
|
|
5317
|
+
process.exit(1);
|
|
5318
|
+
}
|
|
5319
|
+
{
|
|
5320
|
+
const cwd = process.cwd();
|
|
5321
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
5322
|
+
}
|
|
5323
|
+
await cli.configConnectionService.loadConfig("setup.config");
|
|
5324
|
+
{
|
|
5325
|
+
const loader = await cli.configConnectionService.loadConfig("loader.config");
|
|
5326
|
+
try {
|
|
5327
|
+
if (typeof loader === "function") {
|
|
5328
|
+
await loader();
|
|
5329
|
+
}
|
|
5330
|
+
if (typeof loader?.loader === "function") {
|
|
5331
|
+
await loader.loader();
|
|
5332
|
+
}
|
|
5333
|
+
}
|
|
5334
|
+
catch (error) {
|
|
5335
|
+
console.error("Module loader failed", error);
|
|
5336
|
+
process.exit(-1);
|
|
5337
|
+
}
|
|
5338
|
+
}
|
|
5339
|
+
await cli.moduleConnectionService.loadModule("tune.module");
|
|
5340
|
+
{
|
|
5341
|
+
await cli.exchangeSchemaService.addSchema();
|
|
5342
|
+
await cli.symbolSchemaService.addSchema();
|
|
5343
|
+
}
|
|
5344
|
+
const [defaultExchangeName = null] = await BacktestKit.listExchangeSchema();
|
|
5345
|
+
const exchangeName = values.exchange || defaultExchangeName?.exchangeName;
|
|
5346
|
+
if (values.verbose) {
|
|
5347
|
+
BacktestKit.overrideExchangeSchema({
|
|
5348
|
+
exchangeName,
|
|
5349
|
+
callbacks: {
|
|
5350
|
+
onCandleData(symbol, interval, since) {
|
|
5351
|
+
console.log(`Received candle data for symbol: ${symbol}, interval: ${interval}, since: ${since.toUTCString()}`);
|
|
5352
|
+
},
|
|
5353
|
+
},
|
|
5354
|
+
});
|
|
5355
|
+
}
|
|
5356
|
+
const symbol = values.symbol || "BTCUSDT";
|
|
5357
|
+
// Подбор параметров: полная сетка с собирающей прибыль механикой —
|
|
5358
|
+
// замок, трейлинг, взвешенный консенсус, Вильсон, обе метрики бана.
|
|
5359
|
+
// Честность обеспечивается сплитом: обучение видит только голову,
|
|
5360
|
+
// хвост тратится на один выстрел замороженного sharpe-победителя
|
|
5361
|
+
BacktestKit.addSimulatorSchema({
|
|
5362
|
+
simulatorName: SIMULATOR_NAME,
|
|
5363
|
+
exchangeName,
|
|
5364
|
+
gridAxes: {
|
|
5365
|
+
// стопы < 2% сидят внутри медианного шейкаута и не выигрывают
|
|
5366
|
+
hardStopPercent: [2, 2.5, 3, 4, 5, 7],
|
|
5367
|
+
trailingTakePercent: [1, 1.5, 2, 3, 4],
|
|
5368
|
+
holdMinutes: [24 * 60, 2 * 24 * 60, 3 * 24 * 60],
|
|
5369
|
+
minIdeasAligned: [1, 2],
|
|
5370
|
+
minAuthorTrack: [2, 3, 5],
|
|
5371
|
+
minAuthorHitRate: [0.5, 0.6],
|
|
5372
|
+
minAuthorWilson: [0, 0.6],
|
|
5373
|
+
minWeightAligned: [0, 0.6, 1.2],
|
|
5374
|
+
profitLockPercent: [0, 1.5, 2.5],
|
|
5375
|
+
authorMetric: ["close", "reach"],
|
|
5376
|
+
banCriteria: ["sharpe"],
|
|
5377
|
+
},
|
|
5378
|
+
reportOrder: "sharpe",
|
|
5379
|
+
callbacks: {
|
|
5380
|
+
onProgress: (symbol, stage, processed, total) => {
|
|
5381
|
+
if (values.verbose) {
|
|
5382
|
+
console.log("onProgress", { symbol, stage, processed, total });
|
|
5383
|
+
}
|
|
5384
|
+
},
|
|
5385
|
+
onIdeas: (symbol, ideasTotal, ideasDirectional) => {
|
|
5386
|
+
if (values.verbose) {
|
|
5387
|
+
console.log("onIdeas", { symbol, ideasTotal, ideasDirectional });
|
|
5388
|
+
}
|
|
5389
|
+
},
|
|
5390
|
+
onProfiles: (symbol, profiles, truncatedCount) => {
|
|
5391
|
+
if (values.verbose) {
|
|
5392
|
+
console.log("onProfiles", { symbol, profiles: profiles.length, truncatedCount });
|
|
5393
|
+
}
|
|
5394
|
+
},
|
|
5395
|
+
onAuthorsTrained: (symbol, stats, bannedIdeas) => {
|
|
5396
|
+
if (values.verbose) {
|
|
5397
|
+
console.log("onAuthorsTrained", {
|
|
5398
|
+
symbol,
|
|
5399
|
+
authors: stats.length,
|
|
5400
|
+
banned: stats.filter(({ banned }) => banned).length,
|
|
5401
|
+
bannedIdeas,
|
|
5402
|
+
});
|
|
5403
|
+
}
|
|
5404
|
+
},
|
|
5405
|
+
onRanking: (symbol, criterion, _sorted, best) => {
|
|
5406
|
+
if (values.verbose) {
|
|
5407
|
+
console.log("onRanking", {
|
|
5408
|
+
symbol,
|
|
5409
|
+
criterion,
|
|
5410
|
+
point: best.report?.point ?? null,
|
|
5411
|
+
pnl: best.report ? +best.report.totalPnlPercent.toFixed(2) : null,
|
|
5412
|
+
});
|
|
5413
|
+
}
|
|
5414
|
+
},
|
|
5415
|
+
onTestDone: (symbol, result) => {
|
|
5416
|
+
if (values.verbose) {
|
|
5417
|
+
console.log("onTestDone", {
|
|
5418
|
+
symbol,
|
|
5419
|
+
trades: result.report.trades,
|
|
5420
|
+
pnl: +result.report.totalPnlPercent.toFixed(2),
|
|
5421
|
+
});
|
|
5422
|
+
}
|
|
5423
|
+
},
|
|
5424
|
+
},
|
|
5425
|
+
});
|
|
5426
|
+
// обучение видит ТОЛЬКО голову ленты; хвост — один OOS-выстрел
|
|
5427
|
+
const sorted = [...ideas].sort((a, b) => a.ts - b.ts);
|
|
5428
|
+
const cutoff = sorted[0].ts + (sorted[sorted.length - 1].ts - sorted[0].ts) * trainSplit;
|
|
5429
|
+
const trainIdeas = sorted.filter(({ ts }) => ts < cutoff);
|
|
5430
|
+
const testIdeas = sorted.filter(({ ts }) => ts >= cutoff);
|
|
5431
|
+
const train = await BacktestKit.Simulator.run({
|
|
5432
|
+
symbol,
|
|
5433
|
+
simulatorName: SIMULATOR_NAME,
|
|
5434
|
+
ideas: trainIdeas,
|
|
5435
|
+
});
|
|
5436
|
+
const sharpeBest = train.best.find(({ criterion }) => criterion === "sharpe");
|
|
5437
|
+
if (!sharpeBest?.report) {
|
|
5438
|
+
console.error("Error: training produced no sharpe winner — nothing to freeze for the out-of-sample shot");
|
|
5439
|
+
process.exit(1);
|
|
5440
|
+
}
|
|
5441
|
+
// заморозка: точка и трек-рекорд sharpe-победителя как есть — из
|
|
5442
|
+
// замороженных статов test() читает только author/ideas/hits и сам
|
|
5443
|
+
// перевыводит баны и веса под правило точки
|
|
5444
|
+
const test = await BacktestKit.Simulator.test({
|
|
5445
|
+
symbol,
|
|
5446
|
+
simulatorName: SIMULATOR_NAME,
|
|
5447
|
+
ideas: testIdeas,
|
|
5448
|
+
point: sharpeBest.report.point,
|
|
5449
|
+
authorStats: sharpeBest.authorStats,
|
|
5450
|
+
});
|
|
5451
|
+
const dumpName = values.output || `tune_${symbol}_${Date.now()}`;
|
|
5452
|
+
const dumpDir = path.join(process.cwd(), "dump");
|
|
5453
|
+
if (values.json) {
|
|
5454
|
+
const filePath = path.resolve(dumpDir, `${dumpName}.json`);
|
|
5455
|
+
await fs$1.mkdir(dumpDir, { recursive: true });
|
|
5456
|
+
await fs$1.writeFile(filePath, JSON.stringify({ trainSplit, train, test }, null, 2), "utf-8");
|
|
5457
|
+
console.log(`Saved: ${filePath}`);
|
|
5458
|
+
process.exit(0);
|
|
5459
|
+
}
|
|
5460
|
+
if (values.markdown) {
|
|
5461
|
+
const filePath = path.resolve(dumpDir, `${dumpName}.md`);
|
|
5462
|
+
await fs$1.mkdir(dumpDir, { recursive: true });
|
|
5463
|
+
await fs$1.writeFile(filePath, toMarkdown(train, test, trainSplit), "utf-8");
|
|
4836
5464
|
console.log(`Saved: ${filePath}`);
|
|
4837
5465
|
process.exit(0);
|
|
4838
5466
|
}
|
|
4839
|
-
console.log(toMarkdown(
|
|
5467
|
+
console.log(toMarkdown(train, test, trainSplit));
|
|
4840
5468
|
process.exit(0);
|
|
4841
5469
|
};
|
|
4842
5470
|
main$4();
|
|
@@ -5037,7 +5665,8 @@ Modes:
|
|
|
5037
5665
|
--dump Fetch and save raw OHLCV candles
|
|
5038
5666
|
--pnldebug Simulate PnL per minute for a given entry price and direction
|
|
5039
5667
|
--brokerdebug Fire a single broker commit against the live broker adapter
|
|
5040
|
-
--simulator <ideas.jsonl>
|
|
5668
|
+
--simulator <ideas.jsonl> Feasibility probe over crowd ideas: is there a profitable corridor at all
|
|
5669
|
+
--tune <ideas.jsonl> Walk-forward parameter search: train on the head, one frozen shot on the tail
|
|
5041
5670
|
--flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
|
|
5042
5671
|
--init Scaffold a new project in the current directory
|
|
5043
5672
|
--docker Scaffold a Docker workspace for running strategies in a container
|
|
@@ -5172,10 +5801,13 @@ Simulator flags (--simulator):
|
|
|
5172
5801
|
"author": string }. The file is validated BEFORE any work; a line that does not
|
|
5173
5802
|
match the structure aborts the run with an error.
|
|
5174
5803
|
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5804
|
+
A FEASIBILITY PROBE, not a parameter search (that is --tune): the profit-harvesting
|
|
5805
|
+
machinery is off (no profit lock, inert trailing, no weighted consensus), and a
|
|
5806
|
+
fast 48-point grid of hard stop x hold x ban rule answers one question — does the
|
|
5807
|
+
feed contain a profitable corridor at all. One 5-day candle pass per idea, flood
|
|
5808
|
+
dedupe (one idea per author per direction per 8h), default-ban author filter,
|
|
5809
|
+
time-based Sharpe/Sortino, four ranking winners. The report carries the corridor
|
|
5810
|
+
share and the whitelist of the sharpe winner's ban rule as evidence.
|
|
5179
5811
|
Ideas of other symbols are filtered out — one shared feed serves any --symbol.
|
|
5180
5812
|
|
|
5181
5813
|
No output flag → print the Markdown summary to stdout. With --verbose every
|
|
@@ -5185,6 +5817,31 @@ Simulator flags (--simulator):
|
|
|
5185
5817
|
Module file ./modules/simulator.module is loaded automatically if it exists
|
|
5186
5818
|
(register your exchange there); without it CCXT Binance is used by default.
|
|
5187
5819
|
|
|
5820
|
+
Tune flags (--tune):
|
|
5821
|
+
|
|
5822
|
+
--symbol <string> Trading pair to tune (default: BTCUSDT)
|
|
5823
|
+
--exchange <string> Exchange name (default: first registered)
|
|
5824
|
+
--split <string> Train share of the feed time range, 0..1 (default: 0.7)
|
|
5825
|
+
--output <string> Output file base name (default: tune_{SYMBOL}_{TIMESTAMP})
|
|
5826
|
+
--json Save { trainSplit, train, test } to ./dump/<output>.json
|
|
5827
|
+
--markdown Save walk-forward report to ./dump/<output>.md
|
|
5828
|
+
--verbose Log simulator lifecycle callbacks to the console
|
|
5829
|
+
|
|
5830
|
+
Positional: path to an ideas .jsonl file — same shape and validation as --simulator.
|
|
5831
|
+
|
|
5832
|
+
The PARAMETER SEARCH counterpart of the --simulator probe: the full grid with the
|
|
5833
|
+
profit-harvesting machinery on — profit lock, trailing take, weighted consensus,
|
|
5834
|
+
Wilson-bound ban, both author-hit metrics (close/reach). Honesty is structural:
|
|
5835
|
+
training sees ONLY the head of the feed (--split of its time range), then the
|
|
5836
|
+
sharpe winner is frozen — point and raw author track record — and fired exactly
|
|
5837
|
+
once on the tail via Simulator.test. Nothing is trained on the tail; authors
|
|
5838
|
+
unseen in training are banned by default. The report carries the train winners,
|
|
5839
|
+
the out-of-sample result with the trade list, and the frozen author track record
|
|
5840
|
+
to hardcode for production Simulator.test.
|
|
5841
|
+
|
|
5842
|
+
Module file ./modules/tune.module is loaded automatically if it exists
|
|
5843
|
+
(register your exchange there); without it CCXT Binance is used by default.
|
|
5844
|
+
|
|
5188
5845
|
Flush flags (--flush):
|
|
5189
5846
|
|
|
5190
5847
|
One or more positional entry points. For each entry point the following
|
|
@@ -5218,7 +5875,8 @@ Module hooks (loaded automatically by each mode):
|
|
|
5218
5875
|
modules/dump.module --dump Exchange schema for candle dumps
|
|
5219
5876
|
modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
|
|
5220
5877
|
modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
|
|
5221
|
-
modules/simulator.module --simulator Exchange schema for crowd-ideas
|
|
5878
|
+
modules/simulator.module --simulator Exchange schema for the crowd-ideas feasibility probe
|
|
5879
|
+
modules/tune.module --tune Exchange schema for the walk-forward parameter search
|
|
5222
5880
|
|
|
5223
5881
|
--flush has no associated module. It only removes dump subdirectories.
|
|
5224
5882
|
|
|
@@ -5248,7 +5906,9 @@ Examples:
|
|
|
5248
5906
|
node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
|
|
5249
5907
|
node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
|
|
5250
5908
|
node ${ENTRY_PATH} --simulator --symbol BTCUSDT ./assets/tv-ideas.normalized.jsonl
|
|
5251
|
-
node ${ENTRY_PATH} --simulator --symbol BTCUSDT --json --output
|
|
5909
|
+
node ${ENTRY_PATH} --simulator --symbol BTCUSDT --json --output jun_2026_probe ./assets/tv-ideas.normalized.jsonl
|
|
5910
|
+
node ${ENTRY_PATH} --tune --symbol BTCUSDT ./assets/tv-ideas.normalized.jsonl
|
|
5911
|
+
node ${ENTRY_PATH} --tune --symbol BTCUSDT --split 0.7 --markdown --output jun_2026_tune ./assets/tv-ideas.normalized.jsonl
|
|
5252
5912
|
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
|
|
5253
5913
|
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
|
|
5254
5914
|
node ${ENTRY_PATH} --init --output my-trading-bot
|
|
@@ -5262,7 +5922,7 @@ const main$1 = async () => {
|
|
|
5262
5922
|
if (!values.help) {
|
|
5263
5923
|
return;
|
|
5264
5924
|
}
|
|
5265
|
-
process.stdout.write(`@backtest-kit/cli ${"16.
|
|
5925
|
+
process.stdout.write(`@backtest-kit/cli ${"16.6.0"}\n\n`);
|
|
5266
5926
|
process.stdout.write(HELP_TEXT);
|
|
5267
5927
|
process.exit(0);
|
|
5268
5928
|
};
|
|
@@ -5276,7 +5936,7 @@ const main = async () => {
|
|
|
5276
5936
|
if (!values.version) {
|
|
5277
5937
|
return;
|
|
5278
5938
|
}
|
|
5279
|
-
process.stdout.write(`@backtest-kit/cli ${"16.
|
|
5939
|
+
process.stdout.write(`@backtest-kit/cli ${"16.6.0"}\n`);
|
|
5280
5940
|
process.exit(0);
|
|
5281
5941
|
};
|
|
5282
5942
|
main();
|