From ff2c35e1b3c0be0c3ce12a9f9a03359f1bfb1f44 Mon Sep 17 00:00:00 2001 From: blade <8019068@qq.com> Date: Mon, 15 Sep 2025 14:12:47 +0800 Subject: [PATCH] optimize ma_break quant logic --- .../ma_break_statistics.cpython-312.pyc | Bin 32452 -> 35643 bytes .../__pycache__/orb_trade.cpython-313.pyc | Bin 0 -> 38923 bytes core/trade/ma_break_statistics.py | 145 +++++++++++++----- orb_trade_main.py | 13 +- trade_ma_strategy_main.py | 12 +- 5 files changed, 127 insertions(+), 43 deletions(-) create mode 100644 core/trade/__pycache__/orb_trade.cpython-313.pyc diff --git a/core/trade/__pycache__/ma_break_statistics.cpython-312.pyc b/core/trade/__pycache__/ma_break_statistics.cpython-312.pyc index ad1c914e0dddc717bc4feac13fa3e3be07822f46..a61593f3c8391b1bcadf05a619cdc4dfec028156 100644 GIT binary patch delta 10027 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 W=|Mx5Xvq4#A$z`3W5{QP`u_k-Y^6p3 literal 0 HcmV?d00001 diff --git a/core/trade/ma_break_statistics.py b/core/trade/ma_break_statistics.py index 8721002..5bdd774 100644 --- a/core/trade/ma_break_statistics.py +++ b/core/trade/ma_break_statistics.py @@ -4,7 +4,7 @@ import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns -from datetime import datetime +from datetime import datetime, timedelta import re import json from openpyxl import Workbook @@ -12,8 +12,9 @@ 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, US_STOCK_MONITOR_CONFIG, MYSQL_CONFIG, WINDOW_SIZE +from config import OKX_MONITOR_CONFIG, US_STOCK_MONITOR_CONFIG, MYSQL_CONFIG, WINDOW_SIZE, BINANCE_MONITOR_CONFIG from core.db.db_market_data import DBMarketData +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 @@ -32,7 +33,7 @@ class MaBreakStatistics: 之间的涨跌幅 """ - def __init__(self, is_us_stock: bool = False): + def __init__(self, is_us_stock: bool = False, is_binance: bool = False): mysql_user = MYSQL_CONFIG.get("user", "xch") mysql_password = MYSQL_CONFIG.get("password", "") if not mysql_password: @@ -42,28 +43,45 @@ class MaBreakStatistics: mysql_database = 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) + self.db_huge_volume_data = DBHugeVolumeData(self.db_url) + self.is_us_stock = is_us_stock + self.is_binance = is_binance if is_us_stock: self.symbols = US_STOCK_MONITOR_CONFIG.get("volume_monitor", {}).get( "symbols", ["QQQ"] ) - else: - self.symbols = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get( - "symbols", ["XCH-USDT"] - ) - if is_us_stock: 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", "2014-11-30 00:00:00" + ) + self.db_market_data = DBMarketData(self.db_url) else: - self.bars = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get( - "bars", ["5m", "15m", "30m", "1H"] - ) - self.stats_output_dir = "./output/trade_sandbox/ma_strategy/excel/" - os.makedirs(self.stats_output_dir, exist_ok=True) - self.stats_chart_dir = "./output/trade_sandbox/ma_strategy/chart/" - os.makedirs(self.stats_chart_dir, exist_ok=True) + if is_binance: + self.symbols = BINANCE_MONITOR_CONFIG.get("volume_monitor", {}).get( + "symbols", ["BTC-USDT"] + ) + self.bars = ["30m", "1H"] + self.initial_date = BINANCE_MONITOR_CONFIG.get("volume_monitor", {}).get( + "initial_date", "2017-08-16 00:00:00" + ) + self.db_market_data = DBBinanceData(self.db_url) + 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", "30m", "1H"] + ) + self.initial_date = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get( + "initial_date", "2025-05-15 00:00:00" + ) + self.db_market_data = DBMarketData(self.db_url) + if len(self.initial_date) > 10: + self.initial_date = self.initial_date[:10] + self.end_date = datetime.now().strftime("%Y-%m-%d") self.trade_strategy_config = self.get_trade_strategy_config() self.main_strategy = self.trade_strategy_config.get("均线系统策略", None) @@ -73,14 +91,18 @@ class MaBreakStatistics: return trade_strategy_config def batch_statistics(self, strategy_name: str = "全均线策略"): - self.stats_output_dir = ( - f"./output/trade_sandbox/ma_strategy/excel/{strategy_name}/" - ) + if self.is_us_stock: + self.stats_output_dir = f"./output/trade_sandbox/ma_strategy/us_stock/excel/{strategy_name}/" + self.stats_chart_dir = f"./output/trade_sandbox/ma_strategy/us_stock/chart/{strategy_name}/" + elif self.is_binance: + self.stats_output_dir = f"./output/trade_sandbox/ma_strategy/binance/excel/{strategy_name}/" + self.stats_chart_dir = f"./output/trade_sandbox/ma_strategy/binance/chart/{strategy_name}/" + else: + self.stats_output_dir = f"./output/trade_sandbox/ma_strategy/okx/excel/{strategy_name}/" + self.stats_chart_dir = f"./output/trade_sandbox/ma_strategy/okx/chart/{strategy_name}/" os.makedirs(self.stats_output_dir, exist_ok=True) - self.stats_chart_dir = ( - f"./output/trade_sandbox/ma_strategy/chart/{strategy_name}/" - ) os.makedirs(self.stats_chart_dir, exist_ok=True) + ma_break_market_data_list = [] market_data_pct_chg_list = [] if strategy_name not in self.main_strategy.keys() or strategy_name is None: @@ -276,16 +298,11 @@ class MaBreakStatistics: return strategy_info_df def trade_simulate(self, symbol: str, bar: str, strategy_name: str = "全均线策略"): - market_data = self.db_market_data.query_market_data_by_symbol_bar( - symbol, bar, start=None, end=None - ) + market_data = self.get_full_data(symbol, bar) if market_data is None or len(market_data) == 0: logger.warning(f"获取{symbol} {bar} 数据失败") return None, None else: - market_data = pd.DataFrame(market_data) - market_data.sort_values(by="timestamp", ascending=True, inplace=True) - market_data.reset_index(drop=True, inplace=True) logger.info(f"获取{symbol} {bar} 数据成功,数据条数: {len(market_data)}") # 获得ma5, ma10, ma20, ma30不为空的行 market_data = market_data[ @@ -299,7 +316,7 @@ class MaBreakStatistics: ) # 计算volume_ma5 market_data["volume_ma5"] = market_data["volume"].rolling(window=5).mean() - # 获得5上穿10且ma5 > ma10 > ma20 > ma30且close > ma20的行,成交量较前5日均量放大20%以上 + market_data["volume_pct_chg"] = ( market_data["volume"] - market_data["volume_ma5"] ) / market_data["volume_ma5"] @@ -311,9 +328,14 @@ 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" for index, row in market_data.iterrows(): ma_cross = row["ma_cross"] timestamp = row["timestamp"] + date_time = row[date_time_field] close = row["close"] ma5 = row["ma5"] ma10 = row["ma10"] @@ -336,9 +358,7 @@ class MaBreakStatistics: ma_break_market_data_pair["symbol"] = symbol ma_break_market_data_pair["bar"] = bar ma_break_market_data_pair["begin_timestamp"] = timestamp - ma_break_market_data_pair["begin_date_time"] = ( - timestamp_to_datetime(timestamp) - ) + ma_break_market_data_pair["begin_date_time"] = date_time ma_break_market_data_pair["begin_close"] = close ma_break_market_data_pair["begin_ma5"] = ma5 ma_break_market_data_pair["begin_ma10"] = ma10 @@ -358,9 +378,7 @@ class MaBreakStatistics: if sell_condition: ma_break_market_data_pair["end_timestamp"] = timestamp - ma_break_market_data_pair["end_date_time"] = ( - timestamp_to_datetime(timestamp) - ) + ma_break_market_data_pair["end_date_time"] = date_time ma_break_market_data_pair["end_close"] = close ma_break_market_data_pair["end_ma5"] = ma5 ma_break_market_data_pair["end_ma10"] = ma10 @@ -411,6 +429,59 @@ class MaBreakStatistics: return ma_break_market_data, market_data_pct_chg else: return None, None + + def get_full_data(self, symbol: str, bar: str = "5m"): + """ + 分段获取数据,并将数据合并为完整数据 + 分段依据:如果end_date与start_date相差超过一年,则每次取一年数据 + """ + 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", + ] + 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 + ) + 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.reset_index(drop=True, inplace=True) + return data + def fit_strategy( self, @@ -734,12 +805,12 @@ class MaBreakStatistics: # 设置x轴标签 plt.xticks(symbol_bar_data["end_date_time"].iloc[label_indices], - symbol_bar_data["end_date_time"].iloc[label_indices].dt.strftime('%m-%d %H:%M'), + symbol_bar_data["end_date_time"].iloc[label_indices].dt.strftime('%Y%m%d %H:%M'), rotation=45, ha='right') else: # 如果数据点较少,全部显示 plt.xticks(symbol_bar_data["end_date_time"], - symbol_bar_data["end_date_time"].dt.strftime('%m-%d %H:%M'), + symbol_bar_data["end_date_time"].dt.strftime('%Y%m%d %H:%M'), rotation=45, ha='right') plt.tight_layout() diff --git a/orb_trade_main.py b/orb_trade_main.py index 9360c37..8ac38e3 100644 --- a/orb_trade_main.py +++ b/orb_trade_main.py @@ -28,11 +28,20 @@ def main(): else: start_date = "2024-01-01" end_date = datetime.now().strftime("%Y-%m-%d") + + # 原值 盈利目标倍数(默认10倍$R,即10R) profit_target_multiple = 10 + # 新值 盈利目标倍数(默认20倍$R,即10R) -- 20250909 + # profit_target_multiple = 20 + initial_capital = 25000 max_leverage = 4 risk_per_trade = 0.01 - commission_per_share = 0.0005 + # if is_us_stock: + # commission_per_share = 0.0005 + # else: + # commission_per_share = 0 + # commission_per_share = 0 trades_df_list = [] trades_summary_df_list = [] @@ -47,6 +56,7 @@ def main(): symbols = US_STOCK_MONITOR_CONFIG.get("volume_monitor", {}).get( "symbols", ["QQQ"] ) + commission_per_share = 0.0005 else: if is_binance: symbols = BINANCE_MONITOR_CONFIG.get("volume_monitor", {}).get( @@ -56,6 +66,7 @@ def main(): symbols = OKX_MONITOR_CONFIG.get("volume_monitor", {}).get( "symbols", ["BTC-USDT"] ) + commission_per_share = 0 for symbol in symbols: logger.info( f"开始回测 {symbol}, 交易周期:{bar}, 开始日期:{start_date}, 结束日期:{end_date}, 是否是美股:{is_us_stock}, 交易方向:{direction}, 是否使用SAR:{by_sar}, 是否使用R为entry减stop:{price_range_mean_as_R}, 是否使用K线实体过50%:{by_big_k}" diff --git a/trade_ma_strategy_main.py b/trade_ma_strategy_main.py index 927c85a..338c2fa 100644 --- a/trade_ma_strategy_main.py +++ b/trade_ma_strategy_main.py @@ -25,8 +25,8 @@ from config import ( logger = logging.logger class TradeMaStrategyMain: - def __init__(self, is_us_stock: bool = False): - self.ma_break_statistics = MaBreakStatistics(is_us_stock=is_us_stock) + def __init__(self, is_us_stock: bool = False, is_binance: bool = False): + self.ma_break_statistics = MaBreakStatistics(is_us_stock=is_us_stock, is_binance=is_binance) def batch_ma_break_statistics(self): """ @@ -36,8 +36,10 @@ class TradeMaStrategyMain: strategy_dict = self.ma_break_statistics.main_strategy pct_chg_df_list = [] for strategy_name, strategy_info in strategy_dict.items(): - pct_chg_df = self.ma_break_statistics.batch_statistics(strategy_name=strategy_name) - pct_chg_df_list.append(pct_chg_df) + if "macd" in strategy_name: + # 只计算macd策略 + pct_chg_df = self.ma_break_statistics.batch_statistics(strategy_name=strategy_name) + pct_chg_df_list.append(pct_chg_df) pct_chg_df = pd.concat(pct_chg_df_list) @@ -59,5 +61,5 @@ class TradeMaStrategyMain: if __name__ == "__main__": - trade_ma_strategy_main = TradeMaStrategyMain(is_us_stock=True) + trade_ma_strategy_main = TradeMaStrategyMain(is_us_stock=False, is_binance=True) trade_ma_strategy_main.batch_ma_break_statistics() \ No newline at end of file