diff --git a/README_KO.md b/README_KO.md
index 06899ff..74e2e12 100644
--- a/README_KO.md
+++ b/README_KO.md
@@ -59,6 +59,9 @@
---
+## 📚 문서
+- [Python 전략 개발 가이드](docs/STRATEGY_DEV_GUIDE_KO.md)
+
## 📸 비주얼 투어
diff --git a/README_TW.md b/README_TW.md
index 986bdc6..2b7b09c 100644
--- a/README_TW.md
+++ b/README_TW.md
@@ -59,6 +59,9 @@
---
+## 📚 文檔
+- [Python 策略開發指南](docs/STRATEGY_DEV_GUIDE_TW.md)
+
## 📸 功能預覽
diff --git a/backend_api_python/app/data_sources/crypto.py b/backend_api_python/app/data_sources/crypto.py
index f999abf..7b55a8d 100644
--- a/backend_api_python/app/data_sources/crypto.py
+++ b/backend_api_python/app/data_sources/crypto.py
@@ -1,6 +1,6 @@
"""
加密货币数据源
-使用 CCXT (Binance) 获取数据
+使用 CCXT (Coinbase) 获取数据
"""
from typing import Dict, List, Any, Optional
from datetime import datetime, timedelta
@@ -34,11 +34,19 @@ class CryptoDataSource(BaseDataSource):
'https': CCXTConfig.PROXY
}
- self.exchange = ccxt.binance(config)
+ exchange_id = CCXTConfig.DEFAULT_EXCHANGE
+
+ # 动态加载交易所类
+ if not hasattr(ccxt, exchange_id):
+ logger.warning(f"CCXT exchange '{exchange_id}' not found, falling back to 'coinbase'")
+ exchange_id = 'coinbase'
+
+ exchange_class = getattr(ccxt, exchange_id)
+ self.exchange = exchange_class(config)
def get_ticker(self, symbol: str) -> Dict[str, Any]:
"""
- Get latest ticker for a crypto symbol via CCXT (Binance).
+ Get latest ticker for a crypto symbol via CCXT.
Accepts common formats:
- BTC/USDT
@@ -50,6 +58,7 @@ class CryptoDataSource(BaseDataSource):
sym = sym.split(":", 1)[0]
sym = sym.upper()
if "/" not in sym:
+ # Coinbase often uses USD, check if we need to adapt
if sym.endswith("USDT") and len(sym) > 4:
sym = f"{sym[:-4]}/USDT"
elif sym.endswith("USD") and len(sym) > 3:
@@ -131,7 +140,7 @@ class CryptoDataSource(BaseDataSource):
# 分页获取数据,直到覆盖完整时间范围
all_ohlcv = []
- batch_limit = 1000 # Binance 单次最大返回量
+ batch_limit = 300 # Coinbase limit is often 300, safer than 1000
current_since = since
while current_since < end_ms:
diff --git a/backend_api_python/app/routes/settings.py b/backend_api_python/app/routes/settings.py
index e2e91f3..e88db64 100644
--- a/backend_api_python/app/routes/settings.py
+++ b/backend_api_python/app/routes/settings.py
@@ -124,7 +124,7 @@ CONFIG_SCHEMA = {
{'key': 'FINNHUB_API_KEY', 'label': 'Finnhub API Key', 'type': 'password', 'required': False, 'link': 'https://finnhub.io/register', 'link_text': 'settings.link.freeRegister'},
{'key': 'FINNHUB_TIMEOUT', 'label': 'Finnhub超时(秒)', 'type': 'number', 'default': '10'},
{'key': 'FINNHUB_RATE_LIMIT', 'label': 'Finnhub速率限制', 'type': 'number', 'default': '60'},
- {'key': 'CCXT_DEFAULT_EXCHANGE', 'label': 'CCXT默认交易所', 'type': 'text', 'default': 'binance', 'link': 'https://github.com/ccxt/ccxt#supported-cryptocurrency-exchange-markets', 'link_text': 'settings.link.supportedExchanges'},
+ {'key': 'CCXT_DEFAULT_EXCHANGE', 'label': 'CCXT默认交易所', 'type': 'text', 'default': 'coinbase', 'link': 'https://github.com/ccxt/ccxt#supported-cryptocurrency-exchange-markets', 'link_text': 'settings.link.supportedExchanges'},
{'key': 'CCXT_TIMEOUT', 'label': 'CCXT超时(ms)', 'type': 'number', 'default': '10000'},
{'key': 'CCXT_PROXY', 'label': 'CCXT代理', 'type': 'text', 'required': False},
{'key': 'AKSHARE_TIMEOUT', 'label': 'Akshare超时(秒)', 'type': 'number', 'default': '30'},
diff --git a/backend_api_python/env.example b/backend_api_python/env.example
index ff7ee5c..a22b61c 100644
--- a/backend_api_python/env.example
+++ b/backend_api_python/env.example
@@ -130,7 +130,7 @@ FINNHUB_TIMEOUT=10
FINNHUB_RATE_LIMIT=60
# CCXT (crypto via Binance by default)
-CCXT_DEFAULT_EXCHANGE=binance
+CCXT_DEFAULT_EXCHANGE=coinbase
CCXT_TIMEOUT=10000
CCXT_PROXY=
diff --git a/docs/STRATEGY_DEV_GUIDE.md b/docs/STRATEGY_DEV_GUIDE.md
new file mode 100644
index 0000000..11381b6
--- /dev/null
+++ b/docs/STRATEGY_DEV_GUIDE.md
@@ -0,0 +1,235 @@
+# QuantDinger Python Strategy Development Guide
+
+This guide describes how to develop trading strategies using Python in the QuantDinger platform. QuantDinger provides a flexible execution environment that supports data access, indicator calculation, and signal generation.
+
+## 1. Overview
+
+Strategies in QuantDinger operate based on the **Signal Provider** mode. The system executes your Python script, which is expected to process market data (a DataFrame) and output trading signals.
+
+The execution flow is as follows:
+1. **Input**: The system injects a `df` (Pandas DataFrame) containing OHLCV data into your script environment.
+2. **Processing**: You use Python (`pandas`, `numpy`) to calculate indicators and define `buy`/`sell` logic.
+3. **Output**: You construct a specific `output` dictionary containing plot data and signals.
+
+---
+
+## 2. Environment & Data
+
+Your script runs in a sandboxed Python environment.
+
+### 2.1 Pre-imported Libraries
+The following libraries are available by default (do not `import` them):
+* `pd` (pandas)
+* `np` (numpy)
+
+### 2.2 Input Data (`df`)
+A Pandas DataFrame variable named `df` is automatically available in the global scope. It contains the historical market data for the selected symbol and timeframe.
+
+**Columns:**
+* `time`: Timestamp (datetime or int, depending on context, usually localized)
+* `open`: Open price (float)
+* `high`: High price (float)
+* `low`: Low price (float)
+* `close`: Close price (float)
+* `volume`: Trading volume (float)
+
+**Example:**
+```python
+# Access closing prices
+closes = df['close']
+
+# Calculate a Simple Moving Average (SMA)
+sma_20 = df['close'].rolling(20).mean()
+```
+
+---
+
+## 3. Developing a Strategy
+
+A standard strategy script consists of three parts:
+1. **Indicator Calculation**: Compute technical indicators.
+2. **Signal Generation**: Define logic for Buy and Sell signals.
+3. **Output Construction**: Format the results for the chart and execution engine.
+
+### 3.1 Indicator Calculation
+You can use standard Pandas operations to calculate indicators.
+
+```python
+# Example: MACD Calculation
+short_window = 12
+long_window = 26
+signal_window = 9
+
+ema12 = df['close'].ewm(span=short_window, adjust=False).mean()
+ema26 = df['close'].ewm(span=long_window, adjust=False).mean()
+macd = ema12 - ema26
+signal_line = macd.ewm(span=signal_window, adjust=False).mean()
+```
+
+### 3.2 Signal Generation (Crucial)
+
+You **MUST** create two boolean Series in the `df` or as standalone variables, named `buy` and `sell`.
+
+* `True` indicates a signal trigger.
+* `False` indicates no signal.
+
+**Important: Edge Triggering**
+To avoid repeated signals on consecutive candles (which might lead to multiple orders depending on backend config), it is best practice to use **edge-triggered** signals (signal only on the moment the condition becomes true).
+
+```python
+# Condition: Close price crosses above SMA 20
+condition_buy = (df['close'] > sma_20) & (df['close'].shift(1) <= sma_20.shift(1))
+
+# Condition: Close price crosses below SMA 20
+condition_sell = (df['close'] < sma_20) & (df['close'].shift(1) >= sma_20.shift(1))
+
+# Assign to df (Required for backtesting)
+df['buy'] = condition_buy.fillna(False)
+df['sell'] = condition_sell.fillna(False)
+```
+
+**Note on Signal Types:**
+* QuantDinger normalizes signals based on your strategy configuration (Long-only, Short-only, or Bi-directional).
+* Your script simply outputs "buy" (bullish intent) or "sell" (bearish intent). The backend handles opening/closing positions.
+
+### 3.3 Visual Markers
+For charting, you often want to place the signal icon slightly above or below the candle.
+
+```python
+# Place Buy marker 0.5% below the Low
+buy_marks = [
+ df['low'].iloc[i] * 0.995 if df['buy'].iloc[i] else None
+ for i in range(len(df))
+]
+
+# Place Sell marker 0.5% above the High
+sell_marks = [
+ df['high'].iloc[i] * 1.005 if df['sell'].iloc[i] else None
+ for i in range(len(df))
+]
+```
+
+### 3.4 The `output` Variable (Mandatory)
+The final step is to assign a dictionary to the variable `output`. This tells the frontend what to draw and the backend where the signals are.
+
+**Structure:**
+```python
+output = {
+ "name": "My Strategy Name",
+ "plots": [ ... ], # List of lines/indicators to draw
+ "signals": [ ... ] # List of signal markers
+}
+```
+
+**Plots Schema:**
+* `name`: Legend name (e.g., "SMA 20")
+* `data`: List of values (must match `df` length). Use `.tolist()`.
+* `color`: Hex color string (e.g., "#ff0000").
+* `overlay`: `True` to draw on main chart (price), `False` to draw on separate pane (like RSI/MACD).
+
+**Signals Schema:**
+* `type`: Must be "buy" or "sell".
+* `text`: Text to display on icon (e.g., "B", "S").
+* `data`: List of values (prices) where the icon appears. `None` where no signal.
+* `color`: Icon color.
+
+---
+
+## 4. Complete Example: Dual SMA Crossover
+
+Here is a full, copy-pasteable example of a strategy that buys when SMA(10) crosses above SMA(30) and sells when it crosses below.
+
+```python
+# 1. Indicator Calculation
+# -----------------------
+# Calculate Short and Long SMAs
+sma_short = df['close'].rolling(10).mean()
+sma_long = df['close'].rolling(30).mean()
+
+# 2. Signal Logic
+# -----------------------
+# Buy: Short SMA crosses above Long SMA
+raw_buy = (sma_short > sma_long) & (sma_short.shift(1) <= sma_long.shift(1))
+
+# Sell: Short SMA crosses below Long SMA
+raw_sell = (sma_short < sma_long) & (sma_short.shift(1) >= sma_long.shift(1))
+
+# Clean up NaNs and ensure boolean type
+buy = raw_buy.fillna(False)
+sell = raw_sell.fillna(False)
+
+# Assign to df columns (CRITICAL for backend execution)
+df['buy'] = buy
+df['sell'] = sell
+
+# 3. Visual Formatting
+# -----------------------
+# Calculate marker positions
+buy_marks = [
+ df['low'].iloc[i] * 0.995 if buy.iloc[i] else None
+ for i in range(len(df))
+]
+
+sell_marks = [
+ df['high'].iloc[i] * 1.005 if sell.iloc[i] else None
+ for i in range(len(df))
+]
+
+# 4. Final Output
+# -----------------------
+output = {
+ 'name': 'Dual SMA Strategy',
+ 'plots': [
+ {
+ 'name': 'SMA 10',
+ 'data': sma_short.fillna(0).tolist(),
+ 'color': '#1890ff',
+ 'overlay': True
+ },
+ {
+ 'name': 'SMA 30',
+ 'data': sma_long.fillna(0).tolist(),
+ 'color': '#faad14',
+ 'overlay': True
+ }
+ ],
+ 'signals': [
+ {
+ 'type': 'buy',
+ 'text': 'B',
+ 'data': buy_marks,
+ 'color': '#00E676'
+ },
+ {
+ 'type': 'sell',
+ 'text': 'S',
+ 'data': sell_marks,
+ 'color': '#FF5252'
+ }
+ ]
+}
+```
+
+## 5. Best Practices & Troubleshooting
+
+### 5.1 Handling NaNs
+Rolling calculations (like `rolling(14)`) produce `NaN` values at the beginning of the data.
+* **Rule**: Always handle `NaN`s before generating signals.
+* **Fix**: Use `.fillna(0)` or `.fillna(False)` depending on context.
+
+### 5.2 Look-ahead Bias
+The system executes trades based on the signal generated at the *close* of a bar.
+* The backtester typically executes the order at the **Open** of the **Next Bar**.
+* Your signal logic should rely on `close` (current completed bar) or `shift(1)` (previous bar). Do not use `shift(-1)`.
+
+### 5.3 Performance
+Avoid iterating over the DataFrame rows (`for i in range(len(df)): ...`) for calculation logic. It is slow.
+* **Bad**: Loop to calculate SMA.
+* **Good**: `df['close'].rolling(...)`.
+* **Exception**: Constructing the `buy_marks`/`sell_marks` list usually requires a list comprehension, which is acceptable for visual output.
+
+### 5.4 Debugging
+Since you cannot see `print()` output easily in some execution modes, check the backend logs (`backend_api_python/logs/app.log`) if your strategy fails to load.
+* Common error: `KeyError` (wrong column name).
+* Common error: `ValueError` (arrays must be same length). Ensure `plots` data matches `df` length.
+
diff --git a/docs/STRATEGY_DEV_GUIDE_CN.md b/docs/STRATEGY_DEV_GUIDE_CN.md
new file mode 100644
index 0000000..d874ea8
--- /dev/null
+++ b/docs/STRATEGY_DEV_GUIDE_CN.md
@@ -0,0 +1,235 @@
+# QuantDinger Python 策略开发指南
+
+本指南将详细介绍如何在 QuantDinger 平台中使用 Python 开发交易策略。QuantDinger 提供了灵活的执行环境,支持数据访问、指标计算和信号生成。
+
+## 1. 概览
+
+QuantDinger 中的策略基于 **信号提供者 (Signal Provider)** 模式运行。系统执行你的 Python 脚本,该脚本负责处理市场数据(DataFrame)并输出交易信号。
+
+执行流程如下:
+1. **输入**:系统将包含 OHLCV 数据的 `df` (Pandas DataFrame) 注入到你的脚本环境中。
+2. **处理**:你使用 Python (`pandas`, `numpy`) 计算指标并定义 `buy`/`sell` 逻辑。
+3. **输出**:你构造一个特定的 `output` 字典,包含绘图数据和信号。
+
+---
+
+## 2. 环境与数据
+
+你的脚本运行在一个沙盒化的 Python 环境中。
+
+### 2.1 预导入库
+以下库默认可用(**无需** `import`):
+* `pd` (pandas)
+* `np` (numpy)
+
+### 2.2 输入数据 (`df`)
+一个名为 `df` 的 Pandas DataFrame 变量会自动存在于全局作用域中。它包含所选代码和时间周期的历史市场数据。
+
+**列 (Columns):**
+* `time`: 时间戳 (datetime 或 int,视上下文而定)
+* `open`: 开盘价 (float)
+* `high`: 最高价 (float)
+* `low`: 最低价 (float)
+* `close`: 收盘价 (float)
+* `volume`: 成交量 (float)
+
+**示例:**
+```python
+# 获取收盘价序列
+closes = df['close']
+
+# 计算简单移动平均线 (SMA)
+sma_20 = df['close'].rolling(20).mean()
+```
+
+---
+
+## 3. 开发策略
+
+一个标准的策略脚本包含三个部分:
+1. **指标计算**:计算技术指标。
+2. **信号生成**:定义买入和卖出信号逻辑。
+3. **输出构建**:格式化结果以供图表展示和执行引擎使用。
+
+### 3.1 指标计算
+你可以使用标准的 Pandas 操作来计算指标。
+
+```python
+# 示例:MACD 计算
+short_window = 12
+long_window = 26
+signal_window = 9
+
+ema12 = df['close'].ewm(span=short_window, adjust=False).mean()
+ema26 = df['close'].ewm(span=long_window, adjust=False).mean()
+macd = ema12 - ema26
+signal_line = macd.ewm(span=signal_window, adjust=False).mean()
+```
+
+### 3.2 信号生成 (关键)
+
+你 **必须** 在 `df` 中创建(或作为独立变量)两个布尔类型的 Series,分别命名为 `buy` 和 `sell`。
+
+* `True` 表示触发信号。
+* `False` 表示无信号。
+
+**重要:边缘触发 (Edge Triggering)**
+为了避免在连续的 K 线上重复发出信号(这可能导致重复下单,取决于后端配置),最佳实践是使用 **边缘触发** 信号(即只在条件变真的那一刻发出信号)。
+
+```python
+# 条件:收盘价上穿 SMA 20
+condition_buy = (df['close'] > sma_20) & (df['close'].shift(1) <= sma_20.shift(1))
+
+# 条件:收盘价下穿 SMA 20
+condition_sell = (df['close'] < sma_20) & (df['close'].shift(1) >= sma_20.shift(1))
+
+# 赋值给 df (回测必需)
+df['buy'] = condition_buy.fillna(False)
+df['sell'] = condition_sell.fillna(False)
+```
+
+**关于信号类型的说明:**
+* QuantDinger 会根据你的策略配置(仅做多、仅做空或双向)来标准化信号。
+* 你的脚本只需输出 "buy"(看涨意图)或 "sell"(看跌意图)。后端会处理开仓/平仓逻辑。
+
+### 3.3 可视化标记
+为了在图表上展示,你通常希望将信号图标放在 K 线的上方或下方。
+
+```python
+# 将买入标记放在最低价下方 0.5% 处
+buy_marks = [
+ df['low'].iloc[i] * 0.995 if df['buy'].iloc[i] else None
+ for i in range(len(df))
+]
+
+# 将卖出标记放在最高价上方 0.5% 处
+sell_marks = [
+ df['high'].iloc[i] * 1.005 if df['sell'].iloc[i] else None
+ for i in range(len(df))
+]
+```
+
+### 3.4 `output` 变量 (必须)
+最后一步是将一个字典赋值给变量 `output`。这告诉前端如何绘图,以及告诉后端信号在哪里。
+
+**结构:**
+```python
+output = {
+ "name": "我的策略名称",
+ "plots": [ ... ], # 要绘制的线条/指标列表
+ "signals": [ ... ] # 信号标记列表
+}
+```
+
+**Plots Schema (绘图配置):**
+* `name`: 图例名称 (例如 "SMA 20")
+* `data`: 数值列表 (必须与 `df` 长度一致)。使用 `.tolist()` 转换。
+* `color`: 十六进制颜色字符串 (例如 "#ff0000")。
+* `overlay`: `True` 表示绘制在主图(价格图)上,`False` 表示绘制在副图(如 RSI/MACD)。
+
+**Signals Schema (信号配置):**
+* `type`: 必须是 "buy" 或 "sell"。
+* `text`: 图标上显示的文本 (例如 "B", "S")。
+* `data`: 数值列表 (价格位置)。无信号处为 `None`。
+* `color`: 图标颜色。
+
+---
+
+## 4. 完整示例:双均线交叉 (Dual SMA)
+
+以下是一个完整的、可复制的双均线策略示例:当 SMA(10) 上穿 SMA(30) 时买入,下穿时卖出。
+
+```python
+# 1. 指标计算
+# -----------------------
+# 计算短期和长期 SMA
+sma_short = df['close'].rolling(10).mean()
+sma_long = df['close'].rolling(30).mean()
+
+# 2. 信号逻辑
+# -----------------------
+# 买入:短期 SMA 上穿 长期 SMA
+raw_buy = (sma_short > sma_long) & (sma_short.shift(1) <= sma_long.shift(1))
+
+# 卖出:短期 SMA 下穿 长期 SMA
+raw_sell = (sma_short < sma_long) & (sma_short.shift(1) >= sma_long.shift(1))
+
+# 清理 NaN 并确保布尔类型
+buy = raw_buy.fillna(False)
+sell = raw_sell.fillna(False)
+
+# 赋值给 df 列 (后端执行的关键)
+df['buy'] = buy
+df['sell'] = sell
+
+# 3. 可视化格式化
+# -----------------------
+# 计算标记位置
+buy_marks = [
+ df['low'].iloc[i] * 0.995 if buy.iloc[i] else None
+ for i in range(len(df))
+]
+
+sell_marks = [
+ df['high'].iloc[i] * 1.005 if sell.iloc[i] else None
+ for i in range(len(df))
+]
+
+# 4. 最终输出
+# -----------------------
+output = {
+ 'name': '双均线策略',
+ 'plots': [
+ {
+ 'name': 'SMA 10',
+ 'data': sma_short.fillna(0).tolist(),
+ 'color': '#1890ff',
+ 'overlay': True
+ },
+ {
+ 'name': 'SMA 30',
+ 'data': sma_long.fillna(0).tolist(),
+ 'color': '#faad14',
+ 'overlay': True
+ }
+ ],
+ 'signals': [
+ {
+ 'type': 'buy',
+ 'text': '买',
+ 'data': buy_marks,
+ 'color': '#00E676'
+ },
+ {
+ 'type': 'sell',
+ 'text': '卖',
+ 'data': sell_marks,
+ 'color': '#FF5252'
+ }
+ ]
+}
+```
+
+## 5. 最佳实践与故障排除
+
+### 5.1 处理 NaN
+滚动计算(如 `rolling(14)`)会在数据开头产生 `NaN` 值。
+* **规则**:生成信号前必须处理 `NaN`。
+* **修复**:使用 `.fillna(0)` 或 `.fillna(False)`。
+
+### 5.2 未来函数 (Look-ahead Bias)
+系统基于 K 线 **收盘** 时产生的信号执行交易。
+* 回测引擎通常在 **下一根 K 线的开盘价** 执行订单。
+* 你的信号逻辑应依赖 `close` (当前已完成的 K 线) 或 `shift(1)` (前一根 K 线)。切勿使用 `shift(-1)`。
+
+### 5.3 性能
+避免在计算逻辑中遍历 DataFrame 行 (`for i in range(len(df)): ...`)。这非常慢。
+* **错误**:使用循环计算 SMA。
+* **正确**:使用 `df['close'].rolling(...)`。
+* **例外**:构建 `buy_marks`/`sell_marks` 列表通常需要列表推导式,这是可接受的(仅用于可视化输出)。
+
+### 5.4 调试
+由于在某些执行模式下无法轻易看到 `print()` 输出,如果策略加载失败,请检查后端日志 (`backend_api_python/logs/app.log`)。
+* 常见错误:`KeyError` (列名错误)。
+* 常见错误:`ValueError` (数组长度不一致)。确保 `plots` 中的数据长度与 `df` 一致。
+
diff --git a/docs/STRATEGY_DEV_GUIDE_JA.md b/docs/STRATEGY_DEV_GUIDE_JA.md
new file mode 100644
index 0000000..9a60064
--- /dev/null
+++ b/docs/STRATEGY_DEV_GUIDE_JA.md
@@ -0,0 +1,235 @@
+# QuantDinger Python 戦略開発ガイド
+
+このガイドでは、QuantDingerプラットフォームでPythonを使用して取引戦略を開発する方法について詳しく説明します。QuantDingerは、データアクセス、インジケーター計算、シグナル生成をサポートする柔軟な実行環境を提供します。
+
+## 1. 概要
+
+QuantDingerの戦略は、**シグナルプロバイダー(Signal Provider)** モードに基づいて動作します。システムはPythonスクリプトを実行し、スクリプトは市場データ(DataFrame)を処理して取引シグナルを出力します。
+
+実行フローは以下の通りです:
+1. **入力**: システムは、OHLCVデータを含む `df`(Pandas DataFrame)をスクリプト環境に注入します。
+2. **処理**: Python(`pandas`、`numpy`)を使用してインジケーターを計算し、`buy`/`sell` ロジックを定義します。
+3. **出力**: プロットデータとシグナルを含む特定の `output` 辞書を構築します。
+
+---
+
+## 2. 環境とデータ
+
+スクリプトはサンドボックス化されたPython環境で実行されます。
+
+### 2.1 プリインポートされたライブラリ
+以下のライブラリはデフォルトで利用可能です(`import` する**必要はありません**):
+* `pd` (pandas)
+* `np` (numpy)
+
+### 2.2 入力データ (`df`)
+`df` という名前のPandas DataFrame変数が自動的にグローバルスコープに存在します。これには、選択された銘柄と時間枠の過去の市場データが含まれています。
+
+**列 (Columns):**
+* `time`: タイムスタンプ (datetime または int、コンテキストによる)
+* `open`: 始値 (float)
+* `high`: 高値 (float)
+* `low`: 安値 (float)
+* `close`: 終値 (float)
+* `volume`: 出来高 (float)
+
+**例:**
+```python
+# 終値シリーズを取得
+closes = df['close']
+
+# 単純移動平均線 (SMA) を計算
+sma_20 = df['close'].rolling(20).mean()
+```
+
+---
+
+## 3. 戦略の開発
+
+標準的な戦略スクリプトは、3つの部分で構成されます:
+1. **インジケーター計算**: テクニカル指標を計算します。
+2. **シグナル生成**: 買いと売りのシグナルロジックを定義します。
+3. **出力の構築**: チャート表示と実行エンジンのための結果をフォーマットします。
+
+### 3.1 インジケーター計算
+標準的なPandas操作を使用してインジケーターを計算できます。
+
+```python
+# 例: MACD 計算
+short_window = 12
+long_window = 26
+signal_window = 9
+
+ema12 = df['close'].ewm(span=short_window, adjust=False).mean()
+ema26 = df['close'].ewm(span=long_window, adjust=False).mean()
+macd = ema12 - ema26
+signal_line = macd.ewm(span=signal_window, adjust=False).mean()
+```
+
+### 3.2 シグナル生成 (重要)
+
+`df` 内に(または独立した変数として)`buy` と `sell` という名前の2つのブール型Seriesを **作成する必要があります**。
+
+* `True` はシグナルトリガーを示します。
+* `False` はシグナルなしを示します。
+
+**重要: エッジトリガー (Edge Triggering)**
+連続したローソク足でシグナルが繰り返し発生するのを防ぐため(バックエンドの設定によっては重複注文につながる可能性があります)、**エッジトリガー** シグナル(条件が真になった瞬間にのみシグナルを出す)を使用するのがベストプラクティスです。
+
+```python
+# 条件: 終値が SMA 20 を上抜け
+condition_buy = (df['close'] > sma_20) & (df['close'].shift(1) <= sma_20.shift(1))
+
+# 条件: 終値が SMA 20 を下抜け
+condition_sell = (df['close'] < sma_20) & (df['close'].shift(1) >= sma_20.shift(1))
+
+# df に代入 (バックテストに必須)
+df['buy'] = condition_buy.fillna(False)
+df['sell'] = condition_sell.fillna(False)
+```
+
+**シグナルタイプに関する注意:**
+* QuantDingerは、戦略設定(ロングのみ、ショートのみ、または両方向)に基づいてシグナルを正規化します。
+* スクリプトは単に "buy"(強気)または "sell"(弱気)を出力するだけです。バックエンドがエントリー/エグジットロジックを処理します。
+
+### 3.3 視覚的マーカー
+チャートに表示するために、通常はシグナルアイコンをローソク足の上または下に配置します。
+
+```python
+# 買いマーカーを安値の 0.5% 下に配置
+buy_marks = [
+ df['low'].iloc[i] * 0.995 if df['buy'].iloc[i] else None
+ for i in range(len(df))
+]
+
+# 売りマーカーを高値の 0.5% 上に配置
+sell_marks = [
+ df['high'].iloc[i] * 1.005 if df['sell'].iloc[i] else None
+ for i in range(len(df))
+]
+```
+
+### 3.4 `output` 変数 (必須)
+最後のステップは、辞書を変数 `output` に代入することです。これにより、フロントエンドに描画内容を伝え、バックエンドにシグナルの場所を伝えます。
+
+**構造:**
+```python
+output = {
+ "name": "私の戦略名",
+ "plots": [ ... ], # 描画するライン/インジケーターのリスト
+ "signals": [ ... ] # シグナルマーカーのリスト
+}
+```
+
+**Plots Schema (描画設定):**
+* `name`: 凡例名 (例: "SMA 20")
+* `data`: 値のリスト (`df` と同じ長さである必要があります)。`.tolist()` を使用して変換します。
+* `color`: 16進数カラー文字列 (例: "#ff0000")。
+* `overlay`: `True` はメインチャート(価格)上に描画、`False` はサブチャート(RSI/MACDなど)に描画します。
+
+**Signals Schema (シグナル設定):**
+* `type`: "buy" または "sell" である必要があります。
+* `text`: アイコンに表示するテキスト (例: "B", "S")。
+* `data`: 値のリスト(価格位置)。シグナルがない場所は `None`。
+* `color`: アイコンの色。
+
+---
+
+## 4. 完全な例: ダブル移動平均線クロスオーバー (Dual SMA)
+
+以下は、SMA(10) が SMA(30) を上抜けたときに買い、下抜けたときに売る、完全でコピー可能な戦略の例です。
+
+```python
+# 1. インジケーター計算
+# -----------------------
+# 短期と長期の SMA を計算
+sma_short = df['close'].rolling(10).mean()
+sma_long = df['close'].rolling(30).mean()
+
+# 2. シグナルロジック
+# -----------------------
+# 買い: 短期 SMA が 長期 SMA を上抜け
+raw_buy = (sma_short > sma_long) & (sma_short.shift(1) <= sma_long.shift(1))
+
+# 売り: 短期 SMA が 長期 SMA を下抜け
+raw_sell = (sma_short < sma_long) & (sma_short.shift(1) >= sma_long.shift(1))
+
+# NaN をクリーンアップし、ブール型を確保
+buy = raw_buy.fillna(False)
+sell = raw_sell.fillna(False)
+
+# df 列に代入 (バックエンド実行の鍵)
+df['buy'] = buy
+df['sell'] = sell
+
+# 3. 視覚フォーマット
+# -----------------------
+# マーカー位置を計算
+buy_marks = [
+ df['low'].iloc[i] * 0.995 if buy.iloc[i] else None
+ for i in range(len(df))
+]
+
+sell_marks = [
+ df['high'].iloc[i] * 1.005 if sell.iloc[i] else None
+ for i in range(len(df))
+]
+
+# 4. 最終出力
+# -----------------------
+output = {
+ 'name': 'Dual SMA Strategy',
+ 'plots': [
+ {
+ 'name': 'SMA 10',
+ 'data': sma_short.fillna(0).tolist(),
+ 'color': '#1890ff',
+ 'overlay': True
+ },
+ {
+ 'name': 'SMA 30',
+ 'data': sma_long.fillna(0).tolist(),
+ 'color': '#faad14',
+ 'overlay': True
+ }
+ ],
+ 'signals': [
+ {
+ 'type': 'buy',
+ 'text': 'B',
+ 'data': buy_marks,
+ 'color': '#00E676'
+ },
+ {
+ 'type': 'sell',
+ 'text': 'S',
+ 'data': sell_marks,
+ 'color': '#FF5252'
+ }
+ ]
+}
+```
+
+## 5. ベストプラクティスとトラブルシューティング
+
+### 5.1 NaN の処理
+ローリング計算(`rolling(14)` など)は、データの先頭に `NaN` 値を生成します。
+* **ルール**: シグナルを生成する前に必ず `NaN` を処理してください。
+* **修正**: コンテキストに応じて `.fillna(0)` または `.fillna(False)` を使用します。
+
+### 5.2 先読みバイアス (Look-ahead Bias)
+システムは、ローソク足の **終値** で発生したシグナルに基づいて取引を実行します。
+* バックテストエンジンは通常、**次のローソク足の始値** で注文を実行します。
+* シグナルロジックは `close`(現在の完了したローソク足)または `shift(1)`(前のローソク足)に依存する必要があります。`shift(-1)` は絶対に使用しないでください。
+
+### 5.3 パフォーマンス
+計算ロジックで DataFrame の行を反復処理すること(`for i in range(len(df)): ...`)は避けてください。非常に遅くなります。
+* **悪い例**: ループを使用して SMA を計算する。
+* **良い例**: `df['close'].rolling(...)` を使用する。
+* **例外**: `buy_marks`/`sell_marks` リストの構築には通常リスト内包表記が必要ですが、これは視覚的出力のみに使用されるため許容されます。
+
+### 5.4 デバッグ
+一部の実行モードでは `print()` 出力を簡単に確認できないため、戦略のロードに失敗した場合はバックエンドのログ(`backend_api_python/logs/app.log`)を確認してください。
+* 一般的なエラー: `KeyError`(列名の間違い)。
+* 一般的なエラー: `ValueError`(配列の長さが不一致)。`plots` のデータ長が `df` と一致していることを確認してください。
+
diff --git a/docs/STRATEGY_DEV_GUIDE_KO.md b/docs/STRATEGY_DEV_GUIDE_KO.md
new file mode 100644
index 0000000..d0ab614
--- /dev/null
+++ b/docs/STRATEGY_DEV_GUIDE_KO.md
@@ -0,0 +1,235 @@
+# QuantDinger Python 전략 개발 가이드
+
+이 가이드는 QuantDinger 플랫폼에서 Python을 사용하여 거래 전략을 개발하는 방법을 자세히 설명합니다. QuantDinger는 데이터 액세스, 지표 계산 및 신호 생성을 지원하는 유연한 실행 환경을 제공합니다.
+
+## 1. 개요
+
+QuantDinger의 전략은 **신호 제공자(Signal Provider)** 모드를 기반으로 작동합니다. 시스템은 귀하의 Python 스크립트를 실행하며, 이 스크립트는 시장 데이터(DataFrame)를 처리하고 거래 신호를 출력합니다.
+
+실행 흐름은 다음과 같습니다:
+1. **입력**: 시스템은 OHLCV 데이터를 포함하는 `df`(Pandas DataFrame)를 스크립트 환경에 주입합니다.
+2. **처리**: Python(`pandas`, `numpy`)을 사용하여 지표를 계산하고 `buy`/`sell` 로직을 정의합니다.
+3. **출력**: 플롯 데이터와 신호를 포함하는 특정 `output` 딕셔너리를 구성합니다.
+
+---
+
+## 2. 환경 및 데이터
+
+스크립트는 샌드박스 처리된 Python 환경에서 실행됩니다.
+
+### 2.1 사전 가져오기된 라이브러리
+다음 라이브러리는 기본적으로 사용할 수 있습니다(`import`할 **필요 없음**):
+* `pd` (pandas)
+* `np` (numpy)
+
+### 2.2 입력 데이터 (`df`)
+`df`라는 이름의 Pandas DataFrame 변수가 전역 범위에 자동으로 존재합니다. 여기에는 선택한 심볼 및 시간대의 과거 시장 데이터가 포함되어 있습니다.
+
+**열 (Columns):**
+* `time`: 타임스탬프 (datetime 또는 int, 컨텍스트에 따라 다름)
+* `open`: 시가 (float)
+* `high`: 고가 (float)
+* `low`: 저가 (float)
+* `close`: 종가 (float)
+* `volume`: 거래량 (float)
+
+**예시:**
+```python
+# 종가 시리즈 가져오기
+closes = df['close']
+
+# 단순 이동 평균(SMA) 계산
+sma_20 = df['close'].rolling(20).mean()
+```
+
+---
+
+## 3. 전략 개발
+
+표준 전략 스크립트는 세 부분으로 구성됩니다:
+1. **지표 계산**: 기술적 지표를 계산합니다.
+2. **신호 생성**: 매수 및 매도 신호 로직을 정의합니다.
+3. **출력 구성**: 차트 표시 및 실행 엔진을 위한 결과를 포맷팅합니다.
+
+### 3.1 지표 계산
+표준 Pandas 연산을 사용하여 지표를 계산할 수 있습니다.
+
+```python
+# 예시: MACD 계산
+short_window = 12
+long_window = 26
+signal_window = 9
+
+ema12 = df['close'].ewm(span=short_window, adjust=False).mean()
+ema26 = df['close'].ewm(span=long_window, adjust=False).mean()
+macd = ema12 - ema26
+signal_line = macd.ewm(span=signal_window, adjust=False).mean()
+```
+
+### 3.2 신호 생성 (중요)
+
+`df` 내에(또는 독립 변수로) `buy`와 `sell`이라는 이름의 두 개의 불리언(Boolean) Series를 **반드시 생성해야 합니다**.
+
+* `True`는 신호 트리거를 나타냅니다.
+* `False`는 신호 없음을 나타냅니다.
+
+**중요: 엣지 트리거(Edge Triggering)**
+연속된 캔들에서 반복적으로 신호가 발생하는 것을 방지하기 위해(백엔드 설정에 따라 중복 주문으로 이어질 수 있음), **엣지 트리거** 신호(조건이 참이 되는 순간에만 신호 발생)를 사용하는 것이 모범 사례입니다.
+
+```python
+# 조건: 종가가 SMA 20 상향 돌파
+condition_buy = (df['close'] > sma_20) & (df['close'].shift(1) <= sma_20.shift(1))
+
+# 조건: 종가가 SMA 20 하향 돌파
+condition_sell = (df['close'] < sma_20) & (df['close'].shift(1) >= sma_20.shift(1))
+
+# df에 할당 (백테스팅에 필수)
+df['buy'] = condition_buy.fillna(False)
+df['sell'] = condition_sell.fillna(False)
+```
+
+**신호 유형에 대한 참고:**
+* QuantDinger는 전략 구성(롱 전용, 숏 전용 또는 양방향)에 따라 신호를 정규화합니다.
+* 스크립트는 단순히 "buy"(강세 의도) 또는 "sell"(약세 의도)을 출력하면 됩니다. 백엔드가 진입/청산 로직을 처리합니다.
+
+### 3.3 시각적 마커
+차트에 표시하기 위해 일반적으로 신호 아이콘을 캔들 위나 아래에 배치합니다.
+
+```python
+# 매수 마커를 저가보다 0.5% 아래에 배치
+buy_marks = [
+ df['low'].iloc[i] * 0.995 if df['buy'].iloc[i] else None
+ for i in range(len(df))
+]
+
+# 매도 마커를 고가보다 0.5% 위에 배치
+sell_marks = [
+ df['high'].iloc[i] * 1.005 if df['sell'].iloc[i] else None
+ for i in range(len(df))
+]
+```
+
+### 3.4 `output` 변수 (필수)
+마지막 단계는 `output` 변수에 딕셔너리를 할당하는 것입니다. 이는 프론트엔드에 무엇을 그릴지, 백엔드에 신호가 어디에 있는지를 알려줍니다.
+
+**구조:**
+```python
+output = {
+ "name": "내 전략 이름",
+ "plots": [ ... ], # 그릴 라인/지표 목록
+ "signals": [ ... ] # 신호 마커 목록
+}
+```
+
+**Plots Schema (플롯 구성):**
+* `name`: 범례 이름 (예: "SMA 20")
+* `data`: 값 목록 (`df` 길이와 일치해야 함). `.tolist()`를 사용하여 변환.
+* `color`: 16진수 색상 문자열 (예: "#ff0000").
+* `overlay`: `True`는 메인 차트(가격) 위에 그리기, `False`는 보조 차트(RSI/MACD 등)에 그리기.
+
+**Signals Schema (신호 구성):**
+* `type`: "buy" 또는 "sell"이어야 합니다.
+* `text`: 아이콘에 표시할 텍스트 (예: "B", "S").
+* `data`: 값 목록 (가격 위치). 신호 없는 곳은 `None`.
+* `color`: 아이콘 색상.
+
+---
+
+## 4. 전체 예제: 이중 이동 평균 교차 (Dual SMA)
+
+다음은 SMA(10)이 SMA(30)을 상향 돌파할 때 매수하고, 하향 돌파할 때 매도하는 전체 복사 가능한 전략 예제입니다.
+
+```python
+# 1. 지표 계산
+# -----------------------
+# 단기 및 장기 SMA 계산
+sma_short = df['close'].rolling(10).mean()
+sma_long = df['close'].rolling(30).mean()
+
+# 2. 신호 로직
+# -----------------------
+# 매수: 단기 SMA가 장기 SMA 상향 돌파
+raw_buy = (sma_short > sma_long) & (sma_short.shift(1) <= sma_long.shift(1))
+
+# 매도: 단기 SMA가 장기 SMA 하향 돌파
+raw_sell = (sma_short < sma_long) & (sma_short.shift(1) >= sma_long.shift(1))
+
+# NaN 정리 및 불리언 타입 보장
+buy = raw_buy.fillna(False)
+sell = raw_sell.fillna(False)
+
+# df 열에 할당 (백엔드 실행의 핵심)
+df['buy'] = buy
+df['sell'] = sell
+
+# 3. 시각적 포맷팅
+# -----------------------
+# 마커 위치 계산
+buy_marks = [
+ df['low'].iloc[i] * 0.995 if buy.iloc[i] else None
+ for i in range(len(df))
+]
+
+sell_marks = [
+ df['high'].iloc[i] * 1.005 if sell.iloc[i] else None
+ for i in range(len(df))
+]
+
+# 4. 최종 출력
+# -----------------------
+output = {
+ 'name': 'Dual SMA Strategy',
+ 'plots': [
+ {
+ 'name': 'SMA 10',
+ 'data': sma_short.fillna(0).tolist(),
+ 'color': '#1890ff',
+ 'overlay': True
+ },
+ {
+ 'name': 'SMA 30',
+ 'data': sma_long.fillna(0).tolist(),
+ 'color': '#faad14',
+ 'overlay': True
+ }
+ ],
+ 'signals': [
+ {
+ 'type': 'buy',
+ 'text': 'B',
+ 'data': buy_marks,
+ 'color': '#00E676'
+ },
+ {
+ 'type': 'sell',
+ 'text': 'S',
+ 'data': sell_marks,
+ 'color': '#FF5252'
+ }
+ ]
+}
+```
+
+## 5. 모범 사례 및 문제 해결
+
+### 5.1 NaN 처리
+롤링 계산(`rolling(14)` 등)은 데이터 시작 부분에 `NaN` 값을 생성합니다.
+* **규칙**: 신호를 생성하기 전에 항상 `NaN`을 처리하십시오.
+* **수정**: 상황에 따라 `.fillna(0)` 또는 `.fillna(False)`를 사용하십시오.
+
+### 5.2 미래 참조 편향 (Look-ahead Bias)
+시스템은 캔들의 **종가**에서 발생한 신호를 기반으로 거래를 실행합니다.
+* 백테스트 엔진은 일반적으로 **다음 캔들의 시가**에서 주문을 실행합니다.
+* 신호 로직은 `close`(현재 완료된 캔들) 또는 `shift(1)`(이전 캔들)에 의존해야 합니다. `shift(-1)`은 절대 사용하지 마십시오.
+
+### 5.3 성능
+계산 로직에서 DataFrame 행을 반복(`for i in range(len(df)): ...`)하는 것을 피하십시오. 매우 느립니다.
+* **나쁨**: 루프를 사용하여 SMA 계산.
+* **좋음**: `df['close'].rolling(...)` 사용.
+* **예외**: `buy_marks`/`sell_marks` 리스트 구성에는 일반적으로 리스트 컴프리헨션이 필요하며, 이는 시각적 출력에만 사용되므로 허용됩니다.
+
+### 5.4 디버깅
+일부 실행 모드에서는 `print()` 출력을 쉽게 볼 수 없으므로, 전략 로드에 실패하면 백엔드 로그(`backend_api_python/logs/app.log`)를 확인하십시오.
+* 일반적인 오류: `KeyError` (잘못된 열 이름).
+* 일반적인 오류: `ValueError` (배열 길이 불일치). `plots`의 데이터 길이가 `df`와 일치하는지 확인하십시오.
+
diff --git a/docs/STRATEGY_DEV_GUIDE_TW.md b/docs/STRATEGY_DEV_GUIDE_TW.md
new file mode 100644
index 0000000..6669763
--- /dev/null
+++ b/docs/STRATEGY_DEV_GUIDE_TW.md
@@ -0,0 +1,235 @@
+# QuantDinger Python 策略開發指南
+
+本指南將詳細介紹如何在 QuantDinger 平台中使用 Python 開發交易策略。QuantDinger 提供了靈活的執行環境,支持數據訪問、指標計算和信號生成。
+
+## 1. 概覽
+
+QuantDinger 中的策略基於 **信號提供者 (Signal Provider)** 模式運行。系統執行你的 Python 腳本,該腳本負責處理市場數據(DataFrame)並輸出交易信號。
+
+執行流程如下:
+1. **輸入**:系統將包含 OHLCV 數據的 `df` (Pandas DataFrame) 注入到你的腳本環境中。
+2. **處理**:你使用 Python (`pandas`, `numpy`) 計算指標並定義 `buy`/`sell` 邏輯。
+3. **輸出**:你構造一個特定的 `output` 字典,包含繪圖數據和信號。
+
+---
+
+## 2. 環境與數據
+
+你的腳本運行在一個沙盒化的 Python 環境中。
+
+### 2.1 預導入庫
+以下庫默認可用(**無需** `import`):
+* `pd` (pandas)
+* `np` (numpy)
+
+### 2.2 輸入數據 (`df`)
+一個名為 `df` 的 Pandas DataFrame 變量會自動存在於全局作用域中。它包含所選代碼和時間週期的歷史市場數據。
+
+**列 (Columns):**
+* `time`: 時間戳 (datetime 或 int,視上下文而定)
+* `open`: 開盤價 (float)
+* `high`: 最高價 (float)
+* `low`: 最低價 (float)
+* `close`: 收盤價 (float)
+* `volume`: 成交量 (float)
+
+**示例:**
+```python
+# 獲取收盤價序列
+closes = df['close']
+
+# 計算簡單移動平均線 (SMA)
+sma_20 = df['close'].rolling(20).mean()
+```
+
+---
+
+## 3. 開發策略
+
+一個標準的策略腳本包含三個部分:
+1. **指標計算**:計算技術指標。
+2. **信號生成**:定義買入和賣出信號邏輯。
+3. **輸出構建**:格式化結果以供圖表展示和執行引擎使用。
+
+### 3.1 指標計算
+你可以使用標準的 Pandas 操作來計算指標。
+
+```python
+# 示例:MACD 計算
+short_window = 12
+long_window = 26
+signal_window = 9
+
+ema12 = df['close'].ewm(span=short_window, adjust=False).mean()
+ema26 = df['close'].ewm(span=long_window, adjust=False).mean()
+macd = ema12 - ema26
+signal_line = macd.ewm(span=signal_window, adjust=False).mean()
+```
+
+### 3.2 信號生成 (關鍵)
+
+你 **必須** 在 `df` 中創建(或作為獨立變量)兩個布爾類型的 Series,分別命名為 `buy` 和 `sell`。
+
+* `True` 表示觸發信號。
+* `False` 表示無信號。
+
+**重要:邊緣觸發 (Edge Triggering)**
+為了避免在連續的 K 線上重複發出信號(這可能導致重複下單,取決於後端配置),最佳實踐是使用 **邊緣觸發** 信號(即只在條件變真的那一刻發出信號)。
+
+```python
+# 條件:收盤價上穿 SMA 20
+condition_buy = (df['close'] > sma_20) & (df['close'].shift(1) <= sma_20.shift(1))
+
+# 條件:收盤價下穿 SMA 20
+condition_sell = (df['close'] < sma_20) & (df['close'].shift(1) >= sma_20.shift(1))
+
+# 賦值給 df (回測必需)
+df['buy'] = condition_buy.fillna(False)
+df['sell'] = condition_sell.fillna(False)
+```
+
+**關於信號類型的說明:**
+* QuantDinger 會根據你的策略配置(僅做多、僅做空或雙向)來標準化信號。
+* 你的腳本只需輸出 "buy"(看漲意圖)或 "sell"(看跌意圖)。後端會處理開倉/平倉邏輯。
+
+### 3.3 可視化標記
+為了在圖表上展示,你通常希望將信號圖標放在 K 線的上方或下方。
+
+```python
+# 將買入標記放在最低價下方 0.5% 處
+buy_marks = [
+ df['low'].iloc[i] * 0.995 if df['buy'].iloc[i] else None
+ for i in range(len(df))
+]
+
+# 將賣出標記放在最高價上方 0.5% 處
+sell_marks = [
+ df['high'].iloc[i] * 1.005 if df['sell'].iloc[i] else None
+ for i in range(len(df))
+]
+```
+
+### 3.4 `output` 變量 (必須)
+最後一步是將一個字典賦值給變量 `output`。這告訴前端如何繪圖,以及告訴後端信號在哪裡。
+
+**結構:**
+```python
+output = {
+ "name": "我的策略名稱",
+ "plots": [ ... ], # 要繪製的線條/指標列表
+ "signals": [ ... ] # 信號標記列表
+}
+```
+
+**Plots Schema (繪圖配置):**
+* `name`: 圖例名稱 (例如 "SMA 20")
+* `data`: 數值列表 (必須與 `df` 長度一致)。使用 `.tolist()` 轉換。
+* `color`: 十六進制顏色字符串 (例如 "#ff0000")。
+* `overlay`: `True` 表示繪製在主圖(價格圖)上,`False` 表示繪製在副圖(如 RSI/MACD)。
+
+**Signals Schema (信號配置):**
+* `type`: 必須是 "buy" 或 "sell"。
+* `text`: 圖標上顯示的文本 (例如 "B", "S")。
+* `data`: 數值列表 (價格位置)。無信號處為 `None`。
+* `color`: 圖標顏色。
+
+---
+
+## 4. 完整示例:雙均線交叉 (Dual SMA)
+
+以下是一個完整的、可複製的雙均線策略示例:當 SMA(10) 上穿 SMA(30) 時買入,下穿時賣出。
+
+```python
+# 1. 指標計算
+# -----------------------
+# 計算短期和長期 SMA
+sma_short = df['close'].rolling(10).mean()
+sma_long = df['close'].rolling(30).mean()
+
+# 2. 信號邏輯
+# -----------------------
+# 買入:短期 SMA 上穿 長期 SMA
+raw_buy = (sma_short > sma_long) & (sma_short.shift(1) <= sma_long.shift(1))
+
+# 賣出:短期 SMA 下穿 長期 SMA
+raw_sell = (sma_short < sma_long) & (sma_short.shift(1) >= sma_long.shift(1))
+
+# 清理 NaN 並確保布爾類型
+buy = raw_buy.fillna(False)
+sell = raw_sell.fillna(False)
+
+# 賦值給 df 列 (後端執行的關鍵)
+df['buy'] = buy
+df['sell'] = sell
+
+# 3. 可視化格式化
+# -----------------------
+# 計算標記位置
+buy_marks = [
+ df['low'].iloc[i] * 0.995 if buy.iloc[i] else None
+ for i in range(len(df))
+]
+
+sell_marks = [
+ df['high'].iloc[i] * 1.005 if sell.iloc[i] else None
+ for i in range(len(df))
+]
+
+# 4. 最終輸出
+# -----------------------
+output = {
+ 'name': '雙均線策略',
+ 'plots': [
+ {
+ 'name': 'SMA 10',
+ 'data': sma_short.fillna(0).tolist(),
+ 'color': '#1890ff',
+ 'overlay': True
+ },
+ {
+ 'name': 'SMA 30',
+ 'data': sma_long.fillna(0).tolist(),
+ 'color': '#faad14',
+ 'overlay': True
+ }
+ ],
+ 'signals': [
+ {
+ 'type': 'buy',
+ 'text': '買',
+ 'data': buy_marks,
+ 'color': '#00E676'
+ },
+ {
+ 'type': 'sell',
+ 'text': '賣',
+ 'data': sell_marks,
+ 'color': '#FF5252'
+ }
+ ]
+}
+```
+
+## 5. 最佳實踐與故障排除
+
+### 5.1 處理 NaN
+滾動計算(如 `rolling(14)`)會在數據開頭產生 `NaN` 值。
+* **規則**:生成信號前必須處理 `NaN`。
+* **修復**:使用 `.fillna(0)` 或 `.fillna(False)`。
+
+### 5.2 未來函數 (Look-ahead Bias)
+系統基於 K 線 **收盤** 時產生的信號執行交易。
+* 回測引擎通常在 **下一根 K 線的開盤價** 執行訂單。
+* 你的信號邏輯應依賴 `close` (當前已完成的 K 線) 或 `shift(1)` (前一根 K 線)。切勿使用 `shift(-1)`。
+
+### 5.3 性能
+避免在計算邏輯中遍歷 DataFrame 行 (`for i in range(len(df)): ...`)。這非常慢。
+* **錯誤**:使用循環計算 SMA。
+* **正確**:使用 `df['close'].rolling(...)`。
+* **例外**:構建 `buy_marks`/`sell_marks` 列表通常需要列表推導式,這是可接受的(僅用於可視化輸出)。
+
+### 5.4 調試
+由於在某些執行模式下無法輕易看到 `print()` 輸出,如果策略加載失敗,請檢查後端日誌 (`backend_api_python/logs/app.log`)。
+* 常見錯誤:`KeyError` (列名錯誤)。
+* 常見錯誤:`ValueError` (數組長度不一致)。確保 `plots` 中的數據長度與 `df` 一致。
+
diff --git a/quantdinger_vue/src/views/indicator-analysis/components/IndicatorEditor.vue b/quantdinger_vue/src/views/indicator-analysis/components/IndicatorEditor.vue
index a06478b..4f363a3 100644
--- a/quantdinger_vue/src/views/indicator-analysis/components/IndicatorEditor.vue
+++ b/quantdinger_vue/src/views/indicator-analysis/components/IndicatorEditor.vue
@@ -390,7 +390,7 @@ export default {
// 跳转到文档中心
goToDocs () {
- window.open('https://www.quantdinger.com/development_guide.html', '_blank')
+ window.open('https://github.com/brokermr810/QuantDinger/blob/main/docs/STRATEGY_DEV_GUIDE.md', '_blank')
},
// 清理代码中的 markdown 代码块标记