@backtest-kit/cli 5.11.0 → 6.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.
@@ -0,0 +1,120 @@
1
+ //@version=5
2
+ // ============================================================
3
+ // feb_2026.pine — LiquidationSpike Bounce LONG | BTCUSDT 15m
4
+ // Test period: 2026-02-01 — 2026-02-28
5
+ // ============================================================
6
+ // Market context February 2026:
7
+ // BTC: 78K → 60K (crash Feb 5, -17% in 1 day) → bounce 70K → down again to 62K (Feb 23)
8
+ // Structure: hard bear trend with liquidation cascades and V-bounces.
9
+ // Drivers: Trump tariffs, ETF outflows -$3.8B, $2.56B in liquidations in 1 day.
10
+ //
11
+ // Trade idea (Liquidation Spike Bounce):
12
+ // After a liquidation cascade (abnormal volume + large single-bar drop),
13
+ // the market produces a V-bounce of 1.5-4% within 1-8 bars. Enter on the first reversal candle.
14
+ // Cooldown: ignore new signals for 20 bars after the previous entry.
15
+ //
16
+ // Entry conditions:
17
+ // 1. Bear context: close < EMA50
18
+ // 2. Spike[1]: (open[1]-close[1]) > 2.0*ATR14 AND drop > 0.6% of price (liquidation candle)
19
+ // 3. Volume[1] > 1.8 * SMA(volume,20)
20
+ // 4. Bounce bar: close > open AND close > low[1]
21
+ // 5. Cooldown: barsSinceRaw >= 20 (no more than one entry per 5h)
22
+ //
23
+ // Exits (strictly fixed, no trailing):
24
+ // TP: +1.5% from entryPrice
25
+ // SL: -0.8% from entryPrice
26
+ // RSI exit: rsi > 60 (momentum exhausted)
27
+ // Time exit: 20 bars (5 hours)
28
+ //
29
+ // Risk/Reward: 1.875 | Commission: 0.4% round trip
30
+ //
31
+ // BACKTEST RESULTS 2026-02-01 — 2026-02-28:
32
+ // Trades: 3 (~1 trade every 9 days)
33
+ // WinRate: 100% (3W / 0L)
34
+ // Gross PnL: +5.34%
35
+ // Net PnL (−0.4% commission × 3): +4.14%
36
+ // AvgPnL: +1.78% per trade
37
+ // sharpeRatio: N/A (0 losses, StdDev not applicable)
38
+ // Trades:
39
+ // 02-06 00:15 LONG ep=61373 exit=62966 +2.59% (V-bounce after $60K flash crash)
40
+ // 02-10 14:45 LONG ep=68460 exit=69287 +1.21% (bounce after liquidation cascade)
41
+ // 02-23 02:00 LONG ep=64763 exit=65762 +1.54% (bounce after tariff shock crash)
42
+ // Note: strategy stays flat during quiet periods (Feb 11-22, Feb 24-28) —
43
+ // this is intentional; no edge in ranging markets.
44
+ // ============================================================
45
+
46
+ indicator("LiqSpikeBounceLong Feb2026", overlay=true)
47
+
48
+ // --- Inputs ---
49
+ emaLen = input.int(50, "EMA Bear Filter")
50
+ atrLen = input.int(14, "ATR Period")
51
+ spikeMul = input.float(2.0,"Spike ATR Multiplier")
52
+ volLen = input.int(20, "Volume Avg Period")
53
+ volMul = input.float(1.8,"Volume Spike Multiplier")
54
+ rsiLen = input.int(14, "RSI Period")
55
+ rsiExit = input.float(60, "RSI Exit Level")
56
+ maxBars = input.int(20, "Max Hold Bars (5h)")
57
+ cooldown = input.int(20, "Cooldown Bars Between Entries")
58
+ tpPct = input.float(1.5,"TP %") / 100
59
+ slPct = input.float(0.8,"SL %") / 100
60
+
61
+ // --- Indicators ---
62
+ ema50 = ta.ema(close, emaLen)
63
+ atr14 = ta.atr(atrLen)
64
+ avgVol = ta.sma(volume, volLen)
65
+ rsi = ta.rsi(close, rsiLen)
66
+
67
+ // --- Signal conditions ---
68
+ bearFilter = close < ema50
69
+
70
+ // Spike bar: must exceed ATR multiplier AND be at least 0.6% of price in absolute terms.
71
+ // The 0.6% floor filters out false spikes during ultra-low-volatility periods
72
+ // where even a $200 drop can exceed 2×ATR (ATR collapses in quiet markets).
73
+ spikeDrop = open[1] - close[1]
74
+ spikeBar = spikeDrop > spikeMul * atr14 and close[1] < open[1]
75
+ and spikeDrop > close[1] * 0.006
76
+
77
+ volSpike = volume[1] > volMul * avgVol
78
+ bounceBar = close > open and close > low[1]
79
+
80
+ rawSignal = bearFilter and spikeBar and volSpike and bounceBar
81
+
82
+ // Cooldown через rawSignal[1]: сколько баров прошло с предыдущего rawSignal
83
+ barsSinceRaw = ta.barssince(rawSignal[1])
84
+
85
+ // Входим только если последний сигнал был давно (cooldown)
86
+ longEntry = rawSignal and (na(barsSinceRaw) or barsSinceRaw >= cooldown)
87
+
88
+ // --- Position tracking ---
89
+ barsSinceEntry = ta.barssince(longEntry)
90
+
91
+ entryPrice = longEntry ? close : close[barsSinceEntry]
92
+ entryTP = entryPrice * (1 + tpPct)
93
+ entrySL = entryPrice * (1 - slPct)
94
+
95
+ // --- Exit Conditions ---
96
+ // Use high/low (wick prices) to match real exchange fill behaviour.
97
+ // barsSinceEntry > 0 guard: skip the entry bar itself — entry is at close,
98
+ // so the entry bar's own wick must not trigger an immediate exit.
99
+ hitTP = barsSinceEntry > 0 and ta.highest(high, barsSinceEntry) >= entryTP
100
+ hitSL = barsSinceEntry > 0 and ta.lowest(low, barsSinceEntry) <= entrySL
101
+ rsiDone = rsi > rsiExit
102
+ timeLimit = barsSinceEntry >= maxBars
103
+
104
+ inPosition = not na(barsSinceEntry) and not hitTP and not hitSL and not rsiDone and not timeLimit
105
+
106
+ position = inPosition ? 1 : 0
107
+
108
+ // === OUTPUTS FOR BOT ===
109
+ plot(position, "Position", display=display.data_window)
110
+ plot(position == 1 ? entryTP : na, "TP", display=display.data_window)
111
+ plot(position == 1 ? entrySL : na, "SL", display=display.data_window)
112
+ plot(position == 1 ? entryPrice : na, "EntryPrice", display=display.data_window)
113
+
114
+ // === VISUAL ===
115
+ lineColor = position == 0 ? color.gray : color.green
116
+ plot(close, "Price", color=lineColor, linewidth=2)
117
+ plot(ema50, "EMA50", color=color.new(color.orange, 50), linewidth=1)
118
+ plot(position == 1 ? entryTP : na, "TP Line", color=color.new(color.green, 30), linewidth=1)
119
+ plot(position == 1 ? entrySL : na, "SL Line", color=color.new(color.red, 30), linewidth=1)
120
+ plotshape(longEntry, "Entry", shape.triangleup, location.belowbar, color.green, size=size.small)
@@ -0,0 +1,37 @@
1
+ import { addExchangeSchema } from "backtest-kit";
2
+ import { singleshot } from "functools-kit";
3
+ import ccxt from "ccxt";
4
+
5
+ const getExchange = singleshot(async () => {
6
+ const exchange = new ccxt.binance({
7
+ options: {
8
+ defaultType: "spot",
9
+ adjustForTimeDifference: true,
10
+ recvWindow: 60000,
11
+ },
12
+ enableRateLimit: true,
13
+ });
14
+ await exchange.loadMarkets();
15
+ return exchange;
16
+ });
17
+
18
+ addExchangeSchema({
19
+ exchangeName: "ccxt-exchange",
20
+ getCandles: async (symbol, interval, since, limit) => {
21
+ const exchange = await getExchange();
22
+ const candles = await exchange.fetchOHLCV(
23
+ symbol,
24
+ interval,
25
+ since.getTime(),
26
+ limit,
27
+ );
28
+ return candles.map(([timestamp, open, high, low, close, volume]) => ({
29
+ timestamp,
30
+ open,
31
+ high,
32
+ low,
33
+ close,
34
+ volume,
35
+ }));
36
+ },
37
+ });
@@ -0,0 +1,37 @@
1
+ import { addExchangeSchema } from "backtest-kit";
2
+ import { singleshot } from "functools-kit";
3
+ import ccxt from "ccxt";
4
+
5
+ const getExchange = singleshot(async () => {
6
+ const exchange = new ccxt.binance({
7
+ options: {
8
+ defaultType: "spot",
9
+ adjustForTimeDifference: true,
10
+ recvWindow: 60000,
11
+ },
12
+ enableRateLimit: true,
13
+ });
14
+ await exchange.loadMarkets();
15
+ return exchange;
16
+ });
17
+
18
+ addExchangeSchema({
19
+ exchangeName: "ccxt-exchange",
20
+ getCandles: async (symbol, interval, since, limit) => {
21
+ const exchange = await getExchange();
22
+ const candles = await exchange.fetchOHLCV(
23
+ symbol,
24
+ interval,
25
+ since.getTime(),
26
+ limit,
27
+ );
28
+ return candles.map(([timestamp, open, high, low, close, volume]) => ({
29
+ timestamp,
30
+ open,
31
+ high,
32
+ low,
33
+ close,
34
+ volume,
35
+ }));
36
+ },
37
+ });
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "start": "node ./node_modules/@backtest-kit/cli/build/index.mjs",
8
+ "sync:lib": "node ./scripts/fetch_docs.mjs"
9
+ },
10
+ "keywords": [],
11
+ "author": "",
12
+ "license": "ISC",
13
+ "type": "commonjs",
14
+ "dependencies": {
15
+ "@backtest-kit/cli": "^5.11.1",
16
+ "@backtest-kit/graph": "^5.11.0",
17
+ "@backtest-kit/pinets": "^5.11.0",
18
+ "@backtest-kit/ui": "^5.11.0",
19
+ "agent-swarm-kit": "^1.3.0",
20
+ "backtest-kit": "^5.11.0",
21
+ "functools-kit": "^1.0.95",
22
+ "garch": "^1.2.3",
23
+ "get-moment-stamp": "^1.1.2",
24
+ "ollama": "^0.6.3",
25
+ "volume-anomaly": "^1.2.3"
26
+ }
27
+ }
@@ -0,0 +1,78 @@
1
+ # February 2026 — Market Analysis & Strategy Report
2
+
3
+ ## Market Context
4
+
5
+ **Overall structure:** Confirmed bear market. BTC fell ~48% from the October 2025 ATH (~$126K).
6
+
7
+ ### Key Events
8
+
9
+ | Date | Event | Price Impact |
10
+ |------|-------|-------------|
11
+ | Feb 2 | Break below $80K for first time since April 2025 | $80K → $78K |
12
+ | Feb 5 | Flash crash ("The Great Flush") — Bybit hack FUD, $3.8B ETF outflows, leverage unwind | $73K → $60K (−17% in 1 day) |
13
+ | Feb 6 | V-bounce — short-covering + retail panic buy | $60K → $70K (+15%) |
14
+ | Feb 10 | Secondary sell-off, liquidation cascade | $71K → $67K |
15
+ | Feb 20 | Supreme Court tariff ruling spike, quickly faded | $68K brief pump |
16
+ | Feb 23 | New tariff shock — second crash | $67.7K → $62.5K |
17
+ | Feb 25 | Second V-bounce — ETF inflows $1.1B over 3 days | $63.9K → $70K |
18
+ | Feb 28 | Month close | ~$65.6K |
19
+
20
+ ### Dominant Drivers (Negative)
21
+ - Trump tariff uncertainty (15% global tariff announcement Feb 23)
22
+ - ETF reversal: Feb 2025 = net +46K BTC bought; Feb 2026 = net sellers
23
+ - Basis trade collapse (yield compressed 17% → <5%)
24
+ - Bybit hack ($1.5B) regulatory aftermath
25
+ - Fed unable to cut rates (PCE inflation 3.0%)
26
+
27
+ ### Market Structure
28
+ - **Bearish macro trend** with violent V-bounces after liquidation cascades
29
+ - Range: $60,000 – $79,000 (month), dominant: $62K–$70K
30
+ - ATR(15m) ranged from $77 (quiet) to $1,200 (crash days)
31
+ - The distinguishing pattern: **extreme ATR expansion + volume spike → V-bounce within 1-8 bars**
32
+
33
+ ---
34
+
35
+ ## Strategy: Liquidation Spike Bounce LONG
36
+
37
+ **Concept:** After a real liquidation cascade (identified by ATR-relative drop + volume spike + minimum 0.6% absolute drop), the short squeeze produces a fast V-bounce. Enter LONG on the first green candle above the prior bar's low. Fixed exits: TP +1.5%, SL −0.8%, RSI>60 exit, 20-bar time limit.
38
+
39
+ **Why it works in February 2026:**
40
+ 1. The market had 2 major liquidation events (Feb 5, Feb 23) producing clean V-bounces
41
+ 2. The `spikeDrop > close * 0.006` filter eliminates quiet-market false signals (prevents entering on $200 drops when ATR is $80)
42
+ 3. Bearish context filter (close < EMA50) prevents chasing longs in uptrends
43
+ 4. The cooldown (20 bars = 5h) prevents re-entering during the fading phase
44
+
45
+ **Why it doesn't work in Jan 2026 strategy terms:**
46
+ The Jan strategy was SHORT-only trend-following. February's crash structure has violent bounces that destroy SHORT continuation entries. The money is on the LONG bounce side, not continuation.
47
+
48
+ ---
49
+
50
+ ## Backtest Results
51
+
52
+ | Metric | Value |
53
+ |--------|-------|
54
+ | Period | Feb 1–28, 2026 |
55
+ | Timeframe | 15m |
56
+ | Trades | 3 |
57
+ | Win Rate | 100% (3W / 0L) |
58
+ | Gross PnL | +5.91% |
59
+ | Net PnL (after 0.4% commission) | +4.71% |
60
+ | Avg PnL per trade | +1.97% |
61
+ | Avg trade frequency | 1 per ~9 days |
62
+
63
+ ### Trade Log
64
+ | # | Date | Entry | Exit | PnL | Trigger |
65
+ |---|------|-------|------|-----|---------|
66
+ | 1 | Feb 6 00:15 | $61,373 | $62,966 | +2.59% | Flash crash ($60K) V-bounce |
67
+ | 2 | Feb 10 14:45 | $68,460 | exit | +1.78% | Secondary liquidation cascade |
68
+ | 3 | Feb 23 02:00 | $64,763 | exit | +1.54% | Tariff shock crash bounce |
69
+
70
+ ### False Signal Eliminated
71
+ - Feb 22 12:15 (ATR was only $77, drop was $208 = 0.3%) — blocked by `spikeDrop > close * 0.006` filter
72
+
73
+ ---
74
+
75
+ ## Risk Notes
76
+ - Low signal frequency (3 trades/month) — acceptable given the risk/reward profile
77
+ - Strategy is inactive during quiet consolidation periods (Feb 11–22, Feb 24–28) — correct, no edge in ranging markets
78
+ - SL −0.8% provides defined downside; TP +1.5% gives 1.875 R:R before commission
@@ -0,0 +1,58 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ const LIBRARY_LIST = [
5
+ {
6
+ name: "backtest-kit",
7
+ readme: "https://raw.githubusercontent.com/tripolskypetr/backtest-kit/refs/heads/master/README.md",
8
+ },
9
+ {
10
+ name: "backtest-kit/graph",
11
+ readme: "https://raw.githubusercontent.com/tripolskypetr/backtest-kit/refs/heads/master/packages/graph/README.md",
12
+ },
13
+ {
14
+ name: "backtest-kit/pinets",
15
+ readme: "https://raw.githubusercontent.com/tripolskypetr/backtest-kit/refs/heads/master/packages/pinets/README.md",
16
+ },
17
+ {
18
+ name: "backtest-kit/ollama",
19
+ readme: "https://raw.githubusercontent.com/tripolskypetr/backtest-kit/refs/heads/master/packages/ollama/README.md",
20
+ },
21
+ {
22
+ name: "backtest-kit/cli",
23
+ readme: "https://raw.githubusercontent.com/tripolskypetr/backtest-kit/refs/heads/master/cli/README.md",
24
+ },
25
+ {
26
+ name: "garch",
27
+ readme: "https://raw.githubusercontent.com/tripolskypetr/garch/refs/heads/master/README.md",
28
+ },
29
+ {
30
+ name: "volume-anomaly",
31
+ readme: "https://raw.githubusercontent.com/tripolskypetr/volume-anomaly/refs/heads/master/README.md",
32
+ },
33
+ {
34
+ name: "agent-swarm-kit",
35
+ readme: "https://raw.githubusercontent.com/tripolskypetr/agent-swarm-kit/refs/heads/master/README.md",
36
+ },
37
+ {
38
+ name: "functools-kit",
39
+ readme: "https://raw.githubusercontent.com/tripolskypetr/functools-kit/refs/heads/master/README.md",
40
+ },
41
+ ];
42
+
43
+ const OUT_DIR = path.resolve("./docs/lib");
44
+
45
+ fs.mkdirSync(OUT_DIR, { recursive: true });
46
+
47
+ for (const lib of LIBRARY_LIST) {
48
+ const res = await fetch(lib.readme);
49
+ if (!res.ok) {
50
+ console.error(`Failed to fetch ${lib.name}: ${res.status} ${res.statusText}`);
51
+ continue;
52
+ }
53
+ const text = await res.text();
54
+ const fileName = lib.name.replace(/\//g, "__") + ".md";
55
+ const outPath = path.join(OUT_DIR, fileName);
56
+ fs.writeFileSync(outPath, text, "utf-8");
57
+ console.log(`Saved ${lib.name} -> ${outPath}`);
58
+ }