This repository has been archived on 2026-01-07. You can view files and clone it, but cannot push or open issues or pull requests.
Files
resources/stockapp/src/test_hs300_quant.py
2024-12-21 09:32:01 +08:00

146 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import pandas as pd
import pymysql
import backtrader as bt
import config
# 从 MySQL 提取数据
def fetch_data_from_mysql():
connection = pymysql.connect(**config.db_config)
# ['datetime', 'open', 'high', 'low', 'close', 'volume', 'openinterest']]
#query = "SELECT code, time_key, qfq_close as close FROM hs300_ajust_kline_202410 where time_key>='2023-01-01 00:00:00' "
query = "SELECT code, time_key, open, high, low, close, volume FROM hs300_his_kline_none where time_key>='2023-01-01 00:00:00' "
data = pd.read_sql(query, connection)
data['time_key'] = pd.to_datetime(data['time_key'], errors='coerce') # 确保为datetime格式
data['openinterest'] = 0 # 添加 openinterest 列并赋值为 0
connection.close()
return data
# 格式化数据为Backtrader兼容的格式
data = fetch_data_from_mysql()
data_by_code = {code: df for code, df in data.groupby('code')}
class BestPortfolioStrategy(bt.Strategy):
params = dict(
max_stocks=30,
rebalance_monthly=True,
commission=0.0003,
slippage=0.005,
maperiod = 15,
printlog = False
)
def __init__(self):
self.stocks = [] # 持有的股票
self.rebalance_date = None
def next(self):
# 判断是否是月初
if self.rebalance_date and self.data.datetime.date(0) < self.rebalance_date.date():
return
print(f"rebalance date: {self.rebalance_date}")
# 设置下次调仓日
self.rebalance_date = self.data.datetime.date(0).replace(day=1) + pd.DateOffset(months=1)
# 选股逻辑计算过去一段时间的收益率选取最高的30只股票
returns = {}
for data in self.datas:
returns[data._name] = (data.close[0] / data.close[-20]) - 1 # 上个月的收益率
# 按收益率排序并选择最佳组合
sorted_stocks = sorted(returns, key=returns.get, reverse=True)[:self.params.max_stocks]
print(sorted_stocks)
# 调仓:卖出非选中股票,买入新选股票
self.rebalance(sorted_stocks)
def rebalance(self, selected_stocks):
for stock in self.stocks:
if stock not in selected_stocks:
self.close(self.getdatabyname(stock))
for stock in selected_stocks:
if stock not in self.stocks:
self.buy(self.getdatabyname(stock), size=100)
self.stocks = selected_stocks
# 交易记录日志(可省略,默认不输出结果)
def log(self, txt, dt=None, doprint=False):
if self.params.printlog or doprint:
dt = dt or self.datas[0].datetime.date(0)
print(f'{dt.isoformat()},{txt}')
def notify_order(self, order):
# 未被处理的订单
if order.status in [order.Submitted, order.Accepted]:
return
# 已经处理的订单
if order.status in [order.Completed, order.Canceled, order.Margin]:
if order.isbuy():
self.log(
'BUY EXECUTED, ref:%.0fPrice: %.2f, Cost: %.2f, Comm %.2f, Size: %.2f, Stock: %s' %
(order.ref, # 订单编号
order.executed.price, # 成交价
order.executed.value, # 成交额
order.executed.comm, # 佣金
order.executed.size, # 成交量
order.data._name)) # 股票名称
else: # Sell
self.log('SELL EXECUTED, ref:%.0f, Price: %.2f, Cost: %.2f, Comm %.2f, Size: %.2f, Stock: %s' %
(order.ref,
order.executed.price,
order.executed.value,
order.executed.comm,
order.executed.size,
order.data._name))
cerebro = bt.Cerebro()
cerebro.broker.setcash(1_000_000) # 初始资金 100万
cerebro.broker.setcommission(commission=0.0003) # 设置交易费率 万分之三
cerebro.broker.set_slippage_perc(0.005) # 设置滑点 0.005
# 加载数据
for code, df in data_by_code.items():
# 删除缺失值
df = df.dropna(subset=['time_key'])
# 设置DataFrame为Backtrader的数据格式
data_feed = bt.feeds.PandasData(
dataname=df,
datetime='time_key',
openinterest=None # 如果没有openinterest列则为None
)
#bt_data = bt.feeds.PandasData(dataname=df)
cerebro.adddata(data_feed, name=code)
cerebro.addanalyzer(bt.analyzers.TimeReturn, _name='pnl') # 返回收益率时序数据
cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='_AnnualReturn') # 年化收益率
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='_SharpeRatio') # 夏普比率
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='_DrawDown') # 回撤
# 加载策略
cerebro.addstrategy(BestPortfolioStrategy, printlog=True)
# 运行回测
results = cerebro.run()
# 获取分析结果
results = cerebro.run()
strat = results[0]
# 返回日度收益率序列
daily_return = pd.Series(strat.analyzers.pnl.get_analysis())
# 打印评价指标
print("--------------- AnnualReturn -----------------")
print(strat.analyzers._AnnualReturn.get_analysis())
print("--------------- SharpeRatio -----------------")
print(strat.analyzers._SharpeRatio.get_analysis())
print("--------------- DrawDown -----------------")
print(strat.analyzers._DrawDown.get_analysis())