# 手把手教程：编写你的第一个量化策略

本教程将带你一步步编写一个经典的**双均线策略 (Dual Moving Average)**。这通常是量化交易员的"Hello World"。

我们将涵盖从数据准备、策略编写到回测分析的全过程。

## 1. 策略思路

双均线策略的逻辑非常直观：

- **金叉 (Golden Cross)**: 当短期均线（如 10日线）由下向上穿过长期均线（如 30日线）时，认为趋势向上，**买入**。
- **死叉 (Death Cross)**: 当短期均线由上向下穿过长期均线时，认为趋势向下，**卖出**。

## 2. 完整代码

为了方便你直接运行，我们将所有代码放在一个文件中。你可以复制以下代码到 `my_first_strategy.py` 并运行。

```python
import pandas as pd
import numpy as np
from akquant import Strategy, run_backtest

# --------------------------
# 第一步：准备数据
# --------------------------
# 在真实场景中，你会使用 pd.read_csv("stock_data.csv")
# 这里为了演示方便，我们生成一些模拟数据
def generate_mock_data():
    dates = pd.date_range(start="2023-01-01", end="2023-12-31")
    n = len(dates)

    # 生成一条随机走势
    np.random.seed(42) # 固定随机种子，保证每次运行结果一致
    returns = np.random.normal(0.0005, 0.02, n)
    price = 100 * np.cumprod(1 + returns)

    # 构造 DataFrame
    df = pd.DataFrame({
        "date": dates,
        "open": price,
        "high": price * 1.01,
        "low": price * 0.99,
        "close": price,
        "volume": 10000,
        "symbol": "AAPL" # 假设这是苹果公司的股票
    })
    return df

# --------------------------
# 第二步：编写策略
# --------------------------
class DualMAStrategy(Strategy):
    """
    继承 akquant.Strategy 类，这是所有策略的基类。
    """

    # 新增 (推荐): 声明式设置历史数据预热期
    # 这样框架会自动为你处理 set_history_depth，无需在 on_start 中手动调用
    # 优先级: 动态属性 (self.warmup_period) > 类属性 (warmup_period) > AST 自动推断
    warmup_period = 40 # 30日均线 + 10 安全余量

    def __init__(self, fast_window=10, slow_window=30):
        # 定义策略参数：快线周期和慢线周期
        self.fast_window = fast_window
        self.slow_window = slow_window

        # 进阶技巧：动态设置预热期
        # 如果均线周期是动态传入的，建议在这里根据参数覆盖 warmup_period
        self.warmup_period = slow_window + 10

    def on_start(self):
        """
        策略启动时执行一次。
        在这里告诉系统我们要关注哪些股票。
        """
        print("策略启动...")
        self.subscribe("AAPL")

        # 方式 1 (推荐)：使用 warmup_period (支持 类属性 / 实例属性 / AST自动推断)
        # 方式 2 (旧版)：手动调用 self.set_history_depth(self.slow_window + 10)

        # 由于我们已经配置了 warmup_period，这里无需做任何操作
        # 框架会自动取 max(warmup_period, ast_inferred_value, run_backtest_history_depth)

    def on_bar(self, bar):
        """
        核心逻辑：每一根 K 线走完时，都会触发一次这个函数。
        bar 参数包含了当前的行情数据 (bar.close, bar.high 等)。
        """

        # 1. 获取历史收盘价
        # get_history 返回的是一个 numpy 数组，包含最近 N 天的数据
        closes = self.get_history(count=self.slow_window, symbol=bar.symbol, field="close")

        # 如果数据还不够计算长均线（比如刚开始回测的前几天），就直接返回，不操作
        if len(closes) < self.slow_window:
            return

        # 2. 计算均线
        # 使用 numpy 计算平均值
        fast_ma = np.mean(closes[-self.fast_window:]) # 取最后 fast_window 个数据求平均
        slow_ma = np.mean(closes[-self.slow_window:]) # 取最后 slow_window 个数据求平均

        # 3. 获取当前持仓
        # 如果没持仓返回 0，持有 1000 股返回 1000
        position = self.get_position(bar.symbol)

        # 4. 交易信号判断
        # 金叉：短线 > 长线，且当前空仓 -> 买入
        if fast_ma > slow_ma and position == 0:
            print(f"[{bar.timestamp_str}] 金叉买入! 价格: {bar.close:.2f}")
            self.buy(symbol=bar.symbol, quantity=1000)

        # 死叉：短线 < 长线，且当前持仓 -> 卖出
        elif fast_ma < slow_ma and position > 0:
            print(f"[{bar.timestamp_str}] 死叉卖出! 价格: {bar.close:.2f}")
            self.sell(symbol=bar.symbol, quantity=position) # 卖出所有持仓

# --------------------------
# 第三步：运行回测
# --------------------------
if __name__ == "__main__":
    # 1. 获取数据
    df = generate_mock_data()

    # 2. 运行回测
    print("开始回测...")
    result = run_backtest(
        data=df,
        strategy=DualMAStrategy, # 传入我们的策略类
        strategy_params={"fast_window": 10, "slow_window": 30}, # 调整参数
        cash=100_000.0,    # 初始资金 10万
        commission=0.0003  # 佣金万分之三
    )

    # 3. 打印结果
    print("\n" + "="*30)
    print(result) # 打印详细的绩效报表
    print("="*30)
```

## 3. 代码详细解析

### 3.1 策略结构
每一个 AKQuant 策略都是一个 Python 类，继承自 `Strategy`。你需要关注三个主要方法：

*   **`__init__`**: 设置策略的参数（如均线周期）。
*   **`on_start`**: 初始化工作。
    *   **数据预热 (新版推荐)**：设置 `warmup_period`。
        *   **静态设置**: 类属性 `warmup_period = 40`。
        *   **动态设置**: 在 `__init__` 中 `self.warmup_period = slow_window + 10`。
        *   **自动推断**: 如果代码中有 `SMA(30)`，框架也会尝试自动推断 (AST)。
    *   **数据预热 (旧版兼容)**：调用 `self.set_history_depth(40)`。如果不设置，`get_history` 会报错。
*   **`on_bar`**: 这是一个死循环，系统会按时间顺序把每一根 K 线传给你。你在这里做决策：买还是卖？

### 3.2 获取数据
*   `bar.close`: 当前这根 K 线的收盘价。
*   `self.get_history(N, symbol, "close")`: 获取过去 N 天的收盘价数组。这是最高效的数据访问方式。

### 3.3 下单交易
*   `self.buy(symbol, quantity)`: 发出买单。默认是市价单（按当前价格成交）。
*   `self.sell(symbol, quantity)`: 发出卖单。

## 4. 常见问题 (FAQ)

**Q: 为什么程序报错 `RuntimeError: History tracking is not enabled`？**
A: 这是因为你调用了 `get_history` 但未设置历史数据长度。请确保在策略类中设置了 `warmup_period` 属性（推荐），或者在 `on_start` 中调用 `self.set_history_depth()`。系统默认为了性能不开启历史缓存，必须显式开启。

**Q: 为什么 `get_history` 返回了很多 `NaN`？**
A: 这通常发生在回测刚开始阶段。比如你设置了 depth=30，但在第 5 天就调用了 `get_history(30)`。此时数据不足，系统会自动在前面填充 `NaN` 以保证返回数组长度一致。建议在 `on_bar` 开头判断 `if len(closes) < N: return`。

**Q: 回测结果里的 `sharpe_ratio` 是什么？**
A: 夏普比率，衡量策略性价比的指标。大于 1 通常被认为是还可以的策略，大于 2 是非常优秀的策略。

**Q: 如何换成我自己的数据？**
A: 只要将 `data=df` 替换为你自己的 DataFrame 即可。确保你的 DataFrame 包含 `date`, `open`, `high`, `low`, `close`, `volume`, `symbol` 这几列。

## 5. 进阶：参数优化

在实际开发中，我们往往需要寻找策略的最优参数（例如：均线周期到底选 10/30 好，还是 5/20 好？）。AKQuant 提供了内置的网格搜索工具。

```python
from akquant import run_optimization

# 定义参数网格
param_grid = {
    "fast_window": range(5, 30, 5),   # [5, 10, 15, 20, 25]
    "slow_window": range(20, 60, 10), # [20, 30, 40, 50]
}

# 运行优化
results_df = run_optimization(
    strategy=DualMAStrategy,
    param_grid=param_grid,
    data=df,
    cash=100_000.0,
    commission=0.0003,
    sort_by="total_return", # 按收益率排序
    ascending=False
)

# 打印前 5 名
print(results_df.head(5))
```

`run_optimization` 会自动利用多核 CPU 并行加速回测，并返回一个包含所有参数组合及对应绩效指标的 DataFrame。
