BTC 1小时级别技术形态策略 - 趋势确认: EMA排列 (20/50/200) - 动量确认: MACD金叉/死叉 - 超买超卖: RSI极值区 - 波动率: 布林带宽度 - 成交量: 放量确认 - 形态: 吞没/锤子线等K线形态
Timeframe
30m
Direction
Long Only
Stoploss
-3.0%
Trailing Stop
Yes
ROI
0m: 5.0%, 120m: 3.0%, 480m: 1.0%, 1440m: 0.0%
Interface Version
3
Startup Candles
250
Indicators
6
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
# BTC 1H Technical Pattern Strategy
# Combines: EMA trend, MACD, RSI, Bollinger Bands, Volume confirmation
from pandas import DataFrame
import pandas as pd
import numpy as np
import talib.abstract as ta
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter, CategoricalParameter
from functools import reduce
class Btc1hPatternStrategy(IStrategy):
"""
BTC 1小时级别技术形态策略
- 趋势确认: EMA排列 (20/50/200)
- 动量确认: MACD金叉/死叉
- 超买超卖: RSI极值区
- 波动率: 布林带宽度
- 成交量: 放量确认
- 形态: 吞没/锤子线等K线形态
"""
INTERFACE_VERSION = 3
timeframe = '30m'
can_short = False
# === 可优化参数 ===
# EMA周期
ema_fast = IntParameter(10, 30, default=20, space='buy')
ema_mid = IntParameter(40, 60, default=50, space='buy')
ema_slow = IntParameter(100, 250, default=200, space='buy')
# RSI
rsi_period = IntParameter(7, 21, default=14, space='buy')
rsi_oversold = IntParameter(20, 35, default=30, space='buy')
rsi_overbought = IntParameter(65, 85, default=70, space='sell')
# Bollinger
bb_period = IntParameter(15, 30, default=20, space='buy')
bb_std = DecimalParameter(1.5, 3.0, default=2.0, space='buy')
# Volume
volume_ma_period = IntParameter(10, 30, default=20, space='buy')
volume_ratio = DecimalParameter(1.2, 2.5, default=1.5, space='buy')
# MACD
macd_fast = IntParameter(8, 16, default=12, space='buy')
macd_slow = IntParameter(20, 30, default=26, space='buy')
macd_signal = IntParameter(5, 12, default=9, space='buy')
# Stop loss / Take profit
stoploss = -0.03
trailing_stop = True
trailing_stop_positive = 0.01
trailing_stop_positive_offset = 0.02
trailing_only_offset_is_reached = True
minimal_roi = {
"0": 0.05,
"120": 0.03,
"480": 0.01,
"1440": 0
}
max_open_trades = 2
startup_candle_count = 250
process_only_new_candles = True
# === 指标计算 ===
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# EMA
dataframe['ema_fast'] = ta.EMA(dataframe, timeperiod=self.ema_fast.value)
dataframe['ema_mid'] = ta.EMA(dataframe, timeperiod=self.ema_mid.value)
dataframe['ema_slow'] = ta.EMA(dataframe, timeperiod=self.ema_slow.value)
# EMA排列评分: fast>mid>slow = 3, fast>mid = 2, fast>slow = 1
dataframe['ema_trend'] = 0
dataframe.loc[dataframe['ema_fast'] > dataframe['ema_slow'], 'ema_trend'] = 1
dataframe.loc[dataframe['ema_fast'] > dataframe['ema_mid'], 'ema_trend'] += 1
dataframe.loc[dataframe['ema_mid'] > dataframe['ema_slow'], 'ema_trend'] += 1
# RSI
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=self.rsi_period.value)
# MACD
macd = ta.MACD(dataframe,
fastperiod=self.macd_fast.value,
slowperiod=self.macd_slow.value,
signalperiod=self.macd_signal.value)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
# MACD金叉=1, 死叉=-1
dataframe['macd_cross'] = 0
dataframe.loc[(dataframe['macd'] > dataframe['macdsignal']) &
(dataframe['macd'].shift(1) <= dataframe['macdsignal'].shift(1)), 'macd_cross'] = 1
dataframe.loc[(dataframe['macd'] < dataframe['macdsignal']) &
(dataframe['macd'].shift(1) >= dataframe['macdsignal'].shift(1)), 'macd_cross'] = -1
# Bollinger Bands
bollinger = ta.BBANDS(dataframe,
timeperiod=self.bb_period.value,
nbdevup=self.bb_std.value,
nbdevdn=self.bb_std.value)
dataframe['bb_upper'] = bollinger['upperband']
dataframe['bb_middle'] = bollinger['middleband']
dataframe['bb_lower'] = bollinger['lowerband']
dataframe['bb_width'] = (dataframe['bb_upper'] - dataframe['bb_lower']) / dataframe['bb_middle']
dataframe['bb_position'] = (dataframe['close'] - dataframe['bb_lower']) / (dataframe['bb_upper'] - dataframe['bb_lower'])
# Volume
dataframe['volume_ma'] = ta.SMA(dataframe['volume'], timeperiod=self.volume_ma_period.value)
dataframe['volume_ratio_val'] = dataframe['volume'] / dataframe['volume_ma']
# ATR (for volatility)
dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
dataframe['atr_pct'] = dataframe['atr'] / dataframe['close'] * 100
# === K线形态 ===
# 吞没形态
dataframe['engulfing_bull'] = ta.CDLENGULFING(dataframe['open'], dataframe['high'],
dataframe['low'], dataframe['close']) > 0
dataframe['engulfing_bear'] = ta.CDLENGULFING(dataframe['open'], dataframe['high'],
dataframe['low'], dataframe['close']) < 0
# 锤子线 / 上吊线
dataframe['hammer'] = ta.CDLHAMMER(dataframe['open'], dataframe['high'],
dataframe['low'], dataframe['close']) > 0
# 早晨之星 / 黄昏之星
dataframe['morning_star'] = ta.CDLMORNINGSTAR(dataframe['open'], dataframe['high'],
dataframe['low'], dataframe['close']) > 0
dataframe['evening_star'] = ta.CDLEVENINGSTAR(dataframe['open'], dataframe['high'],
dataframe['low'], dataframe['close']) < 0
# 三白兵 / 三乌鸦
dataframe['three_white'] = ta.CDL3WHITESOLDIERS(dataframe['open'], dataframe['high'],
dataframe['low'], dataframe['close']) > 0
dataframe['three_black'] = ta.CDL3BLACKCROWS(dataframe['open'], dataframe['high'],
dataframe['low'], dataframe['close']) < 0
# Doji
dataframe['doji'] = ta.CDLDOJI(dataframe['open'], dataframe['high'],
dataframe['low'], dataframe['close']) != 0
# === 支撑阻力 (简单版: 用近期高低点) ===
dataframe['resistance_20'] = dataframe['high'].rolling(20).max()
dataframe['support_20'] = dataframe['low'].rolling(20).min()
dataframe['near_support'] = (dataframe['close'] - dataframe['support_20']) / dataframe['close'] < 0.02
dataframe['near_resistance'] = (dataframe['resistance_20'] - dataframe['close']) / dataframe['close'] < 0.02
# === 综合评分 ===
# 做多评分
dataframe['long_score'] = 0
# 趋势向上
dataframe.loc[dataframe['ema_trend'] == 3, 'long_score'] += 3 # 完全多头排列
dataframe.loc[dataframe['ema_trend'] == 2, 'long_score'] += 1
# RSI超卖反弹
dataframe.loc[dataframe['rsi'] < self.rsi_oversold.value, 'long_score'] += 2
# 布林带下轨附近
dataframe.loc[dataframe['bb_position'] < 0.15, 'long_score'] += 2
# 放量
dataframe.loc[dataframe['volume_ratio_val'] > self.volume_ratio.value, 'long_score'] += 1
# MACD金叉
dataframe.loc[dataframe['macd_cross'] == 1, 'long_score'] += 3
# 看涨形态
dataframe.loc[dataframe['engulfing_bull'], 'long_score'] += 3
dataframe.loc[dataframe['hammer'], 'long_score'] += 2
dataframe.loc[dataframe['morning_star'], 'long_score'] += 3
dataframe.loc[dataframe['three_white'], 'long_score'] += 2
# 支撑位
dataframe.loc[dataframe['near_support'], 'long_score'] += 1
# === 做空评分 ===
dataframe['short_score'] = 0
# 趋势向下
dataframe.loc[dataframe['ema_trend'] == 0, 'short_score'] += 3
# RSI超买
dataframe.loc[dataframe['rsi'] > self.rsi_overbought.value, 'short_score'] += 2
# 布林带上轨附近
dataframe.loc[dataframe['bb_position'] > 0.85, 'short_score'] += 2
# 放量
dataframe.loc[dataframe['volume_ratio_val'] > self.volume_ratio.value, 'short_score'] += 1
# MACD死叉
dataframe.loc[dataframe['macd_cross'] == -1, 'short_score'] += 3
# 看跌形态
dataframe.loc[dataframe['engulfing_bear'], 'short_score'] += 3
dataframe.loc[dataframe['evening_star'], 'short_score'] += 3
dataframe.loc[dataframe['three_black'], 'short_score'] += 2
# 阻力位
dataframe.loc[dataframe['near_resistance'], 'short_score'] += 1
# === 避免Doji震荡市 ===
dataframe['is_doji_zone'] = dataframe['doji'].rolling(4).sum() >= 2
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# 做多条件: 评分>=6, 不在Doji震荡区, 不是极端超买
dataframe.loc[
(dataframe['long_score'] >= 6) &
(~dataframe['is_doji_zone']) &
(dataframe['rsi'] < 80),
['enter_long', 'enter_tag']
] = (1, 'long_tech_pattern')
# 做空条件: 评分>=6, 不在Doji震荡区, 不是极端超卖
dataframe.loc[
(dataframe['short_score'] >= 6) &
(~dataframe['is_doji_zone']) &
(dataframe['rsi'] > 20),
['enter_short', 'enter_tag']
] = (1, 'short_tech_pattern')
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# 评分衰减时退出
dataframe.loc[
(dataframe['long_score'] <= 2),
['exit_long', 'exit_tag']
] = (1, 'long_score_fade')
dataframe.loc[
(dataframe['short_score'] <= 2),
['exit_short', 'exit_tag']
] = (1, 'short_score_fade')
return dataframe