Compare commits

...

2 Commits

Author SHA1 Message Date
a1a06d055a 优化逻辑 2025-10-27 00:51:34 +08:00
17f43f6baa 用信号产出之后第二根k线买入 2025-10-27 00:51:00 +08:00
2 changed files with 77 additions and 70 deletions

View File

@@ -1,3 +1,5 @@
from turtle import width
from plotly.io import renderers
import vectorbt as vbt
import pandas as pd
@@ -6,9 +8,11 @@ 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")
# df = df[df['date'] >= '2025-07-01']
df.set_index("date", inplace=True)
# 提取收盘价作为交易价格
price = df["close"]
open = df["open"]
# ========================
# 技术指标计算
# ========================
@@ -40,7 +44,7 @@ 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
entries = ema_bullish_cross
# ========================
# 卖出条件定义
@@ -66,8 +70,9 @@ exits = sell_cond
# 使用 vectorbt 的信号回测引擎,构建投资组合
pf = vbt.Portfolio.from_signals(
price, # 价格序列
entries, # 买入信号
exits, # 卖出信号
entries.vbt.fshift(1), # 买入信号
exits.vbt.fshift(1), # 卖出信号
price=open,
init_cash=100, # 初始资金 100 USDT
fees=0.001, # 交易手续费 0.1%(买卖均收)
sl_stop=0.05, # 止损从最高价回撤5%时触发
@@ -78,4 +83,7 @@ pf = vbt.Portfolio.from_signals(
# 输出回测统计结果
# ========================
print(pf.stats())
# fig = pf.plot_orders(width=1200, height=800)
# fig.show(renderer='browser')
orders_df = pf.orders.records_readable
print(orders_df)

View File

@@ -48,49 +48,51 @@ class TrendStrategy(IStrategy):
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
# 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
# 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
# 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
startup_candle_count: int = 0
# Optional order type mapping
order_types = {
'entry': 'limit',
'exit': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': False
}
# 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'
}
# 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")
ema_10_window = IntParameter(8, 15, default=10, space="buy")
ema_20_window = IntParameter(15, 25, default=20, space="buy")
rsi_6_window = IntParameter(4, 8, default=6, space="buy")
rsi_12_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")
rsi_6_oversold = DecimalParameter(15.0, 30.0, default=21.0, space="buy")
rsi_12_oversold = DecimalParameter(20.0, 35.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")
# 趋势检查窗口用于RSI超卖检查
rsi_check_window = IntParameter(2, 5, default=3, space="buy")
# EMA下跌动量检查窗口用于卖出条件
ema_fall_window = IntParameter(3, 7, default=5, space="sell")
# EMA价差扩大检查窗口用于卖出条件
diff_check_window = IntParameter(4, 8, default=6, space="sell")
def informative_pairs(self):
@@ -113,37 +115,21 @@ class TrendStrategy(IStrategy):
"""
# 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)
dataframe['ema_10'] = ta.EMA(dataframe, timeperiod=self.ema_10_window.value)
dataframe['ema_20'] = ta.EMA(dataframe, timeperiod=self.ema_20_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)
dataframe['rsi_6'] = ta.RSI(dataframe, timeperiod=self.rsi_6_window.value)
dataframe['rsi_12'] = ta.RSI(dataframe, timeperiod=self.rsi_12_window.value)
# 计算EMA价差用于卖出条件
dataframe['ema_diff'] = dataframe['ema_long'] - dataframe['ema_short']
dataframe['diff'] = dataframe['ema_20'] - dataframe['ema_10']
# 计算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()
)
macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9,)
dataframe["macd"] = macd["macd"]
dataframe["macdsignal"] = macd["macdsignal"]
dataframe["macdhist"] = macd["macdhist"]
dataframe['cci'] = ta.CCI(dataframe, 14)
return dataframe
@@ -156,20 +142,24 @@ class TrendStrategy(IStrategy):
"""
# 条件1EMA多头排列 + 短期趋势加速
# EMA10 > EMA20短期趋势强于长期趋势多头排列
# EMA10昨日 > 前日:短期均线继续上行,显示动量增强
ema_bullish_cross = (
(dataframe['ema_short'] > dataframe['ema_long']) &
(dataframe['ema_short_rising'] & dataframe['ema_short_rising_2'])
(dataframe['ema_10'] > dataframe['ema_20']) &
(dataframe['ema_10'].shift(1) > dataframe['ema_10'].shift(2))
)
# 条件2双周期RSI深度超卖反弹
# 条件2双周期RSI进入深度超卖
# 过去3天的6周期RSI最高值 < 21表示近期极度超卖
# 过去3天的12周期RSI最高值 < 26.25,确认中周期也处于超卖状态
rsi_oversold_bounce = (
dataframe['rsi_short_oversold'] &
dataframe['rsi_long_oversold']
(dataframe['rsi_6'].shift(1).rolling(window=self.rsi_check_window.value).max() < self.rsi_6_oversold.value) &
(dataframe['rsi_12'].shift(1).rolling(window=self.rsi_check_window.value).max() < self.rsi_12_oversold.value)
)
# 合并买入条件:任一条件满足即产生买入信号
dataframe.loc[
ema_bullish_cross | rsi_oversold_bounce,
ema_bullish_cross,
'enter_long'
] = 1
@@ -183,12 +173,21 @@ class TrendStrategy(IStrategy):
:return: DataFrame with exit columns populated
"""
# 卖出条件EMA10连续下跌 + 空头趋势加速
sell_condition = (
dataframe['ema_short_falling'] &
dataframe['ema_diff_expanding']
# 条件1EMA10连续下跌(动量走弱)
# 当前EMA10 < 过去5天不含今日的最低EMA10值
ema10_falling = (
dataframe['ema_10'] < dataframe['ema_10'].shift(1).rolling(window=self.ema_fall_window.value).min()
)
# 条件2空头趋势加速价差达到近期最大
# 当前diff是过去6天含今日中的最大值表示空头力量最强
diff_expanding = (
dataframe['diff'] == dataframe['diff'].rolling(window=self.diff_check_window.value).max()
)
# 合并卖出条件:两个条件同时满足才卖出
sell_condition = ema10_falling & diff_expanding
dataframe.loc[
sell_condition,
'exit_long'