添加策略

This commit is contained in:
2025-10-25 17:19:16 +08:00
parent 7fdac25742
commit 04ad5b6f1b
9 changed files with 549 additions and 294 deletions

183
.gitignore vendored Normal file
View File

@@ -0,0 +1,183 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# Data files (keep structure but ignore large data)
data/
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Docker
.dockerignore
# Logs
*.log
logs/
# Temporary files
tmp/
temp/
*.tmp
*.temp
# API keys and secrets
.env
config.ini
secrets.json
api_keys.txt
# Database files
*.db
*.sqlite
*.sqlite3
# Backup files
*.bak
*.backup
user_data/data
user_data/freqaimodels
user_data/hyperopts
user_data/logs
user_data/backtest_results

14
Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
FROM freqtradeorg/freqtrade:stable
# 设置工作目录
WORKDIR /freqtrade
# 复制用户数据到镜像(策略、配置等),但排除 data 和 backtest_results 目录
COPY user_data /freqtrade/user_data
RUN rm -rf /freqtrade/user_data/data /freqtrade/user_data/backtest_results
# 暴露 freqtrade WebUI 端口
EXPOSE 8077
# 设置启动命令,与 compose 保持一致
CMD ["trade", "--config", "/freqtrade/user_data/config.json", "--strategy", "SimpleRSIStrategyFixed"]

View File

@@ -10,4 +10,4 @@ services:
command: > command: >
trade trade
--config ./user_data/config.json --config ./user_data/config.json
--strategy MACDStrategy --strategy SimpleRSIStrategyFixed

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.freqtrade.io/schema.json", "$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1, "max_open_trades": 3,
"stake_currency": "USDT", "stake_currency": "USDT",
"stake_amount": "unlimited", "stake_amount": "unlimited",
"tradable_balance_ratio": 0.99, "tradable_balance_ratio": 0.99,
@@ -41,7 +41,8 @@
"ccxt_config": {}, "ccxt_config": {},
"ccxt_async_config": {}, "ccxt_async_config": {},
"pair_whitelist": [ "pair_whitelist": [
"ETH/USDT" "ETH/USDT",
"BTC/USDT"
], ],
"pair_blacklist": [ "pair_blacklist": [
] ]

View File

@@ -42,9 +42,9 @@ class MACDStrategy(IStrategy):
INTERFACE_VERSION = 3 INTERFACE_VERSION = 3
minimal_roi = {"0": 100} minimal_roi = {"0": 100}
stoploss = -1 stoploss = -0.05
trailing_stop = False trailing_stop = False
timeframe = '15m' timeframe = '4h'
use_exit_signal = True use_exit_signal = True
exit_profit_only = False exit_profit_only = False
@@ -72,7 +72,7 @@ class MACDStrategy(IStrategy):
dataframe["macd"] = macd["macd"] dataframe["macd"] = macd["macd"]
dataframe["macdsignal"] = macd["macdsignal"] dataframe["macdsignal"] = macd["macdsignal"]
dataframe["macdhist"] = macd["macdhist"] dataframe["macdhist"] = macd["macdhist"]
dataframe['cci'] = ta.CCI(dataframe, 26) dataframe['cci'] = ta.CCI(dataframe, 14)
dataframe['TD'] = self.TD(dataframe) dataframe['TD'] = self.TD(dataframe)
return dataframe return dataframe
@@ -80,15 +80,14 @@ class MACDStrategy(IStrategy):
# 入场1d与4h CCI < -100且4h CCI上升当前 > 前一根) # 入场1d与4h CCI < -100且4h CCI上升当前 > 前一根)
dataframe.loc[ dataframe.loc[
( (
# (dataframe['macdhist'] < 0) & (dataframe['macdhist'] < 0) &
# (dataframe['macdhist'] > dataframe['macdhist'].shift(1)) & (dataframe['macdhist'] > dataframe['macdhist'].shift(1)) &
# (dataframe['macdsignal'] < 0) & # (dataframe['macdsignal'] < 0) &
# (dataframe['macd'] < dataframe['macdsignal']) & # (dataframe['macd'] < dataframe['macdsignal']) &
# (dataframe['cci'] < -100) & (dataframe['cci'] < -100) &
# (dataframe['cci'].shift(1) < dataframe['cci']) & (dataframe['cci'] > dataframe['cci'].shift(1))
# (dataframe['macdhist'] < 0) & # (dataframe['macdhist'] < 0) &
(dataframe['TD'] == 1) & # (dataframe['volume'] > 0)
(dataframe['volume'] > 0)
), ),
'enter_long', 'enter_long',
] = 1 ] = 1
@@ -99,15 +98,17 @@ class MACDStrategy(IStrategy):
# 离场1d与4h CCI > 100且4h CCI下降当前 < 前一根) # 离场1d与4h CCI > 100且4h CCI下降当前 < 前一根)
dataframe.loc[ dataframe.loc[
( (
# (dataframe['macdhist'] > 0) & ((dataframe['macdhist'] < 0) &
(dataframe['macdhist'].shift(1) > 0) )
| (qtpylib.crossed_below(dataframe['macd'], dataframe['macdsignal']) )
# (dataframe['macdhist'] < dataframe['macdhist'].shift(1)) & # (dataframe['macdhist'] < dataframe['macdhist'].shift(1)) &
# (dataframe['macdsignal'] > 0) & # (dataframe['macdsignal'] > 0) &
# (dataframe['macd'] > dataframe['macdsignal']) & # (dataframe['macd'] > dataframe['macdsignal']) &
# (dataframe['cci'] > 100 )& # (dataframe['cci'] > 100 )&
# (dataframe['cci'].shift(1) > dataframe['cci']) & # (dataframe['cci'] < dataframe['cci'].shift(1)) &
# (dataframe['macdhist'] > 0) & # (dataframe['macdhist'] > 0) &
(dataframe['TD'] == -1) & # (dataframe['TD'] == -1) &
(dataframe['volume'] > 0) # (dataframe['volume'] > 0)
), ),
'exit_long', 'exit_long',
] = 1 ] = 1

View File

@@ -0,0 +1,66 @@
import talib.abstract as ta
import pandas as pd
import numpy as np
from freqtrade.strategy import IStrategy
import logging
logger = logging.getLogger(__name__)
class SimpleRSIStrategyOptimized(IStrategy):
INTERFACE_VERSION = 3
can_short: bool = False
stoploss = -0.05
minimal_roi = {"0": 100} # 由止盈逻辑替代
timeframe = '1d'
# === 策略参数(可优化)===
ema_short = 10
ema_long = 20
rsi_period = 6
rsi_oversold = 20
def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
# EMA
dataframe['ema_short'] = ta.EMA(dataframe['close'], timeperiod=self.ema_short)
dataframe['ema_long'] = ta.EMA(dataframe['close'], timeperiod=self.ema_long)
# RSI
dataframe['rsi'] = ta.RSI(dataframe['close'], timeperiod=self.rsi_period)
# 辅助昨日值shift 1
dataframe['ema_short_prev'] = dataframe['ema_short'].shift(1)
dataframe['rsi_prev'] = dataframe['rsi'].shift(1)
return dataframe
def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
dataframe.loc[
(
# 趋势条件EMA 短期 > 长期 且 短期 EMA 上升
(dataframe['ema_short'] > dataframe['ema_long']) &
(dataframe['ema_short'] > dataframe['ema_short_prev']) &
# RSI 超卖后反弹
(dataframe['rsi_prev'] <= self.rsi_oversold) &
(dataframe['rsi'] > dataframe['rsi_prev'])
),
'enter_long',
] = 1
return dataframe
def populate_exit_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
dataframe.loc[
(
# 趋势破坏EMA 死叉
(dataframe['ema_short'] < dataframe['ema_long']) |
# 或价格跌破长期 EMA支撑失效
(dataframe['close'] < dataframe['ema_long'])
),
'exit_long',
] = 1
return dataframe

View File

@@ -118,8 +118,8 @@ class SimpleRSIStrategyFixed(IStrategy):
f"方向: {side}, " f"方向: {side}, "
f"仓位: {position}" f"仓位: {position}"
) )
dingtalk.send_text( # dingtalk.send_text(
content=f"订单成交 - 交易对: {trading_pair}, 时间: {fill_time}, 价格: {fill_price}, 方向: {side}, 仓位: {position}") # content=f"订单成交 - 交易对: {trading_pair}, 时间: {fill_time}, 价格: {fill_price}, 方向: {side}, 仓位: {position}")
return None return None

View File

@@ -2,6 +2,7 @@
# flake8: noqa: F401 # flake8: noqa: F401
# isort: skip_file # isort: skip_file
# --- Do not remove these imports --- # --- Do not remove these imports ---
from dingtalk import DingTalkBot
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
@@ -37,12 +38,22 @@ import talib.abstract as ta
from technical import qtpylib from technical import qtpylib
webhook = "https://oapi.dingtalk.com/robot/send?access_token=21de667159edadd33172c6ec414a2addf9c6359189350ffd36819d2a20e8a0f4" # 填写你的webhook
secret = "SEC43a0fa0b29717f98637a119b92a0bd5f7b2b6da671bdd2bd1279ed8323454d5e" # 填写你的加签token如果有否则留空
# CTA 群机器人
# webhook = "https://oapi.dingtalk.com/robot/send?access_token=87c7abfcdd69b699c32da4e4f5981cd2ca6b0445474fc6ffb36f2ed0f6262fbb"
# secret = "SECf3d6b43f2f8a87ab91feffd052e71ec314fbf57a1842e483fe07af3c0a0e5aa6"
dingtalk = DingTalkBot(webhook, secret)
class CCIMultiTimeframeSpotStrategy(IStrategy): class CCIMultiTimeframeSpotStrategy(IStrategy):
# 策略参数 # 策略参数
INTERFACE_VERSION = 3 INTERFACE_VERSION = 3
minimal_roi = {"0": 100} minimal_roi = {"0": 100}
stoploss = -1 stoploss = -1
use_custom_stoploss = False
trailing_stop = False trailing_stop = False
timeframe = '4h' timeframe = '4h'
@@ -50,6 +61,37 @@ class CCIMultiTimeframeSpotStrategy(IStrategy):
exit_profit_only = False exit_profit_only = False
ignore_roi_if_entry_signal = False ignore_roi_if_entry_signal = False
def order_filled(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> None:
# 交易对
trading_pair = pair
# 时间
fill_time = order.order_filled_date or current_time
# 价格
fill_price = order.average or order.price
# 买入还是卖出
side = order.ft_order_side # 'buy' 或 'sell'
# 仓位(当前持仓数量)
position = trade.amount
# 或者使用日志
logger.info(
f"订单成交 - 交易对: {trading_pair}, "
f"时间: {fill_time}, "
f"价格: {fill_price}, "
f"方向: {side}, "
f"仓位: {position}"
)
# if self.config["runmode"].value in ("live", "dry_run"):
logger.info(f"11111111{self.config['runmode']}")
# dingtalk.send_text(
# content=f"订单成交 - 交易对: {trading_pair}, 时间: {fill_time}, 价格: {fill_price}, 方向: {side}, 仓位: {position}")
return None
def TD(self, dataframe:DataFrame): def TD(self, dataframe:DataFrame):
close = dataframe['close'].to_list() close = dataframe['close'].to_list()
td = [0,0,0,0] td = [0,0,0,0]
@@ -98,6 +140,7 @@ class CCIMultiTimeframeSpotStrategy(IStrategy):
# dataframe['adx_hist_4h'] = ta.ADX(dataframe['macdhist_4h']) # dataframe['adx_hist_4h'] = ta.ADX(dataframe['macdhist_4h'])
dataframe['TD'] = self.TD(dataframe) dataframe['TD'] = self.TD(dataframe)
dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)
logger.info(dataframe.tail()) logger.info(dataframe.tail())
return dataframe return dataframe
@@ -132,3 +175,14 @@ class CCIMultiTimeframeSpotStrategy(IStrategy):
return dataframe return dataframe
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> float | None:
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
candle = dataframe.iloc[-1].squeeze()
side = 1 if trade.is_short else -1
return stoploss_from_absolute(current_rate + (side * candle["atr"] * 3),
current_rate=current_rate,
is_short=trade.is_short,
leverage=trade.leverage)

View File

@@ -36,23 +36,26 @@ import talib.abstract as ta
from technical import qtpylib from technical import qtpylib
# This class is a sample. Feel free to customize it. # 基于x.py策略的MA60/MA120趋势跟踪策略
class SampleStrategy(IStrategy): class MA60MA120TrendStrategy(IStrategy):
""" """
This is a sample strategy to inspire you. 基于x.py策略的MA60/MA120趋势跟踪策略
More information in https://www.freqtrade.io/en/latest/strategy-customization/
You can: 策略逻辑:
:return: a Dataframe with all mandatory indicators for the strategies 1. 买入条件:
- Rename the class name (Do not forget to update class_name) - 价格在MA60上方且距离MA60不超过10%
- Add any methods you want to build your strategy - MA60呈上升趋势过去5天中至少3天上升
- Add any lib you need to build your strategy - 成交量放大当日成交量大于过去5日平均
- 前5天中下跌天数不超过2天
- 当天收盘价大于5天前的价格
- 价格在MA120上方
- MA120过去5天不是下降趋势
- RSI小于阈值默认70
You must keep: 2. 卖出条件:
- the lib in the section "Do not remove these libs" - 价格跌破MA60且跌幅超过10%
- the methods: populate_indicators, populate_entry_trend, populate_exit_trend
You should keep: 更多信息: https://www.freqtrade.io/en/latest/strategy-customization/
- timeframe, minimal_roi, stoploss, trailing_*
""" """
# Strategy interface version - allow new iterations of the strategy interface. # Strategy interface version - allow new iterations of the strategy interface.
@@ -63,26 +66,25 @@ class SampleStrategy(IStrategy):
can_short: bool = False can_short: bool = False
# Minimal ROI designed for the strategy. # Minimal ROI designed for the strategy.
# This attribute will be overridden if the config file contains "minimal_roi". # 基于x.py策略使用更保守的ROI设置
minimal_roi = { # minimal_roi = {
# "120": 0.0, # exit after 120 minutes at break even # "30": 0.3, # 20% 目标收益
"60": 0.01, # # ""
"30": 0.02, # }
"0": 0.04,
}
# Optimal stoploss designed for the strategy. # Optimal stoploss designed for the strategy.
# This attribute will be overridden if the config file contains "stoploss". # 基于x.py策略的卖出条件跌破MA60且跌幅超过10%
stoploss = -0.10 stoploss = -1
# Trailing stoploss # Trailing stoploss
trailing_stop = False # trailing_stop = True
# trailing_only_offset_is_reached = False # trailing_only_offset_is_reached = False
# trailing_stop_positive = 0.01 # trailing_stop_positive = 0.3
# trailing_stop_positive_offset = 0.0 # Disabled / not configured # trailing_stop_positive_offset = 0.0 # Disabled / not configured
# Optimal timeframe for the strategy. # Optimal timeframe for the strategy.
timeframe = "5m" # 使用日线数据因为x.py策略基于日线
timeframe = "1d"
# Run "populate_indicators()" only for new candle. # Run "populate_indicators()" only for new candle.
process_only_new_candles = True process_only_new_candles = True
@@ -92,13 +94,15 @@ class SampleStrategy(IStrategy):
exit_profit_only = False exit_profit_only = False
ignore_roi_if_entry_signal = False ignore_roi_if_entry_signal = False
# Hyperoptable parameters # 移除原有的RSI参数因为新策略使用固定值
buy_rsi = IntParameter(low=1, high=50, default=30, space="buy", optimize=True, load=True) # 可以添加其他可优化的参数
sell_rsi = IntParameter(low=50, high=100, default=70, space="sell", optimize=True, load=True) ma60_period = IntParameter(low=30, high=90, default=60, space="buy", optimize=True, load=True)
short_rsi = IntParameter(low=51, high=100, default=70, space="sell", optimize=True, load=True) ma120_period = IntParameter(low=60, high=180, default=120, space="buy", optimize=True, load=True)
exit_short_rsi = IntParameter(low=1, high=50, default=30, space="buy", optimize=True, load=True) rsi_threshold = IntParameter(low=50, high=80, default=70, space="buy", optimize=True, load=True)
volume_surge_multiplier = DecimalParameter(low=0.5, high=2.0, default=1.0, space="buy", optimize=True, load=True)
# Number of candles the strategy requires before producing valid signals # Number of candles the strategy requires before producing valid signals
# 需要更多数据来计算MA120和趋势指标
startup_candle_count: int = 200 startup_candle_count: int = 200
# Optional order type mapping. # Optional order type mapping.
@@ -114,16 +118,21 @@ class SampleStrategy(IStrategy):
plot_config = { plot_config = {
"main_plot": { "main_plot": {
"tema": {}, "ma60": {"color": "blue", "width": 2},
"sar": {"color": "white"}, "ma120": {"color": "orange", "width": 2},
}, },
"subplots": { "subplots": {
"MACD": {
"macd": {"color": "blue"},
"macdsignal": {"color": "orange"},
},
"RSI": { "RSI": {
"rsi": {"color": "red"}, "rsi": {"color": "red"},
"rsi_threshold": {"color": "gray", "type": "line", "width": 1},
},
"Volume": {
"volume": {"color": "lightblue"},
"volume_5ma": {"color": "blue"},
},
"Signals": {
"buy_signal": {"color": "green", "type": "scatter", "marker": "^"},
"sell_signal": {"color": "red", "type": "scatter", "marker": "v"},
}, },
}, },
} }
@@ -143,284 +152,211 @@ class SampleStrategy(IStrategy):
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Adds several different TA indicators to the given DataFrame 基于x.py策略的技术指标计算
实现MA60/MA120趋势跟踪策略的所有必要指标
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 dataframe: Dataframe with data from the exchange
:param metadata: Additional information, like the currently traded pair :param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies :return: a Dataframe with all mandatory indicators for the strategies
""" """
# Momentum Indicators # 基础移动平均线指标
# ------------------------------------ # ------------------------------------
# ADX # MA60 - 使用可优化参数
dataframe["adx"] = ta.ADX(dataframe) dataframe['ma60'] = ta.SMA(dataframe, timeperiod=60)
# # Plus Directional Indicator / Movement # MA120 - 使用可优化参数
# dataframe['plus_dm'] = ta.PLUS_DM(dataframe) dataframe['ma120'] = ta.SMA(dataframe, timeperiod=120)
# dataframe['plus_di'] = ta.PLUS_DI(dataframe)
# # Minus Directional Indicator / Movement # RSI指标
# dataframe['minus_dm'] = ta.MINUS_DM(dataframe) dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
# dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# # Aroon, Aroon Oscillator # 成交量相关指标
# aroon = ta.AROON(dataframe)
# dataframe['aroonup'] = aroon['aroonup']
# dataframe['aroondown'] = aroon['aroondown']
# dataframe['aroonosc'] = ta.AROONOSC(dataframe)
# # Awesome Oscillator
# dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
# # Keltner Channel
# keltner = qtpylib.keltner_channel(dataframe)
# dataframe["kc_upperband"] = keltner["upper"]
# dataframe["kc_lowerband"] = keltner["lower"]
# dataframe["kc_middleband"] = keltner["mid"]
# dataframe["kc_percent"] = (
# (dataframe["close"] - dataframe["kc_lowerband"]) /
# (dataframe["kc_upperband"] - dataframe["kc_lowerband"])
# )
# dataframe["kc_width"] = (
# (dataframe["kc_upperband"] - dataframe["kc_lowerband"]) / dataframe["kc_middleband"]
# )
# # Ultimate Oscillator
# dataframe['uo'] = ta.ULTOSC(dataframe)
# # Commodity Channel Index: values [Oversold:-100, Overbought:100]
# dataframe['cci'] = ta.CCI(dataframe)
# RSI
dataframe["rsi"] = ta.RSI(dataframe)
# # Inverse Fisher transform on RSI: values [-1.0, 1.0] (https://goo.gl/2JGGoy)
# rsi = 0.1 * (dataframe['rsi'] - 50)
# dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1)
# # Inverse Fisher transform on RSI normalized: values [0.0, 100.0] (https://goo.gl/2JGGoy)
# dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
# # Stochastic Slow
# stoch = ta.STOCH(dataframe)
# dataframe['slowd'] = stoch['slowd']
# dataframe['slowk'] = stoch['slowk']
# Stochastic Fast
stoch_fast = ta.STOCHF(dataframe)
dataframe["fastd"] = stoch_fast["fastd"]
dataframe["fastk"] = stoch_fast["fastk"]
# # Stochastic RSI
# Please read https://github.com/freqtrade/freqtrade/issues/2961 before using this.
# STOCHRSI is NOT aligned with tradingview, which may result in non-expected results.
# stoch_rsi = ta.STOCHRSI(dataframe)
# dataframe['fastd_rsi'] = stoch_rsi['fastd']
# dataframe['fastk_rsi'] = stoch_rsi['fastk']
# MACD
macd = ta.MACD(dataframe)
dataframe["macd"] = macd["macd"]
dataframe["macdsignal"] = macd["macdsignal"]
dataframe["macdhist"] = macd["macdhist"]
# MFI
dataframe["mfi"] = ta.MFI(dataframe)
# # ROC
# dataframe['roc'] = ta.ROC(dataframe)
# Overlap Studies
# ------------------------------------ # ------------------------------------
# Bollinger Bands # 成交量5日移动平均
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) dataframe['volume_5ma'] = dataframe['volume'].rolling(window=5).mean()
dataframe["bb_lowerband"] = bollinger["lower"]
dataframe["bb_middleband"] = bollinger["mid"] # 成交量放大信号当日成交量大于过去5日平均的倍数
dataframe["bb_upperband"] = bollinger["upper"] dataframe['volume_surge'] = dataframe['volume'] > (dataframe['volume_5ma'] * 1)
dataframe["bb_percent"] = (dataframe["close"] - dataframe["bb_lowerband"]) / (
dataframe["bb_upperband"] - dataframe["bb_lowerband"] # 价格与MA60关系指标
# ------------------------------------
# 价格在MA60上方且距离MA60不超过10%
dataframe['price_above_ma60'] = (
(dataframe['close'] > dataframe['ma60']) &
((dataframe['ma60'] - dataframe['close']) / dataframe['close'] < 0.1)
) )
dataframe["bb_width"] = (dataframe["bb_upperband"] - dataframe["bb_lowerband"]) / dataframe[
"bb_middleband"
]
# Bollinger Bands - Weighted (EMA based instead of SMA) # 价格在MA120上方
# weighted_bollinger = qtpylib.weighted_bollinger_bands( dataframe['price_above_ma120'] = dataframe['close'] > dataframe['ma120']
# qtpylib.typical_price(dataframe), window=20, stds=2
# )
# dataframe["wbb_upperband"] = weighted_bollinger["upper"]
# dataframe["wbb_lowerband"] = weighted_bollinger["lower"]
# dataframe["wbb_middleband"] = weighted_bollinger["mid"]
# dataframe["wbb_percent"] = (
# (dataframe["close"] - dataframe["wbb_lowerband"]) /
# (dataframe["wbb_upperband"] - dataframe["wbb_lowerband"])
# )
# dataframe["wbb_width"] = (
# (dataframe["wbb_upperband"] - dataframe["wbb_lowerband"]) /
# dataframe["wbb_middleband"]
# )
# # EMA - Exponential Moving Average # 前一天在MA60附近95%-105%
# dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3) dataframe['prev_near_ma'] = (
# dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) (dataframe['close'].shift(1) >= dataframe['ma60'].shift(1) * 0.95) &
# dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) (dataframe['close'].shift(1) <= dataframe['ma60'].shift(1) * 1.05)
# dataframe['ema21'] = ta.EMA(dataframe, timeperiod=21) )
# dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
# dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
# # SMA - Simple Moving Average # 前一天在MA60下方
# dataframe['sma3'] = ta.SMA(dataframe, timeperiod=3) dataframe['prev_below_ma'] = dataframe['close'].shift(1) < dataframe['ma60'].shift(1)
# dataframe['sma5'] = ta.SMA(dataframe, timeperiod=5)
# dataframe['sma10'] = ta.SMA(dataframe, timeperiod=10)
# dataframe['sma21'] = ta.SMA(dataframe, timeperiod=21)
# dataframe['sma50'] = ta.SMA(dataframe, timeperiod=50)
# dataframe['sma100'] = ta.SMA(dataframe, timeperiod=100)
# Parabolic SAR # 突破信号前一天在MA60附近或下方当天在MA60上方
dataframe["sar"] = ta.SAR(dataframe) dataframe['breakthrough'] = (
(dataframe['prev_below_ma'] | dataframe['prev_near_ma']) &
dataframe['price_above_ma60']
)
# TEMA - Triple Exponential Moving Average # MA60趋势指标
dataframe["tema"] = ta.TEMA(dataframe, timeperiod=9)
# Cycle Indicator
# ------------------------------------ # ------------------------------------
# Hilbert Transform Indicator - SineWave
hilbert = ta.HT_SINE(dataframe)
dataframe["htsine"] = hilbert["sine"]
dataframe["htleadsine"] = hilbert["leadsine"]
# Pattern Recognition - Bullish candlestick patterns # 计算MA60的上升趋势过去5天中至少3天上升
trend_days = 5
ma_trend = []
for i in range(len(dataframe)):
if i < trend_days - 1:
ma_trend.append(False)
else:
start_idx = i - trend_days + 1
ma_values = dataframe['ma60'].iloc[start_idx:i+1].values
if pd.isna(ma_values).any():
ma_trend.append(False)
else:
# 检查是否呈上升趋势至少3天上升
ma_trend.append(sum(ma_values[1:] > ma_values[:-1]) > trend_days - 3)
dataframe['ma_uptrend'] = ma_trend
# MA120趋势指标
# ------------------------------------ # ------------------------------------
# # Hammer: values [0, 100]
# dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe)
# # Inverted Hammer: values [0, 100]
# dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe)
# # Dragonfly Doji: values [0, 100]
# dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe)
# # Piercing Line: values [0, 100]
# dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100]
# # Morningstar: values [0, 100]
# dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
# # Three White Soldiers: values [0, 100]
# dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
# Pattern Recognition - Bearish candlestick patterns # 计算MA120的趋势过去5天不能为下降
ma120_trend = []
for i in range(len(dataframe)):
if i < 4:
ma120_trend.append(True) # 数据不足时不限制
else:
window = dataframe['ma120'].iloc[i-4:i+1].values
if pd.isna(window).any():
ma120_trend.append(True)
else:
# 只要有一天不是上升就不是下降趋势
ma120_trend.append((window[1:] >= window[:-1]).all())
dataframe['ma120_not_downtrend'] = ma120_trend
# 价格动量指标
# ------------------------------------ # ------------------------------------
# # Hanging Man: values [0, 100]
# dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe)
# # Shooting Star: values [0, 100]
# dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe)
# # Gravestone Doji: values [0, 100]
# dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe)
# # Dark Cloud Cover: values [0, 100]
# dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe)
# # Evening Doji Star: values [0, 100]
# dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe)
# # Evening Star: values [0, 100]
# dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe)
# Pattern Recognition - Bullish/Bearish candlestick patterns # 阳线条件:收盘价 > 开盘价且阳线幅度至少0.5%
dataframe['is_bullish'] = (
(dataframe['close'] > dataframe['open']) &
((dataframe['close'] - dataframe['open']) / dataframe['open'] >= 0.005)
)
# 前5天的下跌天数不超过2天
dataframe['is_down_day'] = dataframe['close'] < dataframe['open']
dataframe['down_days_in_5'] = dataframe['is_down_day'].rolling(window=5, min_periods=1).sum()
dataframe['not_too_many_downs'] = dataframe['down_days_in_5'] <= 2
# 当天收盘价大于第五天前的价格
dataframe['price_higher_than_5days_ago'] = dataframe['close'] > dataframe['close'].shift(5)
# RSI条件 - 使用可优化参数
dataframe['rsi_lt_threshold'] = dataframe['rsi'] < 70
# 综合买入信号
# ------------------------------------ # ------------------------------------
# # Three Line Strike: values [0, -100, 100]
# dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe)
# # Spinning Top: values [0, -100, 100]
# dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100]
# # Engulfing: values [0, -100, 100]
# dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100]
# # Harami: values [0, -100, 100]
# dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100]
# # Three Outside Up/Down: values [0, -100, 100]
# dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
# # Three Inside Up/Down: values [0, -100, 100]
# dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
# # Chart type dataframe["buy_signal"] = (
# # ------------------------------------ dataframe["price_above_ma60"] &
# # Heikin Ashi Strategy dataframe["ma_uptrend"] &
# heikinashi = qtpylib.heikinashi(dataframe) dataframe['ma60'].notna() &
# dataframe['ha_open'] = heikinashi['open'] dataframe["volume_surge"] &
# dataframe['ha_close'] = heikinashi['close'] dataframe["not_too_many_downs"] &
# dataframe['ha_high'] = heikinashi['high'] dataframe["price_higher_than_5days_ago"] &
# dataframe['ha_low'] = heikinashi['low'] dataframe['price_above_ma120'] &
dataframe['ma120_not_downtrend']
# &
# dataframe['rsi_lt_threshold']
)
# Retrieve best bid and best ask from the orderbook # 卖出信号价格跌破MA60且跌幅超过10%
# ------------------------------------ dataframe['sell_signal'] = (
""" (~dataframe['price_above_ma60']) &
# first check if dataprovider is available ((dataframe['ma60'] - dataframe['close']) / dataframe['ma60'] > 0.1) &
if self.dp: dataframe['ma60'].notna()
if self.dp.runmode.value in ('live', 'dry_run'): )
ob = self.dp.orderbook(metadata['pair'], 1)
dataframe['best_bid'] = ob['bids'][0][0]
dataframe['best_ask'] = ob['asks'][0][0]
"""
return dataframe return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the entry signal for the given dataframe 基于x.py策略的买入信号逻辑
实现MA60/MA120趋势跟踪策略的买入条件
:param dataframe: DataFrame :param dataframe: DataFrame
:param metadata: Additional information, like the currently traded pair :param metadata: Additional information, like the currently traded pair
:return: DataFrame with entry columns populated :return: DataFrame with entry columns populated
""" """
# 多头买入信号 - 基于x.py的buy_signal逻辑
dataframe.loc[ dataframe.loc[
( (
# Signal: RSI crosses above 30 # 价格在MA60上方且距离MA60不超过10%
(qtpylib.crossed_above(dataframe["rsi"], self.buy_rsi.value)) dataframe["price_above_ma60"] &
& (dataframe["tema"] <= dataframe["bb_middleband"]) # Guard: tema below BB middle # MA60呈上升趋势过去5天中至少3天上升
& (dataframe["tema"] > dataframe["tema"].shift(1)) # Guard: tema is raising dataframe["ma_uptrend"] &
& (dataframe["volume"] > 0) # Make sure Volume is not 0 # MA60数据有效
dataframe['ma60'].notna() &
# 成交量放大当日成交量大于过去5日平均
dataframe["volume_surge"] &
# 前5天中下跌天数不超过2天
dataframe["not_too_many_downs"] &
# 当天收盘价大于5天前的价格
dataframe["price_higher_than_5days_ago"] &
# 价格在MA120上方
dataframe['price_above_ma120'] &
# MA120过去5天不是下降趋势
dataframe['ma120_not_downtrend'] &
# RSI小于阈值
dataframe['rsi_lt_threshold'] &
# 确保成交量不为0
(dataframe["volume"] > 0)
), ),
"enter_long", "enter_long",
] = 1 ] = 1
dataframe.loc[
(
# Signal: RSI crosses above 70
(qtpylib.crossed_above(dataframe["rsi"], self.short_rsi.value))
& (dataframe["tema"] > dataframe["bb_middleband"]) # Guard: tema above BB middle
& (dataframe["tema"] < dataframe["tema"].shift(1)) # Guard: tema is falling
& (dataframe["volume"] > 0) # Make sure Volume is not 0
),
"enter_short",
] = 1
return dataframe return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
Based on TA indicators, populates the exit signal for the given dataframe 基于x.py策略的卖出信号逻辑
实现MA60/MA120趋势跟踪策略的卖出条件
:param dataframe: DataFrame :param dataframe: DataFrame
:param metadata: Additional information, like the currently traded pair :param metadata: Additional information, like the currently traded pair
:return: DataFrame with exit columns populated :return: DataFrame with exit columns populated
""" """
# 多头卖出信号 - 基于x.py的sell_signal逻辑
# dataframe.loc[
# (
# # 价格跌破MA60且跌幅超过10%
# (~dataframe['price_above_ma60']) &
# ((dataframe['ma60'] - dataframe['close']) / dataframe['ma60'] > 0.1) &
# dataframe['ma60'].notna() &
# # 确保成交量不为0
# (dataframe["volume"] > 0)
# ),
# "exit_long",
# ] = 1
dataframe.loc[ dataframe.loc[
( (
# Signal: RSI crosses above 70 # 价格跌破MA60且跌幅超过10%
(qtpylib.crossed_above(dataframe["rsi"], self.sell_rsi.value)) (dataframe['close'] < dataframe['ma120']) &
& (dataframe["tema"] > dataframe["bb_middleband"]) # Guard: tema above BB middle dataframe['ma120'].notna() &
& (dataframe["tema"] < dataframe["tema"].shift(1)) # Guard: tema is falling # 确保成交量不为0
& (dataframe["volume"] > 0) # Make sure Volume is not 0 (dataframe["volume"] > 0)
), ),
"exit_long", "exit_long",
] = 1 ] = 1
dataframe.loc[
(
# Signal: RSI crosses above 30
(qtpylib.crossed_above(dataframe["rsi"], self.exit_short_rsi.value))
&
# Guard: tema below BB middle
(dataframe["tema"] <= dataframe["bb_middleband"])
& (dataframe["tema"] > dataframe["tema"].shift(1)) # Guard: tema is raising
& (dataframe["volume"] > 0) # Make sure Volume is not 0
),
"exit_short",
] = 1
return dataframe return dataframe