@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.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as BacktestKit from 'backtest-kit';
|
|
3
|
-
import { setConfig, listenExit, emitters, Notification, Cron, Recent, Storage, Markdown, Report, Dump, State, Memory, MarkdownWriter, ReportWriter, PersistSignalAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistPartialAdapter, PersistBreakevenAdapter, PersistCandleAdapter, PersistStorageAdapter, PersistNotificationAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistIntervalAdapter, PersistMemoryAdapter, PersistRecentAdapter, PersistStateAdapter, PersistSessionAdapter, PersistStrategyAdapter, Log, StorageLive, StorageBacktest, NotificationLive, NotificationBacktest, RecentLive, RecentBacktest, Broker, SessionLive, SessionBacktest, MemoryLive, MemoryBacktest, StateLive, StateBacktest, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, shutdown, listenSignal, listStrategySchema, overrideExchangeSchema, Backtest, System, Cache, Interval, alignToInterval, addWalkerSchema, overrideWalkerSchema, Walker, listenDoneWalker, Live, getCandles, checkCandles, warmCandles, listenRisk, listenStrategyCommit, listenOrderFill, listenOrderReject, listenOrderContinue, listenOrderStop, listenSignalNotify, Exchange, addSimulatorSchema, Simulator } from 'backtest-kit';
|
|
4
|
-
import { singleshot, sleep, getErrorMessage, errorData, BehaviorSubject, Subject, str, compose, createAwaiter, execpool, queued, randomString, TIMEOUT_SYMBOL, typo, retry, trycatch, ttl, memoize, isObject } from 'functools-kit';
|
|
3
|
+
import { setConfig, listenExit, emitters, Notification, Cron, Recent, Storage, Markdown, Report, Dump, State, Memory, MarkdownWriter, ReportWriter, PersistSignalAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistPartialAdapter, PersistBreakevenAdapter, PersistCandleAdapter, PersistStorageAdapter, PersistNotificationAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistIntervalAdapter, PersistMemoryAdapter, PersistRecentAdapter, PersistStateAdapter, PersistSessionAdapter, PersistStrategyAdapter, Log, StorageLive, StorageBacktest, NotificationLive, NotificationBacktest, RecentLive, RecentBacktest, Broker, SessionLive, SessionBacktest, MemoryLive, MemoryBacktest, StateLive, StateBacktest, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, shutdown, listenSignal, listStrategySchema, overrideExchangeSchema, Backtest, System, Cache, Interval, alignToInterval, addWalkerSchema, overrideWalkerSchema, Walker, listenDoneWalker, Live, getCandles, checkCandles, warmCandles, listenRisk, listenStrategyCommit, listenOrderFill, listenOrderReject, listenOrderContinue, listenOrderStop, listenSignalNotify, Exchange, runInMockContext, addSimulatorSchema, Simulator } from 'backtest-kit';
|
|
4
|
+
import { singleshot, sleep, getErrorMessage, errorData, BehaviorSubject, Subject, str, compose, createAwaiter, execpool, queued, randomString, TIMEOUT_SYMBOL, typo, retry, trycatch, ttl, Source, memoize, isObject } from 'functools-kit';
|
|
5
5
|
import { exec, spawn } from 'child_process';
|
|
6
6
|
import fs, { constants, realpathSync } from 'fs';
|
|
7
7
|
import * as stackTrace from 'stack-trace';
|
|
@@ -28,7 +28,7 @@ import Module, { createRequire } from 'module';
|
|
|
28
28
|
import * as BacktestKitGraph from '@backtest-kit/graph';
|
|
29
29
|
import * as BacktestKitOllama from '@backtest-kit/ollama';
|
|
30
30
|
import * as BacktestKitPinets from '@backtest-kit/pinets';
|
|
31
|
-
import { run as run$1, Code, toMarkdown as toMarkdown$
|
|
31
|
+
import { run as run$1, Code, toMarkdown as toMarkdown$2 } from '@backtest-kit/pinets';
|
|
32
32
|
import * as BacktestKitSignals from '@backtest-kit/signals';
|
|
33
33
|
import open from 'open';
|
|
34
34
|
|
|
@@ -208,14 +208,14 @@ const getReadySubject = singleshot(() => {
|
|
|
208
208
|
return new Subject();
|
|
209
209
|
});
|
|
210
210
|
|
|
211
|
-
const main$
|
|
211
|
+
const main$l = () => {
|
|
212
212
|
const entrySubject = getEntrySubject();
|
|
213
213
|
entrySubject.subscribe(async (path) => {
|
|
214
214
|
console.log("Running", path);
|
|
215
215
|
await getReadySubject().next();
|
|
216
216
|
});
|
|
217
217
|
};
|
|
218
|
-
main$
|
|
218
|
+
main$l();
|
|
219
219
|
|
|
220
220
|
const ERROR_HANDLER_INSTALLED = Symbol.for("error-handler-installed");
|
|
221
221
|
function dumpStackTrace(error) {
|
|
@@ -870,6 +870,14 @@ const getArgs = singleshot(() => {
|
|
|
870
870
|
type: "boolean",
|
|
871
871
|
default: false,
|
|
872
872
|
},
|
|
873
|
+
tune: {
|
|
874
|
+
type: "boolean",
|
|
875
|
+
default: false,
|
|
876
|
+
},
|
|
877
|
+
split: {
|
|
878
|
+
type: "string",
|
|
879
|
+
default: "",
|
|
880
|
+
},
|
|
873
881
|
commit: {
|
|
874
882
|
type: "string",
|
|
875
883
|
default: "",
|
|
@@ -2702,8 +2710,11 @@ class TelegramLogicService {
|
|
|
2702
2710
|
const unSignalNotify = listenSignalNotify(async (event) => {
|
|
2703
2711
|
await this.notifySignalInfo(event);
|
|
2704
2712
|
});
|
|
2713
|
+
const unGc = Source.fromInterval(90000).connect(() => {
|
|
2714
|
+
this.notifyOrderContinue.gc();
|
|
2715
|
+
});
|
|
2705
2716
|
const unConnect = () => this.connect.clear();
|
|
2706
|
-
const unListen = compose(() => unRisk(), () => unSignal(), () => unCommit(), () => unOrderFill(), () => unOrderReject(), () => unOrderContinue(), () => unOrderStop(), () => this.notifyOrderContinue.clear(), () => unSignalNotify(), () => unConnect());
|
|
2717
|
+
const unListen = compose(() => unRisk(), () => unSignal(), () => unCommit(), () => unOrderFill(), () => unOrderReject(), () => unOrderContinue(), () => unOrderStop(), () => this.notifyOrderContinue.clear(), () => unGc(), () => unSignalNotify(), () => unConnect());
|
|
2707
2718
|
return () => {
|
|
2708
2719
|
STOP_BOT_FN();
|
|
2709
2720
|
unListen();
|
|
@@ -3644,14 +3655,14 @@ const cli = {
|
|
|
3644
3655
|
};
|
|
3645
3656
|
init();
|
|
3646
3657
|
|
|
3647
|
-
const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "simulator", "flush", "init", "docker", "help", "version"];
|
|
3658
|
+
const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "simulator", "tune", "flush", "init", "docker", "help", "version"];
|
|
3648
3659
|
const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
3649
3660
|
const HELP_TEXT$1 = `
|
|
3650
3661
|
Example:
|
|
3651
3662
|
|
|
3652
3663
|
node ${ENTRY_PATH$1} --help
|
|
3653
3664
|
`.trimStart();
|
|
3654
|
-
const main$
|
|
3665
|
+
const main$k = async () => {
|
|
3655
3666
|
if (!getEntry(import.meta.url)) {
|
|
3656
3667
|
return;
|
|
3657
3668
|
}
|
|
@@ -3659,14 +3670,14 @@ const main$i = async () => {
|
|
|
3659
3670
|
if (MODES.some((mode) => values[mode])) {
|
|
3660
3671
|
return;
|
|
3661
3672
|
}
|
|
3662
|
-
process.stdout.write(`@backtest-kit/cli ${"16.
|
|
3673
|
+
process.stdout.write(`@backtest-kit/cli ${"16.6.0"}\n`);
|
|
3663
3674
|
process.stdout.write("\n");
|
|
3664
3675
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
3665
3676
|
process.stdout.write("\n");
|
|
3666
3677
|
process.stdout.write(HELP_TEXT$1);
|
|
3667
3678
|
process.exit(0);
|
|
3668
3679
|
};
|
|
3669
|
-
main$
|
|
3680
|
+
main$k();
|
|
3670
3681
|
|
|
3671
3682
|
const notifyShutdown = singleshot(async () => {
|
|
3672
3683
|
console.log("Graceful shutdown initiated.");
|
|
@@ -3682,7 +3693,7 @@ const flush = async (entryPoint) => {
|
|
|
3682
3693
|
console.log(`Removed: ${target}`);
|
|
3683
3694
|
}
|
|
3684
3695
|
};
|
|
3685
|
-
const main$
|
|
3696
|
+
const main$j = async () => {
|
|
3686
3697
|
if (!getEntry(import.meta.url)) {
|
|
3687
3698
|
return;
|
|
3688
3699
|
}
|
|
@@ -3699,7 +3710,7 @@ const main$h = async () => {
|
|
|
3699
3710
|
}
|
|
3700
3711
|
process.exit(0);
|
|
3701
3712
|
};
|
|
3702
|
-
main$
|
|
3713
|
+
main$j();
|
|
3703
3714
|
|
|
3704
3715
|
const BEFORE_EXIT_FN$6 = singleshot(async () => {
|
|
3705
3716
|
process.off("SIGINT", BEFORE_EXIT_FN$6);
|
|
@@ -3722,7 +3733,7 @@ const BEFORE_EXIT_FN$6 = singleshot(async () => {
|
|
|
3722
3733
|
const listenGracefulShutdown$6 = singleshot(() => {
|
|
3723
3734
|
process.on("SIGINT", BEFORE_EXIT_FN$6);
|
|
3724
3735
|
});
|
|
3725
|
-
const main$
|
|
3736
|
+
const main$i = async () => {
|
|
3726
3737
|
if (!getEntry(import.meta.url)) {
|
|
3727
3738
|
return;
|
|
3728
3739
|
}
|
|
@@ -3740,7 +3751,7 @@ const main$g = async () => {
|
|
|
3740
3751
|
await cli.backtestMainService.connect();
|
|
3741
3752
|
listenGracefulShutdown$6();
|
|
3742
3753
|
};
|
|
3743
|
-
main$
|
|
3754
|
+
main$i();
|
|
3744
3755
|
|
|
3745
3756
|
const BEFORE_EXIT_FN$5 = singleshot(async () => {
|
|
3746
3757
|
process.off("SIGINT", BEFORE_EXIT_FN$5);
|
|
@@ -3759,7 +3770,7 @@ const BEFORE_EXIT_FN$5 = singleshot(async () => {
|
|
|
3759
3770
|
const listenGracefulShutdown$5 = singleshot(() => {
|
|
3760
3771
|
process.on("SIGINT", BEFORE_EXIT_FN$5);
|
|
3761
3772
|
});
|
|
3762
|
-
const main$
|
|
3773
|
+
const main$h = async () => {
|
|
3763
3774
|
if (!getEntry(import.meta.url)) {
|
|
3764
3775
|
return;
|
|
3765
3776
|
}
|
|
@@ -3778,7 +3789,7 @@ const main$f = async () => {
|
|
|
3778
3789
|
listenGracefulShutdown$5();
|
|
3779
3790
|
await cli.walkerMainService.connect();
|
|
3780
3791
|
};
|
|
3781
|
-
main$
|
|
3792
|
+
main$h();
|
|
3782
3793
|
|
|
3783
3794
|
const BEFORE_EXIT_FN$4 = singleshot(async () => {
|
|
3784
3795
|
process.off("SIGINT", BEFORE_EXIT_FN$4);
|
|
@@ -3800,7 +3811,7 @@ const BEFORE_EXIT_FN$4 = singleshot(async () => {
|
|
|
3800
3811
|
const listenGracefulShutdown$4 = singleshot(() => {
|
|
3801
3812
|
process.on("SIGINT", BEFORE_EXIT_FN$4);
|
|
3802
3813
|
});
|
|
3803
|
-
const main$
|
|
3814
|
+
const main$g = async () => {
|
|
3804
3815
|
if (!getEntry(import.meta.url)) {
|
|
3805
3816
|
return;
|
|
3806
3817
|
}
|
|
@@ -3814,7 +3825,7 @@ const main$e = async () => {
|
|
|
3814
3825
|
await cli.paperMainService.connect();
|
|
3815
3826
|
listenGracefulShutdown$4();
|
|
3816
3827
|
};
|
|
3817
|
-
main$
|
|
3828
|
+
main$g();
|
|
3818
3829
|
|
|
3819
3830
|
const BEFORE_EXIT_FN$3 = singleshot(async () => {
|
|
3820
3831
|
process.off("SIGINT", BEFORE_EXIT_FN$3);
|
|
@@ -3836,7 +3847,7 @@ const BEFORE_EXIT_FN$3 = singleshot(async () => {
|
|
|
3836
3847
|
const listenGracefulShutdown$3 = singleshot(() => {
|
|
3837
3848
|
process.on("SIGINT", BEFORE_EXIT_FN$3);
|
|
3838
3849
|
});
|
|
3839
|
-
const main$
|
|
3850
|
+
const main$f = async () => {
|
|
3840
3851
|
if (!getEntry(import.meta.url)) {
|
|
3841
3852
|
return;
|
|
3842
3853
|
}
|
|
@@ -3850,7 +3861,7 @@ const main$d = async () => {
|
|
|
3850
3861
|
await cli.liveMainService.connect();
|
|
3851
3862
|
listenGracefulShutdown$3();
|
|
3852
3863
|
};
|
|
3853
|
-
main$
|
|
3864
|
+
main$f();
|
|
3854
3865
|
|
|
3855
3866
|
const stopBacktestList$1 = async () => {
|
|
3856
3867
|
for (const item of await Backtest.list()) {
|
|
@@ -3914,7 +3925,7 @@ const BEFORE_EXIT_FN$2 = singleshot(async () => {
|
|
|
3914
3925
|
const listenGracefulShutdown$2 = singleshot(() => {
|
|
3915
3926
|
process.on("SIGINT", BEFORE_EXIT_FN$2);
|
|
3916
3927
|
});
|
|
3917
|
-
const main$
|
|
3928
|
+
const main$e = async () => {
|
|
3918
3929
|
if (!getEntry(import.meta.url)) {
|
|
3919
3930
|
return;
|
|
3920
3931
|
}
|
|
@@ -3970,7 +3981,7 @@ const main$c = async () => {
|
|
|
3970
3981
|
listenGracefulShutdown$2();
|
|
3971
3982
|
await cli.resolveService.attachEntry(path.resolve(cwd, entryPoint));
|
|
3972
3983
|
};
|
|
3973
|
-
main$
|
|
3984
|
+
main$e();
|
|
3974
3985
|
|
|
3975
3986
|
const MODE_MODULE = {
|
|
3976
3987
|
backtest: "backtest.module",
|
|
@@ -4073,7 +4084,7 @@ const createGracefulShutdown = (mode) => {
|
|
|
4073
4084
|
process.on("SIGINT", handler);
|
|
4074
4085
|
});
|
|
4075
4086
|
};
|
|
4076
|
-
const main$
|
|
4087
|
+
const main$d = async () => {
|
|
4077
4088
|
if (!getEntry(import.meta.url)) {
|
|
4078
4089
|
return;
|
|
4079
4090
|
}
|
|
@@ -4149,7 +4160,7 @@ const main$b = async () => {
|
|
|
4149
4160
|
}
|
|
4150
4161
|
await getEntrySubject().next(absolutePath);
|
|
4151
4162
|
};
|
|
4152
|
-
main$
|
|
4163
|
+
main$d();
|
|
4153
4164
|
|
|
4154
4165
|
const BEFORE_EXIT_FN$1 = singleshot(async () => {
|
|
4155
4166
|
process.off("SIGINT", BEFORE_EXIT_FN$1);
|
|
@@ -4160,7 +4171,7 @@ const BEFORE_EXIT_FN$1 = singleshot(async () => {
|
|
|
4160
4171
|
const listenGracefulShutdown$1 = singleshot(() => {
|
|
4161
4172
|
process.on("SIGINT", BEFORE_EXIT_FN$1);
|
|
4162
4173
|
});
|
|
4163
|
-
const main$
|
|
4174
|
+
const main$c = async () => {
|
|
4164
4175
|
if (!getEntry(import.meta.url)) {
|
|
4165
4176
|
return;
|
|
4166
4177
|
}
|
|
@@ -4170,7 +4181,7 @@ const main$a = async () => {
|
|
|
4170
4181
|
}
|
|
4171
4182
|
listenGracefulShutdown$1();
|
|
4172
4183
|
};
|
|
4173
|
-
main$
|
|
4184
|
+
main$c();
|
|
4174
4185
|
|
|
4175
4186
|
const BEFORE_EXIT_FN = singleshot(async () => {
|
|
4176
4187
|
process.off("SIGINT", BEFORE_EXIT_FN);
|
|
@@ -4181,7 +4192,7 @@ const BEFORE_EXIT_FN = singleshot(async () => {
|
|
|
4181
4192
|
const listenGracefulShutdown = singleshot(() => {
|
|
4182
4193
|
process.on("SIGINT", BEFORE_EXIT_FN);
|
|
4183
4194
|
});
|
|
4184
|
-
const main$
|
|
4195
|
+
const main$b = async () => {
|
|
4185
4196
|
if (!getEntry(import.meta.url)) {
|
|
4186
4197
|
return;
|
|
4187
4198
|
}
|
|
@@ -4191,7 +4202,7 @@ const main$9 = async () => {
|
|
|
4191
4202
|
}
|
|
4192
4203
|
listenGracefulShutdown();
|
|
4193
4204
|
};
|
|
4194
|
-
main$
|
|
4205
|
+
main$b();
|
|
4195
4206
|
|
|
4196
4207
|
const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
4197
4208
|
const keys = Object.keys(schema);
|
|
@@ -4213,7 +4224,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
|
4213
4224
|
}
|
|
4214
4225
|
return rows;
|
|
4215
4226
|
};
|
|
4216
|
-
const main$
|
|
4227
|
+
const main$a = async () => {
|
|
4217
4228
|
if (!getEntry(import.meta.url)) {
|
|
4218
4229
|
return;
|
|
4219
4230
|
}
|
|
@@ -4305,17 +4316,17 @@ const main$8 = async () => {
|
|
|
4305
4316
|
if (values.markdown) {
|
|
4306
4317
|
const filePath = resolve(dumpDir, `${dumpName}.md`);
|
|
4307
4318
|
await mkdir(dumpDir, { recursive: true });
|
|
4308
|
-
await writeFile(filePath, await toMarkdown$
|
|
4319
|
+
await writeFile(filePath, await toMarkdown$2(signalId, plots, signalSchema), "utf-8");
|
|
4309
4320
|
console.log(`Saved: ${filePath}`);
|
|
4310
4321
|
process.exit(0);
|
|
4311
4322
|
return;
|
|
4312
4323
|
}
|
|
4313
|
-
console.log(await toMarkdown$
|
|
4324
|
+
console.log(await toMarkdown$2(signalId, plots, signalSchema));
|
|
4314
4325
|
process.exit(0);
|
|
4315
4326
|
};
|
|
4316
|
-
main$
|
|
4327
|
+
main$a();
|
|
4317
4328
|
|
|
4318
|
-
const main$
|
|
4329
|
+
const main$9 = async () => {
|
|
4319
4330
|
if (!getEntry(import.meta.url)) {
|
|
4320
4331
|
return;
|
|
4321
4332
|
}
|
|
@@ -4378,9 +4389,9 @@ const main$7 = async () => {
|
|
|
4378
4389
|
};
|
|
4379
4390
|
process.on("SIGINT", beforeExit);
|
|
4380
4391
|
};
|
|
4381
|
-
main$
|
|
4392
|
+
main$9();
|
|
4382
4393
|
|
|
4383
|
-
const main$
|
|
4394
|
+
const main$8 = async () => {
|
|
4384
4395
|
if (!getEntry(import.meta.url)) {
|
|
4385
4396
|
return;
|
|
4386
4397
|
}
|
|
@@ -4461,9 +4472,9 @@ const main$6 = async () => {
|
|
|
4461
4472
|
console.log(JSON.stringify(candles, null, 2));
|
|
4462
4473
|
process.exit(0);
|
|
4463
4474
|
};
|
|
4464
|
-
main$
|
|
4475
|
+
main$8();
|
|
4465
4476
|
|
|
4466
|
-
const main$
|
|
4477
|
+
const main$7 = async () => {
|
|
4467
4478
|
if (!getEntry(import.meta.url)) {
|
|
4468
4479
|
return;
|
|
4469
4480
|
}
|
|
@@ -4577,11 +4588,314 @@ const main$5 = async () => {
|
|
|
4577
4588
|
}
|
|
4578
4589
|
process.exit(0);
|
|
4579
4590
|
};
|
|
4580
|
-
main$
|
|
4591
|
+
main$7();
|
|
4581
4592
|
|
|
4582
|
-
const
|
|
4583
|
-
|
|
4584
|
-
|
|
4593
|
+
const getBroker = singleshot(() => {
|
|
4594
|
+
// Reaches into BrokerAdapter internals on purpose: the public commit*
|
|
4595
|
+
// methods skip when payload.backtest=true and require enable(), while
|
|
4596
|
+
// brokerdebug must fire exactly one commit at the raw adapter proxy.
|
|
4597
|
+
const broker = Broker["getInstance"]();
|
|
4598
|
+
if (!broker) {
|
|
4599
|
+
throw new Error("Broker instance is not initialized.");
|
|
4600
|
+
}
|
|
4601
|
+
return broker;
|
|
4602
|
+
});
|
|
4603
|
+
/** Called when a new signal is opened (position entry confirmed). */
|
|
4604
|
+
const commitOrderOpen = trycatch(async (payload) => {
|
|
4605
|
+
const broker = getBroker();
|
|
4606
|
+
return await broker.onOrderOpenCommit(payload);
|
|
4607
|
+
}, {
|
|
4608
|
+
fallback: (error) => {
|
|
4609
|
+
console.log(error);
|
|
4610
|
+
process.exit(-1);
|
|
4611
|
+
},
|
|
4612
|
+
});
|
|
4613
|
+
/** Called when a new signal is closed (take-profit, stop-loss, or manual close). */
|
|
4614
|
+
const commitOrderClose = trycatch(async (payload) => {
|
|
4615
|
+
const broker = getBroker();
|
|
4616
|
+
return await broker.onOrderCloseCommit(payload);
|
|
4617
|
+
}, {
|
|
4618
|
+
fallback: (error) => {
|
|
4619
|
+
console.log(error);
|
|
4620
|
+
process.exit(-1);
|
|
4621
|
+
},
|
|
4622
|
+
});
|
|
4623
|
+
/** Called when a partial profit close is committed. */
|
|
4624
|
+
const commitPartialProfit = trycatch(async (payload) => {
|
|
4625
|
+
const broker = getBroker();
|
|
4626
|
+
return await broker.onPartialProfitCommit(payload);
|
|
4627
|
+
}, {
|
|
4628
|
+
fallback: (error) => {
|
|
4629
|
+
console.log(error);
|
|
4630
|
+
process.exit(-1);
|
|
4631
|
+
},
|
|
4632
|
+
});
|
|
4633
|
+
/** Called when a partial loss close is committed. */
|
|
4634
|
+
const commitPartialLoss = trycatch(async (payload) => {
|
|
4635
|
+
const broker = getBroker();
|
|
4636
|
+
return await broker.onPartialLossCommit(payload);
|
|
4637
|
+
}, {
|
|
4638
|
+
fallback: (error) => {
|
|
4639
|
+
console.log(error);
|
|
4640
|
+
process.exit(-1);
|
|
4641
|
+
},
|
|
4642
|
+
});
|
|
4643
|
+
/** Called when a trailing stop update is committed. */
|
|
4644
|
+
const commitTrailingStop = trycatch(async (payload) => {
|
|
4645
|
+
const broker = getBroker();
|
|
4646
|
+
return await broker.onTrailingStopCommit(payload);
|
|
4647
|
+
}, {
|
|
4648
|
+
fallback: (error) => {
|
|
4649
|
+
console.log(error);
|
|
4650
|
+
process.exit(-1);
|
|
4651
|
+
},
|
|
4652
|
+
});
|
|
4653
|
+
/** Called when a trailing take-profit update is committed. */
|
|
4654
|
+
const commitTrailingTake = trycatch(async (payload) => {
|
|
4655
|
+
const broker = getBroker();
|
|
4656
|
+
return await broker.onTrailingTakeCommit(payload);
|
|
4657
|
+
}, {
|
|
4658
|
+
fallback: (error) => {
|
|
4659
|
+
console.log(error);
|
|
4660
|
+
process.exit(-1);
|
|
4661
|
+
},
|
|
4662
|
+
});
|
|
4663
|
+
/** Called when a breakeven stop is committed (stop loss moved to entry price). */
|
|
4664
|
+
const commitBreakeven = trycatch(async (payload) => {
|
|
4665
|
+
const broker = getBroker();
|
|
4666
|
+
return await broker.onBreakevenCommit(payload);
|
|
4667
|
+
}, {
|
|
4668
|
+
fallback: (error) => {
|
|
4669
|
+
console.log(error);
|
|
4670
|
+
process.exit(-1);
|
|
4671
|
+
},
|
|
4672
|
+
});
|
|
4673
|
+
/** Called when a DCA (average-buy) entry is committed. */
|
|
4674
|
+
const commitAverageBuy = trycatch(async (payload) => {
|
|
4675
|
+
const broker = getBroker();
|
|
4676
|
+
return await broker.onAverageBuyCommit(payload);
|
|
4677
|
+
}, {
|
|
4678
|
+
fallback: (error) => {
|
|
4679
|
+
console.log(error);
|
|
4680
|
+
process.exit(-1);
|
|
4681
|
+
},
|
|
4682
|
+
});
|
|
4683
|
+
const COMMITS = [
|
|
4684
|
+
"signal-open",
|
|
4685
|
+
"signal-close",
|
|
4686
|
+
"partial-profit",
|
|
4687
|
+
"partial-loss",
|
|
4688
|
+
"average-buy",
|
|
4689
|
+
"trailing-stop",
|
|
4690
|
+
"trailing-take",
|
|
4691
|
+
"breakeven",
|
|
4692
|
+
];
|
|
4693
|
+
const ZERO_PNL = {
|
|
4694
|
+
pnlPercentage: 0,
|
|
4695
|
+
priceOpen: 0,
|
|
4696
|
+
priceClose: 0,
|
|
4697
|
+
pnlCost: 0,
|
|
4698
|
+
pnlEntries: 0,
|
|
4699
|
+
};
|
|
4700
|
+
const main$6 = async () => {
|
|
4701
|
+
if (!getEntry(import.meta.url)) {
|
|
4702
|
+
return;
|
|
4703
|
+
}
|
|
4704
|
+
const { values } = getArgs();
|
|
4705
|
+
if (!values.brokerdebug) {
|
|
4706
|
+
return;
|
|
4707
|
+
}
|
|
4708
|
+
{
|
|
4709
|
+
const cwd = process.cwd();
|
|
4710
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
4711
|
+
}
|
|
4712
|
+
await cli.configConnectionService.loadConfig("setup.config");
|
|
4713
|
+
{
|
|
4714
|
+
const loader = await cli.configConnectionService.loadConfig("loader.config");
|
|
4715
|
+
try {
|
|
4716
|
+
if (typeof loader === "function") {
|
|
4717
|
+
await loader();
|
|
4718
|
+
}
|
|
4719
|
+
if (typeof loader?.loader === "function") {
|
|
4720
|
+
await loader.loader();
|
|
4721
|
+
}
|
|
4722
|
+
}
|
|
4723
|
+
catch (error) {
|
|
4724
|
+
console.error("Module loader failed", error);
|
|
4725
|
+
process.exit(-1);
|
|
4726
|
+
}
|
|
4727
|
+
}
|
|
4728
|
+
await cli.moduleConnectionService.loadModule("brokerdebug.module");
|
|
4729
|
+
const [defaultExchangeName = null] = await listExchangeSchema();
|
|
4730
|
+
const exchangeName = values.exchange || defaultExchangeName?.exchangeName;
|
|
4731
|
+
const symbol = values.symbol || "BTCUSDT";
|
|
4732
|
+
const commit = values.commit || "signal-open";
|
|
4733
|
+
if (!COMMITS.includes(commit)) {
|
|
4734
|
+
console.error(`Unknown --commit value: ${commit}`);
|
|
4735
|
+
console.error(`Available: ${COMMITS.join(", ")}`);
|
|
4736
|
+
process.exit(1);
|
|
4737
|
+
}
|
|
4738
|
+
const candles = await Exchange.getCandles(symbol, "1m", 5, { exchangeName });
|
|
4739
|
+
const lastCandle = candles[candles.length - 1];
|
|
4740
|
+
const currentPrice = lastCandle.close;
|
|
4741
|
+
const priceTakeProfit = currentPrice * 1.02;
|
|
4742
|
+
const priceStopLoss = currentPrice * 0.98;
|
|
4743
|
+
const context = {
|
|
4744
|
+
strategyName: "broker-test",
|
|
4745
|
+
exchangeName: exchangeName,
|
|
4746
|
+
};
|
|
4747
|
+
const signalId = `broker-debug-${Date.now()}`;
|
|
4748
|
+
const when = alignToInterval(new Date(), "1m");
|
|
4749
|
+
{
|
|
4750
|
+
console.log(`symbol=${symbol} price=${currentPrice} commit=${commit}`);
|
|
4751
|
+
console.time(commit);
|
|
4752
|
+
}
|
|
4753
|
+
const run = async () => {
|
|
4754
|
+
if (commit === "signal-open") {
|
|
4755
|
+
await commitOrderOpen({
|
|
4756
|
+
type: "active",
|
|
4757
|
+
symbol,
|
|
4758
|
+
signalId,
|
|
4759
|
+
cost: 100,
|
|
4760
|
+
position: "long",
|
|
4761
|
+
priceOpen: currentPrice,
|
|
4762
|
+
priceTakeProfit,
|
|
4763
|
+
priceStopLoss,
|
|
4764
|
+
pnl: ZERO_PNL,
|
|
4765
|
+
peakProfit: ZERO_PNL,
|
|
4766
|
+
maxDrawdown: ZERO_PNL,
|
|
4767
|
+
attempt: 0,
|
|
4768
|
+
context,
|
|
4769
|
+
when,
|
|
4770
|
+
backtest: true,
|
|
4771
|
+
});
|
|
4772
|
+
}
|
|
4773
|
+
if (commit === "signal-close") {
|
|
4774
|
+
await commitOrderClose({
|
|
4775
|
+
symbol,
|
|
4776
|
+
signalId,
|
|
4777
|
+
cost: 100,
|
|
4778
|
+
position: "long",
|
|
4779
|
+
currentPrice,
|
|
4780
|
+
priceOpen: currentPrice,
|
|
4781
|
+
priceTakeProfit,
|
|
4782
|
+
priceStopLoss,
|
|
4783
|
+
totalEntries: 1,
|
|
4784
|
+
totalPartials: 0,
|
|
4785
|
+
attempt: 0,
|
|
4786
|
+
pnl: ZERO_PNL,
|
|
4787
|
+
peakProfit: ZERO_PNL,
|
|
4788
|
+
maxDrawdown: ZERO_PNL,
|
|
4789
|
+
context,
|
|
4790
|
+
when,
|
|
4791
|
+
backtest: true,
|
|
4792
|
+
});
|
|
4793
|
+
}
|
|
4794
|
+
if (commit === "partial-profit") {
|
|
4795
|
+
await commitPartialProfit({
|
|
4796
|
+
symbol,
|
|
4797
|
+
signalId,
|
|
4798
|
+
percentToClose: 50,
|
|
4799
|
+
cost: 50,
|
|
4800
|
+
currentPrice,
|
|
4801
|
+
position: "long",
|
|
4802
|
+
priceTakeProfit,
|
|
4803
|
+
priceStopLoss,
|
|
4804
|
+
context,
|
|
4805
|
+
when,
|
|
4806
|
+
backtest: true,
|
|
4807
|
+
});
|
|
4808
|
+
}
|
|
4809
|
+
if (commit === "partial-loss") {
|
|
4810
|
+
await commitPartialLoss({
|
|
4811
|
+
symbol,
|
|
4812
|
+
signalId,
|
|
4813
|
+
percentToClose: 50,
|
|
4814
|
+
cost: 50,
|
|
4815
|
+
currentPrice,
|
|
4816
|
+
position: "long",
|
|
4817
|
+
priceTakeProfit,
|
|
4818
|
+
priceStopLoss,
|
|
4819
|
+
context,
|
|
4820
|
+
when,
|
|
4821
|
+
backtest: true,
|
|
4822
|
+
});
|
|
4823
|
+
}
|
|
4824
|
+
if (commit === "average-buy") {
|
|
4825
|
+
await commitAverageBuy({
|
|
4826
|
+
symbol,
|
|
4827
|
+
signalId,
|
|
4828
|
+
currentPrice,
|
|
4829
|
+
cost: 100,
|
|
4830
|
+
position: "long",
|
|
4831
|
+
priceTakeProfit,
|
|
4832
|
+
priceStopLoss,
|
|
4833
|
+
context,
|
|
4834
|
+
when,
|
|
4835
|
+
backtest: true,
|
|
4836
|
+
});
|
|
4837
|
+
}
|
|
4838
|
+
if (commit === "trailing-stop") {
|
|
4839
|
+
await commitTrailingStop({
|
|
4840
|
+
symbol,
|
|
4841
|
+
signalId,
|
|
4842
|
+
percentShift: -50,
|
|
4843
|
+
currentPrice,
|
|
4844
|
+
newStopLossPrice: priceStopLoss + (currentPrice - priceStopLoss) * 0.5,
|
|
4845
|
+
takeProfitPrice: priceTakeProfit,
|
|
4846
|
+
position: "long",
|
|
4847
|
+
context,
|
|
4848
|
+
when,
|
|
4849
|
+
backtest: true,
|
|
4850
|
+
});
|
|
4851
|
+
}
|
|
4852
|
+
if (commit === "trailing-take") {
|
|
4853
|
+
await commitTrailingTake({
|
|
4854
|
+
symbol,
|
|
4855
|
+
signalId,
|
|
4856
|
+
percentShift: 50,
|
|
4857
|
+
currentPrice,
|
|
4858
|
+
newTakeProfitPrice: priceTakeProfit + (priceTakeProfit - currentPrice) * 0.5,
|
|
4859
|
+
takeProfitPrice: priceTakeProfit,
|
|
4860
|
+
position: "long",
|
|
4861
|
+
context,
|
|
4862
|
+
when,
|
|
4863
|
+
backtest: true,
|
|
4864
|
+
});
|
|
4865
|
+
}
|
|
4866
|
+
if (commit === "breakeven") {
|
|
4867
|
+
await commitBreakeven({
|
|
4868
|
+
symbol,
|
|
4869
|
+
signalId,
|
|
4870
|
+
currentPrice,
|
|
4871
|
+
newStopLossPrice: currentPrice,
|
|
4872
|
+
newTakeProfitPrice: priceTakeProfit,
|
|
4873
|
+
position: "long",
|
|
4874
|
+
context,
|
|
4875
|
+
when,
|
|
4876
|
+
backtest: true,
|
|
4877
|
+
});
|
|
4878
|
+
}
|
|
4879
|
+
};
|
|
4880
|
+
await runInMockContext(run, {
|
|
4881
|
+
when,
|
|
4882
|
+
strategyName: context.strategyName,
|
|
4883
|
+
exchangeName: context.exchangeName,
|
|
4884
|
+
frameName: "",
|
|
4885
|
+
symbol,
|
|
4886
|
+
backtest: true,
|
|
4887
|
+
});
|
|
4888
|
+
{
|
|
4889
|
+
console.log(`${commit} OK`);
|
|
4890
|
+
console.timeEnd(commit);
|
|
4891
|
+
}
|
|
4892
|
+
process.exit(0);
|
|
4893
|
+
};
|
|
4894
|
+
main$6();
|
|
4895
|
+
|
|
4896
|
+
const SIMULATOR_NAME$1 = "cli_simulator";
|
|
4897
|
+
const IDEA_DIRECTIONS$1 = ["LONG", "SHORT", "NEUTRAL"];
|
|
4898
|
+
const validateIdea$1 = (idea, line) => {
|
|
4585
4899
|
if (typeof idea !== "object" || idea === null) {
|
|
4586
4900
|
return `line ${line}: not an object`;
|
|
4587
4901
|
}
|
|
@@ -4594,15 +4908,16 @@ const validateIdea = (idea, line) => {
|
|
|
4594
4908
|
if (typeof idea.symbol !== "string" || !idea.symbol) {
|
|
4595
4909
|
return `line ${line}: "symbol" must be a non-empty string`;
|
|
4596
4910
|
}
|
|
4597
|
-
if (!IDEA_DIRECTIONS.includes(idea.direction)) {
|
|
4598
|
-
return `line ${line}: "direction" must be one of ${IDEA_DIRECTIONS.join("|")}, got ${JSON.stringify(idea.direction)}`;
|
|
4911
|
+
if (!IDEA_DIRECTIONS$1.includes(idea.direction)) {
|
|
4912
|
+
return `line ${line}: "direction" must be one of ${IDEA_DIRECTIONS$1.join("|")}, got ${JSON.stringify(idea.direction)}`;
|
|
4599
4913
|
}
|
|
4600
4914
|
if (typeof idea.author !== "string" || !idea.author) {
|
|
4601
4915
|
return `line ${line}: "author" must be a non-empty string`;
|
|
4602
4916
|
}
|
|
4603
4917
|
return null;
|
|
4604
4918
|
};
|
|
4605
|
-
const toMarkdown = (result) => {
|
|
4919
|
+
const toMarkdown$1 = (result) => {
|
|
4920
|
+
const profitable = result.reports.filter(({ totalPnlPercent }) => totalPnlPercent > 0).length;
|
|
4606
4921
|
const lines = [];
|
|
4607
4922
|
lines.push(`# Simulator Report — ${result.symbol}`);
|
|
4608
4923
|
lines.push("");
|
|
@@ -4611,33 +4926,36 @@ const toMarkdown = (result) => {
|
|
|
4611
4926
|
lines.push(`| Ideas (total / directional) | ${result.ideasTotal} / ${result.ideasDirectional} |`);
|
|
4612
4927
|
lines.push(`| Profiles (truncated) | ${result.profileCount} (${result.truncatedCount}) |`);
|
|
4613
4928
|
lines.push(`| Authors allowed / banned | ${result.allowedAuthors.length} / ${result.bannedAuthors.length} |`);
|
|
4614
|
-
lines.push(`|
|
|
4929
|
+
lines.push(`| Profitable corridor | ${profitable} / ${result.reports.length} grid points |`);
|
|
4615
4930
|
lines.push(`| Hold minutes avg / p95 / p99 | ${result.avgHoldMinutes.toFixed(0)} / ${result.p95HoldMinutes} / ${result.p99HoldMinutes} |`);
|
|
4616
4931
|
lines.push("");
|
|
4617
4932
|
lines.push(`## Ranking winners`);
|
|
4618
4933
|
lines.push("");
|
|
4619
|
-
lines.push(`| Criterion | Stop% |
|
|
4620
|
-
lines.push(`| --- | --- | --- | --- | --- | --- | --- | --- | --- | ---
|
|
4934
|
+
lines.push(`| Criterion | Stop% | Hold | Track | HitRate | Trades | PNL% | WinRate | Sharpe | Sortino |`);
|
|
4935
|
+
lines.push(`| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |`);
|
|
4621
4936
|
for (const best of result.best) {
|
|
4622
4937
|
if (!best.report) {
|
|
4623
|
-
lines.push(`| ${best.criterion} | — | — | — | — | — | — | — | — | —
|
|
4938
|
+
lines.push(`| ${best.criterion} | — | — | — | — | — | — | — | — | — |`);
|
|
4624
4939
|
continue;
|
|
4625
4940
|
}
|
|
4626
4941
|
const { point } = best.report;
|
|
4627
|
-
lines.push(`| ${best.criterion} | ${point.hardStopPercent} | ${point.
|
|
4942
|
+
lines.push(`| ${best.criterion} | ${point.hardStopPercent} | ${point.holdMinutes / 60}h | ${point.minAuthorTrack} | ${point.minAuthorHitRate} | ` +
|
|
4628
4943
|
`${best.report.trades} | ${best.report.totalPnlPercent.toFixed(2)}% | ${(best.report.winRate * 100).toFixed(0)}% | ${best.report.sharpe.toFixed(2)} | ${best.report.sortino.toFixed(2)} |`);
|
|
4629
4944
|
}
|
|
4630
4945
|
lines.push("");
|
|
4631
|
-
lines.push(`## Allowed authors (
|
|
4946
|
+
lines.push(`## Allowed authors (whitelist of the sharpe winner rule)`);
|
|
4632
4947
|
lines.push("");
|
|
4633
4948
|
lines.push(`| Author | Ideas | Hits | HitRate |`);
|
|
4634
4949
|
lines.push(`| --- | --- | --- | --- |`);
|
|
4635
|
-
|
|
4950
|
+
// артефакт авторов живёт по-победительно в best[] — ран-левел
|
|
4951
|
+
// authorStats в ISimulatorResult больше нет
|
|
4952
|
+
const sharpeBest = result.best.find(({ criterion }) => criterion === "sharpe");
|
|
4953
|
+
for (const stat of (sharpeBest?.authorStats ?? []).filter(({ banned }) => !banned)) {
|
|
4636
4954
|
lines.push(`| ${stat.author} | ${stat.ideas} | ${stat.hits} | ${(stat.hitRate * 100).toFixed(0)}% |`);
|
|
4637
4955
|
}
|
|
4638
4956
|
return lines.join("\n");
|
|
4639
4957
|
};
|
|
4640
|
-
const main$
|
|
4958
|
+
const main$5 = async () => {
|
|
4641
4959
|
if (!getEntry(import.meta.url)) {
|
|
4642
4960
|
return;
|
|
4643
4961
|
}
|
|
@@ -4674,7 +4992,7 @@ const main$4 = async () => {
|
|
|
4674
4992
|
console.error(`Error: invalid JSON in ideas file, line ${i + 1}`);
|
|
4675
4993
|
process.exit(1);
|
|
4676
4994
|
}
|
|
4677
|
-
const problem = validateIdea(idea, i + 1);
|
|
4995
|
+
const problem = validateIdea$1(idea, i + 1);
|
|
4678
4996
|
if (problem) {
|
|
4679
4997
|
console.error(`Error: ideas file does not match the idea structure — ${problem}`);
|
|
4680
4998
|
console.error(`Expected shape: { "id": number, "ts": number, "symbol": string, "direction": "LONG"|"SHORT"|"NEUTRAL", "author": string }`);
|
|
@@ -4710,20 +5028,42 @@ const main$4 = async () => {
|
|
|
4710
5028
|
}
|
|
4711
5029
|
const [defaultExchangeName = null] = await listExchangeSchema();
|
|
4712
5030
|
const exchangeName = values.exchange || defaultExchangeName?.exchangeName;
|
|
5031
|
+
if (values.verbose) {
|
|
5032
|
+
overrideExchangeSchema({
|
|
5033
|
+
exchangeName,
|
|
5034
|
+
callbacks: {
|
|
5035
|
+
onCandleData(symbol, interval, since) {
|
|
5036
|
+
console.log(`Received candle data for symbol: ${symbol}, interval: ${interval}, since: ${since.toUTCString()}`);
|
|
5037
|
+
},
|
|
5038
|
+
},
|
|
5039
|
+
});
|
|
5040
|
+
}
|
|
4713
5041
|
const symbol = values.symbol || "BTCUSDT";
|
|
5042
|
+
// Проба осуществимости, НЕ поиск заработка (подбор параметров — у
|
|
5043
|
+
// --tune): собирающая прибыль механика выключена, остаётся вопрос
|
|
5044
|
+
// "есть ли прибыльный коридор" по стопу x холду x правилу бана —
|
|
5045
|
+
// 48 точек, быстрый вердикт
|
|
4714
5046
|
addSimulatorSchema({
|
|
4715
|
-
simulatorName: SIMULATOR_NAME,
|
|
5047
|
+
simulatorName: SIMULATOR_NAME$1,
|
|
4716
5048
|
exchangeName,
|
|
4717
5049
|
gridAxes: {
|
|
4718
|
-
|
|
4719
|
-
|
|
5050
|
+
// грубая шкала катастрофы: коридор должен быть широким, не точкой
|
|
5051
|
+
hardStopPercent: [2, 3, 5, 7],
|
|
5052
|
+
// инертен: проба не собирает прибыль, выход — по времени или стопу
|
|
5053
|
+
trailingTakePercent: [100],
|
|
4720
5054
|
holdMinutes: [24 * 60, 2 * 24 * 60, 3 * 24 * 60],
|
|
4721
|
-
|
|
4722
|
-
|
|
5055
|
+
// одного проверенного автора достаточно — консенсус не перебираем
|
|
5056
|
+
minIdeasAligned: [1],
|
|
5057
|
+
// правило бана — единственная перебираемая "умность" пробы
|
|
5058
|
+
minAuthorTrack: [3, 5],
|
|
4723
5059
|
minAuthorHitRate: [0.5, 0.6],
|
|
4724
|
-
|
|
4725
|
-
|
|
5060
|
+
minAuthorWilson: [0],
|
|
5061
|
+
minWeightAligned: [0],
|
|
5062
|
+
profitLockPercent: [0],
|
|
5063
|
+
authorMetric: ["close"],
|
|
5064
|
+
banCriteria: ["sharpe", "pnl"],
|
|
4726
5065
|
},
|
|
5066
|
+
reportOrder: "sharpe",
|
|
4727
5067
|
callbacks: {
|
|
4728
5068
|
onProgress: (symbol, stage, processed, total) => {
|
|
4729
5069
|
if (values.verbose) {
|
|
@@ -4785,7 +5125,7 @@ const main$4 = async () => {
|
|
|
4785
5125
|
});
|
|
4786
5126
|
const result = await Simulator.run({
|
|
4787
5127
|
symbol,
|
|
4788
|
-
simulatorName: SIMULATOR_NAME,
|
|
5128
|
+
simulatorName: SIMULATOR_NAME$1,
|
|
4789
5129
|
ideas,
|
|
4790
5130
|
});
|
|
4791
5131
|
const dumpName = values.output || `simulator_${symbol}_${Date.now()}`;
|
|
@@ -4800,11 +5140,299 @@ const main$4 = async () => {
|
|
|
4800
5140
|
if (values.markdown) {
|
|
4801
5141
|
const filePath = resolve(dumpDir, `${dumpName}.md`);
|
|
4802
5142
|
await mkdir(dumpDir, { recursive: true });
|
|
4803
|
-
await writeFile(filePath, toMarkdown(result), "utf-8");
|
|
5143
|
+
await writeFile(filePath, toMarkdown$1(result), "utf-8");
|
|
5144
|
+
console.log(`Saved: ${filePath}`);
|
|
5145
|
+
process.exit(0);
|
|
5146
|
+
}
|
|
5147
|
+
console.log(toMarkdown$1(result));
|
|
5148
|
+
process.exit(0);
|
|
5149
|
+
};
|
|
5150
|
+
main$5();
|
|
5151
|
+
|
|
5152
|
+
const SIMULATOR_NAME = "cli_tune";
|
|
5153
|
+
/** Доля времени фида, уходящая в обучение; хвост — один OOS-выстрел. */
|
|
5154
|
+
const DEFAULT_TRAIN_SPLIT = 0.7;
|
|
5155
|
+
const IDEA_DIRECTIONS = ["LONG", "SHORT", "NEUTRAL"];
|
|
5156
|
+
const validateIdea = (idea, line) => {
|
|
5157
|
+
if (typeof idea !== "object" || idea === null) {
|
|
5158
|
+
return `line ${line}: not an object`;
|
|
5159
|
+
}
|
|
5160
|
+
if (typeof idea.id !== "number") {
|
|
5161
|
+
return `line ${line}: "id" must be a number, got ${typeof idea.id}`;
|
|
5162
|
+
}
|
|
5163
|
+
if (typeof idea.ts !== "number") {
|
|
5164
|
+
return `line ${line}: "ts" must be a number (unix ms), got ${typeof idea.ts}`;
|
|
5165
|
+
}
|
|
5166
|
+
if (typeof idea.symbol !== "string" || !idea.symbol) {
|
|
5167
|
+
return `line ${line}: "symbol" must be a non-empty string`;
|
|
5168
|
+
}
|
|
5169
|
+
if (!IDEA_DIRECTIONS.includes(idea.direction)) {
|
|
5170
|
+
return `line ${line}: "direction" must be one of ${IDEA_DIRECTIONS.join("|")}, got ${JSON.stringify(idea.direction)}`;
|
|
5171
|
+
}
|
|
5172
|
+
if (typeof idea.author !== "string" || !idea.author) {
|
|
5173
|
+
return `line ${line}: "author" must be a non-empty string`;
|
|
5174
|
+
}
|
|
5175
|
+
return null;
|
|
5176
|
+
};
|
|
5177
|
+
const pointLabel = (point) => `H=${point.hardStopPercent} TT=${point.trailingTakePercent} hold=${point.holdMinutes / 60}h N=${point.minIdeasAligned} ` +
|
|
5178
|
+
`track=${point.minAuthorTrack} rate=${point.minAuthorHitRate} wilson=${point.minAuthorWilson} W=${point.minWeightAligned} ` +
|
|
5179
|
+
`lock=${point.profitLockPercent} metric=${point.authorMetric}`;
|
|
5180
|
+
const fmtRatio = (value) => Number.isFinite(value) ? value.toFixed(2) : "inf";
|
|
5181
|
+
const toMarkdown = (train, test, trainSplit) => {
|
|
5182
|
+
const lines = [];
|
|
5183
|
+
lines.push(`# Tune Report — ${train.symbol}`);
|
|
5184
|
+
lines.push("");
|
|
5185
|
+
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.`);
|
|
5186
|
+
lines.push("");
|
|
5187
|
+
lines.push(`## Train (${train.ideasDirectional} directional ideas, ${train.reports.length} grid points)`);
|
|
5188
|
+
lines.push("");
|
|
5189
|
+
lines.push(`| Criterion | Point | Trades | PNL% | WinRate | DD% | Sharpe | Sortino |`);
|
|
5190
|
+
lines.push(`| --- | --- | --- | --- | --- | --- | --- | --- |`);
|
|
5191
|
+
for (const best of train.best) {
|
|
5192
|
+
if (!best.report) {
|
|
5193
|
+
lines.push(`| ${best.criterion} | — | — | — | — | — | — | — |`);
|
|
5194
|
+
continue;
|
|
5195
|
+
}
|
|
5196
|
+
lines.push(`| ${best.criterion} | ${pointLabel(best.report.point)} | ${best.report.trades} | ${best.report.totalPnlPercent.toFixed(2)}% | ` +
|
|
5197
|
+
`${(best.report.winRate * 100).toFixed(0)}% | ${best.report.maxSeriesDrawdownPercent.toFixed(2)} | ${fmtRatio(best.report.sharpe)} | ${fmtRatio(best.report.sortino)} |`);
|
|
5198
|
+
}
|
|
5199
|
+
lines.push("");
|
|
5200
|
+
lines.push(`## Out-of-sample (frozen sharpe winner, ${test.ideasDirectional} tail ideas)`);
|
|
5201
|
+
lines.push("");
|
|
5202
|
+
lines.push(`Frozen point: ${pointLabel(test.point)}`);
|
|
5203
|
+
lines.push("");
|
|
5204
|
+
lines.push(`| Metric | Value |`);
|
|
5205
|
+
lines.push(`| --- | --- |`);
|
|
5206
|
+
lines.push(`| Trades (skipped busy) | ${test.report.trades} (${test.report.skippedBusy}) |`);
|
|
5207
|
+
lines.push(`| PNL | ${test.report.totalPnlPercent.toFixed(2)}% |`);
|
|
5208
|
+
lines.push(`| Win rate | ${(test.report.winRate * 100).toFixed(0)}% |`);
|
|
5209
|
+
lines.push(`| Profit factor | ${fmtRatio(test.report.profitFactor)} |`);
|
|
5210
|
+
lines.push(`| Max series drawdown | ${test.report.maxSeriesDrawdownPercent.toFixed(2)}% |`);
|
|
5211
|
+
lines.push(`| Sharpe / Sortino | ${fmtRatio(test.report.sharpe)} / ${fmtRatio(test.report.sortino)} |`);
|
|
5212
|
+
lines.push(`| Recovery factor | ${fmtRatio(test.report.recoveryFactor)} |`);
|
|
5213
|
+
lines.push(`| Exits | ${Object.entries(test.report.exitReasons).map(([reason, count]) => `${reason}=${count}`).join(", ")} |`);
|
|
5214
|
+
lines.push("");
|
|
5215
|
+
lines.push(`## Test trades`);
|
|
5216
|
+
lines.push("");
|
|
5217
|
+
lines.push(`| Direction | Exit | PNL% | Hold | Entry (UTC) |`);
|
|
5218
|
+
lines.push(`| --- | --- | --- | --- | --- |`);
|
|
5219
|
+
for (const trade of test.trades) {
|
|
5220
|
+
lines.push(`| ${trade.direction} | ${trade.exitReason} | ${trade.pnlPercent.toFixed(2)} | ${trade.holdMinutesActual}m | ${new Date(trade.entryTimestamp).toISOString()} |`);
|
|
5221
|
+
}
|
|
5222
|
+
lines.push("");
|
|
5223
|
+
lines.push(`## Frozen author track record (train range, sharpe winner rule)`);
|
|
5224
|
+
lines.push("");
|
|
5225
|
+
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.`);
|
|
5226
|
+
lines.push("");
|
|
5227
|
+
lines.push(`| Author | Ideas | Hits | Banned |`);
|
|
5228
|
+
lines.push(`| --- | --- | --- | --- |`);
|
|
5229
|
+
for (const stat of test.authorStats) {
|
|
5230
|
+
lines.push(`| ${stat.author} | ${stat.ideas} | ${stat.hits} | ${stat.banned ? "yes" : ""} |`);
|
|
5231
|
+
}
|
|
5232
|
+
return lines.join("\n");
|
|
5233
|
+
};
|
|
5234
|
+
const main$4 = async () => {
|
|
5235
|
+
if (!getEntry(import.meta.url)) {
|
|
5236
|
+
return;
|
|
5237
|
+
}
|
|
5238
|
+
const { values, positionals } = getArgs();
|
|
5239
|
+
if (!values.tune) {
|
|
5240
|
+
return;
|
|
5241
|
+
}
|
|
5242
|
+
const [ideasPath = null] = positionals.filter((value) => value.endsWith(".jsonl"));
|
|
5243
|
+
if (!ideasPath) {
|
|
5244
|
+
console.error("Error: positional path to an ideas .jsonl file is required");
|
|
5245
|
+
process.exit(1);
|
|
5246
|
+
}
|
|
5247
|
+
let ideas = [];
|
|
5248
|
+
{
|
|
5249
|
+
let content;
|
|
5250
|
+
try {
|
|
5251
|
+
content = await readFile(resolve(ideasPath), "utf-8");
|
|
5252
|
+
}
|
|
5253
|
+
catch (error) {
|
|
5254
|
+
console.error(`Error: cannot read ideas file: ${ideasPath}`);
|
|
5255
|
+
process.exit(1);
|
|
5256
|
+
}
|
|
5257
|
+
const rows = content.split("\n").filter(Boolean);
|
|
5258
|
+
if (!rows.length) {
|
|
5259
|
+
console.error(`Error: ideas file is empty: ${ideasPath}`);
|
|
5260
|
+
process.exit(1);
|
|
5261
|
+
}
|
|
5262
|
+
for (let i = 0; i < rows.length; i++) {
|
|
5263
|
+
let idea;
|
|
5264
|
+
try {
|
|
5265
|
+
idea = JSON.parse(rows[i]);
|
|
5266
|
+
}
|
|
5267
|
+
catch {
|
|
5268
|
+
console.error(`Error: invalid JSON in ideas file, line ${i + 1}`);
|
|
5269
|
+
process.exit(1);
|
|
5270
|
+
}
|
|
5271
|
+
const problem = validateIdea(idea, i + 1);
|
|
5272
|
+
if (problem) {
|
|
5273
|
+
console.error(`Error: ideas file does not match the idea structure — ${problem}`);
|
|
5274
|
+
console.error(`Expected shape: { "id": number, "ts": number, "symbol": string, "direction": "LONG"|"SHORT"|"NEUTRAL", "author": string }`);
|
|
5275
|
+
process.exit(1);
|
|
5276
|
+
}
|
|
5277
|
+
ideas.push(idea);
|
|
5278
|
+
}
|
|
5279
|
+
}
|
|
5280
|
+
const trainSplit = values.split
|
|
5281
|
+
? parseFloat(values.split)
|
|
5282
|
+
: DEFAULT_TRAIN_SPLIT;
|
|
5283
|
+
if (!Number.isFinite(trainSplit) || trainSplit <= 0 || trainSplit >= 1) {
|
|
5284
|
+
console.error(`Error: --split must be a fraction between 0 and 1, got ${values.split}`);
|
|
5285
|
+
process.exit(1);
|
|
5286
|
+
}
|
|
5287
|
+
{
|
|
5288
|
+
const cwd = process.cwd();
|
|
5289
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
5290
|
+
}
|
|
5291
|
+
await cli.configConnectionService.loadConfig("setup.config");
|
|
5292
|
+
{
|
|
5293
|
+
const loader = await cli.configConnectionService.loadConfig("loader.config");
|
|
5294
|
+
try {
|
|
5295
|
+
if (typeof loader === "function") {
|
|
5296
|
+
await loader();
|
|
5297
|
+
}
|
|
5298
|
+
if (typeof loader?.loader === "function") {
|
|
5299
|
+
await loader.loader();
|
|
5300
|
+
}
|
|
5301
|
+
}
|
|
5302
|
+
catch (error) {
|
|
5303
|
+
console.error("Module loader failed", error);
|
|
5304
|
+
process.exit(-1);
|
|
5305
|
+
}
|
|
5306
|
+
}
|
|
5307
|
+
await cli.moduleConnectionService.loadModule("tune.module");
|
|
5308
|
+
{
|
|
5309
|
+
await cli.exchangeSchemaService.addSchema();
|
|
5310
|
+
await cli.symbolSchemaService.addSchema();
|
|
5311
|
+
}
|
|
5312
|
+
const [defaultExchangeName = null] = await listExchangeSchema();
|
|
5313
|
+
const exchangeName = values.exchange || defaultExchangeName?.exchangeName;
|
|
5314
|
+
if (values.verbose) {
|
|
5315
|
+
overrideExchangeSchema({
|
|
5316
|
+
exchangeName,
|
|
5317
|
+
callbacks: {
|
|
5318
|
+
onCandleData(symbol, interval, since) {
|
|
5319
|
+
console.log(`Received candle data for symbol: ${symbol}, interval: ${interval}, since: ${since.toUTCString()}`);
|
|
5320
|
+
},
|
|
5321
|
+
},
|
|
5322
|
+
});
|
|
5323
|
+
}
|
|
5324
|
+
const symbol = values.symbol || "BTCUSDT";
|
|
5325
|
+
// Подбор параметров: полная сетка с собирающей прибыль механикой —
|
|
5326
|
+
// замок, трейлинг, взвешенный консенсус, Вильсон, обе метрики бана.
|
|
5327
|
+
// Честность обеспечивается сплитом: обучение видит только голову,
|
|
5328
|
+
// хвост тратится на один выстрел замороженного sharpe-победителя
|
|
5329
|
+
addSimulatorSchema({
|
|
5330
|
+
simulatorName: SIMULATOR_NAME,
|
|
5331
|
+
exchangeName,
|
|
5332
|
+
gridAxes: {
|
|
5333
|
+
// стопы < 2% сидят внутри медианного шейкаута и не выигрывают
|
|
5334
|
+
hardStopPercent: [2, 2.5, 3, 4, 5, 7],
|
|
5335
|
+
trailingTakePercent: [1, 1.5, 2, 3, 4],
|
|
5336
|
+
holdMinutes: [24 * 60, 2 * 24 * 60, 3 * 24 * 60],
|
|
5337
|
+
minIdeasAligned: [1, 2],
|
|
5338
|
+
minAuthorTrack: [2, 3, 5],
|
|
5339
|
+
minAuthorHitRate: [0.5, 0.6],
|
|
5340
|
+
minAuthorWilson: [0, 0.6],
|
|
5341
|
+
minWeightAligned: [0, 0.6, 1.2],
|
|
5342
|
+
profitLockPercent: [0, 1.5, 2.5],
|
|
5343
|
+
authorMetric: ["close", "reach"],
|
|
5344
|
+
banCriteria: ["sharpe"],
|
|
5345
|
+
},
|
|
5346
|
+
reportOrder: "sharpe",
|
|
5347
|
+
callbacks: {
|
|
5348
|
+
onProgress: (symbol, stage, processed, total) => {
|
|
5349
|
+
if (values.verbose) {
|
|
5350
|
+
console.log("onProgress", { symbol, stage, processed, total });
|
|
5351
|
+
}
|
|
5352
|
+
},
|
|
5353
|
+
onIdeas: (symbol, ideasTotal, ideasDirectional) => {
|
|
5354
|
+
if (values.verbose) {
|
|
5355
|
+
console.log("onIdeas", { symbol, ideasTotal, ideasDirectional });
|
|
5356
|
+
}
|
|
5357
|
+
},
|
|
5358
|
+
onProfiles: (symbol, profiles, truncatedCount) => {
|
|
5359
|
+
if (values.verbose) {
|
|
5360
|
+
console.log("onProfiles", { symbol, profiles: profiles.length, truncatedCount });
|
|
5361
|
+
}
|
|
5362
|
+
},
|
|
5363
|
+
onAuthorsTrained: (symbol, stats, bannedIdeas) => {
|
|
5364
|
+
if (values.verbose) {
|
|
5365
|
+
console.log("onAuthorsTrained", {
|
|
5366
|
+
symbol,
|
|
5367
|
+
authors: stats.length,
|
|
5368
|
+
banned: stats.filter(({ banned }) => banned).length,
|
|
5369
|
+
bannedIdeas,
|
|
5370
|
+
});
|
|
5371
|
+
}
|
|
5372
|
+
},
|
|
5373
|
+
onRanking: (symbol, criterion, _sorted, best) => {
|
|
5374
|
+
if (values.verbose) {
|
|
5375
|
+
console.log("onRanking", {
|
|
5376
|
+
symbol,
|
|
5377
|
+
criterion,
|
|
5378
|
+
point: best.report?.point ?? null,
|
|
5379
|
+
pnl: best.report ? +best.report.totalPnlPercent.toFixed(2) : null,
|
|
5380
|
+
});
|
|
5381
|
+
}
|
|
5382
|
+
},
|
|
5383
|
+
onTestDone: (symbol, result) => {
|
|
5384
|
+
if (values.verbose) {
|
|
5385
|
+
console.log("onTestDone", {
|
|
5386
|
+
symbol,
|
|
5387
|
+
trades: result.report.trades,
|
|
5388
|
+
pnl: +result.report.totalPnlPercent.toFixed(2),
|
|
5389
|
+
});
|
|
5390
|
+
}
|
|
5391
|
+
},
|
|
5392
|
+
},
|
|
5393
|
+
});
|
|
5394
|
+
// обучение видит ТОЛЬКО голову ленты; хвост — один OOS-выстрел
|
|
5395
|
+
const sorted = [...ideas].sort((a, b) => a.ts - b.ts);
|
|
5396
|
+
const cutoff = sorted[0].ts + (sorted[sorted.length - 1].ts - sorted[0].ts) * trainSplit;
|
|
5397
|
+
const trainIdeas = sorted.filter(({ ts }) => ts < cutoff);
|
|
5398
|
+
const testIdeas = sorted.filter(({ ts }) => ts >= cutoff);
|
|
5399
|
+
const train = await Simulator.run({
|
|
5400
|
+
symbol,
|
|
5401
|
+
simulatorName: SIMULATOR_NAME,
|
|
5402
|
+
ideas: trainIdeas,
|
|
5403
|
+
});
|
|
5404
|
+
const sharpeBest = train.best.find(({ criterion }) => criterion === "sharpe");
|
|
5405
|
+
if (!sharpeBest?.report) {
|
|
5406
|
+
console.error("Error: training produced no sharpe winner — nothing to freeze for the out-of-sample shot");
|
|
5407
|
+
process.exit(1);
|
|
5408
|
+
}
|
|
5409
|
+
// заморозка: точка и трек-рекорд sharpe-победителя как есть — из
|
|
5410
|
+
// замороженных статов test() читает только author/ideas/hits и сам
|
|
5411
|
+
// перевыводит баны и веса под правило точки
|
|
5412
|
+
const test = await Simulator.test({
|
|
5413
|
+
symbol,
|
|
5414
|
+
simulatorName: SIMULATOR_NAME,
|
|
5415
|
+
ideas: testIdeas,
|
|
5416
|
+
point: sharpeBest.report.point,
|
|
5417
|
+
authorStats: sharpeBest.authorStats,
|
|
5418
|
+
});
|
|
5419
|
+
const dumpName = values.output || `tune_${symbol}_${Date.now()}`;
|
|
5420
|
+
const dumpDir = join(process.cwd(), "dump");
|
|
5421
|
+
if (values.json) {
|
|
5422
|
+
const filePath = resolve(dumpDir, `${dumpName}.json`);
|
|
5423
|
+
await mkdir(dumpDir, { recursive: true });
|
|
5424
|
+
await writeFile(filePath, JSON.stringify({ trainSplit, train, test }, null, 2), "utf-8");
|
|
5425
|
+
console.log(`Saved: ${filePath}`);
|
|
5426
|
+
process.exit(0);
|
|
5427
|
+
}
|
|
5428
|
+
if (values.markdown) {
|
|
5429
|
+
const filePath = resolve(dumpDir, `${dumpName}.md`);
|
|
5430
|
+
await mkdir(dumpDir, { recursive: true });
|
|
5431
|
+
await writeFile(filePath, toMarkdown(train, test, trainSplit), "utf-8");
|
|
4804
5432
|
console.log(`Saved: ${filePath}`);
|
|
4805
5433
|
process.exit(0);
|
|
4806
5434
|
}
|
|
4807
|
-
console.log(toMarkdown(
|
|
5435
|
+
console.log(toMarkdown(train, test, trainSplit));
|
|
4808
5436
|
process.exit(0);
|
|
4809
5437
|
};
|
|
4810
5438
|
main$4();
|
|
@@ -5005,7 +5633,8 @@ Modes:
|
|
|
5005
5633
|
--dump Fetch and save raw OHLCV candles
|
|
5006
5634
|
--pnldebug Simulate PnL per minute for a given entry price and direction
|
|
5007
5635
|
--brokerdebug Fire a single broker commit against the live broker adapter
|
|
5008
|
-
--simulator <ideas.jsonl>
|
|
5636
|
+
--simulator <ideas.jsonl> Feasibility probe over crowd ideas: is there a profitable corridor at all
|
|
5637
|
+
--tune <ideas.jsonl> Walk-forward parameter search: train on the head, one frozen shot on the tail
|
|
5009
5638
|
--flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
|
|
5010
5639
|
--init Scaffold a new project in the current directory
|
|
5011
5640
|
--docker Scaffold a Docker workspace for running strategies in a container
|
|
@@ -5140,10 +5769,13 @@ Simulator flags (--simulator):
|
|
|
5140
5769
|
"author": string }. The file is validated BEFORE any work; a line that does not
|
|
5141
5770
|
match the structure aborts the run with an error.
|
|
5142
5771
|
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5772
|
+
A FEASIBILITY PROBE, not a parameter search (that is --tune): the profit-harvesting
|
|
5773
|
+
machinery is off (no profit lock, inert trailing, no weighted consensus), and a
|
|
5774
|
+
fast 48-point grid of hard stop x hold x ban rule answers one question — does the
|
|
5775
|
+
feed contain a profitable corridor at all. One 5-day candle pass per idea, flood
|
|
5776
|
+
dedupe (one idea per author per direction per 8h), default-ban author filter,
|
|
5777
|
+
time-based Sharpe/Sortino, four ranking winners. The report carries the corridor
|
|
5778
|
+
share and the whitelist of the sharpe winner's ban rule as evidence.
|
|
5147
5779
|
Ideas of other symbols are filtered out — one shared feed serves any --symbol.
|
|
5148
5780
|
|
|
5149
5781
|
No output flag → print the Markdown summary to stdout. With --verbose every
|
|
@@ -5153,6 +5785,31 @@ Simulator flags (--simulator):
|
|
|
5153
5785
|
Module file ./modules/simulator.module is loaded automatically if it exists
|
|
5154
5786
|
(register your exchange there); without it CCXT Binance is used by default.
|
|
5155
5787
|
|
|
5788
|
+
Tune flags (--tune):
|
|
5789
|
+
|
|
5790
|
+
--symbol <string> Trading pair to tune (default: BTCUSDT)
|
|
5791
|
+
--exchange <string> Exchange name (default: first registered)
|
|
5792
|
+
--split <string> Train share of the feed time range, 0..1 (default: 0.7)
|
|
5793
|
+
--output <string> Output file base name (default: tune_{SYMBOL}_{TIMESTAMP})
|
|
5794
|
+
--json Save { trainSplit, train, test } to ./dump/<output>.json
|
|
5795
|
+
--markdown Save walk-forward report to ./dump/<output>.md
|
|
5796
|
+
--verbose Log simulator lifecycle callbacks to the console
|
|
5797
|
+
|
|
5798
|
+
Positional: path to an ideas .jsonl file — same shape and validation as --simulator.
|
|
5799
|
+
|
|
5800
|
+
The PARAMETER SEARCH counterpart of the --simulator probe: the full grid with the
|
|
5801
|
+
profit-harvesting machinery on — profit lock, trailing take, weighted consensus,
|
|
5802
|
+
Wilson-bound ban, both author-hit metrics (close/reach). Honesty is structural:
|
|
5803
|
+
training sees ONLY the head of the feed (--split of its time range), then the
|
|
5804
|
+
sharpe winner is frozen — point and raw author track record — and fired exactly
|
|
5805
|
+
once on the tail via Simulator.test. Nothing is trained on the tail; authors
|
|
5806
|
+
unseen in training are banned by default. The report carries the train winners,
|
|
5807
|
+
the out-of-sample result with the trade list, and the frozen author track record
|
|
5808
|
+
to hardcode for production Simulator.test.
|
|
5809
|
+
|
|
5810
|
+
Module file ./modules/tune.module is loaded automatically if it exists
|
|
5811
|
+
(register your exchange there); without it CCXT Binance is used by default.
|
|
5812
|
+
|
|
5156
5813
|
Flush flags (--flush):
|
|
5157
5814
|
|
|
5158
5815
|
One or more positional entry points. For each entry point the following
|
|
@@ -5186,7 +5843,8 @@ Module hooks (loaded automatically by each mode):
|
|
|
5186
5843
|
modules/dump.module --dump Exchange schema for candle dumps
|
|
5187
5844
|
modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
|
|
5188
5845
|
modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
|
|
5189
|
-
modules/simulator.module --simulator Exchange schema for crowd-ideas
|
|
5846
|
+
modules/simulator.module --simulator Exchange schema for the crowd-ideas feasibility probe
|
|
5847
|
+
modules/tune.module --tune Exchange schema for the walk-forward parameter search
|
|
5190
5848
|
|
|
5191
5849
|
--flush has no associated module. It only removes dump subdirectories.
|
|
5192
5850
|
|
|
@@ -5216,7 +5874,9 @@ Examples:
|
|
|
5216
5874
|
node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
|
|
5217
5875
|
node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
|
|
5218
5876
|
node ${ENTRY_PATH} --simulator --symbol BTCUSDT ./assets/tv-ideas.normalized.jsonl
|
|
5219
|
-
node ${ENTRY_PATH} --simulator --symbol BTCUSDT --json --output
|
|
5877
|
+
node ${ENTRY_PATH} --simulator --symbol BTCUSDT --json --output jun_2026_probe ./assets/tv-ideas.normalized.jsonl
|
|
5878
|
+
node ${ENTRY_PATH} --tune --symbol BTCUSDT ./assets/tv-ideas.normalized.jsonl
|
|
5879
|
+
node ${ENTRY_PATH} --tune --symbol BTCUSDT --split 0.7 --markdown --output jun_2026_tune ./assets/tv-ideas.normalized.jsonl
|
|
5220
5880
|
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
|
|
5221
5881
|
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
|
|
5222
5882
|
node ${ENTRY_PATH} --init --output my-trading-bot
|
|
@@ -5230,7 +5890,7 @@ const main$1 = async () => {
|
|
|
5230
5890
|
if (!values.help) {
|
|
5231
5891
|
return;
|
|
5232
5892
|
}
|
|
5233
|
-
process.stdout.write(`@backtest-kit/cli ${"16.
|
|
5893
|
+
process.stdout.write(`@backtest-kit/cli ${"16.6.0"}\n\n`);
|
|
5234
5894
|
process.stdout.write(HELP_TEXT);
|
|
5235
5895
|
process.exit(0);
|
|
5236
5896
|
};
|
|
@@ -5244,7 +5904,7 @@ const main = async () => {
|
|
|
5244
5904
|
if (!values.version) {
|
|
5245
5905
|
return;
|
|
5246
5906
|
}
|
|
5247
|
-
process.stdout.write(`@backtest-kit/cli ${"16.
|
|
5907
|
+
process.stdout.write(`@backtest-kit/cli ${"16.6.0"}\n`);
|
|
5248
5908
|
process.exit(0);
|
|
5249
5909
|
};
|
|
5250
5910
|
main();
|