@backtest-kit/cli 0.0.2 → 0.0.3

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 CHANGED
@@ -129,19 +129,19 @@ npm start -- --symbol BTCUSDT --ui
129
129
 
130
130
  ## 🎛️ CLI Flags
131
131
 
132
- | Flag | Type | Default | Description |
133
- |---------------|---------|----------------------|-------------------------------------------|
134
- | `--backtest` | boolean | `false` | Run historical backtest |
135
- | `--paper` | boolean | `false` | Paper trading (live prices, no orders) |
136
- | `--live` | boolean | `false` | Run live trading |
137
- | `--ui` | boolean | `false` | Start web UI dashboard |
138
- | `--telegram` | boolean | `false` | Enable Telegram notifications |
139
- | `--verbose` | boolean | `false` | Log each candle fetch |
140
- | `--symbol` | string | `"BTCUSDT"` | Trading pair |
141
- | `--strategy` | string | first registered | Strategy name |
142
- | `--exchange` | string | first registered | Exchange name |
143
- | `--frame` | string | first registered | Backtest frame name |
144
- | `--cache` | string | `"1m, 15m, 30m, 4h"` | Intervals to pre-cache before backtest |
132
+ | Flag | Type | Description |
133
+ |---------------|---------|--------------------------------------------------------------------|
134
+ | `--backtest` | boolean | Run historical backtest (default: `false`) |
135
+ | `--paper` | boolean | Paper trading (live prices, no orders) (default: `false`) |
136
+ | `--live` | boolean | Run live trading (default: `false`) |
137
+ | `--ui` | boolean | Start web UI dashboard (default: `false`) |
138
+ | `--telegram` | boolean | Enable Telegram notifications (default: `false`) |
139
+ | `--verbose` | boolean | Log each candle fetch (default: `false`) |
140
+ | `--symbol` | string | Trading pair (default: `"BTCUSDT"`) |
141
+ | `--strategy` | string | Strategy name (default: first registered) |
142
+ | `--exchange` | string | Exchange name (default: first registered) |
143
+ | `--frame` | string | Backtest frame name (default: first registered) |
144
+ | `--cache` | string | Intervals to pre-cache before backtest (default: `"1m, 15m, 30m, 4h"`) |
145
145
 
146
146
  **Positional argument (required):** path to your strategy entry point file (set once in `package.json` scripts).
147
147
 
@@ -393,6 +393,89 @@ When your strategy module does not register an exchange, frame, or strategy name
393
393
 
394
394
  > **Note:** The default exchange schema **does not support order book fetching in backtest mode**. If your strategy calls `getOrderBook()` during backtest, you must register a custom exchange schema with your own snapshot storage.
395
395
 
396
+
397
+ ## 🔧 Programmatic API
398
+
399
+ In addition to the CLI, `@backtest-kit/cli` can be used as a library — call `run()` directly from your own script without spawning a child process or parsing CLI flags.
400
+
401
+ ### `run(mode, args)`
402
+
403
+ ```typescript
404
+ import { run } from '@backtest-kit/cli';
405
+
406
+ await run(mode, args);
407
+ ```
408
+
409
+ | Parameter | Description |
410
+ |-----------|-------------|
411
+ | `mode` | `"backtest" \| "paper" \| "live"` — Execution mode |
412
+ | `args` | Mode-specific options (all optional — same defaults as CLI) |
413
+
414
+ `run()` can be called **only once per process**. A second call throws `"Should be called only once"`.
415
+
416
+ ### Payload fields
417
+
418
+ **Backtest** (`mode: "backtest"`):
419
+
420
+ | Field | Type | Description |
421
+ |-------|------|-------------|
422
+ | `entryPoint` | `string` | Path to strategy entry point file |
423
+ | `symbol` | `string` | Trading pair (default: `"BTCUSDT"`) |
424
+ | `strategy` | `string` | Strategy name (default: first registered) |
425
+ | `exchange` | `string` | Exchange name (default: first registered) |
426
+ | `frame` | `string` | Frame name (default: first registered) |
427
+ | `cacheList` | `CandleInterval[]` | Intervals to pre-cache (default: `["1m","15m","30m","1h","4h"]`) |
428
+ | `verbose` | `boolean` | Log each candle fetch (default: `false`) |
429
+
430
+ **Paper** and **Live** (`mode: "paper"` / `mode: "live"`):
431
+
432
+ | Field | Type | Description |
433
+ |-------|------|-------------|
434
+ | `entryPoint` | `string` | Path to strategy entry point file |
435
+ | `symbol` | `string` | Trading pair (default: `"BTCUSDT"`) |
436
+ | `strategy` | `string` | Strategy name (default: first registered) |
437
+ | `exchange` | `string` | Exchange name (default: first registered) |
438
+ | `verbose` | `boolean` | Log each candle fetch (default: `false`) |
439
+
440
+ ### Examples
441
+
442
+ **Backtest:**
443
+
444
+ ```typescript
445
+ import { run } from '@backtest-kit/cli';
446
+
447
+ await run('backtest', {
448
+ entryPoint: './src/index.mjs',
449
+ symbol: 'ETHUSDT',
450
+ frame: 'feb-2024',
451
+ cacheList: ['1m', '15m', '1h'],
452
+ verbose: true,
453
+ });
454
+ ```
455
+
456
+ **Paper trading:**
457
+
458
+ ```typescript
459
+ import { run } from '@backtest-kit/cli';
460
+
461
+ await run('paper', {
462
+ entryPoint: './src/index.mjs',
463
+ symbol: 'BTCUSDT',
464
+ });
465
+ ```
466
+
467
+ **Live trading:**
468
+
469
+ ```typescript
470
+ import { run } from '@backtest-kit/cli';
471
+
472
+ await run('live', {
473
+ entryPoint: './src/index.mjs',
474
+ symbol: 'BTCUSDT',
475
+ verbose: true,
476
+ });
477
+ ```
478
+
396
479
  ## 💡 Why Use @backtest-kit/cli?
397
480
 
398
481
  Instead of writing infrastructure code for every project:
package/build/index.cjs CHANGED
@@ -536,8 +536,8 @@ class BacktestMainService {
536
536
  });
537
537
  notifyFinish();
538
538
  });
539
- this.init = functoolsKit.singleshot(async () => {
540
- this.loggerService.log("backtestMainService init");
539
+ this.connect = functoolsKit.singleshot(async () => {
540
+ this.loggerService.log("backtestMainService connect");
541
541
  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)))) {
542
542
  return;
543
543
  }
@@ -613,8 +613,8 @@ class LiveMainService {
613
613
  });
614
614
  notifyFinish();
615
615
  });
616
- this.init = functoolsKit.singleshot(async () => {
617
- this.loggerService.log("liveMainService init");
616
+ this.connect = functoolsKit.singleshot(async () => {
617
+ this.loggerService.log("liveMainService connect");
618
618
  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)))) {
619
619
  return;
620
620
  }
@@ -683,8 +683,8 @@ class PaperMainService {
683
683
  });
684
684
  notifyFinish();
685
685
  });
686
- this.init = functoolsKit.singleshot(async () => {
687
- this.loggerService.log("paperMainService init");
686
+ this.connect = functoolsKit.singleshot(async () => {
687
+ this.loggerService.log("paperMainService connect");
688
688
  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)))) {
689
689
  return;
690
690
  }
@@ -1845,6 +1845,9 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
1845
1845
  frameName,
1846
1846
  });
1847
1847
  });
1848
+ const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
1849
+ process.on("SIGINT", BEFORE_EXIT_FN$4);
1850
+ });
1848
1851
  const main$4 = async () => {
1849
1852
  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)))) {
1850
1853
  return;
@@ -1853,7 +1856,8 @@ const main$4 = async () => {
1853
1856
  if (!values.backtest) {
1854
1857
  return;
1855
1858
  }
1856
- process.on("SIGINT", BEFORE_EXIT_FN$4);
1859
+ await cli.backtestMainService.connect();
1860
+ listenGracefulShutdown$4();
1857
1861
  };
1858
1862
  main$4();
1859
1863
 
@@ -1873,6 +1877,9 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
1873
1877
  strategyName,
1874
1878
  });
1875
1879
  });
1880
+ const listenGracefulShutdown$3 = functoolsKit.singleshot(() => {
1881
+ process.on("SIGINT", BEFORE_EXIT_FN$3);
1882
+ });
1876
1883
  const main$3 = async () => {
1877
1884
  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)))) {
1878
1885
  return;
@@ -1881,7 +1888,8 @@ const main$3 = async () => {
1881
1888
  if (!values.paper) {
1882
1889
  return;
1883
1890
  }
1884
- process.on("SIGINT", BEFORE_EXIT_FN$3);
1891
+ cli.paperMainService.connect();
1892
+ listenGracefulShutdown$3();
1885
1893
  };
1886
1894
  main$3();
1887
1895
 
@@ -1902,6 +1910,9 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
1902
1910
  });
1903
1911
  backtestKit.listenDoneLive(cli.liveProviderService.disable);
1904
1912
  });
1913
+ const listenGracefulShutdown$2 = functoolsKit.singleshot(() => {
1914
+ process.on("SIGINT", BEFORE_EXIT_FN$2);
1915
+ });
1905
1916
  const main$2 = async () => {
1906
1917
  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)))) {
1907
1918
  return;
@@ -1910,7 +1921,8 @@ const main$2 = async () => {
1910
1921
  if (!values.live) {
1911
1922
  return;
1912
1923
  }
1913
- process.on("SIGINT", BEFORE_EXIT_FN$2);
1924
+ await cli.liveMainService.connect();
1925
+ listenGracefulShutdown$2();
1914
1926
  };
1915
1927
  main$2();
1916
1928
 
@@ -1919,6 +1931,9 @@ const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
1919
1931
  notifyShutdown();
1920
1932
  cli.frontendProviderService.disable();
1921
1933
  });
1934
+ const listenGracefulShutdown$1 = functoolsKit.singleshot(() => {
1935
+ process.on("SIGINT", BEFORE_EXIT_FN$1);
1936
+ });
1922
1937
  const main$1 = async () => {
1923
1938
  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)))) {
1924
1939
  return;
@@ -1927,7 +1942,7 @@ const main$1 = async () => {
1927
1942
  if (!values.ui) {
1928
1943
  return;
1929
1944
  }
1930
- process.on("SIGINT", BEFORE_EXIT_FN$1);
1945
+ listenGracefulShutdown$1();
1931
1946
  };
1932
1947
  main$1();
1933
1948
 
@@ -1936,6 +1951,9 @@ const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
1936
1951
  notifyShutdown();
1937
1952
  cli.telegramProviderService.disable();
1938
1953
  });
1954
+ const listenGracefulShutdown = functoolsKit.singleshot(() => {
1955
+ process.on("SIGINT", BEFORE_EXIT_FN);
1956
+ });
1939
1957
  const main = async () => {
1940
1958
  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)))) {
1941
1959
  return;
@@ -1944,7 +1962,7 @@ const main = async () => {
1944
1962
  if (!values.telegram) {
1945
1963
  return;
1946
1964
  }
1947
- process.on("SIGINT", BEFORE_EXIT_FN);
1965
+ listenGracefulShutdown();
1948
1966
  };
1949
1967
  main();
1950
1968
 
@@ -1961,13 +1979,19 @@ async function run(mode, args) {
1961
1979
  _is_started = true;
1962
1980
  }
1963
1981
  if (mode === "backtest") {
1964
- return await cli.backtestMainService.run(args);
1982
+ await cli.backtestMainService.run(args);
1983
+ listenGracefulShutdown$4();
1984
+ return;
1965
1985
  }
1966
1986
  if (mode === "paper") {
1967
- return await cli.paperMainService.run(args);
1987
+ await cli.paperMainService.run(args);
1988
+ listenGracefulShutdown$3();
1989
+ return;
1968
1990
  }
1969
1991
  if (mode === "live") {
1970
- return await cli.liveMainService.run(args);
1992
+ await cli.liveMainService.run(args);
1993
+ listenGracefulShutdown$2();
1994
+ return;
1971
1995
  }
1972
1996
  throw new Error(`Invalid mode: ${mode}`);
1973
1997
  }
package/build/index.mjs CHANGED
@@ -514,8 +514,8 @@ class BacktestMainService {
514
514
  });
515
515
  notifyFinish();
516
516
  });
517
- this.init = singleshot(async () => {
518
- this.loggerService.log("backtestMainService init");
517
+ this.connect = singleshot(async () => {
518
+ this.loggerService.log("backtestMainService connect");
519
519
  if (!getEntry(import.meta.url)) {
520
520
  return;
521
521
  }
@@ -591,8 +591,8 @@ class LiveMainService {
591
591
  });
592
592
  notifyFinish();
593
593
  });
594
- this.init = singleshot(async () => {
595
- this.loggerService.log("liveMainService init");
594
+ this.connect = singleshot(async () => {
595
+ this.loggerService.log("liveMainService connect");
596
596
  if (!getEntry(import.meta.url)) {
597
597
  return;
598
598
  }
@@ -661,8 +661,8 @@ class PaperMainService {
661
661
  });
662
662
  notifyFinish();
663
663
  });
664
- this.init = singleshot(async () => {
665
- this.loggerService.log("paperMainService init");
664
+ this.connect = singleshot(async () => {
665
+ this.loggerService.log("paperMainService connect");
666
666
  if (!getEntry(import.meta.url)) {
667
667
  return;
668
668
  }
@@ -1823,6 +1823,9 @@ const BEFORE_EXIT_FN$4 = singleshot(async () => {
1823
1823
  frameName,
1824
1824
  });
1825
1825
  });
1826
+ const listenGracefulShutdown$4 = singleshot(() => {
1827
+ process.on("SIGINT", BEFORE_EXIT_FN$4);
1828
+ });
1826
1829
  const main$4 = async () => {
1827
1830
  if (!getEntry(import.meta.url)) {
1828
1831
  return;
@@ -1831,7 +1834,8 @@ const main$4 = async () => {
1831
1834
  if (!values.backtest) {
1832
1835
  return;
1833
1836
  }
1834
- process.on("SIGINT", BEFORE_EXIT_FN$4);
1837
+ await cli.backtestMainService.connect();
1838
+ listenGracefulShutdown$4();
1835
1839
  };
1836
1840
  main$4();
1837
1841
 
@@ -1851,6 +1855,9 @@ const BEFORE_EXIT_FN$3 = singleshot(async () => {
1851
1855
  strategyName,
1852
1856
  });
1853
1857
  });
1858
+ const listenGracefulShutdown$3 = singleshot(() => {
1859
+ process.on("SIGINT", BEFORE_EXIT_FN$3);
1860
+ });
1854
1861
  const main$3 = async () => {
1855
1862
  if (!getEntry(import.meta.url)) {
1856
1863
  return;
@@ -1859,7 +1866,8 @@ const main$3 = async () => {
1859
1866
  if (!values.paper) {
1860
1867
  return;
1861
1868
  }
1862
- process.on("SIGINT", BEFORE_EXIT_FN$3);
1869
+ cli.paperMainService.connect();
1870
+ listenGracefulShutdown$3();
1863
1871
  };
1864
1872
  main$3();
1865
1873
 
@@ -1880,6 +1888,9 @@ const BEFORE_EXIT_FN$2 = singleshot(async () => {
1880
1888
  });
1881
1889
  listenDoneLive(cli.liveProviderService.disable);
1882
1890
  });
1891
+ const listenGracefulShutdown$2 = singleshot(() => {
1892
+ process.on("SIGINT", BEFORE_EXIT_FN$2);
1893
+ });
1883
1894
  const main$2 = async () => {
1884
1895
  if (!getEntry(import.meta.url)) {
1885
1896
  return;
@@ -1888,7 +1899,8 @@ const main$2 = async () => {
1888
1899
  if (!values.live) {
1889
1900
  return;
1890
1901
  }
1891
- process.on("SIGINT", BEFORE_EXIT_FN$2);
1902
+ await cli.liveMainService.connect();
1903
+ listenGracefulShutdown$2();
1892
1904
  };
1893
1905
  main$2();
1894
1906
 
@@ -1897,6 +1909,9 @@ const BEFORE_EXIT_FN$1 = singleshot(async () => {
1897
1909
  notifyShutdown();
1898
1910
  cli.frontendProviderService.disable();
1899
1911
  });
1912
+ const listenGracefulShutdown$1 = singleshot(() => {
1913
+ process.on("SIGINT", BEFORE_EXIT_FN$1);
1914
+ });
1900
1915
  const main$1 = async () => {
1901
1916
  if (!getEntry(import.meta.url)) {
1902
1917
  return;
@@ -1905,7 +1920,7 @@ const main$1 = async () => {
1905
1920
  if (!values.ui) {
1906
1921
  return;
1907
1922
  }
1908
- process.on("SIGINT", BEFORE_EXIT_FN$1);
1923
+ listenGracefulShutdown$1();
1909
1924
  };
1910
1925
  main$1();
1911
1926
 
@@ -1914,6 +1929,9 @@ const BEFORE_EXIT_FN = singleshot(async () => {
1914
1929
  notifyShutdown();
1915
1930
  cli.telegramProviderService.disable();
1916
1931
  });
1932
+ const listenGracefulShutdown = singleshot(() => {
1933
+ process.on("SIGINT", BEFORE_EXIT_FN);
1934
+ });
1917
1935
  const main = async () => {
1918
1936
  if (!getEntry(import.meta.url)) {
1919
1937
  return;
@@ -1922,7 +1940,7 @@ const main = async () => {
1922
1940
  if (!values.telegram) {
1923
1941
  return;
1924
1942
  }
1925
- process.on("SIGINT", BEFORE_EXIT_FN);
1943
+ listenGracefulShutdown();
1926
1944
  };
1927
1945
  main();
1928
1946
 
@@ -1939,13 +1957,19 @@ async function run(mode, args) {
1939
1957
  _is_started = true;
1940
1958
  }
1941
1959
  if (mode === "backtest") {
1942
- return await cli.backtestMainService.run(args);
1960
+ await cli.backtestMainService.run(args);
1961
+ listenGracefulShutdown$4();
1962
+ return;
1943
1963
  }
1944
1964
  if (mode === "paper") {
1945
- return await cli.paperMainService.run(args);
1965
+ await cli.paperMainService.run(args);
1966
+ listenGracefulShutdown$3();
1967
+ return;
1946
1968
  }
1947
1969
  if (mode === "live") {
1948
- return await cli.liveMainService.run(args);
1970
+ await cli.liveMainService.run(args);
1971
+ listenGracefulShutdown$2();
1972
+ return;
1949
1973
  }
1950
1974
  throw new Error(`Invalid mode: ${mode}`);
1951
1975
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backtest-kit/cli",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Zero-boilerplate CLI runner for backtest-kit strategies. Run backtests, paper trading, and live bots with candle cache warming, web dashboard, and Telegram notifications — no setup code required.",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",
package/types.d.ts CHANGED
@@ -32,7 +32,7 @@ declare class PaperMainService {
32
32
  exchange: string;
33
33
  verbose: boolean;
34
34
  }) => Promise<void>) & functools_kit.ISingleshotClearable;
35
- protected init: (() => Promise<void>) & functools_kit.ISingleshotClearable;
35
+ connect: (() => Promise<void>) & functools_kit.ISingleshotClearable;
36
36
  }
37
37
 
38
38
  declare class LiveMainService {
@@ -50,7 +50,7 @@ declare class LiveMainService {
50
50
  exchange: string;
51
51
  verbose: boolean;
52
52
  }) => Promise<void>) & functools_kit.ISingleshotClearable;
53
- protected init: (() => Promise<void>) & functools_kit.ISingleshotClearable;
53
+ connect: (() => Promise<void>) & functools_kit.ISingleshotClearable;
54
54
  }
55
55
 
56
56
  declare class BacktestMainService {
@@ -71,7 +71,7 @@ declare class BacktestMainService {
71
71
  cacheList: string[];
72
72
  verbose: boolean;
73
73
  }) => Promise<void>) & functools_kit.ISingleshotClearable;
74
- protected init: (() => Promise<void>) & functools_kit.ISingleshotClearable;
74
+ connect: (() => Promise<void>) & functools_kit.ISingleshotClearable;
75
75
  }
76
76
 
77
77
  declare class ExchangeSchemaService {