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

海龟交易法介绍

海龟交易法(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个股票测试,其回测结果如下:

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

相关推荐
sg_knight2 小时前
适配器模式(Adapter)
python·设计模式·适配器模式·adapter
52Hz1182 小时前
力扣20.有效的括号、155.最小栈
python·算法·leetcode
小鸡吃米…2 小时前
TensorFlow 实现多层感知机学习
人工智能·python·tensorflow
WW、forever3 小时前
【服务器】上传服务器中数据至 FigShare(Python)
运维·服务器·python
宝贝儿好3 小时前
【强化学习】第十章:随机高斯策略
人工智能·python·深度学习·神经网络·机器人·自动驾驶
haosend3 小时前
【练习版】使用paramiko批量的查询,管理,配置路由器交换机
python·路由器·交换机·网络自动化
Dxy12393102163 小时前
Python生成随机手机号码
开发语言·python
小帅学编程3 小时前
Python学习
开发语言·python·学习
两万五千个小时4 小时前
构建mini Claude Code:08 - Fire and Forget:用后台线程解锁 Multi-Agent 并行执行
人工智能·python·架构