在前面的章节中,我们已经熟悉了看海量化(KHQuant)的界面操作和核心功能。现在,是时候进入最激动人心的部分了——亲手编写一个属于自己的量化交易策略。
本章将作为一份详尽的实战教程,以经典的“双均线交叉”策略为蓝本,从最基础的概念开始,一步步引导您完成从策略思想到代码实现的全过程。您将学到:
- 如何将一个交易想法转化为具体的策略逻辑。
- 如何利用KHQuant框架和工具函数编写一个完整的单股票策略。
- 策略编写中的关键注意事项和“避坑”指南。
- 如何将单股票策略“升级”为能够同时处理多只股票的股票池策略。
- 单/多股票策略在回测设置上的核心区别。
即使您是编程新手,也不必担心。本章将以最直观易懂的方式进行讲解,结合我们之前在第十三章介绍的便捷函数,您会发现编写一个量化策略比想象中要简单得多。
14.1 策略思想:双均线交叉
在开始编写代码之前,我们首先要明确策略的“思想”或“逻辑”。这是所有量化策略的起点。
双均线交叉策略是技术分析中最广为人知的策略之一,它的核心思想是利用两条不同周期的移动平均线(Moving Average, MA)来判断市场趋势的转换,并产生交易信号。
- 短期均线 (Short MA):通常选择较短的周期,如5日、10日。它对价格变动更敏感,能快速反映近期的价格趋势。
- 长期均线 (Long MA):通常选择较长的周期,如20日、60日。它对价格变动较为平滑,能反映长期的市场趋势。
交易规则:
- 买入信号(金叉):当短期均线从下方上穿长期均线时,被视为一个看涨信号,表明短期趋势开始强于长期趋势,可能预示着一轮上涨行情的开始。此时,我们执行买入操作。
- 卖出信号(死叉):当短期均线从上方下穿长期均线时,被视为一个看跌信号,表明短期趋势开始弱于长期趋势,可能预示着一轮下跌行情的开始。此时,我们执行卖出操作。
仓位管理规则:
- 买入:当出现金叉信号且我们当前没有持仓时,全仓买入。
- 卖出:当出现死叉信号且我们当前持有仓位时,全仓卖出。
💡 思考与简化:
这个策略非常纯粹,它只关心一件事:两条均线的相对位置。它不考虑成交量、基本面或其他任何指标。这种简单性使其成为学习策略编写的绝佳范例。
14.2 运行前的准备:回测参数设置
在深入代码细节之前,让我们先在看海量化的主界面上,为即将运行的 双均线精简.py
策略配置好正确的“土壤”。一个正确的配置是策略能否按预期运行的前提。
对于我们的日线级别双均线策略,推荐的核心配置如下:
- 选择策略文件:
- 在主界面的“策略文件”下拉菜单中,找到并选择
strategies/双均线精简.py
。
- 设置回测时段:
- 选择一个您感兴趣的回测开始和结束日期,例如
2025-01-01
到2025-07-03
,以观察策略在过去一整年的表现。
- 配置数据与触发方式 (核心):
- 数据设置:
- 周期: 选择
1d
。因为我们的策略是基于“日”均线,所以必须使用日线级别的数据作为计算基础。
- 周期: 选择
- 触发方式:
- 类型: 选择
日K线触发
。这个设置确保了我们的策略主逻辑函数khHandlebar
每天只在开盘时被调用一次。这完美匹配了我们日线级别的交易频率。
- 类型: 选择
- 设置股票池:
- 因为
双均线精简.py
是一个单股票策略,所以我们只需要在“股票池”区域添加一只股票作为回测标的即可。 - 点击“添加股票”按钮,输入您想回测的股票代码,例如
000001.SZ
(平安银行)。
⚠️ 重要提示1:
数据周期和触发方式的匹配至关重要。对于日线策略,使用1d
数据和日K线触发
是最合理、最高效的组合。如果错误地使用了tick
数据或tick触发
,会导致策略在每个tick都进行一次不必要的日线计算,极大地拖慢回测速度且没有意义。⚠️ 重要提示2:
在运行策略前,务!必!确!认!所选股票在指定的时间段内成功补充了相应周期的数据。
完成以上设置后,我们就有了一个随时可以运行回测的正确环境。现在,让我们带着这些设置,去深入理解策略代码是如何实现我们的交易思想的。
14.3 单股票策略实现 (以 双均线精简.py
为例)
现在,我们将上述思想翻译成代码。我们将以 双均线精简.py
文件为例,逐行进行讲解。
# coding: utf-8
# 1. 导入工具包
from khQuantImport import *
# 2. 初始化函数 (本次策略未使用)
def init(stocks=None, data=None):
"""策略初始化"""
pass
# 3. 策略主逻辑函数
def khHandlebar(data: Dict) -> List[Dict]:
"""策略主逻辑,在每个K线或Tick数据到来时执行"""
# 4. 初始化信号列表
signals = []
# 5. 获取数据
stock_code = khGet(data, "first_stock")
current_price = khPrice(data, stock_code, "open")
current_date_num = khGet(data, "date_num")
# 6. 计算指标
ma_short = MA(stock_code, 5, end_time=current_date_num)
ma_long = MA(stock_code, 20, end_time=current_date_num)
# (可选) 打印日志用于调试
logging.info(f"计算结果 - 短期均线: {ma_short:.2f}, 长期均线: {ma_long:.2f}")
# 7. 获取持仓状态
has_position = khHas(data, stock_code)
# 8. 编写交易逻辑
if ma_short > ma_long and not has_position:
# 9. 生成买入信号
signals = generate_signal(data, stock_code, current_price, 1.0, 'buy',
f"5日线({ma_short:.2f})上穿20日线({ma_long:.2f})")
elif ma_short < ma_long and has_position:
# 10. 生成卖出信号
signals = generate_signal(data, stock_code, current_price, 1.0, 'sell',
f"5日线({ma_short:.2f})下穿20日线({ma_long:.2f})")
# 11. 返回信号
return signals
代码分步详解
- 导入工具包:
from khQuantImport import *
- 这是编写所有策略的“魔法咒语”。它将第十三章中介绍的所有便捷函数(如
khGet
,khPrice
,MA
,generate_signal
等)全部导入,让我们可以直接使用。
- 初始化函数
init
:
- 这个函数在整个回测任务开始时只执行一次。对于一些复杂的策略,可以在这里定义全局变量、加载外部数据等。但对于我们这个简单的双均线策略,不需要任何初始化操作,所以函数体只有一行
pass
,表示“什么都不做”。
- 策略主逻辑函数
khHandlebar
:
- 这是策略的“心脏”。框架会根据你在主界面设置的触发方式(如“日K线触发”),在每个交易日开盘时调用一次这个函数。所有的判断和交易逻辑都在这里完成。
- 它接收一个名为
data
的字典作为参数,这个字典就是我们之前提过的context
,包含了当前时间点所有需要的信息。 - 它必须返回一个列表(
List[Dict]
),这个列表里装着交易信号。
- 初始化信号列表:
signals = []
- 我们首先创建一个空的列表,用来存放本轮可能产生的交易信号。这是一个好习惯。
- 获取数据:
stock_code = khGet(data, "first_stock")
: 对于单股票策略,我们通常只关心股票池里的第一只(也是唯一一只)股票。khGet
函数可以轻松地帮我们拿到它的代码。current_price = khPrice(data, stock_code, "open")
: 我们获取该股票当天的开盘价,并将其作为我们买卖的参考价格。current_date_num = khGet(data, "date_num")
: 我们获取"YYYYMMDD"
格式的当前日期,这是为了在下一步计算历史均线时,告诉函数数据应该截止到哪一天,以避免使用未来数据。
- 计算指标:
ma_short = MA(stock_code, 5, end_time=current_date_num)
: 调用MA
函数计算5日短期均线。ma_long = MA(stock_code, 20, end_time=current_date_num)
: 计算20日长期均线。- ⚠️ 注意:这里传入
end_time
参数至关重要!它确保了在计算均线时,只使用截止到current_date_num
之前的数据,完美地避免了未来函数问题,保证了回测的公平性。
- 获取持仓状态:
has_position = khHas(data, stock_code)
- 使用
khHas
函数判断我们当前是否持有这只股票的仓位。
- 编写交易逻辑:
if ma_short > ma_long and not has_position:
: 这是金叉买入的判断条件。ma_short > ma_long
表示短期均线在长期均线上方(即金叉状态),not has_position
表示我们当前没有持仓。两个条件同时满足,才会触发买入。elif ma_short < ma_long and has_position:
: 这是死叉卖出的判断条件。ma_short < ma_long
表示死叉状态,has_position
表示我们当前持有仓位。
- 生成买入信号:
- 我们调用
generate_signal
函数。 data
: 传入完整的context
。stock_code
,current_price
: 传入股票代码和计划交易的价格。1.0
: 资金使用比例。1.0
表示使用全部可用资金。函数会自动计算能买多少股。'buy'
: 交易方向。f"..."
: 交易原因,使用f-string可以动态地将当前的均线值记录下来,方便复盘。
- 生成卖出信号:
- 同样调用
generate_signal
函数。 1.0
: 持仓卖出比例。1.0
表示卖出全部持仓。'sell'
: 交易方向。
- 同样调用
- 返回信号:
return signals
- 将包含交易信号的列表返回给框架。框架收到后会自动执行下单。如果整个过程没有满足任何条件,返回的就是一个空列表,表示本轮无操作。
14.4 多股票策略进阶 (以 双均线多股票.py
为例)
学会了单股票策略后,将其扩展为能同时处理一个股票池的多股票策略,其实非常简单。核心思想就是将原有的逻辑放入一个循环中,对股票池里的每一只股票都独立地执行一遍。
在动手修改代码之前,请先在主界面上做好准备:
- 选择策略文件:将策略文件切换为
双均线多股票.py
。 - 修改股票池:这是关键一步。您需要清空之前只包含单只股票的股票池,然后通过“添加股票”或“导入股票池”功能,加入多只股票,例如导入“沪深300成分股”列表。
做完以上准备后,让我们看看 双均线多股票.py
是如何实现的。
# coding: utf-8
from khQuantImport import *
def init(stocks=None, data=None):
"""策略初始化"""
pass
def khHandlebar(data: Dict) -> List[Dict]:
"""策略主逻辑,支持多只股票的双均线策略"""
signals = []
# 1. 获取整个股票池
stock_list = khGet(data, "stocks")
current_date_num = khGet(data, "date_num")
# 2. 遍历股票池
for stock_code in stock_list:
# --- 循环内部的逻辑与单股票策略几乎完全相同 ---
current_price = khPrice(data, stock_code, "open")
ma_short = MA(stock_code, 5, end_time=current_date_num)
ma_long = MA(stock_code, 20, end_time=current_date_num)
has_position = khHas(data, stock_code)
if ma_short > ma_long and not has_position:
# 3. 调整仓位管理
buy_signal = generate_signal(data, stock_code, current_price, 0.5, 'buy',
f"{stock_code[:6]} 金叉买入")
# 4. 使用 extend 添加信号
signals.extend(buy_signal)
elif ma_short < ma_long and has_position:
sell_signal = generate_signal(data, stock_code, current_price, 1, 'sell',
f"{stock_code[:6]} 死叉卖出")
signals.extend(sell_signal)
return signals
与单股票策略的核心区别
- 获取整个股票池:
- 不再使用
khGet(data, "first_stock")
,而是stock_list = khGet(data, "stocks")
,一次性获取你在主界面股票池中添加的所有股票代码。
- 遍历股票池:
- 使用
for stock_code in stock_list:
循环,对列表中的每一只股票重复执行后续的逻辑。
- 调整仓位管理:
- 在单股票策略中,我们可以全仓买入(
ratio=1.0
)。但在多股票策略中,如果对第一只满足条件的股票就全仓买入,后面即使有更好的机会也没有资金了。 - 因此,我们将买入的资金比例调整为一个较小的值,例如
0.5
(即50%)。这意味着每只股票最多使用当前可用资金的50%来建仓,这样可以将资金分散投资到多只股票上,降低单一个股的风险。
- 使用
extend
添加信号:
generate_signal
返回的是一个列表。在循环中,我们需要将每次新生成的信号列表追加到总的signals
列表中。这里使用signals.extend(buy_signal)
而不是signals.append(buy_signal)
。- 区别:
append
会将整个列表作为一个元素添加进去,形成[[signal1], [signal2]]
这样的嵌套结构,这是错误的。而extend
会将新列表中的元素逐个取出,添加到现有列表中,形成[signal1, signal2]
这样的扁平结构,这才是框架需要的格式。
当然了,使用多股票的策略是可以兼容单支股票的。
运行上述两个策略,得到的结果如下:
14.5 总结与展望
恭喜你!通过本章的学习,你已经掌握了:
- ✅ 将一个交易思路(双均线交叉)转化为代码的全过程。
- ✅ 编写一个简洁且功能完备的单股票策略。
- ✅ 将单股票策略轻松扩展为支持股票池的多股票策略。
- ✅ 理解了两种策略在回测设置上的根本区别。
这只是你量化交易之旅的开始。基于本章所学的知识,你可以尝试进行各种扩展:
- 更换指标:将
MA
换成EMA
,BOLL
,MACD
等其他你熟悉的技术指标。 - 优化参数:尝试不同的均线周期组合(如10日 vs 30日),看看回测效果有何不同。
- 改进仓位管理:尝试更复杂的仓位管理模型,例如根据市场波动率动态调整仓位大小。
- 增加过滤条件:在买入前增加其他过滤条件,例如要求成交量放大,或者要求股票处于上涨趋势等。
现在,打开看海量化交易平台,开始构建属于你自己的第一个策略吧!