# 📚 TurtleTrader Analyzer    ## 🌟 项目概述 TurtleTrader Analyzer 是一个基于Web的应用系统,允许交易者和投资者对中国A股股票进行著名的海龟交易策略回测和分析。该系统提供了友好的用户界面,用于参数调整、性能可视化和详细报告生成。 ## 功能特点 - **数据获取**:从Akshare API获取实时股票数据或生成模拟数据进行测试 - **技术指标**:计算ATR(平均真实波幅)、唐奇安通道和支撑阻力位 - **信号生成**:根据海龟交易规则识别入场和出场信号 - **回测引擎**:模拟真实交易,包含仓位大小和风险管理 - **绩效分析**:提供全面的绩效指标,包括收益率、回撤和年化表现 - **可视化**:交互式图表展示资金曲线、收益对比和交易信号 - **数据导出**:支持导出回测结果进行进一步分析 ## 📂 项目结构、 - 📁 turtletrader-analyzer/ - 📄 main.py # 主应用文件,包含Streamlit UI - 📄 data_fetcher.py # 数据获取功能模块 - 📄 indicators.py # 技术指标计算模块 - 📄 signals.py # 交易信号生成模块 - 📄 backtest.py # 回测引擎和仓位管理模块 - 📄 performance.py # 绩效分析和报告模块 - 📄 requirements.txt # Python依赖项 - 📄 utils.py # 辅助工具函数 - 📄 README.md # 项目文档 ## 🛠️ 技术栈 | 技术组件| 版本 | 用途 | 备注 | |------|-------|------|----------| | **Python** | 3.8+ |主要编程语言 | 核心运行环境 | | **Streamlit** | 1.49+ |Web应用框架 | 构建交互式用户界面 | | **Pandas** | 2.3+ |数据处理与分析 | 数据清洗、转换和分析 | | **NumPy** | 2.3+ |数值计算 | 数学运算和数组操作 | | **AKshare** | 1.17+ | 金融数据接口|获取中国股票市场数据 | ## 🚀 快速开始 # 📦 依赖安装指南 ## 系统要求 - Python 3.8+ - pip 20.0+ - 推荐使用虚拟环境 ## 基础安装 ### 1. 创建虚拟环境(推荐) ```bash python -m venv env source env/bin/activate # Linux/Mac env\Scripts\activate # Windows ``` ### 2.安装依赖命令: ```bash pip install -r requirements.txt ``` ### 3. 运行项目 ```bash streamlit run main.py
一、股票数据的获取
AKShare是一个开源、免费的金融数据接口库,能够提供丰富且高质量的股票市场数据。
def get_stock_data(code, start_date, end_date):
"""获取股票历史数据"""
try:
# 使用akshare获取数据
stock_data = ak.stock_zh_a_hist(symbol=code, period="daily",
start_date=start_date, end_date=end_date,
adjust="qfq")
# 筛选所需的列
stock_data = stock_data[['日期','开盘', '最高', '最低', '收盘', '成交量']]
# 重命名列
stock_data.rename(columns={
'日期': 'date',
'开盘': 'open',
'最高': 'high',
'最低': 'low',
'收盘': 'close',
'成交量': 'volume'
}, inplace=True)
# 处理日期索引
stock_data['date'] = pd.to_datetime(stock_data['date'])
stock_data.set_index('date', inplace=True)
return stock_data
except Exception as e:
print(f"获取数据失败: {e}")
return generate_mock_data(start_date, end_date)
二、唐奇安通道的绘制
唐奇安通道中三条轨道线的计算方式如下所示:
- 上轨线:由最近N个交易日内股票的最高价的最大值构成。该周期N可由用户自定义,通常默认设置为20日。其计算公式为:
上轨 = Max(最高价, N)
- 下轨线:由最近N个交易日日内股票的最低价的最小值构成。计算公式为:
下轨线=Min(最低价,N)
- 中轨线:作为通道的中间基准,由上轨线与下轨线的算术平均值构成。它代表了该时间段内价格波动的中间水平,计算公式为:
中轨 = (上轨 + 下轨) / 2
def calculate_donchian_channels(data, period=20):
"""计算唐奇安通道"""
up_line = data['high'].rolling(window=period).max()
down_line = data['low'].rolling(window=period).min()
midd_line = (up_line + down_line) * 0.5
return up_line, midd_line, down_line
三、交易信号的生成
唐奇安通道(Donchian Channel)的交易信号制定规则,尤其是其在经典海龟交易法则中的应用,是一套非常系统且纪律性极强的体系。其核心思想是追踪趋势,并在趋势启动时入场,在趋势反转时离场,其信号规则主要分为两个方面:入场信号和离场信号。
入场信号:
- 做多信号(多头入场):其规则是当股票的收盘价向上突破唐奇安通道上轨(即创出N日新高)时,生成一个做多信号。该信号表明市场可能结束了之前的盘整或下跌状态,新的上涨趋势正在启动,买方力量比较强劲。
- 做空信息(空头入场):其规则是当股票的收盘价向下跌破唐奇安通道的下轨(即创出N日新低)时,生成一个做空信号。该信号表明市场可能结束了之前的盘整或上涨状态,新的下跌趋势正在启动,买方力量占据主导。
def generate_signals(data, up_line, down_line):
"""生成交易信号"""
# 将通道线向后平移一个日期
up_line_move = up_line.shift(1)
down_line_move = down_line.shift(1)
# 计算交易信号
up_signal = (data['close'].shift(1) < up_line_move.shift(1)) & (data['close'] > up_line_move)
down_signal = (data['close'].shift(1) > down_line_move.shift(1)) & (data['close'] < down_line_move)
# 合并信号
signals = pd.Series(0.0, index=data.index)
signals[up_signal] = 1 # 买入信号
signals[down_signal] = -1 # 卖出信号
return signals
离场信号:
- 多头离场(卖出平仓):其规则为当持有股票多头头寸时,如果股票的收盘价向下跌破唐奇安通道的下轨(即创出M日新低,M通常小于N,例如10日),生成离场信号。需要卖出所有持有该股票头寸,平仓离场。
- 空头离场(买入平仓):当持有股票空头头寸时,如果股票的收盘价向上突破唐奇安通道的上轨(即创出M日新高),生成离场信号。需要买入回补所有该股票的空头头寸,平仓离场。
# 计算最近N最低价线,用于做多平仓
def calculate_min_low(data, period=10):
"""计算N日最低点"""
return data['low'].rolling(window=period).min().shift(1)
# 计算最近N日最高价线,用于做空平仓
def calculate_min_high(data, period=10):
"""计算N日最低点"""
return data['high'].rolling(window=period).max().shift(1)
四、基于唐奇安通道进行简单的回测
假设初始资金为10000,买入、卖出的手续费为0.01%。
使用生成的交易信号,进行处理并合并,做多信号为1,做空信号为-1,无交易信号为0.根据信号制定交易策略,策略内容为:
- 清仓日:
-
- 若当前交易日的交易信号为1,停止清仓操作,更新清仓日期,向后延长持仓3个交易日。
- 若当前交易日的交易信号不为1,进行清仓操作。
-
- 非清仓日:
-
- 若当前交易日的交易信号为1,购买当前剩余的全部资金金额,设置清仓日期,从当前交易日起,持仓3个交易日。
- 若当前交易日的交易信号不为1,不进行特殊处理。
-
该策略通过信号触发开仓,并采用固定3日持仓期的机制进行平仓管理。
# 初始参数
initial_capital = 100000
positions = 0 #表示股票的持有量
commission = 0.0001 # 假设佣金率为0.1%(手续费)
# 回测框架 Capital':表示金额, 'Positions':表示持有量
portfolio = pd.DataFrame(index=stockData.index, columns=['Capital', 'Positions'])
# 对框架序列进行初始化
portfolio['Capital']=float(initial_capital)
portfolio['Positions'] = 0
pos=0 #进行清仓的交易日
print(portfolio)
for i in range(1,len(stockData)):
# 获取当前行数据
row = stockData.iloc[i]
capital=portfolio['Capital'].iloc[i-1]
# 判断当前交易日是否清仓日
if i==pos:
if row['signal'] == 1:
# 延长持仓时间
pos=i+3
portfolio.loc[row.name, 'Capital'] = capital
portfolio.loc[row.name, 'Positions'] = positions
else:
# 进行清仓处理
sell_quantit = positions
positions -= sell_quantit
capital += sell_quantit * row['close'] * (1 - commission)
portfolio.loc[row.name, 'Capital'] = capital
portfolio.loc[row.name, 'Positions'] = positions
else:
# 根据信号进行交易
if row['signal'] == 1: # 做多
# 建仓与做多
quantity = int(capital * 1 / (row['close'] * (1 + commission))) # 买入当前资金
if quantity > 0: # 确保买入数量不为0
positions += quantity
capital -= quantity * row['close'] * (1 + commission)
portfolio.loc[row.name, 'Capital'] = capital
portfolio.loc[row.name, 'Positions'] = positions
else:
portfolio.loc[row.name, 'Capital'] = capital
portfolio.loc[row.name, 'Positions'] = positions
# 买入后持有3个交易日进行卖出
pos = i + 3
# elif row['signal'] == -1: #做空(做空操作不做处理,仅考虑做多)
# pass
else:
portfolio.loc[row.name, 'Capital'] =capital
portfolio.loc[row.name, 'Positions'] = positions
# 计算总资产
portfolio['price']=portfolio['Capital']+portfolio['Positions']*stockData['close']
# 计算收益率
rate_c=(stockData['close']-stockData['close'].shift(1))/stockData['close'].shift(1)
rate_p=(portfolio['price']-portfolio['price'].shift(1))/portfolio['price'].shift(1)
# # 计算累计收益率
cumu_rate_c=(1 + rate_c).cumprod()*100
cumu_rate_p=(1 + rate_p).cumprod()*100
print(f"""---策略报表---
剩余资金:{portfolio['Capital'].iloc[-1]:.2f}元
总资金:{portfolio['price'].iloc[-1]:.2f}元
平安银行累计收益率:{cumu_rate_c.iloc[-1]:.2f}%
策略累计收益率:{cumu_rate_p.iloc[-1]:.2f}%
-----------------------------------------
""")
# 可视化处理
# 创建图形和第一个Y轴的子图
fig, ax1 = plt.subplots()
ax1.plot(portfolio['Capital'],label='剩余资金')
ax1.plot(portfolio['price'],label='总资金',c='r')
ax1.set_xlabel('日期')
ax1.set_ylabel('金额(元)')
ax1.legend(loc='upper right')
# 创建第二个Y轴
ax2 = ax1.twinx() # 共享x轴
ax2.plot(cumu_rate_c,ls='-.',label='平安银行累计收益率')
# ax2.plot(cumu_rate_y,ls='-.',label='宁波银行累计收益率')
ax2.plot(cumu_rate_p,ls='-.',label='配对策略累计收益率')
ax2.set_ylabel('收益率(%)')
ax2.legend(loc='upper left')
plt.title("平安银行唐奇安通道交易策略回测示意图")
plt.show()

五、资金管理与头寸规模的计算
(1)真实波动幅度均值ATR指标的计算
要进行头寸规模的合理设置,首先需要计算平均真实波幅(ATR)指标。ATR指标反映了价格在一定时期内的波动程度,其计算依赖于过去N个周期(如日、小时或分钟)内“真实波幅”(TR)的平均值。而真实波幅TR并非单一价差,它由以下三项中的最大值决定:
- 当前最高价与当前最低价差(high - low)。
- 当前最高价与昨收价(上一个交易周期的收盘价)之差的绝对值(abs(high - pre_close))。
- 当前最低价与昨收价之差的绝对值(abs(low- pre_close))。
对于TR的计算公式为:
TRt=max(Hight-Lowt,|Hight-Closet-1|,|Lowt-Closet-1|)
然后对TR进行平缓处理,可以使用指数移动平均(EMA)或加权移动平均(WMA),这里出于简化考虑,使用简单的移动平均进行计算,ATR的计算公式为:
ATR=MA(TR,N)
def calculate_atr(data, period=20):
"""计算平均真实波幅(ATR)"""
# 计算当天最高价与最低价之间的价差
h_l = data['high'] - data['low']
# 计算当天最高价与前一天收盘价的绝对价差
h_c = abs(data['high'] - data['close'].shift(1))
# 计算当天最低价与前一天收盘价的绝对价差
l_c = abs(data['low'] - data['close'].shift(1))
# 计算每个交易周期内的真实范围(TR)
tr = np.maximum(h_l, np.maximum(h_c, l_c))
# 计算价差的滚动平均值,即ATR
atr = tr.rolling(window=period).mean()
return atr
(2)头寸单位的计算
对于头寸单位的计算基于交易者的账户总资金与市场的绝对波动幅度,其中市场的绝对波动幅度是ATR与合约每一点数所代表的价值(或称为“美元/点数”)的乘积。在A股市场中涨跌的单位为1元,进行买卖的交易最小单位为手,一手为100股,所以每点价值量为(1× 100),也就是100元。计算头寸规模单位的公式为:

其中各种参数的含义如下所示:
- 头寸规模单位,用于划分头寸的单位。
- 风险系数,交易者能够承受的交易带来的最大亏损,一般选取1%。
- 市场的绝对波动幅度,ATR与每点价值量的乘积,在A股市场为100× ATR。
def calculate_position_unit(capital, atr, risk_per_trade=0.01, point_value=100, vc=1):
"""计算头寸单位"""
"""
:param capital: 交易者可以进行交易的总资产
:param atr: 所选股票的真实波动均值
:param risk_per_trade: 交易者可以承受的最大风险损失
:param point_value: 最小的交易数额(这里是一手,100股)
:param vc: 价值变化的最小单位(这里是一元人民币)
:return:
"""
n_value = atr * point_value * vc # N值
unit = (capital * risk_per_trade) / n_value # 计算单位
return max(1, int(unit)) # 至少1单位
六、改良后的海龟交易策略
改良过后的策略规则如下:
(1)建仓规则:
若当前交易日出现做多信号(信号为1)且当前无持仓,则执行建仓操作。初始建仓规模为1个头寸单位,同时计算下一次加仓价格(当前建仓价+0.5×ATR)和止损价格(建仓价-0.5×ATR),并将剩余加仓次数减1。
(2)加仓规则:
若当前仍有加仓次数(即持仓未满),每当价格上涨达到0.5×ATR的幅度时,则加仓1个头寸单位,直至达到最大持仓限制(加仓次数归零)。每次加仓后,需更新下一次加仓价格与止损价格,并将剩余加仓次数减1。
(3)止损规则:
若当前价格低于当前头寸单位所设置的止损价格,则执行减仓操作,卖出最近一次加仓的头寸单位,并回退加仓价格、加仓次数及止损价至上一状态。
(4)止盈规则:
本策略仅处理做多情形。当价格向下跌破过去10日最低价格轨道线时,执行清仓操作,即卖出全部持仓,并将加仓次数重置为初始值。
class TurtleTradingBacktest:
def __init__(self, initial_capital=100000,risk_per_trade=0.01, commission=0.0001, max_add_positions=4):
"""初始化回测引擎"""
self.initial_capital = initial_capital # 初始资金
self.risk_per_trade = risk_per_trade # 风险比例
self.commission = commission # 手续费
self.max_add_positions = max_add_positions # 最大加仓次数
# 初始化状态变量
self.positions = 0
self.unit = 0
self.entry_price = 0
self.atr_value = 0
def run_backtest(self, data, signals):
"""运行回测"""
# 创建回测数据框架
portfolio = pd.DataFrame(index=data.index, columns=['Capital', 'Positions', 'TotalValue'])
portfolio['Capital'] = float(self.initial_capital)
portfolio['Positions'] = 0
portfolio['TotalValue'] = self.initial_capital
# 回测循环
for i in range(1, len(data)):
current_date = data.index[i]
prev_date = data.index[i - 1]
current_data = data.iloc[i]
current_price = current_data['close']
current_signal = signals.iloc[i]
current_atr = current_data['atr']
# 获取前一天的资金和持仓
prev_capital = portfolio.loc[prev_date, 'Capital']
prev_positions = portfolio.loc[prev_date, 'Positions']
# 执行交易逻辑
if prev_positions == 0:
self._handle_no_position(
portfolio, current_date, prev_capital,
current_signal, current_price, current_atr
)
else:
self._handle_with_position(
portfolio, current_date, prev_capital,
prev_positions, current_price, current_data
)
# 计算总资产
portfolio['TotalValue'] = portfolio['Capital'] + portfolio['Positions'] * data['close']
return portfolio
# 处理无持仓
def _handle_no_position(self, portfolio, current_date, prev_capital,
current_signal, current_price, current_atr):
"""处理无持仓情况"""
current_capital = prev_capital
current_positions = 0
if current_signal == 1: # 买入信号
# 计算头寸单位
unit = calculate_position_unit(prev_capital, current_atr, self.risk_per_trade)
self.atr_value = current_atr
self.entry_price = current_price + 0.5 * self.atr_value # 下一次加仓价格
# 计算购买数量
quantity = unit * 100 # 购买的股数(100股/手)
cost = quantity * current_price * (1 + self.commission) # 总成本
# 判断资金是否足够
if prev_capital >= cost:
current_positions = quantity
current_capital = prev_capital - cost
self.max_add_positions -= 1 # 第一次建仓
self.unit = unit
else:
# 资金不足,无法建仓
self.max_add_positions = 0 # 无法再加仓
# 更新组合状态
portfolio.loc[current_date, 'Capital'] = current_capital
portfolio.loc[current_date, 'Positions'] = current_positions
# 处理有持仓
def _handle_with_position(self, portfolio, current_date, prev_capital,
prev_positions, current_price, current_data):
"""处理有持仓情况"""
current_capital = prev_capital # 剩余资金
current_positions = prev_positions # 持仓数量
# 止损条件: 当前价格 <= 持仓价格 - ATR
if current_price <= self.entry_price - self.atr_value:
# 清仓最近一个单位头寸的持仓
sell_quantity = self.unit * 100 # 清仓数量
current_positions -= sell_quantity # 当前持仓数量
revenue = sell_quantity * current_price * (1 - self.commission)
current_capital += revenue # 当前剩余资金
self.max_add_positions += 1 # 增加加仓次数
self.entry_price -= 0.5 * self.atr_value # 回退上一次的加仓价格
# 止盈条件: 当前价格 <= 10日最低点
elif current_price <= current_data['10_low']:
# 清仓所有头寸
sell_quantity = current_positions
revenue = sell_quantity * current_price * (1 - self.commission)
current_capital += revenue
current_positions = 0
self.max_add_positions = 4 # 重置加仓计数
# 加仓条件: 当前价格 >= 加仓价格 且 还有加仓次数
elif current_price >= self.entry_price and self.max_add_positions > 0:
# 计算加仓数量
add_quantity = self.unit * 100 # 加仓股数
cost = add_quantity * current_price * (1 + self.commission) # 加仓成本
self.entry_price += 0.5 * self.atr_value # 更新下一次加仓价格
current_positions += add_quantity
# 判断资金是否足够
if prev_capital >= cost:
current_capital = prev_capital - cost
self.max_add_positions -= 1 # 增加加仓计数
else:
# 资金不足,无法加仓
self.max_add_positions = 0
# 更新组合状态
portfolio.loc[current_date, 'Capital'] = current_capital
portfolio.loc[current_date, 'Positions'] = current_positions
七、项目运行效果



1803

被折叠的 条评论
为什么被折叠?



