From a8a310ecf07df0c989b564a5fec6bd3eefb50bad Mon Sep 17 00:00:00 2001 From: blade <8019068@qq.com> Date: Sun, 31 Aug 2025 11:20:59 +0800 Subject: [PATCH] support get US STOCK data --- config.py | 17 +- .../__pycache__/market_data.cpython-312.pyc | Bin 13102 -> 16222 bytes core/biz/market_data.py | 77 +++- core/biz/market_data_from_itick.py | 138 ++++++ .../db_market_data.cpython-312.pyc | Bin 16911 -> 16925 bytes core/db/db_market_data.py | 1 + core/statistics/price_volume_stats.py | 8 +- .../ma_break_statistics.cpython-312.pyc | Bin 32078 -> 32078 bytes .../__pycache__/orb_trade.cpython-312.pyc | Bin 0 -> 15826 bytes core/trade/ma_break_statistics.py | 6 +- core/trade/mean_reversion_sandbox.py | 2 +- core/trade/orb_trade.py | 403 ++++++++++++++++++ huge_volume_main.py | 45 +- market_data_from_itick_main.py | 60 +++ market_data_main.py | 99 +++-- market_monitor_main.py | 6 +- orb_trade_main.py | 28 ++ sql/query/sql_playground.sql | 17 +- sql/table/crypto_market_data.sql | 4 + test_ma_methods.py | 2 +- trade_data_main.py | 4 +- trade_ma_strategy_main.py | 2 +- trade_sandbox_main.py | 6 +- 23 files changed, 822 insertions(+), 103 deletions(-) create mode 100644 core/biz/market_data_from_itick.py create mode 100644 core/trade/__pycache__/orb_trade.cpython-312.pyc create mode 100644 core/trade/orb_trade.py create mode 100644 market_data_from_itick_main.py create mode 100644 orb_trade_main.py diff --git a/config.py b/config.py index 58ef29b..835ce7b 100644 --- a/config.py +++ b/config.py @@ -48,10 +48,9 @@ TIME_CONFIG = { "kline_limit": 100, # K线数据条数 } -MONITOR_CONFIG = { +OKX_MONITOR_CONFIG = { "volume_monitor":{ - "symbols": ["XCH-USDT", "BONK-USDT", "PENGU-USDT", - "CFX-USDT", "PUMP-USDT", "SOL-USDT", + "symbols": ["XCH-USDT","SOL-USDT", "BTC-USDT", "ETH-USDT", "DOGE-USDT"], "bars": ["5m", "15m", "30m", "1H"], "initial_date": "2025-05-15 00:00:00" @@ -71,6 +70,14 @@ MONITOR_CONFIG = { } } +US_STOCK_MONITOR_CONFIG = { + "volume_monitor":{ + "symbols": ["QQQ", "TQQQ", "MSFT", "AAPL", "GOOG", "NVDA", "META", "AMZN", "TSLA", "AVGO"], + "bars": ["5m"], + "initial_date": "2015-08-31 00:00:00" + } +} + WINDOW_SIZE = {"window_sizes":[50, 80, 100, 120]} BAR_THRESHOLD = { @@ -92,4 +99,6 @@ MYSQL_CONFIG = { WECHAT_CONFIG = { "key": "11e6f7ac-efa9-418a-904c-9325a9f5d324" -} \ No newline at end of file +} + +ITICK_API_KEY = "dfd4bc0caed148d6bc03b960224754ffb5356349e389431f828702b3a27e8a2b" \ No newline at end of file diff --git a/core/biz/__pycache__/market_data.cpython-312.pyc b/core/biz/__pycache__/market_data.cpython-312.pyc index ebfe61ce632c5c9aca0e7e2707af0dab7e3a577b..7b5991ceb62536af9e53c16f4a87bbc3176226e8 100644 GIT binary patch delta 7094 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 f-zDqs66`8riTPk8ub4Kr~3wQ delta 3798 zcmbVP32dBK5q|IAe|Nq2;k~?<*LT*Ay*?7(b{gU)P0}XRaV|Sfy7j+yePlO%yCF^c zyLQzSS}72xkG2UVkQ6~35>=%}P(dq&LXpd9#}TQvX(>`c2?*pQ2oR!(nSZ_Yx(X8F zt^V(ynK%E;ym|AE{owIe2F*V=ne+tO-@g5|hrU;L((IwfPgd;cq(mYT9Wx)a^jay2 zSx17Xw6gVD1hR=p>SILGTo5vSy?IAzSbK7|lG2m6i+cugzc4Q`x8DNd1rj5@R3g2C zjz~_4##E93j|v_!CJyF-S#_^ka!Fc{w%jJ-k9_bPuR=BGv${g6H;^ z`4hr(cO4~Raa)?!rD^SXk?Ei#ZH^uqK9GzJuvqHAXzVC6pimqfiw=dg%nppA#xTRo zz$I02QCOW8ld-YE+z#wD0wmEne@)eIG9lLmFiCFexnJxQzG#^l6xA9-9SB)HLIeL; z?=DMg1`;tg5IaxPniyjVmSnZidtSvVP-zuHCx1;}-0Vft2aq;EywOzbKx+74?7WeE z(xH6G)}!7^H=csefDl}0hvSFFqTo$PixNv5Vt3DU7&O%B!f%%C_>m3pJ;qJ6m7g`P z(YQg7cJqH5-TbDplormoO#K2a;3-Q;IHa3--qJxe0&CO}nLn&?@{Kl|%^({l-KlKN zkP#<8*_^*te}RH2Xp~F|@jTfe847@tjH6AMKj2NsCo4bwB{g%i%_T|}$;v;lxhH6R zKC3)#Of4BDL^et~#h?u_>ulV#LPP13R%)1Ky`&q3bTgG~8rR3mWxZ?~g(+O(gSi30 zEc0etz%J`wBvQU?0(6{FXT8DDXk6yemY`iW!P;{ z)3Ta1W#%7fgIzKOS6*2K=t~u4Y~rA^_CW5JEr0>p`XV`#lWo}~Ye!*`vzIa+wRiI9 zUiN6q9WMqw5mcNPD$YZSlOov$t{i7{S@#q|=^`iW1Hup)5c;92#_c(ox3A1O1hfyP znjUw`_LIVKk4VOipnr#9Nw(20Sj;S(D&Twa{0`YD=Upt$?pfpcvTme||E@W|I)6Em zcwfBtzuEd9O!;z6(ReY;WXYqj4~(l*Dt_JN7o<{N<8d2=(?N6IGh}7^B%?wj^j;zJnv8{%rtjP`ss>I1B z3!F9=NY^e)l^PF38gpjNnT-(Ytm%v@%b^7+qNJdO&jo!33^l{IA+IXP*lK9bso56U z16Eq)0$3P%xt4a2PuF@=Th4dwxhwl99S|tg_PG5BDIqD`o|RTgSjo79KW#0Hl*tb8 zAS`>>3n%fo<8j9nJ^o&XIzo?-M^t;s5gP88(S*KB4I!|?u<}_kf4eB6i6B|Zt;N+E z3z8*#Lvb^oEbebB1Ga~iqcDt6iBN^G8lf7Y2H-HBNPqb1#p`dra{Wu+|NV*Q*=Al@ zvRT&x;v2YM@;6JWH#GynaFb?j2snkT0|6_BtwXpAVLig~0fEN}+kk+F9NWmhP&(0i z50YB|!a=qb>3b1y=VQGHB7$NU4`y~B!Zv_(DgQ-dX&LKBS~V0)vCm@H{RrI%I}myh zc5+@;-n9$K-3WUS_9FBl>;p)v4i3iwqK|~VZ24S!2>bE$WB~*}ca?|WaBC_r^s)UY zIe_p0LKNXagaQ6cx!)-PnWnL{APuq@iU$GsZ_5KBCWO-pufW{AsG>`Yqe=czbVl!ay7VfKDcvkS8O3TIA!3f%6p0~=ZCMARlL0a-2PX$Tm>^u0A zHJv5?;Hk$>Z@=n~%=;q?{<^2{d)Mnf)pt5_)muI9tzPg(p5DsyBinURN(%ewKmJ8@2>u2ln_nc>zMORYw5;VMvSv1h4*kuT*{^?r98$gn!N(H&zM1Br~@f-w=wP#lDphv*$o|1FytN#J_HF^_)Vj|1WHI8os?lJllCoYrSxt z@Pn=I(HHsVwx1f77rl>r+kMo{8{6w^VB021%*-TN6hw>r85P3`2-6<;SD)@Y9g2iga5<^2!rC54tEi%LLp-&C+pLy*E6Vl= start_time] + if self.is_us_stock: + if to_time > start_time: + candles = [candle for candle in candles if int(candle["timestamp"]) >= start_time] + else: + candles = [candle for candle in candles if int(candle[0]) >= start_time] if len(candles) > 0: candles_pd = pd.DataFrame(candles, columns=columns) all_data.append(candles_pd) @@ -141,6 +166,9 @@ class MarketData: else: break else: + if end_time + 1 == from_time or end_time == from_time: + logger.warning(f"本轮获取{symbol}, {bar} 数据最早时间 {from_time_str} 等于 此次数据获取结束时间, 停止获取数据") + break if len(candles) > 0: candles_pd = pd.DataFrame(candles, columns=columns) all_data.append(candles_pd) @@ -161,6 +189,8 @@ class MarketData: df[col] = pd.to_numeric(df[col], errors='coerce') dt_series = pd.to_datetime(df['timestamp'].astype(int), unit='ms', utc=True, errors='coerce').dt.tz_convert('Asia/Shanghai') df['date_time'] = dt_series.dt.strftime('%Y-%m-%d %H:%M:%S') + dt_us_series = pd.to_datetime(df['timestamp'].astype(int), unit='ms', utc=True, errors='coerce').dt.tz_convert('America/New_York') + df['date_time_us'] = dt_us_series.dt.strftime('%Y-%m-%d %H:%M:%S') # 将timestamp转换为整型 df['timestamp'] = df['timestamp'].astype(int) # 添加虚拟货币名称列,内容为symbol @@ -169,14 +199,35 @@ class MarketData: df['bar'] = bar df['create_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + if self.is_us_stock: + # 如果是美股数据,则仅保留date_time_us字中,在开盘时间内的数据,即开盘时间为美国时间9:30到16:00 + # 请将date_time_us转换为datetime对象,然后判断是否在开盘时间内的数据,如果是,则保留,否则删除 + df['date_time_us'] = pd.to_datetime(df['date_time_us'], errors='coerce') + # 使用 .loc 避免 SettingWithCopyWarning + mask = (df['date_time_us'].dt.hour >= 9) & (df['date_time_us'].dt.hour <= 16) + df = df.loc[mask].copy() + # 对于9点,只保留9:35与之后的数据 + mask_9 = ~((df['date_time_us'].dt.hour == 9) & (df['date_time_us'].dt.minute <= 30)) + df = df.loc[mask_9].copy() + # 对于16点,只保留16:00之前的数据 + mask_16 = ~((df['date_time_us'].dt.hour == 16) & (df['date_time_us'].dt.minute > 0)) + df = df.loc[mask_16].copy() + # 将date_time_us转换为字符串 + df.loc[:, 'date_time_us'] = df['date_time_us'].dt.strftime('%Y-%m-%d %H:%M:%S') + + # 获取df中date_time的最早时间与最新时间 并保存到df中 - df = df[['symbol', 'bar', 'timestamp', 'date_time', 'open', 'high', 'low', 'close', 'volume', 'volCcy', 'volCCyQuote', 'create_time']] + df = df[['symbol', 'bar', 'timestamp', 'date_time', 'date_time_us', 'open', 'high', 'low', 'close', 'volume', 'volCcy', 'volCCyQuote', 'create_time']] df.sort_values('timestamp', inplace=True) df.reset_index(drop=True, inplace=True) logger.info(f"总计获取 {len(df)} 条 K 线数据(仅confirm=1)") # 获取df中date_time的最早时间与最新时间 - earliest_time = df['date_time'].min() - latest_time = df['date_time'].max() + if self.is_us_stock: + earliest_time = df['date_time_us'].min() + latest_time = df['date_time_us'].max() + else: + earliest_time = df['date_time'].min() + latest_time = df['date_time'].max() logger.info(f"本轮更新{symbol}, {bar} 数据最早时间: {earliest_time}, 最新时间: {latest_time}") return df else: diff --git a/core/biz/market_data_from_itick.py b/core/biz/market_data_from_itick.py new file mode 100644 index 0000000..a26f3d3 --- /dev/null +++ b/core/biz/market_data_from_itick.py @@ -0,0 +1,138 @@ +import requests +import pandas as pd +from datetime import datetime, timedelta +import time +import json +from typing import Optional +import mplfinance as mpf +import random + +from core.utils import transform_date_time_to_timestamp, datetime_to_timestamp, timestamp_to_datetime + +import core.logger as logging +from config import ITICK_API_KEY + +logger = logging.logger + +class MarketDataFromItick: + """用于从 Yahoo Finance 下载 K 线数据的类,支持分段下载和数据处理。""" + + def __init__(self, symbol='QQQ', bar='5m', end_time: str=None, limit: int=1000): + """ + 初始化下载器。 + + 参数: + ticker (str): 股票或 ETF 代码,如 'QQQ'。 + start_date (datetime): 开始日期,默认为 1 年前。 + end_date (datetime): 结束日期,默认为当前日期。 + bar (str): K 线间隔,如 '5m'(5 分钟)。 + segment_days (int): 每次下载的天数,默认为 10 天。 + """ + self.symbol = symbol + self.time_stamp_unit = 0 + if bar == '1m': + self.ktype = '1' + self.time_stamp_unit = 1000 * 60 + elif bar == '5m': + self.ktype = '2' + self.time_stamp_unit = 1000 * 60 * 5 + elif bar == '15m': + self.ktype = '3' + self.time_stamp_unit = 1000 * 60 * 15 + elif bar == '30m': + self.ktype = '4' + self.time_stamp_unit = 1000 * 60 * 30 + elif bar == '1H': + self.ktype = '5' + self.time_stamp_unit = 1000 * 60 * 60 + elif bar == '2H': + self.ktype = '6' + self.time_stamp_unit = 1000 * 60 * 60 * 2 + elif bar == '4H': + self.ktype = '7' + self.time_stamp_unit = 1000 * 60 * 60 * 4 + elif bar == '1D': + self.ktype = '8' + elif bar == '1W': + self.ktype = '9' + elif bar == '1M': + self.ktype = '10' + else: + self.ktype = '2' + self.limit = limit + + # 设置默认时间范围 + if end_time is None: + end_time = int(datetime.now().strftime('%Y-%m-%d %H:%M:%S').timestamp()) + if isinstance(end_time, str): + end_time = int(datetime.strptime(end_time, '%Y-%m-%d %H:%M:%S').timestamp()) + self.end_time = end_time + + self.headers = { + "accept": "application/json", + "token": ITICK_API_KEY + } + # 初始化空 DataFrame + self.data = pd.DataFrame() + + # 重试配置 + self.max_retries = 3 + self.base_delay = 5 # 基础延迟秒数 + self.max_delay = 60 # 最大延迟秒数 + + def get_historical_candlesticks_from_api(self): + response = None + count = 0 + end_time_str = timestamp_to_datetime(self.end_time) + logger.info(f"请求数据: {self.symbol} {self.ktype} {end_time_str} {self.limit}") + while True: + try: + url = f"https://api.itick.org/stock/kline?region=US&code={self.symbol}&kType={self.ktype}&et={self.end_time}&limit={self.limit}" + response = requests.get(url, headers=self.headers) + if response: + response = json.loads(response.text) + if response["code"] != 0: + raise Exception(f"请求出错: {response["msg"]}") + data_list = response["data"] + """ + { + "tu": 1292317671.363, + "c": 560.78, + "t": 1752858600000, + "v": 2305857, + "h": 560.79, + "l": 560.06, + "o": 560.45 + } + """ + result_list = [] + # itick的时间,分钟数据与市面上的分钟数据有1个周期的差距,即itick的时间是市面上的时间的前1个周期 + # 因此需要将itick的时间加上1个周期的单位 + for data in data_list: + result_list.append({ + "timestamp": int(data["t"]) + self.time_stamp_unit, + "open": float(data["o"]), + "high": float(data["h"]), + "low": float(data["l"]), + "close": float(data["c"]), + "volume": int(data["v"]), + "volCcy": 0, + "volCCyQuote": 0, + "confirm": "1" + }) + # result_list按照timestamp降序排列 + result_list = sorted(result_list, key=lambda x: x["timestamp"], reverse=True) + response["data"] = result_list + response["code"] = "0" + response["msg"] = "success" + break + except Exception as e: + count += 1 + logger.error(f"请求出错: {e}, 第{count}次重试,30秒后重试") + if count > 3: + break + time.sleep(30) + logger.info(f"请求成功,等待12秒后返回") + time.sleep(12) + return response + \ No newline at end of file diff --git a/core/db/__pycache__/db_market_data.cpython-312.pyc b/core/db/__pycache__/db_market_data.cpython-312.pyc index cf781406127b68fdfa694dd50a2868126026a2ff..274f19e14d1d611d4afa059d230f8060c7ac71de 100644 GIT binary patch delta 1985 zcmb7_T}&KR6vw%8n-FV+V&#=cad4`X8W*{Jp7#l{EE843c0q-?UkJ@?MJ z=l;*Rd(XVKz%DE>=^IHB^YC-kZ%kdkB55|(4)eAqsVJuCYFLja)$p`td|-Q9DD0>b z&*Yyg`a5NW9Cm^A!jQAJvJcntg9uGP$TAPo#4yY{_pm|uO>A?u-f*^lIQpHljfaez z&Uabmh+ zyVrE_Cn=;2A+M?%ff1>dsW2x6SO{)9--naMe{3sDEvYZROV(j{r^Lr}__Bo5u+sC) z-HTh8;^}FX&xEmf3Z~cxm&$xB2+L(1Y#zjN@;zA2gy2c^J;IM7`7wmIfLnU$B)E%E zL?ADIoG^~i3J{nBeRvSgxQEzzSav_!8p8QQrd`t&uA4SB72`6_@Mc0gp_A|+A}T^@ zsoNOcwP<5w@NbpOWZ3PwpEW}O*$(3#nRUWTo@xhq->!fgT@?b4z!gs=Q{ZdQtV)s> z?WRM?WN4#&9Gdy3((x4UMUy{+@Rn{)CPI%Cf=t*#m?=insaRZ(r&AgqhmWcsX2;>j zYLA2dO=bi5YZ{=gW*>Xc7^&%JSjD$$`-NPTqQwYfi0rPU5{-qm_+&~+$lY?+ip4oO zsDub%f?$c6Bx{NgN8CA%Q)qgNHfLbPh>)wbGD+6>USf)nCS(Y7S{fambxe1Fr!`;K z$$o~Vx@FY!P%!Rx411qOB85!>#m6_`s4XuBt}G zzczL-39kA4FxvDvs&+R^w#Id;aZR=?sf43Et!Y$l%iuE*YN=;gm}sfby+|AGC>w9_ z=4@stMwVbji;#5>DHWq1IJ_lz`R?tYVNDbWJ!4rKPuZ9Ig3T5Ci)=4|D& z=|lpaYIG0p{51IU7(rXB=elyv`hwUluK9k)QeO7|!kRXga_ce~pSA7F&(WR3WX_svg$W=u|2mRT5!MhxGo3*d@5Q|0*`i z%YKi+JC+1CX7~;?GuPTy*{>jTU=DuhI_jXg>=)MXlShsmBOxo7Yc+{=O&_1FBym>< zq$n#%Y}Wymz$i0~j{`Pr2=Hn5iCjM}F@;RbEDT})eT*wfl@C)GdWo2}cuMCZG(1c= zK?oC06Li80gjqt4Fi&^|v6#o{ue0)QbC5+S#IJT8UwaFAw}fu{KoR??wBj#2TI|-G zaj|Dfc)(gkzFyIEWRz4)(fC;$nyzS?tNuiKa#H1d5MJ&JRG-87|F^mA3?$RB>4e(D V-+=G?TFQb>Hp>3YvwkeM{{V9At+xOG delta 2048 zcma)-T})eL7{~jb(o$N=XA#=s=&($>QDh?wh4N9lO~h<2h&txtY1bZ0q3tQ}Ia47U zpy6T}Q)V8soB6S*F>dh}+?ldY2ZhSu(Q`u)BS%0d~2!F+U6;+aZM`op8qO@zUzM)oLmuq1#N(*W2Uk zGB)5MTyxj5C|q}+k|=;Bi*AkOa>ieiSZy_OMa-!{fHnd!{!+$lC}(?{0Qc6TK0JIsKqMA~=qd%Q#^K?rifqCaIYq zh!74|OG#>uA(j)L!e*efP@Q45Dltt)lzpBdWC=2X%A!z}jCw2s%d*(;D7yh)HT;CK z-uAtHKCOzm=$tK@oB^Xx{Nmz*1FHcYoF+*u$ze}whQ z{M$?7lvFYsgK~c(D~0cZtq}Eph7mP3InDdmB${HW%*4bvmlcHqF&g|88u#LOTFxd# zWlZKOO|JI%6sXPH9n)I~H&9j>E_vru1jBx-F_)pm+F~tV5qd>FXjBV(0y|ibHX3j+ zD;=tMv2k2xcFqV-6$D*#Tu!I+NlH^_M!M46C~mDW_y5%mZd6_V5^k()P#;(b4lo64 z@h8^dYU}UJzh2VvYeQ*~2vNd|+P7`Hi)QJZ;&nvllaCW72z>J!O7XF5Djvfmsc@>}5%wWm>bQ(|@YPngHn;bRz(%z0$C{X9Wvl5g;bK<} z%ynI3BJ_7(KN3af`LlXY`7J8NwXlHN=vM(~FeL diff --git a/core/db/db_market_data.py b/core/db/db_market_data.py index d922c95..8ced864 100644 --- a/core/db/db_market_data.py +++ b/core/db/db_market_data.py @@ -18,6 +18,7 @@ class DBMarketData: "bar", "timestamp", "date_time", + "date_time_us", "open", "high", "low", diff --git a/core/statistics/price_volume_stats.py b/core/statistics/price_volume_stats.py index 33e34fd..57b210a 100644 --- a/core/statistics/price_volume_stats.py +++ b/core/statistics/price_volume_stats.py @@ -11,7 +11,7 @@ from openpyxl.drawing.image import Image import openpyxl from openpyxl.styles import Font from PIL import Image as PILImage -from config import MONITOR_CONFIG, MYSQL_CONFIG, WINDOW_SIZE +from config import OKX_MONITOR_CONFIG, MYSQL_CONFIG, WINDOW_SIZE from core.db.db_market_data import DBMarketData from core.db.db_huge_volume_data import DBHugeVolumeData from core.utils import timestamp_to_datetime, transform_date_time_to_timestamp @@ -35,10 +35,10 @@ class PriceVolumeStats: self.db_url = f"mysql+pymysql://{mysql_user}:{mysql_password}@{mysql_host}:{mysql_port}/{mysql_database}" self.db_market_data = DBMarketData(self.db_url) self.db_huge_volume_data = DBHugeVolumeData(self.db_url) - self.symbol_list = MONITOR_CONFIG.get("volume_monitor", {}).get("symbols", []) - self.bars_list = MONITOR_CONFIG.get("volume_monitor", {}).get("bars", []) + self.symbol_list = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get("symbols", []) + self.bars_list = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get("bars", []) self.windows_list = WINDOW_SIZE.get("window_sizes", []) - self.initial_date = MONITOR_CONFIG.get("volume_monitor", {}).get( + self.initial_date = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get( "initial_date", "2025-05-15 00:00:00" ) self.end_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S") diff --git a/core/trade/__pycache__/ma_break_statistics.cpython-312.pyc b/core/trade/__pycache__/ma_break_statistics.cpython-312.pyc index 10d11b4e25b2f9655dbfc25afca7b8ebef12343c..db4662d9703daadef0121b593432562d6cd204fb 100644 GIT binary patch delta 20 acmX^2i}BnqM$Xf`yj%=G@OLApYb^j(Tn7XI delta 20 acmX^2i}BnqM$Xf`yj%=G@NpxjYb^j(AO`pV diff --git a/core/trade/__pycache__/orb_trade.cpython-312.pyc b/core/trade/__pycache__/orb_trade.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..741d5d71e2aaddd472df10517434184f83e07607 GIT binary patch literal 15826 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 close1: + # 第一根K线收跌→空头信号 + signal = "Short" + stop_price = high1 # 空头止损=第一根K线最高价 + else: + # 十字星→无信号 + signal = "None" + stop_price = None + + signals.append( + { + "Date": date, + "EntryTime": entry_time, + "Signal": signal, + "EntryPrice": entry_price, + "StopPrice": stop_price, + "High1": high1, + "Low1": low1, + } + ) + + # 将信号合并到原始数据 + signals_df = pd.DataFrame(signals) + # 确保Date列类型一致,将Date转换为datetime64[ns]类型 + signals_df['Date'] = pd.to_datetime(signals_df['Date']) + # 使用merge而不是join来合并数据,根据signals_df的EntryTime与self.data的date_time进行匹配 + # TODO: 这里需要优化 + self.data = self.data.merge(signals_df, left_on="date_time", right_on="EntryTime", how="left") + # 将Date_x和Date_y合并为Date + self.data["Date"] = self.data["Date_x"].combine_first(self.data["Date_y"]) + # 删除Date_x和Date_y + self.data.drop(columns=["Date_x", "Date_y"], inplace=True) + logger.info( + f"生成信号完成:共{len(signals_df)}个交易日,其中多头{sum(signals_df['Signal']=='Long')}次,空头{sum(signals_df['Signal']=='Short')}次" + ) + + def backtest(self, profit_target_multiple=10): + """ + 回测ORB策略 + :param profit_target_multiple: 盈利目标倍数(默认10倍$R,即10R) + """ + logger.info(f"开始回测ORB策略:盈利目标倍数={profit_target_multiple}") + if "Signal" not in self.data.columns: + raise ValueError("请先调用generate_orb_signals生成策略信号") + + account_value = self.initial_capital # 初始账户价值 + current_position = None # 当前持仓(None=空仓,Long/Short=持仓) + equity_history = [account_value] # 净值历史 + trade_id = 0 # 交易ID + + # 按时间遍历数据(每日仅处理第二根K线后的信号) + for date, daily_data in self.data.groupby("Date"): + daily_data = daily_data.sort_index() + if len(daily_data) < 2: + continue + + # 获取当日信号(第二根K线的信号) + signal_row = ( + daily_data[~pd.isna(daily_data["Signal"])].iloc[0] + if sum(~pd.isna(daily_data["Signal"])) > 0 + else None + ) + if signal_row is None: + # 无信号→当日不交易,净值保持不变 + equity_history.append(account_value) + continue + + # 提取信号参数 + signal = signal_row["Signal"] + if pd.isna(signal): + continue + entry_price = signal_row["EntryPrice"] + stop_price = signal_row["StopPrice"] + high1 = signal_row["High1"] + low1 = signal_row["Low1"] + risk_assumed = abs(entry_price - stop_price) # 计算$R + profit_target = ( + entry_price + (risk_assumed * profit_target_multiple) + if signal == "Long" + else entry_price - (risk_assumed * profit_target_multiple) + ) + + # 计算交易股数 + shares = self.calculate_shares(account_value, entry_price, stop_price) + if shares == 0: + # 股数为0→不交易 + equity_history.append(account_value) + continue + + # 计算佣金(买入/卖出各收一次) + total_commission = shares * self.commission_per_share * 2 # 往返佣金 + + # 模拟日内持仓:寻找止损/止盈触发点,或当日收盘平仓 + daily_prices = daily_data[ + daily_data.date_time > signal_row.date_time + ] # 从entry时间开始遍历 + exit_price = None + exit_time = None + exit_reason = None + + for idx, (time, row) in enumerate(daily_prices.iterrows()): + high = row["High"] + low = row["Low"] + close = row["Close"] + + # 检查止损/止盈条件 + if signal == "Long": + # 多头:跌破止损→止损;突破止盈→止盈 + if low <= stop_price: + exit_price = stop_price + exit_reason = "Stop Loss" + exit_time = time + break + elif high >= profit_target: + exit_price = profit_target + exit_reason = "Profit Target (10R)" + exit_time = time + break + elif signal == "Short": + # 空头:突破止损→止损;跌破止盈→止盈 + if high >= stop_price: + exit_price = stop_price + exit_reason = "Stop Loss" + exit_time = time + break + elif low <= profit_target: + exit_price = profit_target + exit_reason = "Profit Target (10R)" + exit_time = time + break + + # 若未触发止损/止盈,当日收盘平仓 + if exit_price is None: + exit_price = daily_prices.iloc[-1]["Close"] + exit_reason = "End of Day (EoD)" + exit_time = daily_prices.iloc[-1].date_time + + # 计算盈亏 + if signal == "Long": + profit_loss = (exit_price - entry_price) * shares - total_commission + else: # Short + profit_loss = (entry_price - exit_price) * shares - total_commission + + # 更新账户价值 + account_value += profit_loss + account_value = max(account_value, 0) # 账户价值不能为负 + + # 记录交易 + self.trades.append( + { + "TradeID": trade_id, + "Date": date, + "Signal": signal, + "EntryTime": signal_row.date_time, + "EntryPrice": entry_price, + "ExitTime": exit_time, + "ExitPrice": exit_price, + "Shares": shares, + "RiskAssumed": risk_assumed, + "ProfitLoss": profit_loss, + "ExitReason": exit_reason, + "AccountValueAfter": account_value, + } + ) + + # 记录净值 + equity_history.append(account_value) + trade_id += 1 + + # 生成净值曲线 + self.equity_curve = pd.Series( + equity_history, + index=pd.date_range( + start=self.data.index[0].date(), periods=len(equity_history), freq="D" + ), + ) + + # 输出回测结果 + trades_df = pd.DataFrame(self.trades) + total_return = ( + (account_value - self.initial_capital) / self.initial_capital * 100 + ) + win_rate = ( + (trades_df["ProfitLoss"] > 0).sum() / len(trades_df) * 100 + if len(trades_df) > 0 + else 0 + ) + + logger.info("\n" + "=" * 50) + logger.info("ORB策略回测结果") + logger.info("=" * 50) + logger.info(f"初始资金:${self.initial_capital:,.2f}") + logger.info(f"最终资金:${account_value:,.2f}") + logger.info(f"总收益率:{total_return:.2f}%") + logger.info(f"总交易次数:{len(trades_df)}") + logger.info(f"胜率:{win_rate:.2f}%") + if len(trades_df) > 0: + logger.info(f"平均每笔盈亏:${trades_df['ProfitLoss'].mean():.2f}") + logger.info(f"最大单笔盈利:${trades_df['ProfitLoss'].max():.2f}") + logger.info(f"最大单笔亏损:${trades_df['ProfitLoss'].min():.2f}") + + def plot_equity_curve(self): + """ + 绘制账户净值曲线 + """ + logger.info("开始绘制账户净值曲线") + if self.equity_curve is None: + raise ValueError("请先调用backtest进行回测") + + # seaborn风格设置 + sns.set_theme(style="whitegrid") + # plt.rcParams['font.family'] = "SimHei" + plt.rcParams["font.sans-serif"] = ["SimHei"] # 也可直接用字体名 + plt.rcParams["font.size"] = 11 # 设置字体大小 + plt.rcParams["axes.unicode_minus"] = False # 解决负号显示问题 + + plt.figure(figsize=(12, 6)) + plt.plot( + self.equity_curve.index, + self.equity_curve.values, + label="ORB策略净值", + color="blue", + ) + plt.axhline( + y=self.initial_capital, color="red", linestyle="--", label="初始资金" + ) + plt.title("ORB策略账户净值曲线", fontsize=14) + plt.xlabel("日期", fontsize=12) + plt.ylabel("账户价值(美元)", fontsize=12) + plt.legend() + plt.grid(True, alpha=0.3) + plt.show() + + +# ------------------- 策略示例:回测QQQ的ORB策略(2016-2023) ------------------- +if __name__ == "__main__": + + # 初始化ORB策略 + orb_strategy = ORBStrategy( + initial_capital=25000, + max_leverage=4, + risk_per_trade=0.01, + commission_per_share=0.0005, + ) + + # 1. 获取QQQ的5分钟日内数据(2024-2025,注意:yfinance免费版可能限制历史日内数据,建议用专业数据源) + orb_strategy.fetch_intraday_data( + symbol="ETH-USDT", start_date="2025-05-15", end_date="2025-08-20", interval="5m" + ) + + # 2. 生成ORB策略信号 + orb_strategy.generate_orb_signals() + + # 3. 回测策略(盈利目标10R) + orb_strategy.backtest(profit_target_multiple=10) + + # 4. 绘制净值曲线 + orb_strategy.plot_equity_curve() diff --git a/huge_volume_main.py b/huge_volume_main.py index 7e5b8aa..e77b2d2 100644 --- a/huge_volume_main.py +++ b/huge_volume_main.py @@ -6,7 +6,7 @@ from core.utils import timestamp_to_datetime, transform_date_time_to_timestamp from market_data_main import MarketDataMain from core.wechat import Wechat import core.logger as logging -from config import MONITOR_CONFIG, MYSQL_CONFIG, WINDOW_SIZE +from config import OKX_MONITOR_CONFIG, US_STOCK_MONITOR_CONFIG, MYSQL_CONFIG, WINDOW_SIZE from datetime import datetime, timedelta import pandas as pd import os @@ -16,7 +16,7 @@ logger = logging.logger class HugeVolumeMain: - def __init__(self, threshold: float = 2.0): + def __init__(self, threshold: float = 2.0, is_us_stock: bool = False): mysql_user = MYSQL_CONFIG.get("user", "xch") mysql_password = MYSQL_CONFIG.get("password", "") if not mysql_password: @@ -29,7 +29,7 @@ class HugeVolumeMain: self.huge_volume = HugeVolume() self.db_market_data = DBMarketData(self.db_url) self.db_huge_volume_data = DBHugeVolumeData(self.db_url) - self.market_data_main = MarketDataMain() + self.market_data_main = MarketDataMain(is_us_stock=is_us_stock) self.threshold = threshold self.output_folder = "./output/huge_volume_statistics/" @@ -41,9 +41,14 @@ class HugeVolumeMain: for symbol in self.market_data_main.symbols: for bar in self.market_data_main.bars: if start is None: - start = MONITOR_CONFIG.get("volume_monitor", {}).get( - "initial_date", "2025-05-15 00:00:00" - ) + if self.is_us_stock: + start = US_STOCK_MONITOR_CONFIG.get("volume_monitor", {}).get( + "initial_date", "2015-08-30 00:00:00" + ) + else: + start = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get( + "initial_date", "2025-05-15 00:00:00" + ) data = self.detect_volume_spike( symbol, bar, @@ -68,9 +73,15 @@ class HugeVolumeMain: is_update: bool = False, ): if start is None: - start = MONITOR_CONFIG.get("volume_monitor", {}).get( - "initial_date", "2025-05-01 00:00:00" - ) + if self.is_us_stock: + start = US_STOCK_MONITOR_CONFIG.get("volume_monitor", {}).get( + "initial_date", "2015-08-31 00:00:00" + ) + else: + start = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get( + "initial_date", "2025-05-01 00:00:00" + ) + if end is None: end = datetime.now().strftime("%Y-%m-%d %H:%M:%S") logger.info( @@ -169,7 +180,7 @@ class HugeVolumeMain: logger.info(f"此次更新巨量交易数据为空") except Exception as e: logger.error( - f"更新巨量交易数据失败: {symbol} {bar} 窗口大小: {window_size} 从 {earliest_date_time} 到 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}: {e}" + f"更新巨量交易数据失败: {symbol} {bar} 窗口大小: {window_size} 到 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}: {e}" ) def get_seconds_by_bar(self, bar: str): @@ -226,7 +237,7 @@ class HugeVolumeMain: periods: list = [3, 5], ): if start is None: - start = MONITOR_CONFIG.get("volume_monitor", {}).get( + start = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get( "initial_date", "2025-05-01 00:00:00" ) if end is None: @@ -286,7 +297,7 @@ class HugeVolumeMain: def send_huge_volume_data_to_wechat(self, start: str = None, end: str = None): if start is None: - start = MONITOR_CONFIG.get("volume_monitor", {}).get( + start = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get( "initial_date", "2025-05-01 00:00:00" ) if end is None: @@ -395,7 +406,7 @@ class HugeVolumeMain: output_excel: bool = False, ): if start is None: - start = MONITOR_CONFIG.get("volume_monitor", {}).get( + start = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get( "initial_date", "2025-05-01 00:00:00" ) if end is None: @@ -464,7 +475,7 @@ def batch_initial_detect_volume_spike(threshold: float = 2.0): ): window_sizes = [50, 80, 100, 120] huge_volume_main = HugeVolumeMain(threshold) - start_date = MONITOR_CONFIG.get("volume_monitor", {}).get( + start_date = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get( "initial_date", "2025-05-15 00:00:00" ) for window_size in window_sizes: @@ -474,7 +485,7 @@ def batch_initial_detect_volume_spike(threshold: float = 2.0): ) -def batch_update_volume_spike(threshold: float = 2.0): +def batch_update_volume_spike(threshold: float = 2.0, is_us_stock: bool = False): window_sizes = WINDOW_SIZE.get("window_sizes", None) if ( window_sizes is None @@ -482,7 +493,7 @@ def batch_update_volume_spike(threshold: float = 2.0): or len(window_sizes) == 0 ): window_sizes = [50, 80, 100, 120] - huge_volume_main = HugeVolumeMain(threshold) + huge_volume_main = HugeVolumeMain(threshold, is_us_stock) for window_size in window_sizes: huge_volume_main.batch_update_volume_spike(window_size=window_size) @@ -501,7 +512,7 @@ def test_send_huge_volume_data_to_wechat(): if __name__ == "__main__": # test_send_huge_volume_data_to_wechat() # batch_initial_detect_volume_spike(threshold=2.0) - batch_update_volume_spike(threshold=2.0) + batch_update_volume_spike(threshold=2.0, is_us_stock=True) # huge_volume_main = HugeVolumeMain(threshold=2.0) # huge_volume_main.batch_next_periods_rise_or_fall(output_excel=True) # data_file_path = "./output/huge_volume_statistics/next_periods_rise_or_fall_stat_20250731200304.xlsx" diff --git a/market_data_from_itick_main.py b/market_data_from_itick_main.py new file mode 100644 index 0000000..88a0756 --- /dev/null +++ b/market_data_from_itick_main.py @@ -0,0 +1,60 @@ +import requests +import core.logger as logging + +from datetime import datetime, timedelta + +from futu import KLType + +logger = logging.logger + + +def main(): + # 配置参数 + symbol = "QQQ" + start_date = "2025-08-01 00:00:00" + end_date = "2025-08-20 00:00:00" + interval = "5m" + segment_days = 5 # 减少每段天数,降低单次请求的数据量 + + print(f"开始下载 {symbol} 数据") + print(f"时间范围: {start_date} 到 {end_date}") + print(f"数据间隔: {interval}") + print(f"分段天数: {segment_days}") + print("-" * 50) + + try: + market_data_from_futu = MarketDataFromAlphaVantage( + symbol=symbol, + start_date=start_date, + end_date=end_date, + interval=interval, + segment_days=segment_days, + ) + + # 下载数据 + market_data_from_futu.download_all() + + # 处理数据 + processed_data = market_data_from_futu.process_data() + + if not processed_data.empty: + logger.info(f"成功下载 {len(processed_data)} 条数据") + + # 保存数据 + filename = f"{symbol}_{interval}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" + market_data_from_futu.save_to_csv(filename) + + # 显示数据统计 + market_data_from_futu.show_head() + market_data_from_futu.show_stats() + + else: + logger.warning("未获取到任何数据") + + except Exception as e: + logger.error(f"下载过程中发生错误: {e}") + print(f"错误详情: {e}") + + +if __name__ == "__main__": + main() diff --git a/market_data_main.py b/market_data_main.py index 49401d2..03c6963 100644 --- a/market_data_main.py +++ b/market_data_main.py @@ -16,7 +16,8 @@ from config import ( SECRET_KEY, PASSPHRASE, SANDBOX, - MONITOR_CONFIG, + OKX_MONITOR_CONFIG, + US_STOCK_MONITOR_CONFIG, MYSQL_CONFIG, BAR_THRESHOLD, ) @@ -25,22 +26,34 @@ logger = logging.logger class MarketDataMain: - def __init__(self): + def __init__(self, is_us_stock: bool = False): self.market_data = MarketData( api_key=API_KEY, secret_key=SECRET_KEY, passphrase=PASSPHRASE, sandbox=SANDBOX, + is_us_stock=is_us_stock, ) - self.symbols = MONITOR_CONFIG.get("volume_monitor", {}).get( - "symbols", ["XCH-USDT"] - ) - self.bars = MONITOR_CONFIG.get("volume_monitor", {}).get( - "bars", ["5m", "15m", "1H", "1D"] - ) - self.initial_date = MONITOR_CONFIG.get("volume_monitor", {}).get( - "initial_date", "2025-07-01 00:00:00" - ) + if is_us_stock: + self.symbols = US_STOCK_MONITOR_CONFIG.get("volume_monitor", {}).get( + "symbols", ["QQQ"] + ) + self.bars = US_STOCK_MONITOR_CONFIG.get("volume_monitor", {}).get( + "bars", ["5m"] + ) + self.initial_date = US_STOCK_MONITOR_CONFIG.get("volume_monitor", {}).get( + "initial_date", "2015-08-30 00:00:00" + ) + else: + self.symbols = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get( + "symbols", ["XCH-USDT"] + ) + self.bars = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get( + "bars", ["5m", "15m", "1H", "1D"] + ) + self.initial_date = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get( + "initial_date", "2025-07-01 00:00:00" + ) mysql_user = MYSQL_CONFIG.get("user", "xch") mysql_password = MYSQL_CONFIG.get("password", "") if not mysql_password: @@ -52,6 +65,7 @@ class MarketDataMain: self.db_url = f"mysql+pymysql://{mysql_user}:{mysql_password}@{mysql_host}:{mysql_port}/{mysql_database}" self.db_market_data = DBMarketData(self.db_url) self.trade_data_main = TradeDataMain() + self.is_us_stock = is_us_stock def initial_data(self): """ @@ -96,7 +110,15 @@ class MarketDataMain: # 获取数据,直到end_time_ts threshold = None if bar in ["5m", "15m", "30m"]: - threshold = 86400000 + if self.is_us_stock: + if bar == "5m": + threshold = 86400000 * 4 + elif bar == "15m": + threshold = 86400000 * 4 * 3 + elif bar == "30m": + threshold = 86400000 * 4 * 6 + else: + threshold = 86400000 elif bar in ["1H", "4H"]: threshold = 432000000 elif bar == "1D": @@ -105,7 +127,7 @@ class MarketDataMain: get_data = False min_start_time_ts = start_time_ts while start_time_ts < end_time_ts: - current_start_time_ts = end_time_ts - threshold + current_start_time_ts = int(end_time_ts - threshold) if current_start_time_ts < start_time_ts: current_start_time_ts = start_time_ts start_date_time = timestamp_to_datetime(current_start_time_ts) @@ -113,46 +135,21 @@ class MarketDataMain: logger.info( f"获取行情数据: {symbol} {bar} 从 {start_date_time} 到 {end_date_time}" ) + if self.is_us_stock: + limit = 1000 + else: + limit = 100 data = self.market_data.get_historical_kline_data( symbol=symbol, start=current_start_time_ts, bar=bar, end_time=end_time_ts, + limit=limit, ) if data is not None and len(data) > 0: data["buy_sz"] = -1 data["sell_sz"] = -1 - # 根据交易数据,设置buy_sz和sell_sz - # 比特币的数据获取过慢,暂时不获取交易数据 - # if not symbol.endswith("-SWAP"): - # # trade_data的end_time需要比market_data的end_time大一个周期 - # trade_data = self.trade_data_main.get_trade_data( - # symbol=symbol, start_time=current_start_time_ts, end_time=end_time_ts - # ) - # for index, row in data.iterrows(): - # try: - # current_from_time = int(row["timestamp"]) - # if index == len(data) - 1: - # current_to_time = current_from_time + BAR_THRESHOLD[bar] - # else: - # current_to_time = int(data.iloc[index + 1]["timestamp"]) - # current_trade_data = trade_data[ - # (trade_data["ts"] >= current_from_time) - # & (trade_data["ts"] <= current_to_time) - # ] - # if current_trade_data is not None and len(current_trade_data) > 0: - # current_buy_sz = current_trade_data[ - # current_trade_data["side"] == "buy" - # ]["sz"].sum() - # current_sell_sz = current_trade_data[ - # current_trade_data["side"] == "sell" - # ]["sz"].sum() - # data.loc[index, "buy_sz"] = current_buy_sz - # data.loc[index, "sell_sz"] = current_sell_sz - # except Exception as e: - # logger.error(f"设置buy_sz和sell_sz失败: {e}") - # continue if data is not None and len(data) > 0: data = data[ [ @@ -160,6 +157,7 @@ class MarketDataMain: "bar", "timestamp", "date_time", + "date_time_us", "open", "high", "low", @@ -174,13 +172,22 @@ class MarketDataMain: ] data = self.add_new_columns(data) self.db_market_data.insert_data_to_mysql(data) - current_min_start_time_ts = data["timestamp"].min() + current_min_start_time_ts = int(data["timestamp"].min()) if current_min_start_time_ts < min_start_time_ts: min_start_time_ts = current_min_start_time_ts + get_data = True + else: + logger.warning(f"获取行情数据为空: {symbol} {bar} 从 {start_date_time} 到 {end_date_time}") + break + if current_start_time_ts == start_time_ts: break - end_time_ts = current_start_time_ts + + if current_min_start_time_ts < current_start_time_ts: + end_time_ts = current_min_start_time_ts + else: + end_time_ts = current_start_time_ts if min_start_time_ts is not None and get_data: # 补充技术指标数据 # 获得min_start_time_ts之前30条数据 @@ -374,7 +381,7 @@ class MarketDataMain: 批量计算技术指标 """ logger.info("开始批量计算技术指标") - start_date_time = MONITOR_CONFIG.get("volume_monitor", {}).get( + start_date_time = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get( "initial_date", "2025-05-15 00:00:00" ) start_timestamp = transform_date_time_to_timestamp(start_date_time) diff --git a/market_monitor_main.py b/market_monitor_main.py index bbbae87..50478c3 100644 --- a/market_monitor_main.py +++ b/market_monitor_main.py @@ -4,7 +4,7 @@ from huge_volume_main import HugeVolumeMain from core.biz.market_monitor import create_metrics_report from core.db.db_market_monitor import DBMarketMonitor from core.wechat import Wechat -from config import MONITOR_CONFIG, MYSQL_CONFIG +from config import OKX_MONITOR_CONFIG, MYSQL_CONFIG from core.utils import timestamp_to_datetime, transform_date_time_to_timestamp import core.logger as logging @@ -21,9 +21,9 @@ class MarketMonitorMain: self.market_data_main = MarketDataMain() self.huge_volume_main = HugeVolumeMain() self.wechat = Wechat() - self.monitor_config = MONITOR_CONFIG + self.monitor_config = OKX_MONITOR_CONFIG self.window_size = 100 - self.start_date = MONITOR_CONFIG.get("volume_monitor", {}).get( + self.start_date = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get( "initial_date", "2025-05-01 00:00:00" ) self.latest_record_file_path = "./output/record/latest_record.json" diff --git a/orb_trade_main.py b/orb_trade_main.py new file mode 100644 index 0000000..d14a0d7 --- /dev/null +++ b/orb_trade_main.py @@ -0,0 +1,28 @@ +from core.trade.orb_trade import ORBStrategy + +def main(): + # 初始化ORB策略 + orb_strategy = ORBStrategy( + initial_capital=25000, + max_leverage=4, + risk_per_trade=0.01, + commission_per_share=0.0005, + ) + + # 1. 获取QQQ的5分钟日内数据(2024-2025,注意:yfinance免费版可能限制历史日内数据,建议用专业数据源) + orb_strategy.fetch_intraday_data( + symbol="ETH-USDT", start_date="2025-05-15", end_date="2025-08-20", interval="5m" + ) + + # 2. 生成ORB策略信号 + orb_strategy.generate_orb_signals() + + # 3. 回测策略(盈利目标10R) + orb_strategy.backtest(profit_target_multiple=10) + + # 4. 绘制净值曲线 + orb_strategy.plot_equity_curve() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/sql/query/sql_playground.sql b/sql/query/sql_playground.sql index 92664b0..41b21c5 100644 --- a/sql/query/sql_playground.sql +++ b/sql/query/sql_playground.sql @@ -1,12 +1,19 @@ -select * from crypto_market_monitor -order by date_time desc; +select * from crypto_market_data +where symbol = "TQQQ" and bar="5m" +order by date_time_us DESC; + +select * from crypto_huge_volume +where symbol = "QQQ" and bar="5m" +order by date_time; + + select symbol, bar, date_time, close, pct_chg, ma_cross, ma5, ma10, ma20, ma30, dif, dea, macd, kdj_k, kdj_d, kdj_k, kdj_pattern, rsi_14, rsi_signal, boll_upper, boll_middle, boll_lower, boll_pattern, boll_signal from crypto_market_data -WHERE close > boll_upper +WHERE ma_cross in ("20下穿10", "10下穿5") order by timestamp desc; select symbol, bar, window_size, date_time, close, @@ -14,8 +21,8 @@ volume, volume_ratio, huge_volume, close_20_low, low_20_low, close_10_low, low_10_low, close_80_high, close_90_high, high_80_high, high_90_high from crypto_huge_volume -WHERE symbol='XCH-USDT' and bar='5m' and window_size=120# and low_10_low=1 -order by timestamp desc; +WHERE symbol='BTC-USDT' and bar='5m' and window_size=120# and low_10_low=1 +order by timestamp; select * from crypto_huge_volume WHERE symbol='BTC-USDT' and bar='5m' #and date_time > '2025-08-04 15:00:00' diff --git a/sql/table/crypto_market_data.sql b/sql/table/crypto_market_data.sql index 5988f76..44876f4 100644 --- a/sql/table/crypto_market_data.sql +++ b/sql/table/crypto_market_data.sql @@ -7,6 +7,7 @@ CREATE TABLE IF NOT EXISTS crypto_market_data ( bar VARCHAR(20) NOT NULL, timestamp BIGINT NOT NULL, date_time VARCHAR(50) NOT NULL, + date_time_us VARCHAR(50) NULL COMMENT '美国时间格式的日期时间', open DECIMAL(20,10) NOT NULL, high DECIMAL(20,10) NOT NULL, low DECIMAL(20,10) NOT NULL, @@ -60,3 +61,6 @@ CREATE TABLE IF NOT EXISTS crypto_market_data ( --修改ma_cross字段长度为150 ALTER TABLE crypto_market_data MODIFY COLUMN ma_cross VARCHAR(150) DEFAULT NULL COMMENT '均线交叉信号'; + +--添加date_time_us字段 +ALTER TABLE crypto_market_data ADD COLUMN date_time_us VARCHAR(50) NULL COMMENT '美国时间格式的日期时间' AFTER date_time; diff --git a/test_ma_methods.py b/test_ma_methods.py index b475563..e399344 100644 --- a/test_ma_methods.py +++ b/test_ma_methods.py @@ -10,7 +10,7 @@ import matplotlib.pyplot as plt from core.db.db_market_data import DBMarketData from core.biz.metrics_calculation import MetricsCalculation import logging -from config import MONITOR_CONFIG, MYSQL_CONFIG, WINDOW_SIZE +from config import OKX_MONITOR_CONFIG, MYSQL_CONFIG, WINDOW_SIZE # plt支持中文 plt.rcParams['font.family'] = ['SimHei'] diff --git a/trade_data_main.py b/trade_data_main.py index 49b7220..5a30ebe 100644 --- a/trade_data_main.py +++ b/trade_data_main.py @@ -9,7 +9,7 @@ from config import ( SECRET_KEY, PASSPHRASE, SANDBOX, - MONITOR_CONFIG, + OKX_MONITOR_CONFIG, MYSQL_CONFIG, ) @@ -48,7 +48,7 @@ class TradeDataMain: # 处理start参数 if start_time is None: # 默认两个月前 - start_time_str = MONITOR_CONFIG.get("volume_monitor", {}).get("initial_date", "2025-05-01 00:00:00") + start_time_str = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get("initial_date", "2025-05-01 00:00:00") start_time = transform_date_time_to_timestamp(start_time_str) else: start_time = transform_date_time_to_timestamp(start_time) diff --git a/trade_ma_strategy_main.py b/trade_ma_strategy_main.py index 7b815b8..128e88e 100644 --- a/trade_ma_strategy_main.py +++ b/trade_ma_strategy_main.py @@ -17,7 +17,7 @@ from config import ( SECRET_KEY, PASSPHRASE, SANDBOX, - MONITOR_CONFIG, + OKX_MONITOR_CONFIG, MYSQL_CONFIG, BAR_THRESHOLD, ) diff --git a/trade_sandbox_main.py b/trade_sandbox_main.py index 3714c00..59a9f04 100644 --- a/trade_sandbox_main.py +++ b/trade_sandbox_main.py @@ -12,7 +12,7 @@ from openpyxl.drawing.image import Image import openpyxl from openpyxl.styles import Font from PIL import Image as PILImage -from config import MONITOR_CONFIG +from config import OKX_MONITOR_CONFIG from core.trade.mean_reversion_sandbox import MeanReversionSandbox from core.utils import timestamp_to_datetime, transform_date_time_to_timestamp @@ -24,14 +24,14 @@ logger = logging.logger class MeanReversionSandboxMain: def __init__(self, start_date: str, end_date: str, window_size: int, only_5m: bool = False, solution_list: list = None): - self.symbols = MONITOR_CONFIG.get("volume_monitor", {}).get( + self.symbols = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get( "symbols", ["XCH-USDT"] ) self.only_5m = only_5m if only_5m: self.bars = ["5m"] else: - self.bars = MONITOR_CONFIG.get("volume_monitor", {}).get( + self.bars = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get( "bars", ["5m", "15m", "30m", "1H"] ) if solution_list is None: