363 lines
13 KiB
Python
363 lines
13 KiB
Python
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
|
||
# flake8: noqa: F401
|
||
# isort: skip_file
|
||
# --- Do not remove these imports ---
|
||
import numpy as np
|
||
import pandas as pd
|
||
from datetime import datetime, timedelta, timezone
|
||
from pandas import DataFrame
|
||
from typing import Optional, Union
|
||
|
||
from freqtrade.strategy import (
|
||
IStrategy,
|
||
Trade,
|
||
Order,
|
||
PairLocks,
|
||
informative, # @informative decorator
|
||
# Hyperopt Parameters
|
||
BooleanParameter,
|
||
CategoricalParameter,
|
||
DecimalParameter,
|
||
IntParameter,
|
||
RealParameter,
|
||
# timeframe helpers
|
||
timeframe_to_minutes,
|
||
timeframe_to_next_date,
|
||
timeframe_to_prev_date,
|
||
# Strategy helper functions
|
||
merge_informative_pair,
|
||
stoploss_from_absolute,
|
||
stoploss_from_open,
|
||
)
|
||
|
||
# --------------------------------
|
||
# Add your lib to import here
|
||
import talib.abstract as ta
|
||
from technical import qtpylib
|
||
|
||
|
||
# 基于x.py策略的MA60/MA120趋势跟踪策略
|
||
class MA60MA120TrendStrategy(IStrategy):
|
||
"""
|
||
基于x.py策略的MA60/MA120趋势跟踪策略
|
||
|
||
策略逻辑:
|
||
1. 买入条件:
|
||
- 价格在MA60上方且距离MA60不超过10%
|
||
- MA60呈上升趋势(过去5天中至少3天上升)
|
||
- 成交量放大(当日成交量大于过去5日平均)
|
||
- 前5天中下跌天数不超过2天
|
||
- 当天收盘价大于5天前的价格
|
||
- 价格在MA120上方
|
||
- MA120过去5天不是下降趋势
|
||
- RSI小于阈值(默认70)
|
||
|
||
2. 卖出条件:
|
||
- 价格跌破MA60且跌幅超过10%
|
||
|
||
更多信息: https://www.freqtrade.io/en/latest/strategy-customization/
|
||
"""
|
||
|
||
# Strategy interface version - allow new iterations of the strategy interface.
|
||
# Check the documentation or the Sample strategy to get the latest version.
|
||
INTERFACE_VERSION = 3
|
||
|
||
# Can this strategy go short?
|
||
can_short: bool = False
|
||
|
||
# Minimal ROI designed for the strategy.
|
||
# 基于x.py策略,使用更保守的ROI设置
|
||
# minimal_roi = {
|
||
# "30": 0.3, # 20% 目标收益
|
||
# # ""
|
||
# }
|
||
|
||
# Optimal stoploss designed for the strategy.
|
||
# 基于x.py策略的卖出条件:跌破MA60且跌幅超过10%
|
||
stoploss = -1
|
||
|
||
# Trailing stoploss
|
||
# trailing_stop = True
|
||
# trailing_only_offset_is_reached = False
|
||
# trailing_stop_positive = 0.3
|
||
# trailing_stop_positive_offset = 0.0 # Disabled / not configured
|
||
|
||
# Optimal timeframe for the strategy.
|
||
# 使用日线数据,因为x.py策略基于日线
|
||
timeframe = "1d"
|
||
|
||
# Run "populate_indicators()" only for new candle.
|
||
process_only_new_candles = True
|
||
|
||
# These values can be overridden in the config.
|
||
use_exit_signal = True
|
||
exit_profit_only = False
|
||
ignore_roi_if_entry_signal = False
|
||
|
||
# 移除原有的RSI参数,因为新策略使用固定值
|
||
# 可以添加其他可优化的参数
|
||
ma60_period = IntParameter(low=30, high=90, default=60, space="buy", optimize=True, load=True)
|
||
ma120_period = IntParameter(low=60, high=180, default=120, space="buy", optimize=True, load=True)
|
||
rsi_threshold = IntParameter(low=50, high=80, default=70, space="buy", optimize=True, load=True)
|
||
volume_surge_multiplier = DecimalParameter(low=0.5, high=2.0, default=1.0, space="buy", optimize=True, load=True)
|
||
|
||
# Number of candles the strategy requires before producing valid signals
|
||
# 需要更多数据来计算MA120和趋势指标
|
||
startup_candle_count: int = 200
|
||
|
||
# Optional order type mapping.
|
||
order_types = {
|
||
"entry": "limit",
|
||
"exit": "limit",
|
||
"stoploss": "market",
|
||
"stoploss_on_exchange": False,
|
||
}
|
||
|
||
# Optional order time in force.
|
||
order_time_in_force = {"entry": "GTC", "exit": "GTC"}
|
||
|
||
plot_config = {
|
||
"main_plot": {
|
||
"ma60": {"color": "blue", "width": 2},
|
||
"ma120": {"color": "orange", "width": 2},
|
||
},
|
||
"subplots": {
|
||
"RSI": {
|
||
"rsi": {"color": "red"},
|
||
"rsi_threshold": {"color": "gray", "type": "line", "width": 1},
|
||
},
|
||
"Volume": {
|
||
"volume": {"color": "lightblue"},
|
||
"volume_5ma": {"color": "blue"},
|
||
},
|
||
"Signals": {
|
||
"buy_signal": {"color": "green", "type": "scatter", "marker": "^"},
|
||
"sell_signal": {"color": "red", "type": "scatter", "marker": "v"},
|
||
},
|
||
},
|
||
}
|
||
|
||
def informative_pairs(self):
|
||
"""
|
||
Define additional, informative pair/interval combinations to be cached from the exchange.
|
||
These pair/interval combinations are non-tradeable, unless they are part
|
||
of the whitelist as well.
|
||
For more information, please consult the documentation
|
||
:return: List of tuples in the format (pair, interval)
|
||
Sample: return [("ETH/USDT", "5m"),
|
||
("BTC/USDT", "15m"),
|
||
]
|
||
"""
|
||
return []
|
||
|
||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||
"""
|
||
基于x.py策略的技术指标计算
|
||
实现MA60/MA120趋势跟踪策略的所有必要指标
|
||
:param dataframe: Dataframe with data from the exchange
|
||
:param metadata: Additional information, like the currently traded pair
|
||
:return: a Dataframe with all mandatory indicators for the strategies
|
||
"""
|
||
|
||
# 基础移动平均线指标
|
||
# ------------------------------------
|
||
|
||
# MA60 - 使用可优化参数
|
||
dataframe['ma60'] = ta.SMA(dataframe, timeperiod=60)
|
||
|
||
# MA120 - 使用可优化参数
|
||
dataframe['ma120'] = ta.SMA(dataframe, timeperiod=120)
|
||
|
||
# RSI指标
|
||
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
|
||
|
||
# 成交量相关指标
|
||
# ------------------------------------
|
||
|
||
# 成交量5日移动平均
|
||
dataframe['volume_5ma'] = dataframe['volume'].rolling(window=5).mean()
|
||
|
||
# 成交量放大信号:当日成交量大于过去5日平均的倍数
|
||
dataframe['volume_surge'] = dataframe['volume'] > (dataframe['volume_5ma'] * 1)
|
||
|
||
# 价格与MA60关系指标
|
||
# ------------------------------------
|
||
|
||
# 价格在MA60上方且距离MA60不超过10%
|
||
dataframe['price_above_ma60'] = (
|
||
(dataframe['close'] > dataframe['ma60']) &
|
||
((dataframe['ma60'] - dataframe['close']) / dataframe['close'] < 0.1)
|
||
)
|
||
|
||
# 价格在MA120上方
|
||
dataframe['price_above_ma120'] = dataframe['close'] > dataframe['ma120']
|
||
|
||
# 前一天在MA60附近(95%-105%)
|
||
dataframe['prev_near_ma'] = (
|
||
(dataframe['close'].shift(1) >= dataframe['ma60'].shift(1) * 0.95) &
|
||
(dataframe['close'].shift(1) <= dataframe['ma60'].shift(1) * 1.05)
|
||
)
|
||
|
||
# 前一天在MA60下方
|
||
dataframe['prev_below_ma'] = dataframe['close'].shift(1) < dataframe['ma60'].shift(1)
|
||
|
||
# 突破信号:前一天在MA60附近或下方,当天在MA60上方
|
||
dataframe['breakthrough'] = (
|
||
(dataframe['prev_below_ma'] | dataframe['prev_near_ma']) &
|
||
dataframe['price_above_ma60']
|
||
)
|
||
|
||
# MA60趋势指标
|
||
# ------------------------------------
|
||
|
||
# 计算MA60的上升趋势(过去5天中至少3天上升)
|
||
trend_days = 5
|
||
ma_trend = []
|
||
for i in range(len(dataframe)):
|
||
if i < trend_days - 1:
|
||
ma_trend.append(False)
|
||
else:
|
||
start_idx = i - trend_days + 1
|
||
ma_values = dataframe['ma60'].iloc[start_idx:i+1].values
|
||
if pd.isna(ma_values).any():
|
||
ma_trend.append(False)
|
||
else:
|
||
# 检查是否呈上升趋势(至少3天上升)
|
||
ma_trend.append(sum(ma_values[1:] > ma_values[:-1]) > trend_days - 3)
|
||
dataframe['ma_uptrend'] = ma_trend
|
||
|
||
# MA120趋势指标
|
||
# ------------------------------------
|
||
|
||
# 计算MA120的趋势(过去5天)不能为下降
|
||
ma120_trend = []
|
||
for i in range(len(dataframe)):
|
||
if i < 4:
|
||
ma120_trend.append(True) # 数据不足时不限制
|
||
else:
|
||
window = dataframe['ma120'].iloc[i-4:i+1].values
|
||
if pd.isna(window).any():
|
||
ma120_trend.append(True)
|
||
else:
|
||
# 只要有一天不是上升就不是下降趋势
|
||
ma120_trend.append((window[1:] >= window[:-1]).all())
|
||
dataframe['ma120_not_downtrend'] = ma120_trend
|
||
|
||
# 价格动量指标
|
||
# ------------------------------------
|
||
|
||
# 阳线条件:收盘价 > 开盘价,且阳线幅度至少0.5%
|
||
dataframe['is_bullish'] = (
|
||
(dataframe['close'] > dataframe['open']) &
|
||
((dataframe['close'] - dataframe['open']) / dataframe['open'] >= 0.005)
|
||
)
|
||
|
||
# 前5天的下跌天数不超过2天
|
||
dataframe['is_down_day'] = dataframe['close'] < dataframe['open']
|
||
dataframe['down_days_in_5'] = dataframe['is_down_day'].rolling(window=5, min_periods=1).sum()
|
||
dataframe['not_too_many_downs'] = dataframe['down_days_in_5'] <= 2
|
||
|
||
# 当天收盘价大于第五天前的价格
|
||
dataframe['price_higher_than_5days_ago'] = dataframe['close'] > dataframe['close'].shift(5)
|
||
|
||
# RSI条件 - 使用可优化参数
|
||
dataframe['rsi_lt_threshold'] = dataframe['rsi'] < 70
|
||
|
||
# 综合买入信号
|
||
# ------------------------------------
|
||
|
||
dataframe["buy_signal"] = (
|
||
dataframe["price_above_ma60"] &
|
||
dataframe["ma_uptrend"] &
|
||
dataframe['ma60'].notna() &
|
||
dataframe["volume_surge"] &
|
||
dataframe["not_too_many_downs"] &
|
||
dataframe["price_higher_than_5days_ago"] &
|
||
dataframe['price_above_ma120'] &
|
||
dataframe['ma120_not_downtrend']
|
||
# &
|
||
# dataframe['rsi_lt_threshold']
|
||
)
|
||
|
||
# 卖出信号:价格跌破MA60且跌幅超过10%
|
||
dataframe['sell_signal'] = (
|
||
(~dataframe['price_above_ma60']) &
|
||
((dataframe['ma60'] - dataframe['close']) / dataframe['ma60'] > 0.1) &
|
||
dataframe['ma60'].notna()
|
||
)
|
||
|
||
return dataframe
|
||
|
||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||
"""
|
||
基于x.py策略的买入信号逻辑
|
||
实现MA60/MA120趋势跟踪策略的买入条件
|
||
:param dataframe: DataFrame
|
||
:param metadata: Additional information, like the currently traded pair
|
||
:return: DataFrame with entry columns populated
|
||
"""
|
||
# 多头买入信号 - 基于x.py的buy_signal逻辑
|
||
dataframe.loc[
|
||
(
|
||
# 价格在MA60上方且距离MA60不超过10%
|
||
dataframe["price_above_ma60"] &
|
||
# MA60呈上升趋势(过去5天中至少3天上升)
|
||
dataframe["ma_uptrend"] &
|
||
# MA60数据有效
|
||
dataframe['ma60'].notna() &
|
||
# 成交量放大(当日成交量大于过去5日平均)
|
||
dataframe["volume_surge"] &
|
||
# 前5天中下跌天数不超过2天
|
||
dataframe["not_too_many_downs"] &
|
||
# 当天收盘价大于5天前的价格
|
||
dataframe["price_higher_than_5days_ago"] &
|
||
# 价格在MA120上方
|
||
dataframe['price_above_ma120'] &
|
||
# MA120过去5天不是下降趋势
|
||
dataframe['ma120_not_downtrend'] &
|
||
# RSI小于阈值
|
||
dataframe['rsi_lt_threshold'] &
|
||
# 确保成交量不为0
|
||
(dataframe["volume"] > 0)
|
||
),
|
||
"enter_long",
|
||
] = 1
|
||
|
||
|
||
return dataframe
|
||
|
||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||
"""
|
||
基于x.py策略的卖出信号逻辑
|
||
实现MA60/MA120趋势跟踪策略的卖出条件
|
||
:param dataframe: DataFrame
|
||
:param metadata: Additional information, like the currently traded pair
|
||
:return: DataFrame with exit columns populated
|
||
"""
|
||
# 多头卖出信号 - 基于x.py的sell_signal逻辑
|
||
# dataframe.loc[
|
||
# (
|
||
# # 价格跌破MA60且跌幅超过10%
|
||
# (~dataframe['price_above_ma60']) &
|
||
# ((dataframe['ma60'] - dataframe['close']) / dataframe['ma60'] > 0.1) &
|
||
# dataframe['ma60'].notna() &
|
||
# # 确保成交量不为0
|
||
# (dataframe["volume"] > 0)
|
||
# ),
|
||
# "exit_long",
|
||
# ] = 1
|
||
|
||
dataframe.loc[
|
||
(
|
||
# 价格跌破MA60且跌幅超过10%
|
||
(dataframe['close'] < dataframe['ma120']) &
|
||
dataframe['ma120'].notna() &
|
||
# 确保成交量不为0
|
||
(dataframe["volume"] > 0)
|
||
),
|
||
"exit_long",
|
||
] = 1
|
||
|
||
|
||
return dataframe
|