From e36c9baa0eda3cb6cf59d6410e49dad500f174a3 Mon Sep 17 00:00:00 2001 From: gyz Date: Thu, 3 Apr 2025 17:26:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E7=A1=80=E5=9F=BA=E6=9C=AC=E4=B8=8A?= =?UTF-8?q?=E5=86=99=E5=AE=8C=E4=BA=86=EF=BC=8C=E5=BC=80=E5=A7=8B=E5=86=99?= =?UTF-8?q?=E7=AA=81=E7=A0=B4=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TurtleOnTime.py | 170 ++++++++++++++++----- __pycache__/mysql_database.cpython-311.pyc | Bin 0 -> 9987 bytes __pycache__/stock_database.cpython-311.pyc | Bin 0 -> 2867 bytes mysql_database.py | 9 +- 4 files changed, 142 insertions(+), 37 deletions(-) create mode 100644 __pycache__/mysql_database.cpython-311.pyc create mode 100644 __pycache__/stock_database.cpython-311.pyc diff --git a/TurtleOnTime.py b/TurtleOnTime.py index 76ae723..422cbe5 100644 --- a/TurtleOnTime.py +++ b/TurtleOnTime.py @@ -39,8 +39,16 @@ class TurtleTrading(object): Args: object (_type_): _description_ """ - def __init__(self, TradeCode) -> None: + def __init__(self, TradeCode, type, riskcoe, Capital, cash) -> None: 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 = [] # 交易记录 def GetRecentData(self): """获取某个标的的最近数据,从两年前到今天, 计算后的数据保存在self.CurrentData @@ -131,7 +139,8 @@ class TurtleTrading(object): """ Donchian = pd.DataFrame() # 创建一个空的DataFrame用于存储唐奇安通道数据 # 计算最高价和最低价的N日移动平均线 - Donchian['Upper'] = self.CurrentData['最高'].rolling(n).max() # 使用rolling函数计算n日最高价的移动最大值 + name = 'Donchian_' + str(n) + '_upper' + Donchian[name] = self.CurrentData['最高'].rolling(n).max() # 使用rolling函数计算n日最高价的移动最大值 # # 计算中间线 # Donchian['Middle'] = (self.Donchian['Upper'] + self.Donchian['Lower']) / 2 # 计算上通道和下通道的中间线,但此行代码被注释掉了 @@ -151,13 +160,29 @@ class TurtleTrading(object): """ Donchian = pd.DataFrame() # 计算最高价和最低价的N日移动平均线 - Donchian['lower'] = self.CurrentData['最低'].rolling(n).min() + name = 'Donchian_' + str(n) + '_lower' + Donchian[name] = self.CurrentData['最低'].rolling(n).min() # # 计算中间线 # 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) + def get_ready(self, days): """创建一个turtle对象,获取数据,计算ATR,计算唐奇安通道 @@ -165,37 +190,82 @@ class TurtleTrading(object): days (_type_): _description_ n (_type_): _description_ """ - # 检查mysql数据库中是否存在该股票的数据 或者数据库最后一条的时间距离今天是否两天以上 - current_date = date.today() - threshold_date = current_date - timedelta(days=2) - last_update = mysql_database.check_db_table_last_date(f"{self.TradeCode}") - if not mysql_database.check_db_table(f"{self.TradeCode}") or last_update < threshold_date: - # 如果不存在,则从akshare获取数据并保存到mysql数据库 - if mysql_database.check_db_table(f"{self.TradeCode}") and last_update < threshold_date: - mysql_database.delete_table(f"{self.TradeCode}") - + # if 不存在database + if not mysql_database.check_db_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}") - - - # 计算ATR - self.CalATR(self.CurrentData, 20) - # 计算唐奇安通道 - self.Donchian_up = self.calculate_donchian_channel_up(20) - self.Donchian_down = 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_up, self.Donchian_down], axis=1) - Code = f"{self.TradeCode}" - mysql_database.insert_db(self.CurrentData, Code, True, "日期") + # 检查数据库最后一条的时间距离今天是否两天以上 + 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}") + + + def CalPositionSize(self): + """根据风险系数、ATR,计算仓位大小, 存于self.IntPositionSize + """ + PositionSize = self.riskcoe * self.Capital /(self.N) # 默认用股票形式了 100 + self.IntPositionSize = int(PositionSize // 100) * 100 + + def system1EnterNormal(self, PriceNow, TempDonchian20Upper, BreakOutLog): + # 没有持仓且价格向上突破---此时包含两种情形:1 对某标的首次使用系统,2 已发生过突破,此时上次突破天然是失败的 + if self.TrigerTime == 0 and PriceNow > TempDonchian20Upper[-1]: + # 买入 + return True + elif self.TrigerTime != 0 and PriceNow > TempDonchian20Upper[-1]: + 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 + + 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' # 一天结束,计算ATR,计算唐奇安通道,追加到已有的mysql数据库中 + + def system1Out(self, PriceNow, TempDonchian10Lower): + # 退出:低于20日最低价(多头方向),空头以突破20日最高价为止损价格--有持仓且价格向下突破 + if self.TrigerTime != 0 and PriceNow < TempDonchian10Lower[-1]: + # 退出 + return True + + else: + return False + def day_end(self): pass @@ -207,13 +277,14 @@ class TurtleTrading_OnTime(object): 3、实时监测主流程 ''' - def __init__(self): - pass + def __init__(self, turtle: TurtleTrading): + self.turtle = turtle + def get_stocks_data(self): """获取实时股票、基金数据,不保存 """ - stock_zh_a_spot_df = ak.stock_zh_a_spot_em() - stock_zh_a_spot_df = stock_zh_a_spot_df.dropna(subset=['最新价']) + stock_data = ak.stock_zh_a_spot_em() + stock_data = stock_data.dropna(subset=['最新价']) # # 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") @@ -224,14 +295,43 @@ class TurtleTrading_OnTime(object): 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 + def Start_S1_system(self): + """启动S1系统 + """ + + # ------------------准备阶段-------------------- + # 获取数据或读取数据 -- 计算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] + + # ------------------实时监测阶段-------------------- + # 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() + + # 根据type,code, 取得实时价格self.turtle.PriceNow + + + + + # ------------------结束阶段-------------------- + # 数据库更新当天数据,增加ATR、donchian数据 + pass if __name__ == '__main__': - # t = TurtleTrading('513300') + t = TurtleTrading('513300', "etf", 0.25, 100000, 200000) # t.get_ready(100) - a = TurtleTrading_OnTime() - a.get_stocks_data() + a = TurtleTrading_OnTime(t) + a.Start_S1_system() # # 全是股票 # stock_zh_a_spot_df = ak.stock_zh_a_spot_em() diff --git a/__pycache__/mysql_database.cpython-311.pyc b/__pycache__/mysql_database.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..607f955fe2d960efeb8e031a9beb13e158a9bb65 GIT binary patch literal 9987 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 literal 0 HcmV?d00001 diff --git a/mysql_database.py b/mysql_database.py index 672fa64..f7aeaaa 100644 --- a/mysql_database.py +++ b/mysql_database.py @@ -22,7 +22,7 @@ MYSQL_HOST = os.environ.get('MYSQL_HOST') if (os.environ.get('MYSQL_HOST') != No MYSQL_USER = os.environ.get('MYSQL_USER') if (os.environ.get('MYSQL_USER') != None) else "root" MYSQL_PWD = os.environ.get('MYSQL_PWD') if (os.environ.get('MYSQL_PWD') != None) else "1212" MYSQL_DB = os.environ.get('MYSQL_DB') if (os.environ.get('MYSQL_DB') != None) else "stock_data" -MYSQL_PORT = os.environ.get('MYSQL_PORT') if (os.environ.get('MYSQL_PORT') != None) else "3306" +MYSQL_PORT = os.environ.get('MYSQL_PORT') if (os.environ.get('MYSQL_PORT') != None) else "3307" print("MYSQL_HOST :", MYSQL_HOST, ",MYSQL_USER :", MYSQL_USER, ",MYSQL_DB :", MYSQL_DB) MYSQL_CONN_URL = "mysql+mysqldb://" + MYSQL_USER + ":" + MYSQL_PWD + "@" + MYSQL_HOST + ":" + MYSQL_PORT + "/" + MYSQL_DB + "?charset=utf8mb4" @@ -45,7 +45,12 @@ def engine_to_db(to_db): # 通过数据库链接 engine。 def conn(): try: - db = MySQLdb.connect(MYSQL_HOST, MYSQL_USER, MYSQL_PWD, MYSQL_DB, charset="utf8") + db = MySQLdb.connect(host=MYSQL_HOST, + user=MYSQL_USER, + passwd=MYSQL_PWD, + db=MYSQL_DB, + port=int(MYSQL_PORT), # 确保转换为整数 + charset="utf8") # db.autocommit = True except Exception as e: print("conn error :", e)