SuperTrend 趋势跟踪策略 使用 SuperTrend 指标判断趋势方向并进行交易
Timeframe
N/A
Direction
Long Only
Stoploss
-100.0%
Trailing Stop
No
ROI
0m: 100.0%
Interface Version
3
Startup Candles
N/A
Indicators
3
freqtrade/freqtrade-strategies
freqtrade/freqtrade-strategies
this is an example class, implementing a PSAR based trailing stop loss you are supposed to take the `custom_stoploss()` and `populate_indicators()` parts and adapt it to your own strategy
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
# isort: skip_file
"""
SuperTrend Strategy + 更优价格重入逻辑(优于止损位)
基于 TradingView Pine Script 的 SuperTrend 策略转换
策略逻辑:
1. 趋势反转时,重置所有标记,允许重新寻找入场机会
2. 记录第一次满足入场条件时的价格
3. 开仓规则(同一趋势内):
- direction < 0 (上涨趋势) 且 supertrend > supertrend[2] 且 MACD 多头
* 第一次满足条件时可以开多
* 或者:已平仓后,价格低于第一次入场的止损位(-5%)时,可以重新开多
- direction > 0 (下跌趋势) 且 supertrend < supertrend[3] 且 MACD 空头
* 第一次满足条件时可以开空
* 或者:已平仓后,价格高于第一次入场的止损位(+5%)时,可以重新开空
4. 平仓条件:
- 趋势反转平仓
- RSI 超买超卖背离平仓
- ROI 止盈(50%,10% 现货 × 5倍杠杆)
5. 风控:
- 止损:-25%(5% 现货 × 5倍杠杆)
"""
import numpy as np
import pandas as pd
from pandas import DataFrame
from typing import Optional
from datetime import datetime
from freqtrade.strategy import (
IStrategy,
IntParameter,
DecimalParameter,
Trade,
stoploss_from_open,
)
import talib.abstract as ta
import pandas_ta as pta
class SuperTrendStrategy(IStrategy):
"""
SuperTrend 趋势跟踪策略
使用 SuperTrend 指标判断趋势方向并进行交易
"""
INTERFACE_VERSION = 3
# 允许做空
can_short: bool = True
max_profit_rate = DecimalParameter(0.1, 0.22, default=0.10, space="sell", optimize=True)
max_stop_loss_rate = DecimalParameter(0.1, 0.15, default=0.05, space="sell", optimize=True)
max_profit_tailing = DecimalParameter(0.02, 0.06, default=0.05, space="sell", optimize=True, load=True)
# 杠杆倍数
leverage_num = 5 # 5倍杠杆
# 直接止盈 - 达到目标立即全部平仓
# 格式: {"分钟数": 收益率}
minimal_roi = {
"0": 1*max_profit_rate.value * leverage_num, # 达到 75% (15% × 5倍杠杆) 直接全部平仓
}
# 关闭内置移动止损,使用自定义分阶段止损
trailing_stop = False
# 初始止损(会被 custom_stoploss 覆盖)
stoploss = -1*max_stop_loss_rate.value * leverage_num
# 启用自定义止损
use_custom_stoploss = False
# 时间周期
# 只在新K线时处理
process_only_new_candles = True
# 使用退出信号
use_exit_signal = True
exit_profit_only = False
ignore_roi_if_entry_signal = False
# 可优化参数 - TradingView 默认: factor=5, atrPeriod=10
st_factor = DecimalParameter(1.0, 10.0, default=5.0, space="buy", optimize=True)
st_period = IntParameter(5, 20, default=10, space="buy", optimize=True)
# st_factor = 9
# st_period = 17
# 策略启动所需的K线数量
startup_candle_count: int = 50
# 订单类型
order_types = {
"entry": "limit",
"exit": "limit",
"stoploss": "market",
"stoploss_on_exchange": False, # 关闭交易所止损,由 Bot 本地监控
}
plot_config = {
"main_plot": {
"supertrend": {"color": "blue"},
},
"subplots": {
"Direction": {
"direction": {"color": "green"},
},
},
}
count = 0
def __init__(self, config: dict) -> None:
super().__init__(config)
# 为每个交易对存储状态(实盘兼容)
self.pair_state = {} # 存储每个交易对的状态
def get_pair_state(self, pair: str):
"""获取交易对的状态,不存在则初始化"""
if pair not in self.pair_state:
self.pair_state[pair] = {
'long_signal_triggered': False,
'short_signal_triggered': False,
'first_long_entry_price': None,
'first_short_entry_price': None,
}
return self.pair_state[pair]
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, after_fill: bool, **kwargs):
"""
从当前价格的回撤止损:
- 利润 < 50%(10% × 5倍杠杆):从当前价格回撤 10% 止损
- 利润 >= 50%:从当前价格回撤 5% 止损
返回值:相对于当前价格的止损百分比(负数)
"""
lev = self.leverage_num
if current_profit >= (self.max_profit_rate.value - self.max_profit_tailing.value)* lev: # 利润 >= 50%(10% × 5倍杠杆)
# 从当前价格回撤 5% 止损
return -1*self.max_profit_tailing.value*0.5*lev
# else:
# # 利润 < 50%:从当前价格回撤 10% 止损
# return -1*self.max_stop_loss_rate.value*lev
def leverage(self, pair: str, current_time, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag,
side: str, **kwargs):
"""
设置杠杆倍数
"""
return self.leverage_num
def check_position(self, pair: str, is_long: bool) -> bool:
"""检查是否有持仓(实盘安全)"""
if hasattr(self, 'wallets'): # 实盘模式
# 从实际交易中检查持仓
from freqtrade.persistence import Trade
trades = Trade.get_trades_proxy(pair=pair, is_open=True)
for trade in trades:
if (is_long and not trade.is_short) or (not is_long and trade.is_short):
return True
return False
def populate_indicators(self, dataframe: DataFrame, metadata: dict):
"""
完全复制 Pine Script 逻辑:SuperTrend + MACD + 趋势反转计数
"""
# ========== SuperTrend 指标 ==========
st = pta.supertrend(
dataframe['high'],
dataframe['low'],
dataframe['close'],
length=int(self.st_period.value),
multiplier=float(self.st_factor.value)
)
length = int(self.st_period.value)
mult = float(self.st_factor.value)
st_col = f'SUPERT_{length}_{mult}'
std_col = f'SUPERTd_{length}_{mult}'
dataframe['supertrend'] = st[st_col]
dataframe['direction'] = -1 * st[std_col]
# 前几根K线的 supertrend 值
dataframe['supertrend_2'] = dataframe['supertrend'].shift(2)
dataframe['supertrend_3'] = dataframe['supertrend'].shift(3)
# 前一根K线的方向
dataframe['prev_direction'] = dataframe['direction'].shift(1)
dataframe['rsi'] = ta.RSI(dataframe['close'], timeperiod=14)
dataframe["rsi_prev"] = dataframe['rsi'].shift(1)
# 前一根K线的 high 和 low(用于 RSI 背离检测)
dataframe['high_prev'] = dataframe['high'].shift(1)
dataframe['low_prev'] = dataframe['low'].shift(1)
# ========== MACD 指标 ==========
macd_line, signal_line, hist_line = ta.MACD(
dataframe['close'],
fastperiod=12,
slowperiod=26,
signalperiod=9
)
dataframe['macd'] = macd_line
dataframe['macdsignal'] = signal_line
dataframe['macdhist'] = hist_line
# MACD 金叉死叉判断
dataframe['macd_golden_cross'] = (
(dataframe['macd'] > dataframe['macdsignal']) &
(dataframe['macd'].shift(1) <= dataframe['macdsignal'].shift(1))
)
dataframe['macd_death_cross'] = (
(dataframe['macd'] < dataframe['macdsignal']) &
(dataframe['macd'].shift(1) >= dataframe['macdsignal'].shift(1))
)
# MACD 趋势判断
dataframe['macd_bullish'] = dataframe['macd'] > dataframe['macdsignal']
dataframe['macd_bearish'] = dataframe['macd'] < dataframe['macdsignal']
# ========== SuperTrend 趋势反转检测 ==========
dataframe['trend_changed_to_up'] = (
(dataframe['direction'] < 0) & (dataframe['prev_direction'] > 0)
)
dataframe['trend_changed_to_down'] = (
(dataframe['direction'] > 0) & (dataframe['prev_direction'] < 0)
)
# 初始化入场和出场信号列
dataframe['enter_long'] = 0
dataframe['exit_long'] = 0
dataframe['enter_short'] = 0
dataframe['exit_short'] = 0
dataframe['rsi_exit_long'] = 0
dataframe['rsi_exit_short'] = 0
# 获取当前交易对的状态(实盘兼容)
pair = metadata.get('pair', 'unknown')
state = self.get_pair_state(pair)
# 使用持久化状态变量(从上次的状态继续)
long_signal_triggered = state['long_signal_triggered']
short_signal_triggered = state['short_signal_triggered']
first_long_entry_price = state['first_long_entry_price']
first_short_entry_price = state['first_short_entry_price']
# 实盘安全:从实际交易查询持仓状态,不依赖本地变量
has_long_position = self.check_position(pair, is_long=True)
has_short_position = self.check_position(pair, is_long=False)
for i in range(len(dataframe)):
# 趋势反转为上涨 - 允许开多仓,重置记录价格
if dataframe['trend_changed_to_up'].iloc[i]:
long_signal_triggered = True # 允许开多仓
short_signal_triggered = False # 禁止开空仓
first_long_entry_price = None # 重置第一次入场价格
# 趋势反转为下跌 - 允许开空仓,重置记录价格
if dataframe['trend_changed_to_down'].iloc[i]:
short_signal_triggered = True # 允许开空仓
long_signal_triggered = False # 禁止开多仓
first_short_entry_price = None # 重置第一次入场价格
# 检测做多条件(第一次或已平仓后更优价格重入)
if dataframe['direction'].iloc[i] < 0:
if dataframe['supertrend'].iloc[i] > dataframe['supertrend_2'].iloc[i]:
# 记录第一次满足条件的价格
if first_long_entry_price is None and dataframe['macd_bullish'].iloc[i]:
first_long_entry_price = dataframe['close'].iloc[i]
# 判断是否可以开多(必须无持仓)
can_open_long = False
if dataframe['macd_bullish'].iloc[i] and not has_long_position:
if long_signal_triggered:
# 第一次入场
can_open_long = True
elif first_long_entry_price is not None and dataframe['close'].iloc[i] < first_long_entry_price * (1 - self.max_stop_loss_rate.value):
# 已平仓,价格低于止损位(-5%),可以重新开仓
can_open_long = True
if can_open_long:
dataframe.loc[dataframe.index[i], 'enter_long'] = 1
long_signal_triggered = False # 标记已开仓
else:
if has_short_position:
dataframe.loc[dataframe.index[i], 'exit_short'] = 1
if dataframe['direction'].iloc[i] > 0:
if dataframe['supertrend'].iloc[i] < dataframe['supertrend_3'].iloc[i]:
# 记录第一次满足条件的价格
if first_short_entry_price is None and dataframe['macd_bearish'].iloc[i]:
first_short_entry_price = dataframe['close'].iloc[i]
# 判断是否可以开空(必须无持仓)
can_open_short = False
if dataframe['macd_bearish'].iloc[i] and not has_short_position:
if short_signal_triggered:
# 第一次入场
can_open_short = True
elif first_short_entry_price is not None and dataframe['close'].iloc[i] > first_short_entry_price * (1 + self.max_stop_loss_rate.value):
# 已平仓,价格高于止损位(+5%),可以重新开仓
can_open_short = True
if can_open_short:
dataframe.loc[dataframe.index[i], 'enter_short'] = 1
short_signal_triggered = False # 标记已开仓
else:
if has_long_position:
dataframe.loc[dataframe.index[i], 'exit_long'] = 1
# ========== RSI 超买超卖背离平仓 ==========
# 平多仓:RSI > 70 连续两根 + 价格背离
if i > self.startup_candle_count: # 确保有前一根K线可以比较
rsi = dataframe['rsi'].iloc[i]
rsi_prev = dataframe['rsi_prev'].iloc[i]
high = dataframe['high'].iloc[i]
high_prev = dataframe['high_prev'].iloc[i]
low = dataframe['low'].iloc[i]
low_prev = dataframe['low_prev'].iloc[i]
# 平多:RSI > 70 连续两根 + 背离
if has_long_position and rsi > 70 and rsi_prev > 70:
# (RSI 下降但价格上升) 或 (RSI 上升但价格下降)
if (rsi < rsi_prev and high > high_prev) or (rsi > rsi_prev and high < high_prev):
dataframe.loc[dataframe.index[i], 'rsi_exit_long'] = 1
# 平空:RSI < 30 连续两根 + 背离
if has_short_position and rsi < 30 and rsi_prev < 30:
# (RSI 下降但价格上升) 或 (RSI 上升但价格下降)
if (rsi < rsi_prev and low > low_prev) or (rsi > rsi_prev and low < low_prev):
dataframe.loc[dataframe.index[i], 'rsi_exit_short'] = 1
# 保存状态到实例变量(实盘兼容)
state['long_signal_triggered'] = long_signal_triggered
state['short_signal_triggered'] = short_signal_triggered
state['first_long_entry_price'] = first_long_entry_price
state['first_short_entry_price'] = first_short_entry_price
# 注意:has_long/short_position 从实际交易查询,不需要保存
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict):
"""
入场信号 - 第一次入场或已平仓后优于止损位重入
开多:
1. 无持仓 + supertrend > supertrend[2] + MACD 多头
2. 第一次满足条件时开仓
3. 或已平仓后,价格 < 第一次入场价格 × 0.95(止损位)时重新开仓
开空:
1. 无持仓 + supertrend < supertrend[3] + MACD 空头
2. 第一次满足条件时开仓
3. 或已平仓后,价格 > 第一次入场价格 × 1.05(止损位)时重新开仓
注:止损率 5%(现货)对应 25%(5倍杠杆)
"""
# 做多信号:首次满足 SuperTrend 条件 + MACD 多头
dataframe.loc[
(
(dataframe['enter_long'] == 1)
),
'enter_long'
] = 1
# 做空信号:首次满足 SuperTrend 条件 + MACD 空头
dataframe.loc[
(
(dataframe['enter_short'] == 1)
),
'enter_short'
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict):
"""
出场信号 - 在 populate_indicators 的 for 循环中已经设置
1. 趋势反转平仓
2. RSI 超买超卖背离平仓
"""
# 合并所有平多信号(趋势反转平仓 OR RSI 背离平仓)
dataframe.loc[
(
(dataframe['exit_long'] == 1) |
(dataframe['rsi_exit_long'] == 1)
),
'exit_long'
] = 1
# 合并所有平空信号(趋势反转平仓 OR RSI 背离平仓)
dataframe.loc[
(
(dataframe['exit_short'] == 1) |
(dataframe['rsi_exit_short'] == 1)
),
'exit_short'
] = 1
return dataframe