TurtleTrade/TurtleOnTime.py

417 lines
16 KiB
Python
Raw Normal View History

2025-04-02 22:55:29 +08:00
import numpy as np
import math
import akshare as ak
import os
from datetime import datetime, timedelta, date
import pandas as pd
import mplfinance as mpf
import sqlite3
import stock_database
import mysql_database
def calc_sma_atr_pd(kdf,period):
"""计算TR与ATR
Args:
kdf (_type_): 历史数据
period (_type_): ATR周期
Returns:
_type_: 返回kdf增加TR与ATR列
"""
kdf['最高'] = kdf['最高'].astype(float)
kdf['最低'] = kdf['最低'].astype(float)
kdf['收盘'] = kdf['收盘'].astype(float)
kdf['HL'] = kdf['最高'] - kdf['最低']
kdf['HC'] = np.abs(kdf['最高'] - kdf['收盘'].shift(1))
kdf['LC'] = np.abs(kdf['最低'] - kdf['收盘'].shift(1))
kdf['TR'] = np.round(kdf[['HL','HC','LC']].max(axis=1), 3)
# ranges = pd.concat([high_low, high_close, low_close], axis=1)
# true_range = np.max(ranges, axis=1)
kdf['ATR'] = np.round(kdf['TR'].rolling(period).mean(), 3)
return kdf.drop(['HL','HC','LC'], axis = 1)
class TurtleTrading(object):
"""对象范围较小对某一个标的创建一个海龟如513300
计算ATR唐奇安通道线
基础数据
Args:
object (_type_): _description_
"""
def __init__(self, TradeCode, type, riskcoe, Capital, cash) -> None:
2025-04-02 22:55:29 +08:00
self.TradeCode = TradeCode
self.type = type
self.riskcoe = riskcoe
self.Capital = Capital
self.cash = cash
self.TrigerTime = 0
self.BuyStates = [[0, None, None, 0, 0, self.cash]]
self.tradeslog = [] # 交易记录
2025-04-02 22:55:29 +08:00
def GetRecentData(self):
"""获取某个标的的最近数据,从两年前到今天, 计算后的数据保存在self.CurrentData
Returns:
_type_: _description_
"""
Today = datetime.today()
# print(Today)
formatted_date = Today.strftime("%Y%m%d")
two_years_ago = (date.today() - timedelta(days=365*2)).strftime("%Y%m%d")
# print(formatted_date)
Code = f"{self.TradeCode}"
CurrentData = ak.fund_etf_hist_em(symbol=Code, period="daily", start_date=two_years_ago, end_date=formatted_date, adjust="")
# 将日期列转换为datetime
CurrentData = pd.DataFrame(CurrentData)
CurrentData['日期'] = pd.to_datetime(CurrentData['日期'])
# print(type(CurrentData['日期'].iloc[0]))
CurrentData.set_index('日期', inplace=True)
# CurrentData.reset_index(inplace=True)
# print(type(CurrentData['日期'].iloc[0]))
# create table
# stock_database.create_table(Code)
# stock_database.insert_data(Code, CurrentData)
# mysql_database.insert_db(CurrentData, Code, True, "'日期'")
self.CurrentData = CurrentData
# return self.CurrentData
def CalATR(self, data, ATRday):
"""计算某个标的的ATR从上市日到今天, 计算后的数据保存在self.CurrentData
Args:
ATRday: 多少日ATR
SaveOrNot (_type_): 是否保存.csv数据
"""
self.CurrentData = calc_sma_atr_pd(data, ATRday)
self.N = self.CurrentData['ATR']
# return self.N
def ReadExistData(self, data):
"""除了通过发请求获取数据也可以读本地的数据库赋值给self.CurrentData
Args:
data (_type_): 本地csv名称
"""
self.CurrentData = pd.read_csv(data)
def DrawKLine(self, days):
"""画出k线图看看,画出最近days天的K线图
"""
# 日期部分
# dates = pd.to_datetime(self.CurrentData['日期'][-days:])
# # Klinedf['Data'] = pd.to_datetime(self.CurrentData['日期'])
Klinedf = pd.DataFrame()
# Klinedf.set_index = Klinedf['Data']
# 其他数据
Klinedf['Date'] = self.CurrentData['日期'][-days:]
Klinedf['Open'] = self.CurrentData['开盘'][-days:].astype(float)
Klinedf['High'] = self.CurrentData['最高'][-days:].astype(float)
Klinedf['Low'] = self.CurrentData['最低'][-days:].astype(float)
Klinedf['Close'] = self.CurrentData['收盘'][-days:].astype(float)
Klinedf['Volume'] = self.CurrentData['成交量'][-days:].astype(float)
Klinedf.set_index(pd.to_datetime(Klinedf['Date']), inplace=True)
# 画图
mpf.plot(Klinedf, type='candle', style='yahoo', volume=False, mav=(5,), addplot=[mpf.make_addplot(self.Donchian_up['Upper'][-days:]), mpf.make_addplot(self.Donchian_down['lower'][-days:])], title=f"{self.TradeCode} K线图")
def calculate_donchian_channel_up(self, n):
"""
计算n日唐奇安上通道
参数:
self.CurrentData (DataFrame): 包含价格数据的Pandas DataFrame包含"High"
n (int): 时间周期
返回:self.Donchian
DataFrame: 唐奇安通道的DataFrame包含"Upper"
"""
Donchian = pd.DataFrame() # 创建一个空的DataFrame用于存储唐奇安通道数据
# 计算最高价和最低价的N日移动平均线
name = 'Donchian_' + str(n) + '_upper'
Donchian[name] = self.CurrentData['最高'].rolling(n).max() # 使用rolling函数计算n日最高价的移动最大值
2025-04-02 22:55:29 +08:00
# # 计算中间线
# Donchian['Middle'] = (self.Donchian['Upper'] + self.Donchian['Lower']) / 2 # 计算上通道和下通道的中间线,但此行代码被注释掉了
return Donchian # 返回包含唐奇安上通道的DataFrame
def calculate_donchian_channel_down(self, n):
"""
计算n日唐奇安上通道
参数:
self.CurrentData (DataFrame): 包含价格数据的Pandas DataFrame包含"High"
n (int): 时间周期
返回:self.Donchian
DataFrame: 唐奇安通道的DataFrame包含"Upper"
"""
Donchian = pd.DataFrame()
# 计算最高价和最低价的N日移动平均线
name = 'Donchian_' + str(n) + '_lower'
Donchian[name] = self.CurrentData['最低'].rolling(n).min()
2025-04-02 22:55:29 +08:00
# # 计算中间线
# Donchian['Middle'] = (self.Donchian['Upper'] + self.Donchian['Lower']) / 2
return Donchian
def calc_atr_donchian_short(self):
"""计算ATR、短期唐奇安通道
"""
# 计算ATR
self.CalATR(self.CurrentData, 20)
# 计算唐奇安通道
self.Donchian_20_ups = self.calculate_donchian_channel_up(20)
self.Donchian_50_ups = self.calculate_donchian_channel_up(50)
self.Donchian_downs = self.calculate_donchian_channel_down(10)
# 画图
# self.DrawKLine(days)
# 把self.N, self.Donchian_up, self.Donchian_down, 添加到self.CurrentData后面保存到mysql数据库
self.CurrentData = pd.concat([self.CurrentData, self.Donchian_20_ups, self.Donchian_50_ups, self.Donchian_downs], axis=1)
2025-04-02 22:55:29 +08:00
def get_ready(self, days):
"""创建一个turtle对象获取数据计算ATR计算唐奇安通道
Args:
days (_type_): _description_
n (_type_): _description_
"""
# if 不存在database
if not mysql_database.check_db_table(f"{self.TradeCode}"):
2025-04-02 22:55:29 +08:00
self.GetRecentData()
self.calc_atr_donchian_short()
Code = f"{self.TradeCode}"
mysql_database.insert_db(self.CurrentData, Code, True, "日期")
else:
# 检查数据库最后一条的时间距离今天是否两天以上
current_date = date.today()
threshold_date = current_date - timedelta(days=2)
last_update = mysql_database.check_db_table_last_date(f"{self.TradeCode}")
if last_update < threshold_date:
# 如果不存在则从akshare获取数据并保存到mysql数据库
mysql_database.delete_table(f"{self.TradeCode}")
self.GetRecentData()
self.calc_atr_donchian_short()
Code = f"{self.TradeCode}"
mysql_database.insert_db(self.CurrentData, Code, True, "日期")
else:
# 如果存在则从mysql数据库中读取数据
self.CurrentData = mysql_database.fetch_all_data(f"{self.TradeCode}")
2025-04-02 22:55:29 +08:00
def CalPositionSize(self):
"""根据风险系数、ATR计算仓位大小, 存于self.IntPositionSize
"""
PositionSize = self.riskcoe * self.Capital /(self.N) # 默认用股票形式了 100
self.IntPositionSize = int(PositionSize // 100) * 100
2025-04-02 22:55:29 +08:00
def system1EnterNormal(self, PriceNow, TempDonchian20Upper, BreakOutLog):
# 没有持仓且价格向上突破---此时包含两种情形1 对某标的首次使用系统2 已发生过突破,此时上次突破天然是失败的
2025-04-15 23:06:41 +08:00
if self.TrigerTime == 0 and PriceNow > TempDonchian20Upper:
# 买入
return True
2025-04-15 23:06:41 +08:00
elif PriceNow > TempDonchian20Upper:#todo !=0不会满足条件 先跳过
self.system1BreakoutValid(PriceNow)
if BreakOutLog[-1][5] == 'Lose': # TT!= 0且突破且上一次突破unseccessful
return True
else:
return False
else:
return False
def system1EnterSafe(self, PriceNow, TempDonchian55Upper):
if PriceNow > TempDonchian55Upper[-1]: # 保底的55日突破
return True
else:
return False
2025-04-02 22:55:29 +08:00
def system1BreakoutValid(self, priceNow):
"""判断前一次突破是否成功是log[-1][5]写入“win”否则写入“Lose”
"""
if priceNow < self.BreakOutLog[-1][3]:
self.BreakOutLog[-1][5] = 'Lose'
else:
self.BreakOutLog[-1][5] = 'None'
2025-04-02 22:55:29 +08:00
# 一天结束计算ATR计算唐奇安通道追加到已有的mysql数据库中
2025-04-15 23:06:41 +08:00
def system_1_Out(self, PriceNow, TempDonchian10Lower):
# 退出:低于20日最低价多头方向,空头以突破20日最高价为止损价格--有持仓且价格向下突破
2025-04-15 23:06:41 +08:00
if self.TrigerTime != 0 and PriceNow < TempDonchian10Lower:
# 退出
return True
else:
return False
2025-04-15 23:06:41 +08:00
def add(self, PriceNow):
"""加仓
"""
if self.TrigerTime < 4 and PriceNow > self.BuyStates[self.TrigerTime - 1][2]:#todo BuyStates是空的
# 买入
return True
else:
return False
def system_1_stop(self, PriceNow):
"""止损判断:如果当前价格<上一次买入后的止损价格则止损
"""
if PriceNow < self.BuyStates[self.TrigerTime - 1][3]:
# 买入
return True
else:
return False
2025-04-02 22:55:29 +08:00
def day_end(self):
pass
class TurtleTrading_OnTime(object):
''' 实时监测主程序可以处理多个turtle
1获取实时大盘数据
2根据turtles的代码比较是否触发条件
3实时监测主流程
'''
def __init__(self, turtle: TurtleTrading):
self.turtle = turtle
2025-04-02 22:55:29 +08:00
def get_stocks_data(self):
"""获取实时股票、基金数据,不保存
"""
stock_data = ak.stock_zh_a_spot_em()
stock_data = stock_data.dropna(subset=['最新价'])
2025-04-02 22:55:29 +08:00
# # print(stock_zh_a_spot_df)
# # stock_zh_a_spot_df第一列加上时间精确到分钟
# stock_zh_a_spot_df['时间'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# mysql_database.insert_db(stock_zh_a_spot_df, "stock_price", True, "代码")
# etf_data = ak.fund_etf_spot_em()
etf_data = ak.fund_etf_spot_ths()
etf_data = etf_data.dropna(subset=['当前-单位净值'])
# etf_data['时间'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# mysql_database.insert_db(etf_data, "etf_price", True, "代码")
return stock_data, etf_data
2025-04-15 23:06:41 +08:00
def Start_short_system(self):
"""启动short系统
"""
# ------------------准备阶段--------------------
# 获取数据或读取数据 -- 计算ATR Donchian 20 50 up, 20 down
self.turtle.get_ready(100)
self.turtle.N = self.turtle.CurrentData['ATR'].iloc[-1]
self.turtle.Donchian_20_up = self.turtle.CurrentData['Donchian_20_upper'].iloc[-1]
self.turtle.Donchian_50_up = self.turtle.CurrentData['Donchian_50_upper'].iloc[-1]
self.turtle.Donchian_10_down = self.turtle.CurrentData['Donchian_10_lower'].iloc[-1]
2025-04-15 23:06:41 +08:00
self.turtle.CalPositionSize()
# ------------------实时监测阶段--------------------
# 9:00 1、判断是否是新的一周是则重新计算Position Size
# 判断是否是新的一周
if datetime.now().weekday() == 0:
self.turtle.CalPositionSize()
# 每分钟获取一次数据,判断是否触发条件 9:30-11:30 13:00-15:00
stock_data, etf_data = self.get_stocks_data()
# 根据typecode, 取得实时价格self.turtle.PriceNow
2025-04-02 22:55:29 +08:00
2025-04-15 23:06:41 +08:00
if self.turtle.Type == "stock":
self.turtle.PriceNow = stock_data[stock_data['代码'] == self.turtle.TradeCode]['最新价'].iloc[-1]
elif self.turtle.Type == "etf":
self.turtle.PriceNow = etf_data[etf_data['代码'] == self.turtle.TradeCode]['当前-单位净值'].iloc[-1]
2025-04-15 23:06:41 +08:00
# 空仓
if self.turtle.TrigerTime == 0:
if self.turtle.system1EnterNormal(self.turtle.PriceNow, self.turtle.Donchian_20_up, self.turtle.BreakOutLog):
# 发出买入指令
pass
elif self.turtle.system1EnterSafe(self.turtle.PriceNow, self.turtle.Donchian_50_up):
# 发出买入指令
pass
2025-04-15 23:06:41 +08:00
# 已有仓位,加仓 / 止损 / 退出
elif 1<=self.turtle.TrigerTime <= 3:
# ---------------------加仓---------------------
# 继续突破
if self.turtle.system1EnterNormal(self.turtle.PriceNow, self.turtle.Donchian_20_up, self.turtle.BreakOutLog):
# 发出买入指令
pass
#
elif self.turtle.system1EnterSafe(self.turtle.PriceNow, self.turtle.Donchian_50_up):
# 发出买入指令
pass
# 触发加仓价格
elif self.turtle.add(self.turtle.PriceNow):
# 发出买入指令
pass
# ---------------------止损-------------------
elif self.turtle.system_1_stop(self.turtle.PriceNow):
# 发出卖出指令
pass
# ---------------------止盈退出---------------------
elif self.turtle.system_1_Out(self.turtle.PriceNow, self.turtle.Donchian_10_down):
# 发出卖出指令
pass
2025-04-15 23:06:41 +08:00
# 满仓 止损 / 退出
elif self.turtle.TrigerTime == 4:
# ---------------------止损-------------------
if self.turtle.system_1_stop(self.turtle.PriceNow):
# 发出卖出指令
pass
# ---------------------止盈退出---------------------
elif self.turtle.system_1_Out(self.turtle.PriceNow, self.turtle.Donchian_10_down):
# 发出卖出指令
pass
# ------------------结束阶段--------------------
# 数据库更新当天数据增加ATR、donchian数据
pass
2025-04-02 22:55:29 +08:00
if __name__ == '__main__':
t = TurtleTrading('513300', "etf", 0.25, 100000, 200000)
2025-04-02 22:55:29 +08:00
# t.get_ready(100)
a = TurtleTrading_OnTime(t)
a.Start_S1_system()
2025-04-02 22:55:29 +08:00
# # 全是股票
# stock_zh_a_spot_df = ak.stock_zh_a_spot_em()
# # stock_zh_a_spot_df.to_csv("stock_zh_a_spot.txt", sep="\t", index=False, encoding="utf-8")
# stock_zh_a_spot_df = stock_zh_a_spot_df.dropna(subset=['最新价'])
# print(stock_zh_a_spot_df)
# # 全是基金
# etf_data = ak.fund_etf_spot_em()
# etf_data = etf_data.dropna(subset=['最新价'])
# etf_data.to_csv("fund_etf_spot.txt", sep="\t", index=False, encoding="utf-8")
# print(etf_data)