量化金融实践-海龟交易法

海龟交易法介绍

海龟交易法(Turtle Trading System)是金融交易领域最著名的趋势跟踪策略之一,起源于1983年理查德·丹尼斯(Richard Dennis)与威廉·埃克哈特(William Eckhardt)的一场著名赌约。丹尼斯认为优秀的交易员可以通过系统化训练培养,而埃克哈特则认为交易能力更多依赖天赋。为验证这一观点,丹尼斯招募了13名毫无交易经验的普通人(来自服务员、音乐家等不同行业),对他们进行了为期两周的培训,并提供了真实资金进行交易。这些学员被称为"海龟",该实验在随后的四年中创造了年均复利超过80%的惊人收益,证明了系统化交易策略的可复制性和有效性。

核心交易哲学与原则

海龟交易法的核心建立在四大原则之上:

  • 掌握优势:找到一个期望值为正的交易策略,长期坚持以获得正回报。

  • 管理风险:严格控制每笔交易的风险,确保在不利行情中能够生存下来。

  • 坚定不移:完全机械地执行交易系统的每一个信号,杜绝情绪干扰。

  • 简单明了:简单的系统比复杂的系统更具生命力和可执行性。

完整的交易系统框架

海龟交易法是一个完整的机械交易系统,涵盖了"交易什么、交易多少、何时买卖、何时退出"等所有环节。

|------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
| 系统组成部分 | 具体规则与说明 |
| 市场选择 | 主要交易高流动性、具有趋势性的市场,如商品期货、外汇、股指期货等。 |
| 头寸规模管理(核心) | 使用平均真实波幅(ATR) ​ 动态衡量市场波动性,确保在不同波动性的市场中承担相同的风险。计算公式为:头寸单位 = (账户总资金 × 风险比例) / (ATR × 合约乘数) 。通常将单笔交易风险控制在账户总资金的1%-2% |
| 入市信号 | 采用唐奇安通道突破 系统,分为两套: • 短期系统(S1) :价格突破过去20日 的最高价时做多,跌破过去20日 的最低价时做空。 • 长期系统(S2) :价格突破过去55日 的最高价时做多,跌破过去55日的最低价时做空。 |
| 加仓策略 | 初始头寸建立后,若价格向有利方向移动0.5个ATR ,则加仓一个单位。最多允许加仓3次,即总持仓可达初始仓位的4倍。 |
| 止损规则 | 任何头寸的初始止损点都设置为2倍ATR。例如,做多入场价为100,ATR为5,则止损价为90(100 - 2×5)。 |
| 退出(离市)规则 | 不预设盈利目标,采用移动止损跟踪趋势: • 短期系统 :多头头寸在价格跌破过去10日 最低价时平仓;空头头寸在价格涨破过去10日 最高价时平仓。 • 长期系统 :多头头寸在价格跌破过去20日 最低价时平仓;空头头寸在价格涨破过去20日最高价时平仓。 |

策略的优势与局限性

优势

  • 系统化与纪律性:完全规则化,消除了交易中情绪和主观判断的干扰。

  • 捕捉大趋势:在单边趋势行情中,能够牢牢抓住主要波段,实现丰厚利润。

  • 风险控制严谨:通过ATR和固定百分比风险模型,严格限制了单笔和整体账户的最大亏损。

局限性与挑战

  • 震荡市表现不佳:在无趋势的盘整行情中,会因频繁的假突破信号导致连续多次的小额亏损,对交易者心理是巨大考验。

  • 资金回撤较大:策略盈利依赖于少数几次大趋势,在抓住趋势前可能经历较长时间的资金回撤(可能高达30%-50%)。

  • 对执行力要求极高:必须像机器一样无条件执行所有信号,在连续亏损时仍能坚持,这违背人性,绝大多数人难以做到。

海龟交易法的策略实现

在聚宽这个量化交易网站上,按照以上介绍的海龟交易法的原则,编写一个海龟交易的策略,并且进行测试,代码如下:

python 复制代码
# -*- coding: utf-8 -*-
# 海龟交易法量化策略
from jqdata import *
import numpy as np
import pandas as pd

def initialize(context):
    """初始化海龟策略"""
    # 设置基准
    set_benchmark('000300.XSHG')
    set_option('use_real_price', True)
    
    # 海龟参数
    g.entry_sys1_days = 20    # 系统1入场周期
    g.entry_sys2_days = 55    # 系统2入场周期
    g.exit_sys1_days = 10     # 系统1离场周期
    g.exit_sys2_days = 20     # 系统2离场周期
    g.atr_days = 20           # ATR计算周期
    g.stop_loss_atr = 2.0     # 止损ATR倍数
    g.pyramid_gap = 0.5       # 加仓间隔(ATR倍数)
    g.max_units = 4           # 最大加仓单位数
    
    # 股票池(示例:沪深300成分股)
    g.stock_pool = get_index_stocks('000300.XSHG')
    
    # 记录每个股票的状态
    g.positions_data = {}  # 结构:{股票代码: {'units': 数量, 'entry_price': [], 'last_pyramid': 价格}}
    
    # 风险参数
    g.risk_per_trade = 0.01  # 单笔交易风险1%
    
    # 设置手续费
    set_order_cost(OrderCost(
        close_tax=0.001,
        open_commission=0.0003,
        close_commission=0.0003,
        min_commission=5
    ), type='stock')
    
    # 每日运行
    run_daily(turtle_trading, time='open', reference_security='000300.XSHG')

def turtle_trading(context):
    """海龟交易主逻辑"""
    # 获取当前时间
    current_date = context.current_dt
    
    for stock in g.stock_pool:
        # 跳过停牌和ST
        #if is_suspended(stock) or is_st_stock(stock):
        if check_stock_status(context, stock):
            continue
            
        # 获取历史数据
        prices = get_price(stock, 
                          end_date=current_date, 
                          count=max(g.entry_sys2_days, g.atr_days)+20, 
                          frequency='daily', 
                          fields=['high', 'low', 'close'])
        
        if len(prices) < max(g.entry_sys2_days, g.atr_days)+10:
            continue
            
        # 计算关键指标
        high_prices = prices['high'].values
        low_prices = prices['low'].values
        close_prices = prices['close'].values
        
        # 计算ATR
        atr_value = calculate_atr(high_prices, low_prices, close_prices, g.atr_days)
        
        # 计算突破价位
        sys1_entry = np.max(high_prices[-g.entry_sys1_days:-1])  # 20日最高
        sys2_entry = np.max(high_prices[-g.entry_sys2_days:-1])  # 55日最高
        sys1_exit = np.min(low_prices[-g.exit_sys1_days:-1])     # 10日最低
        sys2_exit = np.min(low_prices[-g.exit_sys2_days:-1])     # 20日最低
        
        # 当前价格
        current_price = close_prices[-1]
        
        # 获取当前持仓
        if stock in context.portfolio.positions:
            current_position = context.portfolio.positions[stock].total_amount
        else:
            current_position = 0

        # 检查是否已有该股票记录
        if stock not in g.positions_data:
            g.positions_data[stock] = {
                'units': 0,
                'entry_price': [],
                'last_pyramid': None,
                'system': None
            }
        
        pos_data = g.positions_data[stock]

        # ========== 离场逻辑 ==========
        if current_position > 0:
            # 系统1离场条件
            if current_price < sys1_exit and pos_data['system'] == 1:
                if place_order(stock, 0):
                    log.info(f"系统1离场 {stock},价格{current_price:.2f} < 10日最低{sys1_exit:.2f}")
                    pos_data['units'] = 0
                    pos_data['entry_price'] = []
                    pos_data['last_pyramid'] = None
                    continue
            '''    
            # 系统2离场条件
            if current_price < sys2_exit and pos_data['system'] == 2:
                if place_order(stock, 0):
                    log.info(f"系统2离场 {stock},价格{current_price:.2f} < 20日最低{sys2_exit:.2f}")
                    pos_data['units'] = 0
                    pos_data['entry_price'] = []
                    pos_data['last_pyramid'] = None
                    continue
            '''
            # 止损检查
            if len(pos_data['entry_price']) > 0:
                # 使用平均入场价计算止损
                avg_entry = np.mean(pos_data['entry_price'])
                stop_loss_price = avg_entry - g.stop_loss_atr * atr_value
                
                if current_price < stop_loss_price:
                    if place_order(stock, 0):
                        log.info(f"止损离场 {stock},价格{current_price:.2f} < 止损价{stop_loss_price:.2f}")
                        pos_data['units'] = 0
                        pos_data['entry_price'] = []
                        pos_data['last_pyramid'] = None
                        continue
            
            # ========== 金字塔加仓逻辑 ==========
            if pos_data['units'] < g.max_units:
                # 检查加仓条件
                if pos_data['last_pyramid'] is None:
                    log.info(f"stock: {stock}, date: {context.current_dt}, pos_data: {pos_data}")
                    last_pyramid_price = pos_data['entry_price'][0]
                else:
                    last_pyramid_price = pos_data['last_pyramid']
                
                # 价格比上次加仓上涨了0.5ATR
                if current_price >= last_pyramid_price + g.pyramid_gap * atr_value:
                    # 计算加仓数量
                    unit_size = calculate_unit_size(context, stock, atr_value)
                    if unit_size > 0:
                        if place_order(stock, unit_size):
                            pos_data['units'] += 1
                            pos_data['entry_price'].append(current_price)
                            pos_data['last_pyramid'] = current_price
                            log.info(f"金字塔加仓 {stock},第{pos_data['units']}单位,价格{current_price:.2f}")
        
        # ========== 入场逻辑 ==========
        elif current_position == 0:
            # 系统1入场(20日突破)
            if current_price > sys1_entry:
                unit_size = calculate_unit_size(context, stock, atr_value)
                if unit_size > 0:
                    if place_order(stock, unit_size):
                        pos_data['units'] = 1
                        pos_data['entry_price'] = [current_price]
                        pos_data['last_pyramid'] = current_price
                        pos_data['system'] = 1
                        log.info(f"系统1入场 {stock},价格{current_price:.2f} > 20日最高{sys1_entry:.2f}")
            '''
            # 系统2入场(55日突破)- 更保守
            if current_price > sys2_entry:
                unit_size = calculate_unit_size(context, stock, atr_value)
                if unit_size > 0:
                    if place_order(stock, unit_size):
                        pos_data['units'] = 1
                        pos_data['entry_price'] = [current_price]
                        pos_data['last_pyramid'] = current_price
                        pos_data['system'] = 2
                        log.info(f"系统2入场 {stock},价格{current_price:.2f} > 55日最高{sys2_entry:.2f}")
            '''

def calculate_atr(high, low, close, period=20):
    """计算平均真实波幅(ATR)"""
    tr = np.zeros(len(high))
    
    for i in range(1, len(high)):
        hl = high[i] - low[i]
        hc = abs(high[i] - close[i-1])
        lc = abs(low[i] - close[i-1])
        tr[i] = max(hl, hc, lc)
    
    # 计算ATR
    atr = np.zeros(len(tr))
    atr[period] = np.mean(tr[1:period+1])
    
    for i in range(period+1, len(tr)):
        atr[i] = (atr[i-1] * (period-1) + tr[i]) / period
    
    return atr[-1] if len(atr) > 0 else 0

def calculate_unit_size(context, stock, atr_value):
    """计算头寸规模单位(海龟法核心)"""
    if atr_value <= 0:
        return 0
    
    # 获取股票基本信息
    stock_info = get_security_info(stock)
    
    # 计算每点价值(A股通常1点=1元)
    point_value = 1.0
    
    # 计算可承受的亏损金额
    total_capital = context.portfolio.total_value
    risk_amount = total_capital * g.risk_per_trade  # 单笔风险1%
    
    # 计算头寸规模
    # 公式:头寸单位 = 风险金额 / (ATR × 每点价值)
    unit_size = risk_amount / (atr_value * point_value)
    
    # 转换为整手(A股100股一手)
    unit_size = int(unit_size / 100) * 100
    
    # 确保不超过可用资金
    available_cash = context.portfolio.available_cash
    
    price = get_price(stock, count=1, fields='close')['close'].iloc[-1]
    if math.isnan(price):
        return 0
    max_by_cash = available_cash / price
    max_by_cash = int(max_by_cash / 100) * 100
    unit_size = min(unit_size, max_by_cash)
    return unit_size

def handle_data(context, data):
    """备用数据处理器"""
    pass

def check_stock_status(context, stock):
    """检查股票状态:停牌和ST"""
    current_data = get_current_data()
    stock_info = current_data[stock]
    
    # 检查是否停牌
    is_paused = stock_info.paused  # True表示停牌,False表示正常
    
    # 检查是否为ST股票
    is_st = stock_info.is_st  # True表示ST/*ST,False表示正常
    
    return is_paused or is_st
    
def place_order(stock, unit_size):
    order_result = order(stock, unit_size)
    if order_result:
        if order_result.filled > 0:
            return True
    return False

以上代码中分别包括了系统1和系统2的实现,可以对这两个系统分别进行测试。

以系统1为例,对沪深300成分股应用海龟交易法进行回测,测试区间为2019-01-01到2025-12-31,回测结果如下:

从以上结果可见,海龟交易法不太适合股票震荡市,在2019-01到2023-03这个时间段其收益是跑输沪深300的基准收益的。但是在牛市,其收益是大幅跑赢基准收益。

另外如果我们只选取沪深300成分股的其中前100个股票测试,其回测结果如下:

从以上结果可见,选取不同的股票,其收益基本都跑赢基准收益,可见选择不同的股票,对于结果影响也是很大的。这启发了我们可以对股票进行筛选,以提高海龟交易法的收益。

相关推荐
笨鸟先飞,勤能补拙1 小时前
AI 赋能网络安全:技术全景、成熟度评估与实战案例
人工智能·python·安全·web安全·网络安全·sqlite·github
长和信泰光伏储能2 小时前
京津冀光伏发电:绿色能源的未来之路
python·能源
浦信仿真大讲堂3 小时前
从重复操作到自动化闭环:如何让 CST 与 Python 真正协同起来
python·自动化·cst·仿真软件·达索软件
Gu Gu Study3 小时前
ScoutLoop开放域深度研究引擎(agent的初步设计想法)
人工智能·python
卷无止境4 小时前
写代码这件事,到底该讲究点什么?
后端·python
卷无止境4 小时前
循环复杂度到底在算什么,Python 代码怎么才能写得让人一看就懂
后端·python
lpfasd1234 小时前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
Dxy12393102165 小时前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python
bamb005 小时前
一个项目带你入门AI应用开发01
python
0566465 小时前
Python康复训练——常用标准库
开发语言·python·学习