ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AIQT框架手把手实现海龟策略:从原理到回测调优

AIQT框架手把手实现海龟策略:从原理到回测调优 1. 先聊聊AIQT和海龟策略这对组合量化交易圈子里海龟策略算得上是最经典的入门级趋势跟踪策略而AIQT则是一个面向国内市场的开源量化交易框架两者搭配起来做回测和实盘是我个人比较推荐的一条学习路径。很多新手一开始接触量化要么被复杂的金融工程公式吓退要么被各种收费软件劝退而AIQT这类开源框架恰好把门槛降低了不少你只需要会一点Python基础能理解K线数据的基本概念就可以用几百行代码把海龟策略从零写出来跑完回测看到收益曲线在眼前展开。先说清楚这篇文章能解决什么问题。如果你正处于“听过海龟策略但不知道怎么用代码实现”“装了AIQT却不知道该从哪个模块入手”的状态那这篇文章就是给你的。我会从策略原理讲起再到AIQT的框架结构然后完整写出代码实现最后分享回测参数调整和几个我踩过的坑。整个过程不需要你有多深的量化背景但需要你愿意动手敲代码、看数据、调参数。海龟策略本身并不是什么新奇玩意它诞生于上世纪八十年代由Richard Dennis和William Eckhardt设计的一套交易系统核心思想是市场出现趋势时果断追进用波动率决定仓位大小用严格止损控制风险。放在今天的AIQT框架里这套逻辑依然生命力旺盛因为趋势跟踪的本质没有变变化的只是执行工具。用AIQT来做这件事最大的好处是它的数据接口和回测引擎不依赖商业平台代码写完了就跑跑完了就能看到资金曲线和交易明细整个过程全透明、可复现。对我来说学习量化交易最有效的路径从来不是先啃完一本教材而是选一个策略找一个框架把代码跑通再从结果中理解每一个参数的意义。这篇文章就是沿着这条路展开的。2. 海龟策略背后的交易逻辑2.1 入场信号唐奇安通道突破海龟策略最核心的入场信号是唐奇安通道突破简单说就是当价格突破过去20个交易日的最高价时开仓做多当价格跌破过去20个交易日的最低价时开仓做空。这个规则朴素得让人怀疑它是否有效但正是这种“让利润奔跑”的逻辑让它在几十年后依然能捕捉到不少大趋势。为什么用20这个数字原版策略中海龟们使用的入场通道是20日突破离场通道是10日反向突破。20日实际上是一个经过历史回测验证的折中值太短比如5日入场信号会非常频繁手续费和假突破会让你疲于奔命太长比如60日信号又太少可能一年到头都等不到几次入场机会。如果你把这个参数放到AIQT里跑几组对比数据就能明显感受到不同周期对交易频率和收益率的影响。通道突破的实现方法并不复杂。唐奇安通道由三条线组成上轨是过去N日最高价下轨是过去N日最低价中轨通常是上轨和下轨的均值。价格突破上轨时说明多头力量暂时占据主导顺势跟进价格跌破下轨时说明空头力量在起作用反向操作。这种机制的本质是动量效应——价格一旦突破一个阶段的高点历史上看往往具备一定的持续性。不过需要注意的是通道突破有一个明显的短板在震荡行情中价格会反复触碰上下轨导致频繁交易、连续小亏。这也是趋势跟踪策略的通病海龟们并不试图规避这一点而是通过仓位控制和严格的止损来保证弱势行情里亏得少强势行情里赚得够。理解了这一点你再看策略代码时就不会对连续几次的亏损单感到恐慌。2.2 仓位管理ATR与N值的艺术海龟策略最被低估的部分其实是它的仓位管理而不是入场信号。逻辑其实不复杂用ATR指标计算出市场当前的波动幅度再根据这个波动幅度决定每次开仓的手数大小。波动大的时候少买波动小的时候多买这样无论市场怎么折腾每一笔交易承受的风险大致是等量的。ATR指标的计算公式是这样的先算出真实波幅TR取当日最高价减去最低价、当日最高价减去前日收盘价的绝对值、当日最低价减去前日收盘价的绝对值中的最大值再对TR取20日的指数移动平均就得到N值也就是ATR(20)。TR max(high - low, abs(high - prev_close), abs(low - prev_close)) N TR的20日指数移动平均这个N值直接决定了头寸大小。在海龟原版体系中头寸单位是这样计算的单位数量 (账户总资金 * 1%) / (N值 * 合约每点价值)举个例子假设你的账户资金是100万N值为0.5也就是说当前每波动1个百分点对应一个标准合约价值变动0.5万元。按照公式100万乘以1%也就是1万除以0.5万得到2个单位。这意味着你开仓2手如果价格反向波动一个N值你的亏损恰好就是账户资金的1%多一点风险被严格锁定了。在AIQT里实现这个逻辑的时候我建议你先把“合约每点价值”这个参数单独拎出来不要写死在代码里。不同品种的合约乘数差别很大商品期货和股指期货的每点价值可能是几十块和几百块的差距如果你在代码里用了死数换一个品种回测时很容易算错仓位导致资金曲线严重失真。2.3 分批建仓与加仓机制海龟策略和其他趋势策略最大的不同在于它不是一次性满仓进场而是分批加仓。原版规则是入场之后价格每朝有利方向波动0.5个N值就加仓一次每个方向最多加仓4个单位。这种加仓方式背后有一个很重要的心理暗示趋势行情中有回调很正常如果一次性重仓进去几个点的回撤就可能把你震出局而分批加仓让你在行情启动初期只承担小仓位风险趋势确认后仓位逐步加大这样才能在趋势中后期吃到最肥美的一段。我在写AIQT代码时会把加仓判断封装成一个独立的方法每次K线更新时检查当前持仓手数是否达到了上限。这里有一个非常容易误解的地方加仓的触发条件是“价格相对于上一次加仓价上涨了0.5倍的N值”而不是相对于最初入场价。如果你按最初入场价来算加仓点很可能造成加仓频率过高仓位冒进回撤加大。还有一点需要提醒加仓间隔太小会让成本曲线迅速抬高一旦行情反转止损时亏损会比较大加仓间隔太大又可能踏空行情趋势走完了仓位还没建满。0.5N这个参数是海龟们用真实资金试验过的经验值我建议第一版回测时先原样保留跑通之后再去调整对比。2.4 止损与离场规则海龟策略的止损规则同样清晰任何单位头寸价格反向波动2个N值时无条件止损离场。这是一个硬性风险闸门不管你对行情多么看好只要触及2N止损线就必须认赔出局。为什么是2N因为入场本身存在一定概率是假突破价格反向波动2N说明突破逻辑大概率被证伪此时继续持仓只会让亏损扩大。用风险收益比来算如果你在入场后朝有利方向只走了0.5N就回调止损在2N盈亏比并不好但如果你在趋势中不断加仓持仓成本被逐步抬高那么2N止损相对于整体仓位而言实际上变成了一个可接受的回撤幅度。离场规则则是10日反向突破做多单在价格跌破过去10日最低价时平仓做空单在价格升破过去10日最高价时平仓。这一规则的作用是让利润奔跑——只要趋势还在延续10日低点就不会被跌破你就一直持有直到趋势真正拐头10日低点被击穿才把持仓了结。在AIQT里离场判断和入场判断要放在同一根K线内完成但先后顺序有讲究。我的习惯是先检查止损是否触发再检查10日离场信号最后才检查入场和加仓信号。这样做的好处是如果价格同时触及了止损线和离场线止损优先执行避免订单顺序错乱之后出现“该平仓却还在加仓”的荒唐情况。3. AIQT框架下的环境准备3.1 安装AIQT与工程结构AIQT是面向国内金融市场的开源量化框架支持股票和期货的行情数据获取、策略回测以及模拟交易。安装方式和其他Python库一致直接用pip安装即可。pip install aiqt安装完成之后我建议你按下面的结构来组织自己的策略工程别把所有代码堆在一个文件里。工程结构清晰后续换品种、调参数时你的效率会高很多。这是我的目录习惯strategy_sea_turtle/ ├── config.py # 参数配置品种、周期、资金、手续费 ├── data_fetcher.py # 行情数据获取与预处理 ├── turtle_strategy.py # 海龟策略核心逻辑 ├── backtest.py # 回测入口脚本 ├── results/ # 回测结果输出目录 └── logs/ # 运行日志目录这样的分层方式把“数据”、“策略”、“回测”三块逻辑彻底解耦。后续如果想把同一套策略跑在股指期货上只需要改config.py和data_fetcher.pystrategy文件一个字母都不用动。3.2 AIQT的核心模块速览AIQT框架里有几个模块是和策略开发强相关的我简单梳理一下方便你对照着看后面的代码。DataFeed负责接入行情数据源既支持从本地CSV文件读取也支持从数据服务商拉取历史K线。Strategy策略基类需要实现init()和on_bar()两个方法。init()里做指标初始化on_bar()在每根K线收盘时被调用是策略逻辑的宿主。Broker模拟券商通道负责处理订单的撮合、手续费、滑点计算。BacktestEngine回测引擎把DataFeed、Strategy、Broker串起来逐根K线推动策略执行。用一句话概括AIQT的逻辑链条回测引擎拿到数据喂给策略策略发出指令交给券商模块模拟成交再把结果记录到账户里。数据、策略、交易三者各司其职理解了这个模型你写代码时就不会晕头转向。3.3 数据获取与清洗AIQT支持直接对接部分免费数据源但我在本地做策略验证时更倾向于使用CSV格式的历史K线数据简单可控不会在排错时分不清到底是数据源问题还是策略问题。CSV文件至少需要包含这几列datetime时间、open开盘价、high最高价、low最低价、close收盘价、volume成交量。如果你要回测期货、股票或者虚拟币格式完全通用。清洗时有一个关键步骤按时间升序排序并去掉重复的K线时间戳。我遇到过数据源里同一个时间戳出现两行的脏数据导致回测时资金曲线突然异常跳动排查了半天。AIQT的DataFeed模块支持直接读取DataFrame或CSV文件路径你可以在config.py里指定数据文件的路径和格式代码里只需要一行就能完成数据装载。装载数据之后建议先打印一下K线根数和日期范围确认数据完整再进行下一步。data DataFeed.from_csv(path/to/data.csv, datetime_columndatetime) print(data.summary())4. 手把手在AIQT里实现海龟策略4.1 写一个类海龟策略的骨架打开turtle_strategy.py先把策略类的基本结构写好。AIQT里所有策略都继承自Strategy基类核心是重写init()和on_bar()两个方法。from aiqt import Strategy import pandas as pd class TurtleStrategy(Strategy): def init(self): self.entry_period 20 self.exit_period 10 self.atr_period 20 self.max_units 4 self.risk_percent 0.01 self.add_unit_distance 0.5 # 加仓间隔单位N self.stop_loss_n 2.0 # 止损距离单位N # 存放历史高低点用于计算通道突破 self.last_entry_price None self.units 0在init()里我把所有可调参数都沉在这一个类里这是为了方便后续做参数扫描。注意self.last_entry_price在初始化时是None它记录的是最近一次入场价加仓判断依赖这个值而不是依赖均线之类的东西。接着定义唐奇安通道和ATR的计算逻辑。AIQT虽然自带了一些技术指标但海龟策略的通道突破逻辑很简单直接用pandas的滚动窗口计算更直观def calc_indicators(self, df): df[entry_high] df[high].rolling(self.entry_period).max() df[entry_low] df[low].rolling(self.entry_period).min() df[exit_high] df[high].rolling(self.exit_period).max() df[exit_low] df[low].rolling(self.exit_period).min() df[tr] pd.concat([ df[high] - df[low], (df[high] - df[close].shift(1)).abs(), (df[low] - df[close].shift(1)).abs() ], axis1).max(axis1) df[n] df[tr].ewm(alpha1 / self.atr_period, adjustFalse).mean() return df这里有一个细节值得注意ATR的指数移动平均用的是alpha1/N的衰减系数而不是pandas默认的spanN。两者的历史平滑权重略有不同虽然差异不大但既然海龟原版用的是后者我们在代码里也保持一致尽量减少无谓的偏差。4.2 核心交易指令入场、加仓、离场有了指标之后重点就是on_bar()里的交易逻辑。每一根K线结束时函数会被调用一次。我习惯把逻辑拆成三个独立的方法check_stop_loss()、check_exit_signal()、check_entry_signal()这样条理清晰后续加过滤条件也方便。先看止损def check_stop_loss(self, current_price): if self.units 0: return if self.last_entry_price is None: return stop_price self.last_entry_price - self.stop_loss_n * self.n_value if current_price stop_price: self.sell_all()关键点在于止损价的计算用的是最近一次入场价而不是初始入场价。因为海龟是分批加仓的每一批新仓位的成本价更高止损线也随之抬升。如果你从头到尾只用一个初始入场价那加仓部分的风险敞口就等于没有止损保护一旦行情尖顶反转几分钟内就能把浮盈全部吞掉。再看离场信号def check_exit_signal(self, df): if self.units 0: return current_price df[close].iloc[-1] exit_low df[exit_low].iloc[-1] if current_price exit_low: self.sell_all()这个逻辑简洁得有些让人怀疑但10日低点被击穿这个信号本身就是趋势扭转的早期预警。我的实测经验是它比很多复杂的均线交叉系统更抗噪尤其在隔夜跳空频繁的市场里。入场和加仓逻辑是核心中的核心def check_entry_signal(self, df): current_price df[close].iloc[-1] entry_high df[entry_high].iloc[-2] # 注意这里 # 开新仓 if self.units 0: if current_price entry_high: units self.calc_position_units(current_price) self.buy(unitsunits) self.last_entry_price current_price # 加仓 else: if self.units self.max_units: add_price self.last_entry_price self.add_unit_distance * self.n_value if current_price add_price: units self.calc_position_units(current_price) self.buy(unitsunits) self.last_entry_price current_price为什么入场比较用的是entry_high.iloc[-2]而不是[-1]因为当前K线的最高价本身就会随着行情上涨而上涨如果你用包含当前K线在内的最高价做比较就会出现“价格永远突破自己”的尴尬情况。正确做法是使用截至上一根K线结束时的20日最高价让当前这根K线的收盘价去突破历史通道上沿。这个细节极其重要我刚开始写的时候用[-1]回测结果漂亮得不像话后来仔细一查才发现是未来函数导致的虚幻收益。4.3 回测引擎配置与跑通策略写完之后需要配置回测引擎并运行。在backtest.py里我们做三件事装载数据、实例化策略、启动回测。from aiqt import BacktestEngine, DataFeed from turtle_strategy import TurtleStrategy if __name__ __main__: data DataFeed.from_csv(data/eth_1d.csv, datetime_columndate) engine BacktestEngine( strategy_classTurtleStrategy, datadata, initial_cash1000000, commission_rate0.0005, slippage_rate0.0002, ) engine.run() engine.plot_results()这里的initial_cash初始资金100万手续费率万五滑点万分之二。这两个变量的影响在短线策略上极其致命如果你的策略一年交易100次每次手续费加滑点吃掉0.07%的成本那一年的总磨损就是7%足以让一个原本盈利的策略变成亏损。我在回测海龟策略时一开始把手续费设置为零资金曲线极其亮眼后来加了真实费用收益率直接打了对折——所以永远不要忽略交易成本。跑完回测之后AIQT会输出一份回测报告包含总收益率、年化收益率、最大回撤、夏普比率、交易次数等指标同时生成资金曲线图。第一时间应该看的是最大回撤不是收益率。一个动不动回撤40%的策略即使最终年化50%你也未必拿得住。5. 回测结果分析与实战调优5.1 别被收益率迷惑先看回撤我第一次用默认参数跑海龟策略回测结果让我既兴奋又纠结。兴奋的是总收益率确实可观纠结的是资金曲线的回撤幅度大得有点吓人最大回撤接近35%。这意味着。如果你在最高点附近入场心理压力会大到让你怀疑策略是否已经失效然后在底部割肉离场完美错过后面的反弹。所以在分析回测报告时我的习惯是看四个核心指标的组合指标参考意义健康区间经验值总收益率策略整体是否盈利越高越好但要和回撤配比最大回撤你能承受的最坏情况不超过30%为宜夏普比率单位风险的超额收益大于1算及格大于2算优秀盈利交易占比交易胜率海龟策略通常在40%以下正常海龟策略的胜率通常不高可能100笔交易里面只有30笔是赚钱的但赚的那几笔大趋势行情单笔收益能够覆盖前面几十笔小亏损的总和。这决定了你不能用“胜率思维”来评价它而应该用“盈亏比思维”。记住这个前提你分析回测结果时才能保持客观。5.2 参数调整的边界和方向默认参数跑通之后大多数人不满足于现状总想通过调参把收益率再拉高一截。这个想法本身没错但要注意边界。拿入场周期来说我把entry_period从20改成10之后回测结果显示交易次数明显增加资金曲线的回撤幅度反而扩大了改成60之后交易次数骤降收益率也不理想。这说明20日通道在大多数市场里是一个相对均衡的选择。ATR周期也值得调整。我试过把atr_period从20改成14单位头寸变大了一些因为14日ATR通常比20日小最终结果是有几段行情赚得更多了但相应地止损距离也变窄了假突破时的亏损次数增多。这种参数之间的相互影响你在调参时一定要关注不能孤立地只调一个参数来看结果。加仓间隔add_unit_distance也是可以调整的参数。从0.5N放大到1N仓位建立的时间被拉长资金曲线更加平稳但可能在趋势中后期仓位还没建满行情就结束了。从0.5N缩小到0.25N加仓更频繁仓位快速打满趋势初期收益爆发力更强但一旦行情中途反转止损时亏损更大。我的建议是如果你回测的品种波动比较剧烈比如加密货币或者某些商品期货可以把加仓间隔适当调大降低波动对仓位的冲击。还有一个隐藏参数经常被忽略回测数据的时间跨度。我最初只回测了一年数据得出一个非常乐观的结论把策略切换到三年数据之后收益曲线立刻变得非常平庸。原因是那一年正好是单边大牛市趋势跟踪策略当然赚得盆满钵满但一旦行情进入震荡市海龟策略就会连续止损。做策略评估时数据跨度至少要覆盖一轮完整的牛熊周期否则你的回测结论没有任何参考价值。5.3 常见问题与排查技巧实录我在AIQT里实现海龟策略的过程中遇到过几个比较典型的坑写出来供你参考。第一个是前面提过的未来函数问题用[-1]还是[-2]直接决定回测结果是真实收益还是虚假繁荣。要排查这一点你可以在回测日志里随机找几个交易记录手工计算一下入场价和信号触发K线的前后关系确认入场价没有出现在信号K线之前。第二个问题是加仓次数超过最大限制。AIQT框架本身对单笔订单的手数有限制但海龟策略的加仓是多次下单的累积如果你在init()里没有初始化好self.units或者在平仓后没有将它复位归零那策略下一次开仓时系统会认为你仍然持有大量仓位导致仓位计算错乱。我的建议是在sell_all()方法里显式重置self.units 0和self.last_entry_price None。第三个问题是K线周期选择不当。海龟策略原版主要面向日线级别因为唐奇安通道的突破信号在日内周期下会变得非常嘈杂。我试过在15分钟K线上跑同样的策略结果交易频率暴增手续费占比极高资金曲线呈锯齿状。如果你一定要在小时级别以下跑那要重新评估手续费对策略的吞噬效果可能要把手续费调低到万二以下才能勉强看出趋势收益。第四个问题是空仓期间资金利用效率太低。海龟策略本质上是一个低频率交易策略一年可能只有十几笔交易大部分时间资金在账户里躺着。有人觉得这是浪费但我认为这恰恰是策略能活下来的原因——空仓等待本身就是趋势跟踪的一部分。如果你实在想在空窗期做点增强收益的事可以考虑买货币基金之类的低风险产品但这已经属于资金管理层面的扩展了不在策略本身讨论范围内。还有一个经常被忽略的细节除零问题和数据缺失。ATR计算时如果历史K线不足20根tr和n都会出现NaN值策略如果在此时入场就会计算失败。在on_bar()里加一个判断如果当前K线数量少于atr_period 2直接返回不执行任何交易指令。这种防御性编程在实盘中同样是必须的。6. 做实盘之前这几句话值得反复琢磨回测跑通只是第一步真正到实盘阶段你会面临另一个问题AIQT的模拟盘和实盘接口是否稳定、滑点是否可控、程序是否能在关键时刻不宕机。我个人的经验是先跑至少一个月的模拟盘观察策略实盘表现与回测数据之间的偏差重点关注滑点和成交率。如果模拟盘的收益率与回测相差超过20%那大概率是交易成本模型没设置准确或者真实市场中的流动性不足以支撑你的单笔下单量。资金管理上海龟策略有一个容易让人心态失衡的地方连续止损阶段。回测曲线在震荡行情里可能连续十几次小亏账面资金缩水10%这个时候能不能坚持执行策略直接决定了你最终能否吃到后面那波大行情。我的建议是实盘前先想清楚自己的最大心理回撤线在哪里把它写下来贴在看得到的地方。如果策略在回测中的最大回撤是30%那实盘的预期回撤应该按40%来准备因为实盘还会有滑点、系统故障、人工干预等额外损耗。最后再分享一个我后来一直在用的小技巧把AIQT的回测结果导出成交明细然后单独写程序分析每一笔交易的平均持仓天数。海龟策略的盈亏结构和持仓时长高度相关如果某笔持仓盈利巨大但持仓天数远低于平均水平说明你可能过早离场了——这时候检查一下离场逻辑是否使用了10日通道而不是被其他条件干扰。这样的复盘分析比反复调参数更有价值因为它能让你理解策略在什么行情下表现好、什么行情下表现差从而对策略建立一种真实、接地气的信任感。海龟策略的价值不在于它有多新鲜而在于它把入场、仓位、止损、离场四个环节的规则全部显式化、数量化没有任何模糊成分。用AIQT把它实现出来你实际经历的是从交易想法到工程代码的完整链路——这种能力才是你在量化这条路上最核心的资产。
返回列表