From 11c6e25490939a3fa955704db8c3c3139489a105 Mon Sep 17 00:00:00 2001 From: blade <8019068@qq.com> Date: Thu, 25 Sep 2025 12:28:43 +0800 Subject: [PATCH] support quant by A market --- config.py | 49 +- .../db_market_data.cpython-312.pyc | Bin 17144 -> 17192 bytes core/db/db_astock.py | 125 +++++ core/db/db_binance_data.py | 9 +- core/db/db_market_data.py | 285 +++++----- core/statistics/price_volume_stats.py | 12 +- .../ma_break_statistics.cpython-312.pyc | Bin 40337 -> 52539 bytes core/trade/ma_break_statistics.py | 523 ++++++++++++++---- core/trade/mean_reversion_sandbox.py | 12 +- core/trade/orb_trade.py | 12 +- doc/biz_code_file_brief.md | 62 +++ doc/main_code_file_brief.md | 78 +++ doc/trade_code_file_brief.md | 23 + huge_volume_main.py | 12 +- market_data_main.py | 12 +- market_monitor_main.py | 12 +- play.py | 12 +- test_ma_methods.py | 12 +- trade_data_main.py | 12 +- trade_ma_strategy_main.py | 60 +- update_data_main.py | 12 +- 21 files changed, 1017 insertions(+), 317 deletions(-) create mode 100644 core/db/db_astock.py create mode 100644 doc/biz_code_file_brief.md create mode 100644 doc/main_code_file_brief.md create mode 100644 doc/trade_code_file_brief.md diff --git a/config.py b/config.py index 29ed08b..522012e 100644 --- a/config.py +++ b/config.py @@ -134,6 +134,43 @@ US_STOCK_MONITOR_CONFIG = { } } +A_STOCK_MONITOR_CONFIG = { + "volume_monitor": { + "symbols": [ + "600276.SH", + "002714.SZ", + "600111.SH", + "603019.SH", + "600036.SH", + "300474.SZ", + "600519.SH", + "300750.SZ", + "000858.SZ", + "000651.SZ", + "000333.SZ", + "002230.SZ", + "300308.SZ", + "002475.SZ" + ], + "bars": ["1D", "1W", "1M"], + "initial_date": "2015-01-01 00:00:00", + }, +} + +A_INDEX_MONITOR_CONFIG = { + "volume_monitor": { + "symbols": [ + "000001.SH", + "399006.SZ", + "000300.SH", + "399001.SZ", + "000852.SH", + ], + "bars": ["1D", "1W", "1M"], + "initial_date": "2015-01-01 00:00:00", + }, +} + WINDOW_SIZE = {"window_sizes": [50, 80, 100, 120]} BAR_THRESHOLD = { @@ -146,7 +183,7 @@ BAR_THRESHOLD = { "1D": 1000 * 60 * 60 * 24, } -# MYSQL_CONFIG = { +# COIN_MYSQL_CONFIG = { # "host": "localhost", # "port": 3306, # "user": "xch", @@ -154,7 +191,7 @@ BAR_THRESHOLD = { # "database": "okx", # } -MYSQL_CONFIG = { +COIN_MYSQL_CONFIG = { "host": "218.17.89.43", "port": 11013, "user": "xch", @@ -162,6 +199,14 @@ MYSQL_CONFIG = { "database": "okx", } +A_MYSQL_CONFIG = { + "host": "43.139.95.249", + "port": 3306, + "user": "root", + "password": "bengbu_200!", + "database": "astock", +} + WECHAT_CONFIG = { "general_key": "11e6f7ac-efa9-418a-904c-9325a9f5d324", "btc_key": "529e135d-843b-43dc-8aca-677a860f4b4b", diff --git a/core/db/__pycache__/db_market_data.cpython-312.pyc b/core/db/__pycache__/db_market_data.cpython-312.pyc index 8d270393f76397926618b724f27b483b842bba05..41805d589b71ac8df8f3dcd05c9e630475faad3f 100644 GIT binary patch delta 998 zcmY*XOK1~87@mK3C)wTRwM~<>Nz^ob*=n0&ZLKkVU`uTktF-Dtd?8cu0Yy?3lr2f2 zN^e^Ki(rNLj_5($gB}D$JXi#&cv-yYLGa*ZVm)|tlGN8MGygaL|9#)izdJuKLjDqX z-n!j75#s}UcYJ*NyyrFinE$XJ8n6xaC>mjnI15eCgpZ&D*5YSqfMz@*MU2QS-S5-L z?}xSE2C21Fi`TPtOq-CIE~ZaRV?sWrvRWbV|DmtPoK)pwb)w{A^`aCw?`>|iUP^Pa zwox1>OYC6O8h4zPVAxtzM9oT1e_SelyA*lMwV(*6I#ZeP{l{|uWiii!EI8skGv5C=-aj15d_@jI_d36t(U|e5 z@}VqR5@Vb+`>noi9dt)*Xl&Qc*o8?QcPBgXprPTjWCE8qAB7z5NIr$D7};_Mt_VDW zwW$o~cr&$HO84XQRG1d8Rt!I;j;fR56J`mI<6uv#p6klD8Co*pT!Ki{jy7IP{S{(oZ-f5N_E7JH zri_k`rZsT_eARm|B#G8$371lI7_Ln%lVap1V7Bj22ak~f7=nTvq+?(QYj_#N>Q+E| z#|lXGEtg8X+j925hYSNU2*W~{pyOp>2^57Tus*Q@dis}38GaH-b<_^j*#3|mZYy}V zf_UX;pP$nb;tt|5xU-B^Vg$HW#ySz4iZXT;Ic|X~#z0*1K=lt{gS}fH7<*M1gYTpi I(nF&4U%R0X2mk;8 delta 924 zcmY*WTS!zv7@mL5IlE`?uIqYVb-k>v;-YNcuVj?vL!pxGno2rrY9h2=Kse$h6CVPz zd5P4QLNcuo4uWq9NeK3^5`ol9)Jtz2)1t@b>~2;A|9s#4-~WH}GqW}azwUy0-(=Dw zwq|x;_3d9=H1ERK#dm(lrXFa;B!|vHF1YAR@Io&A3^|ZTMO*-GdIlFV-i zU4SEc7BMWW0j^gh&RM~4Be|zaW+3iV)srly+Q%*K0{JbT=M1H6f+?hkD8V6jXhtzK zQHP;O=5$FIe6qu^fZ?Y6!gK+{nEb=ifZ>i@5xa+74_M!Dv^hqw80R^~h;j-xy*s{; zuk#?OYuyajQUg!qxs*O0zR2Iw`+2a)zHFBO_41T^goiQe%l1%5hFLE5 zJl2|?0<+$X%#}*P6Gu-u}x6UCHWGL|y!URurVL$yJ zKed7wmU-ssU|puDZv3kg(=x|YHqEp#s+vz#fmnD|rk?Twn|4wpG0&|VTx5_IBm}by)xBR82o7>8QV|8P@*G{;Zk=9UZGqOZlMh?GFr+ z;jZD4VDLJ-C)mNZVIAA(R&}>{g*^iM?O2*#Q^+fb%cfUr7K$0kvnw2*ka(X{^r+Cg z?cKA7+WZI3_C@odH9lm`R4}BZ9;Uh;R;$G{uQo?*&!cU%0s5wPHSs-0Xcj5BPH2Qr zF}7ntCCXV$XX-lLM`;$J88)*Nq~dl%1oxsCy4HRi7LXS`h-GzQs%P2baOKeel&JUgC`iQ}E`ocl5e(fRQ4$@#bl O2+i;vg&}yJz0z-sQU2xt diff --git a/core/db/db_astock.py b/core/db/db_astock.py new file mode 100644 index 0000000..26eed5a --- /dev/null +++ b/core/db/db_astock.py @@ -0,0 +1,125 @@ +import pandas as pd +from sqlalchemy import create_engine, exc, text +import re +from core.utils import get_current_date_time +import core.logger as logging +from core.utils import transform_data_type + +logger = logging.logger + + +class DBAStockData: + def __init__( + self, + db_url: str, + ): + self.db_url = db_url + self.db_engine = create_engine( + self.db_url, + pool_size=25, # 连接池大小 + max_overflow=10, # 允许的最大溢出连接 + pool_timeout=30, # 连接超时时间(秒) + pool_recycle=60, # 连接回收时间(秒),避免长时间闲置 + ) + + def query_data(self, sql: str, condition_dict: dict, return_multi: bool = True): + """ + 查询数据 + :param sql: 查询SQL + :param db_url: 数据库连接URL + """ + try: + with self.db_engine.connect() as conn: + result = conn.execute(text(sql), condition_dict) + if return_multi: + result = result.fetchall() + if result: + result_list = [ + transform_data_type(dict(row._mapping)) for row in result + ] + return result_list + else: + return None + else: + result = result.fetchone() + if result: + result_dict = transform_data_type(dict(result._mapping)) + return result_dict + else: + return None + except Exception as e: + logger.error(f"查询数据出错: {e}") + return None + + def query_market_data_by_symbol_bar( + self, + symbol: str, + bar: str, + fields: list = None, + start: str = None, + end: str = None, + table_name: str = "index_daily_price_from_2021", + ): + """ + 根据交易对和K线周期查询数据 + :param symbol: 交易对 + :param bar: K线周期 + :param fields: 字段列表 + :param start: 开始时间 + :param end: 结束时间 + """ + if fields is None: + fields = ["*"] + fields_str = ", ".join(fields) + if table_name is None: + table_name = "index_daily_price_from_2021" + join_table = "all_index" + if table_name.startswith("index"): + join_table = "all_index" + else: + join_table = "all_stock" + + if start is None and end is None: + sql = f""" + SELECT {fields_str} FROM {table_name} a + INNER JOIN {join_table} b ON a.ts_code = b.ts_code + WHERE a.ts_code = :symbol + ORDER BY a.trade_date ASC + """ + condition_dict = {"symbol": symbol} + else: + if start is not None and end is not None: + start = start.replace("-", "") + end = end.replace("-", "") + if start > end: + start, end = end, start + sql = f""" + SELECT {fields_str} FROM {table_name} a + INNER JOIN {join_table} b ON a.ts_code = b.ts_code + WHERE a.ts_code = :symbol AND a.trade_date BETWEEN :start AND :end + ORDER BY a.trade_date ASC + """ + condition_dict = { + "symbol": symbol, + "start": start, + "end": end, + } + elif start is not None: + start = start.replace("-", "") + sql = f""" + SELECT {fields_str} FROM {table_name} a + INNER JOIN {join_table} b ON a.ts_code = b.ts_code + WHERE a.ts_code = :symbol AND a.trade_date >= :start + ORDER BY a.trade_date ASC + """ + condition_dict = {"symbol": symbol, "start": start} + elif end is not None: + end = end.replace("-", "") + sql = f""" + SELECT {fields_str} FROM {table_name} a + INNER JOIN {join_table} b ON a.ts_code = b.ts_code + WHERE a.ts_code = :symbol AND a.trade_date <= :end + ORDER BY a.trade_date ASC + """ + condition_dict = {"symbol": symbol, "end": end} + return self.query_data(sql, condition_dict, return_multi=True) diff --git a/core/db/db_binance_data.py b/core/db/db_binance_data.py index cf967c2..daf66b9 100644 --- a/core/db/db_binance_data.py +++ b/core/db/db_binance_data.py @@ -480,6 +480,7 @@ class DBBinanceData: fields: list = None, start: str = None, end: str = None, + table_name: str = "crypto_binance_data", ): """ 根据交易对和K线周期查询数据 @@ -494,7 +495,7 @@ class DBBinanceData: fields_str = ", ".join(fields) if start is None and end is None: sql = f""" - SELECT {fields_str} FROM crypto_binance_data + SELECT {fields_str} FROM {table_name} WHERE symbol = :symbol AND bar = :bar ORDER BY timestamp ASC """ @@ -514,7 +515,7 @@ class DBBinanceData: if start > end: start, end = end, start sql = f""" - SELECT {fields_str} FROM crypto_binance_data + SELECT {fields_str} FROM {table_name} WHERE symbol = :symbol AND bar = :bar AND timestamp BETWEEN :start AND :end ORDER BY timestamp ASC """ @@ -526,14 +527,14 @@ class DBBinanceData: } elif start is not None: sql = f""" - SELECT {fields_str} FROM crypto_binance_data + SELECT {fields_str} FROM {table_name} WHERE symbol = :symbol AND bar = :bar AND timestamp >= :start ORDER BY timestamp ASC """ condition_dict = {"symbol": symbol, "bar": bar, "start": start} elif end is not None: sql = f""" - SELECT {fields_str} FROM crypto_binance_data + SELECT {fields_str} FROM {table_name} WHERE symbol = :symbol AND bar = :bar AND timestamp <= :end ORDER BY timestamp ASC """ diff --git a/core/db/db_market_data.py b/core/db/db_market_data.py index adff462..646f62e 100644 --- a/core/db/db_market_data.py +++ b/core/db/db_market_data.py @@ -7,68 +7,65 @@ logger = logging.logger class DBMarketData: - def __init__( - self, - db_url: str - ): + def __init__(self, db_url: str): self.db_url = db_url self.table_name = "crypto_market_data" self.columns = [ - "symbol", - "bar", - "timestamp", - "date_time", - "date_time_us", - "open", - "high", - "low", - "close", - "pre_close", - "close_change", - "pct_chg", - "volume", - "volCcy", - "volCCyQuote", - "buy_sz", - "sell_sz", - # 技术指标字段 - "ma1", - "ma2", - "dif", - "dea", - "macd", - "macd_signal", - "macd_divergence", - "kdj_k", - "kdj_d", - "kdj_j", - "kdj_signal", - "kdj_pattern", - "sar", - "sar_signal", - "ma5", - "ma10", - "ma20", - "ma30", - "ma_cross", - "ma5_close_diff", - "ma10_close_diff", - "ma20_close_diff", - "ma30_close_diff", - "ma_close_avg", - "ma_long_short", - "ma_divergence", - "rsi_14", - "rsi_signal", - "boll_upper", - "boll_middle", - "boll_lower", - "boll_signal", - "boll_pattern", - "k_length", - "k_shape", - "k_up_down", - "create_time", + "symbol", + "bar", + "timestamp", + "date_time", + "date_time_us", + "open", + "high", + "low", + "close", + "pre_close", + "close_change", + "pct_chg", + "volume", + "volCcy", + "volCCyQuote", + "buy_sz", + "sell_sz", + # 技术指标字段 + "ma1", + "ma2", + "dif", + "dea", + "macd", + "macd_signal", + "macd_divergence", + "kdj_k", + "kdj_d", + "kdj_j", + "kdj_signal", + "kdj_pattern", + "sar", + "sar_signal", + "ma5", + "ma10", + "ma20", + "ma30", + "ma_cross", + "ma5_close_diff", + "ma10_close_diff", + "ma20_close_diff", + "ma30_close_diff", + "ma_close_avg", + "ma_long_short", + "ma_divergence", + "rsi_14", + "rsi_signal", + "boll_upper", + "boll_middle", + "boll_lower", + "boll_signal", + "boll_pattern", + "k_length", + "k_shape", + "k_up_down", + "create_time", ] self.db_manager = DBData(db_url, self.table_name, self.columns) @@ -85,7 +82,7 @@ class DBMarketData: return self.db_manager.insert_data_to_mysql(df) - + def insert_data_to_mysql_fast(self, df: pd.DataFrame): """ 快速插入K线行情数据(方案2:使用executemany批量插入) @@ -97,9 +94,9 @@ class DBMarketData: if df is None or df.empty: logger.warning("DataFrame为空,无需写入数据库。") return - + self.db_manager.insert_data_to_mysql_fast(df) - + def insert_data_to_mysql_chunk(self, df: pd.DataFrame, chunk_size: int = 1000): """ 分块插入K线行情数据(方案3:适合大数据量) @@ -112,9 +109,9 @@ class DBMarketData: if df is None or df.empty: logger.warning("DataFrame为空,无需写入数据库。") return - + self.db_manager.insert_data_to_mysql_chunk(df, chunk_size) - + def insert_data_to_mysql_simple(self, df: pd.DataFrame): """ 简单插入K线行情数据(方案4:直接使用to_sql,忽略重复) @@ -125,9 +122,9 @@ class DBMarketData: if df is None or df.empty: logger.warning("DataFrame为空,无需写入数据库。") return - + self.db_manager.insert_data_to_mysql_simple(df) - + def query_latest_data(self, symbol: str, bar: str): """ 查询最新数据 @@ -142,8 +139,10 @@ class DBMarketData: """ condition_dict = {"symbol": symbol, "bar": bar} return self.db_manager.query_data(sql, condition_dict, return_multi=False) - - def query_data_before_timestamp(self, symbol: str, bar: str, timestamp: int, limit: int = 100): + + def query_data_before_timestamp( + self, symbol: str, bar: str, timestamp: int, limit: int = 100 + ): """ 根据时间戳查询之前的数据 :param symbol: 交易对 @@ -157,20 +156,25 @@ class DBMarketData: ORDER BY timestamp DESC LIMIT :limit """ - condition_dict = {"symbol": symbol, "bar": bar, "timestamp": timestamp, "limit": limit} + condition_dict = { + "symbol": symbol, + "bar": bar, + "timestamp": timestamp, + "limit": limit, + } return self.db_manager.query_data(sql, condition_dict, return_multi=True) - + def query_data_by_technical_indicators( - self, - symbol: str, - bar: str, - start: str = None, + self, + symbol: str, + bar: str, + start: str = None, end: str = None, macd_signal: str = None, kdj_signal: str = None, rsi_signal: str = None, boll_signal: str = None, - ma_cross: str = None + ma_cross: str = None, ): """ 根据技术指标查询数据 @@ -186,7 +190,7 @@ class DBMarketData: """ conditions = ["symbol = :symbol", "bar = :bar"] condition_dict = {"symbol": symbol, "bar": bar} - + if macd_signal: conditions.append("macd_signal = :macd_signal") condition_dict["macd_signal"] = macd_signal @@ -202,7 +206,7 @@ class DBMarketData: if ma_cross: conditions.append("ma_cross = :ma_cross") condition_dict["ma_cross"] = ma_cross - + # 处理时间范围 if start: start_timestamp = transform_date_time_to_timestamp(start) @@ -214,23 +218,23 @@ class DBMarketData: if end_timestamp: conditions.append("timestamp <= :end") condition_dict["end"] = end_timestamp - + where_clause = " AND ".join(conditions) sql = f""" SELECT * FROM crypto_market_data WHERE {where_clause} ORDER BY timestamp DESC """ - + return self.db_manager.query_data(sql, condition_dict, return_multi=True) - + def query_macd_signals( - self, - symbol: str, - bar: str, + self, + symbol: str, + bar: str, signal: str = None, - start: str = None, - end: str = None + start: str = None, + end: str = None, ): """ 查询MACD信号数据 @@ -242,11 +246,11 @@ class DBMarketData: """ conditions = ["symbol = :symbol", "bar = :bar"] condition_dict = {"symbol": symbol, "bar": bar} - + if signal: conditions.append("macd_signal = :signal") condition_dict["signal"] = signal - + # 处理时间范围 if start: start_timestamp = transform_date_time_to_timestamp(start) @@ -258,24 +262,24 @@ class DBMarketData: if end_timestamp: conditions.append("timestamp <= :end") condition_dict["end"] = end_timestamp - + where_clause = " AND ".join(conditions) sql = f""" SELECT * FROM crypto_market_data WHERE {where_clause} ORDER BY timestamp DESC """ - + return self.db_manager.query_data(sql, condition_dict, return_multi=True) - + def query_kdj_signals( - self, - symbol: str, - bar: str, + self, + symbol: str, + bar: str, signal: str = None, pattern: str = None, - start: str = None, - end: str = None + start: str = None, + end: str = None, ): """ 查询KDJ信号数据 @@ -288,14 +292,14 @@ class DBMarketData: """ conditions = ["symbol = :symbol", "bar = :bar"] condition_dict = {"symbol": symbol, "bar": bar} - + if signal: conditions.append("kdj_signal = :signal") condition_dict["signal"] = signal if pattern: conditions.append("kdj_pattern = :pattern") condition_dict["pattern"] = pattern - + # 处理时间范围 if start: start_timestamp = transform_date_time_to_timestamp(start) @@ -307,25 +311,25 @@ class DBMarketData: if end_timestamp: conditions.append("timestamp <= :end") condition_dict["end"] = end_timestamp - + where_clause = " AND ".join(conditions) sql = f""" SELECT * FROM crypto_market_data WHERE {where_clause} ORDER BY timestamp DESC """ - + return self.db_manager.query_data(sql, condition_dict, return_multi=True) - + def query_ma_signals( - self, - symbol: str, - bar: str, + self, + symbol: str, + bar: str, cross: str = None, long_short: str = None, divergence: str = None, - start: str = None, - end: str = None + start: str = None, + end: str = None, ): """ 查询均线信号数据 @@ -339,7 +343,7 @@ class DBMarketData: """ conditions = ["symbol = :symbol", "bar = :bar"] condition_dict = {"symbol": symbol, "bar": bar} - + if cross: conditions.append("ma_cross = :cross") condition_dict["cross"] = cross @@ -349,7 +353,7 @@ class DBMarketData: if divergence: conditions.append("ma_divergence = :divergence") condition_dict["divergence"] = divergence - + # 处理时间范围 if start: start_timestamp = transform_date_time_to_timestamp(start) @@ -361,24 +365,24 @@ class DBMarketData: if end_timestamp: conditions.append("timestamp <= :end") condition_dict["end"] = end_timestamp - + where_clause = " AND ".join(conditions) sql = f""" SELECT * FROM crypto_market_data WHERE {where_clause} ORDER BY timestamp DESC """ - + return self.db_manager.query_data(sql, condition_dict, return_multi=True) - + def query_bollinger_signals( - self, - symbol: str, - bar: str, + self, + symbol: str, + bar: str, signal: str = None, pattern: str = None, - start: str = None, - end: str = None + start: str = None, + end: str = None, ): """ 查询布林带信号数据 @@ -391,14 +395,14 @@ class DBMarketData: """ conditions = ["symbol = :symbol", "bar = :bar"] condition_dict = {"symbol": symbol, "bar": bar} - + if signal: conditions.append("boll_signal = :signal") condition_dict["signal"] = signal if pattern: conditions.append("boll_pattern = :pattern") condition_dict["pattern"] = pattern - + # 处理时间范围 if start: start_timestamp = transform_date_time_to_timestamp(start) @@ -410,22 +414,18 @@ class DBMarketData: if end_timestamp: conditions.append("timestamp <= :end") condition_dict["end"] = end_timestamp - + where_clause = " AND ".join(conditions) sql = f""" SELECT * FROM crypto_market_data WHERE {where_clause} ORDER BY timestamp DESC """ - + return self.db_manager.query_data(sql, condition_dict, return_multi=True) - + def get_technical_statistics( - self, - symbol: str, - bar: str, - start: str = None, - end: str = None + self, symbol: str, bar: str, start: str = None, end: str = None ): """ 获取技术指标统计信息 @@ -436,7 +436,7 @@ class DBMarketData: """ conditions = ["symbol = :symbol", "bar = :bar"] condition_dict = {"symbol": symbol, "bar": bar} - + # 处理时间范围 if start: start_timestamp = transform_date_time_to_timestamp(start) @@ -448,7 +448,7 @@ class DBMarketData: if end_timestamp: conditions.append("timestamp <= :end") condition_dict["end"] = end_timestamp - + where_clause = " AND ".join(conditions) sql = f""" SELECT @@ -470,10 +470,18 @@ class DBMarketData: FROM crypto_market_data WHERE {where_clause} """ - + return self.db_manager.query_data(sql, condition_dict, return_multi=False) - - def query_market_data_by_symbol_bar(self, symbol: str, bar: str, fields: list = None, start: str = None, end: str = None): + + def query_market_data_by_symbol_bar( + self, + symbol: str, + bar: str, + fields: list = None, + start: str = None, + end: str = None, + table_name: str = "crypto_market_data", + ): """ 根据交易对和K线周期查询数据 :param symbol: 交易对 @@ -487,7 +495,7 @@ class DBMarketData: fields_str = ", ".join(fields) if start is None and end is None: sql = f""" - SELECT {fields_str} FROM crypto_market_data + SELECT {fields_str} FROM {table_name} WHERE symbol = :symbol AND bar = :bar ORDER BY timestamp ASC """ @@ -507,23 +515,28 @@ class DBMarketData: if start > end: start, end = end, start sql = f""" - SELECT {fields_str} FROM crypto_market_data + SELECT {fields_str} FROM {table_name} WHERE symbol = :symbol AND bar = :bar AND timestamp BETWEEN :start AND :end ORDER BY timestamp ASC """ - condition_dict = {"symbol": symbol, "bar": bar, "start": start, "end": end} + condition_dict = { + "symbol": symbol, + "bar": bar, + "start": start, + "end": end, + } elif start is not None: sql = f""" - SELECT {fields_str} FROM crypto_market_data + SELECT {fields_str} FROM {table_name} WHERE symbol = :symbol AND bar = :bar AND timestamp >= :start ORDER BY timestamp ASC """ condition_dict = {"symbol": symbol, "bar": bar, "start": start} elif end is not None: sql = f""" - SELECT {fields_str} FROM crypto_market_data + SELECT {fields_str} FROM {table_name} WHERE symbol = :symbol AND bar = :bar AND timestamp <= :end ORDER BY timestamp ASC """ condition_dict = {"symbol": symbol, "bar": bar, "end": end} - return self.db_manager.query_data(sql, condition_dict, return_multi=True) \ No newline at end of file + return self.db_manager.query_data(sql, condition_dict, return_multi=True) diff --git a/core/statistics/price_volume_stats.py b/core/statistics/price_volume_stats.py index 9d6d5fa..c9a0aea 100644 --- a/core/statistics/price_volume_stats.py +++ b/core/statistics/price_volume_stats.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 OKX_MONITOR_CONFIG, MYSQL_CONFIG, WINDOW_SIZE +from config import OKX_MONITOR_CONFIG, COIN_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 @@ -25,13 +25,13 @@ logger = logging.logger class PriceVolumeStats: def __init__(self): - mysql_user = MYSQL_CONFIG.get("user", "xch") - mysql_password = MYSQL_CONFIG.get("password", "") + mysql_user = COIN_MYSQL_CONFIG.get("user", "xch") + mysql_password = COIN_MYSQL_CONFIG.get("password", "") if not mysql_password: raise ValueError("MySQL password is not set") - mysql_host = MYSQL_CONFIG.get("host", "localhost") - mysql_port = MYSQL_CONFIG.get("port", 3306) - mysql_database = MYSQL_CONFIG.get("database", "okx") + mysql_host = COIN_MYSQL_CONFIG.get("host", "localhost") + mysql_port = COIN_MYSQL_CONFIG.get("port", 3306) + mysql_database = COIN_MYSQL_CONFIG.get("database", "okx") self.db_url = f"mysql+pymysql://{mysql_user}:{mysql_password}@{mysql_host}:{mysql_port}/{mysql_database}" self.db_market_data = DBMarketData(self.db_url) diff --git a/core/trade/__pycache__/ma_break_statistics.cpython-312.pyc b/core/trade/__pycache__/ma_break_statistics.cpython-312.pyc index 4501d00d4d4a35f915cc22bb6aee342ed9c115b9..3c88a56e852e1acb03c1cd6fd9846ef84707876c 100644 GIT binary patch delta 22201 zcmc(H3v^t?mEe2*Q>*{#kGfmkEw%n?{j85=h=&YanOcHejF-dk0->eZ{db?ZKzdQbA+i&FjHX|*Z_uH}u>2OeVCQ+g}gbE@=j z_VpIF74{aj74;Uk6|)S-aLS&N-qN;Gif8te^?KSo6j$|>_g1u3P+Z+p*<00CMR83} zb?>6KMHJWe7C4mX{$fRdWe6CWU|UvocPxKZDEg$v7qa zGU2C!pV}wqH1N~<6#I3Y?h$!g2B&|7X;X603vHR)A}-6P+AraZz)_2-AS6wY)Iieg zQ*jpfp^$>h2C9~`LQxXV_K2)a$JwC}Jy#Q#+b`pCK*kVHa}Ju%39?z7%V+E^i8U?d z24&>}-^}GvMcj{=WNnsA2LExjSTR@NGm<+WG8?%GLr&SUwW`b)Vo z8yj4|%Oi^s(e#OFI0RRYr6-xoOGk=<1GdK(&hy|pzrGZ6w>S94R{>+Rs$xvu^D z8*6SVFwt1kS=H?P%t*Mw#%)B%9KvVmre@58<#V2NC^_ZM3C|V zIU%orVShrNM?RKoq?tk{`3HG3tD=-fg@#pAtXrXxYx*7zWRQG?OV4te_{%e-6lp2B z?W`!J;4%_YDk|lXszvD)Do&f=X*ivrJi~B$ArqiMP*GeBFiX&!Va}-prd<|qNkC0L zxF?sml>%kU|PtEM5ZBt&8KyJFn!7AVsGor1oWSrqr|`gOoZh zFR=W}0XxHxLWP4h6IPXP&+cU7tpQ1GTryzC-l8&j4l8GyCza46RmyJXa#Bi)PXkOK zKi+CnR=o5PN#6s41av7*)RJ#^%&Q}*NmAzPG)a-VttO=lXu|fr4`b=&famwnBq{TC znxshmftol38UJvgBB@D*bKySWTwHD^j+Nw-Nao6eTJsis5>@f`YZFhR@1?+^WPNt` z!u6%dlm&uhk+(12UdMN6IXPh_)S=C@B)b(5d%B}QjSE5>0U-&$<8qV(Un46PV&{IEhQY2 zqHS)1F9+$oL`nfEcRY2B@szekWdnX6AC>X_{Q=UiZq+Da^Pu!-#{u%bI-ks~v6G*u zx2Q5}@T#rexsjA=8Vrq4OjKIi(95p@jBg@6noYrc9>;xD+UW~KRrh#%27K#yzMtnc zNL6tA+6VX^J{uDzNE{gG^tB)E?-}UzwR5l*^95K^A%Y^5(F4)f9_Rx0{w`k+#}^|D zX9Io(f>j7sBQODo>bSyv?Y(qm7mG6>%Zvb@6pua6TS%GK#g>uv+Lk&V7eu}kfJYmZ z`F%b6(=-q(Oi1x%u2mMhBtres+e^x3cD6fs3zCWxKL46y?i!kuMXR*#}7{o%-C0%fmDF zW~?bm3C!46&M#yDY8r06Vk;ZZ8P`lSP4tBt)=ll08l370-L*5cYcKg9x~dX;!LaP4 zE)u(`^|NZnwAv9VtQ~IoMB~0vSUucwS>q-){qw;eYqLLf)Xq8@!j6W}lJy@+W*l2) z9oxf>?Gb~0bj#?f8&Zi=H{5b^>#r4zebKC~E^Mm{)i+PwHDlX2Yug&OZH*YRN6n)O z5VWJT@gcW$lU;P`5<>$;{rkQlHTzDTZc<8 z=>x3kvN9)9uy|N@S(z6pUOp`QL|H&ASwDB&1>-5o?&t5safL<$3^aZ>-1+UqWUOJI zCoRT1B^@jYZ#7H4#}Z9VHF;UCWm#gVDQ`rJG(HUsNjTY%6qZc602b;Xj9N%ZXi5=F zNjcfUGANoomI1Od@~3xZ6Z@SOt1?*%$b&sg<&%6>8eg5p*QD{a^Z7wt8ecE+K~KXV zG*yrx+{t*dT6rg6>v^2-_$mNVN$tJx+ZHwV^{1L*?cD=hr;o>ZFA64LLuUZ(Kptnl zs2V1P+8U(P)$neh1fyCY#cadQKn%sQ zzRT*B5shthOITAnyfvL)F)?siy$bo;!Wz%5{HjUIW%X+0Zx3rKh99`4fU=2om(|Nr zU~^bg0tM(@W74p`aI7b+uL~*b_%>*FRO=7$up)FGYwz>+`Z{8JNZ>FGPW9BpepL&v zL=O4HxQP9VG^w(Q!?Z{;Es*|Ri-NxkR%12s<~a690yvA)OOV9ZUQR*RY!{ayNWcVE z3M@d$Ib1?xt24vIc$q1@gdTlQaw?K%vVh_KE2G5~xQ)sC`)Z;28ZAWL!dg^uTB?N( zwUB0WdQ4djF^$nEPiP#-A{)QqB2PFi1}^J@F+O#$OpFmRWjp54J7E%mpA7B(ZZlXO zCh~;Y^sHa0In2VYhCL3u*CCc%G-qeFFb}*7bBiz zhfcN#3V_*EMJtr-ChwRF*+O#BYHF4X3W0qSA1++?gX%*WLrNjzID2wK%0tYU{}BD` z$$JDP$m|iM-NjfAnX;%=^J=%~lU3UUWp^pviP||uyc|j_lC-MFv6sPe%n&67InMBqqB4R4 zQ-d&?X^sYb7YfxW-KdUhD7FK-u|ZJBGjomqlG$inq($e5yz zF-4tJx*>&bNRiJp7Wed zJZ+X4VNafvA^f(C5l8DfRpK$21v6}#Jm*=gYsd^E6c@&{3na>6TNRrrV-IT+J}hem zSIJdz)!-GXxlsFN%y|G~2S(pI3rr;zu5KliCRm`1`dBTN->a6zwDq^v(lEc4T$oyf z>{ut!T*~CIi(h9Z31rmkY=*$NlwnAKpwA?De3X@QyU<0_C&W>x& zWenN-Gz)N=kP|SF-#MLN5jm9`tChZ^9loAL{8I8yx#u!?RA8{2{3^H5A?}tJ z4Q4D7MF8nI;N=6025XS7kMU#7d9;`uEhp1UU5<%dVk$M4LX;rK^R5t|`vLYC`75{I z9aZ~}_3rEMp%#%x&G&(3ynx_Q1Vf}H|5O%OvneE?lTY$rZa`6w>Xsas?VAXmL-0I; z(+EZpyg*(pIA61HCbUoavk1;1cnQJF2*$`mg?VfhIaQcnj5Al%-0N-Mhej`WU%df; z`+mN^w>>Ir5A?V5-5@|dDg0Pk#}|-Si%NsVSVSR$UIg0^Q15`G-ya3P??E5e#rydk zK#$4}`i}XNzJaKsr@s?+1yNa7-~N6cRz7eNfCC$y;K z5EoT|5#QkrL^JVpU_DP(6yIAQ1;aC%(aHA@9NKp*D)n}D^3PCt-WQen2lnw#ledeX zbmfYB1nk(TA=NdEmOj~BvIN{Op#2bewB%4x5YZQN=f9>TO=S;(BUWd8X73YwBYB0hc`L$sD`xUmg*47j^~R8? za7JG=_RzGx>`L~UP}7dkn!7@G?FqGYhV~o?9q0;~yXT}#mgTaccuvV!o7mfnd}1h? zQ!&;J?9|Xyf9UReLgss8S%Wn1Eey=?|3tI;+pBh4&AxT9Ok%IlIz|)`N9{y$*m1|K zV|CcEddAUogDJoC!}9mo;HM>?@kNtW;qr~M<=evL+dlG!%kP{i*)gh)*s5o34PjeD zq-uGjq%u;mBvM!sS-dgQusKq*CeqLnsb3$dT^p%hdEKg6n)5#yjotC(s`4zyoD}N( z^=_6aDtUe7D=R}a>t_l#gf#9*;nFW9nk?gWsjf%|>A3nNj7+OQy z_k|4|WP!MX^E<%bUx+^iEZsq&T8&D!xx4{Rf5o zK7Gc#trJ*E;?_wgzoe;ok|oDY7W4w>G${uKY9UVErb{7f1U0XPZM*uYrNX72L%WH0 znVURao2R#TCH${LV2zUe42xdTnWX7N8u?|7rclpGhYaw`5_GUF0;?6a0|t5mqdX_U z8iovHZ(W{OFX&@@6&P-KHWTM(oz#GtmONX@im%dKCf&kn1Oum{t`ur|!V}8D=g}vJ zdSW=RY2%&yMp|AlMI`IUr z(ygLyn?X(DBo9e$D0)zr@Y*!i0P%k6xETb04<79SfjV(}_jR zcms@H|K}2Ne)0bf!o&w&m_NijLme~r(drqECsegQtXU5TbH(!DNmuZ$=*zBjB{ zF{253(#7J=nV496ShEr;NSBJcXySD?hBb`~h{YW>vBK^TYnGu@S;ItMxNO~Q*%ocM zY|Bj9wqfl^Uszd8&NaMkOX1FE?pR_gLC210oeTli0gm*<1uud)F_ow;J+tI3iB!f} zV8rIj;aH^GiWVO3>B(!$@=9Z#gFwtn05eZws||0BjXF~7f(-o<=7=Arp4pOm{&-s|3O;1Qq9?J0f+TRUrC9mg;#lIq;n# zA21iiG=ODMOe?5Roghuf_?}$ zD^>6i%;kbDZjn>(j2*l)c8(Pt%O|G=yP(J2IEYu1zgdwVbkdbUEhsn##Uy|gQA`V1 zKE-r^6;n(PSP8`pfR$1#3$QYZ83FT947RsiImOI?RZtAPN?awypkZ7U#q9ZjR#P+w zutgM80#<_57=P81WNE?4fG+|0i#|5=q){L zpX`D&(G@c*E7%(HyOkCQ0kEukn5`wJR;>d3N2|KndOEnbHX5XolgmhZqf0s+Xdr8N zCppu2krdd>#ryzf`8fdCc~EOu%J(0EfDPXP@8K>u4#ZcTe}%l$G{8PWD%RQ!*MRT^ zdJgzMLU5jJU;99TAJIQX@C^V_^&!50e^&s!{(e9ICrCa{{(h~SeUbcXZ54Z%xSAV! zP5{{>PuuPAv>>#%J+7!0z3cl2dV0hoLb@8z*U|U%H)0hfj)0EgYWO@ zLE;D!75lq-diuQlB}njZlMkAm)msq7y#W6-0<0ve>?YcOusUME64ja9-LjILZ*kOr8)Y(~*TiQcM+`Qw z48IH+QpA3P*i8ig8^LeMFI(&qB})|R@)RZ%fbL)C&ZE7Vi)k~0zeicxX+HwYUl`kf z7~+fGcgX2=#fqPSsQ=KVzg+h$OFB1f4ED?(5W?{P{-OWHFb}Y6CD+B<4ZO8T=3qW$ z*WqoQL~yfJmySviu4m~7U)I1K-t4P@=cL6b8tl3y~H!4xmK!N`iU zN49;bA=4Z0Q8l-QY_$_rVcQx~x@n>e#Y#b}WGRT1WRJ97*D$t{am(9<69;GN)=upR z*R8+oS%2ly^-X`tD!o$F_}3hHZ_7_NM{?^XR)uq$W^>nvbJtHj6wci=qP~(_6e+9* z6(e~ikI2M?(0 z*Z4sHn2^@xWpu}hW1S9hk3<}4HTW3Op%Fi4^pLMwtJpK7aQnw>+oiefBT^}EVaYdk zUTjNbrh94rbI65neBeM-zQ3oxpO0p)13$?j41(f60H!BXtOFte8NLBP)Ewv!czfWW z%pbrYs&+s4N_@PDB~R~aQqW`0O7h9BQue~7f7=yc)kY{p3cnlsWMoKQfId3%`aNg1 z!D%Jzr56k+g2kSrK5t&iDTWk%yC9uG7b7XBg!EQOXVUZrP6g=}fz*D(Qpcy}Qvjy+k#nic2LN_6$xYanPAj_7! zC14JLodphL2^=Y83MxTyp)BrN9a0PG6UzgI@uGx;JDKy42X_~16X_v!fF;U9&LUV< z)t!m+HaL$~!ckJZIqb;~;IM|C!eo_SsEAu7;9Qh~Rf6W;4^~)=T=|8{_<}}RRoH;C za-dhG&=(vWvEEnk_`DX86D!SRMRsO}RFJ}Ur<&~F^POM|v<8Md!@R*pWtNmW3B zBj=W=g9Zq~9Ng7&to;zgl|onwdamkf>T3B8lyZs^7@l~GNDa@Sh)Wcqfs5%fwGGjr zgL?iJt|aOdnG2U6b&1^D6>aR`j~xORTgp-rHN|MLD93g#2u`n?CT}%(YAibmt&MIy{Rlk;yy2bQ&)o+2JC=iulLYRtLSXQFdk$i!g=_;*cGw^ zU7ZJ_(w_b!bg{arg;1a%%84OCH&xr38ZZev!<(9IO|`Ue+!SJ}E|!8gPe|3rQuQ@o z*`VmF=N^MC=4an~_3AT^izSGL%1u?vru9^luKj?oYofT%i$yfIU?Pb23+w@t>u+*J zy#q?7n)ADGKD!$MU1k3bTch%OVLI#T?DO{Avg~3;x;!f|PX+=dfG^%g#=jzvL2>|Q zx*+q5$1c4g#ce4#k)u*@O-5A#@4g-%?QZIM-sNHWW02_$34a9hB2Eo7k1fTTz-cq8 zgxxJn$Gtu=OsW+naf_8=nc&VwjB`RWCYxs2PqQRv`>n)%U$K|kN*6&00<-x3L+#wa zp`I>qmHPamMMP~OY*JJ`aELx@0A7zK&7#5AD&U3w!WN=MwU21@$r|G)DBs5$=hHBd+DARFN$6sO(us z#I+&?UkSK2i+W0bKhasOYg+4y*sC7jj2YXXXph*dAK!e%UOl||3RZge6OE(0Uf%oS z-YbUUh@muMG>tTmG>j@khWv=Z9Wi(yPKo}vL=4u5AunQZLO@dde>qJeWv{Av#^#x6Q)xaGQ#2(1I^|(A( zlV_RozLg0jzOF3o?aG3h;bp~ZNDfOqJ0wqOLc;C9>BRk@AU}>>r^n@`h;{ z+{0q8phg0@(q~zQE3X2MZ>56lLP28Xh2#Ab-jr&S(GNQ6|Jxa`km`OX`OuA>b4&dshiodkKc>2$;KJ)w+&wU7fz-wn;zV_S%_yozS!`2|h5^~FVUtoamTkcL1 zO$fBo<(Dp<{6?HNah>HiAhrd;W(3VhqYHOFO#=8rhcs*{--@*D2<~KIe&u%{dKZG7 z2yiJ%pV03?#=Qu@U(fJ+5VRq<55fHi9zd`cffvCtC}kJ#L-Yg|_!|VtjTfEwamo*R zbSZN$&Zto%oCEejAS|6R;g|~SX&xtUem|B*XX{SHwj%8SViSm=vr?RQ>Ao>$X(!jM z*qjO#WcI)zq&U&`1~LCZ08tele*qmgs_5H|KRVH|88zcRdEOZoOxlAb3rDM^%Pj`| z{r>{B9B1Y7M!?of=-#ZuNchf^YyI~pQ?4kN1t3Zx^`Nf z8?icOt<_;`_4v^l>m9=zBWm49(Gx2^v$Acr zH$`muv$pcEt$cj>jBUx|n<84nNXrxVrDyuuJXdVa;Z2``CWB9|9`!;H-Pp2F;bLg! zg6l%?4_X+Ex?#m!8DlWd>I=g9f?56AuzqbsV;pH7%^o{8ap0rna1Li^W^XVOgKAg? zu4j!Qq;|Y29c%ek=6KV2?WZQo>6NEej>%?B#jp-&44tzYXISGLQ%~%IW5UatEvAP#r!} zoL}fK{^Ej|DPj6TNmt+fuBcFg2z4ilsIPW^as0(!eE8U{pQu57VDi@1)Ya72TzmfA zYnPrA%Za<&pctG=_uOVG6@{;kJdJwKm!Z>CN9-BUj>U<_1iu@2BJv!2nt7b``jZi7tcV)Hu7TT-oF8-N$(w2N&HsL8RD%)hm1YJG?#q zMnJ?2kK;^%3jFJFW|Et2ygS@@cj%sbXBvaMkHee`jD^)jEB`cuDv6hnqAri|ZJ;p9 z!0lm3`B5--WFr9n@9P;%Q}hW`R12jXXD&DH4YfZw)98(9NS$sTu-PG$pywEm78Q*_ z7M~!vK<@&IIO&>t#SaqOp%>y`B>M3gJQ(-Bbfb2zZQfvdRB%c+9!FjFKrZOCIU~5g z#vEa1)vU8V?5v+zynDvEXIM6TUs!36INd3%@M7QF-E`-)PA~>&9hV_-> zyJqxt!?KvMi0fxGQ+#cFM+4+XTeE|A0&QNWC!@VYcfS?hU$E117>-78Lkc3#bC^NM zm!QZwR;28NSpeQ)Me7$z;EQYZH?UT=>I((j;nCsx823}1V8MSi&ryX>f3WD*0e928dxcrsL3|M zIcxGb7%bD{F0!?z*eVmG-~y7LlfMbY;l(n?q=Fm|kS(bP$nZ%YSaM8vCcd2}JT!qQ z&o|LLfD1S@N0u_>$XOYeu9kqs1|hR4(o)d}Hp24n3mE|41)EqrSjwa){ma4bhvRv% z^i&A4XCRzYjn*yT&p|#7+JZwWh)-2R9vvR(>$RLTZl8l0uO{EW!=l$FTbPj3Eg+{` zKn{+~Zz)f|fE*mFr^{6sl5zo@YUq{XtjeM$^T(hQ1~ivu8B@yG3{-m5=z_pzY$pUP zs}iSS8bNcS4kYL^$%kNwv}cmxcLril2x4}r|IKBA9-}3}Q_nt^*z$li9_f(`%!761!|1yBwI6X>RuAO`3v-iGBzp)5XyFVp+TIhgB zn|8)8dZZ0#y3+4K+IgRUpeK-GapR$>cqGH1-2fogQm8jHflqOo1$bu-3l_--Uc?#QHUvTMe?aX9me z+B7mSqjsLRUol!ne5cBXn)+1wJ7 z2_`Bln_PF5+XwRXl-H?d>dv?Q@nn6=i0t#uR2Ks{?qq+-!*MN7D% zWoqX~w(ma>+O<2>)-hATg=}RJM@7i7G*VVMTed1(wkp)v8oIMP)bmitFU*t;jc)pJ zVP)`BcggGOSJa{E=Beh7l;7VrwK{ZHXQ=YPWp`J4eaoS#)^-1}`qHagol@Twy$xJK zbe&2avvi~?aLmFnjh6QwNq(%vSK{N3H`hRXpBXo05!ODU?H0)L1Qlx5}J8Dx@abN+Ic*6 zqD^@82A>^BIZbTcTrWskVEufc5$F)+#c8QGEmM#kWziSRX$ARV_{`>{5qQ*-#3y;W z7|#tneOf+!j8_dj1Ldi(%pkg-!H<-IOC1!u3fNgmHsxjr${2SmaE)nPr9dOgAn$#^ zHKlREITXv=4O}zj!g2^sBW#cyOgC5?EisXYK_ok^01ao2ai0LLEsYC*_9Di80l4-w zt}ekH2W}4KCVMLfdn5>Ye;1gJG^QigT|WSxGmYnr@uq?2N~;ok5~g7&^=H7%P2*xu zirhKi<^dO1l2}(T0(Pay{vEL0G_yLv#jd1Wh$O+0k)OuLo{IDHfM1Zt$G(d57XiNz z`9VHe0d`khU^P_IB!g%vFvg`^5sHFQ*OD?g4G>9~GC2M72d5!*a8h1!a2iquXON}L zf1APSDQ*=H(5he}O?R&T7HRY#jzw#2{uvvOHtYa-$5)|B+NZ3|NB&w{CRP?c5)tdHA<7 zG!My|1AR3Swc~7Htoij#uWULK46Car>O)I6gzGnk>UVwWDj2H|l`ahz-ZA4^_IT?R zSN?D-`QSi#Frv;Hx%y6uB5V|f+f>gOC}pbEAFNRZJcPFY7W2*;rpg7HI%wGIKfTsx_B_1oJvhp z*gWBzzA1lr$!4*j2c|7`l-x4WHI;v{SEQyCbYLn= zEGU>iwtL!AMTJ*GtvfCrNYmv%v-J~P@DFYMv-BaPH&CReH&CRex9{0)KQr5K5NPdl zZLr~mQJ_Re*QJ|1Z)CD|^6KH`>`C&|!wxI%|LNc0>cBtR(&raL51t}fM;wa7ICTw@ zx+6vGqvYNryQRFJC4Y9rqdW_<*BdPV8u|H=b~ZqokCvO?0#a0R*vr3$lis%xoF^xa zI@xaW($U9l?_g2i1rSy6J`6gEM_jHxW;5Zw9lqM~2aZAf7@d)aNcXWvmVxQQ@NXda zD+E74Fb*J^;rAZ)?eFTOON7Uq^u~&xzI{qc2OCV-G6+zG5Dsv8da2Kt^bW3JUnAcc zbcum!ACd52y<#~`ul`M!R1Y_^xk{#@9wPS2n`X;5!lyzXn`X+ljVMQ5VMFO>HV5ey z{!u*?&TW{IOda{eu>Dfo(C=6Uy0rPwrS{+t)WJs)r#=;2g=p|7Popr8W0D5Z{t;rY zAUKaeja`+EK!d=FK#RbJK!?DNK#w2?fdPR7K^6ih0wV$!`cuUyPdBDt#PSOeD+I8M z7oR5(quCII((di(=pX0{v>(Pl>VseE)rwKMI%IZ0Om2*L21|Ge!6<;JY#)g8V~Dc- z5SjZ5a?WEivxnrLbQxYmT;9q1e0@>Y&-0}9WT6|~_58OGyb53dHm1zA)8GB#^xHfR zCVrfpI%(N=8qx0{_)`F1u>H2tgPDbx%Ew0YOnYkLSap z*Xl>Wi%PfvPmhFWQSx>JpN}9H?IMpcWzdIE4eH@qf!HbppCX|CIyxGsAsNlw51&;A z+U0OQ%m+#J(}n6QSl(3xuaWzoc8G7q56RHemAP-@Tk#e|a)4th2flw9844SVXN{F% zVdRb_KlbBdqygU zsMGduek@VYzOj(n(`yuBC=xkx+Qt4QdG2&U)k;+3bJSCYuXj`q@pyd!_~?f^@P3Qj ze*+Me9N7ojes;PzrwNjN2nu1`iA%Ju{uWwbCCf)G60e->7`;)%@(+O+96vPTO-fU( zVmP~mnv=UcF4FfxeGnpMMKu}c$QV0Sx0Qrwl>W2va7Qh=YU(mT^H&PsNfP=w*9mF4Du1geh z<(!#u6-09K@V_H3vS{&Caky|}q<-m?FLeKdp`JtGl84|k#_L%ftbD~4i+xUx2plBC zijYB3C8C)WRWTr-rlZUVpvvuvBgv z@3_I>{raJdC35$;{{{oM>xbB*5_!Sc&N&9}<9_izReyt~=K^xQd;>dSxxwK5%Yt%w z(e>SI8!I=DHO~d%Pjkx6ST5e@j!HJMP)+?DgZBw267N&ZH|TwCw?qrMB(G~;(cEAF zoy(QWUE{!o+nkZfT?8lL5u59JW}V!A#hNoGhdX}6YItVP<9p^ZFr{Qn**B1X{Q%p` z%FA41YtgozV<6$042buSpkhpix_WO=WNsf(oat(!4-21@XK_^B(a-y;#gCK4kJBtZeBowNBXv#~^sJErHW{P(d+I{b{Vh*7s={7ksFpBw1$HS)#404c`*$HaJUl`WHn j(0%|n5PW53*p)wF7X5@NC-%U|W(xUz^1~Ma! delta 12075 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 XBqoM!CaUjOn+GiH%IgeL+W!9mog?!g diff --git a/core/trade/ma_break_statistics.py b/core/trade/ma_break_statistics.py index accba00..140b733 100644 --- a/core/trade/ma_break_statistics.py +++ b/core/trade/ma_break_statistics.py @@ -17,11 +17,16 @@ from PIL import Image as PILImage from config import ( OKX_MONITOR_CONFIG, US_STOCK_MONITOR_CONFIG, - MYSQL_CONFIG, + COIN_MYSQL_CONFIG, + A_MYSQL_CONFIG, WINDOW_SIZE, BINANCE_MONITOR_CONFIG, + A_STOCK_MONITOR_CONFIG, + A_INDEX_MONITOR_CONFIG, ) +from core.biz.metrics_calculation import MetricsCalculation from core.db.db_market_data import DBMarketData +from core.db.db_astock import DBAStockData from core.db.db_binance_data import DBBinanceData from core.db.db_huge_volume_data import DBHugeVolumeData from core.utils import timestamp_to_datetime, transform_date_time_to_timestamp @@ -44,21 +49,40 @@ class MaBreakStatistics: def __init__( self, is_us_stock: bool = False, - is_binance: bool = False, + is_astock: bool = False, + is_aindex: bool = False, + is_binance: bool = True, + buy_by_long_period: dict = {"by_week": False, "by_month": False}, + long_period_condition: dict = {"ma5>ma10": True, "ma10>ma20": False, "macd_diff>0": True, "macd>0": True}, commission_per_share: float = 0.0008, ): - mysql_user = MYSQL_CONFIG.get("user", "xch") - mysql_password = MYSQL_CONFIG.get("password", "") - if not mysql_password: - raise ValueError("MySQL password is not set") - mysql_host = MYSQL_CONFIG.get("host", "localhost") - mysql_port = MYSQL_CONFIG.get("port", 3306) - mysql_database = MYSQL_CONFIG.get("database", "okx") + if is_astock or is_aindex: + mysql_user = A_MYSQL_CONFIG.get("user", "root") + mysql_password = A_MYSQL_CONFIG.get("password", "") + if not mysql_password: + raise ValueError("MySQL password is not set") + mysql_host = A_MYSQL_CONFIG.get("host", "localhost") + mysql_port = A_MYSQL_CONFIG.get("port", 3306) + mysql_database = A_MYSQL_CONFIG.get("database", "astock") + else: + mysql_user = COIN_MYSQL_CONFIG.get("user", "xch") + mysql_password = COIN_MYSQL_CONFIG.get("password", "") + if not mysql_password: + raise ValueError("MySQL password is not set") + mysql_host = COIN_MYSQL_CONFIG.get("host", "localhost") + mysql_port = COIN_MYSQL_CONFIG.get("port", 3306) + mysql_database = COIN_MYSQL_CONFIG.get("database", "okx") self.db_url = f"mysql+pymysql://{mysql_user}:{mysql_password}@{mysql_host}:{mysql_port}/{mysql_database}" self.db_huge_volume_data = DBHugeVolumeData(self.db_url) self.is_us_stock = is_us_stock + self.is_astock = is_astock + self.is_aindex = is_aindex + if self.is_us_stock: + self.date_time_field = "date_time_us" + else: + self.date_time_field = "date_time" self.is_binance = is_binance if is_us_stock: self.symbols = US_STOCK_MONITOR_CONFIG.get("volume_monitor", {}).get( @@ -71,6 +95,28 @@ class MaBreakStatistics: "initial_date", "2014-11-30 00:00:00" ) self.db_market_data = DBMarketData(self.db_url) + elif is_astock: + self.symbols = A_STOCK_MONITOR_CONFIG.get("volume_monitor", {}).get( + "symbols", ["000001.SH"] + ) + self.bars = A_STOCK_MONITOR_CONFIG.get("volume_monitor", {}).get( + "bars", ["5m"] + ) + self.initial_date = A_STOCK_MONITOR_CONFIG.get("volume_monitor", {}).get( + "initial_date", "2014-11-30 00:00:00" + ) + self.db_market_data = DBAStockData(self.db_url) + elif is_aindex: + self.symbols = A_INDEX_MONITOR_CONFIG.get("volume_monitor", {}).get( + "symbols", ["000001.SH"] + ) + self.bars = A_INDEX_MONITOR_CONFIG.get("volume_monitor", {}).get( + "bars", ["5m"] + ) + self.initial_date = A_INDEX_MONITOR_CONFIG.get("volume_monitor", {}).get( + "initial_date", "2014-11-30 00:00:00" + ) + self.db_market_data = DBAStockData(self.db_url) else: if is_binance: self.symbols = BINANCE_MONITOR_CONFIG.get("volume_monitor", {}).get( @@ -98,12 +144,36 @@ class MaBreakStatistics: self.commission_per_share = commission_per_share self.trade_strategy_config = self.get_trade_strategy_config() self.main_strategy = self.trade_strategy_config.get("均线系统策略", None) + self.buy_by_long_period = buy_by_long_period + self.long_period_condition = long_period_condition def get_trade_strategy_config(self): with open("./json/trade_strategy.json", "r", encoding="utf-8") as f: trade_strategy_config = json.load(f) return trade_strategy_config + def get_by_long_period_desc(self): + by_week = self.buy_by_long_period.get("by_week", False) + by_month = self.buy_by_long_period.get("by_month", False) + by_long_period = "" + if by_week: + by_long_period += "1W" + if by_month: + by_long_period += "1M" + if by_long_period == "": + return "no_long_period_judge" + by_condition = "" + if self.long_period_condition.get("ma5>ma10", False): + by_condition += "ma5gtma10" + if self.long_period_condition.get("ma10>ma20", False): + by_condition += "_ma10gtma20" + if self.long_period_condition.get("macd_diff>0", False): + by_condition += "_macd_diffgt0" + if self.long_period_condition.get("macd>0", False): + by_condition += "_macdgt0" + return by_long_period + "_" + by_condition + + def batch_statistics(self, strategy_name: str = "全均线策略"): if self.is_us_stock: self.stats_output_dir = ( @@ -119,6 +189,38 @@ class MaBreakStatistics: self.stats_chart_dir = ( f"./output/trade_sandbox/ma_strategy/binance/chart/{strategy_name}/" ) + elif self.is_astock: + long_period_desc = self.get_by_long_period_desc() + if len(long_period_desc) > 0: + self.stats_output_dir = ( + f"./output/trade_sandbox/ma_strategy/astock/{long_period_desc}/excel/{strategy_name}/" + ) + self.stats_chart_dir = ( + f"./output/trade_sandbox/ma_strategy/astock/{long_period_desc}/chart/{strategy_name}/" + ) + else: + self.stats_output_dir = ( + f"./output/trade_sandbox/ma_strategy/astock/excel/{strategy_name}/" + ) + self.stats_chart_dir = ( + f"./output/trade_sandbox/ma_strategy/astock/chart/{strategy_name}/" + ) + elif self.is_aindex: + long_period_desc = self.get_by_long_period_desc() + if len(long_period_desc) > 0: + self.stats_output_dir = ( + f"./output/trade_sandbox/ma_strategy/aindex/{long_period_desc}/excel/{strategy_name}/" + ) + self.stats_chart_dir = ( + f"./output/trade_sandbox/ma_strategy/aindex/{long_period_desc}/chart/{strategy_name}/" + ) + else: + self.stats_output_dir = ( + f"./output/trade_sandbox/ma_strategy/aindex/excel/{strategy_name}/" + ) + self.stats_chart_dir = ( + f"./output/trade_sandbox/ma_strategy/aindex/chart/{strategy_name}/" + ) else: self.stats_output_dir = ( f"./output/trade_sandbox/ma_strategy/okx/excel/{strategy_name}/" @@ -192,9 +294,11 @@ class MaBreakStatistics: total_commission = round(total_commission, 4) total_buy_commission = round(total_buy_commission, 4) total_sell_commission = round(total_sell_commission, 4) + symbol_name = str(symbol_bar_data["symbol_name"].iloc[0]) account_value_chg_list.append({ "strategy_name": strategy_name, "symbol": symbol, + "symbol_name": symbol_name, "bar": bar, "total_buy_commission": total_buy_commission, "total_sell_commission": total_sell_commission, @@ -209,6 +313,7 @@ class MaBreakStatistics: [ "strategy_name", "symbol", + "symbol_name", "bar", "total_buy_commission", "total_sell_commission", @@ -221,7 +326,7 @@ class MaBreakStatistics: ] account_value_statistics_df = ( - ma_break_market_data.groupby(["symbol", "bar"])["end_account_value"] + ma_break_market_data.groupby(["symbol", "symbol_name", "bar"])["end_account_value"] .agg( account_value_max="max", account_value_min="min", @@ -237,6 +342,7 @@ class MaBreakStatistics: [ "strategy_name", "symbol", + "symbol_name", "bar", "account_value_max", "account_value_min", @@ -249,7 +355,7 @@ class MaBreakStatistics: # 依据symbol和bar分组,统计每个symbol和bar的interval_minutes的max, min, mean, std, median, count interval_minutes_df = ( - ma_break_market_data.groupby(["symbol", "bar"])["interval_minutes"] + ma_break_market_data.groupby(["symbol", "symbol_name", "bar"])["interval_minutes"] .agg( interval_minutes_max="max", interval_minutes_min="min", @@ -265,6 +371,7 @@ class MaBreakStatistics: [ "strategy_name", "symbol", + "symbol_name", "bar", "interval_minutes_max", "interval_minutes_min", @@ -335,6 +442,23 @@ class MaBreakStatistics: strategy_info["买入策略"] = buy_and_text + " 或者 \n" + buy_or_text else: strategy_info["买入策略"] = buy_and_text + + # 假如根据长周期判断买入,则需要设置长周期策略 + by_week = self.buy_by_long_period.get("by_week", False) + by_month = self.buy_by_long_period.get("by_month", False) + if by_week: + strategy_info["买入策略"] += "根据周线指标,\n" + if by_month: + strategy_info["买入策略"] += "根据月线指标,\n" + if self.long_period_condition.get("ma5>ma10", False): + strategy_info["买入策略"] += "ma5>ma10, \n" + if self.long_period_condition.get("ma10>ma20", False): + strategy_info["买入策略"] += "ma10>ma20, \n" + if self.long_period_condition.get("macd_diff>0", False): + strategy_info["买入策略"] += "macd_diff>0, \n" + if self.long_period_condition.get("macd>0", False): + strategy_info["买入策略"] += "macd>0, \n" + strategy_info["买入策略"] = strategy_info["买入策略"].strip() sell_dict = strategy_config.get("sell", {}) sell_and_list = sell_dict.get("and", []) sell_or_list = sell_dict.get("or", []) @@ -349,6 +473,7 @@ class MaBreakStatistics: strategy_info["卖出策略"] = sell_and_text + " 或者 \n" + sell_or_text else: strategy_info["卖出策略"] = sell_and_text + strategy_info["卖出策略"] = strategy_info["卖出策略"].strip() # 将strategy_info转换为pd.DataFrame strategy_info_df = pd.DataFrame([strategy_info]) return strategy_info_df @@ -384,22 +509,24 @@ class MaBreakStatistics: market_data.reset_index(drop=True, inplace=True) ma_break_market_data_pair_list = [] ma_break_market_data_pair = {} - if self.is_us_stock: - date_time_field = "date_time_us" - else: - date_time_field = "date_time" close_mean = market_data["close"].mean() self.update_initial_capital(close_mean) logger.info( - f"成功获取{symbol}数据:{len(market_data)}根{bar}K线,开始日期={market_data[date_time_field].min()},结束日期={market_data[date_time_field].max()}" + f"成功获取{symbol}数据:{len(market_data)}根{bar}K线,开始日期={market_data[self.date_time_field].min()},结束日期={market_data[self.date_time_field].max()}" ) account_value = self.initial_capital for index, row in market_data.iterrows(): + if self.is_astock: + symbol_name = row["symbol_name"] + elif self.is_aindex: + symbol_name = row["symbol_name"] + else: + symbol_name = row["symbol"] ma_cross = row["ma_cross"] timestamp = row["timestamp"] - date_time = row[date_time_field] + date_time = row[self.date_time_field] close = row["close"] ma5 = row["ma5"] ma10 = row["ma10"] @@ -411,7 +538,6 @@ class MaBreakStatistics: if ma_break_market_data_pair.get("begin_timestamp", None) is None: buy_condition = self.fit_strategy( strategy_name=strategy_name, - market_data=market_data, row=row, behavior="buy", ) @@ -431,6 +557,7 @@ class MaBreakStatistics: ma_break_market_data_pair = {} ma_break_market_data_pair["symbol"] = symbol + ma_break_market_data_pair["symbol_name"] = symbol_name ma_break_market_data_pair["bar"] = bar ma_break_market_data_pair["begin_timestamp"] = timestamp ma_break_market_data_pair["begin_date_time"] = date_time @@ -449,12 +576,12 @@ class MaBreakStatistics: else: sell_condition = self.fit_strategy( strategy_name=strategy_name, - market_data=market_data, row=row, behavior="sell", ) - if sell_condition: + if sell_condition or index == len(market_data) - 1: + # 达到卖出条件或者最后一条数据,则卖出 shares = ma_break_market_data_pair["shares"] entry_price = ma_break_market_data_pair["begin_close"] exit_price = close @@ -525,8 +652,10 @@ class MaBreakStatistics: * 100 ) pct_chg = round(pct_chg, 4) + symbol_name = ma_break_market_data["symbol_name"].iloc[0] market_data_pct_chg = { "symbol": symbol, + "symbol_name": symbol_name, "bar": bar, "pct_chg": pct_chg, "initial_capital": self.initial_capital, @@ -604,52 +733,191 @@ class MaBreakStatistics: data = pd.DataFrame() start_date = datetime.strptime(self.initial_date, "%Y-%m-%d") end_date = datetime.strptime(self.end_date, "%Y-%m-%d") + timedelta(days=1) - fields = [ - "symbol", - "bar", - "timestamp", - "date_time", - "date_time_us", - "open", - "high", - "low", - "close", - "volume", - "sar_signal", - "ma5", - "ma10", - "ma20", - "ma30", - "ma_cross", - "dif", - "dea", - "macd", - ] + table_name = "" + if self.is_astock: + if bar == "1D": + table_name = "stock_daily_price_from_2021" + elif bar == "1W": + table_name = "stock_weekly_price_from_2020" + elif bar == "1M": + table_name = "stock_monthly_price_from_2015" + elif self.is_aindex: + if bar == "1D": + table_name = "index_daily_price_from_2021" + elif bar == "1W": + table_name = "index_weekly_price_from_2020" + elif bar == "1M": + table_name = "index_monthly_price_from_2015" + elif self.is_us_stock: + table_name = "crypto_market_data" + elif self.is_binance: + table_name = "crypto_binance_data" + else: + table_name = "crypto_binance_data" + + if self.is_astock or self.is_aindex: + fields = [ + "a.ts_code as symbol", + "b.name as symbol_name", + f"'{bar}' as bar", + "0 as timestamp", + "trade_date as date_time", + "open", + "high", + "low", + "close", + "vol as volume", + "MA5 as ma5", + "MA10 as ma10", + "MA20 as ma20", + "MA30 as ma30", + "均线交叉 as ma_cross", + "DIF as dif", + "DEA as dea", + "MACD as macd", + ] + else: + fields = [ + "symbol", + "bar", + "timestamp", + "date_time", + "date_time_us", + "open", + "high", + "low", + "close", + "volume", + "sar_signal", + "ma5", + "ma10", + "ma20", + "ma30", + "ma_cross", + "dif", + "dea", + "macd", + ] while start_date < end_date: current_end_date = min(start_date + timedelta(days=180), end_date) start_date_str = start_date.strftime("%Y-%m-%d") current_end_date_str = current_end_date.strftime("%Y-%m-%d") logger.info(f"获取{symbol}数据:{start_date_str}至{current_end_date_str}") current_data = self.db_market_data.query_market_data_by_symbol_bar( - symbol, bar, fields, start=start_date_str, end=current_end_date_str + symbol, bar, fields, start=start_date_str, end=current_end_date_str, table_name=table_name ) if current_data is not None and len(current_data) > 0: current_data = pd.DataFrame(current_data) data = pd.concat([data, current_data]) start_date = current_end_date data.drop_duplicates(inplace=True) - if self.is_us_stock: - date_time_field = "date_time_us" - else: - date_time_field = "date_time" - data.sort_values(by=date_time_field, inplace=True) + data.sort_values(by=self.date_time_field, inplace=True) data.reset_index(drop=True, inplace=True) + if self.is_astock or self.is_aindex: + data = self.update_data(data) return data + + def get_long_period_data(self, symbol: str, bar: str, end_date: str): + """ + 获取长周期数据 + :param data: 数据 + :return: 长周期数据 + """ + if not (self.is_astock or self.is_aindex): + return None + table_name = "" + if self.is_astock: + if bar == "1M": + table_name = "stock_monthly_price_from_2015" + elif bar == "1W": + table_name = "stock_weekly_price_from_2020" + else: + pass + elif self.is_aindex: + if bar == "1M": + table_name = "index_monthly_price_from_2015" + elif bar == "1W": + table_name = "index_weekly_price_from_2020" + else: + pass + if len(end_date) != 10: + end_date = self.change_date_format(end_date) + if bar == "1M": + # 获取上两个月的日期 + last_date = datetime.strptime(end_date, "%Y-%m-%d") - timedelta(days=60) + last_date = last_date.strftime("%Y-%m-%d") + elif bar == "1W": + # 获取上两周的日期 + last_date = datetime.strptime(end_date, "%Y-%m-%d") - timedelta(days=14) + last_date = last_date.strftime("%Y-%m-%d") + else: + last_date = None + + if len(table_name) == 0 or last_date is None: + return None + fields = [ + "a.ts_code as symbol", + "b.name as symbol_name", + f"'{bar}' as bar", + "0 as timestamp", + "trade_date as date_time", + "open", + "high", + "low", + "close", + "vol as volume", + "MA5 as ma5", + "MA10 as ma10", + "MA20 as ma20", + "MA30 as ma30", + "均线交叉 as ma_cross", + "DIF as dif", + "DEA as dea", + "MACD as macd", + ] + data = self.db_market_data.query_market_data_by_symbol_bar( + symbol, bar, fields, start=last_date, end=end_date, table_name=table_name + ) + if data is not None and len(data) > 0: + data = pd.DataFrame(data) + data.sort_values(by="date_time", inplace=True) + latest_row = data.iloc[-1] + if (latest_row["ma5"] is None or + latest_row["ma10"] is None or + latest_row["ma20"] is None or + latest_row["dif"] is None or + latest_row["macd"] is None): + return None + return latest_row + else: + return None + + + def update_data(self, data: pd.DataFrame): + """ + 更新数据 + 1. 将date_time列中的20210104这种格式,替换为2021-01-04的格式 + 2. 将date_time转换为timestamp,并更新timestamp列 + 3. 通过MetricsCalculation的ma5102030方法更新ma_cross列 + :param data: 数据 + :return: 更新后的数据 + """ + data["date_time"] = data["date_time"].apply(lambda x: self.change_date_format(x)) + data["timestamp"] = data["date_time"].apply(lambda x: transform_date_time_to_timestamp(x)) + metrics_calculation = MetricsCalculation() + data = metrics_calculation.ma5102030(data) + return data + + def change_date_format(self, date_text: str): + # 将20210104这种格式,替换为2021-01-04的格式 + if len(date_text) == 8: + return date_text[0:4] + "-" + date_text[4:6] + "-" + date_text[6:8] + else: + return date_text def fit_strategy( self, strategy_name: str = "全均线策略", - market_data: pd.DataFrame = None, row: pd.Series = None, behavior: str = "buy", ): @@ -661,6 +929,45 @@ class MaBreakStatistics: if condition_dict is None: logger.error(f"策略{strategy_name}的{behavior}条件不存在") return False + + and_list = condition_dict.get("and", []) + + condition = True + condition = self.get_judge_result(row, and_list, "and", condition) + or_list = condition_dict.get("or", []) + condition = self.get_judge_result(row, or_list, "or", condition) + + if behavior == "buy" and condition: + # 如果满足条件,则判断是否根据长周期指标买入 + bar = row["bar"] + if (self.is_astock or self.is_aindex) and bar == "1D": + date_time = row["date_time"] + long_period_condition_list = [] + if self.long_period_condition.get("ma5>ma10", False): + long_period_condition_list.append("ma5>ma10") + if self.long_period_condition.get("ma10>ma20", False): + long_period_condition_list.append("ma10>ma20") + if self.long_period_condition.get("macd_diff>0", False): + long_period_condition_list.append("macd_diff>0") + if self.long_period_condition.get("macd>0", False): + long_period_condition_list.append("macd>0") + if len(long_period_condition_list) > 0: + if self.buy_by_long_period.get("by_week", False): + long_period_data = self.get_long_period_data(row["symbol"], "1W", date_time) + if long_period_data is not None: + condition = self.get_judge_result(long_period_data, long_period_condition_list, "and", condition) + if not condition: + logger.info(f"根据周线指标,{row['symbol']}不满足买入条件") + if self.buy_by_long_period.get("by_month", False): + long_period_data = self.get_long_period_data(row["symbol"], "1M", date_time) + if long_period_data is not None: + condition = self.get_judge_result(long_period_data, long_period_condition_list, "and", condition) + if not condition: + logger.info(f"根据月线指标,{row['symbol']}不满足买入条件") + + return condition + + def get_judge_result(self, row: pd.Series, condition_list: list, and_or: str = "and", raw_condition: bool = True): ma_cross = row["ma_cross"] if pd.isna(ma_cross) or ma_cross is None: ma_cross = "" @@ -670,107 +977,107 @@ class MaBreakStatistics: ma20 = float(row["ma20"]) ma30 = float(row["ma30"]) close = float(row["close"]) - volume_pct_chg = float(row["volume_pct_chg"]) + if "volume_pct_chg" in list(row.index) and row["volume_pct_chg"] is not None: + volume_pct_chg = float(row["volume_pct_chg"]) + else: + volume_pct_chg = None macd_diff = float(row["dif"]) macd_dea = float(row["dea"]) macd = float(row["macd"]) - - and_list = condition_dict.get("and", []) - - condition = True - for and_condition in and_list: + if and_or == "and": + for and_condition in condition_list: if and_condition == "5上穿10": - condition = condition and ("5上穿10" in ma_cross) + raw_condition = raw_condition and ("5上穿10" in ma_cross) elif and_condition == "10上穿20": - condition = condition and ("10上穿20" in ma_cross) + raw_condition = raw_condition and ("10上穿20" in ma_cross) elif and_condition == "20上穿30": - condition = condition and ("20上穿30" in ma_cross) + raw_condition = raw_condition and ("20上穿30" in ma_cross) elif and_condition == "ma5>ma10": - condition = condition and (ma5 > ma10) + raw_condition = raw_condition and (ma5 > ma10) elif and_condition == "ma10>ma20": - condition = condition and (ma10 > ma20) + raw_condition = raw_condition and (ma10 > ma20) elif and_condition == "ma20>ma30": - condition = condition and (ma20 > ma30) + raw_condition = raw_condition and (ma20 > ma30) elif and_condition == "close>ma20": - condition = condition and (close > ma20) - elif and_condition == "volume_pct_chg>0.2": - condition = condition and (volume_pct_chg > 0.2) + raw_condition = raw_condition and (close > ma20) + elif and_condition == "volume_pct_chg>0.2" and volume_pct_chg is not None: + raw_condition = raw_condition and (volume_pct_chg > 0.2) elif and_condition == "macd_diff>0": - condition = condition and (macd_diff > 0) + raw_condition = raw_condition and (macd_diff > 0) elif and_condition == "macd_dea>0": - condition = condition and (macd_dea > 0) + raw_condition = raw_condition and (macd_dea > 0) elif and_condition == "macd>0": - condition = condition and (macd > 0) + raw_condition = raw_condition and (macd > 0) elif and_condition == "10下穿5": - condition = condition and ("10下穿5" in ma_cross) + raw_condition = raw_condition and ("10下穿5" in ma_cross) elif and_condition == "20下穿10": - condition = condition and ("20下穿10" in ma_cross) + raw_condition = raw_condition and ("20下穿10" in ma_cross) elif and_condition == "30下穿20": - condition = condition and ("30下穿20" in ma_cross) + raw_condition = raw_condition and ("30下穿20" in ma_cross) elif and_condition == "ma5ma10": - condition = condition or (ma5 > ma10) + raw_condition = raw_condition or (ma5 > ma10) elif or_condition == "ma10>ma20": - condition = condition or (ma10 > ma20) + raw_condition = raw_condition or (ma10 > ma20) elif or_condition == "ma20>ma30": - condition = condition or (ma20 > ma30) + raw_condition = raw_condition or (ma20 > ma30) elif or_condition == "close>ma20": - condition = condition or (close > ma20) - elif or_condition == "volume_pct_chg>0.2": - condition = condition or (volume_pct_chg > 0.2) + raw_condition = raw_condition or (close > ma20) + elif or_condition == "volume_pct_chg>0.2" and volume_pct_chg is not None: + raw_condition = raw_condition or (volume_pct_chg > 0.2) elif or_condition == "macd_diff>0": - condition = condition or (macd_diff > 0) + raw_condition = raw_condition or (macd_diff > 0) elif or_condition == "macd_dea>0": - condition = condition or (macd_dea > 0) + raw_condition = raw_condition or (macd_dea > 0) elif or_condition == "macd>0": - condition = condition or (macd > 0) + raw_condition = raw_condition or (macd > 0) elif or_condition == "10下穿5": - condition = condition or ("10下穿5" in ma_cross) + raw_condition = raw_condition or ("10下穿5" in ma_cross) elif or_condition == "20下穿10": - condition = condition or ("20下穿10" in ma_cross) + raw_condition = raw_condition or ("20下穿10" in ma_cross) elif or_condition == "30下穿20": - condition = condition or ("30下穿20" in ma_cross) + raw_condition = raw_condition or ("30下穿20" in ma_cross) elif or_condition == "ma5 MaBreakStatistics 批量统计。 + - 聚合多个策略结果输出合并的资金曲线/收益数据。 + - 可配置是否美股/是否Binance、佣金参数等。 + +- trade_sandbox_main.py + - 功能: 均值回归策略沙盒回测,批量跑不同方案并输出Excel和图表。 + - 要点: + - 批量维度:symbols × bars × solutions。 + - 统计指标:止盈/止损次数与占比、收益分布、均值等,按 `symbol, bar` 分组。 + - 自动绘制2×2面板图(不同bar),嵌入Excel文件。 + - 可仅跑5m,也可多周期。 + +- market_data_from_itick_main.py + - 功能: 按时间段分片下载美股/ETF数据(示例使用AlphaVantage类命名),处理并保存CSV,展示统计。 + - 要点: + - 配置symbol/interval/分段天数,降低单次请求压力。 + - 下载→处理→保存→打印统计的串行流程。 + - 作为离线数据拉取脚本模版使用。 + +- auto_schedule.py + - 功能: 简易定时调度器,周期性运行 `huge_volume_main.py`。 + - 要点: + - 使用 schedule 每小时执行一次;记录执行时间与耗时。 + - 兼容不同当前工作目录定位脚本路径。 + - 适合本地常驻调度。 + +- auto_update_market_data.py + - 功能: 同上,定时运行 `huge_volume_main.py`(与 auto_schedule.py 功能基本一致)。 + - 要点: + - 同样是每小时执行,日志与输出一致。 + - 可按需要二选一保留,避免重复。 + +- update_data_main.py + - 功能: 批量更新数据库中行情数据的技术指标与美东时间字段。 + - 要点: + - 从DB读取全量数据→按timestamp排序→更新 `date_time_us` 与 `SAR` 指标→回写DB。 + - 支持美股/加密两套symbols与bars配置。 + - 严格校验MySQL配置是否存在密码。 + +- trade_data_main.py + - 功能: 交易明细拉取与补齐(API与DB结合),返回时间段内整理过的交易数据。 + - 要点: + - 依据DB现有最早/最新时间决定是否调用API补齐前段或后段。 + - 默认时间范围从配置初始时间到当前;最终结果从DB聚合、排序、去重。 + - 依赖 TradeData 实现API交互与DB写入。 + +- statistics_main.py + - 功能: 批量价格/成交量统计。 + - 要点: + - 调用 PriceVolumeStats 批处理,返回价格统计、成交量统计与联动统计结果。 + - 用作一次性统计入口,便于离线分析。 + +- trade_main.py + - 功能: 交易流程演示脚本(三段式示例:开空→现货卖出→平空)。 + - 要点: + - 封装 QuantTrader,对接实盘/模拟(由配置SANDBOX决定)。 + - 展示下单参数:逐仓/全仓、张数、杠杆、缓冲比例;以及余额检查、下单、平仓流程。 + - 以日志形式串联完整交易生命周期,适合作为交易API联通性验证与流程Demo。 + +- huge_volume_main.py + - 功能: 巨量成交检测与统计分析的核心入口(OKX与Binance均支持)。 + - 要点: + - 从行情表读取K线,计算滑窗放量、价格分位等;支持初始化、按窗口增量更新。 + - Binance 支持CSV历史导入,导入后联动更新巨量表。 + - 提供后续N周期涨跌统计、Excel导出与可视化,以及企业微信推送过滤(如volume_ratio>10且极值价位)。 + - 多窗口(50/80/100/120)与多周期(1m~1D)批量处理能力,MySQL落库去重。 \ No newline at end of file diff --git a/doc/trade_code_file_brief.md b/doc/trade_code_file_brief.md new file mode 100644 index 0000000..e33b203 --- /dev/null +++ b/doc/trade_code_file_brief.md @@ -0,0 +1,23 @@ +- ma_break_statistics.py + - 功能: 统计“均线突破”后的收益表现,批量跑不同标的/周期,生成统计与图表/Excel。 + - 要点: + - 数据源可切换:OKX/Binance/美股;时间范围从配置读取。 + - 关注 MA5/10/20/30 多组“上穿/下穿”组合,度量突破后的区间收益。 + - 可配置手续费率,输出多策略合并结果,落地到指定 output 目录。 + - 与数据库类 `DBMarketData/DBBinanceData/DBHugeVolumeData` 协同,读取K线、过滤区间。 + +- mean_reversion_sandbox.py + - 功能: 均值回归策略沙盒(回测器),按多种“买入/止损/止盈方案”跑批评估,生成统计+图表+Excel。 + - 要点: + - 条件以“价格分位+巨量”触发为主:如 close_10_low=1 或 close_80/90_high=1 且近2根K线任一巨量。 + - 多个方案(solution_1/2/3)策略化定义(止盈可用波段中位数、分位、高位形态等)。 + - 读自合并视图 `DBMergeMarketHugeVolume`(含价量与巨量事件),统一回测窗口与分组汇总。 + - 自动出图(seaborn/matplotlib)并将图贴入 Excel,结果分 symbol×bar 汇总。 + +- orb_trade.py + - 功能: ORB(Opening Range Breakout)日内策略回测与可视化。 + - 要点: + - 以开盘第一根5分钟K线的高低(High1/Low1)作为区间,第二根K线产生多空信号;入场价=第二根开盘价,止损价=第一根极值;盈亏基于 $R(entry-stop)。 + - 支持参数:账户初始资金、最大杠杆、单笔风险比例、佣金、盈利目标倍数、仅做多/仅做空/双向、是否参考 SAR、是否参考 1H 形态等。 + - 数据获取两路:优先本地DB(OKX/Binance),也提供 yfinance 拉取美股数据的流程;自动调整初始资金规模以适配价格量级。 + - 回测输出交易清单、资金曲线,生成图表与Excel摘要到 output 目录。 \ No newline at end of file diff --git a/huge_volume_main.py b/huge_volume_main.py index 035f67d..52b4f0e 100644 --- a/huge_volume_main.py +++ b/huge_volume_main.py @@ -11,7 +11,7 @@ import core.logger as logging from config import ( OKX_MONITOR_CONFIG, US_STOCK_MONITOR_CONFIG, - MYSQL_CONFIG, + COIN_MYSQL_CONFIG, WINDOW_SIZE, BINANCE_MONITOR_CONFIG, ) @@ -30,13 +30,13 @@ class HugeVolumeMain: is_us_stock: bool = False, is_binance: bool = False, ): - mysql_user = MYSQL_CONFIG.get("user", "xch") - mysql_password = MYSQL_CONFIG.get("password", "") + mysql_user = COIN_MYSQL_CONFIG.get("user", "xch") + mysql_password = COIN_MYSQL_CONFIG.get("password", "") if not mysql_password: raise ValueError("MySQL password is not set") - mysql_host = MYSQL_CONFIG.get("host", "localhost") - mysql_port = MYSQL_CONFIG.get("port", 3306) - mysql_database = MYSQL_CONFIG.get("database", "okx") + mysql_host = COIN_MYSQL_CONFIG.get("host", "localhost") + mysql_port = COIN_MYSQL_CONFIG.get("port", 3306) + mysql_database = COIN_MYSQL_CONFIG.get("database", "okx") self.db_url = f"mysql+pymysql://{mysql_user}:{mysql_password}@{mysql_host}:{mysql_port}/{mysql_database}" self.huge_volume = HugeVolume() diff --git a/market_data_main.py b/market_data_main.py index 2278d68..afea5f4 100644 --- a/market_data_main.py +++ b/market_data_main.py @@ -21,7 +21,7 @@ from config import ( OKX_MONITOR_CONFIG, BINANCE_MONITOR_CONFIG, US_STOCK_MONITOR_CONFIG, - MYSQL_CONFIG, + COIN_MYSQL_CONFIG, BAR_THRESHOLD, ) @@ -67,13 +67,13 @@ class MarketDataMain: 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", "") + mysql_user = COIN_MYSQL_CONFIG.get("user", "xch") + mysql_password = COIN_MYSQL_CONFIG.get("password", "") if not mysql_password: raise ValueError("MySQL password is not set") - mysql_host = MYSQL_CONFIG.get("host", "localhost") - mysql_port = MYSQL_CONFIG.get("port", 3306) - mysql_database = MYSQL_CONFIG.get("database", "okx") + mysql_host = COIN_MYSQL_CONFIG.get("host", "localhost") + mysql_port = COIN_MYSQL_CONFIG.get("port", 3306) + mysql_database = COIN_MYSQL_CONFIG.get("database", "okx") self.db_url = f"mysql+pymysql://{mysql_user}:{mysql_password}@{mysql_host}:{mysql_port}/{mysql_database}" if is_binance: diff --git a/market_monitor_main.py b/market_monitor_main.py index 0d65f38..05b1bd5 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 OKX_MONITOR_CONFIG, OKX_REALTIME_MONITOR_CONFIG, MYSQL_CONFIG, WECHAT_CONFIG +from config import OKX_MONITOR_CONFIG, OKX_REALTIME_MONITOR_CONFIG, COIN_MYSQL_CONFIG, WECHAT_CONFIG from core.utils import timestamp_to_datetime, transform_date_time_to_timestamp import core.logger as logging @@ -31,13 +31,13 @@ class MarketMonitorMain: self.output_folder = "./output/report/market_monitor/" os.makedirs(self.output_folder, exist_ok=True) - mysql_user = MYSQL_CONFIG.get("user", "xch") - mysql_password = MYSQL_CONFIG.get("password", "") + mysql_user = COIN_MYSQL_CONFIG.get("user", "xch") + mysql_password = COIN_MYSQL_CONFIG.get("password", "") if not mysql_password: raise ValueError("MySQL password is not set") - mysql_host = MYSQL_CONFIG.get("host", "localhost") - mysql_port = MYSQL_CONFIG.get("port", 3306) - mysql_database = MYSQL_CONFIG.get("database", "okx") + mysql_host = COIN_MYSQL_CONFIG.get("host", "localhost") + mysql_port = COIN_MYSQL_CONFIG.get("port", 3306) + mysql_database = COIN_MYSQL_CONFIG.get("database", "okx") self.db_url = f"mysql+pymysql://{mysql_user}:{mysql_password}@{mysql_host}:{mysql_port}/{mysql_database}" diff --git a/play.py b/play.py index 621dce7..689f07b 100644 --- a/play.py +++ b/play.py @@ -2,7 +2,7 @@ import logging from core.biz.quant_trader import QuantTrader from core.biz.strategy import QuantStrategy -from config import MYSQL_CONFIG +from config import COIN_MYSQL_CONFIG from sqlalchemy import create_engine, exc, text import pandas as pd @@ -100,13 +100,13 @@ def main() -> None: def test_query(): - mysql_user = MYSQL_CONFIG.get("user", "xch") - mysql_password = MYSQL_CONFIG.get("password", "") + mysql_user = COIN_MYSQL_CONFIG.get("user", "xch") + mysql_password = COIN_MYSQL_CONFIG.get("password", "") if not mysql_password: raise ValueError("MySQL password is not set") - mysql_host = MYSQL_CONFIG.get("host", "localhost") - mysql_port = MYSQL_CONFIG.get("port", 3306) - mysql_database = MYSQL_CONFIG.get("database", "okx") + mysql_host = COIN_MYSQL_CONFIG.get("host", "localhost") + mysql_port = COIN_MYSQL_CONFIG.get("port", 3306) + mysql_database = COIN_MYSQL_CONFIG.get("database", "okx") db_url = f"mysql+pymysql://{mysql_user}:{mysql_password}@{mysql_host}:{mysql_port}/{mysql_database}" db_engine = create_engine( db_url, diff --git a/test_ma_methods.py b/test_ma_methods.py index e399344..add27b9 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 OKX_MONITOR_CONFIG, MYSQL_CONFIG, WINDOW_SIZE +from config import OKX_MONITOR_CONFIG, COIN_MYSQL_CONFIG, WINDOW_SIZE # plt支持中文 plt.rcParams['font.family'] = ['SimHei'] @@ -18,13 +18,13 @@ plt.rcParams['font.family'] = ['SimHei'] logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') def get_real_data(symbol, bar, start, end): - mysql_user = MYSQL_CONFIG.get("user", "xch") - mysql_password = MYSQL_CONFIG.get("password", "") + mysql_user = COIN_MYSQL_CONFIG.get("user", "xch") + mysql_password = COIN_MYSQL_CONFIG.get("password", "") if not mysql_password: raise ValueError("MySQL password is not set") - mysql_host = MYSQL_CONFIG.get("host", "localhost") - mysql_port = MYSQL_CONFIG.get("port", 3306) - mysql_database = MYSQL_CONFIG.get("database", "okx") + mysql_host = COIN_MYSQL_CONFIG.get("host", "localhost") + mysql_port = COIN_MYSQL_CONFIG.get("port", 3306) + mysql_database = COIN_MYSQL_CONFIG.get("database", "okx") db_url = f"mysql+pymysql://{mysql_user}:{mysql_password}@{mysql_host}:{mysql_port}/{mysql_database}" db_market_data = DBMarketData(db_url) diff --git a/trade_data_main.py b/trade_data_main.py index 5a30ebe..c3a447c 100644 --- a/trade_data_main.py +++ b/trade_data_main.py @@ -10,7 +10,7 @@ from config import ( PASSPHRASE, SANDBOX, OKX_MONITOR_CONFIG, - MYSQL_CONFIG, + COIN_MYSQL_CONFIG, ) logger = logging.logger @@ -18,13 +18,13 @@ logger = logging.logger class TradeDataMain: def __init__(self): - mysql_user = MYSQL_CONFIG.get("user", "xch") - mysql_password = MYSQL_CONFIG.get("password", "") + mysql_user = COIN_MYSQL_CONFIG.get("user", "xch") + mysql_password = COIN_MYSQL_CONFIG.get("password", "") if not mysql_password: raise ValueError("MySQL password is not set") - mysql_host = MYSQL_CONFIG.get("host", "localhost") - mysql_port = MYSQL_CONFIG.get("port", 3306) - mysql_database = MYSQL_CONFIG.get("database", "okx") + mysql_host = COIN_MYSQL_CONFIG.get("host", "localhost") + mysql_port = COIN_MYSQL_CONFIG.get("port", 3306) + mysql_database = COIN_MYSQL_CONFIG.get("database", "okx") self.db_url = f"mysql+pymysql://{mysql_user}:{mysql_password}@{mysql_host}:{mysql_port}/{mysql_database}" self.trade_data = TradeData( diff --git a/trade_ma_strategy_main.py b/trade_ma_strategy_main.py index b7df31c..dd7c2bc 100644 --- a/trade_ma_strategy_main.py +++ b/trade_ma_strategy_main.py @@ -18,7 +18,6 @@ from config import ( PASSPHRASE, SANDBOX, OKX_MONITOR_CONFIG, - MYSQL_CONFIG, BAR_THRESHOLD, ) @@ -29,13 +28,21 @@ class TradeMaStrategyMain: def __init__( self, is_us_stock: bool = False, + is_astock: bool = False, + is_aindex: bool = True, is_binance: bool = False, commission_per_share: float = 0, + buy_by_long_period: dict = {"by_week": False, "by_month": False}, + long_period_condition: dict = {"ma5>ma10": False, "ma10>ma20": False, "macd_diff>0": False, "macd>0": False}, ): self.ma_break_statistics = MaBreakStatistics( is_us_stock=is_us_stock, + is_astock=is_astock, + is_aindex=is_aindex, is_binance=is_binance, commission_per_share=commission_per_share, + buy_by_long_period=buy_by_long_period, + long_period_condition=long_period_condition, ) def batch_ma_break_statistics(self): @@ -60,12 +67,51 @@ class TradeMaStrategyMain: logger.info("开始统计account_value_chg") +def test_single_symbol(): + ma_break_statistics = MaBreakStatistics( + is_us_stock=False, + is_astock=True, + is_aindex=False, + is_binance=False, + commission_per_share=0, + ) + symbol = "600111.SH" + bar = "1D" + ma_break_statistics.trade_simulate(symbol=symbol, bar=bar, strategy_name="均线macd结合策略2") + + if __name__ == "__main__": commission_per_share_list = [0, 0.0008] + buy_by_long_period_list = [{"by_week": True, "by_month": True}, + {"by_week": True, "by_month": False}, + {"by_week": False, "by_month": True}, + {"by_week": False, "by_month": False}] + long_period_condition_list = [{"ma5>ma10": True, "ma10>ma20": True, "macd_diff>0": True, "macd>0": True}, + {"ma5>ma10": True, "ma10>ma20": False, "macd_diff>0": True, "macd>0": True}, + {"ma5>ma10": False, "ma10>ma20": True, "macd_diff>0": True, "macd>0": True}] + for commission_per_share in commission_per_share_list: - trade_ma_strategy_main = TradeMaStrategyMain( - is_us_stock=False, - is_binance=True, - commission_per_share=commission_per_share, - ) - trade_ma_strategy_main.batch_ma_break_statistics() + for buy_by_long_period in buy_by_long_period_list: + for long_period_condition in long_period_condition_list: + logger.info(f"开始计算, 主要参数:commission_per_share: {commission_per_share}, buy_by_long_period: {buy_by_long_period}, long_period_condition: {long_period_condition}") + trade_ma_strategy_main = TradeMaStrategyMain( + is_us_stock=False, + is_astock=False, + is_aindex=True, + is_binance=False, + commission_per_share=commission_per_share, + buy_by_long_period=buy_by_long_period, + long_period_condition=long_period_condition, + ) + trade_ma_strategy_main.batch_ma_break_statistics() + + trade_ma_strategy_main = TradeMaStrategyMain( + is_us_stock=False, + is_astock=True, + is_aindex=False, + is_binance=False, + commission_per_share=commission_per_share, + buy_by_long_period=buy_by_long_period, + long_period_condition=long_period_condition, + ) + trade_ma_strategy_main.batch_ma_break_statistics() diff --git a/update_data_main.py b/update_data_main.py index a6a5b37..bd7de01 100644 --- a/update_data_main.py +++ b/update_data_main.py @@ -2,20 +2,20 @@ import pandas as pd from core.db.db_market_data import DBMarketData from core.biz.metrics_calculation import MetricsCalculation -from config import MYSQL_CONFIG, US_STOCK_MONITOR_CONFIG, OKX_MONITOR_CONFIG +from config import COIN_MYSQL_CONFIG, US_STOCK_MONITOR_CONFIG, OKX_MONITOR_CONFIG import core.logger as logging logger = logging.logger class UpdateDataMain: def __init__(self): - mysql_user = MYSQL_CONFIG.get("user", "xch") - mysql_password = MYSQL_CONFIG.get("password", "") + mysql_user = COIN_MYSQL_CONFIG.get("user", "xch") + mysql_password = COIN_MYSQL_CONFIG.get("password", "") if not mysql_password: raise ValueError("MySQL password is not set") - mysql_host = MYSQL_CONFIG.get("host", "localhost") - mysql_port = MYSQL_CONFIG.get("port", 3306) - mysql_database = MYSQL_CONFIG.get("database", "okx") + mysql_host = COIN_MYSQL_CONFIG.get("host", "localhost") + mysql_port = COIN_MYSQL_CONFIG.get("port", 3306) + mysql_database = COIN_MYSQL_CONFIG.get("database", "okx") self.db_url = f"mysql+pymysql://{mysql_user}:{mysql_password}@{mysql_host}:{mysql_port}/{mysql_database}" self.db_market_data = DBMarketData(self.db_url)