Crypto Strategy Backtest Skill Transform natural language trading ideas into validated strategies with professional backtesting, beautiful reports, and runnable code. ⚠️ SPOT ONLY : This skill supports spot trading strategies only . No leverage, no shorting, no futures/perpetual contracts. All strategies are long-only (buy low → hold → sell high). Your Superpower You turn vague trading intuitions into professional-grade, multi-dimensional strategies . When users say "buy when cheap", you don't just slap on RSI < 30 — you build a comprehensive valuation model using multiple indicators, each wi…

,\n 'buy': 'Buy',\n 'sell': 'Sell',\n 'entry_signal': 'Entry Signal',\n 'exit_signal': 'Exit Signal',\n 'price': 'Price',\n 'equity': 'Equity',\n 'days': 'days',\n 'date_range': 'Date Range',\n 'original_idea': 'Original Strategy Idea',\n 'to': 'to',\n },\n 'zh': {\n 'title': '策略回测报告',\n 'strategy_summary': '完整策略',\n 'strategy_config': '策略配置',\n 'symbol': '交易对',\n 'timeframe': '时间周期',\n 'period': '回测周期',\n 'backtest_period': '回测周期',\n 'initial_capital': '初始资金',\n 'entry': '入场条件',\n 'exit': '出场条件',\n 'entry_all': '入场条件(全部满足)',\n 'exit_any': '出场条件(任一触发)',\n 'stop_loss': '止损',\n 'take_profit': '止盈',\n 'position_size': '仓位大小',\n 'commission': '手续费',\n 'risk_management': '风险管理',\n 'performance_metrics': '绩效指标',\n 'total_return': '总收益',\n 'sharpe_ratio': '夏普比率',\n 'max_drawdown': '最大回撤',\n 'win_rate': '胜率',\n 'total_trades': '总交易次数',\n 'profit_factor': '盈亏比',\n 'avg_trade': '平均交易收益',\n 'best_trade': '最佳交易',\n 'worst_trade': '最差交易',\n 'equity_curve': '资金曲线',\n 'price_signals': '价格与信号',\n 'trade_pnl': '交易盈亏分布',\n 'indicators': '技术指标',\n 'tagline': '几分钟验证你的交易策略想法',\n 'share_cta': '截图分享你的回测结果,帮助更多人发现这个工具!',\n 'generated': '生成时间',\n 'disclaimer': '过往表现不代表未来收益',\n 'analysis_title': '整体分析',\n 'trade_table_title': '交易记录',\n 'trade_no': '序号',\n 'trade_entry_date': '入场日期',\n 'trade_exit_date': '出场日期',\n 'trade_type': '类型',\n 'trade_cost': '本金',\n 'trade_quantity': '数量',\n 'trade_entry_price': '入场价格',\n 'trade_exit_price': '出场价格',\n 'trade_pnl_pct': '盈亏%',\n 'trade_pnl_amount': '盈亏

Crypto Strategy Backtest Skill Transform natural language trading ideas into validated strategies with professional backtesting, beautiful reports, and runnable code. ⚠️ SPOT ONLY : This skill supports spot trading strategies only . No leverage, no shorting, no futures/perpetual contracts. All strategies are long-only (buy low → hold → sell high). Your Superpower You turn vague trading intuitions into professional-grade, multi-dimensional strategies . When users say "buy when cheap", you don't just slap on RSI < 30 — you build a comprehensive valuation model using multiple indicators, each wi…

,\n 'buy': '买入',\n 'sell': '卖出',\n 'entry_signal': '入场信号',\n 'exit_signal': '出场信号',\n 'price': '价格',\n 'equity': '资金',\n 'days': '天',\n 'date_range': '回测区间',\n 'original_idea': '原始策略想法',\n 'to': '至',\n }\n}\n\n\n# ============================================================================\n# DATA FETCHING\n# ============================================================================\n\ndef fetch_ohlcv(\n symbol: str = \"BTC/USDT\",\n timeframe: str = \"4h\",\n days: int = 365,\n exchange_id: str = \"binance\"\n) -> pd.DataFrame:\n \"\"\"Fetch historical OHLCV data from exchange.\"\"\"\n \n exchange_class = getattr(ccxt, exchange_id)\n exchange = exchange_class({'enableRateLimit': True})\n \n # Calculate start time\n since = exchange.parse8601((datetime.utcnow() - timedelta(days=days)).isoformat())\n \n all_ohlcv = []\n while True:\n ohlcv = exchange.fetch_ohlcv(symbol, timeframe, since=since, limit=1000)\n if not ohlcv:\n break\n all_ohlcv.extend(ohlcv)\n since = ohlcv[-1][0] + 1\n if len(ohlcv) \u003c 1000:\n break\n \n df = pd.DataFrame(all_ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])\n df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')\n df.set_index('timestamp', inplace=True)\n df = df[~df.index.duplicated(keep='first')]\n \n return df\n\n\n# ============================================================================\n# INDICATOR CALCULATION\n# ============================================================================\n\ndef calculate_indicators(df: pd.DataFrame, config: Dict) -> pd.DataFrame:\n \"\"\"Calculate comprehensive technical indicators.\n \n Indicators available after calculation:\n - Momentum: rsi, stoch_k, stoch_d, willr, cci, mfi, roc\n - Trend: sma{9,21,50,100,200}, ema{9,21,50,100,200}, adx, plus_di, minus_di\n - Volatility: bb_upper, bb_middle, bb_lower, bb_width, bb_pct, atr, atr_pct\n - Volume: volume_sma, volume_ratio, obv, obv_sma\n - Price Position: price_pct_from_high, price_pct_from_low, drawdown\n - Derived: price_change, price_pct_change, rsi_change, macd_change\n \"\"\"\n \n df = df.copy()\n \n # ========== MOMENTUM INDICATORS ==========\n \n # RSI\n df['rsi'] = ta.rsi(df['close'], length=config.get('rsi_period', 14))\n \n # MACD\n macd = ta.macd(\n df['close'],\n fast=config.get('macd_fast', 12),\n slow=config.get('macd_slow', 26),\n signal=config.get('macd_signal', 9)\n )\n if macd is not None:\n df['macd'] = macd.iloc[:, 0]\n df['macd_hist'] = macd.iloc[:, 1]\n df['macd_signal'] = macd.iloc[:, 2]\n \n # Stochastic (KDJ)\n stoch = ta.stoch(df['high'], df['low'], df['close'], k=14, d=3)\n if stoch is not None:\n df['stoch_k'] = stoch.iloc[:, 0]\n df['stoch_d'] = stoch.iloc[:, 1]\n \n # Williams %R\n df['willr'] = ta.willr(df['high'], df['low'], df['close'], length=14)\n \n # CCI (Commodity Channel Index)\n df['cci'] = ta.cci(df['high'], df['low'], df['close'], length=20)\n \n # MFI (Money Flow Index) - RSI with volume\n df['mfi'] = ta.mfi(df['high'], df['low'], df['close'], df['volume'], length=14)\n \n # ROC (Rate of Change)\n df['roc'] = ta.roc(df['close'], length=10)\n df['roc_20'] = ta.roc(df['close'], length=20)\n \n # ========== TREND INDICATORS ==========\n \n # Moving Averages\n for period in [9, 21, 50, 100, 200]:\n df[f'sma{period}'] = ta.sma(df['close'], length=period)\n df[f'ema{period}'] = ta.ema(df['close'], length=period)\n \n # ADX (Average Directional Index) - Trend Strength\n adx = ta.adx(df['high'], df['low'], df['close'], length=14)\n if adx is not None:\n df['adx'] = adx.iloc[:, 0]\n df['plus_di'] = adx.iloc[:, 1] # +DI\n df['minus_di'] = adx.iloc[:, 2] # -DI\n \n # ========== VOLATILITY INDICATORS ==========\n \n # Bollinger Bands\n bb = ta.bbands(df['close'], length=config.get('bb_period', 20), std=config.get('bb_std', 2.0))\n if bb is not None:\n df['bb_lower'] = bb.iloc[:, 0]\n df['bb_middle'] = bb.iloc[:, 1]\n df['bb_upper'] = bb.iloc[:, 2]\n df['bb_width'] = bb.iloc[:, 3] if bb.shape[1] > 3 else (df['bb_upper'] - df['bb_lower']) / df['bb_middle']\n df['bb_pct'] = bb.iloc[:, 4] if bb.shape[1] > 4 else (df['close'] - df['bb_lower']) / (df['bb_upper'] - df['bb_lower'])\n \n # ATR\n df['atr'] = ta.atr(df['high'], df['low'], df['close'], length=config.get('atr_period', 14))\n df['atr_pct'] = df['atr'] / df['close'] * 100 # ATR as percentage of price\n \n # ========== VOLUME INDICATORS ==========\n \n # Volume SMA and ratio\n df['volume_sma'] = ta.sma(df['volume'], length=20)\n df['volume_ratio'] = df['volume'] / df['volume_sma']\n \n # OBV (On-Balance Volume)\n df['obv'] = ta.obv(df['close'], df['volume'])\n df['obv_sma'] = ta.sma(df['obv'], length=20)\n \n # ========== PRICE POSITION INDICATORS ==========\n \n # Rolling High/Low (for drawdown and position calculation)\n for period in [20, 50, 90, 200]:\n df[f'high_{period}'] = df['high'].rolling(window=period).max()\n df[f'low_{period}'] = df['low'].rolling(window=period).min()\n \n # Drawdown from rolling high\n df['drawdown'] = (df['close'] - df['high_90']) / df['high_90'] * 100\n df['drawdown_50'] = (df['close'] - df['high_50']) / df['high_50'] * 100\n \n # Price position relative to range\n df['price_position_90'] = (df['close'] - df['low_90']) / (df['high_90'] - df['low_90'])\n \n # Distance from moving averages (percentage)\n df['dist_sma50'] = (df['close'] - df['sma50']) / df['sma50'] * 100\n df['dist_sma200'] = (df['close'] - df['sma200']) / df['sma200'] * 100\n \n # ========== DERIVED / CHANGE INDICATORS ==========\n \n # Price changes\n df['price_change'] = df['close'].diff()\n df['price_pct_change'] = df['close'].pct_change() * 100\n df['price_change_5'] = df['close'].diff(5)\n df['price_pct_change_5'] = df['close'].pct_change(5) * 100\n \n # Indicator changes (for \"turning\" detection)\n df['rsi_change'] = df['rsi'].diff()\n df['macd_change'] = df['macd'].diff() if 'macd' in df.columns else None\n df['macd_hist_change'] = df['macd_hist'].diff() if 'macd_hist' in df.columns else None\n \n # Consecutive conditions (count of consecutive up/down days)\n df['consecutive_up'] = df['close'].gt(df['close'].shift(1)).astype(int)\n df['consecutive_up'] = df['consecutive_up'].groupby((df['consecutive_up'] != df['consecutive_up'].shift()).cumsum()).cumsum()\n \n df['consecutive_down'] = df['close'].lt(df['close'].shift(1)).astype(int)\n df['consecutive_down'] = df['consecutive_down'].groupby((df['consecutive_down'] != df['consecutive_down'].shift()).cumsum()).cumsum()\n \n return df\n\n\n# ============================================================================\n# SIGNAL GENERATION\n# ============================================================================\n\ndef parse_conditions(condition_str: str) -> List[Dict]:\n \"\"\"Parse condition string into list of condition dicts.\n \n Supported patterns:\n \n 1. Simple comparison:\n \"rsi\u003c30\", \"price>sma50\", \"adx>=25\"\n \n 2. Percentage-based reference:\n \"price\u003csma200_98pct\" - Price below 98% of SMA200\n \"price>sma50_105pct\" - Price above 105% of SMA50\n \"price\u003cbb_lower\" - Price below BB lower band\n \n 3. Crossover/Crossunder:\n \"macd_crossover\" - MACD crosses above signal\n \"ema9_cross_above_ema21\" - EMA9 crosses above EMA21\n \"price_crossunder_sma200\" - Price crosses below SMA200\n \n 4. Consecutive periods:\n \"consecutive_up>=3\" - 3+ consecutive up days\n \"rsi\u003c30_for_3\" - RSI below 30 for 3 consecutive periods\n \n 5. Change/Turning:\n \"rsi_turning_up\" - RSI is increasing (change > 0)\n \"macd_hist_turning_down\" - MACD histogram decreasing\n \n 6. Percentile/Position:\n \"bb_pct\u003c0.2\" - Price in lower 20% of BB range\n \"price_position_90\u003c0.3\" - Price in lower 30% of 90-day range\n \n 7. Distance from MA:\n \"dist_sma200\u003c-10\" - Price 10% below SMA200\n \"\"\"\n conditions = []\n if not condition_str:\n return conditions\n \n for cond in condition_str.split(','):\n cond = cond.strip().lower()\n \n # Pattern: indicator_cross_above_indicator2 or indicator_crossover_indicator2\n cross_match = re.match(r'(\\w+)_(cross_?(?:over|above))_(\\w+)', cond)\n if cross_match:\n ind1, _, ind2 = cross_match.groups()\n conditions.append({'type': 'crossover', 'indicator': ind1, 'ref': ind2})\n continue\n \n cross_under_match = re.match(r'(\\w+)_(cross_?(?:under|below))_(\\w+)', cond)\n if cross_under_match:\n ind1, _, ind2 = cross_under_match.groups()\n conditions.append({'type': 'crossunder', 'indicator': ind1, 'ref': ind2})\n continue\n \n # Pattern: indicator_turning_up or indicator_turning_down\n turning_match = re.match(r'(\\w+)_turning_(up|down)', cond)\n if turning_match:\n indicator, direction = turning_match.groups()\n conditions.append({'type': 'turning', 'indicator': indicator, 'direction': direction})\n continue\n \n # Pattern: indicator\u003cvalue_for_N (consecutive periods)\n consecutive_match = re.match(r'(\\w+)(>=|\u003c=|>|\u003c|==)(\\d+(?:\\.\\d+)?)_for_(\\d+)', cond)\n if consecutive_match:\n indicator, op, value, periods = consecutive_match.groups()\n conditions.append({\n 'type': 'consecutive',\n 'indicator': indicator,\n 'op': op,\n 'value': float(value),\n 'periods': int(periods)\n })\n continue\n \n # Pattern: simple comparison like rsi\u003c30, price>sma50\n # Also supports percentage references: price\u003csma200_98pct, price>sma50_105pct\n match = re.match(r'(\\w+)(>=|\u003c=|>|\u003c|==|=)(\\w+(?:\\.\\d+)?)', cond)\n if match:\n indicator, op, value = match.groups()\n op = '==' if op == '=' else op\n \n # Check for percentage reference pattern: sma200_98pct, ema50_105pct\n pct_match = re.match(r'(\\w+)_(\\d+)pct', value)\n if pct_match:\n ref_indicator, pct = pct_match.groups()\n conditions.append({\n 'indicator': indicator, \n 'op': op, \n 'ref': ref_indicator,\n 'ref_pct': float(pct) / 100.0 # Convert 98 to 0.98\n })\n continue\n \n # Check if value is numeric or another indicator\n try:\n value = float(value)\n conditions.append({'indicator': indicator, 'op': op, 'value': value})\n except ValueError:\n conditions.append({'indicator': indicator, 'op': op, 'ref': value})\n continue\n \n # Legacy patterns for backwards compatibility\n if 'crossover' in cond or 'cross_above' in cond:\n parts = cond.replace('crossover', '').replace('cross_above', '').replace('_', '').strip()\n conditions.append({'type': 'crossover', 'indicator': parts or 'macd', 'ref': 'macd_signal' if not parts or 'macd' in parts else None})\n elif 'crossunder' in cond or 'cross_below' in cond:\n parts = cond.replace('crossunder', '').replace('cross_below', '').replace('_', '').strip()\n conditions.append({'type': 'crossunder', 'indicator': parts or 'macd', 'ref': 'macd_signal' if not parts or 'macd' in parts else None})\n \n return conditions\n\n\ndef evaluate_condition(df: pd.DataFrame, condition: Dict) -> pd.Series:\n \"\"\"Evaluate a single condition across the dataframe.\n \n Supports:\n - Simple comparisons (\u003c, >, \u003c=, >=, ==)\n - Crossover/Crossunder between any two indicators\n - Turning up/down detection\n - Consecutive periods meeting condition\n \"\"\"\n \n # Handle crossover between two indicators\n if condition.get('type') == 'crossover':\n ind = condition['indicator']\n ref = condition.get('ref')\n \n # Default ref for MACD\n if 'macd' in ind and ref is None:\n ref = 'macd_signal'\n ind = 'macd'\n \n # Get the two series\n if ind == 'price':\n series1 = df['close']\n elif ind in df.columns:\n series1 = df[ind]\n else:\n return pd.Series(False, index=df.index)\n \n if ref in df.columns:\n series2 = df[ref]\n else:\n return pd.Series(False, index=df.index)\n \n # Crossover: was below/equal, now above\n return (series1 > series2) & (series1.shift(1) \u003c= series2.shift(1))\n \n # Handle crossunder\n if condition.get('type') == 'crossunder':\n ind = condition['indicator']\n ref = condition.get('ref')\n \n if 'macd' in ind and ref is None:\n ref = 'macd_signal'\n ind = 'macd'\n \n if ind == 'price':\n series1 = df['close']\n elif ind in df.columns:\n series1 = df[ind]\n else:\n return pd.Series(False, index=df.index)\n \n if ref in df.columns:\n series2 = df[ref]\n else:\n return pd.Series(False, index=df.index)\n \n return (series1 \u003c series2) & (series1.shift(1) >= series2.shift(1))\n \n # Handle turning up/down\n if condition.get('type') == 'turning':\n ind = condition['indicator']\n direction = condition['direction']\n \n if ind in df.columns:\n series = df[ind]\n elif ind == 'price':\n series = df['close']\n else:\n return pd.Series(False, index=df.index)\n \n change = series.diff()\n \n if direction == 'up':\n # Turning up: current change > 0, previous change \u003c= 0\n return (change > 0) & (change.shift(1) \u003c= 0)\n else:\n # Turning down: current change \u003c 0, previous change >= 0\n return (change \u003c 0) & (change.shift(1) >= 0)\n \n # Handle consecutive periods\n if condition.get('type') == 'consecutive':\n ind = condition['indicator']\n op = condition['op']\n value = condition['value']\n periods = condition['periods']\n \n if ind in df.columns:\n series = df[ind]\n elif ind == 'price':\n series = df['close']\n else:\n return pd.Series(False, index=df.index)\n \n # Evaluate base condition\n if op == '\u003c':\n base_cond = series \u003c value\n elif op == '\u003c=':\n base_cond = series \u003c= value\n elif op == '>':\n base_cond = series > value\n elif op == '>=':\n base_cond = series >= value\n elif op == '==':\n base_cond = series == value\n else:\n return pd.Series(False, index=df.index)\n \n # Check if condition met for N consecutive periods\n # Rolling sum of True values, must equal periods\n consecutive_count = base_cond.astype(int).rolling(window=periods).sum()\n return consecutive_count >= periods\n \n # Standard comparison\n indicator = condition.get('indicator')\n op = condition.get('op')\n \n if not indicator or not op:\n return pd.Series(False, index=df.index)\n \n # Get indicator values\n if indicator == 'price':\n left = df['close']\n elif indicator in df.columns:\n left = df[indicator]\n else:\n return pd.Series(False, index=df.index)\n \n # Get comparison value\n if 'value' in condition:\n right = condition['value']\n elif 'ref' in condition:\n ref = condition['ref']\n if ref in df.columns:\n right = df[ref]\n # Apply percentage multiplier if specified (e.g., sma200_98pct -> SMA200 * 0.98)\n if 'ref_pct' in condition:\n right = right * condition['ref_pct']\n else:\n return pd.Series(False, index=df.index)\n else:\n return pd.Series(False, index=df.index)\n \n # Evaluate\n if op == '\u003c':\n return left \u003c right\n elif op == '\u003c=':\n return left \u003c= right\n elif op == '>':\n return left > right\n elif op == '>=':\n return left >= right\n elif op == '==':\n return left == right\n \n return pd.Series(False, index=df.index)\n\n\ndef generate_signals(\n df: pd.DataFrame,\n entry_conditions: List[Dict],\n exit_conditions: List[Dict]\n) -> pd.DataFrame:\n \"\"\"Generate trading signals based on conditions.\"\"\"\n \n df = df.copy()\n \n # Entry: ALL conditions must be true (AND)\n if entry_conditions:\n entry_signals = pd.Series(True, index=df.index)\n for cond in entry_conditions:\n entry_signals &= evaluate_condition(df, cond)\n else:\n entry_signals = pd.Series(False, index=df.index)\n \n # Exit: ANY condition can be true (OR)\n if exit_conditions:\n exit_signals = pd.Series(False, index=df.index)\n for cond in exit_conditions:\n exit_signals |= evaluate_condition(df, cond)\n else:\n exit_signals = pd.Series(False, index=df.index)\n \n df['entry_signal'] = entry_signals.astype(int)\n df['exit_signal'] = exit_signals.astype(int)\n \n return df\n\n\n# ============================================================================\n# PORTFOLIO SIMULATION\n# ============================================================================\n\ndef simulate_portfolio(\n df: pd.DataFrame,\n initial_capital: float = 10000,\n position_size_pct: float = 10,\n stop_loss_pct: float = 5,\n take_profit_pct: float = 15,\n commission_pct: float = 0.1,\n slippage_pct: float = 0.05\n) -> Dict:\n \"\"\"Simulate portfolio with position management.\"\"\"\n \n capital = initial_capital\n position = 0.0\n entry_price = 0.0\n trades = []\n equity_curve = []\n current_trade = None\n total_commission = 0.0 # Track total commission paid\n \n for i, (timestamp, row) in enumerate(df.iterrows()):\n price = row['close']\n \n # Check stop-loss / take-profit if in position\n if position > 0 and entry_price > 0:\n pnl_pct = (price - entry_price) / entry_price * 100\n \n # Stop loss\n if pnl_pct \u003c= -stop_loss_pct:\n exit_price = entry_price * (1 - stop_loss_pct / 100)\n gross_proceeds = position * exit_price\n commission = gross_proceeds * commission_pct / 100\n total_commission += commission\n proceeds = gross_proceeds - commission\n capital += proceeds\n \n if current_trade:\n current_trade['exit_time'] = timestamp\n current_trade['exit_price'] = exit_price\n current_trade['pnl_pct'] = -stop_loss_pct\n current_trade['pnl_amount'] = proceeds - current_trade['cost']\n current_trade['exit_reason'] = 'stop_loss'\n trades.append(current_trade)\n \n position = 0\n entry_price = 0\n current_trade = None\n \n # Take profit\n elif pnl_pct >= take_profit_pct:\n exit_price = entry_price * (1 + take_profit_pct / 100)\n gross_proceeds = position * exit_price\n commission = gross_proceeds * commission_pct / 100\n total_commission += commission\n proceeds = gross_proceeds - commission\n capital += proceeds\n \n if current_trade:\n current_trade['exit_time'] = timestamp\n current_trade['exit_price'] = exit_price\n current_trade['pnl_pct'] = take_profit_pct\n current_trade['pnl_amount'] = proceeds - current_trade['cost']\n current_trade['exit_reason'] = 'take_profit'\n trades.append(current_trade)\n \n position = 0\n entry_price = 0\n current_trade = None\n \n # Process signals\n if row['entry_signal'] == 1 and position == 0:\n # Buy\n position_value = capital * position_size_pct / 100\n actual_price = price * (1 + slippage_pct / 100)\n commission = position_value * commission_pct / 100\n total_commission += commission\n cost = position_value + commission\n \n if cost \u003c= capital:\n position = position_value / actual_price\n entry_price = actual_price\n capital -= cost\n \n current_trade = {\n 'entry_time': timestamp,\n 'entry_price': actual_price,\n 'position_size': position,\n 'cost': cost\n }\n \n elif row['exit_signal'] == 1 and position > 0:\n # Sell on signal\n actual_price = price * (1 - slippage_pct / 100)\n gross_proceeds = position * actual_price\n commission = gross_proceeds * commission_pct / 100\n total_commission += commission\n proceeds = gross_proceeds - commission\n pnl_pct = (actual_price - entry_price) / entry_price * 100\n capital += proceeds\n \n if current_trade:\n current_trade['exit_time'] = timestamp\n current_trade['exit_price'] = actual_price\n current_trade['pnl_pct'] = pnl_pct\n current_trade['pnl_amount'] = proceeds - current_trade['cost']\n current_trade['exit_reason'] = 'signal'\n trades.append(current_trade)\n \n position = 0\n entry_price = 0\n current_trade = None\n \n # Record equity\n equity = capital + (position * price if position > 0 else 0)\n equity_curve.append({\n 'timestamp': timestamp,\n 'equity': equity,\n 'price': price,\n 'position': position\n })\n \n # Close remaining position\n if position > 0:\n final_price = df.iloc[-1]['close']\n gross_proceeds = position * final_price\n commission = gross_proceeds * commission_pct / 100\n total_commission += commission\n proceeds = gross_proceeds - commission\n pnl_pct = (final_price - entry_price) / entry_price * 100\n capital += proceeds\n \n if current_trade:\n current_trade['exit_time'] = df.index[-1]\n current_trade['exit_price'] = final_price\n current_trade['pnl_pct'] = pnl_pct\n current_trade['pnl_amount'] = proceeds - current_trade['cost']\n current_trade['exit_reason'] = 'end_of_backtest'\n trades.append(current_trade)\n \n return {\n 'trades': trades,\n 'equity_curve': equity_curve,\n 'final_equity': equity_curve[-1]['equity'] if equity_curve else initial_capital,\n 'initial_capital': initial_capital,\n 'total_commission': total_commission\n }\n\n\n# ============================================================================\n# PERFORMANCE METRICS\n# ============================================================================\n\ndef calculate_metrics(results: Dict, df: pd.DataFrame) -> Dict:\n \"\"\"Calculate comprehensive performance metrics.\"\"\"\n \n equity_curve = results['equity_curve']\n trades = results['trades']\n initial_capital = results['initial_capital']\n final_equity = results['final_equity']\n \n # Basic returns\n total_return_pct = (final_equity - initial_capital) / initial_capital * 100\n \n # Trade statistics\n if trades:\n winning = [t for t in trades if t['pnl_pct'] > 0]\n losing = [t for t in trades if t['pnl_pct'] \u003c= 0]\n \n total_trades = len(trades)\n win_rate = len(winning) / total_trades * 100 if total_trades > 0 else 0\n \n avg_win = np.mean([t['pnl_pct'] for t in winning]) if winning else 0\n avg_loss = np.mean([t['pnl_pct'] for t in losing]) if losing else 0\n \n gross_profit = sum(t['pnl_amount'] for t in winning) if winning else 0\n gross_loss = abs(sum(t['pnl_amount'] for t in losing)) if losing else 0\n profit_factor = gross_profit / gross_loss if gross_loss > 0 else float('inf')\n else:\n total_trades = win_rate = avg_win = avg_loss = 0\n profit_factor = 0\n winning = losing = []\n \n # Drawdown\n equities = [e['equity'] for e in equity_curve]\n peak = equities[0]\n max_drawdown = 0\n drawdowns = []\n \n for eq in equities:\n if eq > peak:\n peak = eq\n dd = (peak - eq) / peak * 100\n drawdowns.append(dd)\n if dd > max_drawdown:\n max_drawdown = dd\n \n # Sharpe Ratio (annualized, assuming 0% risk-free rate)\n if len(equities) > 1:\n returns = [(equities[i] - equities[i-1]) / equities[i-1] for i in range(1, len(equities))]\n avg_return = np.mean(returns)\n std_return = np.std(returns)\n \n # Annualization factor depends on timeframe\n periods_per_year = 365 * 6 # Assume 4h default\n sharpe = (avg_return / std_return) * np.sqrt(periods_per_year) if std_return > 0 else 0\n else:\n sharpe = 0\n \n # Buy and hold comparison\n first_price = df.iloc[0]['close']\n last_price = df.iloc[-1]['close']\n buy_hold_return = (last_price - first_price) / first_price * 100\n \n # Additional trade stats\n if trades:\n all_pnls = [t['pnl_pct'] for t in trades]\n avg_trade = np.mean(all_pnls)\n best_trade = max(all_pnls)\n worst_trade = min(all_pnls)\n else:\n avg_trade = best_trade = worst_trade = 0\n \n # Get total commission from results\n total_commission = results.get('total_commission', 0)\n \n return {\n 'total_return_pct': round(total_return_pct, 2),\n 'max_drawdown_pct': round(max_drawdown, 2),\n 'sharpe_ratio': round(sharpe, 2),\n 'total_trades': total_trades,\n 'winning_trades': len(winning),\n 'losing_trades': len(losing),\n 'win_rate_pct': round(win_rate, 1),\n 'avg_win_pct': round(avg_win, 2),\n 'avg_loss_pct': round(avg_loss, 2),\n 'profit_factor': round(profit_factor, 2) if profit_factor != float('inf') else 'Inf',\n 'final_equity': round(final_equity, 2),\n 'initial_capital': initial_capital,\n 'buy_hold_return_pct': round(buy_hold_return, 2),\n 'drawdowns': drawdowns,\n 'avg_trade_pct': round(avg_trade, 2),\n 'best_trade_pct': round(best_trade, 2),\n 'worst_trade_pct': round(worst_trade, 2),\n 'total_commission': round(total_commission, 2)\n }\n\n\n# ============================================================================\n# STRATEGY ANALYSIS GENERATION\n# ============================================================================\n\ndef generate_strategy_analysis(metrics: Dict, config: Dict, lang: str = 'en') -> str:\n \"\"\"Generate professional strategy analysis based on backtest results.\"\"\"\n \n # Extract key metrics\n total_return = metrics.get('total_return_pct', 0)\n max_dd = metrics.get('max_drawdown_pct', 0)\n sharpe = metrics.get('sharpe_ratio', 0)\n win_rate = metrics.get('win_rate_pct', 0)\n profit_factor = metrics.get('profit_factor', 0)\n total_trades = metrics.get('total_trades', 0)\n buy_hold = metrics.get('buy_hold_return_pct', 0)\n avg_trade = metrics.get('avg_trade_pct', 0)\n \n if lang == 'zh':\n # Performance assessment\n if total_return > 20:\n perf = \"表现出色\"\n elif total_return > 5:\n perf = \"表现良好\"\n elif total_return > 0:\n perf = \"小幅盈利\"\n elif total_return > -5:\n perf = \"轻微亏损\"\n else:\n perf = \"表现不佳\"\n \n # Risk assessment\n if max_dd \u003c 5:\n risk = \"风险控制极佳\"\n elif max_dd \u003c 10:\n risk = \"风险可控\"\n elif max_dd \u003c 20:\n risk = \"风险中等\"\n else:\n risk = \"风险较高\"\n \n # Sharpe assessment\n if sharpe > 2:\n sharpe_eval = \"风险调整收益优秀\"\n elif sharpe > 1:\n sharpe_eval = \"风险调整收益良好\"\n elif sharpe > 0.5:\n sharpe_eval = \"风险调整收益一般\"\n else:\n sharpe_eval = \"风险调整收益偏低\"\n \n # Trade quality\n if win_rate >= 60 and avg_trade > 2:\n trade_quality = \"交易质量高,胜率和平均收益都表现不错\"\n elif win_rate >= 50:\n trade_quality = \"交易胜率尚可,但需关注单笔收益\"\n else:\n trade_quality = \"胜率偏低,策略可能依赖少数大赢家\"\n \n # vs Buy & Hold\n vs_bh = total_return - buy_hold\n if vs_bh > 10:\n bh_compare = f\"大幅跑赢买入持有策略 {vs_bh:+.1f}%\"\n elif vs_bh > 0:\n bh_compare = f\"小幅跑赢买入持有策略 {vs_bh:+.1f}%\"\n elif vs_bh > -10:\n bh_compare = f\"略逊于买入持有策略 {vs_bh:+.1f}%\"\n else:\n bh_compare = f\"明显落后于买入持有策略 {vs_bh:+.1f}%\"\n \n # Build analysis\n analysis = f\"\"\"\n**📊 绩效评估**:策略在回测期间{perf},总收益率 {total_return:+.2f}%。{bh_compare}。\n\n**⚠️ 风险评估**:最大回撤 {max_dd:.1f}%,{risk}。夏普比率 {sharpe:.2f},{sharpe_eval}。\n\n**📈 交易分析**:共执行 {total_trades} 笔交易,胜率 {win_rate:.1f}%,盈亏比 {profit_factor}。{trade_quality}。\n\n**💡 建议**:\"\"\"\n \n if total_return > 0 and max_dd \u003c 15 and sharpe > 0.5:\n analysis += \"策略整体表现稳健,可考虑在实盘中小仓位测试。建议持续监控关键指标,设置严格的风控规则。\"\n elif total_return > 0:\n analysis += \"策略有盈利潜力,但需注意风险控制。建议优化止损设置,或调整仓位管理来降低回撤。\"\n else:\n analysis += \"策略在当前参数下表现欠佳。建议重新审视入场条件、调整参数,或考虑其他市场环境。\"\n \n else: # English\n # Performance assessment\n if total_return > 20:\n perf = \"excellent performance\"\n elif total_return > 5:\n perf = \"solid performance\"\n elif total_return > 0:\n perf = \"modest gains\"\n elif total_return > -5:\n perf = \"slight losses\"\n else:\n perf = \"underperformance\"\n \n # Risk assessment\n if max_dd \u003c 5:\n risk = \"excellent risk control\"\n elif max_dd \u003c 10:\n risk = \"manageable risk\"\n elif max_dd \u003c 20:\n risk = \"moderate risk\"\n else:\n risk = \"elevated risk\"\n \n # Sharpe assessment\n if sharpe > 2:\n sharpe_eval = \"outstanding risk-adjusted returns\"\n elif sharpe > 1:\n sharpe_eval = \"good risk-adjusted returns\"\n elif sharpe > 0.5:\n sharpe_eval = \"acceptable risk-adjusted returns\"\n else:\n sharpe_eval = \"below-average risk-adjusted returns\"\n \n # Trade quality\n if win_rate >= 60 and avg_trade > 2:\n trade_quality = \"High trade quality with strong win rate and average profit\"\n elif win_rate >= 50:\n trade_quality = \"Decent win rate but monitor per-trade profitability\"\n else:\n trade_quality = \"Low win rate - strategy may rely on few big winners\"\n \n # vs Buy & Hold\n vs_bh = total_return - buy_hold\n if vs_bh > 10:\n bh_compare = f\"significantly outperformed buy-and-hold by {vs_bh:+.1f}%\"\n elif vs_bh > 0:\n bh_compare = f\"slightly outperformed buy-and-hold by {vs_bh:+.1f}%\"\n elif vs_bh > -10:\n bh_compare = f\"slightly underperformed buy-and-hold by {vs_bh:+.1f}%\"\n else:\n bh_compare = f\"significantly underperformed buy-and-hold by {vs_bh:+.1f}%\"\n \n # Build analysis\n analysis = f\"\"\"\n**📊 Performance**: The strategy showed {perf} with a total return of {total_return:+.2f}%. It {bh_compare}.\n\n**⚠️ Risk Assessment**: Maximum drawdown of {max_dd:.1f}% indicates {risk}. Sharpe ratio of {sharpe:.2f} suggests {sharpe_eval}.\n\n**📈 Trade Analysis**: {total_trades} trades executed with {win_rate:.1f}% win rate and {profit_factor} profit factor. {trade_quality}.\n\n**💡 Recommendation**: \"\"\"\n \n if total_return > 0 and max_dd \u003c 15 and sharpe > 0.5:\n analysis += \"Strategy shows robust performance. Consider paper trading or small position live testing. Maintain strict risk management and monitor key metrics.\"\n elif total_return > 0:\n analysis += \"Strategy has profit potential but needs risk optimization. Consider tightening stop losses or adjusting position sizing to reduce drawdown.\"\n else:\n analysis += \"Strategy underperformed with current parameters. Recommend reviewing entry conditions, parameter tuning, or testing in different market conditions.\"\n \n return analysis.strip()\n\n\n# ============================================================================\n# HTML REPORT GENERATION\n# ============================================================================\n\ndef generate_html_report(\n df: pd.DataFrame,\n results: Dict,\n metrics: Dict,\n config: Dict,\n lang: str = 'en'\n) -> str:\n \"\"\"Generate beautiful interactive HTML report.\n \n Args:\n df: DataFrame with OHLCV data and indicators\n results: Backtest results dict\n metrics: Performance metrics dict\n config: Strategy configuration\n lang: Language code ('en' or 'zh')\n \"\"\"\n L = LABELS.get(lang, LABELS['en'])\n \n # Prepare data for charts\n timestamps = [str(t) for t in df.index.tolist()]\n \n # Equity curve data\n equity_data = results['equity_curve']\n equity_times = [str(e['timestamp']) for e in equity_data]\n equity_values = [e['equity'] for e in equity_data]\n \n # Trade markers\n trades = results['trades']\n buy_times = [str(t['entry_time']) for t in trades]\n buy_prices = [t['entry_price'] for t in trades]\n sell_times = [str(t['exit_time']) for t in trades]\n sell_prices = [t['exit_price'] for t in trades]\n \n # Determine result colors\n return_class = 'positive' if metrics['total_return_pct'] > 0 else 'negative'\n vs_bh = metrics['total_return_pct'] - metrics['buy_hold_return_pct']\n vs_bh_class = 'positive' if vs_bh > 0 else 'negative'\n \n # Format trades for table\n trades_html = ''\n for t in trades[-15:]: # Last 15 trades\n pnl_class = 'positive' if t['pnl_pct'] > 0 else 'negative'\n pnl_amount = t.get('pnl_amount', 0)\n cost = t.get('cost', 0)\n position_size = t.get('position_size', 0)\n trades_html += f'''\n \u003ctr>\n \u003ctd>{str(t['entry_time'])[:16]}\u003c/td>\n \u003ctd>{str(t['exit_time'])[:16]}\u003c/td>\n \u003ctd>${cost:,.2f}\u003c/td>\n \u003ctd>{position_size:,.6f}\u003c/td>\n \u003ctd>${t['entry_price']:,.2f}\u003c/td>\n \u003ctd>${t['exit_price']:,.2f}\u003c/td>\n \u003ctd class=\"{pnl_class}\">{t['pnl_pct']:+.2f}%\u003c/td>\n \u003ctd class=\"{pnl_class}\">${pnl_amount:+,.2f}\u003c/td>\n \u003ctd class=\"exit-reason\">{t['exit_reason']}\u003c/td>\n \u003c/tr>'''\n \n # Generate strategy analysis\n analysis_text = generate_strategy_analysis(metrics, config, lang)\n # Convert markdown-style bold to HTML\n analysis_html = analysis_text.replace('**', '\u003c/strong>').replace('\u003c/strong>', '\u003cstrong>', 1)\n # Fix alternating strong tags\n parts = analysis_text.split('**')\n analysis_html = ''\n for i, part in enumerate(parts):\n if i % 2 == 1:\n analysis_html += f'\u003cstrong>{part}\u003c/strong>'\n else:\n analysis_html += part\n analysis_html = analysis_html.replace('\\n\\n', '\u003c/p>\u003cp>').replace('\\n', '\u003cbr>')\n analysis_html = f'\u003cp>{analysis_html}\u003c/p>'\n \n html = f'''\u003c!DOCTYPE html>\n\u003chtml lang=\"en\">\n\u003chead>\n \u003cmeta charset=\"UTF-8\">\n \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n \u003ctitle>Strategy Backtest Report | {config.get('symbol', 'BTC/USDT')}\u003c/title>\n \u003cscript src=\"https://cdn.plot.ly/plotly-2.27.0.min.js\">\u003c/script>\n \u003clink href=\"https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap\" rel=\"stylesheet\">\n \u003cstyle>\n :root {{\n /* Light theme - clean and professional */\n --bg-void: #f8fafc;\n --bg-deep: #ffffff;\n --bg-surface: #ffffff;\n --bg-elevated: #f1f5f9;\n --bg-hover: #e2e8f0;\n --text-primary: #1e293b;\n --text-secondary: #64748b;\n --text-muted: #94a3b8;\n --accent-cyan: #0ea5e9;\n --accent-green: #10b981;\n --accent-red: #ef4444;\n --accent-gold: #f59e0b;\n --accent-purple: #8b5cf6;\n --gradient-cyan: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%);\n --gradient-green: linear-gradient(135deg, #10b981 0%, #059669 100%);\n --gradient-gold: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);\n --border-subtle: #e2e8f0;\n --border-accent: rgba(14,165,233,0.4);\n --glow-cyan: 0 4px 20px rgba(14,165,233,0.15);\n --glow-green: 0 4px 20px rgba(16,185,129,0.15);\n }}\n \n * {{ margin: 0; padding: 0; box-sizing: border-box; }}\n \n body {{\n font-family: 'Space Grotesk', -apple-system, sans-serif;\n background: var(--bg-void);\n color: var(--text-primary);\n line-height: 1.6;\n min-height: 100vh;\n }}\n \n /* Subtle background pattern */\n .bg-pattern {{\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: \n radial-gradient(ellipse at 20% 20%, rgba(14,165,233,0.05) 0%, transparent 50%),\n radial-gradient(ellipse at 80% 80%, rgba(139,92,246,0.03) 0%, transparent 50%);\n pointer-events: none;\n z-index: 0;\n }}\n \n .container {{\n position: relative;\n z-index: 1;\n max-width: 1400px;\n margin: 0 auto;\n padding: 40px 24px;\n }}\n \n /* Header */\n .header {{\n text-align: center;\n padding: 60px 0;\n border-bottom: 1px solid var(--border-subtle);\n margin-bottom: 48px;\n }}\n \n .header-subtitle {{\n font-size: 0.85rem;\n letter-spacing: 1px;\n text-transform: uppercase;\n color: var(--text-muted);\n margin-bottom: 12px;\n }}\n \n .header-title {{\n font-size: clamp(1.5rem, 3.5vw, 2.2rem);\n font-weight: 600;\n letter-spacing: -0.01em;\n margin-bottom: 16px;\n color: var(--text-primary);\n max-width: 800px;\n margin-left: auto;\n margin-right: auto;\n line-height: 1.3;\n }}\n \n .header-meta {{\n display: flex;\n justify-content: center;\n gap: 24px;\n flex-wrap: wrap;\n color: var(--text-secondary);\n font-size: 0.85rem;\n }}\n \n .header-meta span {{\n display: flex;\n align-items: center;\n gap: 6px;\n }}\n \n .header-meta .dot {{\n width: 5px;\n height: 5px;\n background: var(--accent-cyan);\n border-radius: 50%;\n }}\n \n /* Metrics Grid */\n .metrics-hero {{\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 20px;\n margin-bottom: 48px;\n }}\n \n .metric-card {{\n background: var(--bg-surface);\n border: 1px solid var(--border-subtle);\n border-radius: 16px;\n padding: 28px;\n text-align: center;\n transition: all 0.3s ease;\n position: relative;\n overflow: hidden;\n }}\n \n .metric-card::before {{\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 3px;\n background: var(--gradient-cyan);\n opacity: 0;\n transition: opacity 0.3s ease;\n }}\n \n .metric-card:hover {{\n transform: translateY(-4px);\n border-color: var(--border-accent);\n box-shadow: var(--glow-cyan);\n }}\n \n .metric-card:hover::before {{\n opacity: 1;\n }}\n \n .metric-card.hero {{\n background: linear-gradient(135deg, var(--bg-elevated) 0%, var(--bg-surface) 100%);\n border-color: var(--border-accent);\n }}\n \n .metric-card.hero::before {{\n opacity: 1;\n }}\n \n .metric-value {{\n font-family: 'JetBrains Mono', monospace;\n font-size: 2.2rem;\n font-weight: 700;\n margin-bottom: 8px;\n letter-spacing: -0.02em;\n }}\n \n .metric-value.positive {{ color: var(--accent-green); }}\n .metric-value.negative {{ color: var(--accent-red); }}\n .metric-value.neutral {{ color: var(--accent-cyan); }}\n \n .metric-label {{\n color: var(--text-secondary);\n font-size: 0.85rem;\n text-transform: uppercase;\n letter-spacing: 1px;\n }}\n \n .metric-sub {{\n margin-top: 12px;\n padding-top: 12px;\n border-top: 1px solid var(--border-subtle);\n font-size: 0.8rem;\n color: var(--text-muted);\n }}\n \n /* Sections */\n .section {{\n background: var(--bg-surface);\n border: 1px solid var(--border-subtle);\n border-radius: 20px;\n padding: 32px;\n margin-bottom: 32px;\n }}\n \n .section-header {{\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 24px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--border-subtle);\n }}\n \n .section-icon {{\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--bg-elevated);\n border-radius: 10px;\n font-size: 1.2rem;\n }}\n \n .section h2 {{\n font-size: 1.25rem;\n font-weight: 600;\n }}\n \n /* Strategy Summary */\n .strategy-summary {{\n border: 1px solid var(--border-subtle);\n background: var(--bg-surface);\n box-shadow: 0 1px 3px rgba(0,0,0,0.05);\n }}\n \n .original-idea {{\n background: var(--bg-elevated);\n border-left: 4px solid var(--accent-cyan);\n padding: 16px 20px;\n margin-bottom: 24px;\n border-radius: 0 8px 8px 0;\n font-style: italic;\n color: var(--text-secondary);\n }}\n \n .original-idea strong {{\n display: block;\n font-style: normal;\n font-size: 0.75rem;\n text-transform: uppercase;\n letter-spacing: 1px;\n color: var(--text-muted);\n margin-bottom: 8px;\n }}\n \n .strategy-info-grid {{\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 16px;\n margin-bottom: 24px;\n padding: 20px;\n background: var(--bg-elevated);\n border-radius: 12px;\n }}\n \n .info-item {{\n display: flex;\n flex-direction: column;\n gap: 4px;\n }}\n \n .info-label {{\n font-size: 0.75rem;\n text-transform: uppercase;\n letter-spacing: 1px;\n color: var(--text-muted);\n }}\n \n .info-value {{\n font-size: 1.1rem;\n font-weight: 600;\n color: var(--text-primary);\n font-family: 'JetBrains Mono', monospace;\n }}\n \n /* Strategy Rules */\n .strategy-grid {{\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));\n gap: 24px;\n }}\n \n .rule-block {{\n background: var(--bg-elevated);\n border-radius: 12px;\n padding: 24px;\n }}\n \n .rule-block.entry-block {{\n border-left: 3px solid var(--accent-green);\n }}\n \n .rule-block.exit-block {{\n border-left: 3px solid var(--accent-red);\n }}\n \n .rule-block.risk-block {{\n border-left: 3px solid var(--accent-gold);\n }}\n \n .rule-block h3 {{\n font-size: 0.9rem;\n color: var(--accent-cyan);\n margin-bottom: 16px;\n display: flex;\n align-items: center;\n gap: 8px;\n }}\n \n .rule-block ul {{\n list-style: none;\n }}\n \n .rule-block li {{\n padding: 8px 0;\n border-bottom: 1px solid var(--border-subtle);\n font-size: 0.9rem;\n }}\n \n .rule-block li code {{\n font-family: 'JetBrains Mono', monospace;\n background: var(--bg-deep);\n padding: 2px 8px;\n border-radius: 4px;\n color: var(--accent-cyan);\n }}\n \n .rule-block li:last-child {{\n border-bottom: none;\n }}\n \n /* Strategy Compact Layout - 3x2 uniform grid */\n .strategy-compact {{\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n grid-template-rows: repeat(2, 140px);\n gap: 12px;\n }}\n \n @media (max-width: 900px) {{\n .strategy-compact {{ \n grid-template-columns: repeat(2, 1fr); \n grid-template-rows: repeat(3, 140px);\n }}\n }}\n \n @media (max-width: 600px) {{\n .strategy-compact {{ \n grid-template-columns: 1fr; \n grid-template-rows: repeat(6, auto);\n }}\n }}\n \n .strategy-block {{\n background: var(--bg-elevated);\n border-radius: 8px;\n padding: 14px 16px;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }}\n \n .strategy-block h4 {{\n font-size: 0.7rem;\n font-weight: 700;\n color: var(--text-muted);\n margin-bottom: 10px;\n text-transform: uppercase;\n letter-spacing: 1px;\n flex-shrink: 0;\n }}\n \n .strategy-block .param-row:first-of-type {{\n margin-top: auto;\n }}\n \n .signal-codes {{\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: auto;\n }}\n \n .param-row {{\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 4px 0;\n font-size: 0.85rem;\n }}\n \n .param-row span {{\n color: var(--text-secondary);\n font-size: 0.8rem;\n }}\n \n .param-row code {{\n font-family: 'JetBrains Mono', monospace;\n background: var(--bg-deep);\n padding: 2px 6px;\n border-radius: 3px;\n font-size: 0.8rem;\n color: var(--accent-cyan);\n }}\n \n .param-row code.green {{ color: var(--accent-green); }}\n .param-row code.red {{ color: var(--accent-red); }}\n \n .signal-block code {{\n display: inline-block;\n font-family: 'JetBrains Mono', monospace;\n background: var(--bg-deep);\n padding: 3px 8px;\n border-radius: 3px;\n font-size: 0.8rem;\n margin: 2px 4px 2px 0;\n }}\n \n .signal-block.entry code {{ \n border-left: 2px solid var(--accent-green);\n color: var(--accent-green);\n }}\n \n .signal-block.exit code {{ \n border-left: 3px solid var(--accent-red);\n color: var(--accent-red);\n }}\n \n /* Metrics Table */\n .metrics-table-container {{\n overflow-x: auto;\n }}\n \n .metrics-table {{\n width: 100%;\n border-collapse: collapse;\n font-size: 0.9rem;\n }}\n \n .metrics-table thead th {{\n background: var(--bg-elevated);\n padding: 16px 20px;\n text-align: center;\n font-weight: 600;\n color: var(--text-secondary);\n text-transform: uppercase;\n letter-spacing: 1px;\n font-size: 0.75rem;\n border-bottom: 2px solid var(--border-subtle);\n }}\n \n .metrics-table tbody tr {{\n border-bottom: 1px solid var(--border-subtle);\n }}\n \n .metrics-table tbody tr:hover {{\n background: var(--bg-elevated);\n }}\n \n .metrics-table td {{\n padding: 14px 20px;\n }}\n \n .metrics-table .metric-name {{\n color: var(--text-secondary);\n font-weight: 500;\n position: relative;\n cursor: help;\n }}\n \n .metrics-table .metric-name[data-tooltip]:hover::after {{\n content: attr(data-tooltip);\n position: absolute;\n bottom: 100%;\n left: 50%;\n transform: translateX(-50%);\n background: var(--text-primary);\n color: var(--bg-surface);\n padding: 8px 12px;\n border-radius: 6px;\n font-size: 0.75rem;\n font-weight: 400;\n white-space: nowrap;\n z-index: 100;\n box-shadow: 0 4px 12px rgba(0,0,0,0.15);\n }}\n \n .metrics-table .metric-name[data-tooltip]:hover::before {{\n content: '';\n position: absolute;\n bottom: calc(100% - 6px);\n left: 50%;\n transform: translateX(-50%);\n border: 6px solid transparent;\n border-top-color: var(--text-primary);\n z-index: 100;\n }}\n \n .metrics-table .metric-val {{\n font-family: 'JetBrains Mono', monospace;\n font-weight: 600;\n text-align: right;\n }}\n \n .metrics-table .metric-val.positive {{ color: var(--accent-green); }}\n .metrics-table .metric-val.negative {{ color: var(--accent-red); }}\n \n /* Charts */\n .chart-container {{\n background: var(--bg-deep);\n border-radius: 12px;\n padding: 16px;\n margin-bottom: 24px;\n min-height: 200px;\n width: 100%;\n }}\n \n .chart-container#equity-chart {{\n min-height: 380px;\n }}\n \n .chart-container#drawdown-chart {{\n min-height: 250px;\n }}\n \n .chart-container#price-chart {{\n min-height: 480px;\n }}\n \n /* Side-by-side Charts */\n .charts-row {{\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 24px;\n margin-bottom: 24px;\n }}\n \n .chart-half {{\n margin-bottom: 0;\n }}\n \n .chart-half .chart-container {{\n min-height: 280px;\n margin-bottom: 0;\n }}\n \n @media (max-width: 992px) {{\n .charts-row {{\n grid-template-columns: 1fr;\n }}\n }}\n \n /* Trades Table */\n .trades-table {{\n width: 100%;\n border-collapse: collapse;\n font-size: 0.9rem;\n }}\n \n .trades-table th {{\n text-align: left;\n padding: 16px;\n background: var(--bg-elevated);\n color: var(--text-secondary);\n font-weight: 500;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n font-size: 0.75rem;\n }}\n \n .trades-table th:first-child {{\n border-radius: 8px 0 0 8px;\n }}\n \n .trades-table th:last-child {{\n border-radius: 0 8px 8px 0;\n }}\n \n .trades-table td {{\n padding: 14px 16px;\n border-bottom: 1px solid var(--border-subtle);\n font-family: 'JetBrains Mono', monospace;\n }}\n \n .trades-table tr:hover td {{\n background: var(--bg-hover);\n }}\n \n .trades-table .positive {{ color: var(--accent-green); }}\n .trades-table .negative {{ color: var(--accent-red); }}\n \n .exit-reason {{\n padding: 4px 10px;\n border-radius: 12px;\n font-size: 0.75rem;\n background: var(--bg-elevated);\n }}\n \n /* Footer */\n .footer {{\n margin-top: 48px;\n padding: 32px 24px;\n text-align: center;\n border-top: 1px solid var(--border-subtle);\n background: var(--bg-surface);\n }}\n \n .footer-tagline {{\n font-size: 1rem;\n font-weight: 500;\n color: var(--text-primary);\n margin-bottom: 16px;\n }}\n \n .footer-github {{\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 10px 20px;\n background: var(--bg-elevated);\n border: 1px solid var(--border-subtle);\n border-radius: 8px;\n color: var(--text-secondary);\n text-decoration: none;\n font-size: 0.9rem;\n transition: all 0.2s ease;\n }}\n \n .footer-github:hover {{\n background: var(--bg-hover);\n border-color: var(--border-accent);\n color: var(--text-primary);\n }}\n \n .footer-github svg {{\n opacity: 0.7;\n }}\n \n .footer-github:hover svg {{\n opacity: 1;\n }}\n \n /* Analysis Section */\n .analysis-section {{\n background: linear-gradient(135deg, var(--bg-surface) 0%, var(--bg-elevated) 100%);\n border: 1px solid var(--border-accent);\n }}\n \n .analysis-content {{\n font-size: 0.95rem;\n line-height: 1.8;\n color: var(--text-secondary);\n }}\n \n .analysis-content p {{\n margin-bottom: 16px;\n }}\n \n .analysis-content p:last-child {{\n margin-bottom: 0;\n }}\n \n .analysis-content strong {{\n color: var(--text-primary);\n font-weight: 600;\n }}\n \n .footer-note {{\n margin-top: 16px;\n color: var(--text-muted);\n font-size: 0.75rem;\n }}\n \n /* Responsive */\n @media (max-width: 768px) {{\n .container {{ padding: 20px 16px; }}\n .header {{ padding: 40px 0; }}\n .header h1 {{ font-size: 2rem; }}\n .metric-value {{ font-size: 1.6rem; }}\n .section {{ padding: 20px; }}\n }}\n \u003c/style>\n\u003c/head>\n\u003cbody>\n \u003cdiv class=\"bg-pattern\">\u003c/div>\n \n \u003cdiv class=\"container\">\n \u003cheader class=\"header\">\n \u003ch1 class=\"header-title\">{L['title']}\u003c/h1>\n \u003cdiv class=\"header-subtitle\">{config.get('description') or config.get('name') or ''}\u003c/div>\n \u003cdiv class=\"header-meta\">\n \u003cspan>\u003cdiv class=\"dot\">\u003c/div>{config.get('symbol', 'BTC/USDT')}\u003c/span>\n \u003cspan>\u003cdiv class=\"dot\">\u003c/div>{config.get('timeframe', '4h')}\u003c/span>\n \u003cspan>\u003cdiv class=\"dot\">\u003c/div>{config.get('start_date', 'N/A')} → {config.get('end_date', 'N/A')}\u003c/span>\n \u003cspan>\u003cdiv class=\"dot\">\u003c/div>{metrics['total_trades']} trades\u003c/span>\n \u003c/div>\n \u003c/header>\n \n \u003c!-- Complete Strategy Configuration -->\n \u003csection class=\"section strategy-summary\">\n \u003cdiv class=\"section-header\">\n \u003cdiv class=\"section-icon\">📋\u003c/div>\n \u003ch2>{L['strategy_summary']}\u003c/h2>\n \u003c/div>\n \n \u003cdiv class=\"strategy-compact\">\n \u003c!-- Row 1 -->\n \u003cdiv class=\"strategy-block\">\n \u003ch4>📊 DATA\u003c/h4>\n \u003cdiv class=\"param-row\">\u003cspan>Symbol\u003c/span>\u003ccode>{config.get('symbol', 'BTC/USDT')}\u003c/code>\u003c/div>\n \u003cdiv class=\"param-row\">\u003cspan>Timeframe\u003c/span>\u003ccode>{config.get('timeframe', '4h')}\u003c/code>\u003c/div>\n \u003cdiv class=\"param-row\">\u003cspan>Period\u003c/span>\u003ccode>{config.get('start_date', 'N/A')} → {config.get('end_date', 'N/A')}\u003c/code>\u003c/div>\n \u003c/div>\n \u003cdiv class=\"strategy-block signal-block entry\">\n \u003ch4>🟢 ENTRY\u003c/h4>\n \u003cdiv class=\"signal-codes\">{''.join(f'\u003ccode>{c}\u003c/code>' for c in config.get('entry_display', ['N/A']))}\u003c/div>\n \u003c/div>\n \u003cdiv class=\"strategy-block\">\n \u003ch4>⚠️ RISK\u003c/h4>\n \u003cdiv class=\"param-row\">\u003cspan>Stop Loss\u003c/span>\u003ccode class=\"red\">-{config.get('stop_loss', 5)}%\u003c/code>\u003c/div>\n \u003cdiv class=\"param-row\">\u003cspan>Take Profit\u003c/span>\u003ccode class=\"green\">+{config.get('take_profit', 15)}%\u003c/code>\u003c/div>\n \u003c/div>\n \u003c!-- Row 2 -->\n \u003cdiv class=\"strategy-block\">\n \u003ch4>💰 CAPITAL\u003c/h4>\n \u003cdiv class=\"param-row\">\u003cspan>Initial\u003c/span>\u003ccode>${config.get('initial_capital', 10000):,.0f}\u003c/code>\u003c/div>\n \u003cdiv class=\"param-row\">\u003cspan>Position\u003c/span>\u003ccode>{config.get('position_size', 10)}%\u003c/code>\u003c/div>\n \u003cdiv class=\"param-row\">\u003cspan>Fee\u003c/span>\u003ccode>{config.get('commission', 0.1)}%\u003c/code>\u003c/div>\n \u003c/div>\n \u003cdiv class=\"strategy-block signal-block exit\">\n \u003ch4>🔴 EXIT\u003c/h4>\n \u003cdiv class=\"signal-codes\">{''.join(f'\u003ccode>{c}\u003c/code>' for c in config.get('exit_display', ['N/A']))}\u003c/div>\n \u003c/div>\n \u003cdiv class=\"strategy-block\">\n \u003ch4>⚙️ EXECUTION\u003c/h4>\n \u003cdiv class=\"param-row\">\u003cspan>Leverage\u003c/span>\u003ccode>1x\u003c/code>\u003c/div>\n \u003cdiv class=\"param-row\">\u003cspan>Type\u003c/span>\u003ccode>Market\u003c/code>\u003c/div>\n \u003cdiv class=\"param-row\">\u003cspan>Side\u003c/span>\u003ccode>Long\u003c/code>\u003c/div>\n \u003c/div>\n \u003c/div>\n \u003c/section>\n \n \u003c!-- Trade History - Chart + Table combined -->\n \u003csection class=\"section\">\n \u003cdiv class=\"section-header\">\n \u003cdiv class=\"section-icon\">📈\u003c/div>\n \u003ch2>{L['trade_table_title']}\u003c/h2>\n \u003c/div>\n \u003cdiv class=\"chart-container\" id=\"price-chart\">\u003c/div>\n \u003ctable class=\"trades-table\">\n \u003cthead>\n \u003ctr>\n \u003cth>{L['trade_entry_date']}\u003c/th>\n \u003cth>{L['trade_exit_date']}\u003c/th>\n \u003cth>{L['trade_cost']}\u003c/th>\n \u003cth>{L['trade_quantity']}\u003c/th>\n \u003cth>{L['trade_entry_price']}\u003c/th>\n \u003cth>{L['trade_exit_price']}\u003c/th>\n \u003cth>{L['trade_pnl_pct']}\u003c/th>\n \u003cth>{L['trade_pnl_amount']}\u003c/th>\n \u003cth>{'原因' if lang == 'zh' else 'Reason'}\u003c/th>\n \u003c/tr>\n \u003c/thead>\n \u003ctbody>\n {trades_html}\n \u003c/tbody>\n \u003c/table>\n \u003c/section>\n \n \u003c!-- Performance Metrics - Professional Table Layout -->\n \u003csection class=\"section\">\n \u003cdiv class=\"section-header\">\n \u003cdiv class=\"section-icon\">📊\u003c/div>\n \u003ch2>{L['performance_metrics']}\u003c/h2>\n \u003c/div>\n \n \u003cdiv class=\"metrics-table-container\">\n \u003ctable class=\"metrics-table\">\n \u003cthead>\n \u003ctr>\n \u003cth colspan=\"2\">Returns\u003c/th>\n \u003cth colspan=\"2\">Risk\u003c/th>\n \u003cth colspan=\"2\">Trading\u003c/th>\n \u003c/tr>\n \u003c/thead>\n \u003ctbody>\n \u003ctr>\n \u003ctd class=\"metric-name\" data-tooltip=\"{'策略总收益率 = (最终资金 - 初始资金) / 初始资金' if lang == 'zh' else 'Total profit/loss as % of initial capital'}\">{L['total_return']}\u003c/td>\n \u003ctd class=\"metric-val {return_class}\">{metrics['total_return_pct']:+.2f}%\u003c/td>\n \u003ctd class=\"metric-name\" data-tooltip=\"{'最大回撤:从历史最高点到最低点的最大跌幅,衡量最坏情况下的亏损' if lang == 'zh' else 'Largest peak-to-trough decline, measures worst-case loss'}\">{L['max_drawdown']}\u003c/td>\n \u003ctd class=\"metric-val negative\">-{metrics['max_drawdown_pct']:.2f}%\u003c/td>\n \u003ctd class=\"metric-name\" data-tooltip=\"{'回测期间完成的交易总次数(买入+卖出算一次)' if lang == 'zh' else 'Total number of completed trades (buy + sell = 1 trade)'}\">{L['total_trades']}\u003c/td>\n \u003ctd class=\"metric-val\">{metrics['total_trades']}\u003c/td>\n \u003c/tr>\n \u003ctr>\n \u003ctd class=\"metric-name\" data-tooltip=\"{'买入持有策略收益:在回测开始时买入并持有到结束的收益率' if lang == 'zh' else 'Return if you bought at start and held until end'}\">Buy & Hold\u003c/td>\n \u003ctd class=\"metric-val\">{metrics['buy_hold_return_pct']:+.2f}%\u003c/td>\n \u003ctd class=\"metric-name\" data-tooltip=\"{'夏普比率:风险调整后收益,>1为好,>2为优秀' if lang == 'zh' else 'Risk-adjusted return. >1 is good, >2 is excellent'}\">{L['sharpe_ratio']}\u003c/td>\n \u003ctd class=\"metric-val\">{metrics['sharpe_ratio']:.2f}\u003c/td>\n \u003ctd class=\"metric-name\" data-tooltip=\"{'胜率:盈利交易占总交易的百分比' if lang == 'zh' else 'Percentage of profitable trades'}\">{L['win_rate']}\u003c/td>\n \u003ctd class=\"metric-val {'positive' if metrics['win_rate_pct'] > 50 else 'negative'}\">{metrics['win_rate_pct']:.1f}%\u003c/td>\n \u003c/tr>\n \u003ctr>\n \u003ctd class=\"metric-name\" data-tooltip=\"{'策略收益 vs 买入持有收益的差值,正数表示跑赢大盘' if lang == 'zh' else 'Strategy return minus buy-and-hold. Positive = outperformed'}\">vs B&H\u003c/td>\n \u003ctd class=\"metric-val {vs_bh_class}\">{vs_bh:+.2f}%\u003c/td>\n \u003ctd class=\"metric-name\" data-tooltip=\"{'盈亏比:总盈利 / 总亏损,>1表示总体盈利' if lang == 'zh' else 'Gross profit / gross loss. >1 means overall profitable'}\">{L['profit_factor']}\u003c/td>\n \u003ctd class=\"metric-val\">{metrics['profit_factor']}\u003c/td>\n \u003ctd class=\"metric-name\" data-tooltip=\"{'盈利交易数 / 亏损交易数' if lang == 'zh' else 'Winning trades vs losing trades count'}\">W/L Ratio\u003c/td>\n \u003ctd class=\"metric-val\">{metrics['winning_trades']}W / {metrics['losing_trades']}L\u003c/td>\n \u003c/tr>\n \u003ctr>\n \u003ctd class=\"metric-name\" data-tooltip=\"{'回测结束时的账户总资金' if lang == 'zh' else 'Account value at end of backtest'}\">Final Equity\u003c/td>\n \u003ctd class=\"metric-val\">${metrics['final_equity']:,.0f}\u003c/td>\n \u003ctd class=\"metric-name\" data-tooltip=\"{'所有交易的平均收益率' if lang == 'zh' else 'Average return per trade'}\">Avg Trade\u003c/td>\n \u003ctd class=\"metric-val\">{metrics.get('avg_trade_pct', 0):+.2f}%\u003c/td>\n \u003ctd class=\"metric-name\" data-tooltip=\"{'单笔交易的最高收益率' if lang == 'zh' else 'Highest return from a single trade'}\">Best Trade\u003c/td>\n \u003ctd class=\"metric-val positive\">{metrics.get('best_trade_pct', 0):+.2f}%\u003c/td>\n \u003c/tr>\n \u003ctr>\n \u003ctd class=\"metric-name\" data-tooltip=\"{'回测开始时的初始资金' if lang == 'zh' else 'Starting capital for backtest'}\">Initial\u003c/td>\n \u003ctd class=\"metric-val\">${metrics['initial_capital']:,.0f}\u003c/td>\n \u003ctd class=\"metric-name\" data-tooltip=\"{'所有交易支付的手续费总额(含买入和卖出)' if lang == 'zh' else 'Total fees paid for all trades (buy + sell)'}\">{'手续费' if lang == 'zh' else 'Commission'}\u003c/td>\n \u003ctd class=\"metric-val negative\">${metrics.get('total_commission', 0):,.2f}\u003c/td>\n \u003ctd class=\"metric-name\" data-tooltip=\"{'单笔交易的最大亏损' if lang == 'zh' else 'Largest loss from a single trade'}\">Worst Trade\u003c/td>\n \u003ctd class=\"metric-val negative\">{metrics.get('worst_trade_pct', 0):+.2f}%\u003c/td>\n \u003c/tr>\n \u003c/tbody>\n \u003c/table>\n \u003c/div>\n \u003c/section>\n \n \u003c!-- Equity Curve & Drawdown - Side by side -->\n \u003cdiv class=\"charts-row\">\n \u003csection class=\"section chart-half\">\n \u003cdiv class=\"section-header\">\n \u003cdiv class=\"section-icon\">📊\u003c/div>\n \u003ch2>{L['equity_curve']}\u003c/h2>\n \u003c/div>\n \u003cdiv class=\"chart-container\" id=\"equity-chart\">\u003c/div>\n \u003c/section>\n \n \u003csection class=\"section chart-half\">\n \u003cdiv class=\"section-header\">\n \u003cdiv class=\"section-icon\">📉\u003c/div>\n \u003ch2>{L['max_drawdown']}\u003c/h2>\n \u003c/div>\n \u003cdiv class=\"chart-container\" id=\"drawdown-chart\">\u003c/div>\n \u003c/section>\n \u003c/div>\n \n \u003c!-- Strategy Analysis -->\n \u003csection class=\"section analysis-section\">\n \u003cdiv class=\"section-header\">\n \u003cdiv class=\"section-icon\">🧠\u003c/div>\n \u003ch2>{L['analysis_title']}\u003c/h2>\n \u003c/div>\n \u003cdiv class=\"analysis-content\">\n {analysis_html}\n \u003c/div>\n \u003c/section>\n \n \u003cfooter class=\"footer\">\n \u003cdiv class=\"footer-tagline\">{L['tagline']}\u003c/div>\n \u003ca href=\"https://github.com/0xrikt/crypto-skills\" target=\"_blank\" class=\"footer-github\">\n \u003csvg height=\"20\" width=\"20\" viewBox=\"0 0 16 16\" fill=\"currentColor\">\n \u003cpath d=\"M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z\"/>\n \u003c/svg>\n \u003cspan>github.com/0xrikt/crypto-skills\u003c/span>\n \u003c/a>\n \u003cdiv class=\"footer-note\">\n {L['generated']} {datetime.now().strftime('%Y-%m-%d %H:%M')} • {L['disclaimer']}\n \u003c/div>\n \u003c/footer>\n \u003c/div>\n \n \u003cscript>\n const chartTheme = {{\n paper_bgcolor: 'rgba(0,0,0,0)',\n plot_bgcolor: 'rgba(0,0,0,0)',\n font: {{ color: '#1e293b', family: 'Space Grotesk' }},\n xaxis: {{ gridcolor: 'rgba(0,0,0,0.08)', zerolinecolor: 'rgba(0,0,0,0.15)' }},\n yaxis: {{ gridcolor: 'rgba(0,0,0,0.08)', zerolinecolor: 'rgba(0,0,0,0.15)' }}\n }};\n \n // Equity Chart\n Plotly.newPlot('equity-chart', [{{\n x: {json.dumps(equity_times)},\n y: {json.dumps(equity_values)},\n type: 'scatter',\n mode: 'lines',\n fill: 'tozeroy',\n fillcolor: 'rgba(14,165,233,0.15)',\n line: {{ color: '#0ea5e9', width: 2 }},\n name: 'Portfolio Value'\n }}], {{\n ...chartTheme,\n height: 350,\n margin: {{ t: 20, r: 20, b: 40, l: 70 }},\n yaxis: {{ ...chartTheme.yaxis, title: 'Value ($)', tickformat: '$,.0f' }},\n shapes: [{{\n type: 'line',\n y0: {metrics['initial_capital']},\n y1: {metrics['initial_capital']},\n x0: 0, x1: 1,\n xref: 'paper',\n line: {{ dash: 'dash', color: 'rgba(0,0,0,0.3)', width: 1 }}\n }}]\n }}, {{ responsive: true }});\n \n // Drawdown Chart\n Plotly.newPlot('drawdown-chart', [{{\n x: {json.dumps(equity_times)},\n y: {json.dumps([-d for d in metrics['drawdowns']])},\n type: 'scatter',\n mode: 'lines',\n fill: 'tozeroy',\n fillcolor: 'rgba(239,68,68,0.15)',\n line: {{ color: '#ef4444', width: 1.5 }},\n name: 'Drawdown'\n }}], {{\n ...chartTheme,\n height: 220,\n margin: {{ t: 20, r: 20, b: 40, l: 70 }},\n yaxis: {{ ...chartTheme.yaxis, title: 'Drawdown %', tickformat: '.1f' }}\n }}, {{ responsive: true }});\n \n // Price Chart\n Plotly.newPlot('price-chart', [\n {{\n x: {json.dumps(timestamps)},\n open: {json.dumps(df['open'].tolist())},\n high: {json.dumps(df['high'].tolist())},\n low: {json.dumps(df['low'].tolist())},\n close: {json.dumps(df['close'].tolist())},\n type: 'candlestick',\n name: 'Price',\n increasing: {{ line: {{ color: '#10b981' }} }},\n decreasing: {{ line: {{ color: '#ef4444' }} }}\n }},\n {{\n x: {json.dumps(buy_times)},\n y: {json.dumps(buy_prices)},\n type: 'scatter',\n mode: 'markers',\n name: 'Buy',\n marker: {{ symbol: 'triangle-up', size: 16, color: '#3b82f6', line: {{ color: '#1e40af', width: 2 }} }}\n }},\n {{\n x: {json.dumps(sell_times)},\n y: {json.dumps(sell_prices)},\n type: 'scatter',\n mode: 'markers',\n name: 'Sell',\n marker: {{ symbol: 'triangle-down', size: 16, color: '#f59e0b', line: {{ color: '#b45309', width: 2 }} }}\n }}\n ], {{\n ...chartTheme,\n height: 450,\n margin: {{ t: 20, r: 20, b: 40, l: 70 }},\n xaxis: {{ ...chartTheme.xaxis, rangeslider: {{ visible: false }} }},\n yaxis: {{ ...chartTheme.yaxis, title: 'Price ($)', tickformat: '$,.0f' }},\n legend: {{ orientation: 'h', y: 1.1 }}\n }}, {{ responsive: true }});\n \u003c/script>\n\u003c/body>\n\u003c/html>'''\n \n return html\n\n\n# ============================================================================\n# CODE GENERATION\n# ============================================================================\n\ndef generate_strategy_code(config: Dict, df: pd.DataFrame) -> str:\n \"\"\"Generate runnable Python strategy code.\"\"\"\n \n code = f'''#!/usr/bin/env python3\n\"\"\"\n{config.get('name', 'Trading Strategy')}\n{'=' * len(config.get('name', 'Trading Strategy'))}\n\nAuto-generated by Crypto Backtest Skill\nhttps://github.com/0xrikt/crypto-skills\n\nAsset: {config.get('symbol', 'BTC/USDT')}\nTimeframe: {config.get('timeframe', '4h')}\n\nEntry: {config.get('entry_str', 'N/A')}\nExit: {config.get('exit_str', 'N/A')}\nStop Loss: {config.get('stop_loss', 5)}%\nTake Profit: {config.get('take_profit', 15)}%\n\"\"\"\n\nimport ccxt\nimport pandas as pd\nimport pandas_ta as ta\nfrom datetime import datetime, timedelta\n\n\n# =============================================================================\n# CONFIGURATION\n# =============================================================================\n\nSYMBOL = \"{config.get('symbol', 'BTC/USDT')}\"\nTIMEFRAME = \"{config.get('timeframe', '4h')}\"\nEXCHANGE = \"binance\"\n\n# Risk Management\nINITIAL_CAPITAL = {config.get('initial_capital', 10000)}\nPOSITION_SIZE_PCT = {config.get('position_size', 10)} # % of portfolio per trade\nSTOP_LOSS_PCT = {config.get('stop_loss', 5)}\nTAKE_PROFIT_PCT = {config.get('take_profit', 15)}\nCOMMISSION_PCT = {config.get('commission', 0.1)}\n\n\n# =============================================================================\n# DATA FETCHING\n# =============================================================================\n\ndef fetch_data(days: int = 365) -> pd.DataFrame:\n \"\"\"Fetch historical OHLCV data.\"\"\"\n exchange = getattr(ccxt, EXCHANGE)({{'enableRateLimit': True}})\n since = exchange.parse8601((datetime.utcnow() - timedelta(days=days)).isoformat())\n \n all_ohlcv = []\n while True:\n ohlcv = exchange.fetch_ohlcv(SYMBOL, TIMEFRAME, since=since, limit=1000)\n if not ohlcv:\n break\n all_ohlcv.extend(ohlcv)\n since = ohlcv[-1][0] + 1\n if len(ohlcv) \u003c 1000:\n break\n \n df = pd.DataFrame(all_ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])\n df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')\n df.set_index('timestamp', inplace=True)\n return df\n\n\n# =============================================================================\n# INDICATORS\n# =============================================================================\n\ndef calculate_indicators(df: pd.DataFrame) -> pd.DataFrame:\n \"\"\"Calculate technical indicators.\"\"\"\n df = df.copy()\n \n # RSI\n df['rsi'] = ta.rsi(df['close'], length=14)\n \n # MACD\n macd = ta.macd(df['close'], fast=12, slow=26, signal=9)\n if macd is not None:\n df['macd'] = macd.iloc[:, 0]\n df['macd_signal'] = macd.iloc[:, 2]\n \n # Moving Averages\n df['sma50'] = ta.sma(df['close'], length=50)\n df['ema21'] = ta.ema(df['close'], length=21)\n \n # Bollinger Bands\n bb = ta.bbands(df['close'], length=20, std=2.0)\n if bb is not None:\n df['bb_upper'] = bb.iloc[:, 2]\n df['bb_lower'] = bb.iloc[:, 0]\n \n return df\n\n\n# =============================================================================\n# SIGNAL GENERATION\n# =============================================================================\n\ndef generate_signals(df: pd.DataFrame) -> pd.DataFrame:\n \"\"\"Generate entry and exit signals.\"\"\"\n df = df.copy()\n \n # Entry conditions: {config.get('entry_str', 'rsi\u003c30')}\n entry = pd.Series(True, index=df.index)\n # TODO: Customize entry conditions\n entry &= df['rsi'] \u003c 30 # Example\n \n # Exit conditions: {config.get('exit_str', 'rsi>70')}\n exit_signal = pd.Series(False, index=df.index)\n # TODO: Customize exit conditions\n exit_signal |= df['rsi'] > 70 # Example\n \n df['entry_signal'] = entry.astype(int)\n df['exit_signal'] = exit_signal.astype(int)\n \n return df\n\n\n# =============================================================================\n# BACKTEST\n# =============================================================================\n\ndef backtest(df: pd.DataFrame) -> dict:\n \"\"\"Run backtest simulation.\"\"\"\n capital = INITIAL_CAPITAL\n position = 0.0\n entry_price = 0.0\n trades = []\n \n for timestamp, row in df.iterrows():\n price = row['close']\n \n # Check stop-loss / take-profit\n if position > 0:\n pnl_pct = (price - entry_price) / entry_price * 100\n \n if pnl_pct \u003c= -STOP_LOSS_PCT:\n proceeds = position * price * (1 - COMMISSION_PCT / 100)\n capital += proceeds\n trades.append({{'pnl_pct': pnl_pct, 'reason': 'stop_loss'}})\n position = 0\n \n elif pnl_pct >= TAKE_PROFIT_PCT:\n proceeds = position * price * (1 - COMMISSION_PCT / 100)\n capital += proceeds\n trades.append({{'pnl_pct': pnl_pct, 'reason': 'take_profit'}})\n position = 0\n \n # Entry\n if row['entry_signal'] == 1 and position == 0:\n position_value = capital * POSITION_SIZE_PCT / 100\n position = position_value / price\n entry_price = price\n capital -= position_value * (1 + COMMISSION_PCT / 100)\n \n # Exit\n elif row['exit_signal'] == 1 and position > 0:\n proceeds = position * price * (1 - COMMISSION_PCT / 100)\n pnl_pct = (price - entry_price) / entry_price * 100\n capital += proceeds\n trades.append({{'pnl_pct': pnl_pct, 'reason': 'signal'}})\n position = 0\n \n # Close remaining\n if position > 0:\n capital += position * df.iloc[-1]['close']\n \n return {{\n 'final_capital': capital,\n 'return_pct': (capital - INITIAL_CAPITAL) / INITIAL_CAPITAL * 100,\n 'total_trades': len(trades),\n 'trades': trades\n }}\n\n\n# =============================================================================\n# MAIN\n# =============================================================================\n\nif __name__ == \"__main__\":\n print(f\"Fetching {{SYMBOL}} data...\")\n df = fetch_data(days=365)\n print(f\"Got {{len(df)}} candles\")\n \n print(\"Calculating indicators...\")\n df = calculate_indicators(df)\n \n print(\"Generating signals...\")\n df = generate_signals(df)\n \n print(\"Running backtest...\")\n results = backtest(df)\n \n print(\"\\\\n\" + \"=\"*50)\n print(\"BACKTEST RESULTS\")\n print(\"=\"*50)\n print(f\"Final Capital: ${{results['final_capital']:,.2f}}\")\n print(f\"Return: {{results['return_pct']:+.2f}}%\")\n print(f\"Total Trades: {{results['total_trades']}}\")\n'''\n \n return code\n\n\n# ============================================================================\n# MAIN\n# ============================================================================\n\ndef main():\n parser = argparse.ArgumentParser(description='Crypto Strategy Backtest Engine')\n parser.add_argument('--symbol', default='BTC/USDT', help='Trading pair')\n parser.add_argument('--timeframe', default='4h', help='Candle timeframe')\n parser.add_argument('--days', type=int, default=365, help='Backtest period in days')\n parser.add_argument('--exchange', default='binance', help='Exchange to fetch data from')\n parser.add_argument('--entry', default='rsi\u003c30', help='Entry conditions (comma-separated)')\n parser.add_argument('--exit', default='rsi>70', help='Exit conditions (comma-separated)')\n parser.add_argument('--stop-loss', type=float, default=5, help='Stop loss percentage')\n parser.add_argument('--take-profit', type=float, default=15, help='Take profit percentage')\n parser.add_argument('--position-size', type=float, default=10, help='Position size percentage')\n parser.add_argument('--initial-capital', type=float, default=10000, help='Initial capital')\n parser.add_argument('--commission', type=float, default=0.1, help='Commission percentage')\n parser.add_argument('--output', default='report.html', help='Output HTML file')\n parser.add_argument('--name', default='Trading Strategy', help='Strategy name')\n parser.add_argument('--description', default='', help='Original strategy idea in natural language')\n parser.add_argument('--lang', default='en', choices=['en', 'zh'], help='Report language (en/zh)')\n \n args = parser.parse_args()\n \n print(f\"🚀 Crypto Backtest Engine\")\n print(f\"{'='*50}\")\n print(f\"Symbol: {args.symbol}\")\n print(f\"Timeframe: {args.timeframe}\")\n print(f\"Period: {args.days} days\")\n print(f\"Entry: {args.entry}\")\n print(f\"Exit: {args.exit}\")\n print()\n \n # Fetch data\n print(\"📊 Fetching historical data...\")\n df = fetch_ohlcv(args.symbol, args.timeframe, args.days, args.exchange)\n print(f\" Got {len(df)} candles\")\n \n # Validate data - warn if significantly less than requested\n if len(df) > 1:\n actual_days = (df.index[-1] - df.index[0]).days\n if actual_days \u003c args.days * 0.5: # Less than 50% of requested\n print()\n print(\"⚠️ WARNING: Received much less data than requested!\")\n print(f\" Requested: {args.days} days\")\n print(f\" Received: {actual_days} days ({len(df)} candles)\")\n print(f\" Date range: {df.index[0].strftime('%Y-%m-%d')} to {df.index[-1].strftime('%Y-%m-%d')}\")\n print()\n print(\" 💡 TIP: This is likely due to exchange data limits.\")\n print(\" - OKX only provides ~60-90 days of history\")\n print(\" - Use --exchange kucoin (~200 days) or --exchange binance (365+ days, if accessible)\")\n print()\n \n # Calculate indicators\n print(\"📈 Calculating indicators...\")\n indicator_config = {}\n df = calculate_indicators(df, indicator_config)\n \n # Generate signals\n print(\"🎯 Generating signals...\")\n entry_conditions = parse_conditions(args.entry)\n exit_conditions = parse_conditions(args.exit)\n df = generate_signals(df, entry_conditions, exit_conditions)\n \n entry_count = df['entry_signal'].sum()\n exit_count = df['exit_signal'].sum()\n print(f\" Entry signals: {entry_count}\")\n print(f\" Exit signals: {exit_count}\")\n \n # Simulate portfolio\n print(\"💰 Running backtest...\")\n results = simulate_portfolio(\n df,\n initial_capital=args.initial_capital,\n position_size_pct=args.position_size,\n stop_loss_pct=args.stop_loss,\n take_profit_pct=args.take_profit,\n commission_pct=args.commission\n )\n \n # Calculate metrics\n metrics = calculate_metrics(results, df)\n \n # Prepare config for report\n # Get actual date range from data\n start_date = df.index[0].strftime('%Y-%m-%d') if len(df) > 0 else 'N/A'\n end_date = df.index[-1].strftime('%Y-%m-%d') if len(df) > 0 else 'N/A'\n actual_days = (df.index[-1] - df.index[0]).days if len(df) > 1 else 0\n \n config = {\n 'name': args.name,\n 'description': args.description, # User's original strategy idea\n 'symbol': args.symbol,\n 'timeframe': args.timeframe,\n 'days': args.days,\n 'actual_days': actual_days,\n 'start_date': start_date,\n 'end_date': end_date,\n 'entry_str': args.entry,\n 'exit_str': args.exit,\n 'entry_display': [html.escape(c.strip()) for c in args.entry.split(',')],\n 'exit_display': [html.escape(c.strip()) for c in args.exit.split(',')] + [f'Stop Loss: -{args.stop_loss}%', f'Take Profit: +{args.take_profit}%'],\n 'stop_loss': args.stop_loss,\n 'take_profit': args.take_profit,\n 'position_size': args.position_size,\n 'commission': args.commission,\n 'initial_capital': args.initial_capital\n }\n \n # Generate HTML report\n print(\"📄 Generating report...\")\n report_html = generate_html_report(df, results, metrics, config, lang=args.lang)\n \n output_path = Path(args.output)\n output_path.write_text(report_html)\n print(f\" Saved: {output_path.absolute()}\")\n \n # Generate strategy code\n code_path = output_path.with_suffix('.py')\n code = generate_strategy_code(config, df)\n code_path.write_text(code)\n print(f\" Saved: {code_path.absolute()}\")\n \n # Print results\n print()\n print(f\"{'='*50}\")\n print(\"📈 BACKTEST RESULTS\")\n print(f\"{'='*50}\")\n print(f\"Total Return: {metrics['total_return_pct']:+.2f}%\")\n print(f\"Max Drawdown: -{metrics['max_drawdown_pct']:.2f}%\")\n print(f\"Sharpe Ratio: {metrics['sharpe_ratio']:.2f}\")\n print(f\"Win Rate: {metrics['win_rate_pct']:.1f}%\")\n print(f\"Total Trades: {metrics['total_trades']}\")\n print(f\"Profit Factor: {metrics['profit_factor']}\")\n print(f\"Final Equity: ${metrics['final_equity']:,.2f}\")\n print(f\"Buy & Hold: {metrics['buy_hold_return_pct']:+.2f}%\")\n print()\n \n vs_bh = metrics['total_return_pct'] - metrics['buy_hold_return_pct']\n if vs_bh > 0:\n print(f\"✅ Strategy beats Buy & Hold by {vs_bh:+.2f}%\")\n else:\n print(f\"❌ Strategy underperforms Buy & Hold by {vs_bh:.2f}%\")\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":91624,"content_sha256":"470854f62ea56ba93e8d3cf935b9b78f6ea3c26f8e0d7a40235ba28a2c66f0fb"},{"filename":"src/pair_trading.py","content":"#!/usr/bin/env python3\n\"\"\"\nPair Trading / Relative Strength Strategy Backtest\n\nThis strategy compares two assets (e.g., BTC and ETH) and trades based on\nthe assumption that their trends will eventually align.\n\nStrategy Logic:\n- If Asset A significantly outperforms Asset B → Long Asset B (expect catch-up)\n- If Asset B significantly outperforms Asset A → Long Asset A (expect catch-up)\n\nSPOT ONLY: All trades are long-only, no shorting, no leverage.\n\"\"\"\n\nimport argparse\nimport json\nimport sys\nfrom datetime import datetime, timedelta\nfrom typing import Dict, List, Tuple, Optional\n\nimport ccxt\nimport numpy as np\nimport pandas as pd\nimport pandas_ta as ta\nimport plotly.graph_objects as go\nfrom plotly.subplots import make_subplots\n\n# Language labels\nLABELS = {\n 'en': {\n 'title': 'Pair Trading Backtest Report',\n 'subtitle': 'Relative Strength Mean Reversion',\n 'strategy_summary': 'Strategy Summary',\n 'original_strategy_idea': 'Original Strategy Idea',\n 'no_description_provided': 'Trade underperforming asset when spread deviates from mean',\n 'symbol_a': 'Symbol A',\n 'symbol_b': 'Symbol B',\n 'timeframe': 'Timeframe',\n 'backtest_period': 'Backtest Period',\n 'to': 'to',\n 'days': 'days',\n 'lookback': 'Lookback Period',\n 'threshold': 'Entry Threshold',\n 'exit_threshold': 'Exit Threshold',\n 'initial_capital': 'Initial Capital',\n 'position_size': 'Position Size',\n 'stop_loss': 'Stop Loss',\n 'take_profit': 'Take Profit',\n 'commission': 'Commission',\n 'strategy_logic': 'Strategy Logic',\n 'logic_desc': 'When {a} outperforms {b} by more than {t}%, long {b} (expect catch-up). Vice versa.',\n 'exit_logic': 'Exit when spread returns within ±{t}% of mean, or stop-loss/take-profit hit.',\n 'performance_metrics': 'Performance Metrics',\n 'total_return': 'Total Return',\n 'buy_hold_a': 'Buy & Hold {a}',\n 'buy_hold_b': 'Buy & Hold {b}',\n 'max_drawdown': 'Max Drawdown',\n 'sharpe_ratio': 'Sharpe Ratio',\n 'total_trades': 'Total Trades',\n 'win_rate': 'Win Rate',\n 'profit_factor': 'Profit Factor',\n 'trades_on_a': 'Trades on {a}',\n 'trades_on_b': 'Trades on {b}',\n 'price_comparison': 'Price Comparison (Normalized)',\n 'relative_spread': 'Relative Performance Spread',\n 'equity_curve': 'Portfolio Equity Curve',\n 'trade_history': 'Trade History',\n 'date': 'Date',\n 'action': 'Action',\n 'asset': 'Asset',\n 'price': 'Price',\n 'amount': 'Amount',\n 'pnl': 'P&L',\n 'buy': 'BUY',\n 'sell': 'SELL',\n 'tagline': 'Validate your trading ideas in minutes',\n 'share_cta': 'Found this useful? Share it with fellow traders!',\n 'generated': 'Generated',\n 'disclaimer': 'Past performance ≠ future results',\n },\n 'zh': {\n 'title': '配对交易回测报告',\n 'subtitle': '相对强弱均值回归策略',\n 'strategy_summary': '策略概览',\n 'original_strategy_idea': '原始策略思路',\n 'no_description_provided': '当价差偏离均值时,做多表现落后的资产',\n 'symbol_a': '资产 A',\n 'symbol_b': '资产 B',\n 'timeframe': '时间周期',\n 'backtest_period': '回测区间',\n 'to': '至',\n 'days': '天',\n 'lookback': '回溯周期',\n 'threshold': '入场阈值',\n 'exit_threshold': '出场阈值',\n 'initial_capital': '初始资金',\n 'position_size': '仓位比例',\n 'stop_loss': '止损',\n 'take_profit': '止盈',\n 'commission': '手续费',\n 'strategy_logic': '策略逻辑',\n 'logic_desc': '当 {a} 相对 {b} 跑赢超过 {t}% 时,做多 {b}(预期追涨)。反之亦然。',\n 'exit_logic': '当价差回归至均值 ±{t}% 内,或触发止损/止盈时平仓。',\n 'performance_metrics': '绩效指标',\n 'total_return': '总收益率',\n 'buy_hold_a': '持有 {a}',\n 'buy_hold_b': '持有 {b}',\n 'max_drawdown': '最大回撤',\n 'sharpe_ratio': '夏普比率',\n 'total_trades': '总交易次数',\n 'win_rate': '胜率',\n 'profit_factor': '盈亏比',\n 'trades_on_a': '{a} 交易次数',\n 'trades_on_b': '{b} 交易次数',\n 'price_comparison': '价格对比(标准化)',\n 'relative_spread': '相对强弱价差',\n 'equity_curve': '账户权益曲线',\n 'trade_history': '交易记录',\n 'date': '日期',\n 'action': '操作',\n 'asset': '资产',\n 'price': '价格',\n 'amount': '数量',\n 'pnl': '盈亏',\n 'buy': '买入',\n 'sell': '卖出',\n 'tagline': '几分钟验证你的交易策略想法',\n 'share_cta': '觉得有用?分享给其他交易者吧!',\n 'generated': '生成时间',\n 'disclaimer': '历史表现不代表未来收益',\n }\n}\n\n\ndef fetch_data(symbol: str, days: int, timeframe: str = '4h', exchange_id: str = 'okx') -> pd.DataFrame:\n \"\"\"Fetch OHLCV data from exchange.\"\"\"\n print(f\"📊 Fetching {symbol} data ({days} days, {timeframe})...\")\n \n exchange_class = getattr(ccxt, exchange_id)\n exchange = exchange_class({'enableRateLimit': True})\n \n since = exchange.parse8601((datetime.now(tz=None) - timedelta(days=days)).isoformat())\n \n all_ohlcv = []\n while True:\n ohlcv = exchange.fetch_ohlcv(symbol, timeframe, since=since, limit=1000)\n if not ohlcv:\n break\n all_ohlcv.extend(ohlcv)\n since = ohlcv[-1][0] + 1\n if len(ohlcv) \u003c 1000:\n break\n \n df = pd.DataFrame(all_ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])\n df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')\n df.set_index('timestamp', inplace=True)\n df = df[~df.index.duplicated(keep='first')]\n \n print(f\" ✓ Loaded {len(df)} candles for {symbol}\")\n \n # Validate data - warn if significantly less than requested\n if len(df) > 1:\n actual_days = (df.index[-1] - df.index[0]).days\n if actual_days \u003c days * 0.5:\n print()\n print(f\"⚠️ WARNING: {symbol} - Received much less data than requested!\")\n print(f\" Requested: {days} days, Received: {actual_days} days\")\n print(\" 💡 TIP: OKX ~90 day limit. Use --exchange kucoin or binance for longer backtests\")\n print()\n \n return df\n\n\ndef calculate_spread(df_a: pd.DataFrame, df_b: pd.DataFrame, lookback: int = 20) -> pd.DataFrame:\n \"\"\"\n Calculate relative performance spread between two assets.\n \n Spread = (Return_A - Return_B) over lookback period\n Positive spread = A outperforming B\n Negative spread = B outperforming A\n \"\"\"\n # Align dataframes on common index\n common_idx = df_a.index.intersection(df_b.index)\n df_a = df_a.loc[common_idx].copy()\n df_b = df_b.loc[common_idx].copy()\n \n # Calculate rolling returns\n df_a['return'] = df_a['close'].pct_change(lookback) * 100\n df_b['return'] = df_b['close'].pct_change(lookback) * 100\n \n # Create combined dataframe\n df = pd.DataFrame(index=common_idx)\n df['price_a'] = df_a['close']\n df['price_b'] = df_b['close']\n df['return_a'] = df_a['return']\n df['return_b'] = df_b['return']\n df['spread'] = df['return_a'] - df['return_b']\n \n # Calculate spread statistics\n df['spread_mean'] = df['spread'].rolling(window=lookback*2).mean()\n df['spread_std'] = df['spread'].rolling(window=lookback*2).std()\n df['spread_zscore'] = (df['spread'] - df['spread_mean']) / df['spread_std']\n \n # Normalize prices for comparison chart\n df['price_a_norm'] = df['price_a'] / df['price_a'].iloc[0] * 100\n df['price_b_norm'] = df['price_b'] / df['price_b'].iloc[0] * 100\n \n return df.dropna()\n\n\ndef generate_signals(df: pd.DataFrame, threshold: float = 10.0, exit_threshold: float = 2.0, only_long: str = 'both') -> pd.DataFrame:\n \"\"\"\n Generate trading signals based on spread deviation.\n \n - Long B when spread > threshold (A outperforming, expect B to catch up)\n - Long A when spread \u003c -threshold (B outperforming, expect A to catch up)\n - Exit when spread returns to within ±exit_threshold of mean\n \n Args:\n only_long: 'A' = only long A, 'B' = only long B, 'both' = trade both directions\n \"\"\"\n df = df.copy()\n df['signal'] = 0 # 0 = no position, 1 = long A, 2 = long B\n df['target_asset'] = ''\n \n position = 0 # Current position\n \n for i in range(1, len(df)):\n spread = df['spread'].iloc[i]\n \n if position == 0:\n # No position - check for entry\n if spread > threshold and only_long in ['B', 'both']:\n # A significantly outperforming B → Long B\n position = 2\n df.iloc[i, df.columns.get_loc('signal')] = 2\n df.iloc[i, df.columns.get_loc('target_asset')] = 'B'\n elif spread \u003c -threshold and only_long in ['A', 'both']:\n # B significantly outperforming A → Long A\n position = 1\n df.iloc[i, df.columns.get_loc('signal')] = 1\n df.iloc[i, df.columns.get_loc('target_asset')] = 'A'\n else:\n # Have position - check for exit\n df.iloc[i, df.columns.get_loc('signal')] = position\n \n if abs(spread) \u003c exit_threshold:\n # Spread reverted to mean - exit\n position = 0\n df.iloc[i, df.columns.get_loc('signal')] = 0\n \n return df\n\n\ndef run_backtest(df: pd.DataFrame, config: Dict) -> Tuple[Dict, List[Dict], pd.DataFrame]:\n \"\"\"Run backtest simulation.\"\"\"\n \n initial_capital = config.get('initial_capital', 10000)\n position_pct = config.get('position_size', 20) / 100\n stop_loss_pct = config.get('stop_loss', 10) / 100\n take_profit_pct = config.get('take_profit', 25) / 100\n commission_pct = config.get('commission', 0.1) / 100\n \n # Track portfolio\n cash = initial_capital\n position_asset = None # 'A' or 'B'\n position_qty = 0\n entry_price = 0\n \n equity_curve = []\n trades = []\n \n for i in range(len(df)):\n row = df.iloc[i]\n current_signal = row['signal']\n \n # Get current prices\n price_a = row['price_a']\n price_b = row['price_b']\n \n # Calculate current equity\n if position_asset == 'A':\n position_value = position_qty * price_a\n elif position_asset == 'B':\n position_value = position_qty * price_b\n else:\n position_value = 0\n \n current_equity = cash + position_value\n equity_curve.append({\n 'timestamp': df.index[i],\n 'equity': current_equity,\n 'cash': cash,\n 'position_value': position_value\n })\n \n # Check stop-loss / take-profit if in position\n if position_asset:\n current_price = price_a if position_asset == 'A' else price_b\n pnl_pct = (current_price - entry_price) / entry_price\n \n if pnl_pct \u003c= -stop_loss_pct or pnl_pct >= take_profit_pct:\n # Exit position\n exit_value = position_qty * current_price * (1 - commission_pct)\n pnl = exit_value - (entry_price * position_qty)\n cash += exit_value\n \n trades.append({\n 'date': df.index[i],\n 'action': 'SELL',\n 'asset': position_asset,\n 'price': current_price,\n 'qty': position_qty,\n 'value': exit_value,\n 'pnl': pnl,\n 'reason': 'stop_loss' if pnl_pct \u003c= -stop_loss_pct else 'take_profit'\n })\n \n position_asset = None\n position_qty = 0\n entry_price = 0\n continue\n \n # Process signals\n if current_signal == 0 and position_asset:\n # Exit signal - close position\n current_price = price_a if position_asset == 'A' else price_b\n exit_value = position_qty * current_price * (1 - commission_pct)\n pnl = exit_value - (entry_price * position_qty)\n cash += exit_value\n \n trades.append({\n 'date': df.index[i],\n 'action': 'SELL',\n 'asset': position_asset,\n 'price': current_price,\n 'qty': position_qty,\n 'value': exit_value,\n 'pnl': pnl,\n 'reason': 'signal_exit'\n })\n \n position_asset = None\n position_qty = 0\n entry_price = 0\n \n elif current_signal > 0 and not position_asset:\n # Entry signal\n target = 'A' if current_signal == 1 else 'B'\n price = price_a if target == 'A' else price_b\n \n # Calculate position size\n position_value = cash * position_pct\n position_qty = (position_value * (1 - commission_pct)) / price\n entry_price = price\n position_asset = target\n cash -= position_value\n \n trades.append({\n 'date': df.index[i],\n 'action': 'BUY',\n 'asset': target,\n 'price': price,\n 'qty': position_qty,\n 'value': position_value,\n 'pnl': 0,\n 'reason': 'signal_entry'\n })\n \n # Close any remaining position at end\n if position_asset:\n final_price = df.iloc[-1]['price_a'] if position_asset == 'A' else df.iloc[-1]['price_b']\n exit_value = position_qty * final_price * (1 - commission_pct)\n pnl = exit_value - (entry_price * position_qty)\n cash += exit_value\n \n trades.append({\n 'date': df.index[-1],\n 'action': 'SELL',\n 'asset': position_asset,\n 'price': final_price,\n 'qty': position_qty,\n 'value': exit_value,\n 'pnl': pnl,\n 'reason': 'end_of_backtest'\n })\n \n # Calculate metrics\n equity_df = pd.DataFrame(equity_curve).set_index('timestamp')\n final_equity = equity_df['equity'].iloc[-1]\n total_return = (final_equity - initial_capital) / initial_capital * 100\n \n # Buy & Hold returns\n bh_return_a = (df['price_a'].iloc[-1] / df['price_a'].iloc[0] - 1) * 100\n bh_return_b = (df['price_b'].iloc[-1] / df['price_b'].iloc[0] - 1) * 100\n \n # Max drawdown\n rolling_max = equity_df['equity'].cummax()\n drawdown = (equity_df['equity'] - rolling_max) / rolling_max * 100\n max_drawdown = drawdown.min()\n \n # Trade statistics\n if trades:\n sell_trades = [t for t in trades if t['action'] == 'SELL']\n winning_trades = [t for t in sell_trades if t['pnl'] > 0]\n losing_trades = [t for t in sell_trades if t['pnl'] \u003c 0]\n \n win_rate = len(winning_trades) / len(sell_trades) * 100 if sell_trades else 0\n total_profit = sum(t['pnl'] for t in winning_trades)\n total_loss = abs(sum(t['pnl'] for t in losing_trades))\n profit_factor = total_profit / total_loss if total_loss > 0 else float('inf')\n \n trades_a = len([t for t in trades if t['asset'] == 'A' and t['action'] == 'BUY'])\n trades_b = len([t for t in trades if t['asset'] == 'B' and t['action'] == 'BUY'])\n else:\n win_rate = 0\n profit_factor = 0\n trades_a = 0\n trades_b = 0\n \n # Sharpe ratio (annualized)\n returns = equity_df['equity'].pct_change().dropna()\n sharpe = (returns.mean() / returns.std()) * np.sqrt(252 * 6) if returns.std() > 0 else 0 # 6 for 4h timeframe\n \n metrics = {\n 'initial_capital': initial_capital,\n 'final_equity': final_equity,\n 'total_return': total_return,\n 'buy_hold_return_a': bh_return_a,\n 'buy_hold_return_b': bh_return_b,\n 'max_drawdown': max_drawdown,\n 'sharpe_ratio': sharpe,\n 'total_trades': len([t for t in trades if t['action'] == 'BUY']),\n 'win_rate': win_rate,\n 'profit_factor': profit_factor,\n 'trades_a': trades_a,\n 'trades_b': trades_b\n }\n \n return metrics, trades, equity_df\n\n\ndef generate_html_report(df: pd.DataFrame, metrics: Dict, trades: List[Dict], \n equity_df: pd.DataFrame, config: Dict, lang: str = 'en') -> str:\n \"\"\"Generate professional HTML report matching single-asset style.\"\"\"\n \n L = LABELS.get(lang, LABELS['en'])\n \n symbol_a = config.get('symbol_a', 'BTC/USDT')\n symbol_b = config.get('symbol_b', 'ETH/USDT')\n name_a = symbol_a.split('/')[0]\n name_b = symbol_b.split('/')[0]\n \n # Calculate additional metrics\n initial_capital = config.get('initial_capital', 10000)\n final_equity = metrics.get('final_equity', initial_capital)\n total_profit = final_equity - initial_capital\n \n # Calculate drawdowns for chart\n equities = equity_df['equity'].tolist()\n peak = equities[0]\n drawdowns = []\n for eq in equities:\n if eq > peak:\n peak = eq\n dd = (peak - eq) / peak * 100\n drawdowns.append(-dd)\n \n # Prepare trade markers for spread chart and price chart\n buy_times = []\n buy_spreads = []\n buy_prices_norm = []\n sell_times = []\n sell_spreads = []\n sell_prices_norm = []\n \n for t in trades:\n trade_time = t['date'].isoformat()\n # Find closest values\n if t['date'] in df.index:\n spread_val = df.loc[t['date'], 'spread']\n price_norm = df.loc[t['date'], 'price_b_norm'] # ETH normalized price\n else:\n # Find nearest index\n idx = df.index.get_indexer([t['date']], method='nearest')[0]\n spread_val = df.iloc[idx]['spread']\n price_norm = df.iloc[idx]['price_b_norm']\n \n if t['action'] == 'BUY':\n buy_times.append(trade_time)\n buy_spreads.append(spread_val)\n buy_prices_norm.append(price_norm)\n else:\n sell_times.append(trade_time)\n sell_spreads.append(spread_val)\n sell_prices_norm.append(price_norm)\n \n # Timestamps for charts\n timestamps = [ts.isoformat() for ts in df.index]\n equity_timestamps = [ts.isoformat() for ts in equity_df.index]\n \n # Format trades table (paired entry/exit)\n trades_html = ''\n buy_trades = [t for t in trades if t['action'] == 'BUY']\n sell_trades = [t for t in trades if t['action'] == 'SELL']\n \n for i, (buy, sell) in enumerate(zip(buy_trades, sell_trades)):\n pnl_class = 'positive' if sell['pnl'] > 0 else 'negative'\n pnl_pct = (sell['price'] - buy['price']) / buy['price'] * 100\n asset_name = name_b if buy['asset'] == 'B' else name_a\n trades_html += f'''\n \u003ctr>\n \u003ctd>{buy['date'].strftime('%Y-%m-%d %H:%M')}\u003c/td>\n \u003ctd>{sell['date'].strftime('%Y-%m-%d %H:%M')}\u003c/td>\n \u003ctd>\u003cspan class=\"asset-badge\">{asset_name}\u003c/span>\u003c/td>\n \u003ctd>${buy['value']:,.2f}\u003c/td>\n \u003ctd>{buy['qty']:,.6f}\u003c/td>\n \u003ctd>${buy['price']:,.2f}\u003c/td>\n \u003ctd>${sell['price']:,.2f}\u003c/td>\n \u003ctd class=\"{pnl_class}\">{pnl_pct:+.2f}%\u003c/td>\n \u003ctd class=\"{pnl_class}\">${sell['pnl']:+,.2f}\u003c/td>\n \u003c/tr>'''\n \n # Determine colors\n return_class = 'positive' if metrics['total_return'] > 0 else 'negative'\n profit_class = 'positive' if total_profit > 0 else 'negative'\n \n # Only long info\n only_long = config.get('only_long', 'both')\n if only_long == 'B':\n only_long_text = f\"只做多 {name_b}\" if lang == 'zh' else f\"Long {name_b} only\"\n elif only_long == 'A':\n only_long_text = f\"只做多 {name_a}\" if lang == 'zh' else f\"Long {name_a} only\"\n else:\n only_long_text = \"双向交易\" if lang == 'zh' else \"Both directions\"\n \n html = f'''\u003c!DOCTYPE html>\n\u003chtml lang=\"{lang}\">\n\u003chead>\n \u003cmeta charset=\"UTF-8\">\n \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n \u003ctitle>{L['title']} | {name_a} vs {name_b}\u003c/title>\n \u003cscript src=\"https://cdn.plot.ly/plotly-2.27.0.min.js\">\u003c/script>\n \u003clink href=\"https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap\" rel=\"stylesheet\">\n \u003cstyle>\n :root {{\n --bg-void: #f8fafc;\n --bg-deep: #ffffff;\n --bg-surface: #ffffff;\n --bg-elevated: #f1f5f9;\n --bg-hover: #e2e8f0;\n --text-primary: #1e293b;\n --text-secondary: #64748b;\n --text-muted: #94a3b8;\n --accent-cyan: #0ea5e9;\n --accent-btc: #f7931a;\n --accent-eth: #627eea;\n --accent-green: #10b981;\n --accent-red: #ef4444;\n --accent-gold: #f59e0b;\n --accent-purple: #8b5cf6;\n --gradient-pair: linear-gradient(135deg, #f7931a 0%, #627eea 100%);\n --border-subtle: #e2e8f0;\n --border-accent: rgba(14,165,233,0.4);\n --glow-cyan: 0 4px 20px rgba(14,165,233,0.15);\n }}\n \n * {{ margin: 0; padding: 0; box-sizing: border-box; }}\n \n body {{\n font-family: 'Space Grotesk', -apple-system, sans-serif;\n background: var(--bg-void);\n color: var(--text-primary);\n line-height: 1.6;\n min-height: 100vh;\n }}\n \n .bg-pattern {{\n position: fixed;\n top: 0; left: 0; right: 0; bottom: 0;\n background: \n radial-gradient(ellipse at 20% 20%, rgba(247,147,26,0.05) 0%, transparent 50%),\n radial-gradient(ellipse at 80% 80%, rgba(98,126,234,0.05) 0%, transparent 50%);\n pointer-events: none;\n z-index: 0;\n }}\n \n .container {{\n position: relative;\n z-index: 1;\n max-width: 1400px;\n margin: 0 auto;\n padding: 40px 24px;\n }}\n \n /* Header */\n .header {{\n text-align: center;\n padding: 60px 0;\n border-bottom: 1px solid var(--border-subtle);\n margin-bottom: 48px;\n }}\n \n .header-subtitle {{\n font-size: 0.85rem;\n letter-spacing: 1px;\n text-transform: uppercase;\n color: var(--text-muted);\n margin-bottom: 12px;\n }}\n \n .header-title {{\n font-size: clamp(1.8rem, 4vw, 2.5rem);\n font-weight: 700;\n margin-bottom: 16px;\n background: var(--gradient-pair);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n background-clip: text;\n }}\n \n .header-desc {{\n max-width: 700px;\n margin: 0 auto 20px;\n color: var(--text-secondary);\n font-size: 1.1rem;\n line-height: 1.5;\n }}\n \n .header-meta {{\n display: flex;\n justify-content: center;\n gap: 24px;\n flex-wrap: wrap;\n color: var(--text-secondary);\n font-size: 0.85rem;\n }}\n \n .header-meta span {{\n display: flex;\n align-items: center;\n gap: 6px;\n }}\n \n .header-meta .dot {{\n width: 5px;\n height: 5px;\n background: var(--accent-cyan);\n border-radius: 50%;\n }}\n \n /* Strategy Compact Grid - 3x2 */\n .section {{\n background: var(--bg-surface);\n border: 1px solid var(--border-subtle);\n border-radius: 20px;\n padding: 32px;\n margin-bottom: 32px;\n }}\n \n .section-header {{\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 24px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--border-subtle);\n }}\n \n .section-icon {{\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--bg-elevated);\n border-radius: 10px;\n font-size: 1.2rem;\n }}\n \n .section h2 {{\n font-size: 1.25rem;\n font-weight: 600;\n }}\n \n .strategy-compact {{\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n grid-template-rows: repeat(2, minmax(130px, auto));\n gap: 12px;\n }}\n \n @media (max-width: 900px) {{\n .strategy-compact {{ \n grid-template-columns: repeat(2, 1fr); \n grid-template-rows: repeat(3, minmax(130px, auto));\n }}\n }}\n \n @media (max-width: 600px) {{\n .strategy-compact {{ \n grid-template-columns: 1fr; \n grid-template-rows: repeat(6, auto);\n }}\n }}\n \n .strategy-block {{\n background: var(--bg-elevated);\n border-radius: 12px;\n padding: 16px;\n display: flex;\n flex-direction: column;\n }}\n \n .strategy-block h4 {{\n font-size: 0.7rem;\n font-weight: 700;\n color: var(--text-muted);\n margin-bottom: 12px;\n text-transform: uppercase;\n letter-spacing: 1px;\n }}\n \n .param-row {{\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 5px 0;\n font-size: 0.85rem;\n }}\n \n .param-row span {{\n color: var(--text-secondary);\n font-size: 0.8rem;\n }}\n \n .param-row code {{\n font-family: 'JetBrains Mono', monospace;\n background: var(--bg-deep);\n padding: 2px 8px;\n border-radius: 4px;\n font-size: 0.8rem;\n color: var(--accent-cyan);\n }}\n \n .param-row code.green {{ color: var(--accent-green); }}\n .param-row code.red {{ color: var(--accent-red); }}\n .param-row code.btc {{ color: var(--accent-btc); }}\n .param-row code.eth {{ color: var(--accent-eth); }}\n \n /* Metrics Table */\n .metrics-table-container {{\n overflow-x: auto;\n margin-bottom: 32px;\n }}\n \n .metrics-table {{\n width: 100%;\n border-collapse: collapse;\n font-size: 0.9rem;\n }}\n \n .metrics-table thead th {{\n background: var(--bg-elevated);\n padding: 16px 20px;\n text-align: center;\n font-weight: 600;\n color: var(--text-secondary);\n text-transform: uppercase;\n letter-spacing: 1px;\n font-size: 0.75rem;\n border-bottom: 2px solid var(--border-subtle);\n }}\n \n .metrics-table tbody tr {{\n border-bottom: 1px solid var(--border-subtle);\n }}\n \n .metrics-table tbody tr:hover {{\n background: var(--bg-elevated);\n }}\n \n .metrics-table td {{\n padding: 14px 20px;\n }}\n \n .metrics-table .metric-name {{\n color: var(--text-secondary);\n font-weight: 500;\n }}\n \n .metrics-table .metric-val {{\n font-family: 'JetBrains Mono', monospace;\n font-weight: 600;\n text-align: right;\n }}\n \n .metrics-table .metric-val.positive {{ color: var(--accent-green); }}\n .metrics-table .metric-val.negative {{ color: var(--accent-red); }}\n \n /* Charts */\n .chart-container {{\n background: var(--bg-deep);\n border-radius: 12px;\n padding: 16px;\n margin-bottom: 32px;\n min-height: 320px;\n position: relative;\n overflow: hidden;\n }}\n \n .chart-container:last-child {{\n margin-bottom: 0;\n }}\n \n .chart-label {{\n font-size: 0.85rem;\n font-weight: 600;\n color: var(--text-secondary);\n margin-bottom: 12px;\n padding-left: 8px;\n }}\n \n .charts-row {{\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 24px;\n margin-bottom: 32px;\n }}\n \n @media (max-width: 992px) {{\n .charts-row {{\n grid-template-columns: 1fr;\n }}\n }}\n \n .chart-half {{\n min-height: auto;\n }}\n \n .chart-half .chart-container {{\n margin-bottom: 0;\n min-height: 280px;\n }}\n \n .trades-wrapper {{\n margin-top: 40px;\n padding-top: 24px;\n border-top: 1px solid var(--border-subtle);\n }}\n \n /* Trades Table */\n .trades-table {{\n width: 100%;\n border-collapse: collapse;\n font-size: 0.9rem;\n }}\n \n .trades-table th {{\n text-align: left;\n padding: 14px 16px;\n background: var(--bg-elevated);\n color: var(--text-secondary);\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n font-size: 0.75rem;\n }}\n \n .trades-table th:first-child {{ border-radius: 8px 0 0 8px; }}\n .trades-table th:last-child {{ border-radius: 0 8px 8px 0; }}\n \n .trades-table td {{\n padding: 14px 16px;\n border-bottom: 1px solid var(--border-subtle);\n font-family: 'JetBrains Mono', monospace;\n font-size: 0.85rem;\n }}\n \n .trades-table tr:hover td {{\n background: var(--bg-hover);\n }}\n \n .positive {{ color: var(--accent-green); }}\n .negative {{ color: var(--accent-red); }}\n \n .asset-badge {{\n display: inline-block;\n padding: 4px 10px;\n border-radius: 8px;\n font-size: 0.75rem;\n font-weight: 600;\n background: linear-gradient(135deg, rgba(98,126,234,0.15) 0%, rgba(98,126,234,0.25) 100%);\n color: var(--accent-eth);\n }}\n \n /* Footer */\n .footer {{\n margin-top: 48px;\n padding: 32px 24px;\n text-align: center;\n border-top: 1px solid var(--border-subtle);\n background: var(--bg-surface);\n }}\n \n .footer-tagline {{\n font-size: 1rem;\n font-weight: 500;\n color: var(--text-primary);\n margin-bottom: 16px;\n }}\n \n .footer-github {{\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 10px 20px;\n background: var(--bg-elevated);\n border: 1px solid var(--border-subtle);\n border-radius: 8px;\n color: var(--text-secondary);\n text-decoration: none;\n font-size: 0.9rem;\n transition: all 0.2s ease;\n }}\n \n .footer-github:hover {{\n background: var(--bg-hover);\n border-color: var(--border-accent);\n color: var(--text-primary);\n }}\n \n .footer-note {{\n margin-top: 16px;\n color: var(--text-muted);\n font-size: 0.75rem;\n }}\n \u003c/style>\n\u003c/head>\n\u003cbody>\n \u003cdiv class=\"bg-pattern\">\u003c/div>\n \n \u003cdiv class=\"container\">\n \u003cheader class=\"header\">\n \u003cdiv class=\"header-subtitle\">{L['title']}\u003c/div>\n \u003ch1 class=\"header-title\">{name_a} ↔ {name_b}\u003c/h1>\n \u003cp class=\"header-desc\">\"{config.get('description', L['no_description_provided'])}\"\u003c/p>\n \u003cdiv class=\"header-meta\">\n \u003cspan>\u003cdiv class=\"dot\">\u003c/div>{config.get('timeframe', '4h')}\u003c/span>\n \u003cspan>\u003cdiv class=\"dot\">\u003c/div>{df.index.min().strftime('%Y-%m-%d')} → {df.index.max().strftime('%Y-%m-%d')}\u003c/span>\n \u003cspan>\u003cdiv class=\"dot\">\u003c/div>{metrics['total_trades']} trades\u003c/span>\n \u003c/div>\n \u003c/header>\n \n \u003c!-- Strategy Summary - Compact 3x2 Grid -->\n \u003csection class=\"section\">\n \u003cdiv class=\"section-header\">\n \u003cdiv class=\"section-icon\">📋\u003c/div>\n \u003ch2>{L['strategy_summary']}\u003c/h2>\n \u003c/div>\n \n \u003cdiv class=\"strategy-compact\">\n \u003cdiv class=\"strategy-block\">\n \u003ch4>📊 DATA\u003c/h4>\n \u003cdiv class=\"param-row\">\u003cspan>{L['symbol_a']}\u003c/span>\u003ccode class=\"btc\">{name_a}\u003c/code>\u003c/div>\n \u003cdiv class=\"param-row\">\u003cspan>{L['symbol_b']}\u003c/span>\u003ccode class=\"eth\">{name_b}\u003c/code>\u003c/div>\n \u003cdiv class=\"param-row\">\u003cspan>{L['timeframe']}\u003c/span>\u003ccode>{config.get('timeframe', '4h')}\u003c/code>\u003c/div>\n \u003c/div>\n \u003cdiv class=\"strategy-block\">\n \u003ch4>🟢 ENTRY\u003c/h4>\n \u003cdiv class=\"param-row\">\u003cspan>{L['threshold']}\u003c/span>\u003ccode class=\"green\">spread > {config.get('threshold', 3)}%\u003c/code>\u003c/div>\n \u003cdiv class=\"param-row\">\u003cspan>Direction\u003c/span>\u003ccode>{only_long_text}\u003c/code>\u003c/div>\n \u003c/div>\n \u003cdiv class=\"strategy-block\">\n \u003ch4>🔴 EXIT\u003c/h4>\n \u003cdiv class=\"param-row\">\u003cspan>{L['exit_threshold']}\u003c/span>\u003ccode>|spread| < {config.get('exit_threshold', 1)}%\u003c/code>\u003c/div>\n \u003cdiv class=\"param-row\">\u003cspan>{L['stop_loss']}\u003c/span>\u003ccode class=\"red\">-{config.get('stop_loss', 20)}%\u003c/code>\u003c/div>\n \u003cdiv class=\"param-row\">\u003cspan>{L['take_profit']}\u003c/span>\u003ccode class=\"green\">+{config.get('take_profit', 20)}%\u003c/code>\u003c/div>\n \u003c/div>\n \u003cdiv class=\"strategy-block\">\n \u003ch4>💰 CAPITAL\u003c/h4>\n \u003cdiv class=\"param-row\">\u003cspan>{L['initial_capital']}\u003c/span>\u003ccode>${config.get('initial_capital', 10000):,}\u003c/code>\u003c/div>\n \u003cdiv class=\"param-row\">\u003cspan>{L['position_size']}\u003c/span>\u003ccode>{config.get('position_size', 20)}%\u003c/code>\u003c/div>\n \u003cdiv class=\"param-row\">\u003cspan>{L['commission']}\u003c/span>\u003ccode>{config.get('commission', 0.1)}%\u003c/code>\u003c/div>\n \u003c/div>\n \u003cdiv class=\"strategy-block\">\n \u003ch4>📅 PERIOD\u003c/h4>\n \u003cdiv class=\"param-row\">\u003cspan>{'实际天数' if lang == 'zh' else 'Days'}\u003c/span>\u003ccode>{(df.index.max() - df.index.min()).days}\u003c/code>\u003c/div>\n \u003cdiv class=\"param-row\">\u003cspan>{'开始' if lang == 'zh' else 'Start'}\u003c/span>\u003ccode>{df.index.min().strftime('%m-%d')}\u003c/code>\u003c/div>\n \u003cdiv class=\"param-row\">\u003cspan>{'结束' if lang == 'zh' else 'End'}\u003c/span>\u003ccode>{df.index.max().strftime('%m-%d')}\u003c/code>\u003c/div>\n \u003c/div>\n \u003cdiv class=\"strategy-block\">\n \u003ch4>⚙️ EXECUTION\u003c/h4>\n \u003cdiv class=\"param-row\">\u003cspan>Leverage\u003c/span>\u003ccode>1x\u003c/code>\u003c/div>\n \u003cdiv class=\"param-row\">\u003cspan>Order\u003c/span>\u003ccode>Market\u003c/code>\u003c/div>\n \u003cdiv class=\"param-row\">\u003cspan>Side\u003c/span>\u003ccode>Long Only\u003c/code>\u003c/div>\n \u003c/div>\n \u003c/div>\n \u003c/section>\n \n \u003c!-- Trade History with Charts -->\n \u003csection class=\"section\">\n \u003cdiv class=\"section-header\">\n \u003cdiv class=\"section-icon\">📈\u003c/div>\n \u003ch2>{L['trade_history']}\u003c/h2>\n \u003c/div>\n \n \u003cdiv class=\"chart-label\">📊 {L['price_comparison']}\u003c/div>\n \u003cdiv class=\"chart-container\" id=\"price-chart\">\u003c/div>\n \n \u003cdiv class=\"chart-label\">📉 {L['relative_spread']}\u003c/div>\n \u003cdiv class=\"chart-container\" id=\"spread-chart\">\u003c/div>\n \n \u003cdiv class=\"trades-wrapper\">\n \u003ctable class=\"trades-table\">\n \u003cthead>\n \u003ctr>\n \u003cth>{'入场时间' if lang == 'zh' else 'Entry'}\u003c/th>\n \u003cth>{'出场时间' if lang == 'zh' else 'Exit'}\u003c/th>\n \u003cth>{L['asset']}\u003c/th>\n \u003cth>{'本金' if lang == 'zh' else 'Cost'}\u003c/th>\n \u003cth>{'数量' if lang == 'zh' else 'Qty'}\u003c/th>\n \u003cth>{'入场价' if lang == 'zh' else 'Entry

Crypto Strategy Backtest Skill Transform natural language trading ideas into validated strategies with professional backtesting, beautiful reports, and runnable code. ⚠️ SPOT ONLY : This skill supports spot trading strategies only . No leverage, no shorting, no futures/perpetual contracts. All strategies are long-only (buy low → hold → sell high). Your Superpower You turn vague trading intuitions into professional-grade, multi-dimensional strategies . When users say "buy when cheap", you don't just slap on RSI < 30 — you build a comprehensive valuation model using multiple indicators, each wi…

}\u003c/th>\n \u003cth>{'出场价' if lang == 'zh' else 'Exit

Crypto Strategy Backtest Skill Transform natural language trading ideas into validated strategies with professional backtesting, beautiful reports, and runnable code. ⚠️ SPOT ONLY : This skill supports spot trading strategies only . No leverage, no shorting, no futures/perpetual contracts. All strategies are long-only (buy low → hold → sell high). Your Superpower You turn vague trading intuitions into professional-grade, multi-dimensional strategies . When users say "buy when cheap", you don't just slap on RSI < 30 — you build a comprehensive valuation model using multiple indicators, each wi…

}\u003c/th>\n \u003cth>{'收益率' if lang == 'zh' else 'Return'}\u003c/th>\n \u003cth>{L['pnl']}\u003c/th>\n \u003c/tr>\n \u003c/thead>\n \u003ctbody>\n {trades_html}\n \u003c/tbody>\n \u003c/table>\n \u003c/div>\n \u003c/section>\n \n \u003c!-- Performance Metrics Table -->\n \u003csection class=\"section\">\n \u003cdiv class=\"section-header\">\n \u003cdiv class=\"section-icon\">📊\u003c/div>\n \u003ch2>{L['performance_metrics']}\u003c/h2>\n \u003c/div>\n \n \u003cdiv class=\"metrics-table-container\">\n \u003ctable class=\"metrics-table\">\n \u003cthead>\n \u003ctr>\n \u003cth colspan=\"2\">{'收益指标' if lang == 'zh' else 'Returns'}\u003c/th>\n \u003cth colspan=\"2\">{'风险指标' if lang == 'zh' else 'Risk'}\u003c/th>\n \u003cth colspan=\"2\">{'交易统计' if lang == 'zh' else 'Trading'}\u003c/th>\n \u003c/tr>\n \u003c/thead>\n \u003ctbody>\n \u003ctr>\n \u003ctd class=\"metric-name\">{L['total_return']}\u003c/td>\n \u003ctd class=\"metric-val {return_class}\">{metrics['total_return']:+.2f}%\u003c/td>\n \u003ctd class=\"metric-name\">{L['max_drawdown']}\u003c/td>\n \u003ctd class=\"metric-val negative\">{metrics['max_drawdown']:.2f}%\u003c/td>\n \u003ctd class=\"metric-name\">{L['total_trades']}\u003c/td>\n \u003ctd class=\"metric-val\">{metrics['total_trades']}\u003c/td>\n \u003c/tr>\n \u003ctr>\n \u003ctd class=\"metric-name\">{L['buy_hold_a'].format(a=name_a)}\u003c/td>\n \u003ctd class=\"metric-val\" style=\"color: var(--accent-btc);\">{metrics['buy_hold_return_a']:+.2f}%\u003c/td>\n \u003ctd class=\"metric-name\">{L['sharpe_ratio']}\u003c/td>\n \u003ctd class=\"metric-val\">{metrics['sharpe_ratio']:.2f}\u003c/td>\n \u003ctd class=\"metric-name\">{L['win_rate']}\u003c/td>\n \u003ctd class=\"metric-val {'positive' if metrics['win_rate'] > 50 else 'negative'}\">{metrics['win_rate']:.0f}%\u003c/td>\n \u003c/tr>\n \u003ctr>\n \u003ctd class=\"metric-name\">{L['buy_hold_b'].format(b=name_b)}\u003c/td>\n \u003ctd class=\"metric-val\" style=\"color: var(--accent-eth);\">{metrics['buy_hold_return_b']:+.2f}%\u003c/td>\n \u003ctd class=\"metric-name\">{L['profit_factor']}\u003c/td>\n \u003ctd class=\"metric-val\">{metrics['profit_factor']:.2f}\u003c/td>\n \u003ctd class=\"metric-name\">{L['trades_on_a'].format(a=name_a)}\u003c/td>\n \u003ctd class=\"metric-val\">{metrics['trades_a']}\u003c/td>\n \u003c/tr>\n \u003ctr>\n \u003ctd class=\"metric-name\">{'最终权益' if lang == 'zh' else 'Final Equity'}\u003c/td>\n \u003ctd class=\"metric-val\">${final_equity:,.2f}\u003c/td>\n \u003ctd class=\"metric-name\">{'vs 持有' if lang == 'zh' else 'vs Hold'}\u003c/td>\n \u003ctd class=\"metric-val {return_class}\">{metrics['total_return'] - metrics['buy_hold_return_b']:+.2f}%\u003c/td>\n \u003ctd class=\"metric-name\">{L['trades_on_b'].format(b=name_b)}\u003c/td>\n \u003ctd class=\"metric-val\">{metrics['trades_b']}\u003c/td>\n \u003c/tr>\n \u003c/tbody>\n \u003c/table>\n \u003c/div>\n \u003c/section>\n \n \u003c!-- Charts Row: Equity + Drawdown -->\n \u003cdiv class=\"charts-row\">\n \u003csection class=\"section chart-half\">\n \u003cdiv class=\"section-header\">\n \u003cdiv class=\"section-icon\">💰\u003c/div>\n \u003ch2>{L['equity_curve']}\u003c/h2>\n \u003c/div>\n \u003cdiv class=\"chart-container\" id=\"equity-chart\">\u003c/div>\n \u003c/section>\n \n \u003csection class=\"section chart-half\">\n \u003cdiv class=\"section-header\">\n \u003cdiv class=\"section-icon\">📉\u003c/div>\n \u003ch2>{L['max_drawdown']}\u003c/h2>\n \u003c/div>\n \u003cdiv class=\"chart-container\" id=\"drawdown-chart\">\u003c/div>\n \u003c/section>\n \u003c/div>\n \n \u003cfooter class=\"footer\">\n \u003cdiv class=\"footer-tagline\">{L['tagline']}\u003c/div>\n \u003ca href=\"https://github.com/0xrikt/crypto-skills\" target=\"_blank\" class=\"footer-github\">\n \u003csvg height=\"20\" width=\"20\" viewBox=\"0 0 16 16\" fill=\"currentColor\">\n \u003cpath d=\"M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z\"/>\n \u003c/svg>\n \u003cspan>github.com/0xrikt/crypto-skills\u003c/span>\n \u003c/a>\n \u003cdiv class=\"footer-note\">\n {L['generated']} {datetime.now().strftime('%Y-%m-%d %H:%M')} • {L['disclaimer']}\n \u003c/div>\n \u003c/footer>\n \u003c/div>\n \n \u003cscript>\n const chartTheme = {{\n paper_bgcolor: 'rgba(0,0,0,0)',\n plot_bgcolor: 'rgba(0,0,0,0)',\n font: {{ color: '#1e293b', family: 'Space Grotesk' }},\n xaxis: {{ gridcolor: 'rgba(0,0,0,0.08)', zerolinecolor: 'rgba(0,0,0,0.15)' }},\n yaxis: {{ gridcolor: 'rgba(0,0,0,0.08)', zerolinecolor: 'rgba(0,0,0,0.15)' }}\n }};\n \n // Spread Chart with Trade Markers\n Plotly.newPlot('spread-chart', [\n {{\n x: {json.dumps(timestamps)},\n y: {json.dumps(df['spread'].tolist())},\n type: 'scatter',\n mode: 'lines',\n name: 'Spread',\n line: {{ color: '#6366f1', width: 1.5 }},\n fill: 'tozeroy',\n fillcolor: 'rgba(99, 102, 241, 0.1)'\n }},\n {{\n x: {json.dumps(buy_times)},\n y: {json.dumps(buy_spreads)},\n type: 'scatter',\n mode: 'markers',\n name: '{\"买入\" if lang == \"zh\" else \"Buy\"}',\n marker: {{ symbol: 'triangle-up', size: 14, color: '#10b981', line: {{ color: '#059669', width: 2 }} }}\n }},\n {{\n x: {json.dumps(sell_times)},\n y: {json.dumps(sell_spreads)},\n type: 'scatter',\n mode: 'markers',\n name: '{\"卖出\" if lang == \"zh\" else \"Sell\"}',\n marker: {{ symbol: 'triangle-down', size: 14, color: '#f59e0b', line: {{ color: '#d97706', width: 2 }} }}\n }}\n ], {{\n ...chartTheme,\n height: 350,\n margin: {{ t: 30, r: 50, b: 50, l: 70 }},\n yaxis: {{ ...chartTheme.yaxis, title: '{name_a} vs {name_b} Spread (%)' }},\n legend: {{ orientation: 'h', y: 1.1 }},\n shapes: [\n {{ type: 'line', y0: {config.get('threshold', 3)}, y1: {config.get('threshold', 3)}, x0: 0, x1: 1, xref: 'paper', line: {{ dash: 'dash', color: '#ef4444', width: 1 }} }},\n {{ type: 'line', y0: -{config.get('threshold', 3)}, y1: -{config.get('threshold', 3)}, x0: 0, x1: 1, xref: 'paper', line: {{ dash: 'dash', color: '#22c55e', width: 1 }} }},\n {{ type: 'line', y0: 0, y1: 0, x0: 0, x1: 1, xref: 'paper', line: {{ color: '#9ca3af', width: 1 }} }}\n ]\n }}, {{ responsive: true }});\n \n // Equity Chart\n Plotly.newPlot('equity-chart', [{{\n x: {json.dumps(equity_timestamps)},\n y: {json.dumps(equities)},\n type: 'scatter',\n mode: 'lines',\n fill: 'tozeroy',\n fillcolor: 'rgba(16, 185, 129, 0.15)',\n line: {{ color: '#10b981', width: 2 }},\n name: 'Portfolio'\n }}], {{\n ...chartTheme,\n height: 280,\n margin: {{ t: 20, r: 20, b: 40, l: 70 }},\n yaxis: {{ ...chartTheme.yaxis, title: 'Equity ($)', tickformat: '$,.0f' }},\n shapes: [{{\n type: 'line',\n y0: {initial_capital}, y1: {initial_capital},\n x0: 0, x1: 1, xref: 'paper',\n line: {{ dash: 'dash', color: 'rgba(0,0,0,0.3)', width: 1 }}\n }}]\n }}, {{ responsive: true }});\n \n // Drawdown Chart\n Plotly.newPlot('drawdown-chart', [{{\n x: {json.dumps(equity_timestamps)},\n y: {json.dumps(drawdowns)},\n type: 'scatter',\n mode: 'lines',\n fill: 'tozeroy',\n fillcolor: 'rgba(239, 68, 68, 0.15)',\n line: {{ color: '#ef4444', width: 1.5 }},\n name: 'Drawdown'\n }}], {{\n ...chartTheme,\n height: 280,\n margin: {{ t: 20, r: 20, b: 40, l: 70 }},\n yaxis: {{ ...chartTheme.yaxis, title: 'Drawdown %', tickformat: '.1f' }}\n }}, {{ responsive: true }});\n \n // Price Comparison Chart with Trade Markers\n Plotly.newPlot('price-chart', [\n {{\n x: {json.dumps(timestamps)},\n y: {json.dumps(df['price_a_norm'].tolist())},\n type: 'scatter',\n mode: 'lines',\n name: '{name_a}',\n line: {{ color: '#f7931a', width: 2 }}\n }},\n {{\n x: {json.dumps(timestamps)},\n y: {json.dumps(df['price_b_norm'].tolist())},\n type: 'scatter',\n mode: 'lines',\n name: '{name_b}',\n line: {{ color: '#627eea', width: 2 }}\n }},\n {{\n x: {json.dumps(buy_times)},\n y: {json.dumps(buy_prices_norm)},\n type: 'scatter',\n mode: 'markers',\n name: '{\"买入\" if lang == \"zh\" else \"Buy\"}',\n marker: {{ symbol: 'triangle-up', size: 12, color: '#10b981', line: {{ color: '#059669', width: 2 }} }}\n }},\n {{\n x: {json.dumps(sell_times)},\n y: {json.dumps(sell_prices_norm)},\n type: 'scatter',\n mode: 'markers',\n name: '{\"卖出\" if lang == \"zh\" else \"Sell\"}',\n marker: {{ symbol: 'triangle-down', size: 12, color: '#f59e0b', line: {{ color: '#d97706', width: 2 }} }}\n }}\n ], {{\n ...chartTheme,\n height: 320,\n margin: {{ t: 30, r: 50, b: 50, l: 70 }},\n yaxis: {{ ...chartTheme.yaxis, title: '{\"标准化价格 (基准=100)\" if lang == \"zh\" else \"Normalized Price (Base=100)\"}' }},\n legend: {{ orientation: 'h', y: 1.12 }}\n }}, {{ responsive: true }});\n \u003c/script>\n\u003c/body>\n\u003c/html>'''\n \n return html\n\n\ndef main():\n parser = argparse.ArgumentParser(description='Pair Trading Backtest')\n parser.add_argument('--symbol-a', default='BTC/USDT', help='First symbol (default: BTC/USDT)')\n parser.add_argument('--symbol-b', default='ETH/USDT', help='Second symbol (default: ETH/USDT)')\n parser.add_argument('--days', type=int, default=365, help='Backtest period in days')\n parser.add_argument('--timeframe', default='4h', help='Candle timeframe')\n parser.add_argument('--lookback', type=int, default=20, help='Lookback period for spread calculation')\n parser.add_argument('--threshold', type=float, default=10.0, help='Entry threshold (spread %)')\n parser.add_argument('--exit-threshold', type=float, default=2.0, help='Exit threshold (spread %)')\n parser.add_argument('--initial-capital', type=float, default=10000, help='Initial capital')\n parser.add_argument('--position-size', type=float, default=20, help='Position size percentage')\n parser.add_argument('--stop-loss', type=float, default=10, help='Stop loss percentage')\n parser.add_argument('--take-profit', type=float, default=25, help='Take profit percentage')\n parser.add_argument('--commission', type=float, default=0.1, help='Commission percentage')\n parser.add_argument('--exchange', default='okx', help='Exchange (default: okx). Note: OKX has ~90 day limit, use binance/kucoin for longer backtests')\n parser.add_argument('--output', default='pair_trading_report.html', help='Output HTML file')\n parser.add_argument('--lang', default='en', choices=['en', 'zh'], help='Report language')\n parser.add_argument('--description', default='', help='Original strategy description')\n parser.add_argument('--only-long', default='both', choices=['A', 'B', 'both'], \n help='Only long specific asset: A, B, or both (default: both)')\n \n args = parser.parse_args()\n \n config = {\n 'symbol_a': args.symbol_a,\n 'symbol_b': args.symbol_b,\n 'days': args.days,\n 'timeframe': args.timeframe,\n 'lookback': args.lookback,\n 'threshold': args.threshold,\n 'exit_threshold': args.exit_threshold,\n 'initial_capital': args.initial_capital,\n 'position_size': args.position_size,\n 'stop_loss': args.stop_loss,\n 'take_profit': args.take_profit,\n 'commission': args.commission,\n 'description': args.description,\n 'only_long': args.only_long\n }\n \n print(f\"\\n{'='*60}\")\n print(f\" PAIR TRADING BACKTEST\")\n print(f\" {args.symbol_a} ↔ {args.symbol_b}\")\n print(f\"{'='*60}\\n\")\n \n # Fetch data\n df_a = fetch_data(args.symbol_a, args.days, args.timeframe, args.exchange)\n df_b = fetch_data(args.symbol_b, args.days, args.timeframe, args.exchange)\n \n # Calculate spread\n print(\"\\n📈 Calculating relative spread...\")\n df = calculate_spread(df_a, df_b, args.lookback)\n print(f\" ✓ Spread range: {df['spread'].min():.1f}% to {df['spread'].max():.1f}%\")\n \n # Generate signals\n print(\"\\n🎯 Generating signals...\")\n df = generate_signals(df, args.threshold, args.exit_threshold, args.only_long)\n signal_count = (df['signal'] != df['signal'].shift()).sum()\n only_long_msg = f\" (only long {args.only_long})\" if args.only_long != 'both' else \"\"\n print(f\" ✓ Generated {signal_count} signal changes{only_long_msg}\")\n \n # Run backtest\n print(\"\\n💰 Running backtest simulation...\")\n metrics, trades, equity_df = run_backtest(df, config)\n \n print(f\"\\n{'='*60}\")\n print(f\" RESULTS\")\n print(f\"{'='*60}\")\n print(f\" Total Return: {metrics['total_return']:+.1f}%\")\n print(f\" Buy & Hold {args.symbol_a.split('/')[0]}: {metrics['buy_hold_return_a']:+.1f}%\")\n print(f\" Buy & Hold {args.symbol_b.split('/')[0]}: {metrics['buy_hold_return_b']:+.1f}%\")\n print(f\" Max Drawdown: {metrics['max_drawdown']:.1f}%\")\n print(f\" Sharpe Ratio: {metrics['sharpe_ratio']:.2f}\")\n print(f\" Win Rate: {metrics['win_rate']:.0f}%\")\n print(f\" Total Trades: {metrics['total_trades']}\")\n print(f\"{'='*60}\\n\")\n \n # Generate report\n print(\"📄 Generating HTML report...\")\n html = generate_html_report(df, metrics, trades, equity_df, config, args.lang)\n \n with open(args.output, 'w', encoding='utf-8') as f:\n f.write(html)\n \n print(f\" ✓ Report saved to: {args.output}\")\n print(f\"\\n✅ Done!\\n\")\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":53157,"content_sha256":"d72f3c5a869b5b5b1111df9c2cc89746df875570f05d959f9dca04a694408b16"},{"filename":"src/smart_dca.py","content":"#!/usr/bin/env python3\n\"\"\"\nSmart DCA (Dollar Cost Averaging) Backtest\n==========================================\nIntelligent periodic investment with valuation-based allocation.\n\nFeatures:\n- Multi-factor valuation scoring\n- Dynamic allocation based on market state\n- Comparison with fixed DCA\n- Beautiful HTML report\n- Multi-language support (en/zh)\n\"\"\"\n\nimport argparse\nimport json\nfrom datetime import datetime, timedelta\nfrom pathlib import Path\n\nimport ccxt\nimport numpy as np\nimport pandas as pd\nimport pandas_ta as ta\n\n\n# ============================================================================\n# LANGUAGE LABELS\n# ============================================================================\n\nLABELS = {\n 'en': {\n 'title': 'Smart DCA vs Fixed DCA',\n 'periods': 'periods',\n 'base': 'Base',\n 'per_period': '/period',\n 'smart_dca': 'Smart DCA',\n 'fixed_dca': 'Fixed DCA',\n 'total_invested': 'Total Invested',\n 'final_value': 'Final Value',\n 'avg_cost': 'Avg Cost',\n 'smart_alpha': 'Smart DCA Alpha',\n 'cost_reduction': 'Cost Reduction',\n 'smart_btc': 'Smart DCA BTC',\n 'fixed_btc': 'Fixed DCA BTC',\n 'portfolio_growth': 'Portfolio Growth',\n 'valuation_investment': 'Valuation Score & Investment',\n 'price_chart': 'Price',\n 'valuation_score': 'Valuation Score',\n 'investment': 'Investment',\n 'smart_cost_basis': 'Smart Cost Basis',\n 'tagline': 'Validate your trading ideas in minutes',\n 'share_cta': 'Share your results to help others discover this tool!',\n 'invested': 'Invested',\n 'total_btc': 'Total BTC',\n 'return': 'Return',\n 'smart_wins': 'Smart DCA wins! Alpha',\n 'fixed_wins': 'Fixed DCA wins by',\n 'cost_reduction_label': 'Cost reduction',\n # Strategy summary labels\n 'strategy_summary': 'Strategy Summary',\n 'symbol': 'Symbol',\n 'frequency': 'Frequency',\n 'base_amount': 'Base Amount',\n 'backtest_period': 'Backtest Period',\n 'days': 'days',\n 'every_n_days': 'Every {n} days',\n 'valuation_model': 'Valuation Model',\n 'allocation_rules': 'Allocation Rules',\n 'bullish_signal': 'Bullish Signal',\n 'bearish_signal': 'Bearish Signal',\n 'weight': 'Weight',\n 'factor': 'Factor',\n 'indicator': 'Indicator',\n 'score_range': 'Score Range',\n 'allocation': 'Allocation',\n 'rsi_low': 'RSI \u003c 35',\n 'rsi_high': 'RSI > 65',\n 'below_sma200': 'Price \u003c SMA200',\n 'above_sma200_130': 'Price > SMA200 × 1.3',\n 'below_bb_lower': 'Price \u003c BB Lower',\n 'above_bb_upper': 'Price > BB Upper',\n 'drawdown_25': 'Drawdown > 25%',\n 'near_ath': 'Near ATH (\u003c 5% DD)',\n 'macd_turning_up': 'MACD turning up (negative zone)',\n 'macd_turning_down': 'MACD turning down (positive zone)',\n 'strong_buy': '🟢🟢 Strong buy zone',\n 'undervalued': '🟢 Undervalued',\n 'fair_value': '🟡 Fair value',\n 'overvalued': '🔴 Overvalued',\n 'extreme_caution': '🔴🔴 Extreme caution',\n 'date_range': 'Date Range',\n 'to': 'to',\n 'original_idea': 'Original Strategy Idea',\n },\n 'zh': {\n 'title': '智能定投 vs 等额定投',\n 'periods': '期',\n 'base': '基准',\n 'per_period': '/期',\n 'smart_dca': '智能定投',\n 'fixed_dca': '等额定投',\n 'total_invested': '总投入',\n 'final_value': '最终价值',\n 'avg_cost': '平均成本',\n 'smart_alpha': '智能定投超额收益',\n 'cost_reduction': '平均成本降低',\n 'smart_btc': '智能定投累计 BTC',\n 'fixed_btc': '等额定投累计 BTC',\n 'portfolio_growth': '资产增长对比',\n 'valuation_investment': '估值分数 & 投入金额',\n 'price_chart': '价格走势',\n 'valuation_score': '估值分数',\n 'investment': '投入金额',\n 'smart_cost_basis': '智能投入成本',\n 'tagline': '几分钟验证你的交易策略想法',\n 'share_cta': '截图分享你的回测结果,帮助更多人发现这个工具!',\n 'invested': '总投入',\n 'total_btc': '累计 BTC',\n 'return': '收益率',\n 'smart_wins': '智能定投胜出!超额收益',\n 'fixed_wins': '等额定投胜出,差距',\n 'cost_reduction_label': '平均成本降低',\n # Strategy summary labels\n 'strategy_summary': '策略摘要',\n 'symbol': '交易对',\n 'frequency': '定投频率',\n 'base_amount': '基准金额',\n 'backtest_period': '回测周期',\n 'days': '天',\n 'every_n_days': '每 {n} 天',\n 'valuation_model': '估值模型',\n 'allocation_rules': '投资分配规则',\n 'bullish_signal': '看涨信号',\n 'bearish_signal': '看跌信号',\n 'weight': '权重',\n 'factor': '因子',\n 'indicator': '指标',\n 'score_range': '分数区间',\n 'allocation': '投资倍数',\n 'rsi_low': 'RSI \u003c 35',\n 'rsi_high': 'RSI > 65',\n 'below_sma200': '价格 \u003c SMA200',\n 'above_sma200_130': '价格 > SMA200 × 1.3',\n 'below_bb_lower': '价格 \u003c 布林带下轨',\n 'above_bb_upper': '价格 > 布林带上轨',\n 'drawdown_25': '回撤 > 25%',\n 'near_ath': '接近历史高点 (回撤 \u003c 5%)',\n 'macd_turning_up': 'MACD 负值区域转正',\n 'macd_turning_down': 'MACD 正值区域转负',\n 'strong_buy': '🟢🟢 强烈买入区',\n 'undervalued': '🟢 低估',\n 'fair_value': '🟡 合理估值',\n 'overvalued': '🔴 高估',\n 'extreme_caution': '🔴🔴 极度谨慎',\n 'date_range': '回测区间',\n 'to': '至',\n 'original_idea': '原始策略想法',\n }\n}\n\n\n# ============================================================================\n# DATA FETCHING\n# ============================================================================\n\ndef fetch_ohlcv(symbol: str, timeframe: str, days: int, exchange_id: str = \"kucoin\") -> pd.DataFrame:\n \"\"\"Fetch historical OHLCV data.\"\"\"\n exchange_class = getattr(ccxt, exchange_id)\n exchange = exchange_class({'enableRateLimit': True})\n \n since = exchange.parse8601((datetime.utcnow() - timedelta(days=days)).isoformat())\n \n all_ohlcv = []\n while True:\n ohlcv = exchange.fetch_ohlcv(symbol, timeframe, since=since, limit=1000)\n if not ohlcv:\n break\n all_ohlcv.extend(ohlcv)\n since = ohlcv[-1][0] + 1\n if len(ohlcv) \u003c 1000:\n break\n \n df = pd.DataFrame(all_ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])\n df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')\n df.set_index('timestamp', inplace=True)\n df = df[~df.index.duplicated(keep='first')]\n \n return df\n\n\n# ============================================================================\n# VALUATION MODEL\n# ============================================================================\n\ndef calculate_valuation_score(df: pd.DataFrame) -> pd.DataFrame:\n \"\"\"Calculate multi-factor valuation score.\"\"\"\n df = df.copy()\n \n # 1. RSI - Momentum\n df['rsi'] = ta.rsi(df['close'], length=14)\n \n # 2. SMA(200) - Long-term trend position\n df['sma200'] = ta.sma(df['close'], length=200)\n \n # 3. Bollinger Bands - Statistical deviation\n bb = ta.bbands(df['close'], length=20, std=2.0)\n if bb is not None:\n df['bb_upper'] = bb.iloc[:, 2]\n df['bb_middle'] = bb.iloc[:, 1]\n df['bb_lower'] = bb.iloc[:, 0]\n \n # 4. Drawdown from rolling high\n df['rolling_high_90'] = df['close'].rolling(window=90).max()\n df['drawdown_pct'] = (df['close'] - df['rolling_high_90']) / df['rolling_high_90'] * 100\n \n # 5. MACD - Momentum direction\n macd = ta.macd(df['close'], fast=12, slow=26, signal=9)\n if macd is not None:\n df['macd'] = macd.iloc[:, 0]\n df['macd_signal'] = macd.iloc[:, 2]\n df['macd_hist'] = macd.iloc[:, 1]\n \n # 6. Volume relative to average\n df['volume_sma'] = ta.sma(df['volume'], length=20)\n df['volume_ratio'] = df['volume'] / df['volume_sma']\n \n # Calculate valuation score\n df['score'] = 0.0\n \n # RSI score (weight: 1.0)\n df.loc[df['rsi'] \u003c 35, 'score'] += 1.0\n df.loc[df['rsi'] > 70, 'score'] -= 1.0\n \n # Price vs SMA200 (weight: 1.0)\n df.loc[df['close'] \u003c df['sma200'], 'score'] += 1.0\n df.loc[df['close'] > df['sma200'] * 1.3, 'score'] -= 1.0\n \n # Bollinger Band position (weight: 1.0)\n df.loc[df['close'] \u003c df['bb_lower'], 'score'] += 1.0\n df.loc[df['close'] > df['bb_upper'], 'score'] -= 1.0\n \n # Drawdown score (weight: 1.0)\n df.loc[df['drawdown_pct'] \u003c -25, 'score'] += 1.0\n df.loc[df['drawdown_pct'] > -5, 'score'] -= 0.5\n \n # MACD turning (weight: 0.5)\n df['macd_turning_up'] = (df['macd_hist'] > df['macd_hist'].shift(1)) & (df['macd_hist'].shift(1) \u003c 0)\n df['macd_turning_down'] = (df['macd_hist'] \u003c df['macd_hist'].shift(1)) & (df['macd_hist'].shift(1) > 0)\n df.loc[df['macd_turning_up'], 'score'] += 0.5\n df.loc[df['macd_turning_down'], 'score'] -= 0.5\n \n # Classify market state\n df['market_state'] = 'normal'\n df.loc[df['score'] >= 3.0, 'market_state'] = 'extreme_undervalued'\n df.loc[(df['score'] >= 1.5) & (df['score'] \u003c 3.0), 'market_state'] = 'undervalued'\n df.loc[(df['score'] \u003c= -1.5) & (df['score'] > -3.0), 'market_state'] = 'overvalued'\n df.loc[df['score'] \u003c= -3.0, 'market_state'] = 'extreme_overvalued'\n \n return df\n\n\ndef get_allocation_multiplier(score: float) -> float:\n \"\"\"Get allocation multiplier based on valuation score.\"\"\"\n if score >= 3.0:\n return 2.0 # Extreme undervaluation: 2x\n elif score >= 1.5:\n return 1.5 # Undervalued: 1.5x\n elif score >= -1.5:\n return 1.0 # Normal: 1x\n elif score >= -3.0:\n return 0.5 # Overvalued: 0.5x\n else:\n return 0.25 # Extreme overvaluation: 0.25x\n\n\n# ============================================================================\n# SMART DCA SIMULATION\n# ============================================================================\n\ndef simulate_smart_dca(\n df: pd.DataFrame,\n base_amount: float = 200,\n frequency_days: int = 7\n) -> dict:\n \"\"\"Simulate smart DCA with valuation-based allocation.\"\"\"\n \n # Resample to weekly (or specified frequency)\n # Get one data point per period\n df_weekly = df.resample(f'{frequency_days}D').last().dropna()\n \n # Calculate valuation for each period\n df_with_score = calculate_valuation_score(df)\n \n # Align scores with weekly data\n df_weekly = df_weekly.copy()\n df_weekly['score'] = df_with_score['score'].reindex(df_weekly.index, method='ffill')\n df_weekly['rsi'] = df_with_score['rsi'].reindex(df_weekly.index, method='ffill')\n df_weekly['market_state'] = df_with_score['market_state'].reindex(df_weekly.index, method='ffill')\n \n # Smart DCA simulation\n smart_records = []\n smart_total_invested = 0\n smart_total_btc = 0\n \n # Fixed DCA simulation (for comparison)\n fixed_records = []\n fixed_total_invested = 0\n fixed_total_btc = 0\n \n for timestamp, row in df_weekly.iterrows():\n price = row['close']\n score = row['score'] if pd.notna(row['score']) else 0\n \n # Smart DCA\n multiplier = get_allocation_multiplier(score)\n smart_amount = base_amount * multiplier\n smart_btc_bought = smart_amount / price\n smart_total_invested += smart_amount\n smart_total_btc += smart_btc_bought\n \n smart_records.append({\n 'timestamp': timestamp,\n 'price': price,\n 'score': score,\n 'market_state': row['market_state'],\n 'multiplier': multiplier,\n 'amount_invested': smart_amount,\n 'btc_bought': smart_btc_bought,\n 'total_invested': smart_total_invested,\n 'total_btc': smart_total_btc,\n 'portfolio_value': smart_total_btc * price,\n 'avg_cost': smart_total_invested / smart_total_btc if smart_total_btc > 0 else 0\n })\n \n # Fixed DCA\n fixed_btc_bought = base_amount / price\n fixed_total_invested += base_amount\n fixed_total_btc += fixed_btc_bought\n \n fixed_records.append({\n 'timestamp': timestamp,\n 'price': price,\n 'amount_invested': base_amount,\n 'btc_bought': fixed_btc_bought,\n 'total_invested': fixed_total_invested,\n 'total_btc': fixed_total_btc,\n 'portfolio_value': fixed_total_btc * price,\n 'avg_cost': fixed_total_invested / fixed_total_btc if fixed_total_btc > 0 else 0\n })\n \n # Final metrics\n final_price = df_weekly.iloc[-1]['close']\n \n smart_final_value = smart_total_btc * final_price\n smart_return_pct = (smart_final_value - smart_total_invested) / smart_total_invested * 100\n smart_avg_cost = smart_total_invested / smart_total_btc\n \n fixed_final_value = fixed_total_btc * final_price\n fixed_return_pct = (fixed_final_value - fixed_total_invested) / fixed_total_invested * 100\n fixed_avg_cost = fixed_total_invested / fixed_total_btc\n \n return {\n 'smart': {\n 'records': smart_records,\n 'total_invested': smart_total_invested,\n 'total_btc': smart_total_btc,\n 'final_value': smart_final_value,\n 'return_pct': smart_return_pct,\n 'avg_cost': smart_avg_cost\n },\n 'fixed': {\n 'records': fixed_records,\n 'total_invested': fixed_total_invested,\n 'total_btc': fixed_total_btc,\n 'final_value': fixed_final_value,\n 'return_pct': fixed_return_pct,\n 'avg_cost': fixed_avg_cost\n },\n 'comparison': {\n 'extra_return_pct': smart_return_pct - fixed_return_pct,\n 'cost_savings_pct': (fixed_avg_cost - smart_avg_cost) / fixed_avg_cost * 100,\n 'extra_btc': smart_total_btc - fixed_total_btc,\n 'investment_diff': smart_total_invested - fixed_total_invested\n }\n }\n\n\n# ============================================================================\n# HTML REPORT\n# ============================================================================\n\ndef generate_html_report(results: dict, config: dict, lang: str = 'en') -> str:\n \"\"\"Generate beautiful HTML report for Smart DCA.\n \n Args:\n results: Backtest results dict\n config: Configuration dict\n lang: Language code ('en' or 'zh')\n \"\"\"\n L = LABELS.get(lang, LABELS['en'])\n \n smart = results['smart']\n fixed = results['fixed']\n comp = results['comparison']\n \n # Prepare chart data\n timestamps = [str(r['timestamp']) for r in smart['records']]\n smart_values = [r['portfolio_value'] for r in smart['records']]\n fixed_values = [r['portfolio_value'] for r in fixed['records']]\n smart_invested = [r['total_invested'] for r in smart['records']]\n fixed_invested = [r['total_invested'] for r in fixed['records']]\n prices = [r['price'] for r in smart['records']]\n scores = [r['score'] for r in smart['records']]\n amounts = [r['amount_invested'] for r in smart['records']]\n \n # Market state distribution\n state_counts = {}\n for r in smart['records']:\n state = r['market_state']\n state_counts[state] = state_counts.get(state, 0) + 1\n \n html = f'''\u003c!DOCTYPE html>\n\u003chtml lang=\"en\">\n\u003chead>\n \u003cmeta charset=\"UTF-8\">\n \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n \u003ctitle>Smart DCA Report | {config.get('symbol', 'BTC/USDT')}\u003c/title>\n \u003cscript src=\"https://cdn.plot.ly/plotly-2.27.0.min.js\">\u003c/script>\n \u003clink href=\"https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap\" rel=\"stylesheet\">\n \u003cstyle>\n :root {{\n /* Light theme - clean and professional */\n --bg-void: #f8fafc;\n --bg-deep: #ffffff;\n --bg-surface: #ffffff;\n --bg-elevated: #f1f5f9;\n --text-primary: #1e293b;\n --text-secondary: #64748b;\n --text-muted: #94a3b8;\n --accent-cyan: #0ea5e9;\n --accent-green: #10b981;\n --accent-red: #ef4444;\n --accent-gold: #f59e0b;\n --accent-purple: #8b5cf6;\n --border-subtle: #e2e8f0;\n }}\n \n * {{ margin: 0; padding: 0; box-sizing: border-box; }}\n \n body {{\n font-family: 'Space Grotesk', sans-serif;\n background: var(--bg-void);\n color: var(--text-primary);\n line-height: 1.6;\n }}\n \n .bg-pattern {{\n position: fixed;\n inset: 0;\n background: \n radial-gradient(ellipse at 20% 20%, rgba(14,165,233,0.05) 0%, transparent 50%),\n radial-gradient(ellipse at 80% 80%, rgba(139,92,246,0.03) 0%, transparent 50%);\n pointer-events: none;\n }}\n \n .container {{ position: relative; max-width: 1400px; margin: 0 auto; padding: 40px 24px; }}\n \n .header {{\n text-align: center;\n padding: 60px 0;\n border-bottom: 1px solid var(--border-subtle);\n margin-bottom: 48px;\n }}\n \n .header-badge {{\n display: inline-block;\n padding: 6px 16px;\n background: var(--bg-elevated);\n border: 1px solid var(--accent-cyan);\n border-radius: 20px;\n font-size: 0.75rem;\n letter-spacing: 2px;\n text-transform: uppercase;\n color: var(--accent-cyan);\n margin-bottom: 24px;\n }}\n \n .header h1 {{\n font-size: clamp(2rem, 4vw, 3rem);\n font-weight: 700;\n margin-bottom: 16px;\n color: var(--text-primary);\n }}\n \n .header-meta {{\n display: flex;\n justify-content: center;\n gap: 32px;\n flex-wrap: wrap;\n color: var(--text-secondary);\n }}\n \n /* Strategy Summary */\n .strategy-summary {{\n background: var(--bg-surface);\n border: 1px solid var(--border-subtle);\n border-radius: 20px;\n padding: 32px;\n margin-bottom: 48px;\n box-shadow: 0 1px 3px rgba(0,0,0,0.05);\n }}\n \n .strategy-summary h2 {{\n font-size: 1.25rem;\n margin-bottom: 24px;\n color: var(--text-primary);\n }}\n \n .original-idea {{\n background: var(--bg-elevated);\n border-left: 4px solid var(--accent-cyan);\n padding: 16px 20px;\n margin-bottom: 24px;\n border-radius: 0 8px 8px 0;\n font-style: italic;\n color: var(--text-secondary);\n }}\n \n .original-idea strong {{\n display: block;\n font-style: normal;\n font-size: 0.75rem;\n text-transform: uppercase;\n letter-spacing: 1px;\n color: var(--text-muted);\n margin-bottom: 8px;\n }}\n \n .info-grid {{\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n gap: 16px;\n margin-bottom: 24px;\n padding: 20px;\n background: var(--bg-elevated);\n border-radius: 12px;\n }}\n \n .info-item {{\n display: flex;\n flex-direction: column;\n gap: 4px;\n }}\n \n .info-label {{\n font-size: 0.75rem;\n text-transform: uppercase;\n letter-spacing: 1px;\n color: var(--text-muted);\n }}\n \n .info-value {{\n font-size: 1.1rem;\n font-weight: 600;\n color: var(--text-primary);\n font-family: 'JetBrains Mono', monospace;\n }}\n \n .model-grid {{\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n gap: 24px;\n }}\n \n .model-block {{\n background: var(--bg-elevated);\n border-radius: 12px;\n padding: 20px;\n }}\n \n .model-block h3 {{\n font-size: 0.9rem;\n color: var(--accent-cyan);\n margin-bottom: 16px;\n }}\n \n .model-table {{\n width: 100%;\n border-collapse: collapse;\n font-size: 0.85rem;\n }}\n \n .model-table th {{\n text-align: left;\n padding: 8px;\n background: var(--bg-deep);\n color: var(--text-secondary);\n font-weight: 500;\n text-transform: uppercase;\n font-size: 0.7rem;\n letter-spacing: 0.5px;\n }}\n \n .model-table td {{\n padding: 8px;\n border-bottom: 1px solid var(--border-subtle);\n font-family: 'JetBrains Mono', monospace;\n }}\n \n .model-table .bullish {{\n color: var(--accent-green);\n }}\n \n .model-table .bearish {{\n color: var(--accent-red);\n }}\n \n .comparison-hero {{\n display: grid;\n grid-template-columns: 1fr auto 1fr;\n gap: 24px;\n margin-bottom: 48px;\n align-items: center;\n }}\n \n .strategy-card {{\n background: var(--bg-surface);\n border: 1px solid var(--border-subtle);\n border-radius: 20px;\n padding: 32px;\n text-align: center;\n }}\n \n .strategy-card.winner {{\n border-color: var(--accent-green);\n box-shadow: 0 0 40px rgba(0,255,157,0.15);\n }}\n \n .strategy-card h3 {{\n font-size: 1.1rem;\n color: var(--text-secondary);\n margin-bottom: 16px;\n text-transform: uppercase;\n letter-spacing: 1px;\n }}\n \n .strategy-card .value {{\n font-family: 'JetBrains Mono', monospace;\n font-size: 2.5rem;\n font-weight: 700;\n margin-bottom: 8px;\n }}\n \n .strategy-card .value.positive {{ color: var(--accent-green); }}\n .strategy-card .value.negative {{ color: var(--accent-red); }}\n \n .strategy-card .sub {{ color: var(--text-secondary); font-size: 0.9rem; }}\n \n .vs-badge {{\n background: var(--bg-elevated);\n border-radius: 50%;\n width: 60px;\n height: 60px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 700;\n color: var(--accent-purple);\n }}\n \n .metrics-grid {{\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 16px;\n margin-bottom: 48px;\n }}\n \n .metric-card {{\n background: var(--bg-surface);\n border: 1px solid var(--border-subtle);\n border-radius: 12px;\n padding: 24px;\n text-align: center;\n }}\n \n .metric-card .value {{\n font-family: 'JetBrains Mono', monospace;\n font-size: 1.5rem;\n font-weight: 600;\n color: var(--accent-cyan);\n }}\n \n .metric-card .label {{\n color: var(--text-secondary);\n font-size: 0.8rem;\n text-transform: uppercase;\n margin-top: 8px;\n }}\n \n .section {{\n background: var(--bg-surface);\n border: 1px solid var(--border-subtle);\n border-radius: 20px;\n padding: 32px;\n margin-bottom: 32px;\n }}\n \n .section h2 {{\n font-size: 1.25rem;\n margin-bottom: 24px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--border-subtle);\n }}\n \n .chart-container {{\n background: var(--bg-deep);\n border-radius: 12px;\n padding: 16px;\n }}\n \n .footer {{\n margin-top: 64px;\n padding: 48px;\n background: linear-gradient(135deg, var(--bg-surface) 0%, var(--bg-elevated) 100%);\n border: 1px solid var(--accent-cyan);\n border-radius: 24px;\n text-align: center;\n }}\n \n .footer-brand {{\n font-size: 1.5rem;\n font-weight: 700;\n background: linear-gradient(135deg, var(--accent-cyan), var(--accent-green));\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n margin-bottom: 12px;\n }}\n \n .footer-cta {{\n display: inline-block;\n padding: 14px 32px;\n background: linear-gradient(135deg, var(--accent-cyan), var(--accent-green));\n color: var(--bg-void);\n font-weight: 600;\n border-radius: 12px;\n text-decoration: none;\n margin-top: 16px;\n }}\n \n @media (max-width: 768px) {{\n .comparison-hero {{ grid-template-columns: 1fr; }}\n .vs-badge {{ margin: 16px auto; }}\n }}\n \u003c/style>\n\u003c/head>\n\u003cbody>\n \u003cdiv class=\"bg-pattern\">\u003c/div>\n \u003cdiv class=\"container\">\n \u003cheader class=\"header\">\n \u003cdiv class=\"header-badge\">Smart DCA Backtest\u003c/div>\n \u003ch1>{L['title']}\u003c/h1>\n \u003cdiv class=\"header-meta\">\n \u003cspan>📊 {config.get('symbol', 'BTC/USDT')}\u003c/span>\n \u003cspan>📅 {len(smart['records'])} {L['periods']}\u003c/span>\n \u003cspan>💰 {L['base']} {config.get('base_amount', 200)} USDT{L['per_period']}\u003c/span>\n \u003c/div>\n \u003c/header>\n \n \u003c!-- Strategy Summary -->\n \u003csection class=\"strategy-summary\">\n \u003ch2>📋 {L['strategy_summary']}\u003c/h2>\n \n {f'\u003cdiv class=\"original-idea\">\u003cstrong>{L[\"original_idea\"]}\u003c/strong>\"{config.get(\"description\", \"\")}\"\u003c/div>' if config.get('description') else ''}\n \n \u003cdiv class=\"info-grid\">\n \u003cdiv class=\"info-item\">\n \u003cspan class=\"info-label\">{L['symbol']}\u003c/span>\n \u003cspan class=\"info-value\">{config.get('symbol', 'BTC/USDT')}\u003c/span>\n \u003c/div>\n \u003cdiv class=\"info-item\">\n \u003cspan class=\"info-label\">{L['frequency']}\u003c/span>\n \u003cspan class=\"info-value\">{L['every_n_days'].format(n=config.get('frequency', 7))}\u003c/span>\n \u003c/div>\n \u003cdiv class=\"info-item\">\n \u003cspan class=\"info-label\">{L['base_amount']}\u003c/span>\n \u003cspan class=\"info-value\">${config.get('base_amount', 200)}\u003c/span>\n \u003c/div>\n \u003cdiv class=\"info-item\">\n \u003cspan class=\"info-label\">{L['date_range']}\u003c/span>\n \u003cspan class=\"info-value\">{config.get('start_date', 'N/A')} {L['to']} {config.get('end_date', 'N/A')}\u003c/span>\n \u003c/div>\n \u003c/div>\n \n \u003cdiv class=\"model-grid\">\n \u003cdiv class=\"model-block\">\n \u003ch3>📊 {L['valuation_model']}\u003c/h3>\n \u003ctable class=\"model-table\">\n \u003cthead>\n \u003ctr>\n \u003cth>{L['factor']}\u003c/th>\n \u003cth>{L['bullish_signal']}\u003c/th>\n \u003cth>{L['bearish_signal']}\u003c/th>\n \u003cth>{L['weight']}\u003c/th>\n \u003c/tr>\n \u003c/thead>\n \u003ctbody>\n \u003ctr>\u003ctd>RSI(14)\u003c/td>\u003ctd class=\"bullish\">{L['rsi_low']}\u003c/td>\u003ctd class=\"bearish\">{L['rsi_high']}\u003c/td>\u003ctd>1.0\u003c/td>\u003c/tr>\n \u003ctr>\u003ctd>SMA(200)\u003c/td>\u003ctd class=\"bullish\">{L['below_sma200']}\u003c/td>\u003ctd class=\"bearish\">{L['above_sma200_130']}\u003c/td>\u003ctd>1.0\u003c/td>\u003c/tr>\n \u003ctr>\u003ctd>Bollinger\u003c/td>\u003ctd class=\"bullish\">{L['below_bb_lower']}\u003c/td>\u003ctd class=\"bearish\">{L['above_bb_upper']}\u003c/td>\u003ctd>1.0\u003c/td>\u003c/tr>\n \u003ctr>\u003ctd>Drawdown\u003c/td>\u003ctd class=\"bullish\">{L['drawdown_25']}\u003c/td>\u003ctd class=\"bearish\">{L['near_ath']}\u003c/td>\u003ctd>1.0\u003c/td>\u003c/tr>\n \u003ctr>\u003ctd>MACD\u003c/td>\u003ctd class=\"bullish\">{L['macd_turning_up']}\u003c/td>\u003ctd class=\"bearish\">{L['macd_turning_down']}\u003c/td>\u003ctd>0.5\u003c/td>\u003c/tr>\n \u003c/tbody>\n \u003c/table>\n \u003c/div>\n \n \u003cdiv class=\"model-block\">\n \u003ch3>💰 {L['allocation_rules']}\u003c/h3>\n \u003ctable class=\"model-table\">\n \u003cthead>\n \u003ctr>\n \u003cth>{L['score_range']}\u003c/th>\n \u003cth>State\u003c/th>\n \u003cth>{L['allocation']}\u003c/th>\n \u003c/tr>\n \u003c/thead>\n \u003ctbody>\n \u003ctr>\u003ctd>≥ +3.0\u003c/td>\u003ctd>{L['strong_buy']}\u003c/td>\u003ctd class=\"bullish\">× 2.0\u003c/td>\u003c/tr>\n \u003ctr>\u003ctd>+1.5 ~ +3.0\u003c/td>\u003ctd>{L['undervalued']}\u003c/td>\u003ctd class=\"bullish\">× 1.5\u003c/td>\u003c/tr>\n \u003ctr>\u003ctd>-1.5 ~ +1.5\u003c/td>\u003ctd>{L['fair_value']}\u003c/td>\u003ctd>× 1.0\u003c/td>\u003c/tr>\n \u003ctr>\u003ctd>-3.0 ~ -1.5\u003c/td>\u003ctd>{L['overvalued']}\u003c/td>\u003ctd class=\"bearish\">× 0.5\u003c/td>\u003c/tr>\n \u003ctr>\u003ctd>≤ -3.0\u003c/td>\u003ctd>{L['extreme_caution']}\u003c/td>\u003ctd class=\"bearish\">× 0.25\u003c/td>\u003c/tr>\n \u003c/tbody>\n \u003c/table>\n \u003c/div>\n \u003c/div>\n \u003c/section>\n \n \u003cdiv class=\"comparison-hero\">\n \u003cdiv class=\"strategy-card {'winner' if comp['extra_return_pct'] > 0 else ''}\">\n \u003ch3>🧠 {L['smart_dca']}\u003c/h3>\n \u003cdiv class=\"value {'positive' if smart['return_pct'] > 0 else 'negative'}\">{smart['return_pct']:+.1f}%\u003c/div>\n \u003cdiv class=\"sub\">{L['total_invested']} ${smart['total_invested']:,.0f}\u003c/div>\n \u003cdiv class=\"sub\">{L['final_value']} ${smart['final_value']:,.0f}\u003c/div>\n \u003cdiv class=\"sub\">{L['avg_cost']} ${smart['avg_cost']:,.0f}\u003c/div>\n \u003c/div>\n \n \u003cdiv class=\"vs-badge\">VS\u003c/div>\n \n \u003cdiv class=\"strategy-card {'winner' if comp['extra_return_pct'] \u003c 0 else ''}\">\n \u003ch3>📊 {L['fixed_dca']}\u003c/h3>\n \u003cdiv class=\"value {'positive' if fixed['return_pct'] > 0 else 'negative'}\">{fixed['return_pct']:+.1f}%\u003c/div>\n \u003cdiv class=\"sub\">{L['total_invested']} ${fixed['total_invested']:,.0f}\u003c/div>\n \u003cdiv class=\"sub\">{L['final_value']} ${fixed['final_value']:,.0f}\u003c/div>\n \u003cdiv class=\"sub\">{L['avg_cost']} ${fixed['avg_cost']:,.0f}\u003c/div>\n \u003c/div>\n \u003c/div>\n \n \u003cdiv class=\"metrics-grid\">\n \u003cdiv class=\"metric-card\">\n \u003cdiv class=\"value\" style=\"color: {'var(--accent-green)' if comp['extra_return_pct'] > 0 else 'var(--accent-red)'}\">{comp['extra_return_pct']:+.2f}%\u003c/div>\n \u003cdiv class=\"label\">{L['smart_alpha']}\u003c/div>\n \u003c/div>\n \u003cdiv class=\"metric-card\">\n \u003cdiv class=\"value\">{comp['cost_savings_pct']:+.2f}%\u003c/div>\n \u003cdiv class=\"label\">{L['cost_reduction']}\u003c/div>\n \u003c/div>\n \u003cdiv class=\"metric-card\">\n \u003cdiv class=\"value\">{smart['total_btc']:.6f}\u003c/div>\n \u003cdiv class=\"label\">{L['smart_btc']}\u003c/div>\n \u003c/div>\n \u003cdiv class=\"metric-card\">\n \u003cdiv class=\"value\">{fixed['total_btc']:.6f}\u003c/div>\n \u003cdiv class=\"label\">{L['fixed_btc']}\u003c/div>\n \u003c/div>\n \u003c/div>\n \n \u003csection class=\"section\">\n \u003ch2>📈 {L['portfolio_growth']}\u003c/h2>\n \u003cdiv class=\"chart-container\" id=\"value-chart\">\u003c/div>\n \u003c/section>\n \n \u003csection class=\"section\">\n \u003ch2>📊 {L['valuation_investment']}\u003c/h2>\n \u003cdiv class=\"chart-container\" id=\"allocation-chart\">\u003c/div>\n \u003c/section>\n \n \u003csection class=\"section\">\n \u003ch2>💰 {config.get('symbol', 'BTC').split('/')[0]} {L['price_chart']}\u003c/h2>\n \u003cdiv class=\"chart-container\" id=\"price-chart\">\u003c/div>\n \u003c/section>\n \n \u003cfooter class=\"footer\">\n \u003cdiv class=\"footer-brand\">🚀 Crypto Backtest Skill\u003c/div>\n \u003cdiv style=\"color: var(--text-secondary);\">{L['tagline']}\u003c/div>\n \u003ca href=\"https://github.com/0xrikt/crypto-skills\" class=\"footer-cta\" target=\"_blank\">⭐ Star on GitHub\u003c/a>\n \u003cdiv style=\"margin-top: 16px; color: var(--text-secondary); font-size: 0.85rem;\">\n {L['share_cta']}\u003cbr>\n Generated on {datetime.now().strftime('%Y-%m-%d %H:%M')} • Past performance ≠ future results\n \u003c/div>\n \u003c/footer>\n \u003c/div>\n \n \u003cscript>\n const theme = {{\n paper_bgcolor: 'rgba(0,0,0,0)',\n plot_bgcolor: 'rgba(0,0,0,0)',\n font: {{ color: '#e6edf3', family: 'Space Grotesk' }},\n xaxis: {{ gridcolor: 'rgba(255,255,255,0.06)' }},\n yaxis: {{ gridcolor: 'rgba(255,255,255,0.06)' }}\n }};\n \n // Portfolio Value Comparison\n Plotly.newPlot('value-chart', [\n {{\n x: {json.dumps(timestamps)},\n y: {json.dumps(smart_values)},\n type: 'scatter',\n mode: 'lines',\n name: '{L['smart_dca']}',\n line: {{ color: '#00ff9d', width: 2 }},\n fill: 'tozeroy',\n fillcolor: 'rgba(0,255,157,0.1)'\n }},\n {{\n x: {json.dumps(timestamps)},\n y: {json.dumps(fixed_values)},\n type: 'scatter',\n mode: 'lines',\n name: '{L['fixed_dca']}',\n line: {{ color: '#00d9ff', width: 2, dash: 'dash' }}\n }},\n {{\n x: {json.dumps(timestamps)},\n y: {json.dumps(smart_invested)},\n type: 'scatter',\n mode: 'lines',\n name: '{L['smart_cost_basis']}',\n line: {{ color: '#ffd93d', width: 1, dash: 'dot' }}\n }}\n ], {{\n ...theme,\n height: 400,\n margin: {{ t: 20, r: 20, b: 40, l: 60 }},\n yaxis: {{ ...theme.yaxis, title: 'Value ($)', tickformat: '$,.0f' }},\n legend: {{ orientation: 'h', y: 1.1 }}\n }}, {{ responsive: true }});\n \n // Allocation Chart\n Plotly.newPlot('allocation-chart', [\n {{\n x: {json.dumps(timestamps)},\n y: {json.dumps(scores)},\n type: 'scatter',\n mode: 'lines',\n name: '{L['valuation_score']}',\n line: {{ color: '#a855f7', width: 2 }},\n yaxis: 'y'\n }},\n {{\n x: {json.dumps(timestamps)},\n y: {json.dumps(amounts)},\n type: 'bar',\n name: '{L['investment']}',\n marker: {{ \n color: {json.dumps(amounts)},\n colorscale: [[0, '#ff4757'], [0.5, '#ffd93d'], [1, '#00ff9d']]\n }},\n yaxis: 'y2'\n }}\n ], {{\n ...theme,\n height: 350,\n margin: {{ t: 20, r: 60, b: 40, l: 60 }},\n yaxis: {{ ...theme.yaxis, title: '{L['valuation_score']}', side: 'left' }},\n yaxis2: {{ title: '{L['investment']} ($)', side: 'right', overlaying: 'y', tickformat: '$,.0f' }},\n legend: {{ orientation: 'h', y: 1.1 }},\n shapes: [\n {{ type: 'line', y0: 0, y1: 0, x0: 0, x1: 1, xref: 'paper', yref: 'y', line: {{ dash: 'dash', color: 'rgba(255,255,255,0.3)' }} }}\n ]\n }}, {{ responsive: true }});\n \n // Price Chart\n Plotly.newPlot('price-chart', [{{\n x: {json.dumps(timestamps)},\n y: {json.dumps(prices)},\n type: 'scatter',\n mode: 'lines',\n name: '{config.get('symbol', 'BTC').split('/')[0]} Price',\n line: {{ color: '#ffd93d', width: 2 }},\n fill: 'tozeroy',\n fillcolor: 'rgba(255,217,61,0.1)'\n }}], {{\n ...theme,\n height: 300,\n margin: {{ t: 20, r: 20, b: 40, l: 60 }},\n yaxis: {{ ...theme.yaxis, title: '{L['price_chart']} ($)', tickformat: '$,.0f' }}\n }}, {{ responsive: true }});\n \u003c/script>\n\u003c/body>\n\u003c/html>'''\n \n return html\n\n\n# ============================================================================\n# MAIN\n# ============================================================================\n\ndef main():\n parser = argparse.ArgumentParser(description='Smart DCA Backtest')\n parser.add_argument('--symbol', default='BTC/USDT', help='Trading pair')\n parser.add_argument('--days', type=int, default=1095, help='Backtest period (default: 3 years)')\n parser.add_argument('--base-amount', type=float, default=200, help='Base investment per period')\n parser.add_argument('--frequency', type=int, default=7, help='Investment frequency in days')\n parser.add_argument('--output', default='smart_dca_report.html', help='Output HTML file')\n parser.add_argument('--description', default='', help='Original strategy idea in natural language')\n parser.add_argument('--lang', default='en', choices=['en', 'zh'], help='Report language (en/zh)')\n \n args = parser.parse_args()\n \n L = LABELS.get(args.lang, LABELS['en'])\n \n print(f\"🧠 Smart DCA Backtest\")\n print(f\"{'='*50}\")\n print(f\"Symbol: {args.symbol}\")\n print(f\"Period: {args.days} days ({args.days // 365} years)\")\n print(f\"Base Amount: ${args.base_amount}/period\")\n print(f\"Frequency: Every {args.frequency} days\")\n print(f\"Language: {args.lang}\")\n print()\n \n # Fetch data\n print(\"📊 Fetching historical data...\")\n df = fetch_ohlcv(args.symbol, '1d', args.days)\n print(f\" Got {len(df)} daily candles\")\n \n # Validate data - warn if significantly less than requested\n if len(df) > 1:\n actual_days = (df.index[-1] - df.index[0]).days\n if actual_days \u003c args.days * 0.5:\n print()\n print(\"⚠️ WARNING: Received much less data than requested!\")\n print(f\" Requested: {args.days} days\")\n print(f\" Received: {actual_days} days ({len(df)} candles)\")\n print()\n print(\" 💡 TIP: OKX ~90 day limit. Use --exchange kucoin or binance for longer backtests.\")\n print()\n \n # Run simulation\n print(\"🧮 Running Smart DCA simulation...\")\n results = simulate_smart_dca(df, args.base_amount, args.frequency)\n \n smart = results['smart']\n fixed = results['fixed']\n comp = results['comparison']\n \n # Generate report\n print(\"📄 Generating report...\")\n \n # Get actual date range from data\n start_date = df.index[0].strftime('%Y-%m-%d') if len(df) > 0 else 'N/A'\n end_date = df.index[-1].strftime('%Y-%m-%d') if len(df) > 0 else 'N/A'\n \n config = {\n 'symbol': args.symbol,\n 'base_amount': args.base_amount,\n 'frequency': args.frequency,\n 'days': args.days,\n 'start_date': start_date,\n 'end_date': end_date,\n 'description': args.description,\n }\n html = generate_html_report(results, config, lang=args.lang)\n \n output_path = Path(args.output)\n output_path.write_text(html)\n print(f\" Saved: {output_path.absolute()}\")\n \n # Print results\n print()\n print(f\"{'='*50}\")\n print(f\"📈 {L['smart_dca']} vs {L['fixed_dca']}\")\n print(f\"{'='*50}\")\n print()\n print(f\"🧠 {L['smart_dca']}:\")\n print(f\" {L['invested']}: ${smart['total_invested']:,.0f}\")\n print(f\" {L['total_btc']}: {smart['total_btc']:.6f}\")\n print(f\" {L['final_value']}: ${smart['final_value']:,.0f}\")\n print(f\" {L['return']}: {smart['return_pct']:+.2f}%\")\n print(f\" {L['avg_cost']}: ${smart['avg_cost']:,.0f}\")\n print()\n print(f\"📊 {L['fixed_dca']}:\")\n print(f\" {L['invested']}: ${fixed['total_invested']:,.0f}\")\n print(f\" {L['total_btc']}: {fixed['total_btc']:.6f}\")\n print(f\" {L['final_value']}: ${fixed['final_value']:,.0f}\")\n print(f\" {L['return']}: {fixed['return_pct']:+.2f}%\")\n print(f\" {L['avg_cost']}: ${fixed['avg_cost']:,.0f}\")\n print()\n print(f\"{'='*50}\")\n \n if comp['extra_return_pct'] > 0:\n print(f\"✅ {L['smart_wins']}: {comp['extra_return_pct']:+.2f}%\")\n else:\n print(f\"📊 {L['fixed_wins']} {abs(comp['extra_return_pct']):.2f}%\")\n \n print(f\" {L['cost_reduction_label']}: {comp['cost_savings_pct']:.2f}%\")\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":41653,"content_sha256":"b2d079f96b2466040cbd3cecb549026c48b8ee6c793188310c6934c700d2322d"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Crypto Strategy Backtest Skill","type":"text"}]},{"type":"paragraph","content":[{"text":"Transform natural language trading ideas into validated strategies with professional backtesting, beautiful reports, and runnable code.","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"⚠️ ","type":"text"},{"text":"SPOT ONLY","type":"text","marks":[{"type":"strong"}]},{"text":": This skill supports ","type":"text"},{"text":"spot trading strategies only","type":"text","marks":[{"type":"strong"}]},{"text":". No leverage, no shorting, no futures/perpetual contracts. All strategies are long-only (buy low → hold → sell high).","type":"text"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Your Superpower","type":"text"}]},{"type":"paragraph","content":[{"text":"You turn vague trading intuitions into ","type":"text"},{"text":"professional-grade, multi-dimensional strategies","type":"text","marks":[{"type":"strong"}]},{"text":". When users say \"buy when cheap\", you don't just slap on RSI \u003c 30 — you build a comprehensive valuation model using multiple indicators, each with proper reasoning.","type":"text"}]},{"type":"paragraph","content":[{"text":"Your goal","type":"text","marks":[{"type":"strong"}]},{"text":": Make strategy completion so thorough that users think \"wow, I wouldn't have thought of all this myself.\"","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"CRITICAL: Parameter Estimation Principles","type":"text"}]},{"type":"paragraph","content":[{"text":"Think like a quant","type":"text","marks":[{"type":"strong"}]},{"text":": Every parameter must be justified, not guessed.","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Threshold = f(historical volatility)","type":"text","marks":[{"type":"strong"}]},{"text":": For spreads, use 1.5-2× standard deviation. For RSI, use 30/70 (BTC) or 25/75 (alts).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Stop Loss > normal noise","type":"text","marks":[{"type":"strong"}]},{"text":": BTC daily vol is 3-5%, so stop loss should be 5-8%, not 2%.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Respect user's exact specs","type":"text","marks":[{"type":"strong"}]},{"text":": If user says \"every 6 hours\", use 6h, not 2h.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Spot only","type":"text","marks":[{"type":"strong"}]},{"text":": leverage = 1x, always. No perpetuals, no shorting.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Position Size is PERCENTAGE","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"--position-size 10","type":"text","marks":[{"type":"code_inline"}]},{"text":" means 10% of capital per trade, NOT $10!","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"OKX data limit","type":"text","marks":[{"type":"strong"}]},{"text":": OKX only provides ~60-90 days of history. For longer backtests, use ","type":"text"},{"text":"--exchange kucoin","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"--exchange binance","type":"text","marks":[{"type":"code_inline"}]},{"text":" (if accessible).","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"CRITICAL: Strategy Completion Standards","type":"text"}]},{"type":"paragraph","content":[{"text":"When translating natural language to technical conditions, ","type":"text"},{"text":"NEVER use single indicators","type":"text","marks":[{"type":"strong"}]},{"text":". Always combine multiple dimensions:","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"🎯 \"Undervalued/Cheap/Oversold/Dip\" → Multi-Factor Valuation Model","type":"text"}]},{"type":"paragraph","content":[{"text":"DON'T:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"RSI(14) \u003c 30","type":"text","marks":[{"type":"code_inline"}]},{"text":" (too simplistic, easily fooled by trends)","type":"text"}]},{"type":"paragraph","content":[{"text":"DO:","type":"text","marks":[{"type":"strong"}]},{"text":" Combine 4-5 indicators for robust valuation scoring:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dimension","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Indicator","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bullish Signal","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Weight","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Momentum","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RSI(14)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003c 35","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"1.0","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Trend Position","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price vs SMA(200)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price \u003c SMA200","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"1.0","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Volatility Band","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bollinger Bands","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price \u003c BB_Lower","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"1.0","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Drawdown","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price vs 90-day High","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Drawdown > 25%","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"1.0","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Momentum Divergence","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MACD Histogram","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Turning positive while price low","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.5","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Volume Confirmation","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Volume vs MA(20)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Volume spike (>1.5x) on dip","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.5","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"Valuation Score","type":"text","marks":[{"type":"strong"}]},{"text":" = Sum of triggered signals × weights","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Score ≥ 3.0: Strong undervaluation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Score 2.0-3.0: Moderate undervaluation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Score \u003c 2.0: Weak/no signal","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"📈 \"Overvalued/Expensive/Overbought\" → Multi-Factor Model","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dimension","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Indicator","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bearish Signal","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Weight","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Momentum","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RSI(14)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"> 70","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"1.0","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Trend Extension","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price vs SMA(200)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price > SMA200 × 1.3","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"1.0","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Volatility Band","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bollinger Bands","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price > BB_Upper","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"1.0","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"From Recent Low","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price vs 90-day Low","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Gain > 50%","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"1.0","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Momentum Divergence","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MACD Histogram","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Turning negative while price high","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.5","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Volume Dry-up","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Volume vs MA(20)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Volume declining on rally","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.5","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"🚀 \"Trend/Bullish/Uptrend\" → Multi-Timeframe Confirmation","type":"text"}]},{"type":"paragraph","content":[{"text":"DON'T:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"Price > EMA(21)","type":"text","marks":[{"type":"code_inline"}]},{"text":" (single timeframe, easily whipsawed)","type":"text"}]},{"type":"paragraph","content":[{"text":"DO:","type":"text","marks":[{"type":"strong"}]},{"text":" Require alignment across timeframes:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Timeframe","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Condition","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Purpose","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Long-term","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price > SMA(200)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Major trend direction","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Medium-term","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price > EMA(50)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Intermediate trend","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Short-term","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"EMA(9) > EMA(21)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Recent momentum","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Momentum","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MACD > Signal Line","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Acceleration","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Strength","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ADX > 25","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Trend strength confirmation","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"Entry","type":"text","marks":[{"type":"strong"}]},{"text":": All conditions aligned ","type":"text"},{"text":"Exit","type":"text","marks":[{"type":"strong"}]},{"text":": Short-term reversal (EMA9 \u003c EMA21) OR momentum loss (MACD cross down)","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"💥 \"Breakout\" → Volume-Confirmed Breakout","type":"text"}]},{"type":"paragraph","content":[{"text":"DON'T:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"Price > BB_Upper","type":"text","marks":[{"type":"code_inline"}]},{"text":" (many false breakouts)","type":"text"}]},{"type":"paragraph","content":[{"text":"DO:","type":"text","marks":[{"type":"strong"}]},{"text":" Require multiple confirmations:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Condition","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Purpose","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price > BB_Upper(20, 2.0)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Statistical breakout","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Volume > 2.0 × Volume_MA(20)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Strong participation","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Close in top 25% of candle range","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Buying pressure","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RSI(14) > 50 but \u003c 80","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Momentum without exhaustion","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Previous 5 candles: tight range (BB width contracting)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Coiled energy","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"📊 \"DCA\" → Smart DCA with Valuation Adjustment","type":"text"}]},{"type":"paragraph","content":[{"text":"DON'T:","type":"text","marks":[{"type":"strong"}]},{"text":" Fixed amount every period (misses opportunities)","type":"text"}]},{"type":"paragraph","content":[{"text":"DO:","type":"text","marks":[{"type":"strong"}]},{"text":" Dynamic allocation based on valuation score:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Valuation Score","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Market State","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Allocation","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"≥ +3.0","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"🟢🟢 Extreme undervaluation","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Base × 2.0","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"+1.5 to +3.0","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"🟢 Undervalued","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Base × 1.5","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"-1.5 to +1.5","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"🟡 Fair value","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Base × 1.0","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"-3.0 to -1.5","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"🔴 Overvalued","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Base × 0.5","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"≤ -3.0","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"🔴🔴 Extreme overvaluation","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Base × 0.25","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"🔄 \"Mean Reversion\" → Statistical Deviation Strategy","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Condition","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Entry","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Exit","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Z-Score","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price Z-score \u003c -2.0","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Z-score > 0","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"BB Position","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price \u003c BB_Lower","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price > BB_Middle","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RSI","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RSI \u003c 30","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RSI > 50","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Confirmation","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Volume spike on dip","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"-","type":"text"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Workflow","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 1: Understand & Expand the Intent","type":"text"}]},{"type":"paragraph","content":[{"text":"When user describes a trading idea:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Identify the core strategy type","type":"text","marks":[{"type":"strong"}]},{"text":": Mean reversion? Trend following? Breakout? DCA?","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extract constraints","type":"text","marks":[{"type":"strong"}]},{"text":": Asset, timeframe, risk tolerance","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Expand to multi-dimensional conditions","type":"text","marks":[{"type":"strong"}]},{"text":" using the templates above","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add appropriate risk management","type":"text","marks":[{"type":"strong"}]},{"text":" based on strategy type","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 2: Present Complete Strategy for Confirmation","type":"text"}]},{"type":"paragraph","content":[{"text":"CRITICAL","type":"text","marks":[{"type":"strong"}]},{"text":": Present strategy in this exact YAML format. Users should be impressed by the thoroughness.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"## 📊 Strategy: [Descriptive Name]\n# Core Logic: [One sentence explaining the edge]\n\nData:\n primary_symbol: BTC/USDT\n timeframe: 6h # MUST match user's specification\n backtest_period: 365 days\n indicators:\n RSI: { period: 14 }\n SMA: { period: 50, 200 }\n BB: { period: 20, std_dev: 2 }\n data_requirements: [close_price, volume]\n\nSignal:\n entry_conditions:\n condition_type: ALL # ALL conditions must be met\n conditions:\n - RSI \u003c 35\n - Price \u003c BB_lower\n - Price \u003c SMA200 * 0.98\n exit_conditions:\n condition_type: ANY # ANY condition triggers exit\n conditions:\n - RSI > 70\n - Price > BB_upper\n - Price > SMA200 * 1.05\n execution_schedule:\n frequency: 6h # MUST match user's specification\n check_times: [00:00, 06:00, 12:00, 18:00]\n\nCapital:\n total_capital: 10000\n position_size_pct: 10 # 10% of capital per trade (NOT fixed dollar amount!)\n reserve_ratio: 0.2 # 20% kept as cash buffer\n max_drawdown_limit: 0.15 # 15% max drawdown\n\nRisk:\n stop_loss: 8%\n take_profit: 15% # or null if exit by signal only\n max_account_risk: 0.75\n emergency_rules:\n account_risk_threshold: 0.8 # If 80% at risk → close_all\n\nExecution:\n leverage: 1x # SPOT ONLY - always 1x\n order_type: market\n position_side: long_only\n max_positions: 1","type":"text"}]},{"type":"paragraph","content":[{"text":"✅ Please review and confirm. Reply \"OK\" to run backtest, or tell me what to adjust.","type":"text","marks":[{"type":"strong"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"⛔ STOP: WAIT FOR USER CONFIRMATION","type":"text"}]},{"type":"paragraph","content":[{"text":"DO NOT proceed to Step 3 until user explicitly confirms the strategy.","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If user says \"OK\", \"确认\", \"没问题\", \"go ahead\" → proceed to backtest","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If user has questions or wants changes → modify strategy and present again","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"NEVER run backtest without user confirmation","type":"text","marks":[{"type":"strong"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 3: Run Backtest (ONLY after user confirms)","type":"text"}]},{"type":"paragraph","content":[{"text":"IMPORTANT","type":"text","marks":[{"type":"strong"}]},{"text":": Detect the user's language and pass the ","type":"text"},{"text":"--lang","type":"text","marks":[{"type":"code_inline"}]},{"text":" parameter accordingly:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If user writes in Chinese → ","type":"text"},{"text":"--lang zh","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If user writes in English → ","type":"text"},{"text":"--lang en","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"paragraph","content":[{"text":"This ensures the HTML report text matches the user's language.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python src/backtest.py \\\n --symbol \"BTC/USDT\" \\\n --timeframe \"4h\" \\\n --days 365 \\\n --entry \"rsi\u003c35,price\u003csma200,price\u003cbb_lower\" \\\n --exit \"rsi>50,price>bb_middle\" \\\n --stop-loss 8 \\\n --take-profit 20 \\\n --output report.html \\\n --lang zh # or --lang en based on user's language","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 4: Present Results with Insights","type":"text"}]},{"type":"paragraph","content":[{"text":"Show metrics AND provide actionable insights:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"markdown"},"content":[{"text":"## 📈 Backtest Results\n\n| Metric | Value | Assessment |\n|--------|-------|------------|\n| Total Return | +47.3% | ✅ Beats B&H |\n| Max Drawdown | -18.2% | ⚠️ Moderate |\n| Sharpe Ratio | 1.42 | ✅ Good |\n| Win Rate | 64% | ✅ Healthy |\n| Profit Factor | 2.1 | ✅ Strong |\n\n### Key Insights:\n- Strategy performed best during [market condition]\n- Largest drawdown occurred during [event]\n- Consider [specific improvement] to reduce drawdown\n\n### Generated Files:\n- `report.html` - Interactive visual report\n- `strategy.py` - Runnable Python code","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 5: Suggest Iterations","type":"text"}]},{"type":"paragraph","content":[{"text":"Based on results, proactively suggest:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Parameter optimizations","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Additional filters","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Alternative approaches","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Risk adjustments","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Strategy Templates Reference","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Template 1: Multi-Factor Value Buying","type":"text"}]},{"type":"paragraph","content":[{"text":"DATA:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Symbol: BTC/USDT | Timeframe: 4h | Period: 365d","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Indicators: RSI(14), SMA(200), BB(20,2), High_90","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"SIGNAL:","type":"text","marks":[{"type":"strong"}]}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Entry (ALL)","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Exit (ANY)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RSI \u003c 35","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RSI > 65","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price \u003c SMA200_98pct","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price > SMA200","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price \u003c BB_lower","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price > BB_middle","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Drawdown > 25%","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Stop Loss 10%","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"RISK:","type":"text","marks":[{"type":"strong"}]},{"text":" Stop 10% | Take Profit 25% | Position 10%","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"Template 2: Trend Following with Confirmation","type":"text"}]},{"type":"paragraph","content":[{"text":"DATA:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Symbol: BTC/USDT | Timeframe: 4h | Period: 365d","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Indicators: SMA(200), EMA(9,21,50), MACD, ADX","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"SIGNAL:","type":"text","marks":[{"type":"strong"}]}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Entry (ALL)","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Exit (ANY)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price > SMA200","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"EMA9 \u003c EMA21","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"EMA9 > EMA21","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price \u003c SMA50","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MACD > MACD_signal","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MACD crossunder","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ADX > 25","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Stop Loss 8%","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"RISK:","type":"text","marks":[{"type":"strong"}]},{"text":" Stop 8% | Trailing Stop 3xATR | Position 15%","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"Template 3: Volume-Confirmed Breakout","type":"text"}]},{"type":"paragraph","content":[{"text":"DATA:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Symbol: BTC/USDT | Timeframe: 1h | Period: 180d","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Indicators: High_20, Volume_MA(20), RSI(14), BB(20,2)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"SIGNAL:","type":"text","marks":[{"type":"strong"}]}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Entry (ALL)","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Exit (ANY)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price > High_20","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price \u003c EMA21","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Volume > Volume_MA_200pct","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RSI > 80","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RSI between 50-75","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Stop Loss 5%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"BB_width contracting","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Take Profit 15%","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"RISK:","type":"text","marks":[{"type":"strong"}]},{"text":" Stop 5% | Take Profit 15% | Position 20%","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"Template 4: Smart DCA","type":"text"}]},{"type":"paragraph","content":[{"text":"DATA:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Symbol: BTC/USDT | Timeframe: 1d | Frequency: Weekly","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"SIGNAL (Valuation-Based Allocation):","type":"text","marks":[{"type":"strong"}]}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Score","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Market State","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Allocation","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"≥ +3","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"🟢🟢 Strong buy zone","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Base × 2.0","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"+1.5 to +3","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"🟢 Undervalued","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Base × 1.5","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"-1.5 to +1.5","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"🟡 Fair value","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Base × 1.0","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"-3 to -1.5","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"🔴 Overvalued","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Base × 0.5","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"≤ -3","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"🔴🔴 Extreme caution","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Base × 0.25","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"CAPITAL:","type":"text","marks":[{"type":"strong"}]},{"text":" Base $200/week | Reserve 20% | Max DD 15%","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"Template 5: Pair Trading / Relative Strength","type":"text"}]},{"type":"paragraph","content":[{"text":"CONCEPT:","type":"text","marks":[{"type":"strong"}]},{"text":" When two correlated assets (e.g., BTC & ETH) diverge significantly, the underperformer tends to catch up. Trade this mean reversion.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"Data:\n primary_symbols: [BTC/USDT, ETH/USDT]\n timeframe: 4h\n lookback_period: 20 # For calculating relative performance\n data_requirements: [close_price]\n\nSignal:\n entry_conditions:\n condition_type: ANY\n conditions:\n - spread > +5% # BTC outperforming → Long ETH\n - spread \u003c -5% # ETH outperforming → Long BTC\n exit_conditions:\n condition_type: ANY\n conditions:\n - abs(spread) \u003c 1% # Spread reverted to mean\n - stop_loss: -8%\n - take_profit: +15%\n\nCapital:\n total_capital: 10000\n allocation_per_trade: 20% # of total capital\n reserve_ratio: 0.3\n\nRisk:\n stop_loss: 8%\n take_profit: 15%\n max_drawdown_limit: 15%\n\nExecution:\n leverage: 1x (spot only)\n order_type: market\n position_side: long_only","type":"text"}]},{"type":"paragraph","content":[{"text":"IMPORTANT THRESHOLD GUIDELINES:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"20-period (80h ≈ 3.3 days) spread: use 3-5% threshold","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"50-period (200h ≈ 8 days) spread: use 5-8% threshold","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Never use >10% for short lookback - signals will never trigger!","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Technical Reference","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Indicators Available (pandas-ta)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Momentum: RSI, MACD, Stochastic, Williams %R, CCI\nTrend: SMA, EMA, ADX, Aroon, Supertrend\nVolatility: Bollinger Bands, ATR, Keltner Channels\nVolume: OBV, Volume SMA, VWAP","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Risk Profiles","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Conservative: SL=5%, TP=12%, position=5%, max 3 concurrent\nModerate: SL=8%, TP=20%, position=10%, max 5 concurrent\nAggressive: SL=12%, TP=35%, position=20%, max 8 concurrent","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Technical Reference: Available Indicators","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Momentum Indicators","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Indicator","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Column Name","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Description","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RSI","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"rsi","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Relative Strength Index (14)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Stochastic %K","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"stoch_k","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Stochastic oscillator K line","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Stochastic %D","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"stoch_d","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Stochastic oscillator D line","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Williams %R","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"willr","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Williams %R (14)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CCI","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"cci","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Commodity Channel Index (20)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MFI","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"mfi","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Money Flow Index (14)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ROC","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"roc","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"roc_20","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Rate of Change (10, 20)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MACD","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"macd","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"macd_signal","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"macd_hist","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MACD line, signal, histogram","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Trend Indicators","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Indicator","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Column Name","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Description","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SMA","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"sma9","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"sma21","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"sma50","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"sma100","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"sma200","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Simple Moving Averages","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"EMA","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ema9","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ema21","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ema50","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ema100","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ema200","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Exponential Moving Averages","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ADX","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"adx","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Average Directional Index (trend strength)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"+DI / -DI","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"plus_di","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"minus_di","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Directional Indicators","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Volatility Indicators","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Indicator","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Column Name","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Description","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bollinger Bands","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"bb_upper","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"bb_middle","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"bb_lower","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Upper, middle, lower bands","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"BB Width","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"bb_width","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Band width (volatility measure)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"BB %B","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"bb_pct","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price position in BB range (0-1)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ATR","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"atr","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Average True Range (14)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ATR %","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"atr_pct","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ATR as % of price","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Volume Indicators","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Indicator","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Column Name","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Description","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Volume SMA","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"volume_sma","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"20-period volume average","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Volume Ratio","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"volume_ratio","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Current volume / average","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OBV","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"obv","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"obv_sma","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"On-Balance Volume","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Price Position Indicators","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Indicator","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Column Name","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Description","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Rolling High","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"high_20","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"high_50","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"high_90","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"high_200","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"N-period high","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Rolling Low","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"low_20","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"low_50","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"low_90","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"low_200","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"N-period low","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Drawdown","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"drawdown","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"drawdown_50","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"% from rolling high","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price Position","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"price_position_90","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Position in 90-day range (0-1)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Distance from MA","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"dist_sma50","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"dist_sma200","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"% distance from MA","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Derived / Change Indicators","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Indicator","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Column Name","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Description","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price Change","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"price_change","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"price_pct_change","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"1-period change","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price Change 5","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"price_change_5","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"price_pct_change_5","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"5-period change","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RSI Change","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"rsi_change","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RSI momentum","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MACD Change","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"macd_change","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"macd_hist_change","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MACD momentum","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Consecutive Up","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"consecutive_up","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Count of consecutive up days","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Consecutive Down","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"consecutive_down","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Count of consecutive down days","type":"text"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Technical Reference: Condition Syntax","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1. Simple Comparisons","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"rsi\u003c30 # RSI below 30\nprice>sma200 # Price above SMA 200\nadx>=25 # ADX at least 25\nbb_pct\u003c0.2 # Price in lower 20% of BB range\ndrawdown\u003c-20 # Down 20% from recent high\nvolume_ratio>2 # Volume 2x above average","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"2. Crossover / Crossunder","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"macd_crossover # MACD crosses above signal (default)\nema9_cross_above_ema21 # EMA9 crosses above EMA21\nprice_crossover_sma50 # Price crosses above SMA50\nrsi_crossunder_50 # RSI crosses below 50\nstoch_k_cross_above_stoch_d # Stochastic golden cross","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"3. Turning Points","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"rsi_turning_up # RSI starts increasing\nmacd_hist_turning_down # MACD histogram starts decreasing\nprice_turning_up # Price reversal upward","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"4. Consecutive Periods","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"rsi\u003c30_for_3 # RSI below 30 for 3 consecutive periods\nprice>sma200_for_5 # Price above SMA200 for 5 periods\nconsecutive_up>=3 # At least 3 consecutive up days","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"5. Combined Conditions","type":"text"}]},{"type":"paragraph","content":[{"text":"Conditions are comma-separated. Entry uses AND logic, Exit uses OR logic.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"--entry \"rsi\u003c35,price\u003cbb_lower,volume_ratio>1.5\"\n--exit \"rsi>70,price>bb_upper\"","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Important Guidelines","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Never use single indicators","type":"text","marks":[{"type":"strong"}]},{"text":" - Always combine multiple dimensions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Explain the logic","type":"text","marks":[{"type":"strong"}]},{"text":" - Users should understand WHY each indicator is included","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Match complexity to strategy","type":"text","marks":[{"type":"strong"}]},{"text":" - DCA needs valuation model, trend following needs multi-TF","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Be honest about limitations","type":"text","marks":[{"type":"strong"}]},{"text":" - Past performance ≠ future results","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Encourage iteration","type":"text","marks":[{"type":"strong"}]},{"text":" - Backtesting is a process, not a one-shot","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"⚠️ CRITICAL: Exchange Data Limits","type":"text"}]},{"type":"paragraph","content":[{"text":"Different exchanges have different historical data limits!","type":"text","marks":[{"type":"strong"}]}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Exchange","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Approximate Limit","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Notes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OKX","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~60-90 days","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Default. Good for short-term backtests","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"KuCoin","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~200 days","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Good alternative for medium-term","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Binance","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"1000+ days","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Most data, but blocked in some regions","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bybit","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~200 days","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Good alternative","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"If you need more than 90 days of data:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# OKX default - will only get ~90 days even if you request 365\npython src/backtest.py --symbol BTC/USDT --days 365 ...\n\n# Use KuCoin for ~200 days (works in most regions)\npython src/backtest.py --symbol BTC/USDT --days 180 --exchange kucoin ...\n\n# Use Binance for 365+ days (if accessible in your region)\npython src/backtest.py --symbol BTC/USDT --days 365 --exchange binance ...","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"File Locations","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Backtest engine: ","type":"text"},{"text":"src/backtest.py","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Smart DCA: ","type":"text"},{"text":"src/smart_dca.py","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pair Trading: ","type":"text"},{"text":"src/pair_trading.py","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Output reports: current working directory","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Generated code: current working directory","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Smart DCA Usage","type":"text"}]},{"type":"paragraph","content":[{"text":"For DCA strategies, use the dedicated Smart DCA script:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python src/smart_dca.py \\\n --symbol \"BTC/USDT\" \\\n --days 1095 \\\n --base-amount 200 \\\n --frequency 7 \\\n --output smart_dca_report.html \\\n --lang zh # or --lang en based on user's language","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Pair Trading / Relative Strength Usage","type":"text"}]},{"type":"paragraph","content":[{"text":"For pair trading strategies that compare two assets (e.g., BTC vs ETH):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python src/pair_trading.py \\\n --symbol-a \"BTC/USDT\" \\\n --symbol-b \"ETH/USDT\" \\\n --days 365 \\\n --timeframe 4h \\\n --lookback 20 \\\n --threshold 10 \\\n --exit-threshold 2 \\\n --output pair_trading_report.html \\\n --lang zh # or --lang en based on user's language \\\n --description \"Long the underperformer when BTC/ETH spread deviates\"","type":"text"}]},{"type":"paragraph","content":[{"text":"Parameters:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"--lookback","type":"text","marks":[{"type":"code_inline"}]},{"text":": Period for calculating relative performance (default: 20)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"--threshold","type":"text","marks":[{"type":"code_inline"}]},{"text":": Entry threshold - spread deviation % to trigger entry (default: 10)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"--exit-threshold","type":"text","marks":[{"type":"code_inline"}]},{"text":": Exit threshold - spread deviation % to close position (default: 2)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Language Support","type":"text"}]},{"type":"paragraph","content":[{"text":"CRITICAL","type":"text","marks":[{"type":"strong"}]},{"text":": Always detect the user's language and pass the appropriate ","type":"text"},{"text":"--lang","type":"text","marks":[{"type":"code_inline"}]},{"text":" parameter:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User writes in Chinese → ","type":"text"},{"text":"--lang zh","type":"text","marks":[{"type":"code_inline"}]},{"text":" (report in Chinese)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User writes in English → ","type":"text"},{"text":"--lang en","type":"text","marks":[{"type":"code_inline"}]},{"text":" (report in English)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"This ensures the generated HTML report matches the user's language preference.","type":"text"}]}]},"metadata":{"date":"2026-06-05","name":"crypto-backtest","author":"@skillopedia","source":{"stars":18,"repo_name":"crypto-skills","origin_url":"https://github.com/0xrikt/crypto-skills/blob/HEAD/crypto-backtest/SKILL.md","repo_owner":"0xrikt","body_sha256":"5b75d5ffe4f8e00608c72f0d274e09958f940dab3a2316d493426de309e52b2f","cluster_key":"15c3f0e5a054455e34f3cefad18065a46c74e8b61ef4059e29673232e0f8696f","clean_bundle":{"format":"clean-skill-bundle-v1","source":"0xrikt/crypto-skills/crypto-backtest/SKILL.md","attachments":[{"id":"19f5c476-b01f-5f72-9dd2-3cb39ff21d4d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/19f5c476-b01f-5f72-9dd2-3cb39ff21d4d/attachment.md","path":"README.md","size":7642,"sha256":"1d0e389fa08defb7e732b56c61644ff9776cf1a6af7438d93ce3105537061c42","contentType":"text/markdown; charset=utf-8"},{"id":"b6215eeb-0069-572d-883d-f83255be0832","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b6215eeb-0069-572d-883d-f83255be0832/attachment.py","path":"btc_sentiment_strategy.py","size":6220,"sha256":"f977c0d18b3717860e0aec113271a5aff99864747ce97a08c464cb02b1d96cf4","contentType":"text/x-python; charset=utf-8"},{"id":"15097883-7fa7-569a-8cfd-7f3e48cb8279","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/15097883-7fa7-569a-8cfd-7f3e48cb8279/attachment.py","path":"btc_sentiment_strategy_20pct.py","size":6221,"sha256":"90f3cfa8ce62629c1f73caa5e4207988227260d53102b912d67ce49ff4bcc46a","contentType":"text/x-python; charset=utf-8"},{"id":"caff9150-ab2a-5b45-a2ed-2cc40e6f93fd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/caff9150-ab2a-5b45-a2ed-2cc40e6f93fd/attachment.py","path":"btc_valuation_strategy.py","size":6036,"sha256":"4a886a2872bb7a95e9070e2246e1c253385651aec948be3aa21e96113ee6b8b1","contentType":"text/x-python; charset=utf-8"},{"id":"652993d7-439a-58f1-aa76-601343668100","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/652993d7-439a-58f1-aa76-601343668100/attachment.txt","path":"requirements.txt","size":74,"sha256":"100450754b9ffdcf90e7c29f75085787be4bfdb2e3025185b2bcc55429c0099a","contentType":"text/plain; charset=utf-8"},{"id":"e0686645-f3be-56a4-a8a9-faa4e1503273","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e0686645-f3be-56a4-a8a9-faa4e1503273/attachment.py","path":"src/backtest.py","size":91624,"sha256":"470854f62ea56ba93e8d3cf935b9b78f6ea3c26f8e0d7a40235ba28a2c66f0fb","contentType":"text/x-python; charset=utf-8"},{"id":"7023db2a-ecc1-5c9d-bea6-831b3d0d6a08","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7023db2a-ecc1-5c9d-bea6-831b3d0d6a08/attachment.py","path":"src/pair_trading.py","size":53157,"sha256":"d72f3c5a869b5b5b1111df9c2cc89746df875570f05d959f9dca04a694408b16","contentType":"text/x-python; charset=utf-8"},{"id":"2db0cb1b-25ba-5e11-84ad-c613c1c8162a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2db0cb1b-25ba-5e11-84ad-c613c1c8162a/attachment.py","path":"src/smart_dca.py","size":41653,"sha256":"b2d079f96b2466040cbd3cecb549026c48b8ee6c793188310c6934c700d2322d","contentType":"text/x-python; charset=utf-8"}],"bundle_sha256":"bc9fe4529164947cae7f6aa1d44cb5353dbe8b9e9c0e29e47b303476ebf52d83","attachment_count":8,"text_attachments":8,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"crypto-backtest/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"testing-qa","category_label":"Testing"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"testing-qa","import_tag":"clean-skills-v1","description":"Backtest crypto trading strategies from natural language ideas.\nUse when: user describes trading ideas, wants to validate strategies, mentions\n\"backtest\", \"trading strategy\", \"buy low sell high\", \"RSI\", \"MACD\", \"oversold\",\n\"overbought\", \"crypto strategy\", \"validate strategy\", \"backtest\", \"DCA\", or similar.\n","allowed-tools":"Bash, Read, Write, Edit, Grep, Glob"}},"renderedAt":1782986761516}

Crypto Strategy Backtest Skill Transform natural language trading ideas into validated strategies with professional backtesting, beautiful reports, and runnable code. ⚠️ SPOT ONLY : This skill supports spot trading strategies only . No leverage, no shorting, no futures/perpetual contracts. All strategies are long-only (buy low → hold → sell high). Your Superpower You turn vague trading intuitions into professional-grade, multi-dimensional strategies . When users say "buy when cheap", you don't just slap on RSI < 30 — you build a comprehensive valuation model using multiple indicators, each wi…