add files

This commit is contained in:
songkunling
2025-07-25 17:35:01 +08:00
commit 559d78388d
19 changed files with 1106 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
/GEMINI.md
/logs
/myenv
__pycache__/
+45
View File
@@ -0,0 +1,45 @@
import pandas as pd
class BacktestEngine:
def __init__(self, df):
"""
df: 包含历史k线的DataFrame,至少包括open, high, low, close字段
"""
self.df = df
def run_strategy(self, strategy):
"""
执行策略的run_backtest,得到信号序列
"""
return strategy.run_backtest(self.df)
def combine_signals(self, signals_list, weights, buy_threshold, sell_threshold):
"""
多策略信号加权合成,并根据阈值生成最终信号
返回合成信号序列
"""
df_signals = pd.concat(signals_list, axis=1).fillna(0)
weighted_signals = df_signals * weights
combined = weighted_signals.sum(axis=1)
def apply_threshold(score):
if score >= buy_threshold:
return 1
elif score <= sell_threshold:
return -1
else:
return 0
combined_signal = combined.apply(apply_threshold)
return combined_signal
def calc_returns(self, signals):
"""
根据信号计算策略回测收益率(简化版)
"""
df = self.df.copy()
df['signal'] = signals.shift(1).fillna(0) # 防止未来函数
df['returns'] = df['close'].pct_change()
df['strategy_returns'] = df['signal'] * df['returns']
cum_ret = (1 + df['strategy_returns']).cumprod() - 1
return cum_ret
+24
View File
@@ -0,0 +1,24 @@
from strategies import ma_cross, rsi, bollinger, mean_reversion, momentum_breakout, macd, kdj, turtle, daily_breakout, profit_protect, resilient_trend
SYMBOL = "XAUUSD"
INTERVAL = 60 # 秒
INITIAL_CAPITAL = 10000 # 初始资金
# 策略权重配置 (策略实例, 权重)
STRATEGIES = [
(ma_cross.Strategy(), 2.28),
(rsi.Strategy(), 1.84),
(bollinger.Strategy(), 1.46),
(mean_reversion.Strategy(), 1.90),
(momentum_breakout.Strategy(), 2.77),
(macd.Strategy(), 1.70),
(kdj.Strategy(), 0.65),
(turtle.Strategy(), 0.68),
(daily_breakout.Strategy(), 0.66),
(profit_protect.Strategy(), 0.57),
(resilient_trend.Strategy(), 1.0), # 新增带容错的趋势策略
]
# 交易信号阈值
BUY_THRESHOLD = 1.5
SELL_THRESHOLD = -1.5
+40
View File
@@ -0,0 +1,40 @@
import logging
import os
# 全局变量,用于存储logger实例
_logger_instance = None
def setup_logger():
global _logger_instance
if _logger_instance:
return _logger_instance
log_dir = "logs"
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, "strategy.log")
logger = logging.getLogger("StrategyLogger")
logger.setLevel(logging.DEBUG)
# 防止重复添加handler
if not logger.handlers:
# 输出到控制台
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 输出到文件
file_handler = logging.FileHandler(log_file, encoding="utf-8")
file_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - [%(levelname)s] - %(message)s')
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
logger.addHandler(console_handler)
logger.addHandler(file_handler)
_logger_instance = logger
return logger
# 直接导出一个已经初始化好的logger实例
logger = setup_logger()
+90
View File
@@ -0,0 +1,90 @@
import importlib
import pandas as pd
from utils import initialize, shutdown, get_rates, close_all, send_order
from backtest import BacktestEngine
from logger import logger
from config import INITIAL_CAPITAL, STRATEGIES, BUY_THRESHOLD, SELL_THRESHOLD
# 导入优化器
from optimizer import run_optimizer
def run_realtime():
if not initialize():
logger.error("MT5初始化失败")
return
signals = []
weights = []
for strat, weight in STRATEGIES:
try:
logger.info(f"执行策略:{strat.__class__.__module__}")
signal = strat.generate_signal()
signals.append(signal)
weights.append(weight)
except Exception as e:
logger.exception(f"运行策略 {strat.__class__.__module__} 时出错:{e}")
weighted_signal_sum = sum(s * w for s, w in zip(signals, weights))
if weighted_signal_sum >= BUY_THRESHOLD:
logger.info(f"加权信号总和 ({weighted_signal_sum:.2f}) 达到买入阈值 ({BUY_THRESHOLD}),发送买入信号")
close_all("XAUUSD")
send_order("XAUUSD", 'buy')
elif weighted_signal_sum <= SELL_THRESHOLD:
logger.info(f"加权信号总和 ({weighted_signal_sum:.2f}) 达到卖出阈值 ({SELL_THRESHOLD}),发送卖出信号")
close_all("XAUUSD")
send_order("XAUUSD", 'sell')
else:
logger.info(f"加权信号总和 ({weighted_signal_sum:.2f}) 未达到交易阈值,无操作")
shutdown()
def run_backtest():
if not initialize():
logger.error("MT5初始化失败")
return
symbol = "XAUUSD"
timeframe = 1 # M1
count = 50000
rates = get_rates(symbol, timeframe, count)
shutdown()
if rates is None:
logger.error("获取历史数据失败")
return
logger.info(f"初始资金: {INITIAL_CAPITAL}")
df = pd.DataFrame(rates)
engine = BacktestEngine(df)
signals_list = []
weights = []
for strat, weight in STRATEGIES:
try:
logger.info(f"回测策略:{strat.__class__.__module__}")
signals = engine.run_strategy(strat)
signals_list.append(signals)
weights.append(weight)
except Exception as e:
logger.exception(f"回测策略 {strat.__class__.__module__} 时出错:{e}")
combined_signal = engine.combine_signals(signals_list, weights, BUY_THRESHOLD, SELL_THRESHOLD)
cum_ret = engine.calc_returns(combined_signal)
final_capital = INITIAL_CAPITAL * (1 + cum_ret.iloc[-1])
logger.info("策略组合回测完成")
logger.info(f"最终资金: {final_capital:.2f}")
logger.info(cum_ret.tail())
if __name__ == "__main__":
# --- 选择运行模式 ---
# 1. 运行一次回测 (使用config.py中的默认权重)
# run_backtest()
# 2. 运行实盘交易 (使用config.py中的默认权重)
# run_realtime()
# 3. 运行遗传算法优化,寻找最佳权重
run_optimizer()
+115
View File
@@ -0,0 +1,115 @@
import random
import numpy as np
from deap import base, creator, tools, algorithms
import multiprocessing
import pandas as pd
from utils import initialize, shutdown, get_rates
from backtest import BacktestEngine
from logger import logger
from config import INITIAL_CAPITAL, STRATEGIES, BUY_THRESHOLD, SELL_THRESHOLD
# 1. 定义适应度函数 (已优化)
def evaluate_fitness(individual, df_data):
"""
评估函数现在接收预先加载的DataFrame作为参数,避免了重复IO。
输入:
- individual: 一个代表策略权重的列表。
- df_data: 包含历史K线数据的Pandas DataFrame。
输出: 一个元组,包含适应度分数(最终资金)。
"""
weights = individual
# 直接使用传入的df_data,不再需要get_rates
engine = BacktestEngine(df_data)
signals_list = []
strategy_instances = [s for s, w in STRATEGIES]
for strat in strategy_instances:
signals = engine.run_strategy(strat)
signals_list.append(signals)
combined_signal = engine.combine_signals(signals_list, weights, BUY_THRESHOLD, SELL_THRESHOLD)
cum_ret = engine.calc_returns(combined_signal)
final_capital = INITIAL_CAPITAL * (1 + cum_ret.iloc[-1])
# 在优化过程中,可以注释掉这行日志以提高速度,因为它会大量输出
# logger.info(f"评估权重: {[f'{w:.2f}' for w in weights]} -> 最终资金: {final_capital:.2f}")
return (final_capital,)
# 2. 设置遗传算法 (已优化)
def run_optimizer():
"""
配置并运行遗传算法
"""
# --- 数据预加载 ---
logger.info("--- 开始遗传算法优化 --- ")
logger.info("步骤 1/4: 初始化MT5并预加载历史数据...")
if not initialize():
logger.error("MT5初始化失败,无法开始优化")
return
symbol = "XAUUSD"
timeframe = 1 # M1
count = 50000 # 使用与回测相同的数据量
rates = get_rates(symbol, timeframe, count)
shutdown() # 获取数据后即可关闭连接
if rates is None:
logger.error("获取历史数据失败,优化终止")
return
df_historical_data = pd.DataFrame(rates)
logger.info(f"历史数据加载完成,共 {len(df_historical_data)} 条记录。")
# --- DEAP 设置 ---
logger.info("步骤 2/4: 配置遗传算法...")
creator.create("FitnessMax", base.Fitness, weights=(1.0,))
creator.create("Individual", list, fitness=creator.FitnessMax)
toolbox = base.Toolbox()
toolbox.register("attr_float", random.uniform, 0.1, 2.0)
num_strategies = len(STRATEGIES)
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_float, n=num_strategies)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
toolbox.register("evaluate", evaluate_fitness, df_data=df_historical_data)
toolbox.register("mate", tools.cxTwoPoint)
toolbox.register("mutate", tools.mutGaussian, mu=0, sigma=0.5, indpb=0.2)
toolbox.register("select", tools.selTournament, tournsize=3)
# --- 并行计算设置 ---
logger.info("步骤 3/4: 配置并行计算和统计...")
pool = multiprocessing.Pool()
toolbox.register("map", pool.map)
# --- 统计功能设置 ---
stats = tools.Statistics(lambda ind: ind.fitness.values)
stats.register("avg", np.mean)
stats.register("std", np.std)
stats.register("min", np.min)
stats.register("max", np.max)
# --- 运行算法 ---
population = toolbox.population(n=50)
ngen = 20
cxpb = 0.5
mutpb = 0.2
logger.info(f"步骤 4/4: 开始并行进化... (种群大小: {len(population)}, 进化代数: {ngen})")
algorithms.eaSimple(population, toolbox, cxpb, mutpb, ngen, stats=stats, verbose=True)
pool.close()
# --- 结果 ---
best_individual = tools.selBest(population, k=1)[0]
best_fitness = best_individual.fitness.values[0]
logger.info("--- 遗传算法优化结束 ---")
logger.info(f"找到的最佳权重: {[f'{w:.2f}' for w in best_individual]}")
logger.info(f"对应的最佳最终资金: {best_fitness:.2f}")
return best_individual, best_fitness
+3
View File
@@ -0,0 +1,3 @@
MetaTrader5
pandas
deap
+57
View File
@@ -0,0 +1,57 @@
import MetaTrader5 as mt5
import pandas as pd
from utils import get_rates, close_all, send_order
from logger import logger
class Strategy:
def __init__(self):
self.symbol = "XAUUSD"
self.timeframe = mt5.TIMEFRAME_M1
self.bollinger_period = 20
self.bollinger_std_dev = 2
def _calculate_indicators(self, df):
"""
计算布林带指标
"""
mean = df['close'].rolling(self.bollinger_period).mean()
std = df['close'].rolling(self.bollinger_period).std()
df['upper_band'] = mean + self.bollinger_std_dev * std
df['lower_band'] = mean - self.bollinger_std_dev * std
return df
def generate_signal(self):
"""
布林带策略实盘:
当价格跌破下轨买入,涨破上轨卖出。
"""
rates = get_rates(self.symbol, self.timeframe, self.bollinger_period + 30)
if rates is None or len(rates) < self.bollinger_period:
return 0
df = pd.DataFrame(rates)
df = self._calculate_indicators(df)
if df['close'].iloc[-2] < df['lower_band'].iloc[-2]:
logger.info(f"价格跌破下轨,产生买入信号: {self.symbol}")
return 1
elif df['close'].iloc[-2] > df['upper_band'].iloc[-2]:
logger.info(f"价格涨破上轨,产生卖出信号: {self.symbol}")
return -1
return 0
def run_backtest(self, df):
"""
布林带策略回测:
价格突破下轨买入,突破上轨卖出
返回信号序列:1买入,-1卖出,0无操作
"""
df = df.copy()
df = self._calculate_indicators(df)
signals = pd.Series(0, index=df.index)
for i in range(self.bollinger_period, len(df)):
if df['close'].iloc[i-1] < df['lower_band'].iloc[i-1]:
signals.iat[i] = 1
elif df['close'].iloc[i-1] > df['upper_band'].iloc[i-1]:
signals.iat[i] = -1
return signals
+68
View File
@@ -0,0 +1,68 @@
import MetaTrader5 as mt5
import pandas as pd
from datetime import datetime
from utils import get_rates, has_open_position, close_all, send_order
from logger import logger
class Strategy:
def __init__(self):
self.symbol = "XAUUSD"
self.timeframe = mt5.TIMEFRAME_M1
def _calculate_indicators(self, df):
"""
计算日内突破指标
"""
df['time'] = pd.to_datetime(df['time'], unit='s')
today = datetime.now().date()
day_data = df[df['time'].dt.date == today]
if day_data.empty:
return df, None, None
day_high = day_data['high'].max()
day_low = day_data['low'].min()
return df, day_high, day_low
def generate_signal(self):
"""
日内突破策略实盘
当价格突破当日最高买入,突破当日最低卖出
"""
rates = get_rates(self.symbol, self.timeframe, 1440) # 24 hours * 60 minutes
if rates is None or len(rates) < 2:
return 0
df = pd.DataFrame(rates)
df, day_high, day_low = self._calculate_indicators(df)
if day_high is None or day_low is None:
return 0
if df['close'].iloc[-2] > day_high:
logger.info(f"价格突破当日最高,产生买入信号: {self.symbol}")
return 1
elif df['close'].iloc[-2] < day_low:
logger.info(f"价格突破当日最低,产生卖出信号: {self.symbol}")
return -1
return 0
def run_backtest(self, df):
"""
日内突破回测方法
计算每个交易日的高低点,突破买卖信号
"""
df = df.copy()
df['time'] = pd.to_datetime(df['time'], unit='s')
signals = pd.Series(0, index=df.index)
grouped = df.groupby(df['time'].dt.date)
for date, group in grouped:
day_high = group['high'].max()
day_low = group['low'].min()
for i, row in group.iterrows():
if row['close'] > day_high:
signals.loc[i] = 1
elif row['close'] < day_low:
signals.loc[i] = -1
return signals
+60
View File
@@ -0,0 +1,60 @@
import MetaTrader5 as mt5
import pandas as pd
from utils import get_rates, has_open_position, close_all, send_order
from logger import logger
class Strategy:
def __init__(self):
self.symbol = "XAUUSD"
self.timeframe = mt5.TIMEFRAME_M1
self.kdj_period = 9
self.kdj_buy_threshold = 10
self.kdj_sell_threshold = 90
def _calculate_indicators(self, df):
"""
计算KDJ指标
"""
low_min = df['low'].rolling(self.kdj_period).min()
high_max = df['high'].rolling(self.kdj_period).max()
rsv = (df['close'] - low_min) / (high_max - low_min) * 100
df['k'] = rsv.ewm(com=2).mean()
df['d'] = df['k'].ewm(com=2).mean()
df['j'] = 3 * df['k'] - 2 * df['d']
return df
def generate_signal(self):
"""
KDJ策略实盘
J值小于10买入,大于90卖出
"""
rates = get_rates(self.symbol, self.timeframe, self.kdj_period + 30)
if rates is None or len(rates) < self.kdj_period:
return 0
df = pd.DataFrame(rates)
df = self._calculate_indicators(df)
if df['j'].iloc[-2] < self.kdj_buy_threshold:
logger.info(f"J值小于{self.kdj_buy_threshold},产生买入信号: {self.symbol}")
return 1
elif df['j'].iloc[-2] > self.kdj_sell_threshold:
logger.info(f"J值大于{self.kdj_sell_threshold},产生卖出信号: {self.symbol}")
return -1
return 0
def run_backtest(self, df):
"""
KDJ回测方法
根据J值极端生成信号
"""
df = df.copy()
df = self._calculate_indicators(df)
signals = pd.Series(0, index=df.index)
for i in range(self.kdj_period, len(df)):
if df['j'].iloc[i-1] < self.kdj_buy_threshold:
signals.iat[i] = 1
elif df['j'].iloc[i-1] > self.kdj_sell_threshold:
signals.iat[i] = -1
return signals
+56
View File
@@ -0,0 +1,56 @@
import MetaTrader5 as mt5
import pandas as pd
from utils import get_rates, close_all, send_order
from logger import logger
class Strategy:
def __init__(self):
self.symbol = "XAUUSD"
self.timeframe = mt5.TIMEFRAME_M1
self.fast_ma_period = 5
self.slow_ma_period = 20
def _calculate_indicators(self, df):
"""
计算技术指标
"""
df['fast_ma'] = df['close'].rolling(self.fast_ma_period).mean()
df['slow_ma'] = df['close'].rolling(self.slow_ma_period).mean()
return df
def generate_signal(self):
"""
均线交叉策略实盘:
短期均线上穿长期均线买入,下穿卖出。
"""
rates = get_rates(self.symbol, self.timeframe, self.slow_ma_period + 30)
if rates is None or len(rates) < self.slow_ma_period:
return 0
df = pd.DataFrame(rates)
df = self._calculate_indicators(df)
if df['fast_ma'].iloc[-2] > df['slow_ma'].iloc[-2] and df['fast_ma'].iloc[-3] <= df['slow_ma'].iloc[-3]:
logger.info(f"短期均线上穿长期均线,产生买入信号: {self.symbol}")
return 1
elif df['fast_ma'].iloc[-2] < df['slow_ma'].iloc[-2] and df['fast_ma'].iloc[-3] >= df['slow_ma'].iloc[-3]:
logger.info(f"短期均线下穿长期均线,产生卖出信号: {self.symbol}")
return -1
return 0
def run_backtest(self, df):
"""
均线交叉回测:
短期均线和长期均线交叉产生信号
"""
df = df.copy()
df = self._calculate_indicators(df)
signals = pd.Series(0, index=df.index)
# 从 slow_ma_period 开始循环,避免早期数据 NaN 问题
for i in range(self.slow_ma_period, len(df)):
if df['fast_ma'].iloc[i-1] > df['slow_ma'].iloc[i-1] and df['fast_ma'].iloc[i-2] <= df['slow_ma'].iloc[i-2]:
signals.iat[i] = 1
elif df['fast_ma'].iloc[i-1] < df['slow_ma'].iloc[i-1] and df['fast_ma'].iloc[i-2] >= df['slow_ma'].iloc[i-2]:
signals.iat[i] = -1
return signals
+58
View File
@@ -0,0 +1,58 @@
import MetaTrader5 as mt5
import pandas as pd
from utils import get_rates, has_open_position, close_all, send_order
from logger import logger
class Strategy:
def __init__(self):
self.symbol = "XAUUSD"
self.timeframe = mt5.TIMEFRAME_M1
self.fast_ema_period = 12
self.slow_ema_period = 26
self.signal_period = 9
def _calculate_indicators(self, df):
"""
计算MACD指标
"""
df['exp12'] = df['close'].ewm(span=self.fast_ema_period, adjust=False).mean()
df['exp26'] = df['close'].ewm(span=self.slow_ema_period, adjust=False).mean()
df['dif'] = df['exp12'] - df['exp26']
df['dea'] = df['dif'].ewm(span=self.signal_period, adjust=False).mean()
return df
def generate_signal(self):
"""
MACD策略实盘
DIF线上穿DEA买入,反之卖出
"""
rates = get_rates(self.symbol, self.timeframe, self.slow_ema_period + self.signal_period + 30)
if rates is None or len(rates) < self.slow_ema_period + self.signal_period:
return 0
df = pd.DataFrame(rates)
df = self._calculate_indicators(df)
if df['dif'].iloc[-2] > df['dea'].iloc[-2] and df['dif'].iloc[-3] <= df['dea'].iloc[-3]:
logger.info(f"DIF线上穿DEA,产生买入信号: {self.symbol}")
return 1
elif df['dif'].iloc[-2] < df['dea'].iloc[-2] and df['dif'].iloc[-3] >= df['dea'].iloc[-3]:
logger.info(f"DIF线下穿DEA,产生卖出信号: {self.symbol}")
return -1
return 0
def run_backtest(self, df):
"""
MACD回测方法
根据DIF和DEA金叉死叉生成信号
"""
df = df.copy()
df = self._calculate_indicators(df)
signals = pd.Series(0, index=df.index)
for i in range(2, len(df)):
if df['dif'].iloc[i-1] > df['dea'].iloc[i-1] and df['dif'].iloc[i-2] <= df['dea'].iloc[i-2]:
signals.iat[i] = 1
elif df['dif'].iloc[i-1] < df['dea'].iloc[i-1] and df['dif'].iloc[i-2] >= df['dea'].iloc[i-2]:
signals.iat[i] = -1
return signals
+57
View File
@@ -0,0 +1,57 @@
import MetaTrader5 as mt5
import pandas as pd
from utils import get_rates, close_all, send_order
from logger import logger
class Strategy:
def __init__(self):
self.symbol = "XAUUSD"
self.timeframe = mt5.TIMEFRAME_M1
self.mean_reversion_period = 20
self.mean_reversion_std_dev = 2
def _calculate_indicators(self, df):
"""
计算均值回归指标
"""
mean = df['close'].rolling(self.mean_reversion_period).mean()
std = df['close'].rolling(self.mean_reversion_period).std()
df['upper_band'] = mean + self.mean_reversion_std_dev * std
df['lower_band'] = mean - self.mean_reversion_std_dev * std
return df
def generate_signal(self):
"""
均值回归策略实盘:
当价格超过20日均线正负2个标准差买卖。
"""
rates = get_rates(self.symbol, self.timeframe, self.mean_reversion_period + 30)
if rates is None or len(rates) < self.mean_reversion_period:
return 0
df = pd.DataFrame(rates)
df = self._calculate_indicators(df)
if df['close'].iloc[-2] > df['upper_band'].iloc[-2]:
logger.info(f"价格超过上轨,产生卖出信号: {self.symbol}")
return -1
elif df['close'].iloc[-2] < df['lower_band'].iloc[-2]:
logger.info(f"价格低于下轨,产生买入信号: {self.symbol}")
return 1
return 0
def run_backtest(self, df):
"""
均值回归回测:
价格突破上下轨卖出/买入
"""
df = df.copy()
df = self._calculate_indicators(df)
signals = pd.Series(0, index=df.index)
for i in range(self.mean_reversion_period, len(df)):
if df['close'].iloc[i-1] > df['upper_band'].iloc[i-1]:
signals.iat[i] = -1
elif df['close'].iloc[i-1] < df['lower_band'].iloc[i-1]:
signals.iat[i] = 1
return signals
+54
View File
@@ -0,0 +1,54 @@
import MetaTrader5 as mt5
import pandas as pd
from utils import get_rates, has_open_position, close_all, send_order
from logger import logger
class Strategy:
def __init__(self):
self.symbol = "XAUUSD"
self.timeframe = mt5.TIMEFRAME_M1
self.breakout_period = 20
def _calculate_indicators(self, df):
"""
计算动量突破指标
"""
df['high_20'] = df['high'].rolling(self.breakout_period).max()
df['low_20'] = df['low'].rolling(self.breakout_period).min()
return df
def generate_signal(self):
"""
动量突破策略实盘
价格突破过去20根K线最高点买入,突破最低点卖出
"""
rates = get_rates(self.symbol, self.timeframe, self.breakout_period + 30)
if rates is None or len(rates) < self.breakout_period:
return 0
df = pd.DataFrame(rates)
df = self._calculate_indicators(df)
if df['close'].iloc[-2] > df['high_20'].iloc[-3]:
logger.info(f"价格突破{self.breakout_period}日最高点,产生买入信号: {self.symbol}")
return 1
elif df['close'].iloc[-2] < df['low_20'].iloc[-3]:
logger.info(f"价格突破{self.breakout_period}日最低点,产生卖出信号: {self.symbol}")
return -1
return 0
def run_backtest(self, df):
"""
动量突破回测方法
过去20根K线最高最低突破生成买卖信号
"""
df = df.copy()
df = self._calculate_indicators(df)
signals = pd.Series(0, index=df.index)
for i in range(self.breakout_period, len(df)):
if df['close'].iloc[i-1] > df['high_20'].iloc[i-2]:
signals.iat[i] = 1
elif df['close'].iloc[i-1] < df['low_20'].iloc[i-2]:
signals.iat[i] = -1
return signals
+79
View File
@@ -0,0 +1,79 @@
import pandas as pd
from logger import logger
class Strategy:
def __init__(self):
self.symbol = "XAUUSD"
# --- 策略核心参数 ---
# 固定止损线:亏损10%则卖出
self.stop_loss_pct = -0.10
# 利润回撤百分比:从最高利润点回撤30%则卖出
self.profit_retracement_pct = 0.30
# 追踪止损的激活阈值:当利润超过5%后,才开始启动追踪止损逻辑
self.min_profit_for_trailing = 0.05
def generate_signal(self):
"""
此策略为资金管理和退出策略,不产生独立的买入信号。
实盘逻辑应与其他策略结合,此处仅为框架完整性。
"""
logger.warning("ProfitProtect策略是一个退出策略,不应单独用于实盘产生信号。")
return 0
def run_backtest(self, df):
"""
盈利保护策略回测:
- 固定止损:亏损10%卖出。
- 追踪止损:利润超过5%后启动,从最高利润点回撤30%卖出。
为了独立回测,本策略会在一开始买入,然后应用退出逻辑。
"""
df = df.copy()
signals = pd.Series(0, index=df.index)
if len(df) < 2:
return signals
# --- 回测状态变量 ---
position_open = False
entry_price = 0.0
peak_profit_pct = 0.0 # 记录达到的最高利润百分比
for i in range(len(df)):
# 如果没有持仓,就在第一个机会买入(用于独立回测)
if not position_open:
position_open = True
entry_price = df['close'].iloc[i]
signals.iat[i] = 1 # 买入信号
peak_profit_pct = 0.0 # 重置最高利润
continue
# 如果有持仓,则执行退出逻辑
if position_open:
current_price = df['close'].iloc[i]
current_profit_pct = (current_price - entry_price) / entry_price
# 1. 更新最高利润点
peak_profit_pct = max(peak_profit_pct, current_profit_pct)
# 2. 检查固定止损条件
if current_profit_pct <= self.stop_loss_pct:
logger.info(f"索引 {i}: 触发固定止损。入场价: {entry_price:.2f}, 当前价: {current_price:.2f}, 亏损: {current_profit_pct:.2%}")
signals.iat[i] = -1 # 卖出信号
position_open = False # 平仓
continue
# 3. 检查追踪止损条件
# 只有当最高利润超过了激活阈值,才开始计算回撤
if peak_profit_pct > self.min_profit_for_trailing:
retracement_from_peak = (peak_profit_pct - current_profit_pct)
# 避免除以零或负数的情况
if peak_profit_pct > 0:
retracement_pct = retracement_from_peak / peak_profit_pct
if retracement_pct >= self.profit_retracement_pct:
logger.info(f"索引 {i}: 触发追踪止损。最高利润: {peak_profit_pct:.2%}, 当前利润: {current_profit_pct:.2%}, 回撤超过30%")
signals.iat[i] = -1 # 卖出信号
position_open = False # 平仓
continue
return signals
+113
View File
@@ -0,0 +1,113 @@
import pandas as pd
from logger import logger
from utils import get_rates
class Strategy:
def __init__(self):
self.symbol = "XAUUSD"
# --- 策略核心参数 ---
self.trend_period = 50
self.retracement_tolerance = 0.30
# --- 策略状态变量 ---
self.current_trend = "none" # none, uptrend, downtrend
self.trend_peak = 0.0 # 上升趋势中的最高价
self.trend_trough = float('inf') # 下降趋势中的最低价
def generate_signal(self):
"""
带状态维护的实盘信号生成方法。
"""
# 获取足够的数据来计算滚动高低点
rates = get_rates(self.symbol, 1, self.trend_period + 5)
if rates is None or len(rates) < self.trend_period:
return 0 # 数据不足,不产生信号
df = pd.DataFrame(rates)
# 获取当前价格和用于判断突破的历史高低点
current_price = df['close'].iloc[-1]
high_period = df['high'].rolling(self.trend_period).max().iloc[-2]
low_period = df['low'].rolling(self.trend_period).min().iloc[-2]
signal = 0
# 状态 1: 当前无趋势,等待趋势开始
if self.current_trend == "none":
if current_price > high_period:
self.current_trend = "uptrend"
self.trend_peak = current_price
signal = 1
logger.info(f"实盘: 突破进入上升趋势,买入价: {current_price:.2f}")
elif current_price < low_period:
self.current_trend = "downtrend"
self.trend_trough = current_price
signal = -1
logger.info(f"实盘: 跌破进入下降趋势,卖出价: {current_price:.2f}")
# 状态 2: 当前处于上升趋势
elif self.current_trend == "uptrend":
if current_price < self.trend_peak * (1 - self.retracement_tolerance):
logger.info(f"实盘: 上升趋势结束。最高点: {self.trend_peak:.2f}, 当前价: {current_price:.2f}。平仓卖出。")
signal = -1
self.current_trend = "none" # 重置状态
else:
self.trend_peak = max(self.trend_peak, current_price)
# 状态 3: 当前处于下降趋势
elif self.current_trend == "downtrend":
if current_price > self.trend_trough * (1 + self.retracement_tolerance):
logger.info(f"实盘: 下降趋势结束。最低点: {self.trend_trough:.2f}, 当前价: {current_price:.2f}。平仓买入。")
signal = 1
self.current_trend = "none" # 重置状态
else:
self.trend_trough = min(self.trend_trough, current_price)
return signal
def run_backtest(self, df):
"""
带容错的趋势跟踪策略回测:
- 突破N周期高点,进入上升趋势,回撤30%则趋势结束。
- 跌破N周期低点,进入下降趋势,反弹30%则趋势结束。
"""
df = df.copy()
signals = pd.Series(0, index=df.index)
df['high_period'] = df['high'].rolling(self.trend_period).max().shift(1)
df['low_period'] = df['low'].rolling(self.trend_period).min().shift(1)
# 回测时使用局部变量来管理状态,避免干扰实盘状态
backtest_trend = "none"
backtest_peak = 0.0
backtest_trough = float('inf')
for i in range(self.trend_period, len(df)):
current_price = df['close'].iloc[i]
if backtest_trend == "none":
if current_price > df['high_period'].iloc[i]:
backtest_trend = "uptrend"
backtest_peak = current_price
signals.iat[i] = 1
elif current_price < df['low_period'].iloc[i]:
backtest_trend = "downtrend"
backtest_trough = current_price
signals.iat[i] = -1
elif backtest_trend == "uptrend":
if current_price < backtest_peak * (1 - self.retracement_tolerance):
signals.iat[i] = -1
backtest_trend = "none"
else:
backtest_peak = max(backtest_peak, current_price)
elif backtest_trend == "downtrend":
if current_price > backtest_trough * (1 + self.retracement_tolerance):
signals.iat[i] = 1
backtest_trend = "none"
else:
backtest_trough = min(backtest_trough, current_price)
return signals
+59
View File
@@ -0,0 +1,59 @@
import MetaTrader5 as mt5
import pandas as pd
from utils import get_rates, close_all, send_order
from logger import logger
class Strategy:
def __init__(self):
self.symbol = "XAUUSD"
self.timeframe = mt5.TIMEFRAME_M1
self.rsi_period = 14
self.rsi_buy_threshold = 30
self.rsi_sell_threshold = 70
def _calculate_indicators(self, df):
"""
计算RSI指标
"""
delta = df['close'].diff()
gain = delta.where(delta > 0, 0).rolling(self.rsi_period).mean()
loss = -delta.where(delta < 0, 0).rolling(self.rsi_period).mean()
rs = gain / loss
df['rsi'] = 100 - (100 / (1 + rs))
return df
def generate_signal(self):
"""
RSI策略实盘:
RSI < 30买入,RSI > 70卖出。
"""
rates = get_rates(self.symbol, self.timeframe, self.rsi_period + 30)
if rates is None or len(rates) < self.rsi_period:
return 0
df = pd.DataFrame(rates)
df = self._calculate_indicators(df)
if df['rsi'].iloc[-2] < self.rsi_buy_threshold:
logger.info(f"RSI小于{self.rsi_buy_threshold},产生买入信号: {self.symbol}")
return 1
elif df['rsi'].iloc[-2] > self.rsi_sell_threshold:
logger.info(f"RSI大于{self.rsi_sell_threshold},产生卖出信号: {self.symbol}")
return -1
return 0
def run_backtest(self, df):
"""
RSI回测:
RSI < 30买入,RSI > 70卖出。
"""
df = df.copy()
df = self._calculate_indicators(df)
signals = pd.Series(0, index=df.index)
for i in range(self.rsi_period, len(df)):
if df['rsi'].iloc[i-1] < self.rsi_buy_threshold:
signals.iat[i] = 1
elif df['rsi'].iloc[i-1] > self.rsi_sell_threshold:
signals.iat[i] = -1
return signals
+54
View File
@@ -0,0 +1,54 @@
import MetaTrader5 as mt5
import pandas as pd
from utils import get_rates, has_open_position, close_all, send_order
from logger import logger
class Strategy:
def __init__(self):
self.symbol = "XAUUSD"
self.timeframe = mt5.TIMEFRAME_M1
self.turtle_period = 20
def _calculate_indicators(self, df):
"""
计算海龟交易指标
"""
df['high_20'] = df['high'].rolling(self.turtle_period).max()
df['low_20'] = df['low'].rolling(self.turtle_period).min()
return df
def generate_signal(self):
"""
海龟交易策略实盘
20日最高突破买入,20日最低突破卖出
"""
rates = get_rates(self.symbol, self.timeframe, self.turtle_period + 30)
if rates is None or len(rates) < self.turtle_period:
return 0
df = pd.DataFrame(rates)
df = self._calculate_indicators(df)
if df['close'].iloc[-2] > df['high_20'].iloc[-3]:
logger.info(f"价格突破{self.turtle_period}日最高点,产生买入信号: {self.symbol}")
return 1
elif df['close'].iloc[-2] < df['low_20'].iloc[-3]:
logger.info(f"价格突破{self.turtle_period}日最低点,产生卖出信号: {self.symbol}")
return -1
return 0
def run_backtest(self, df):
"""
海龟交易回测方法
根据20日高低突破生成买卖信号
"""
df = df.copy()
df = self._calculate_indicators(df)
signals = pd.Series(0, index=df.index)
for i in range(self.turtle_period, len(df)):
if df['close'].iloc[i-1] > df['high_20'].iloc[i-2]:
signals.iat[i] = 1
elif df['close'].iloc[i-1] < df['low_20'].iloc[i-2]:
signals.iat[i] = -1
return signals
+70
View File
@@ -0,0 +1,70 @@
import MetaTrader5 as mt5
import pandas as pd
from logger import setup_logger
logger = setup_logger()
def initialize():
if not mt5.initialize():
logger.error("MT5初始化失败,错误代码:%d", mt5.last_error())
return False
return True
def shutdown():
mt5.shutdown()
def get_rates(symbol, timeframe, count):
rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, count)
if rates is None:
logger.info(f"获取{symbol}历史数据失败")
return None
return rates
def has_open_position(symbol):
positions = mt5.positions_get(symbol=symbol)
return positions is not None and len(positions) > 0
def close_all(symbol):
positions = mt5.positions_get(symbol=symbol)
if positions is None:
return
for pos in positions:
request = {
"action": mt5.TRADE_ACTION_DEAL,
"position": pos.ticket,
"symbol": symbol,
"volume": pos.volume,
"type": mt5.ORDER_TYPE_SELL if pos.type == 0 else mt5.ORDER_TYPE_BUY,
"price": mt5.symbol_info_tick(symbol).bid if pos.type == 0 else mt5.symbol_info_tick(symbol).ask,
"deviation": 20,
"magic": 234000,
"comment": "Close position",
"type_filling": mt5.ORDER_FILLING_RETURN,
}
mt5.order_send(request)
def send_order(symbol, order_type, volume=0.01):
symbol_info_tick = mt5.symbol_info_tick(symbol)
if symbol_info_tick is None:
logger.error(f"无法获取{symbol}行情")
return
price = symbol_info_tick.ask if order_type == "buy" else symbol_info_tick.bid
order_type_mt5 = mt5.ORDER_TYPE_BUY if order_type == "buy" else mt5.ORDER_TYPE_SELL
request = {
"action": mt5.TRADE_ACTION_DEAL,
"symbol": symbol,
"volume": volume,
"type": order_type_mt5,
"price": price,
"deviation": 20,
"magic": 234000,
"comment": f"{order_type} order",
"type_filling": mt5.ORDER_FILLING_RETURN,
}
result = mt5.order_send(request)
if result.retcode != mt5.TRADE_RETCODE_DONE:
logger.error(f"下单失败,retcode={result.retcode}")
else:
logger.info(f"下单成功: {order_type} {symbol} {volume}")