Update US stock trading evaluation and add V5 stock strategy
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
bd88a4e512
commit
1ecd296af3
@@ -0,0 +1,144 @@
|
||||
import pandas as pd
|
||||
import talib.abstract as ta
|
||||
from functools import reduce
|
||||
from datetime import datetime
|
||||
from freqtrade.strategy import (IStrategy, IntParameter, DecimalParameter)
|
||||
from freqtrade.persistence import Trade
|
||||
|
||||
|
||||
class MultiAssetChannelBreakoutV5Stock(IStrategy):
|
||||
"""
|
||||
多股票通道突破策略 V5-Stock(OKX 代币化美股版)。
|
||||
|
||||
基于 MultiAssetChannelBreakoutV5 改造,适配 OKX 现货代币化股票:
|
||||
- spot 模式(1x,无杠杆)
|
||||
- 增加美股交易时段过滤,排除盘前/盘后/周末的薄流动性噪声
|
||||
- 参数面向股票市场特性调整(ADX 阈值降低、滑点容忍放宽)
|
||||
- stoploss / custom_stoploss 逻辑不变(V5 已是 1x 价格维度,spot 直接适用)
|
||||
|
||||
前置条件:
|
||||
- freqtrade spot 模式实例(trading_mode 留空,非 futures)
|
||||
- pair_whitelist 使用 X 前缀代币化股票(如 XAAPL/USDT)
|
||||
- 已验证 OKX 账户可交易 X 前缀标的(小额实单测试)
|
||||
"""
|
||||
|
||||
INTERFACE_VERSION = 3
|
||||
|
||||
# --- 1) 基础交易设置 ---
|
||||
minimal_roi = {"0": 100}
|
||||
stoploss = -0.08
|
||||
trailing_stop = False
|
||||
use_custom_stoploss = True
|
||||
timeframe = "15m"
|
||||
can_short = False
|
||||
startup_candle_count = 600
|
||||
|
||||
# --- 2) 可优化参数(默认值面向股票市场调整) ---
|
||||
up_line_span = IntParameter(100, 400, default=340, space="buy", optimize=True)
|
||||
buy_stop_profit_span = IntParameter(50, 200, default=141, space="buy", optimize=True)
|
||||
ma_span_long_days = IntParameter(1, 5, default=2, space="buy", optimize=True)
|
||||
# 降低默认值:股票 ADX 普遍低于加密货币(BTC/ETH 15m 常态 ADX 30-50,股票 15m 常态 15-30)
|
||||
adx_threshold = IntParameter(15, 40, default=25, space="buy", optimize=True)
|
||||
up_line_offset = DecimalParameter(-0.2, 0.4, default=-0.18, decimals=2, space="buy", optimize=True)
|
||||
buy_stop_profit_offset = DecimalParameter(-0.2, 0.2, default=0.19, decimals=2, space="sell", optimize=True)
|
||||
# 滑点容忍:代币化股票流动性薄,从 V5 的 1% 放宽到 1.5%
|
||||
max_slippage = DecimalParameter(0.005, 0.03, default=0.015, decimals=3, space="buy", optimize=True)
|
||||
|
||||
def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
|
||||
dataframe["n_value"] = ta.ATR(dataframe, timeperiod=10)
|
||||
|
||||
ma_length = self.ma_span_long_days.value * 96
|
||||
dataframe["ma_long"] = ta.SMA(dataframe, timeperiod=ma_length)
|
||||
|
||||
base_up_line = dataframe["high"].rolling(window=self.up_line_span.value).max().shift(1)
|
||||
dataframe["up_line"] = base_up_line + (dataframe["n_value"] * self.up_line_offset.value)
|
||||
|
||||
dataframe["bottom_line"] = dataframe["low"].rolling(window=self.buy_stop_profit_span.value).min().shift(1)
|
||||
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
|
||||
|
||||
# --- 美股交易时段标记 ---
|
||||
# OKX 代币化股票 24/7 有 K 线,但非美股时段流动性极薄、价格漂移。
|
||||
# 只在美股正常交易时段(周一至周五 9:30-16:00 美东)允许入场。
|
||||
if "date" in dataframe.columns:
|
||||
df_date = pd.to_datetime(dataframe["date"], utc=True)
|
||||
et_time = df_date.dt.tz_convert("US/Eastern")
|
||||
et_hour = et_time.dt.hour
|
||||
et_minute = et_time.dt.minute
|
||||
et_weekday = et_time.dt.weekday # 0=Mon, 6=Sun
|
||||
|
||||
is_market_hours = (
|
||||
(et_weekday < 5)
|
||||
& (
|
||||
(et_hour > 9)
|
||||
| ((et_hour == 9) & (et_minute >= 30))
|
||||
)
|
||||
& (et_hour < 16)
|
||||
)
|
||||
dataframe["is_market_hours"] = is_market_hours
|
||||
else:
|
||||
dataframe["is_market_hours"] = True
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
|
||||
conditions = []
|
||||
conditions.append(dataframe["n_value"].notnull())
|
||||
conditions.append(dataframe["up_line"].notnull())
|
||||
conditions.append(dataframe["close"] > dataframe["ma_long"])
|
||||
conditions.append(dataframe["adx"] > self.adx_threshold.value)
|
||||
conditions.append(dataframe["close"] > dataframe["up_line"])
|
||||
conditions.append(dataframe["is_market_hours"])
|
||||
|
||||
if conditions:
|
||||
is_entry = reduce(lambda x, y: x & y, conditions)
|
||||
dataframe.loc[is_entry, "enter_long"] = 1
|
||||
dataframe.loc[is_entry, "enter_tag"] = "trend_breakout"
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
|
||||
offset = self.buy_stop_profit_offset.value
|
||||
exit_line = dataframe["bottom_line"] + (dataframe["n_value"] * offset)
|
||||
|
||||
exit_condition = dataframe["close"] < exit_line
|
||||
dataframe.loc[exit_condition, "exit_long"] = 1
|
||||
dataframe.loc[exit_condition, "exit_tag"] = "channel_exit"
|
||||
return dataframe
|
||||
|
||||
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
|
||||
current_rate: float, current_profit: float, **kwargs) -> float:
|
||||
if current_profit > 0.30:
|
||||
return 0.10
|
||||
if current_profit > 0.15:
|
||||
return 0.10
|
||||
if current_profit > 0.05:
|
||||
breakeven_target = trade.open_rate * 1.005
|
||||
return (current_rate - breakeven_target) / current_rate
|
||||
return 1.0
|
||||
|
||||
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
|
||||
time_in_force: str, current_time: datetime, entry_tag: str,
|
||||
side: str, **kwargs) -> bool:
|
||||
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||
|
||||
if len(dataframe) < 3:
|
||||
return False
|
||||
|
||||
signal_candle = None
|
||||
for i in range(-1, -4, -1):
|
||||
if dataframe["enter_long"].iloc[i] == 1:
|
||||
signal_candle = dataframe.iloc[i]
|
||||
break
|
||||
|
||||
if signal_candle is None:
|
||||
return False
|
||||
|
||||
if rate <= signal_candle["ma_long"]:
|
||||
return False
|
||||
if rate <= signal_candle["up_line"]:
|
||||
return False
|
||||
|
||||
if rate > signal_candle["close"] * (1 + self.max_slippage.value):
|
||||
return False
|
||||
|
||||
return True
|
||||
Reference in New Issue
Block a user