@backtest-kit/cli 7.3.1 → 7.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import * as BacktestKit from 'backtest-kit';
3
- import { setConfig, Log, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, shutdown, listenSignal, Notification, Recent, Storage, Markdown, Report, Dump, Memory, StorageLive, StorageBacktest, RecentLive, RecentBacktest, NotificationLive, NotificationBacktest, MarkdownWriter, ReportWriter, PersistSignalAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistPartialAdapter, PersistBreakevenAdapter, PersistCandleAdapter, PersistStorageAdapter, PersistNotificationAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistIntervalAdapter, PersistMemoryAdapter, PersistRecentAdapter, listStrategySchema, overrideExchangeSchema, Backtest, Session, Cache, Interval, alignToInterval, addWalkerSchema, overrideWalkerSchema, Walker, listenDoneWalker, Live, getCandles, checkCandles, warmCandles, listenRisk, listenStrategyCommit, listenSync, listenSignalNotify, Exchange } from 'backtest-kit';
3
+ import { setConfig, Log, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, shutdown, listenSignal, Notification, Recent, Storage, Markdown, Report, Dump, State, Memory, StorageLive, StorageBacktest, RecentLive, RecentBacktest, NotificationLive, NotificationBacktest, MemoryLive, MemoryBacktest, StateLive, StateBacktest, MarkdownWriter, ReportWriter, PersistSignalAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistPartialAdapter, PersistBreakevenAdapter, PersistCandleAdapter, PersistStorageAdapter, PersistNotificationAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistIntervalAdapter, PersistMemoryAdapter, PersistRecentAdapter, PersistStateAdapter, listStrategySchema, overrideExchangeSchema, Backtest, Session, Cache, Interval, alignToInterval, addWalkerSchema, overrideWalkerSchema, Walker, listenDoneWalker, Live, getCandles, checkCandles, warmCandles, listenRisk, listenStrategyCommit, listenSync, listenSignalNotify, Exchange } from 'backtest-kit';
4
4
  import { getErrorMessage, errorData, singleshot, str, BehaviorSubject, compose, createAwaiter, execpool, queued, sleep, randomString, TIMEOUT_SYMBOL, typo, retry, trycatch, memoize, isObject } from 'functools-kit';
5
5
  import fs, { constants } from 'fs';
6
6
  import * as stackTrace from 'stack-trace';
@@ -545,6 +545,22 @@ const getArgs = singleshot(() => {
545
545
  type: "boolean",
546
546
  default: false,
547
547
  },
548
+ pnldebug: {
549
+ type: "boolean",
550
+ default: false,
551
+ },
552
+ priceopen: {
553
+ type: "string",
554
+ default: "",
555
+ },
556
+ direction: {
557
+ type: "string",
558
+ default: "",
559
+ },
560
+ minutes: {
561
+ type: "string",
562
+ default: "",
563
+ },
548
564
  init: {
549
565
  type: "boolean",
550
566
  default: false,
@@ -691,11 +707,11 @@ class SetupUtils {
691
707
  Markdown.enable();
692
708
  Report.enable();
693
709
  Dump.enable();
710
+ State.enable();
694
711
  Memory.enable();
695
712
  }
696
713
  {
697
714
  Dump.useMarkdown();
698
- Memory.usePersist();
699
715
  }
700
716
  {
701
717
  StorageLive.usePersist();
@@ -709,6 +725,18 @@ class SetupUtils {
709
725
  NotificationLive.usePersist();
710
726
  NotificationBacktest.useMemory();
711
727
  }
728
+ {
729
+ RecentLive.usePersist();
730
+ RecentBacktest.useMemory();
731
+ }
732
+ {
733
+ MemoryLive.usePersist();
734
+ MemoryBacktest.useLocal();
735
+ }
736
+ {
737
+ StateLive.usePersist();
738
+ StateBacktest.useLocal();
739
+ }
712
740
  {
713
741
  Markdown.useDummy();
714
742
  Log.useJsonl();
@@ -751,6 +779,7 @@ class SetupUtils {
751
779
  PersistIntervalAdapter.clear();
752
780
  PersistMemoryAdapter.clear();
753
781
  PersistRecentAdapter.clear();
782
+ PersistStateAdapter.clear();
754
783
  }
755
784
  {
756
785
  Dump.clear();
@@ -1904,6 +1933,9 @@ class TelegramLogicService {
1904
1933
  event,
1905
1934
  });
1906
1935
  const markdown = await this.telegramTemplateService.getTrailingTakeMarkdown(event);
1936
+ if (!markdown) {
1937
+ return;
1938
+ }
1907
1939
  await this.telegramWebService.publishNotify({
1908
1940
  symbol: event.symbol,
1909
1941
  markdown,
@@ -1914,6 +1946,9 @@ class TelegramLogicService {
1914
1946
  event,
1915
1947
  });
1916
1948
  const markdown = await this.telegramTemplateService.getTrailingStopMarkdown(event);
1949
+ if (!markdown) {
1950
+ return;
1951
+ }
1917
1952
  await this.telegramWebService.publishNotify({
1918
1953
  symbol: event.symbol,
1919
1954
  markdown,
@@ -1924,6 +1959,9 @@ class TelegramLogicService {
1924
1959
  event,
1925
1960
  });
1926
1961
  const markdown = await this.telegramTemplateService.getBreakevenMarkdown(event);
1962
+ if (!markdown) {
1963
+ return;
1964
+ }
1927
1965
  await this.telegramWebService.publishNotify({
1928
1966
  symbol: event.symbol,
1929
1967
  markdown,
@@ -1934,6 +1972,9 @@ class TelegramLogicService {
1934
1972
  event,
1935
1973
  });
1936
1974
  const markdown = await this.telegramTemplateService.getPartialProfitMarkdown(event);
1975
+ if (!markdown) {
1976
+ return;
1977
+ }
1937
1978
  await this.telegramWebService.publishNotify({
1938
1979
  symbol: event.symbol,
1939
1980
  markdown,
@@ -1944,6 +1985,9 @@ class TelegramLogicService {
1944
1985
  event,
1945
1986
  });
1946
1987
  const markdown = await this.telegramTemplateService.getPartialLossMarkdown(event);
1988
+ if (!markdown) {
1989
+ return;
1990
+ }
1947
1991
  await this.telegramWebService.publishNotify({
1948
1992
  symbol: event.symbol,
1949
1993
  markdown,
@@ -1954,6 +1998,9 @@ class TelegramLogicService {
1954
1998
  event,
1955
1999
  });
1956
2000
  const markdown = await this.telegramTemplateService.getScheduledMarkdown(event);
2001
+ if (!markdown) {
2002
+ return;
2003
+ }
1957
2004
  await this.telegramWebService.publishNotify({
1958
2005
  symbol: event.symbol,
1959
2006
  markdown,
@@ -1964,6 +2011,9 @@ class TelegramLogicService {
1964
2011
  event,
1965
2012
  });
1966
2013
  const markdown = await this.telegramTemplateService.getCancelledMarkdown(event);
2014
+ if (!markdown) {
2015
+ return;
2016
+ }
1967
2017
  await this.telegramWebService.publishNotify({
1968
2018
  symbol: event.symbol,
1969
2019
  markdown,
@@ -1974,6 +2024,9 @@ class TelegramLogicService {
1974
2024
  event,
1975
2025
  });
1976
2026
  const markdown = await this.telegramTemplateService.getOpenedMarkdown(event);
2027
+ if (!markdown) {
2028
+ return;
2029
+ }
1977
2030
  await this.telegramWebService.publishNotify({
1978
2031
  symbol: event.symbol,
1979
2032
  markdown,
@@ -1984,6 +2037,9 @@ class TelegramLogicService {
1984
2037
  event,
1985
2038
  });
1986
2039
  const markdown = await this.telegramTemplateService.getClosedMarkdown(event);
2040
+ if (!markdown) {
2041
+ return;
2042
+ }
1987
2043
  await this.telegramWebService.publishNotify({
1988
2044
  symbol: event.symbol,
1989
2045
  markdown,
@@ -1994,6 +2050,9 @@ class TelegramLogicService {
1994
2050
  event,
1995
2051
  });
1996
2052
  const markdown = await this.telegramTemplateService.getRiskMarkdown(event);
2053
+ if (!markdown) {
2054
+ return;
2055
+ }
1997
2056
  await this.telegramWebService.publishNotify({
1998
2057
  symbol: event.symbol,
1999
2058
  markdown,
@@ -2004,6 +2063,9 @@ class TelegramLogicService {
2004
2063
  event,
2005
2064
  });
2006
2065
  const markdown = await this.telegramTemplateService.getAverageBuyMarkdown(event);
2066
+ if (!markdown) {
2067
+ return;
2068
+ }
2007
2069
  await this.telegramWebService.publishNotify({
2008
2070
  symbol: event.symbol,
2009
2071
  markdown,
@@ -2014,6 +2076,9 @@ class TelegramLogicService {
2014
2076
  event,
2015
2077
  });
2016
2078
  const markdown = await this.telegramTemplateService.getSignalOpenMarkdown(event);
2079
+ if (!markdown) {
2080
+ return;
2081
+ }
2017
2082
  await this.telegramWebService.publishNotify({
2018
2083
  symbol: event.symbol,
2019
2084
  markdown,
@@ -2024,6 +2089,9 @@ class TelegramLogicService {
2024
2089
  event,
2025
2090
  });
2026
2091
  const markdown = await this.telegramTemplateService.getSignalCloseMarkdown(event);
2092
+ if (!markdown) {
2093
+ return;
2094
+ }
2027
2095
  await this.telegramWebService.publishNotify({
2028
2096
  symbol: event.symbol,
2029
2097
  markdown,
@@ -2034,6 +2102,9 @@ class TelegramLogicService {
2034
2102
  event,
2035
2103
  });
2036
2104
  const markdown = await this.telegramTemplateService.getSignalInfoMarkdown(event);
2105
+ if (!markdown) {
2106
+ return;
2107
+ }
2037
2108
  await this.telegramWebService.publishNotify({
2038
2109
  symbol: event.symbol,
2039
2110
  markdown,
@@ -2044,6 +2115,9 @@ class TelegramLogicService {
2044
2115
  event,
2045
2116
  });
2046
2117
  const markdown = await this.telegramTemplateService.getCancelScheduledMarkdown(event);
2118
+ if (!markdown) {
2119
+ return;
2120
+ }
2047
2121
  await this.telegramWebService.publishNotify({
2048
2122
  symbol: event.symbol,
2049
2123
  markdown,
@@ -2054,6 +2128,9 @@ class TelegramLogicService {
2054
2128
  event,
2055
2129
  });
2056
2130
  const markdown = await this.telegramTemplateService.getClosePendingMarkdown(event);
2131
+ if (!markdown) {
2132
+ return;
2133
+ }
2057
2134
  await this.telegramWebService.publishNotify({
2058
2135
  symbol: event.symbol,
2059
2136
  markdown,
@@ -2155,104 +2232,182 @@ const RENDER_TEMPLATE_FN = async (fileName, event, self) => {
2155
2232
  const template = await READ_TEMPLATE_FN(fileName, self);
2156
2233
  return Mustache.render(template, event);
2157
2234
  };
2235
+ const GET_TELEGRAM_CONFIG_FN = async (self) => {
2236
+ const exports = await self.configConnectionService.loadConfig("telegram.config");
2237
+ if (!exports) {
2238
+ return null;
2239
+ }
2240
+ return "default" in exports
2241
+ ? exports.default
2242
+ : exports;
2243
+ };
2158
2244
  class TelegramTemplateService {
2159
2245
  constructor() {
2160
2246
  this.loggerService = inject(TYPES.loggerService);
2161
2247
  this.resolveService = inject(TYPES.resolveService);
2248
+ this.configConnectionService = inject(TYPES.configConnectionService);
2249
+ this.getTelegramAdapter = singleshot(async () => {
2250
+ this.loggerService.log("telegramTemplateService getTelegramAdapter");
2251
+ return await GET_TELEGRAM_CONFIG_FN(this);
2252
+ });
2162
2253
  this.getTrailingTakeMarkdown = async (event) => {
2163
2254
  this.loggerService.log("telegramTemplateService getTrailingTakeMarkdown", {
2164
2255
  event,
2165
2256
  });
2257
+ const adapter = await this.getTelegramAdapter();
2258
+ if (adapter?.getTrailingTakeMarkdown) {
2259
+ return await adapter.getTrailingTakeMarkdown(event);
2260
+ }
2166
2261
  return await RENDER_TEMPLATE_FN("trailing-take.mustache", event, this);
2167
2262
  };
2168
2263
  this.getTrailingStopMarkdown = async (event) => {
2169
2264
  this.loggerService.log("telegramTemplateService getTrailingStopMarkdown", {
2170
2265
  event,
2171
2266
  });
2267
+ const adapter = await this.getTelegramAdapter();
2268
+ if (adapter?.getTrailingStopMarkdown) {
2269
+ return await adapter.getTrailingStopMarkdown(event);
2270
+ }
2172
2271
  return await RENDER_TEMPLATE_FN("trailing-stop.mustache", event, this);
2173
2272
  };
2174
2273
  this.getBreakevenMarkdown = async (event) => {
2175
2274
  this.loggerService.log("telegramTemplateService getBreakevenMarkdown", {
2176
2275
  event,
2177
2276
  });
2277
+ const adapter = await this.getTelegramAdapter();
2278
+ if (adapter?.getBreakevenMarkdown) {
2279
+ return await adapter.getBreakevenMarkdown(event);
2280
+ }
2178
2281
  return await RENDER_TEMPLATE_FN("breakeven.mustache", event, this);
2179
2282
  };
2180
2283
  this.getPartialProfitMarkdown = async (event) => {
2181
2284
  this.loggerService.log("telegramTemplateService getPartialProfitMarkdown", {
2182
2285
  event,
2183
2286
  });
2287
+ const adapter = await this.getTelegramAdapter();
2288
+ if (adapter?.getPartialProfitMarkdown) {
2289
+ return await adapter.getPartialProfitMarkdown(event);
2290
+ }
2184
2291
  return await RENDER_TEMPLATE_FN("partial-profit.mustache", event, this);
2185
2292
  };
2186
2293
  this.getPartialLossMarkdown = async (event) => {
2187
2294
  this.loggerService.log("telegramTemplateService getPartialLossMarkdown", {
2188
2295
  event,
2189
2296
  });
2297
+ const adapter = await this.getTelegramAdapter();
2298
+ if (adapter?.getPartialLossMarkdown) {
2299
+ return await adapter.getPartialLossMarkdown(event);
2300
+ }
2190
2301
  return await RENDER_TEMPLATE_FN("partial-loss.mustache", event, this);
2191
2302
  };
2192
2303
  this.getScheduledMarkdown = async (event) => {
2193
2304
  this.loggerService.log("telegramTemplateService getScheduledMarkdown", {
2194
2305
  event,
2195
2306
  });
2307
+ const adapter = await this.getTelegramAdapter();
2308
+ if (adapter?.getScheduledMarkdown) {
2309
+ return await adapter.getScheduledMarkdown(event);
2310
+ }
2196
2311
  return await RENDER_TEMPLATE_FN("scheduled.mustache", event, this);
2197
2312
  };
2198
2313
  this.getCancelledMarkdown = async (event) => {
2199
2314
  this.loggerService.log("telegramTemplateService getCancelledMarkdown", {
2200
2315
  event,
2201
2316
  });
2317
+ const adapter = await this.getTelegramAdapter();
2318
+ if (adapter?.getCancelledMarkdown) {
2319
+ return await adapter.getCancelledMarkdown(event);
2320
+ }
2202
2321
  return await RENDER_TEMPLATE_FN("cancelled.mustache", event, this);
2203
2322
  };
2204
2323
  this.getOpenedMarkdown = async (event) => {
2205
2324
  this.loggerService.log("telegramTemplateService getOpenedMarkdown", {
2206
2325
  event,
2207
2326
  });
2327
+ const adapter = await this.getTelegramAdapter();
2328
+ if (adapter?.getOpenedMarkdown) {
2329
+ return await adapter.getOpenedMarkdown(event);
2330
+ }
2208
2331
  return await RENDER_TEMPLATE_FN("opened.mustache", event, this);
2209
2332
  };
2210
2333
  this.getClosedMarkdown = async (event) => {
2211
2334
  this.loggerService.log("telegramTemplateService getClosedMarkdown", {
2212
2335
  event,
2213
2336
  });
2337
+ const adapter = await this.getTelegramAdapter();
2338
+ if (adapter?.getClosedMarkdown) {
2339
+ return await adapter.getClosedMarkdown(event);
2340
+ }
2214
2341
  return await RENDER_TEMPLATE_FN("closed.mustache", event, this);
2215
2342
  };
2216
2343
  this.getRiskMarkdown = async (event) => {
2217
2344
  this.loggerService.log("telegramTemplateService getRiskMarkdown", {
2218
2345
  event,
2219
2346
  });
2347
+ const adapter = await this.getTelegramAdapter();
2348
+ if (adapter?.getRiskMarkdown) {
2349
+ return await adapter.getRiskMarkdown(event);
2350
+ }
2220
2351
  return await RENDER_TEMPLATE_FN("risk.mustache", event, this);
2221
2352
  };
2222
2353
  this.getAverageBuyMarkdown = async (event) => {
2223
2354
  this.loggerService.log("telegramTemplateService getAverageBuyMarkdown", {
2224
2355
  event,
2225
2356
  });
2357
+ const adapter = await this.getTelegramAdapter();
2358
+ if (adapter?.getAverageBuyMarkdown) {
2359
+ return await adapter.getAverageBuyMarkdown(event);
2360
+ }
2226
2361
  return await RENDER_TEMPLATE_FN("average-buy.mustache", event, this);
2227
2362
  };
2228
2363
  this.getSignalOpenMarkdown = async (event) => {
2229
2364
  this.loggerService.log("telegramTemplateService getSignalOpenMarkdown", {
2230
2365
  event,
2231
2366
  });
2367
+ const adapter = await this.getTelegramAdapter();
2368
+ if (adapter?.getSignalOpenMarkdown) {
2369
+ return await adapter.getSignalOpenMarkdown(event);
2370
+ }
2232
2371
  return await RENDER_TEMPLATE_FN("signal-open.mustache", event, this);
2233
2372
  };
2234
2373
  this.getSignalCloseMarkdown = async (event) => {
2235
2374
  this.loggerService.log("telegramTemplateService getSignalCloseMarkdown", {
2236
2375
  event,
2237
2376
  });
2377
+ const adapter = await this.getTelegramAdapter();
2378
+ if (adapter?.getSignalCloseMarkdown) {
2379
+ return await adapter.getSignalCloseMarkdown(event);
2380
+ }
2238
2381
  return await RENDER_TEMPLATE_FN("signal-close.mustache", event, this);
2239
2382
  };
2240
2383
  this.getCancelScheduledMarkdown = async (event) => {
2241
2384
  this.loggerService.log("telegramTemplateService getCancelScheduledMarkdown", {
2242
2385
  event,
2243
2386
  });
2387
+ const adapter = await this.getTelegramAdapter();
2388
+ if (adapter?.getCancelScheduledMarkdown) {
2389
+ return await adapter.getCancelScheduledMarkdown(event);
2390
+ }
2244
2391
  return await RENDER_TEMPLATE_FN("cancel-scheduled.mustache", event, this);
2245
2392
  };
2246
2393
  this.getClosePendingMarkdown = async (event) => {
2247
2394
  this.loggerService.log("telegramTemplateService getClosePendingMarkdown", {
2248
2395
  event,
2249
2396
  });
2397
+ const adapter = await this.getTelegramAdapter();
2398
+ if (adapter?.getClosePendingMarkdown) {
2399
+ return await adapter.getClosePendingMarkdown(event);
2400
+ }
2250
2401
  return await RENDER_TEMPLATE_FN("close-pending.mustache", event, this);
2251
2402
  };
2252
2403
  this.getSignalInfoMarkdown = async (event) => {
2253
2404
  this.loggerService.log("telegramTemplateService getSignalInfoMarkdown", {
2254
2405
  event,
2255
2406
  });
2407
+ const adapter = await this.getTelegramAdapter();
2408
+ if (adapter?.getSignalInfoMarkdown) {
2409
+ return await adapter.getSignalInfoMarkdown(event);
2410
+ }
2256
2411
  return await RENDER_TEMPLATE_FN("signal-info.mustache", event, this);
2257
2412
  };
2258
2413
  }
@@ -2810,14 +2965,14 @@ const cli = {
2810
2965
  };
2811
2966
  init();
2812
2967
 
2813
- const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "flush", "init", "help", "version"];
2968
+ const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "pnldebug", "flush", "init", "help", "version"];
2814
2969
  const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
2815
2970
  const HELP_TEXT$1 = `
2816
2971
  Example:
2817
2972
 
2818
2973
  node ${ENTRY_PATH$1} --help
2819
2974
  `.trimStart();
2820
- const main$d = async () => {
2975
+ const main$e = async () => {
2821
2976
  if (!getEntry(import.meta.url)) {
2822
2977
  return;
2823
2978
  }
@@ -2825,14 +2980,14 @@ const main$d = async () => {
2825
2980
  if (MODES.some((mode) => values[mode])) {
2826
2981
  return;
2827
2982
  }
2828
- process.stdout.write(`@backtest-kit/cli ${"7.3.1"}\n`);
2983
+ process.stdout.write(`@backtest-kit/cli ${"7.5.0"}\n`);
2829
2984
  process.stdout.write("\n");
2830
2985
  process.stdout.write(`Run with --help to see available commands.\n`);
2831
2986
  process.stdout.write("\n");
2832
2987
  process.stdout.write(HELP_TEXT$1);
2833
2988
  process.exit(0);
2834
2989
  };
2835
- main$d();
2990
+ main$e();
2836
2991
 
2837
2992
  const notifyShutdown = singleshot(async () => {
2838
2993
  console.log("Graceful shutdown initiated. Press Ctrl+C again to force quit.");
@@ -2848,7 +3003,7 @@ const flush = async (entryPoint) => {
2848
3003
  console.log(`Removed: ${target}`);
2849
3004
  }
2850
3005
  };
2851
- const main$c = async () => {
3006
+ const main$d = async () => {
2852
3007
  if (!getEntry(import.meta.url)) {
2853
3008
  return;
2854
3009
  }
@@ -2865,7 +3020,7 @@ const main$c = async () => {
2865
3020
  }
2866
3021
  process.exit(0);
2867
3022
  };
2868
- main$c();
3023
+ main$d();
2869
3024
 
2870
3025
  const BEFORE_EXIT_FN$5 = singleshot(async () => {
2871
3026
  process.off("SIGINT", BEFORE_EXIT_FN$5);
@@ -2887,7 +3042,7 @@ const BEFORE_EXIT_FN$5 = singleshot(async () => {
2887
3042
  const listenGracefulShutdown$5 = singleshot(() => {
2888
3043
  process.on("SIGINT", BEFORE_EXIT_FN$5);
2889
3044
  });
2890
- const main$b = async () => {
3045
+ const main$c = async () => {
2891
3046
  if (!getEntry(import.meta.url)) {
2892
3047
  return;
2893
3048
  }
@@ -2902,7 +3057,7 @@ const main$b = async () => {
2902
3057
  await cli.backtestMainService.connect();
2903
3058
  listenGracefulShutdown$5();
2904
3059
  };
2905
- main$b();
3060
+ main$c();
2906
3061
 
2907
3062
  const BEFORE_EXIT_FN$4 = singleshot(async () => {
2908
3063
  process.off("SIGINT", BEFORE_EXIT_FN$4);
@@ -2920,7 +3075,7 @@ const BEFORE_EXIT_FN$4 = singleshot(async () => {
2920
3075
  const listenGracefulShutdown$4 = singleshot(() => {
2921
3076
  process.on("SIGINT", BEFORE_EXIT_FN$4);
2922
3077
  });
2923
- const main$a = async () => {
3078
+ const main$b = async () => {
2924
3079
  if (!getEntry(import.meta.url)) {
2925
3080
  return;
2926
3081
  }
@@ -2936,7 +3091,7 @@ const main$a = async () => {
2936
3091
  listenGracefulShutdown$4();
2937
3092
  await cli.walkerMainService.connect();
2938
3093
  };
2939
- main$a();
3094
+ main$b();
2940
3095
 
2941
3096
  const BEFORE_EXIT_FN$3 = singleshot(async () => {
2942
3097
  process.off("SIGINT", BEFORE_EXIT_FN$3);
@@ -2957,7 +3112,7 @@ const BEFORE_EXIT_FN$3 = singleshot(async () => {
2957
3112
  const listenGracefulShutdown$3 = singleshot(() => {
2958
3113
  process.on("SIGINT", BEFORE_EXIT_FN$3);
2959
3114
  });
2960
- const main$9 = async () => {
3115
+ const main$a = async () => {
2961
3116
  if (!getEntry(import.meta.url)) {
2962
3117
  return;
2963
3118
  }
@@ -2968,7 +3123,7 @@ const main$9 = async () => {
2968
3123
  cli.paperMainService.connect();
2969
3124
  listenGracefulShutdown$3();
2970
3125
  };
2971
- main$9();
3126
+ main$a();
2972
3127
 
2973
3128
  const BEFORE_EXIT_FN$2 = singleshot(async () => {
2974
3129
  process.off("SIGINT", BEFORE_EXIT_FN$2);
@@ -2989,7 +3144,7 @@ const BEFORE_EXIT_FN$2 = singleshot(async () => {
2989
3144
  const listenGracefulShutdown$2 = singleshot(() => {
2990
3145
  process.on("SIGINT", BEFORE_EXIT_FN$2);
2991
3146
  });
2992
- const main$8 = async () => {
3147
+ const main$9 = async () => {
2993
3148
  if (!getEntry(import.meta.url)) {
2994
3149
  return;
2995
3150
  }
@@ -3000,7 +3155,7 @@ const main$8 = async () => {
3000
3155
  await cli.liveMainService.connect();
3001
3156
  listenGracefulShutdown$2();
3002
3157
  };
3003
- main$8();
3158
+ main$9();
3004
3159
 
3005
3160
  const BEFORE_EXIT_FN$1 = singleshot(async () => {
3006
3161
  process.off("SIGINT", BEFORE_EXIT_FN$1);
@@ -3010,7 +3165,7 @@ const BEFORE_EXIT_FN$1 = singleshot(async () => {
3010
3165
  const listenGracefulShutdown$1 = singleshot(() => {
3011
3166
  process.on("SIGINT", BEFORE_EXIT_FN$1);
3012
3167
  });
3013
- const main$7 = async () => {
3168
+ const main$8 = async () => {
3014
3169
  if (!getEntry(import.meta.url)) {
3015
3170
  return;
3016
3171
  }
@@ -3020,7 +3175,7 @@ const main$7 = async () => {
3020
3175
  }
3021
3176
  listenGracefulShutdown$1();
3022
3177
  };
3023
- main$7();
3178
+ main$8();
3024
3179
 
3025
3180
  const BEFORE_EXIT_FN = singleshot(async () => {
3026
3181
  process.off("SIGINT", BEFORE_EXIT_FN);
@@ -3030,7 +3185,7 @@ const BEFORE_EXIT_FN = singleshot(async () => {
3030
3185
  const listenGracefulShutdown = singleshot(() => {
3031
3186
  process.on("SIGINT", BEFORE_EXIT_FN);
3032
3187
  });
3033
- const main$6 = async () => {
3188
+ const main$7 = async () => {
3034
3189
  if (!getEntry(import.meta.url)) {
3035
3190
  return;
3036
3191
  }
@@ -3040,7 +3195,7 @@ const main$6 = async () => {
3040
3195
  }
3041
3196
  listenGracefulShutdown();
3042
3197
  };
3043
- main$6();
3198
+ main$7();
3044
3199
 
3045
3200
  const EXTRACT_ROWS_FN = (plots, schema) => {
3046
3201
  const keys = Object.keys(schema);
@@ -3062,7 +3217,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
3062
3217
  }
3063
3218
  return rows;
3064
3219
  };
3065
- const main$5 = async () => {
3220
+ const main$6 = async () => {
3066
3221
  if (!getEntry(import.meta.url)) {
3067
3222
  return;
3068
3223
  }
@@ -3142,9 +3297,9 @@ const main$5 = async () => {
3142
3297
  console.log(await toMarkdown(signalId, plots, signalSchema));
3143
3298
  process.exit(0);
3144
3299
  };
3145
- main$5();
3300
+ main$6();
3146
3301
 
3147
- const main$4 = async () => {
3302
+ const main$5 = async () => {
3148
3303
  if (!getEntry(import.meta.url)) {
3149
3304
  return;
3150
3305
  }
@@ -3179,9 +3334,9 @@ const main$4 = async () => {
3179
3334
  };
3180
3335
  process.on("SIGINT", beforeExit);
3181
3336
  };
3182
- main$4();
3337
+ main$5();
3183
3338
 
3184
- const main$3 = async () => {
3339
+ const main$4 = async () => {
3185
3340
  if (!getEntry(import.meta.url)) {
3186
3341
  return;
3187
3342
  }
@@ -3242,6 +3397,102 @@ const main$3 = async () => {
3242
3397
  console.log(JSON.stringify(candles, null, 2));
3243
3398
  process.exit(0);
3244
3399
  };
3400
+ main$4();
3401
+
3402
+ const main$3 = async () => {
3403
+ if (!getEntry(import.meta.url)) {
3404
+ return;
3405
+ }
3406
+ const { values } = getArgs();
3407
+ if (!values.pnldebug) {
3408
+ return;
3409
+ }
3410
+ await cli.moduleConnectionService.loadModule("./pnldebug.module");
3411
+ {
3412
+ await cli.exchangeSchemaService.addSchema();
3413
+ await cli.symbolSchemaService.addSchema();
3414
+ }
3415
+ const [defaultExchangeName = null] = await listExchangeSchema();
3416
+ const exchangeName = values.exchange || defaultExchangeName?.exchangeName;
3417
+ const symbol = values.symbol || "BTCUSDT";
3418
+ const priceOpenStr = values.priceopen;
3419
+ if (!priceOpenStr) {
3420
+ console.error("Error: --priceopen is required");
3421
+ process.exit(1);
3422
+ }
3423
+ const priceOpen = parseFloat(priceOpenStr);
3424
+ if (isNaN(priceOpen)) {
3425
+ console.error(`Error: --priceopen must be a number, got: ${priceOpenStr}`);
3426
+ process.exit(1);
3427
+ }
3428
+ const direction = (values.direction || "long").toLowerCase();
3429
+ if (direction !== "long" && direction !== "short") {
3430
+ console.error(`Error: --direction must be 'long' or 'short', got: ${direction}`);
3431
+ process.exit(1);
3432
+ }
3433
+ const whenStr = values.when || Date.now().toString();
3434
+ const whenStamp = Date.parse(whenStr);
3435
+ const when = isNaN(whenStamp) ? new Date() : new Date(whenStamp);
3436
+ const timestamp = alignToInterval(when, "1m").getTime();
3437
+ const minutesStr = values.minutes || "60";
3438
+ const minutesNum = parseInt(minutesStr);
3439
+ const minutes = isNaN(minutesNum) ? 60 : minutesNum;
3440
+ const candles = await Exchange.getRawCandles(symbol, "1m", { exchangeName }, minutes, undefined, timestamp);
3441
+ if (candles.length === 0) {
3442
+ console.error("Error: no candles returned for the given parameters");
3443
+ process.exit(1);
3444
+ }
3445
+ let peak = 0;
3446
+ let drawdown = 0;
3447
+ const rows = candles.map((c, i) => {
3448
+ const pnl = direction === "short"
3449
+ ? (priceOpen - c.close) / priceOpen * 100
3450
+ : (c.close - priceOpen) / priceOpen * 100;
3451
+ if (pnl > peak)
3452
+ peak = pnl;
3453
+ if (pnl < drawdown)
3454
+ drawdown = pnl;
3455
+ return { min: i + 1, timestamp: c.timestamp, close: c.close, pnl, peak, drawdown };
3456
+ });
3457
+ const dumpName = values.output || `${symbol}_${direction}_${priceOpen}_${timestamp}`;
3458
+ const dumpDir = join(process.cwd(), "dump");
3459
+ if (values.json) {
3460
+ const filePath = resolve(dumpDir, `${dumpName}.json`);
3461
+ await mkdir(dumpDir, { recursive: true });
3462
+ await writeFile(filePath, JSON.stringify(rows, null, 2), "utf-8");
3463
+ console.log(`Saved: ${filePath}`);
3464
+ process.exit(0);
3465
+ }
3466
+ if (values.jsonl) {
3467
+ const filePath = resolve(dumpDir, `${dumpName}.jsonl`);
3468
+ await mkdir(dumpDir, { recursive: true });
3469
+ await writeFile(filePath, rows.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
3470
+ console.log(`Saved: ${filePath}`);
3471
+ process.exit(0);
3472
+ }
3473
+ if (values.markdown) {
3474
+ const header = `| min | timestamp | close | pnl% | peak% | drawdown% |\n| --- | --- | --- | --- | --- | --- |`;
3475
+ const mdRows = rows.map((r) => `| ${r.min} | ${new Date(r.timestamp).toISOString()} | ${r.close.toFixed(2)} | ${(r.pnl >= 0 ? "+" : "") + r.pnl.toFixed(2)}% | +${r.peak.toFixed(2)}% | ${r.drawdown.toFixed(2)}% |`);
3476
+ const filePath = resolve(dumpDir, `${dumpName}.md`);
3477
+ await mkdir(dumpDir, { recursive: true });
3478
+ await writeFile(filePath, [header, ...mdRows].join("\n"), "utf-8");
3479
+ console.log(`Saved: ${filePath}`);
3480
+ process.exit(0);
3481
+ }
3482
+ console.log(`Symbol: ${symbol} | Direction: ${direction} | PriceOpen: ${priceOpen} | From: ${new Date(timestamp).toISOString()} | Minutes: ${minutes}\n`);
3483
+ console.log(`${"min".padStart(5)} | ${"timestamp".padEnd(24)} | ${"close".padStart(12)} | ${"pnl%".padStart(8)} | ${"peak%".padStart(8)} | ${"drawdown%".padStart(10)}`);
3484
+ console.log("-".repeat(83));
3485
+ for (const r of rows) {
3486
+ const min = String(r.min).padStart(5);
3487
+ const ts = new Date(r.timestamp).toISOString().padEnd(24);
3488
+ const close = r.close.toFixed(2).padStart(12);
3489
+ const pnlStr = (r.pnl >= 0 ? "+" : "") + r.pnl.toFixed(2) + "%";
3490
+ const peakStr = "+" + r.peak.toFixed(2) + "%";
3491
+ const drawdownStr = r.drawdown.toFixed(2) + "%";
3492
+ console.log(`${min} | ${ts} | ${close} | ${pnlStr.padStart(8)} | ${peakStr.padStart(8)} | ${drawdownStr.padStart(10)}`);
3493
+ }
3494
+ process.exit(0);
3495
+ };
3245
3496
  main$3();
3246
3497
 
3247
3498
  const __filename = fileURLToPath(import.meta.url);
@@ -3360,6 +3611,7 @@ Modes:
3360
3611
  --pine <entry> Execute a local .pine indicator file
3361
3612
  --editor Open the Pine Script visual editor in the browser
3362
3613
  --dump Fetch and save raw OHLCV candles
3614
+ --pnldebug Simulate PnL per minute for a given entry price and direction
3363
3615
  --flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
3364
3616
  --init Scaffold a new project in the current directory
3365
3617
  --help Print this help message
@@ -3432,6 +3684,21 @@ Candle dump flags (--dump):
3432
3684
 
3433
3685
  Module file ./modules/dump.module is loaded automatically if it exists.
3434
3686
 
3687
+ PnL debug flags (--pnldebug):
3688
+
3689
+ --symbol <string> Trading pair (default: BTCUSDT)
3690
+ --priceopen <number> Entry price (required)
3691
+ --direction <string> Position direction: long or short (default: long)
3692
+ --when <string> Start timestamp — ISO 8601 or Unix ms (default: now)
3693
+ --minutes <string> Number of 1m candles to simulate (default: 60)
3694
+ --exchange <string> Exchange name (default: first registered)
3695
+ --output <string> Output file base name (default: {SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP})
3696
+ --json Save as JSON array to ./dump/<output>.json
3697
+ --jsonl Save as JSONL to ./dump/<output>.jsonl
3698
+ --markdown Save as Markdown table to ./dump/<output>.md
3699
+
3700
+ Module file ./modules/pnldebug.module is loaded automatically if it exists.
3701
+
3435
3702
  Flush flags (--flush):
3436
3703
 
3437
3704
  One or more positional entry points. For each entry point the following
@@ -3454,6 +3721,7 @@ Module hooks (loaded automatically by each mode):
3454
3721
  modules/pine.module --pine Exchange schema for PineScript runs
3455
3722
  modules/editor.module --editor Exchange schema for the visual Pine editor
3456
3723
  modules/dump.module --dump Exchange schema for candle dumps
3724
+ modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
3457
3725
 
3458
3726
  --flush has no associated module. It only removes dump subdirectories.
3459
3727
 
@@ -3477,6 +3745,8 @@ Examples:
3477
3745
  node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
3478
3746
  node ${ENTRY_PATH} --editor
3479
3747
  node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
3748
+ node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
3749
+ node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
3480
3750
  node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
3481
3751
  node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
3482
3752
  node ${ENTRY_PATH} --init --output my-trading-bot
@@ -3489,7 +3759,7 @@ const main$1 = async () => {
3489
3759
  if (!values.help) {
3490
3760
  return;
3491
3761
  }
3492
- process.stdout.write(`@backtest-kit/cli ${"7.3.1"}\n\n`);
3762
+ process.stdout.write(`@backtest-kit/cli ${"7.5.0"}\n\n`);
3493
3763
  process.stdout.write(HELP_TEXT);
3494
3764
  process.exit(0);
3495
3765
  };
@@ -3503,7 +3773,7 @@ const main = async () => {
3503
3773
  if (!values.version) {
3504
3774
  return;
3505
3775
  }
3506
- process.stdout.write(`@backtest-kit/cli ${"7.3.1"}\n`);
3776
+ process.stdout.write(`@backtest-kit/cli ${"7.5.0"}\n`);
3507
3777
  process.exit(0);
3508
3778
  };
3509
3779
  main();