Files
cta/user_data/strategies/sample_strategy.py
2025-10-25 17:19:16 +08:00

363 lines
13 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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