ema双均线趋势策略

This commit is contained in:
2025-10-26 01:52:05 +08:00
parent 04ad5b6f1b
commit 2e4b08ac8c
2 changed files with 286 additions and 0 deletions

81
trend.py Normal file
View 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
# ========================
# 买入条件定义
# ========================
# 条件1EMA多头排列 + 短期趋势加速
# - 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扩大表示空头趋势加强
# 条件1EMA10连续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())

View 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
"""
# 条件1EMA多头排列 + 短期趋势加速
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