ema双均线趋势策略
This commit is contained in:
81
trend.py
Normal file
81
trend.py
Normal file
@@ -0,0 +1,81 @@
|
||||
import vectorbt as vbt
|
||||
import pandas as pd
|
||||
|
||||
# ========================
|
||||
# 数据加载
|
||||
# ========================
|
||||
# 从本地读取 ETH/USDT 1天K线数据(来自OKX)
|
||||
df = pd.read_feather("/Users/aszer/Documents/vscode/cta/user_data/data/okx/ADA_USDT-1d.feather")
|
||||
# 提取收盘价作为交易价格
|
||||
price = df["close"]
|
||||
|
||||
# ========================
|
||||
# 技术指标计算
|
||||
# ========================
|
||||
# 计算10日指数移动平均线(EMA)
|
||||
ema_10 = vbt.MA.run(price, window=10, ewm=True).ma
|
||||
|
||||
# 计算20日指数移动平均线(EMA)
|
||||
ema_20 = vbt.MA.run(price, window=20, ewm=True).ma
|
||||
|
||||
# 计算6周期相对强弱指数(RSI)
|
||||
rsi_6 = vbt.RSI.run(price, window=6).rsi
|
||||
|
||||
# 计算12周期相对强弱指数(RSI)
|
||||
rsi_12 = vbt.RSI.run(price, window=12).rsi
|
||||
|
||||
# ========================
|
||||
# 买入条件定义
|
||||
# ========================
|
||||
# 条件1:EMA多头排列 + 短期趋势加速
|
||||
# - EMA10 > EMA20:短期趋势强于长期趋势(多头排列)
|
||||
# - EMA10昨日 > 前日:短期均线继续上行,显示动量增强
|
||||
ema_bullish_cross = (ema_10 > ema_20) & (ema_10.shift(1) > ema_10.shift(2))
|
||||
|
||||
# 条件2:双周期RSI进入深度超卖区,暗示反弹可能
|
||||
# - 过去3天(含前天)的6周期RSI最高值 < 21,表示近期极度超卖
|
||||
# - 过去3天的12周期RSI最高值 < 26.25,确认中周期也处于超卖状态
|
||||
# 注:此条件未显式检查“今日反弹”,可后续增强
|
||||
rsi_oversold_bounce = (rsi_6.shift(1).rolling(3).max() < 21) & \
|
||||
(rsi_12.shift(1).rolling(3).max() < 26.25)
|
||||
|
||||
# 合并所有买入信号:任一条件满足即产生买入信号
|
||||
entries = ema_bullish_cross | rsi_oversold_bounce
|
||||
|
||||
# ========================
|
||||
# 卖出条件定义
|
||||
# ========================
|
||||
# 计算长期均线与短期均线的价差(空头趋势强度)
|
||||
diff = ema_20 - ema_10 # 当diff扩大,表示空头趋势加强
|
||||
|
||||
# 条件1:EMA10连续5天下跌(动量走弱)
|
||||
# - 当前EMA10 < 过去5天(不含今日)的最低EMA10值
|
||||
ema10_falling = ema_10 < ema_10.shift(1).rolling(window=5).min()
|
||||
|
||||
# 条件2:空头趋势加速(价差达到近期最大)
|
||||
# - 当前diff是过去6天(含今日)中的最大值,表示空头力量最强
|
||||
diff_expanding = diff == diff.rolling(window=6).max()
|
||||
|
||||
# 合并卖出信号:两个条件同时满足才卖出
|
||||
sell_cond = ema10_falling & diff_expanding
|
||||
exits = sell_cond
|
||||
|
||||
# ========================
|
||||
# 回测执行
|
||||
# ========================
|
||||
# 使用 vectorbt 的信号回测引擎,构建投资组合
|
||||
pf = vbt.Portfolio.from_signals(
|
||||
price, # 价格序列
|
||||
entries, # 买入信号
|
||||
exits, # 卖出信号
|
||||
init_cash=100, # 初始资金 100 USDT
|
||||
fees=0.001, # 交易手续费 0.1%(买卖均收)
|
||||
sl_stop=0.05, # 止损:从最高价回撤5%时触发
|
||||
freq="1D" # 数据频率为每日,用于复利和年化计算
|
||||
)
|
||||
|
||||
# ========================
|
||||
# 输出回测统计结果
|
||||
# ========================
|
||||
print(pf.stats())
|
||||
|
||||
205
user_data/strategies/trend_strategy.py
Normal file
205
user_data/strategies/trend_strategy.py
Normal file
@@ -0,0 +1,205 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
|
||||
# flake8: noqa: F401
|
||||
# isort: skip_file
|
||||
# --- Do not remove these libs ---
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from pandas import DataFrame
|
||||
from datetime import datetime
|
||||
from typing import Optional, Union
|
||||
|
||||
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter,
|
||||
IntParameter, merge_informative_pair, stoploss_from_absolute,
|
||||
stoploss_from_open)
|
||||
from freqtrade.strategy.interface import IStrategy
|
||||
import talib.abstract as ta
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
|
||||
|
||||
class TrendStrategy(IStrategy):
|
||||
"""
|
||||
趋势策略 - 基于EMA和RSI的趋势跟踪策略
|
||||
|
||||
策略逻辑:
|
||||
买入条件:
|
||||
1. EMA多头排列 + 短期趋势加速:EMA10 > EMA20 且 EMA10继续上行
|
||||
2. 双周期RSI深度超卖反弹:6周期RSI和12周期RSI都进入超卖区域
|
||||
|
||||
卖出条件:
|
||||
1. EMA10连续下跌(动量走弱)
|
||||
2. 空头趋势加速(EMA价差达到近期最大)
|
||||
"""
|
||||
|
||||
# Strategy interface version - allow new iterations of the strategy
|
||||
INTERFACE_VERSION = 3
|
||||
|
||||
# Optimal timeframe for the strategy
|
||||
timeframe = '1d'
|
||||
|
||||
# Can this strategy go short?
|
||||
can_short: bool = False
|
||||
|
||||
# Minimal ROI designed for the strategy.
|
||||
# minimal_roi = {
|
||||
# "0": 0.1 # 10% ROI,让止损和卖出条件来控制退出
|
||||
# }
|
||||
|
||||
# Optimal stoploss designed for the strategy
|
||||
stoploss = -0.05 # 5% 止损
|
||||
|
||||
# Trailing stoploss
|
||||
trailing_stop = False
|
||||
trailing_stop_positive = None
|
||||
trailing_stop_positive_offset = 0.0
|
||||
trailing_only_offset_is_reached = False
|
||||
|
||||
# Run "populate_indicators" only for new candle
|
||||
process_only_new_candles = False
|
||||
|
||||
# These values can be overridden in the config
|
||||
use_exit_signal = True
|
||||
exit_profit_only = False
|
||||
ignore_roi_if_entry_signal = False
|
||||
|
||||
# Number of candles the strategy requires before producing valid signals
|
||||
startup_candle_count: int = 30
|
||||
|
||||
# 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'
|
||||
}
|
||||
|
||||
# 策略参数
|
||||
ema_short_window = IntParameter(8, 15, default=10, space="buy")
|
||||
ema_long_window = IntParameter(15, 25, default=20, space="buy")
|
||||
rsi_short_window = IntParameter(4, 8, default=6, space="buy")
|
||||
rsi_long_window = IntParameter(10, 15, default=12, space="buy")
|
||||
|
||||
# RSI超卖阈值
|
||||
rsi_short_oversold = DecimalParameter(15.0, 25.0, default=21.0, space="buy")
|
||||
rsi_long_oversold = DecimalParameter(20.0, 30.0, default=26.25, space="buy")
|
||||
|
||||
# 趋势检查窗口
|
||||
trend_check_window = IntParameter(3, 7, default=3, space="buy")
|
||||
momentum_check_window = IntParameter(3, 7, default=5, space="sell")
|
||||
diff_check_window = IntParameter(4, 8, default=6, space="sell")
|
||||
|
||||
def informative_pairs(self):
|
||||
"""
|
||||
Define additional, informative pair/interval combinations to be cached from the exchange.
|
||||
These pairs will automatically be available for use in the `populate_indicators` method.
|
||||
"""
|
||||
return []
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
Adds several different TA indicators to the given DataFrame
|
||||
|
||||
Performance Note: For the best performance be frugal on the number of indicators
|
||||
you are using. Let uncomment only the indicator you are using in your strategies
|
||||
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
|
||||
: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 strategy
|
||||
"""
|
||||
|
||||
# EMA指标
|
||||
dataframe['ema_short'] = ta.EMA(dataframe, timeperiod=self.ema_short_window.value)
|
||||
dataframe['ema_long'] = ta.EMA(dataframe, timeperiod=self.ema_long_window.value)
|
||||
|
||||
# RSI指标
|
||||
dataframe['rsi_short'] = ta.RSI(dataframe, timeperiod=self.rsi_short_window.value)
|
||||
dataframe['rsi_long'] = ta.RSI(dataframe, timeperiod=self.rsi_long_window.value)
|
||||
|
||||
# 计算EMA价差(用于卖出条件)
|
||||
dataframe['ema_diff'] = dataframe['ema_long'] - dataframe['ema_short']
|
||||
|
||||
# 计算EMA短期趋势(用于买入条件)
|
||||
dataframe['ema_short_rising'] = dataframe['ema_short'] > dataframe['ema_short'].shift(1)
|
||||
dataframe['ema_short_rising_2'] = dataframe['ema_short'].shift(1) > dataframe['ema_short'].shift(2)
|
||||
|
||||
# 计算RSI超卖条件
|
||||
dataframe['rsi_short_oversold'] = (
|
||||
dataframe['rsi_short'].shift(1).rolling(window=self.trend_check_window.value).max() < self.rsi_short_oversold.value
|
||||
)
|
||||
dataframe['rsi_long_oversold'] = (
|
||||
dataframe['rsi_long'].shift(1).rolling(window=self.trend_check_window.value).max() < self.rsi_long_oversold.value
|
||||
)
|
||||
|
||||
# 计算EMA10连续下跌条件(用于卖出)
|
||||
dataframe['ema_short_falling'] = (
|
||||
dataframe['ema_short'] < dataframe['ema_short'].shift(1).rolling(window=self.momentum_check_window.value).min()
|
||||
)
|
||||
|
||||
# 计算价差扩大条件(用于卖出)
|
||||
dataframe['ema_diff_expanding'] = (
|
||||
dataframe['ema_diff'] == dataframe['ema_diff'].rolling(window=self.diff_check_window.value).max()
|
||||
)
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
Based on TA indicators, populates the entry signal for the given dataframe
|
||||
:param dataframe: DataFrame populated with indicators
|
||||
:param metadata: Additional information, like the currently traded pair
|
||||
:return: DataFrame with entry columns populated
|
||||
"""
|
||||
|
||||
# 条件1:EMA多头排列 + 短期趋势加速
|
||||
ema_bullish_cross = (
|
||||
(dataframe['ema_short'] > dataframe['ema_long']) &
|
||||
(dataframe['ema_short_rising'] & dataframe['ema_short_rising_2'])
|
||||
)
|
||||
|
||||
# 条件2:双周期RSI深度超卖反弹
|
||||
rsi_oversold_bounce = (
|
||||
dataframe['rsi_short_oversold'] &
|
||||
dataframe['rsi_long_oversold']
|
||||
)
|
||||
|
||||
# 合并买入条件:任一条件满足即产生买入信号
|
||||
dataframe.loc[
|
||||
ema_bullish_cross | rsi_oversold_bounce,
|
||||
'enter_long'
|
||||
] = 1
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
Based on TA indicators, populates the exit signal for the given dataframe
|
||||
:param dataframe: DataFrame populated with indicators
|
||||
:param metadata: Additional information, like the currently traded pair
|
||||
:return: DataFrame with exit columns populated
|
||||
"""
|
||||
|
||||
# 卖出条件:EMA10连续下跌 + 空头趋势加速
|
||||
sell_condition = (
|
||||
dataframe['ema_short_falling'] &
|
||||
dataframe['ema_diff_expanding']
|
||||
)
|
||||
|
||||
dataframe.loc[
|
||||
sell_condition,
|
||||
'exit_long'
|
||||
] = 1
|
||||
|
||||
return dataframe
|
||||
|
||||
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
|
||||
current_rate: float, current_profit: float, **kwargs) -> float:
|
||||
"""
|
||||
自定义止损逻辑
|
||||
这里保持原有的5%止损,但可以根据需要添加更复杂的止损逻辑
|
||||
"""
|
||||
return self.stoploss
|
||||
Reference in New Issue
Block a user