optimize trade strategy

This commit is contained in:
blade 2025-08-20 16:40:33 +08:00
parent 1e204839a9
commit 43bd225cfa
6 changed files with 246 additions and 49 deletions

View File

@ -0,0 +1,29 @@
import schedule
import time
import datetime
import core.logger as logging
import subprocess
import os
logger = logging.logger
# 定义要执行的任务
def run_script():
start_time = time.time()
logger.info(f"Executing script at: {datetime.datetime.now()}")
output_file = r'./output/auto_schedule.txt'
with open(output_file, 'a') as f:
f.write(f"Task ran at {datetime.datetime.now()}\n")
python_path = r"D:\miniconda3\envs\okx\python.exe"
script_path = r"D:\python_projects\crypto_quant\huge_volume_main.py"
subprocess.run([python_path, script_path])
end_time = time.time()
logger.info(f"Script execution time: {end_time - start_time} seconds")
# 设置每小时运行一次
interval = 60 * 60
schedule.every(interval).seconds.do(run_script)
# 保持程序运行并检查调度
logger.info("Scheduler started. Press Ctrl+C to stop.")
while True:
schedule.run_pending()
time.sleep(1)

View File

@ -171,9 +171,9 @@ class MaBreakStatistics:
ma_cross = str(ma_cross)
buy_condition = False
if all_change:
buy_condition = (ma5 > ma10 and ma10 > ma20 and ma20 > ma30) and (close > ma20)
buy_condition = (ma_cross == "5上穿10") and (ma5 > ma10 and ma10 > ma20 and ma20 > ma30) and (close > ma20)
else:
buy_condition = ma_cross == "5上穿10" and (ma5 > ma10)
buy_condition = (ma_cross == "5上穿10") and (ma5 > ma10)
if buy_condition:
ma_break_market_data_pair = {}
ma_break_market_data_pair["symbol"] = symbol

View File

@ -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
from openpyxl import Workbook
from openpyxl.drawing.image import Image
@ -45,21 +45,22 @@ class MeanReversionSandbox:
desc_dict = {
"买入": [
"1. 窗口周期为100, 即100个K线",
"2. 当前low_10_low为1, 即当前最低价格在窗口周期的10分位以下",
"2. 当前close_10_low为1, 即当前收盘价在窗口周期的10分位以下",
"3. 之前2个K线与当前K线, 存在任意一个K线huge_volume为1, 即存在一个K线是巨量",
"4. 当前K线为阳线, 即close > open",
"4. 当前K线为阳线, 即close > open或者K线为一字, 长倒T线, 倒T线, 长十字星, 十字星",
# "5. 相同symbol的1H当前周期, ma5大于ma10",
# "5. KDJ, RSI, BOLL任意一个指标出现超卖",
],
"止损": ["跌幅超过下跌周期跌幅中位数, 即down_median后卖出"],
"止盈": {
"solution_1": [
"高位放量止盈 - 简易版",
"1. 当前high_80_high为1或者high_90_high为1",
"1. 当前close_80_high为1或者close_90_high为1",
"2. 之前2个K线与当前K线, 存在任意一个K线huge_volume为1, 即存在一个K线是巨量",
],
"solution_2": [
"高位放量止盈 - 复杂版",
"前提条件"
"1. 当前high_80_high为1或者high_90_high为1",
"前提条件" "1. 当前close_80_high为1或者close_90_high为1",
"2. 之前2个K线与当前K线, 存在任意一个K线huge_volume为1, 即存在一个K线是巨量",
"以下两个条件, 任一满足即可",
"1. K线为阴线, 即close < open",
@ -68,9 +69,10 @@ class MeanReversionSandbox:
],
"solution_3": [
"上涨波段盈利中位数止盈法",
"1. 超过波段中位数涨幅, 即up_median, 记录当前价格, 继续持仓",
"1. 超过波段中位数涨幅, 即up_median/ 到达价位90分位/ 任意技术指标出现中度超卖, 记录当前价格, 继续持仓",
"2. 之后一个周期, 如果价格上涨, 则记录该价格继续持仓",
"3. 之后一个周期, 如果价格跌到记录价格之下, 则卖出",
"4. 如果买入时ma5小于ma10, 过程中ma5大于ma10, 进行记录。之后出现ma5小于ma10, 则卖出",
],
},
}
@ -160,7 +162,8 @@ class MeanReversionSandbox:
trade_pair_dict["buy_huge_volume"] = row["huge_volume"]
trade_pair_dict["buy_volume_ratio"] = row["volume_ratio"]
trade_pair_dict["buy_k_shape"] = row["k_shape"]
trade_pair_dict["buy_low_10_low"] = row["low_10_low"]
trade_pair_dict["buy_close_10_low"] = row["close_10_low"]
trade_pair_dict["buy_ma5_lt_ma10"] = row["ma5"] < row["ma10"]
continue
if trade_pair_dict.get("buy_timestamp", None) is not None:
@ -188,22 +191,27 @@ class MeanReversionSandbox:
trade_pair_dict["sell_huge_volume"] = row["huge_volume"]
trade_pair_dict["sell_volume_ratio"] = row["volume_ratio"]
trade_pair_dict["sell_k_shape"] = row["k_shape"]
trade_pair_dict["sell_high_80_high"] = row["high_80_high"]
trade_pair_dict["sell_high_90_high"] = row["high_90_high"]
trade_pair_dict["sell_low_10_low"] = row["low_10_low"]
trade_pair_dict["sell_low_20_low"] = row["low_20_low"]
trade_pair_dict["sell_close_80_high"] = row["close_80_high"]
trade_pair_dict["sell_close_90_high"] = row["close_90_high"]
trade_pair_dict["sell_close_10_low"] = row["close_10_low"]
trade_pair_dict["sell_close_20_low"] = row["close_20_low"]
trade_pair_dict["profit_pct"] = round(
(trade_pair_dict["sell_close"] - trade_pair_dict["buy_close"])
/ trade_pair_dict["buy_close"]
* 100,
4,
)
if trade_pair_dict["sell_type"] == "止盈" and trade_pair_dict["profit_pct"] < 0:
if trade_pair_dict["profit_pct"] <= 0:
trade_pair_dict["sell_type"] = "止损"
else:
trade_pair_dict["sell_type"] = "止盈"
if trade_pair_dict.get("last_max_close", None) is not None:
# remove last_max_close
trade_pair_dict.pop("last_max_close")
if trade_pair_dict.get("process_ma5_gt_ma10", None) is not None:
trade_pair_dict.pop("process_ma5_gt_ma10")
trade_list.append(trade_pair_dict)
trade_pair_dict = {}
@ -219,30 +227,75 @@ class MeanReversionSandbox:
):
"""
买入条件
1. 窗口周期为100, 即100个K线
2. 当前low_10_low为1, 即当前最低价格在窗口周期的10分位以下
3. 之前2个K线与当前K线, 存在任意一个K线huge_volume为1, 即存在一个K线是巨量
4. 当前K线为阳线, 即close > open
5. TODO: 考虑K线形态
1. 窗口周期为100, 即100个K线,
2. 当前close_10_low为1, 即当前收盘价在窗口周期的10分位以下,
3. 之前2个K线与当前K线, 存在任意一个K线huge_volume为1, 即存在一个K线是巨量,
4. (当前K线为阳线, 即close > open)或者K线为一字, 长倒T线, 倒T线, 长十字星, 十字星,
"""
if index < 2:
return False
if row["close"] <= row["open"]:
if row["close"] <= row["open"] and row["k_shape"] not in [
"一字",
"长倒T线",
"倒T线",
"长十字星",
"十字星",
]:
return False
if row["low_10_low"] != 1:
if row["close_10_low"] != 1:
return False
# 如果当前与前两个K线huge_volume都不为1则返回False
# 如果当前与前两个K线, huge_volume都不为1, 则返回False
if (
row["huge_volume"] != 1
and market_data.loc[index - 1, "huge_volume"] != 1
and market_data.loc[index - 2, "huge_volume"] != 1
):
return False
logger.info(f"符合买入条件")
# if not self.check_metrics_over_sell(row):
# return False
# latest_1h_data = self.get_latest_1h_data(row["symbol"], row["date_time"])
# if latest_1h_data is None or len(latest_1h_data) == 0:
# logger.info(f"符合买入条件")
# return True
# # 当前小时周期的ma5小于ma10, 表明空头趋势, 则返回False
# elif (
# not pd.isna(latest_1h_data["ma5"])
# and not pd.isna(latest_1h_data["ma10"])
# and latest_1h_data["ma5"] < latest_1h_data["ma10"]
# ):
# # logger.info(f"当前小时周期的ma5小于ma10, 空头趋势, 不符合买入条件")
# return False
# else:
# logger.info(f"符合买入条件")
# return True
return True
def get_latest_1h_data(self, symbol: str, current_date_time: str):
bar = "1H"
# 根据current_date_time, 获取当前时间往前推1H的日期时间,
# 如当前时间为2025-08-20 10:20:05, 则获取2025-08-20 09:00:00
before_date_time = datetime.strptime(current_date_time, "%Y-%m-%d %H:%M:%S")
before_date_time = before_date_time - timedelta(hours=1)
# current_date_time取整数小时如2025-08-20 10:20:05, 取2025-08-20 10:00:00
before_date_time = before_date_time.replace(minute=0, second=0, microsecond=0)
before_date_time = before_date_time.strftime("%Y-%m-%d %H:%M:%S")
end_date_time = datetime.strptime(current_date_time, "%Y-%m-%d %H:%M:%S")
end_date_time = end_date_time.replace(minute=0, second=0, microsecond=0)
end_date_time = end_date_time - timedelta(seconds=1)
end_date_time = end_date_time.strftime("%Y-%m-%d %H:%M:%S")
latest_1h_data = self.db_merge_market_huge_volume.merge_market_huge_volume(
symbol, bar, 100, before_date_time, end_date_time
)
if latest_1h_data is None or len(latest_1h_data) == 0:
return None
# 只获取第一行数据
latest_1h_data = latest_1h_data.iloc[0]
return latest_1h_data
def check_stop_loss_condition(self, trade_pair_dict: dict, row: pd.Series):
symbol = trade_pair_dict["symbol"]
bar = trade_pair_dict["bar"]
@ -282,9 +335,7 @@ class MeanReversionSandbox:
market_data, row, index
)
elif self.solution == "solution_3":
return self.check_take_profit_condition_solution_3(
trade_pair_dict, row
)
return self.check_take_profit_condition_solution_3(trade_pair_dict, row)
else:
raise ValueError(f"Invalid strategy name: {self.solution}")
except Exception as e:
@ -299,10 +350,10 @@ class MeanReversionSandbox:
):
"""
高位放量止盈 - 简易版
1. 当前high_80_high为1或者high_90_high为1
1. 当前close_80_high为1或者close_90_high为1
2. 之前2个K线与当前K线, 存在任意一个K线huge_volume为1, 即存在一个K线是巨量
"""
if row["high_80_high"] != 1 and row["high_90_high"] != 1:
if row["close_80_high"] != 1 and row["close_90_high"] != 1:
return False
if (
row["huge_volume"] != 1
@ -322,7 +373,7 @@ class MeanReversionSandbox:
"""
高位放量止盈 - 复杂版
前提条件
1. 当前high_80_high为1或者high_90_high为1
1. 当前close_80_high为1或者close_90_high为1
2. 之前2个K线与当前K线, 存在任意一个K线huge_volume为1, 即存在一个K线是巨量
以下两个条件, 任一满足即可
1. K线为阴线, 即close < open
@ -334,25 +385,46 @@ class MeanReversionSandbox:
if row["close"] < row["open"]:
logger.info(f"符合高位放量止盈 - 复杂版条件")
return True
elif row["k_shape"] in ["一字", "长吊锤线", "吊锤线", "长倒T线", "倒T线", "长十字星", "十字星", "长上影线纺锤体", "长下影线纺锤体"]:
elif row["k_shape"] in [
"一字",
"长吊锤线",
"吊锤线",
"长倒T线",
"倒T线",
"长十字星",
"十字星",
"长上影线纺锤体",
"长下影线纺锤体",
]:
logger.info(f"符合高位放量止盈 - 复杂版条件")
return True
else:
return False
def check_take_profit_condition_solution_3(
self,
trade_pair_dict: dict,
row: pd.Series
self, trade_pair_dict: dict, row: pd.Series
):
"""
上涨波段盈利中位数止盈法
1. 超过波段中位数涨幅, 即up_median, 记录当前价格, 继续持仓
上涨波段盈利阶段止盈法
1. 超过波段中位数涨幅, 即up_median/ 到达价位90分位/ 任意技术指标出现中度超卖, 记录当前价格, 继续持仓
2. 之后一个周期, 如果价格上涨, 则记录该价格继续持仓
3. 之后一个周期, 如果价格跌到记录价格之下, 则卖出
4. 如果买入时ma5小于ma10, 过程中ma5大于ma10, 进行记录之后出现ma5小于ma10, 则卖出
"""
current_close = row["close"]
last_max_close = trade_pair_dict.get("last_max_close", None)
if trade_pair_dict["buy_ma5_lt_ma10"]:
if trade_pair_dict.get("process_ma5_gt_ma10", None):
if row["ma5"] < row["ma10"]:
logger.info(f"MA5小于MA10发生转势, 卖出")
return True
if row["ma5"] > row["ma10"]:
trade_pair_dict["process_ma5_gt_ma10"] = True
else:
trade_pair_dict["process_ma5_gt_ma10"] = False
if last_max_close is not None:
if current_close >= last_max_close:
logger.info(f"价格上涨, 继续持仓")
@ -373,9 +445,72 @@ class MeanReversionSandbox:
/ 100
)
need_record = False
buy_close = trade_pair_dict["buy_close"]
price_chg = (current_close - buy_close) / buy_close
if price_chg > up_median:
logger.info(f"当前价格上涨超过波段中位数涨幅, 记录当前价格")
need_record = True
elif self.check_metrics_over_buy(row):
logger.info(f"技术指标超买, 记录当前价格")
need_record = True
elif row["close_90_high"] == 1:
logger.info(f"到达价位90分位, 记录当前价格")
need_record = True
else:
need_record = False
if need_record:
trade_pair_dict["last_max_close"] = current_close
return False
def check_metrics_over_buy(self, row: pd.Series):
"""
检查技术指标是否出现中度超买
KDJ
K85.00
D80.00
J100.00
说明K D 进一步上升, J 显著高于100, 表示超买加剧, 回调概率增加, 但可能仍需确认
RSI 14
RSI80.00
说明RSI 进一步上升, 超买程度加深, 市场可能接近短期顶部, 回调概率增加
BOLL
价格位置价格突破上轨, 偏离上轨约 +2.00%即价格 = 上轨 × 1.02
说明价格显著突破上轨, 超买程度加深, 可能预示短期回调或反转
"""
if row["kdj_k"] > 85 and row["kdj_d"] > 80 and row["kdj_j"] > 100:
logger.info(f"KDJ超买")
return True
if row["rsi_14"] > 80:
logger.info(f"RSI超买")
return True
if row["boll_upper"] * 1.02 < row["close"]:
logger.info(f"BOLL超买")
return True
return False
def check_metrics_over_sell(self, row: pd.Series):
"""
检查技术指标是否出现超卖
KDJ
K: 25.00
D: 30.00
J: 20.00
RSI 14
RSI:30.00
说明: RSI 进一步下降, 超卖程度加深, 市场可能接近短期底部, 反弹概率增加
BOLL
价格位置: 价格接近下轨
"""
if row["kdj_k"] < 25 and row["kdj_d"] < 30 and row["kdj_j"] < 20:
logger.info(f"KDJ超卖")
return True
if row["rsi_14"] < 30:
logger.info(f"RSI超卖")
return True
if row["boll_lower"] >= row["close"]:
logger.info(f"BOLL超卖")
return True
return False

View File

@ -1,4 +1,5 @@
okx>=2.1.2
python-okx >= 0.3.9
pandas>=2.0.0
requests>=2.25.0
sqlalchemy >= 2.0.41
@ -6,3 +7,6 @@ pymysql >= 1.1.1
wechatpy >= 1.8.18
seaborn >= 0.13.2
schedule >= 1.2.2
xlsxwriter >= 3.2.5
openpyxl >= 3.1.5
cryptography >= 3.4.8

View File

@ -23,14 +23,21 @@ logger = logging.logger
class MeanReversionSandboxMain:
def __init__(self, start_date: str, end_date: str, window_size: int):
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(
"symbols", ["XCH-USDT"]
)
self.bars = MONITOR_CONFIG.get("volume_monitor", {}).get(
"bars", ["5m", "15m", "30m", "1H"]
)
self.solution_list = ["solution_1", "solution_2", "solution_3"]
self.only_5m = only_5m
if only_5m:
self.bars = ["5m"]
else:
self.bars = MONITOR_CONFIG.get("volume_monitor", {}).get(
"bars", ["5m", "15m", "30m", "1H"]
)
if solution_list is None:
self.solution_list = ["solution_1", "solution_2", "solution_3"]
else:
self.solution_list = solution_list
self.start_date = start_date
self.end_date = end_date
self.window_size = window_size
@ -176,8 +183,13 @@ class MeanReversionSandboxMain:
sheet_name = f"{solution}_chart"
chart_dict[sheet_name] = {}
for y_axis_field in y_axis_fields:
# 绘制2x2的画布
fig, axs = plt.subplots(2, 2, figsize=(10, 10))
if self.only_5m:
fig, axs = plt.subplots(1, 1, figsize=(10, 10))
# 当只有一个子图时将axs包装成数组以便统一处理
axs = np.array([[axs]])
else:
# 绘制2x2的画布
fig, axs = plt.subplots(2, 2, figsize=(10, 10))
for j, bar in enumerate(bars_in_order):
ax = axs[j // 2, j % 2]
bar_data = stat_data[stat_data["bar"] == bar].copy()
@ -191,6 +203,21 @@ class MeanReversionSandboxMain:
palette=colors,
ax=ax,
)
# 在柱子上方添加数值标签
for i, (idx, row) in enumerate(bar_data.iterrows()):
value = row[y_axis_field]
# 根据数值类型格式化标签
if "ratio" in y_axis_field:
label = f"{value:.2f}%"
else:
label = f"{value:.4f}"
# 在柱子上方显示数值
ax.text(i, value, label,
ha='center', va='bottom',
fontsize=9, fontweight='bold')
ax.set_ylabel(y_axis_field)
ax.set_xlabel("symbol")
ax.set_title(f"{solution} {bar}")
@ -203,9 +230,10 @@ class MeanReversionSandboxMain:
label.set_horizontalalignment("right")
# 隐藏未使用的subplot
total_used = len(bars_in_order)
for k in range(total_used, 4):
ax = axs[k // 2, k % 2]
ax.axis("off")
if not self.only_5m:
for k in range(total_used, 4):
ax = axs[k // 2, k % 2]
ax.axis("off")
fig.tight_layout()
file_name = f"{solution}_{y_axis_field}.png"
fig.savefig(os.path.join(save_path, file_name))
@ -271,7 +299,8 @@ class MeanReversionSandboxMain:
if __name__ == "__main__":
start_date = "2025-05-15 00:00:00"
end_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
solution_list = ["solution_3"]
mean_reversion_sandbox_main = MeanReversionSandboxMain(
start_date=start_date, end_date=end_date, window_size=100
start_date=start_date, end_date=end_date, window_size=100, only_5m=True, solution_list=solution_list
)
mean_reversion_sandbox_main.batch_mean_reversion_sandbox()