@backtest-kit/sidekick 0.1.1 → 3.0.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.
Files changed (43) hide show
  1. package/README.md +120 -13
  2. package/content/config/source/timeframe_15m.pine +114 -0
  3. package/content/config/source/timeframe_4h.pine +57 -0
  4. package/content/config/symbol.config.cjs +460 -0
  5. package/content/docker/ollama/docker-compose.yaml +34 -0
  6. package/content/docker/ollama/watch.sh +2 -0
  7. package/content/scripts/cache/cache_candles.mjs +47 -0
  8. package/content/scripts/cache/cache_model.mjs +42 -0
  9. package/content/scripts/cache/validate_candles.mjs +46 -0
  10. package/content/scripts/run_timeframe_15m.mjs +77 -0
  11. package/content/scripts/run_timeframe_4h.mjs +68 -0
  12. package/package.json +2 -2
  13. package/scripts/init.mjs +40 -9
  14. package/src/classes/BacktestLowerStopOnBreakevenAction.mjs +18 -0
  15. package/src/classes/{PartialProfitTakingAction.mjs → BacktestPartialProfitTakingAction.mjs} +7 -2
  16. package/src/classes/BacktestPositionMonitorAction.mjs +57 -0
  17. package/src/config/params.mjs +1 -0
  18. package/src/config/setup.mjs +27 -1
  19. package/src/enum/ActionName.mjs +3 -1
  20. package/src/enum/FrameName.mjs +1 -0
  21. package/src/enum/RiskName.mjs +1 -1
  22. package/src/logic/action/backtest_lower_stop_on_breakeven.action.mjs +9 -0
  23. package/src/logic/action/backtest_partial_profit_taking.action.mjs +9 -0
  24. package/src/logic/action/backtest_position_monitor.action.mjs +9 -0
  25. package/src/logic/exchange/binance.exchange.mjs +12 -2
  26. package/src/logic/frame/feb_2024.frame.mjs +10 -0
  27. package/src/logic/index.mjs +5 -2
  28. package/src/logic/risk/sl_distance.risk.mjs +32 -0
  29. package/src/logic/risk/tp_distance.risk.mjs +5 -3
  30. package/src/logic/strategy/main.strategy.mjs +29 -10
  31. package/src/main/bootstrap.mjs +52 -0
  32. package/src/math/timeframe_15m.math.mjs +68 -0
  33. package/src/math/timeframe_4h.math.mjs +53 -0
  34. package/src/utils/getArgs.mjs +15 -23
  35. package/template/CLAUDE.mustache +421 -0
  36. package/template/README.mustache +232 -24
  37. package/template/env.mustache +1 -17
  38. package/template/jsconfig.json.mustache +1 -0
  39. package/template/package.mustache +8 -6
  40. package/src/func/market.func.mjs +0 -46
  41. package/src/logic/action/partial_profit_taking.action.mjs +0 -8
  42. package/src/logic/risk/rr_ratio.risk.mjs +0 -39
  43. /package/{types/backtest-kit.d.ts → template/types.mustache} +0 -0
@@ -0,0 +1,77 @@
1
+ import { addExchangeSchema } from "backtest-kit";
2
+ import { singleshot, randomString } from "functools-kit";
3
+ import { run, File, toMarkdown } from "@backtest-kit/pinets";
4
+ import ccxt from "ccxt";
5
+
6
+ const SIGNAL_SCHEMA = {
7
+ position: "Signal",
8
+ priceOpen: "Close",
9
+ priceTakeProfit: "TakeProfit",
10
+ priceStopLoss: "StopLoss",
11
+ minuteEstimatedTime: "EstimatedTime",
12
+ d_RSI: "d_RSI",
13
+ d_EmaFast: "d_EmaFast",
14
+ d_EmaSlow: "d_EmaSlow",
15
+ d_EmaTrend: "d_EmaTrend",
16
+ d_ATR: "d_ATR",
17
+ d_Volume: "d_Volume",
18
+ d_VolMA: "d_VolMA",
19
+ d_VolSpike: "d_VolSpike",
20
+ d_Mom: "d_Mom",
21
+ d_MomUp: "d_MomUp",
22
+ d_MomDown: "d_MomDown",
23
+ d_TrendUp: "d_TrendUp",
24
+ d_TrendDown: "d_TrendDown",
25
+ d_LongCond: "d_LongCond",
26
+ d_ShortCond: "d_ShortCond",
27
+ d_BarsSinceSignal: "d_BarsSinceSignal",
28
+ };
29
+
30
+ const SIGNAL_ID = randomString();
31
+
32
+ const getExchange = singleshot(async () => {
33
+ const exchange = new ccxt.binance({
34
+ options: {
35
+ defaultType: "spot",
36
+ adjustForTimeDifference: true,
37
+ recvWindow: 60000,
38
+ },
39
+ enableRateLimit: true,
40
+ });
41
+ await exchange.loadMarkets();
42
+ return exchange;
43
+ });
44
+
45
+ addExchangeSchema({
46
+ exchangeName: "ccxt-exchange",
47
+ getCandles: async (symbol, interval, since, limit) => {
48
+ const exchange = await getExchange();
49
+ const candles = await exchange.fetchOHLCV(
50
+ symbol,
51
+ interval,
52
+ since.getTime(),
53
+ limit,
54
+ );
55
+ return candles.map(([timestamp, open, high, low, close, volume]) => ({
56
+ timestamp,
57
+ open,
58
+ high,
59
+ low,
60
+ close,
61
+ volume,
62
+ }));
63
+ },
64
+ });
65
+
66
+ const plots = await run(
67
+ File.fromPath("timeframe_15m.pine"),
68
+ {
69
+ symbol: "BTCUSDT",
70
+ timeframe: "15m",
71
+ limit: 60,
72
+ },
73
+ "ccxt-exchange",
74
+ new Date("2025-09-23T16:00:00.000Z"),
75
+ );
76
+
77
+ console.log(await toMarkdown(SIGNAL_ID, plots, SIGNAL_SCHEMA));
@@ -0,0 +1,68 @@
1
+ import { addExchangeSchema } from "backtest-kit";
2
+ import { singleshot, randomString } from "functools-kit";
3
+ import { run, File, toMarkdown } from "@backtest-kit/pinets";
4
+ import ccxt from "ccxt";
5
+
6
+ const SIGNAL_SCHEMA = {
7
+ allowLong: "AllowLong",
8
+ allowShort: "AllowShort",
9
+ allowBoth: "AllowBoth",
10
+ noTrades: "NoTrades",
11
+ rsi: "RSI",
12
+ adx: "ADX",
13
+ d_MACDLine: "d_MACDLine",
14
+ d_SignalLine: "d_SignalLine",
15
+ d_MACDHist: "d_MACDHist",
16
+ d_DIPlus: "d_DIPlus",
17
+ d_DIMinus: "d_DIMinus",
18
+ d_StrongTrend: "d_StrongTrend",
19
+ };
20
+
21
+ const SIGNAL_ID = randomString();
22
+
23
+ const getExchange = singleshot(async () => {
24
+ const exchange = new ccxt.binance({
25
+ options: {
26
+ defaultType: "spot",
27
+ adjustForTimeDifference: true,
28
+ recvWindow: 60000,
29
+ },
30
+ enableRateLimit: true,
31
+ });
32
+ await exchange.loadMarkets();
33
+ return exchange;
34
+ });
35
+
36
+ addExchangeSchema({
37
+ exchangeName: "ccxt-exchange",
38
+ getCandles: async (symbol, interval, since, limit) => {
39
+ const exchange = await getExchange();
40
+ const candles = await exchange.fetchOHLCV(
41
+ symbol,
42
+ interval,
43
+ since.getTime(),
44
+ limit,
45
+ );
46
+ return candles.map(([timestamp, open, high, low, close, volume]) => ({
47
+ timestamp,
48
+ open,
49
+ high,
50
+ low,
51
+ close,
52
+ volume,
53
+ }));
54
+ },
55
+ });
56
+
57
+ const plots = await run(
58
+ File.fromPath("timeframe_4h.pine"),
59
+ {
60
+ symbol: "BTCUSDT",
61
+ timeframe: "4h",
62
+ limit: 60,
63
+ },
64
+ "ccxt-exchange",
65
+ new Date("2025-09-23T23:00:00.000Z"),
66
+ );
67
+
68
+ console.log(await toMarkdown(SIGNAL_ID, plots, SIGNAL_SCHEMA));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backtest-kit/sidekick",
3
- "version": "0.1.1",
3
+ "version": "3.0.0",
4
4
  "description": "The easiest way to create a new Backtest Kit trading bot project. Like create-react-app, but for algorithmic trading with LLM integration and technical analysis.",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",
@@ -37,7 +37,7 @@
37
37
  "src",
38
38
  "scripts",
39
39
  "template",
40
- "types",
40
+ "content",
41
41
  "README.md"
42
42
  ],
43
43
  "repository": {
package/scripts/init.mjs CHANGED
@@ -110,7 +110,7 @@ async function main() {
110
110
 
111
111
  const projectPath = path.resolve(process.cwd(), projectName);
112
112
  const srcTemplatePath = path.resolve(__dirname, '..', 'src');
113
- const typesTemplatePath = path.resolve(__dirname, '..', 'types');
113
+ const contentPath = path.resolve(__dirname, '..', 'content');
114
114
  const templateDir = path.resolve(__dirname, '..', 'template');
115
115
 
116
116
  // Template data for Mustache
@@ -141,16 +141,32 @@ async function main() {
141
141
 
142
142
  // Copy source template files using glob
143
143
  {
144
- log.info('Copying template files...');
144
+ log.info('Copying source files...');
145
145
  await copyFiles(srcTemplatePath, path.join(projectPath, 'src'));
146
- log.success('Copied template files');
146
+ log.success('Copied source files');
147
147
  }
148
148
 
149
- // Copy types template files
149
+ // Copy content files (config/, scripts/, docker/)
150
150
  {
151
- log.info('Copying types files...');
152
- await copyFiles(typesTemplatePath, path.join(projectPath, 'types'));
153
- log.success('Copied types files');
151
+ log.info('Copying content files...');
152
+ await copyFiles(contentPath, projectPath);
153
+ log.success('Copied content files');
154
+ }
155
+
156
+ // Create types/backtest-kit.d.ts from template
157
+ {
158
+ log.info('Creating types/backtest-kit.d.ts...');
159
+ await fs.mkdir(path.join(projectPath, 'types'), { recursive: true });
160
+ const typesContent = await renderTemplate(
161
+ path.join(templateDir, 'types.mustache'),
162
+ templateData
163
+ );
164
+ await fs.writeFile(
165
+ path.join(projectPath, 'types', 'backtest-kit.d.ts'),
166
+ typesContent,
167
+ 'utf-8'
168
+ );
169
+ log.success('Created types/backtest-kit.d.ts');
154
170
  }
155
171
 
156
172
  // Create package.json from template
@@ -216,7 +232,6 @@ async function main() {
216
232
  'utf-8'
217
233
  );
218
234
  log.success('Created README.md');
219
- console.log();
220
235
  }
221
236
 
222
237
  // Create jsconfig.json from template
@@ -234,6 +249,22 @@ async function main() {
234
249
  log.success('Created jsconfig.json');
235
250
  }
236
251
 
252
+ // Create CLAUDE.md from template
253
+ {
254
+ log.info('Creating CLAUDE.md...');
255
+ const claudeContent = await renderTemplate(
256
+ path.join(templateDir, 'CLAUDE.mustache'),
257
+ templateData
258
+ );
259
+ await fs.writeFile(
260
+ path.join(projectPath, 'CLAUDE.md'),
261
+ claudeContent,
262
+ 'utf-8'
263
+ );
264
+ log.success('Created CLAUDE.md');
265
+ console.log();
266
+ }
267
+
237
268
  // Install dependencies
238
269
  {
239
270
  await runNpmInstall(projectPath);
@@ -249,7 +280,7 @@ async function main() {
249
280
  console.log('Inside that directory, you can run several commands:');
250
281
  console.log();
251
282
  console.log(` ${pc.cyan('npm start')}`);
252
- console.log(' Starts the trading bot.');
283
+ console.log(' Runs the default backtest.');
253
284
  console.log();
254
285
  console.log('We suggest that you begin by typing:');
255
286
  console.log();
@@ -0,0 +1,18 @@
1
+ import { ActionBase, commitTrailingStop } from "backtest-kit";
2
+
3
+ /**
4
+ * Lowers trailing-stop by 3 points when breakeven is reached (ignores volatility)
5
+ * @implements {bt.IPublicAction}
6
+ */
7
+ export class BacktestLowerStopOnBreakevenAction extends ActionBase {
8
+ /**
9
+ *
10
+ * @param {bt.BreakevenContract} param0
11
+ */
12
+ async breakevenAvailable({ symbol, currentPrice }) {
13
+ // Lower trailing-stop by 3 points (negative value brings stop-loss closer to entry)
14
+ await commitTrailingStop(symbol, -3, currentPrice);
15
+ }
16
+ }
17
+
18
+ export default BacktestLowerStopOnBreakevenAction;
@@ -2,8 +2,13 @@ import { ActionBase, Constant, commitPartialProfit } from "backtest-kit";
2
2
 
3
3
  /**
4
4
  * Scale out at Kelly-optimized levels
5
+ * @implements {bt.IPublicAction}
5
6
  */
6
- export class PartialProfitTakingAction extends ActionBase {
7
+ export class BacktestPartialProfitTakingAction extends ActionBase {
8
+ /**
9
+ *
10
+ * @param {bt.PartialProfitContract} param0
11
+ */
7
12
  async partialProfitAvailable({ symbol, level }) {
8
13
  if (level === Constant.TP_LEVEL3) {
9
14
  await commitPartialProfit(symbol, 33);
@@ -17,4 +22,4 @@ export class PartialProfitTakingAction extends ActionBase {
17
22
  }
18
23
  }
19
24
 
20
- export default PartialProfitTakingAction;
25
+ export default BacktestPartialProfitTakingAction;
@@ -0,0 +1,57 @@
1
+ import { ActionBase } from "backtest-kit";
2
+
3
+ /**
4
+ * Monitors position lifecycle and logs open/close events in backtest mode
5
+ * @implements {bt.IPublicAction}
6
+ */
7
+ export class BacktestPositionMonitorAction extends ActionBase {
8
+ /**
9
+ * @param {bt.IStrategyTickResult} event
10
+ */
11
+ async signalBacktest(event) {
12
+ switch (event.action) {
13
+ case "scheduled":
14
+ console.log(`[POSITION SCHEDULED] ${event.symbol}`);
15
+ console.log(` Strategy: ${event.strategyName}`);
16
+ console.log(` Current Price: ${event.currentPrice}`);
17
+ console.log(` Entry Price: ${event.signal.priceOpen}`);
18
+ console.log(` Signal ID: ${event.signal.id}`);
19
+ console.log(` Direction: ${event.signal.position}`);
20
+ console.log(` Stop Loss: ${event.signal.priceStopLoss}`);
21
+ console.log(` Take Profit: ${event.signal.priceTakeProfit}`);
22
+ break;
23
+
24
+ case "opened":
25
+ console.log(`[POSITION OPENED] ${event.symbol}`);
26
+ console.log(` Strategy: ${event.strategyName}`);
27
+ console.log(` Entry Price: ${event.currentPrice}`);
28
+ console.log(` Signal ID: ${event.signal.id}`);
29
+ console.log(` Direction: ${event.signal.position}`);
30
+ console.log(` Stop Loss: ${event.signal.priceStopLoss}`);
31
+ console.log(` Take Profit: ${event.signal.priceTakeProfit}`);
32
+ break;
33
+
34
+ case "closed":
35
+ console.log(`[POSITION CLOSED] ${event.symbol}`);
36
+ console.log(` Strategy: ${event.strategyName}`);
37
+ console.log(` Entry Price (adj): ${event.pnl.priceOpen}`);
38
+ console.log(` Exit Price (adj): ${event.pnl.priceClose}`);
39
+ console.log(` Signal ID: ${event.signal.id}`);
40
+ console.log(` Close Reason: ${event.closeReason}`);
41
+ console.log(` PnL: ${event.pnl.pnlPercentage.toFixed(2)}%`);
42
+ console.log(` Win: ${event.pnl.pnlPercentage > 0 ? "YES" : "NO"}`);
43
+ break;
44
+
45
+ case "cancelled":
46
+ console.log(`[POSITION CANCELLED] ${event.symbol}`);
47
+ console.log(` Strategy: ${event.strategyName}`);
48
+ console.log(` Signal ID: ${event.signal.id}`);
49
+ console.log(` Current Price: ${event.currentPrice}`);
50
+ console.log(` Cancel Reason: ${event.reason}`);
51
+ console.log(` Cancelled At: ${new Date(event.closeTimestamp).toISOString()}`);
52
+ break;
53
+ }
54
+ }
55
+ }
56
+
57
+ export default BacktestPositionMonitorAction;
@@ -0,0 +1 @@
1
+ export const CC_OLLAMA_API_KEY = process.env.CC_OLLAMA_API_KEY || "";
@@ -1,4 +1,15 @@
1
- import { Markdown, Report, setLogger } from "backtest-kit"
1
+ import {
2
+ Markdown,
3
+ Report,
4
+ Notification,
5
+ Storage,
6
+ setLogger,
7
+ StorageLive,
8
+ StorageBacktest,
9
+ NotificationLive,
10
+ NotificationBacktest,
11
+ } from "backtest-kit";
12
+ import { serve } from "@backtest-kit/ui";
2
13
  import { createLogger } from "pinolog";
3
14
 
4
15
  {
@@ -11,9 +22,24 @@ import { createLogger } from "pinolog";
11
22
  });
12
23
  }
13
24
 
25
+ {
26
+ Storage.enable();
27
+ Notification.enable();
28
+ }
29
+
14
30
  {
15
31
  Markdown.disable();
16
32
  Report.enable();
17
33
  }
18
34
 
35
+ {
36
+ StorageLive.usePersist();
37
+ StorageBacktest.usePersist();
38
+ }
39
+
40
+ {
41
+ NotificationLive.usePersist();
42
+ NotificationBacktest.usePersist();
43
+ }
19
44
 
45
+ serve();
@@ -1,3 +1,5 @@
1
1
  export default {
2
- PartialProfitTakingAction: "partial_profit_taking_action",
2
+ BacktestPartialProfitTakingAction: "backtest_partial_profit_taking_action",
3
+ BacktestLowerStopOnBreakevenAction: "backtest_lower_stop_on_breakeven_action",
4
+ BacktestPositionMonitorAction: "backtest_position_monitor_action",
3
5
  };
@@ -1,4 +1,5 @@
1
1
  export default {
2
+ February2024: "feb_2024_frame",
2
3
  October2025: "oct_2025_frame",
3
4
  November2025: "nov_2025_frame",
4
5
  December2025: "dec_2025_frame",
@@ -1,4 +1,4 @@
1
1
  export default {
2
2
  TakeProfitDistanceRisk: "tp_distance_risk",
3
- RiskRewardRatioRisk: "rr_ratio_risk",
3
+ StopLossDistanceRisk: "sl_distance_risk",
4
4
  };
@@ -0,0 +1,9 @@
1
+ import { addActionSchema } from "backtest-kit";
2
+ import ActionName from "../../enum/ActionName.mjs";
3
+ import { BacktestLowerStopOnBreakevenAction } from "../../classes/BacktestLowerStopOnBreakevenAction.mjs";
4
+
5
+ addActionSchema({
6
+ actionName: ActionName.BacktestLowerStopOnBreakevenAction,
7
+ handler: BacktestLowerStopOnBreakevenAction,
8
+ note: "Lower trailing-stop by 3 points when breakeven is reached",
9
+ });
@@ -0,0 +1,9 @@
1
+ import { addActionSchema } from "backtest-kit";
2
+ import ActionName from "../../enum/ActionName.mjs";
3
+ import { BacktestPartialProfitTakingAction } from "../../classes/BacktestPartialProfitTakingAction.mjs";
4
+
5
+ addActionSchema({
6
+ actionName: ActionName.BacktestPartialProfitTakingAction,
7
+ handler: BacktestPartialProfitTakingAction,
8
+ note: "Scale out at Kelly-optimized levels (33%, 33%, 34%)",
9
+ });
@@ -0,0 +1,9 @@
1
+ import { addActionSchema } from "backtest-kit";
2
+ import ActionName from "../../enum/ActionName.mjs";
3
+ import { BacktestPositionMonitorAction } from "../../classes/BacktestPositionMonitorAction.mjs";
4
+
5
+ addActionSchema({
6
+ actionName: ActionName.BacktestPositionMonitorAction,
7
+ handler: BacktestPositionMonitorAction,
8
+ note: "Monitors and logs position lifecycle events (open/close/scheduled)",
9
+ });
@@ -12,8 +12,13 @@ addExchangeSchema({
12
12
  symbol,
13
13
  interval,
14
14
  since.getTime(),
15
- limit
15
+ limit,
16
16
  );
17
+ if (
18
+ candles.flatMap((candle) => candle).some((value) => value === undefined)
19
+ ) {
20
+ throw new Error("Invalid candles found");
21
+ }
17
22
  return candles.map(([timestamp, open, high, low, close, volume]) => ({
18
23
  timestamp,
19
24
  open,
@@ -55,5 +60,10 @@ addExchangeSchema({
55
60
  quantity: String(quantity),
56
61
  })),
57
62
  };
58
- }
63
+ },
64
+ callbacks: {
65
+ onCandleData(symbol, interval, since) {
66
+ console.log(`Received candle data for symbol: ${symbol}, interval: ${interval}, since: ${since.toUTCString()}`);
67
+ }
68
+ },
59
69
  });
@@ -0,0 +1,10 @@
1
+ import { addFrameSchema } from "backtest-kit";
2
+ import FrameName from "../../enum/FrameName.mjs";
3
+
4
+ addFrameSchema({
5
+ frameName: FrameName.February2024,
6
+ interval: "1m",
7
+ startDate: new Date("2024-02-01T00:00:00Z"),
8
+ endDate: new Date("2024-02-29T23:59:59Z"),
9
+ note: "Bull run period",
10
+ });
@@ -1,12 +1,15 @@
1
1
  import "./exchange/binance.exchange.mjs";
2
2
 
3
- import "./action/partial_profit_taking.action.mjs";
3
+ import "./action/backtest_partial_profit_taking.action.mjs";
4
+ import "./action/backtest_lower_stop_on_breakeven.action.mjs";
5
+ import "./action/backtest_position_monitor.action.mjs";
4
6
 
5
7
  import "./frame/dec_2025.frame.mjs";
6
8
  import "./frame/nov_2025.frame.mjs";
7
9
  import "./frame/oct_2025.frame.mjs";
10
+ import "./frame/feb_2024.frame.mjs";
8
11
 
9
- import "./risk/rr_ratio.risk.mjs";
12
+ import "./risk/sl_distance.risk.mjs";
10
13
  import "./risk/tp_distance.risk.mjs";
11
14
 
12
15
  import "./strategy/main.strategy.mjs";
@@ -0,0 +1,32 @@
1
+ import { addRiskSchema } from "backtest-kit";
2
+ import RiskName from "../../enum/RiskName.mjs";
3
+
4
+ const SLIPPAGE_THRESHOLD = 0.2;
5
+
6
+ addRiskSchema({
7
+ riskName: RiskName.StopLossDistanceRisk,
8
+ validations: [
9
+ {
10
+ validate: ({ currentSignal, currentPrice }) => {
11
+ const {
12
+ priceOpen = currentPrice,
13
+ priceStopLoss,
14
+ position,
15
+ } = currentSignal;
16
+ if (!priceOpen) {
17
+ return;
18
+ }
19
+ // Calculate SL distance percentage
20
+ const slDistance =
21
+ position === "long"
22
+ ? ((priceOpen - priceStopLoss) / priceOpen) * 100
23
+ : ((priceStopLoss - priceOpen) / priceOpen) * 100;
24
+
25
+ if (slDistance < SLIPPAGE_THRESHOLD) {
26
+ throw new Error(`SL distance ${slDistance.toFixed(2)}% < 1%`);
27
+ }
28
+ },
29
+ note: "SL distance must be at least 1%",
30
+ },
31
+ ],
32
+ });
@@ -1,16 +1,18 @@
1
1
  import { addRiskSchema } from "backtest-kit";
2
2
  import RiskName from "../../enum/RiskName.mjs";
3
3
 
4
+ const SLIPPAGE_THRESHOLD = 0.2;
5
+
4
6
  addRiskSchema({
5
7
  riskName: RiskName.TakeProfitDistanceRisk,
6
8
  validations: [
7
9
  {
8
- validate: ({ pendingSignal, currentPrice }) => {
10
+ validate: ({ currentSignal, currentPrice }) => {
9
11
  const {
10
12
  priceOpen = currentPrice,
11
13
  priceTakeProfit,
12
14
  position,
13
- } = pendingSignal;
15
+ } = currentSignal;
14
16
  if (!priceOpen) {
15
17
  return;
16
18
  }
@@ -20,7 +22,7 @@ addRiskSchema({
20
22
  ? ((priceTakeProfit - priceOpen) / priceOpen) * 100
21
23
  : ((priceOpen - priceTakeProfit) / priceOpen) * 100;
22
24
 
23
- if (tpDistance < 1) {
25
+ if (tpDistance < SLIPPAGE_THRESHOLD) {
24
26
  throw new Error(`TP distance ${tpDistance.toFixed(2)}% < 1%`);
25
27
  }
26
28
  },
@@ -1,7 +1,8 @@
1
1
  import { addStrategySchema } from "backtest-kit";
2
- import { ollama } from "@backtest-kit/ollama";
2
+ import { randomString } from "functools-kit";
3
3
 
4
- import { commitHistorySetup } from "../../func/market.func.mjs";
4
+ import * as math_15m from "../../math/timeframe_15m.math.mjs";
5
+ import * as math_4h from "../../math/timeframe_4h.math.mjs";
5
6
 
6
7
  import StrategyName from "../../enum/StrategyName.mjs";
7
8
  import RiskName from "../../enum/RiskName.mjs";
@@ -10,20 +11,38 @@ addStrategySchema({
10
11
  strategyName: StrategyName.MainStrategy,
11
12
  interval: "5m",
12
13
  getSignal: async (symbol) => {
13
- const messages = [];
14
+
15
+ const signalId = randomString();
16
+
17
+ const data_4h = await math_4h.getData(signalId, symbol);
18
+
19
+ if (data_4h.noTrades) {
20
+ return null;
21
+ }
22
+
23
+ const data_15m = await math_15m.getData(signalId, symbol);
24
+
25
+ if (data_15m.position === 0) {
26
+ return null;
27
+ }
28
+
29
+ if (data_4h.allowShort && data_15m.position === 1) {
30
+ return null;
31
+ }
32
+
33
+ if (data_4h.allowLong && data_15m.position === -1) {
34
+ return null;
35
+ }
14
36
 
15
37
  {
16
- await commitHistorySetup(symbol, messages);
38
+ math_15m.dumpPlot(signalId, symbol);
39
+ math_4h.dumpPlot(signalId, symbol);
17
40
  }
18
41
 
19
- return await ollama(
20
- messages,
21
- "glm-4.6:cloud",
22
- process.env.CC_OLLAMA_API_KEY
23
- );
42
+ return await math_15m.getSignal(signalId, symbol);
24
43
  },
25
44
  riskList: [
26
45
  RiskName.TakeProfitDistanceRisk,
27
- RiskName.RiskRewardRatioRisk
46
+ RiskName.StopLossDistanceRisk
28
47
  ],
29
48
  });
@@ -0,0 +1,52 @@
1
+ import { Backtest, listenError, overrideStrategySchema } from "backtest-kit";
2
+
3
+ import { getArgs } from "../utils/getArgs.mjs";
4
+
5
+ import ActionName from "../enum/ActionName.mjs";
6
+
7
+ const beginBacktest = async () => {
8
+ const { symbol, frameName, strategyName, exchangeName } = getArgs();
9
+
10
+ overrideStrategySchema({
11
+ strategyName,
12
+ actions: [
13
+ ActionName.BacktestPartialProfitTakingAction,
14
+ ActionName.BacktestLowerStopOnBreakevenAction,
15
+ ActionName.BacktestPositionMonitorAction,
16
+ ],
17
+ });
18
+
19
+ Backtest.background(symbol, {
20
+ strategyName,
21
+ frameName,
22
+ exchangeName,
23
+ });
24
+ };
25
+
26
+ const beginPaper = async () => {
27
+ throw new Error("Todo: implement");
28
+ };
29
+
30
+ const beginLive = async () => {
31
+ throw new Error("Todo: implement");
32
+ };
33
+
34
+ const main = async () => {
35
+ const { backtest, live, paper } = getArgs();
36
+
37
+ if (backtest) {
38
+ await beginBacktest();
39
+ }
40
+
41
+ if (paper) {
42
+ await beginPaper();
43
+ }
44
+
45
+ if (live) {
46
+ await beginLive();
47
+ }
48
+ };
49
+
50
+ listenError(console.log);
51
+
52
+ main();