merged
This commit is contained in:
commit
4e512554ba
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,4 @@
|
||||
# Freqtrade rules
|
||||
config*.json
|
||||
*.sqlite
|
||||
*.sqlite-shm
|
||||
*.sqlite-wal
|
||||
|
||||
84
config_examples/config.json
Normal file
84
config_examples/config.json
Normal file
@ -0,0 +1,84 @@
|
||||
{
|
||||
"exchange": {
|
||||
"name": "okx",
|
||||
"key": "your_api_key",
|
||||
"secret": "your_api_secret",
|
||||
"ccxt_config": {
|
||||
"enableRateLimit": true,
|
||||
"defaultType": "spot",
|
||||
"rateLimit": 2000,
|
||||
"timeout": 30000
|
||||
},
|
||||
"pair_whitelist": ["OKB/USDT", "SOL/USDT"]
|
||||
},
|
||||
"pairlists": [
|
||||
{
|
||||
"method": "StaticPairList"
|
||||
}
|
||||
],
|
||||
"timeframe": "15m",
|
||||
"strategy": "NostalgiaForInfinityV8",
|
||||
"freqai": {
|
||||
"enabled": true,
|
||||
"freqaimodel": "LightGBMRegressor",
|
||||
"train_period_days": 7,
|
||||
"backtest_period_days": 0.2,
|
||||
"feature_parameters": {
|
||||
"include_timeframes": ["15m"],
|
||||
"include_corr_pairlist": ["OKB/USDT", "SOL/USDT"],
|
||||
"label_period_candles": 4,
|
||||
"include_shifted_candles": 0,
|
||||
"indicator_periods_candles": [5, 10, 20],
|
||||
"include_default_features": ["close", "open", "high", "low", "volume", "rsi", "ema", "sma"],
|
||||
"DI_threshold": 0.3
|
||||
},
|
||||
"data_split_parameters": {
|
||||
"test_size": 0.2,
|
||||
"shuffle": false
|
||||
},
|
||||
"model_training_parameters": {
|
||||
"n_estimators": 100,
|
||||
"learning_rate": 0.1,
|
||||
"num_leaves": 15,
|
||||
"verbose": -1
|
||||
}
|
||||
},
|
||||
"order_types": {
|
||||
"entry": "market",
|
||||
"exit": "market",
|
||||
"stoploss": "limit",
|
||||
"stoploss_on_exchange": false
|
||||
},
|
||||
"order_time_in_force": {
|
||||
"entry": "gtc",
|
||||
"exit": "gtc"
|
||||
},
|
||||
"entry_pricing": {
|
||||
"price_side": "other",
|
||||
"use_order_book": false,
|
||||
"price_last_balance": 0.0
|
||||
},
|
||||
"exit_pricing": {
|
||||
"price_side": "other",
|
||||
"use_order_book": false
|
||||
},
|
||||
"use_exit_signal": true,
|
||||
"exit_profit_only": false,
|
||||
"ignore_roi_if_entry_signal": false,
|
||||
"stoploss": -0.05,
|
||||
"minimal_roi": {
|
||||
"0": 0.06,
|
||||
"30": 0.04,
|
||||
"60": 0.02,
|
||||
"120": 0
|
||||
},
|
||||
"stake_currency": "USDT",
|
||||
"stake_amount": 150,
|
||||
"max_open_trades": 4,
|
||||
"unfilledtimeout": {
|
||||
"entry": 5,
|
||||
"exit": 15,
|
||||
"unit": "minutes"
|
||||
},
|
||||
"fee": 0.0008
|
||||
}
|
||||
@ -1,128 +0,0 @@
|
||||
{
|
||||
"$schema": "https://schema.freqtrade.io/schema.json",
|
||||
"trading_mode": "spot",
|
||||
"margin_mode": "isolated",
|
||||
"max_open_trades": 4,
|
||||
"stake_currency": "USDT",
|
||||
"stake_amount": 150,
|
||||
"startup_candle_count": 30,
|
||||
"tradable_balance_ratio": 1,
|
||||
"fiat_display_currency": "USD",
|
||||
"dry_run": true,
|
||||
"timeframe": "3m",
|
||||
"dry_run_wallet": 1000,
|
||||
"cancel_open_orders_on_exit": true,
|
||||
"stoploss": -0.05,
|
||||
"use_exit_signal": true,
|
||||
"unfilledtimeout": {
|
||||
"entry": 5,
|
||||
"exit": 15
|
||||
},
|
||||
"exchange": {
|
||||
"name": "okx",
|
||||
"key": "eca767d4-fda5-4a1b-bb28-49ae18093307",
|
||||
"secret": "8CA3628A556ED137977DB298D37BC7F3",
|
||||
"enable_ws": false,
|
||||
"ccxt_config": {
|
||||
"enableRateLimit": true,
|
||||
"rateLimit": 500,
|
||||
"options": {
|
||||
"defaultType": "spot"
|
||||
}
|
||||
},
|
||||
"ccxt_async_config": {
|
||||
"enableRateLimit": true,
|
||||
"rateLimit": 500,
|
||||
"timeout": 20000
|
||||
},
|
||||
"pair_whitelist": [
|
||||
"OKB/USDT"
|
||||
],
|
||||
"pair_blacklist": []
|
||||
},
|
||||
"entry_pricing": {
|
||||
"price_side": "same",
|
||||
"use_order_book": true,
|
||||
"order_book_top": 1,
|
||||
"price_last_balance": 0.0,
|
||||
"check_depth_of_market": {
|
||||
"enabled": false,
|
||||
"bids_to_ask_delta": 1
|
||||
}
|
||||
},
|
||||
"exit_pricing": {
|
||||
"price_side": "other",
|
||||
"use_order_book": true,
|
||||
"order_book_top": 1
|
||||
},
|
||||
"pairlists": [
|
||||
{
|
||||
"method": "StaticPairList"
|
||||
}
|
||||
],
|
||||
"freqai": {
|
||||
"enabled": true,
|
||||
"identifier": "test175",
|
||||
"freqaimodel": "XGBoostRegressor",
|
||||
"model_path": "/freqtrade/user_data/models",
|
||||
"save_backtesting_prediction": true,
|
||||
"purge_old_models": true,
|
||||
"load_trained_model": true,
|
||||
"train_period_days": 90,
|
||||
"backtest_period_days": 10,
|
||||
"live_retrain_hours": 0,
|
||||
"include_predictions_in_final_dataframe": true,
|
||||
"data_kitchen": {
|
||||
"fillna": "ffill",
|
||||
"feature_parameters": {
|
||||
"DI_threshold": 0.5,
|
||||
"weight_factor": 0.9
|
||||
}
|
||||
},
|
||||
"feature_parameters": {
|
||||
"include_timeframes": ["5m", "15m", "1h"],
|
||||
"include_corr_pairlist": ["BTC/USDT", "ETH/USDT"],
|
||||
"label_period_candles": 12,
|
||||
"include_shifted_candles": 3,
|
||||
"indicator_periods_candles": [10, 20, 50],
|
||||
"plot_feature_importances": 1,
|
||||
"feature_selection": {
|
||||
"method": "none"
|
||||
}
|
||||
},
|
||||
"data_split_parameters": {
|
||||
"test_size": 0.2,
|
||||
"shuffle": false,
|
||||
"random_state": 42
|
||||
},
|
||||
"model_training_parameters": {
|
||||
"n_estimators": 200,
|
||||
"learning_rate": 0.05,
|
||||
"max_depth": 6,
|
||||
"subsample": 0.8,
|
||||
"colsample_bytree": 0.8,
|
||||
"reg_alpha": 0.1,
|
||||
"reg_lambda": 1.0
|
||||
},
|
||||
},
|
||||
"api_server": {
|
||||
"enabled": true,
|
||||
"listen_ip_address": "0.0.0.0",
|
||||
"listen_port": 8080,
|
||||
"verbosity": "error",
|
||||
"enable_openapi": false,
|
||||
"jwt_secret_key": "6a599ab046dbb419014807dffd7b8823bfa7e5df56b17d545485deb87331b4ca",
|
||||
"ws_token": "6O5pBDiRigiZrmIsofaE2rkKMJtf9h8zVQ",
|
||||
"CORS_origins": [],
|
||||
"username": "freqAdmin",
|
||||
"password": "admin"
|
||||
},
|
||||
"bot_name": "freqtrade",
|
||||
"initial_state": "running",
|
||||
"force_entry_enable": false,
|
||||
"internals": {
|
||||
"process_throttle_secs": 5,
|
||||
"heartbeat_interval": 20,
|
||||
"loglevel": "DEBUG"
|
||||
}
|
||||
}
|
||||
@ -31,24 +31,36 @@ services:
|
||||
# Default command used when running `docker compose up`
|
||||
|
||||
# --freqaimodel XGBoostRegressor
|
||||
# commangd: >
|
||||
# # trade
|
||||
# --logfile /freqtrade/user_data/logs/freqtrade.log
|
||||
# --db-url sqlite:////freqtrade/user_data/tradesv3.sqlite
|
||||
# --freqaimodel LightGBMRegressor
|
||||
# --config /freqtrade/config_examples/config_freqai.okx.json
|
||||
# --strategy FreqaiExampleStrategy
|
||||
# --strategy FreqaiExampleHybridStrategy
|
||||
# --strategy-path /freqtrade/templates
|
||||
# command: >
|
||||
# trade
|
||||
# --logfile /freqtrade/user_data/logs/freqtrade.log
|
||||
# --db-url sqlite:////freqtrade/user_data/tradesv3.sqlite
|
||||
# --freqaimodel LightGBMRegressor
|
||||
# --config /freqtrade/config_examples/config_freqai.okx.json
|
||||
# --strategy FreqaiExampleStrategy
|
||||
# --strategy-path /freqtrade/templates
|
||||
# command: >
|
||||
# backtesting
|
||||
# --logfile /freqtrade/user_data/logs/freqtrade.log
|
||||
# --freqaimodel XGBoostRegressor
|
||||
# --freqaimodel LightGBMRegressor
|
||||
# --config /freqtrade/config_examples/config_freqai.okx.json
|
||||
# --strategy-path /freqtrade/templates
|
||||
# --strategy FreqaiExampleStrategy
|
||||
# --breakdown week month
|
||||
# --timerange 20250310-20250410
|
||||
# --export trades
|
||||
# command: >
|
||||
# hyperopt
|
||||
# --logfile /freqtrade/user_data/logs/freqtrade.log
|
||||
# --freqaimodel LightGBMRegressor
|
||||
# --config /freqtrade/config_examples/config_freqai.okx.json
|
||||
# --strategy-path /freqtrade/templates
|
||||
# --strategy FreqaiExampleStrategy
|
||||
# --timerange 20250301-20250420
|
||||
# --hyperopt-loss SharpeHyperOptLoss
|
||||
# --spaces roi stoploss trailing
|
||||
# -e 200
|
||||
|
||||
# command: >
|
||||
# hyperopt
|
||||
# --logfile /freqtrade/user_data/logs/freqtrade.log
|
||||
|
||||
7
filter.py
Normal file
7
filter.py
Normal file
@ -0,0 +1,7 @@
|
||||
with open('output.log', 'r') as input_file, open('output_filted.log', 'w') as output_file:
|
||||
for line in input_file:
|
||||
if "validation_0" in line:
|
||||
if "[99]" in line:
|
||||
output_file.write(line)
|
||||
else:
|
||||
output_file.write(line)
|
||||
85
freqtrade/templates/chat.md
Normal file
85
freqtrade/templates/chat.md
Normal file
@ -0,0 +1,85 @@
|
||||
> 请把当前策略的运行逻辑进行梳理和讲解
|
||||
|
||||
我来为您梳理和讲解这个策略的运行逻辑。这是一个基于 FreqAI 的机器学习交易策略,主要使用 RSI 指标和机器学习模型进行交易决策。以下是策略的主要逻辑:
|
||||
|
||||
### 1. 策略配置
|
||||
```python
|
||||
minimal_roi = {
|
||||
"0": 0.02,
|
||||
"7": 0.01,
|
||||
"13": 0.005,
|
||||
"60": 0
|
||||
}
|
||||
stoploss = 0.0
|
||||
trailing_stop = True
|
||||
```
|
||||
- 设置了静态的 ROI 目标
|
||||
- 启用了追踪止损
|
||||
|
||||
### 2. 特征工程
|
||||
策略通过多个方法构建特征:
|
||||
- `feature_engineering_expand_all`: 计算 RSI 和布林带宽度
|
||||
- `feature_engineering_expand_basic`: 计算价格变化百分比
|
||||
- `feature_engineering_standard`: 计算波动率和时间特征
|
||||
|
||||
### 3. 目标变量设置
|
||||
在 `set_freqai_targets` 方法中:
|
||||
- 使用未来 `label_period_candles` 周期的 RSI 值作为目标变量
|
||||
- 计算并标准化波动率特征
|
||||
|
||||
### 4. 指标计算
|
||||
`populate_indicators` 方法中计算了多个技术指标:
|
||||
- RSI
|
||||
- 波动率
|
||||
- TEMA(三重指数移动平均线)
|
||||
- 布林带下轨
|
||||
- 未来价格方向(up_or_down)
|
||||
|
||||
### 5. 机器学习模型
|
||||
使用 LightGBM 回归模型:
|
||||
```python
|
||||
freqai_info = {
|
||||
"model": "LightGBMRegressor",
|
||||
"feature_parameters": {...},
|
||||
"data_split_parameters": {...},
|
||||
"model_training_parameters": {...}
|
||||
}
|
||||
```
|
||||
- 模型预测买入和卖出的 RSI 值
|
||||
- 使用 SOL/USDT 作为相关交易对
|
||||
|
||||
### 6. 入场逻辑
|
||||
`populate_entry_trend` 方法中:
|
||||
- 当实际 RSI 上穿预测的买入 RSI 时
|
||||
- TEMA 指标向上
|
||||
- 交易量大于 0
|
||||
- 模型预测结果为 1
|
||||
- 未来价格方向为上涨
|
||||
|
||||
### 7. 出场逻辑
|
||||
`populate_exit_trend` 方法中:
|
||||
- 当实际 RSI 上穿预测的卖出 RSI 时
|
||||
- 或价格下跌超过 2%
|
||||
- 或价格跌破布林带下轨
|
||||
- 或持有时间超过 1 天
|
||||
|
||||
### 8. 交易确认
|
||||
`confirm_trade_entry` 方法:
|
||||
- 检查当前价格是否在可接受范围内
|
||||
- 记录交易确认信息
|
||||
|
||||
### 策略特点
|
||||
1. 机器学习驱动:使用 LightGBM 模型预测 RSI 值
|
||||
2. 多特征融合:结合技术指标、波动率和时间特征
|
||||
3. 风险控制:设置了 ROI 目标和追踪止损
|
||||
4. 动态调整:通过机器学习模型动态调整交易信号
|
||||
|
||||
### 运行流程
|
||||
1. 数据准备:获取历史数据,计算技术指标
|
||||
2. 特征工程:构建机器学习特征
|
||||
3. 模型训练:使用历史数据训练 LightGBM 模型
|
||||
4. 信号生成:根据模型预测结果生成交易信号
|
||||
5. 交易执行:根据入场和出场逻辑执行交易
|
||||
6. 风险控制:根据 ROI 和止损规则管理仓位
|
||||
|
||||
这个策略结合了传统技术分析和机器学习方法,通过模型预测来增强交易信号的准确性,同时保持了严格的风险控制机制。
|
||||
@ -1,277 +0,0 @@
|
||||
import logging
|
||||
import numpy as np
|
||||
from functools import reduce
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
from technical import qtpylib
|
||||
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class FreqaiPrimer(IStrategy):
|
||||
minimal_roi = {
|
||||
0: 0.135,
|
||||
9: 0.052,
|
||||
15: 0.007,
|
||||
60: 0
|
||||
}
|
||||
stoploss = -0.263
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.324
|
||||
trailing_stop_positive_offset = 0.411
|
||||
trailing_only_offset_is_reached = False
|
||||
max_open_trades = 4
|
||||
process_only_new_candles = True
|
||||
use_exit_signal = True
|
||||
startup_candle_count: int = 40
|
||||
can_short = False
|
||||
|
||||
buy_rsi = IntParameter(low=10, high=50, default=30, space="buy", optimize=False, load=True)
|
||||
sell_rsi = IntParameter(low=50, high=90, default=70, space="sell", optimize=False, load=True)
|
||||
roi_0 = DecimalParameter(low=0.01, high=0.2, default=0.135, space="roi", optimize=True, load=True)
|
||||
roi_15 = DecimalParameter(low=0.005, high=0.1, default=0.052, space="roi", optimize=True, load=True)
|
||||
roi_30 = DecimalParameter(low=0.001, high=0.05, default=0.007, space="roi", optimize=True, load=True)
|
||||
stoploss_param = DecimalParameter(low=-0.35, high=-0.1, default=-0.263, space="stoploss", optimize=True, load=True)
|
||||
trailing_stop_positive_param = DecimalParameter(low=0.1, high=0.5, default=0.324, space="trailing", optimize=True, load=True)
|
||||
trailing_stop_positive_offset_param = DecimalParameter(low=0.2, high=0.6, default=0.411, space="trailing", optimize=True, load=True)
|
||||
|
||||
freqai_info = {
|
||||
"model": "LightGBMRegressor",
|
||||
"feature_parameters": {
|
||||
"include_timeframes": ["5m", "15m", "1h"],
|
||||
"include_corr_pairlist": [],
|
||||
"label_period_candles": 12,
|
||||
"include_shifted_candles": 3,
|
||||
},
|
||||
"data_split_parameters": {
|
||||
"test_size": 0.2,
|
||||
"shuffle": False,
|
||||
},
|
||||
"model_training_parameters": {
|
||||
"n_estimators": 200,
|
||||
"learning_rate": 0.05,
|
||||
"num_leaves": 31,
|
||||
"verbose": -1,
|
||||
},
|
||||
}
|
||||
|
||||
plot_config = {
|
||||
"main_plot": {},
|
||||
"subplots": {
|
||||
"&-buy_rsi": {"&-buy_rsi": {"color": "green"}},
|
||||
"&-sell_rsi": {"&-sell_rsi": {"color": "red"}},
|
||||
"&-stoploss": {"&-stoploss": {"color": "purple"}},
|
||||
"&-roi_0": {"&-roi_0": {"color": "orange"}},
|
||||
"do_predict": {"do_predict": {"color": "brown"}},
|
||||
},
|
||||
}
|
||||
|
||||
def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame:
|
||||
dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period)
|
||||
dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period)
|
||||
dataframe["%-sma-period"] = ta.SMA(dataframe, timeperiod=period)
|
||||
dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period)
|
||||
dataframe["%-adx-period"] = ta.ADX(dataframe, timeperiod=period)
|
||||
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=period, stds=2.2)
|
||||
dataframe["bb_lowerband-period"] = bollinger["lower"]
|
||||
dataframe["bb_middleband-period"] = bollinger["mid"]
|
||||
dataframe["bb_upperband-period"] = bollinger["upper"]
|
||||
dataframe["%-bb_width-period"] = (
|
||||
dataframe["bb_upperband-period"] - dataframe["bb_lowerband-period"]
|
||||
) / dataframe["bb_middleband-period"]
|
||||
dataframe["%-close-bb_lower-period"] = dataframe["close"] / dataframe["bb_lowerband-period"]
|
||||
dataframe["%-roc-period"] = ta.ROC(dataframe, timeperiod=period)
|
||||
dataframe["%-relative_volume-period"] = (
|
||||
dataframe["volume"] / dataframe["volume"].rolling(period).mean()
|
||||
)
|
||||
dataframe = dataframe.replace([np.inf, -np.inf], 0)
|
||||
dataframe = dataframe.ffill()
|
||||
dataframe = dataframe.fillna(0)
|
||||
return dataframe
|
||||
|
||||
def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
|
||||
dataframe["%-pct-change"] = dataframe["close"].pct_change()
|
||||
dataframe["%-raw_volume"] = dataframe["volume"]
|
||||
dataframe["%-raw_price"] = dataframe["close"]
|
||||
dataframe = dataframe.replace([np.inf, -np.inf], 0)
|
||||
dataframe = dataframe.ffill()
|
||||
dataframe = dataframe.fillna(0)
|
||||
return dataframe
|
||||
|
||||
def feature_engineering_standard(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
|
||||
if len(dataframe["close"]) < 20:
|
||||
logger.warning(f"数据不足 {len(dataframe)} 根 K 线,%-volatility 可能不完整")
|
||||
dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek
|
||||
dataframe["%-hour_of_day"] = dataframe["date"].dt.hour
|
||||
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20, min_periods=1).std()
|
||||
dataframe["%-volatility"] = dataframe["%-volatility"].replace([np.inf, -np.inf], 0)
|
||||
dataframe["%-volatility"] = dataframe["%-volatility"].ffill()
|
||||
dataframe["%-volatility"] = dataframe["%-volatility"].fillna(0)
|
||||
return dataframe
|
||||
|
||||
def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
|
||||
logger.info(f"设置 FreqAI 目标,交易对:{metadata['pair']}")
|
||||
if "close" not in dataframe.columns:
|
||||
logger.error("数据框缺少必要的 'close' 列")
|
||||
raise ValueError("数据框缺少必要的 'close' 列")
|
||||
|
||||
try:
|
||||
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
|
||||
if "%-volatility" not in dataframe.columns:
|
||||
logger.warning("缺少 %-volatility 列,强制重新生成")
|
||||
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20, min_periods=1).std()
|
||||
dataframe["%-volatility"] = dataframe["%-volatility"].replace([np.inf, -np.inf], 0)
|
||||
dataframe["%-volatility"] = dataframe["%-volatility"].ffill()
|
||||
dataframe["%-volatility"] = dataframe["%-volatility"].fillna(0)
|
||||
|
||||
dataframe["&-buy_rsi"] = ta.RSI(dataframe, timeperiod=14)
|
||||
dataframe["&-buy_rsi"] = dataframe["&-buy_rsi"].shift(-label_period).ffill().bfill()
|
||||
|
||||
for col in ["&-buy_rsi", "%-volatility"]:
|
||||
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0)
|
||||
dataframe[col] = dataframe[col].ffill()
|
||||
dataframe[col] = dataframe[col].fillna(0)
|
||||
if dataframe[col].isna().any():
|
||||
logger.warning(f"目标列 {col} 仍包含 NaN,数据预览:\n{dataframe[col].tail(10)}")
|
||||
except Exception as e:
|
||||
logger.error(f"创建 FreqAI 目标失败:{str(e)}")
|
||||
raise
|
||||
|
||||
logger.info(f"目标列预览:\n{dataframe[['&-buy_rsi']].head().to_string()}")
|
||||
return dataframe
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
logger.info(f"处理交易对:{metadata['pair']}")
|
||||
logger.debug(f"输入特征列:{list(dataframe.columns)}")
|
||||
dataframe = self.freqai.start(dataframe, metadata, self)
|
||||
logger.debug(f"FreqAI 输出特征列:{list(dataframe.columns)}")
|
||||
|
||||
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
|
||||
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
||||
dataframe["bb_lowerband"] = bollinger["lower"]
|
||||
dataframe["bb_middleband"] = bollinger["mid"]
|
||||
dataframe["bb_upperband"] = bollinger["upper"]
|
||||
dataframe["tema"] = ta.TEMA(dataframe, timeperiod=9)
|
||||
|
||||
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
|
||||
dataframe["up_or_down"] = np.where(
|
||||
dataframe["close"].shift(-label_period) > dataframe["close"], 1, 0
|
||||
)
|
||||
|
||||
if "&-buy_rsi" in dataframe.columns:
|
||||
if "%-volatility" not in dataframe.columns:
|
||||
logger.warning("缺少 %-volatility 列,强制重新生成")
|
||||
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20, min_periods=1).std()
|
||||
dataframe["%-volatility"] = dataframe["%-volatility"].replace([np.inf, -np.inf], 0)
|
||||
dataframe["%-volatility"] = dataframe["%-volatility"].ffill()
|
||||
dataframe["%-volatility"] = dataframe["%-volatility"].fillna(0)
|
||||
|
||||
dataframe["&-sell_rsi"] = dataframe["&-buy_rsi"] + 30
|
||||
dataframe["&-stoploss"] = self.stoploss - (dataframe["%-volatility"] * 5).clip(-0.05, 0.05)
|
||||
dataframe["&-roi_0"] = (dataframe["close"].shift(-label_period) / dataframe["close"] - 1).clip(0, 0.2)
|
||||
|
||||
for col in ["&-buy_rsi", "&-sell_rsi", "&-stoploss", "&-roi_0"]:
|
||||
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0)
|
||||
dataframe[col] = dataframe[col].ffill()
|
||||
dataframe[col] = dataframe[col].fillna(0)
|
||||
|
||||
dataframe["buy_rsi_pred"] = dataframe["&-buy_rsi"].rolling(5).mean().clip(10, 50)
|
||||
dataframe["sell_rsi_pred"] = dataframe["&-sell_rsi"].rolling(5).mean().clip(50, 90)
|
||||
dataframe["stoploss_pred"] = dataframe["&-stoploss"].clip(-0.35, -0.1)
|
||||
dataframe["roi_0_pred"] = dataframe["&-roi_0"].clip(0.01, 0.2)
|
||||
|
||||
for col in ["buy_rsi_pred", "sell_rsi_pred", "stoploss_pred", "roi_0_pred"]:
|
||||
if dataframe[col].isna().any():
|
||||
logger.warning(f"列 {col} 包含 NaN,填充为默认值")
|
||||
dataframe[col] = dataframe[col].ffill()
|
||||
dataframe[col] = dataframe[col].fillna(dataframe[col].mean())
|
||||
|
||||
dataframe["trailing_stop_positive"] = (dataframe["roi_0_pred"] * 0.5).clip(0.01, 0.3)
|
||||
dataframe["trailing_stop_positive_offset"] = (dataframe["roi_0_pred"] * 0.75).clip(0.02, 0.4)
|
||||
|
||||
self.buy_rsi.value = float(dataframe["buy_rsi_pred"].iloc[-1])
|
||||
self.sell_rsi.value = float(dataframe["sell_rsi_pred"].iloc[-1])
|
||||
self.stoploss = float(self.stoploss_param.value)
|
||||
self.minimal_roi = {
|
||||
0: float(self.roi_0.value),
|
||||
15: float(self.roi_15.value),
|
||||
30: float(self.roi_30.value),
|
||||
60: 0
|
||||
}
|
||||
self.trailing_stop_positive = float(self.trailing_stop_positive_param.value)
|
||||
self.trailing_stop_positive_offset = float(self.trailing_stop_positive_offset_param.value)
|
||||
|
||||
logger.info(f"动态参数:buy_rsi={self.buy_rsi.value}, sell_rsi={self.sell_rsi.value}, "
|
||||
f"stoploss={self.stoploss}, trailing_stop_positive={self.trailing_stop_positive}")
|
||||
else:
|
||||
logger.warning(f"&-buy_rsi 列缺失,跳过 FreqAI 预测逻辑,检查 freqai.start 输出")
|
||||
|
||||
dataframe = dataframe.replace([np.inf, -np.inf], 0)
|
||||
dataframe = dataframe.ffill()
|
||||
dataframe = dataframe.fillna(0)
|
||||
|
||||
logger.info(f"up_or_down 值统计:\n{dataframe['up_or_down'].value_counts().to_string()}")
|
||||
logger.info(f"do_predict 值统计:\n{dataframe['do_predict'].value_counts().to_string()}")
|
||||
logger.debug(f"最终特征列:{list(dataframe.columns)}")
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
|
||||
enter_long_conditions = [
|
||||
qtpylib.crossed_above(df["rsi"], df["buy_rsi_pred"]),
|
||||
df["tema"] > df["tema"].shift(1),
|
||||
df["volume"] > 0,
|
||||
df["do_predict"] == 1,
|
||||
df["up_or_down"] == 1
|
||||
]
|
||||
if enter_long_conditions:
|
||||
df.loc[
|
||||
reduce(lambda x, y: x & y, enter_long_conditions),
|
||||
["enter_long", "enter_tag"]
|
||||
] = (1, "long")
|
||||
return df
|
||||
|
||||
def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
|
||||
exit_long_conditions = [
|
||||
qtpylib.crossed_above(df["rsi"], df["sell_rsi_pred"]),
|
||||
(df["close"] < df["close"].shift(1) * 0.97),
|
||||
df["volume"] > 0,
|
||||
df["do_predict"] == 1,
|
||||
df["up_or_down"] == 0
|
||||
]
|
||||
if exit_long_conditions:
|
||||
df.loc[
|
||||
reduce(lambda x, y: x & y, exit_long_conditions),
|
||||
"exit_long"
|
||||
] = 1
|
||||
return df
|
||||
|
||||
def confirm_trade_entry(
|
||||
self, pair: str, order_type: str, amount: float, rate: float,
|
||||
time_in_force: str, current_time, entry_tag, side: str, **kwargs
|
||||
) -> bool:
|
||||
try:
|
||||
df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||
if df is None or df.empty:
|
||||
logger.warning(f"无法获取 {pair} 的分析数据,拒绝交易")
|
||||
return False
|
||||
|
||||
last_candle = df.iloc[-1].squeeze()
|
||||
if "close" not in last_candle or np.isnan(last_candle["close"]):
|
||||
logger.warning(f"{pair} 的最新 K 线缺少有效 close 价格,拒绝交易")
|
||||
return False
|
||||
|
||||
if side == "long":
|
||||
max_rate = last_candle["close"] * (1 + 0.0025) # 0.25% 滑点阈值
|
||||
if rate > max_rate:
|
||||
logger.debug(f"拒绝 {pair} 的买入,价格 {rate} 超过最大允许价格 {max_rate}")
|
||||
return False
|
||||
elif side == "short":
|
||||
logger.warning(f"{pair} 尝试做空,但策略不支持做空 (can_short={self.can_short})")
|
||||
return False
|
||||
|
||||
logger.debug(f"确认 {pair} 的交易:side={side}, rate={rate}, close={last_candle['close']}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"确认 {pair} 交易时出错:{str(e)}")
|
||||
return False
|
||||
30
run.sh
Executable file
30
run.sh
Executable file
@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
|
||||
rm -rf user_data/models/*
|
||||
rm -rf ./freqtrade/user_data/data/backtest_results/*
|
||||
|
||||
docker-compose run --rm freqtrade >output.log 2>&1
|
||||
sed -i 's/\x1B\[[0-9;]*m//g' output.log
|
||||
python3 filter.py
|
||||
|
||||
rm ./result/* -fr
|
||||
mv ./user_data/backtest_results/* ./result/
|
||||
|
||||
cd ./result
|
||||
# 查找当前目录下的所有 zip 文件
|
||||
zip_files=(*.zip)
|
||||
|
||||
# 检查是否只有一个 zip 文件
|
||||
if [ ${#zip_files[@]} -eq 1 ]; then
|
||||
# 解压缩该 zip 文件到当前目录
|
||||
unzip "${zip_files[0]}"
|
||||
rm *.zip
|
||||
rm *.feather
|
||||
else
|
||||
echo "当前目录下没有 zip 文件或者有多个 zip 文件,无法操作。"
|
||||
fi
|
||||
|
||||
cd -
|
||||
sed -i 's/\x1B\[[0-9;]*m//g' output.log
|
||||
python3 filter.py
|
||||
cp output_filted.log result/ -f
|
||||
2202
temp_validation.log
2202
temp_validation.log
File diff suppressed because it is too large
Load Diff
129
tests/config_test_comments.json
Normal file
129
tests/config_test_comments.json
Normal file
@ -0,0 +1,129 @@
|
||||
{
|
||||
/* Single-line C-style comment */
|
||||
"max_open_trades": 3,
|
||||
/*
|
||||
* Multi-line C-style comment
|
||||
*/
|
||||
"stake_currency": "BTC",
|
||||
"stake_amount": 0.05,
|
||||
"fiat_display_currency": "USD", // C++-style comment
|
||||
"amount_reserve_percent": 0.05, // And more, tabs before this comment
|
||||
"dry_run": false,
|
||||
"timeframe": "5m",
|
||||
"trailing_stop": false,
|
||||
"trailing_stop_positive": 0.005,
|
||||
"trailing_stop_positive_offset": 0.0051,
|
||||
"trailing_only_offset_is_reached": false,
|
||||
"minimal_roi": {
|
||||
"40": 0.0,
|
||||
"30": 0.01,
|
||||
"20": 0.02,
|
||||
"0": 0.04
|
||||
},
|
||||
"stoploss": -0.10,
|
||||
"unfilledtimeout": {
|
||||
"buy": 10,
|
||||
"sell": 30, // Trailing comma should also be accepted now
|
||||
},
|
||||
"bid_strategy": {
|
||||
"use_order_book": false,
|
||||
"ask_last_balance": 0.0,
|
||||
"order_book_top": 1,
|
||||
"check_depth_of_market": {
|
||||
"enabled": false,
|
||||
"bids_to_ask_delta": 1
|
||||
}
|
||||
},
|
||||
"ask_strategy": {
|
||||
"use_order_book": false,
|
||||
"order_book_min": 1,
|
||||
"order_book_max": 9
|
||||
},
|
||||
"order_types": {
|
||||
"buy": "limit",
|
||||
"sell": "limit",
|
||||
"stoploss": "market",
|
||||
"stoploss_on_exchange": false,
|
||||
"stoploss_on_exchange_interval": 60
|
||||
},
|
||||
"order_time_in_force": {
|
||||
"buy": "gtc",
|
||||
"sell": "gtc"
|
||||
},
|
||||
"pairlist": {
|
||||
"method": "VolumePairList",
|
||||
"config": {
|
||||
"number_assets": 20,
|
||||
"sort_key": "quoteVolume",
|
||||
"precision_filter": false
|
||||
}
|
||||
},
|
||||
"exchange": {
|
||||
"name": "binance",
|
||||
"sandbox": false,
|
||||
"key": "your_exchange_key",
|
||||
"secret": "your_exchange_secret",
|
||||
"password": "",
|
||||
"ccxt_config": {
|
||||
"enableRateLimit": true
|
||||
},
|
||||
"ccxt_async_config": {
|
||||
"enableRateLimit": false,
|
||||
"rateLimit": 500,
|
||||
"aiohttp_trust_env": false
|
||||
},
|
||||
"pair_whitelist": [
|
||||
"ETH/BTC",
|
||||
"LTC/BTC",
|
||||
"ETC/BTC",
|
||||
"DASH/BTC",
|
||||
"ZEC/BTC",
|
||||
"XLM/BTC",
|
||||
"NXT/BTC",
|
||||
"TRX/BTC",
|
||||
"ADA/BTC",
|
||||
"XMR/BTC"
|
||||
],
|
||||
"pair_blacklist": [
|
||||
"DOGE/BTC"
|
||||
],
|
||||
"outdated_offset": 5,
|
||||
"markets_refresh_interval": 60
|
||||
},
|
||||
"edge": {
|
||||
"enabled": false,
|
||||
"process_throttle_secs": 3600,
|
||||
"calculate_since_number_of_days": 7,
|
||||
"allowed_risk": 0.01,
|
||||
"stoploss_range_min": -0.01,
|
||||
"stoploss_range_max": -0.1,
|
||||
"stoploss_range_step": -0.01,
|
||||
"minimum_winrate": 0.60,
|
||||
"minimum_expectancy": 0.20,
|
||||
"min_trade_number": 10,
|
||||
"max_trade_duration_minute": 1440,
|
||||
"remove_pumps": false
|
||||
},
|
||||
"telegram": {
|
||||
// We can now comment out some settings
|
||||
// "enabled": true,
|
||||
"enabled": false,
|
||||
"token": "your_telegram_token",
|
||||
"chat_id": "your_telegram_chat_id"
|
||||
},
|
||||
"api_server": {
|
||||
"enabled": false,
|
||||
"listen_ip_address": "127.0.0.1",
|
||||
"listen_port": 8080,
|
||||
"username": "freqtrader",
|
||||
"password": "SuperSecurePassword"
|
||||
},
|
||||
"db_url": "sqlite:///tradesv3.sqlite",
|
||||
"initial_state": "running",
|
||||
"forcebuy_enable": false,
|
||||
"internals": {
|
||||
"process_throttle_secs": 5
|
||||
},
|
||||
"strategy": "DefaultStrategy",
|
||||
"strategy_path": "user_data/strategies/"
|
||||
}
|
||||
@ -60,11 +60,14 @@ if [[ "$@" == *"--timerange"* ]] && [[ "$@" == *"--days"* ]]; then
|
||||
fi
|
||||
|
||||
# Get timerange or days from parameters
|
||||
timerange=""
|
||||
days=""
|
||||
if [[ "$@" == *"--timerange"* ]]; then
|
||||
timerange=$(get_param_value "--timerange" "$@")
|
||||
elif [[ "$@" == *"--days"* ]]; then
|
||||
days=$(get_param_value "--days" "$@")
|
||||
fi
|
||||
|
||||
# Get pairs and timeframe from parameters or use defaults
|
||||
pairs=$(get_csv_param_value "--pairs" "$@")
|
||||
timeframe=$(get_csv_param_value "--timeframe" "$@")
|
||||
@ -75,18 +78,22 @@ if [[ -z "$pairs" ]]; then
|
||||
fi
|
||||
|
||||
if [[ -z "$timeframe" ]]; then
|
||||
timeframe="3m,5m,15m,30m,1h,4h,6h,12h,1d"
|
||||
timeframe="5m,15m,30m,1h,4h,6h,12h,1d"
|
||||
fi
|
||||
|
||||
# Convert timeframe string to array
|
||||
IFS=',' read -r -a timeframe_array <<<"$timeframe"
|
||||
timeframe_array_str=$(printf " '%s'" "${timeframe_array[@]}")
|
||||
|
||||
# Initialize the base command
|
||||
cmd="docker-compose run --rm freqtrade download-data --config /freqtrade/config_examples/basic.json --pairs $pairs --timeframe $timeframe"
|
||||
cmd="docker-compose run --rm freqtrade download-data --config /freqtrade/config_examples/basic.json --pairs $pairs --timeframe$timeframe_array_str"
|
||||
|
||||
# Add timerange or days if provided
|
||||
if [[ -n "$timerange" ]]; then
|
||||
cmd+=" --timerange $timerange"
|
||||
cmd+=" --timerange='$timerange'"
|
||||
elif [[ -n "$days" ]]; then
|
||||
cmd+=" --days $days"
|
||||
cmd+=" --days=$days"
|
||||
fi
|
||||
|
||||
# Execute the command
|
||||
eval $cmd
|
||||
eval "$cmd"
|
||||
|
||||
@ -1,4 +1,26 @@
|
||||
#!/bin/bash
|
||||
# 加载 .env 文件中的变量
|
||||
if [ -f ".env" ]; then
|
||||
export $(grep -v '^#' .env | xargs)
|
||||
else
|
||||
echo "⚠️ 本地缺少 .env 文件,请创建并配置。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 获取当前分支名称
|
||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
|
||||
# 检查当前分支是否匹配 TEST_BRANCH 或 DRYRUN_BRANCH
|
||||
if [[ "$CURRENT_BRANCH" != "$TEST_BRANCH" && "$CURRENT_BRANCH" != "$DRYRUN_BRANCH" ]]; then
|
||||
echo "⚠️ 错误:当前分支 '$CURRENT_BRANCH' 不符合环境变量配置的预期。"
|
||||
echo "请确保当前分支与 .env 文件中的 TEST_BRANCH 或 DRYRUN_BRANCH 配置一致。"
|
||||
echo "TEST_BRANCH=$TEST_BRANCH"
|
||||
echo "DRYRUN_BRANCH=$DRYRUN_BRANCH"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ 当前分支 '$CURRENT_BRANCH' 符合环境变量配置要求。"
|
||||
fi
|
||||
|
||||
# 检查当前分支名称是否包含 "dryrun"
|
||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
if [[ "$CURRENT_BRANCH" != *"dryrun"* ]]; then
|
||||
@ -33,7 +55,6 @@ if [ ! -f ".env" ]; then
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 加载 .env 文件中的变量
|
||||
export $(grep -v '^#' .env | xargs)
|
||||
# 设置默认值
|
||||
|
||||
42
tools/mergeme.sh
Executable file
42
tools/mergeme.sh
Executable file
@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e # 出错立即停止
|
||||
|
||||
# 获取参数
|
||||
source_branch=$1
|
||||
target_branch=$2
|
||||
|
||||
# 确保传入了两个参数
|
||||
if [ -z "$source_branch" ] || [ -z "$target_branch" ]; then
|
||||
echo "❌ 使用方法: $0 <源分支> <目标分支>"
|
||||
echo "示例: $0 theforce-noai-test base"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 定位到 Git 仓库根目录
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
|
||||
# 获取当前分支名
|
||||
current_branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
|
||||
# 如果当前不在 source_branch 上,则切换过去
|
||||
if [ "$current_branch" != "$source_branch" ]; then
|
||||
echo "🔄 当前在分支 '$current_branch',正在切换到 '$source_branch'"
|
||||
git checkout "$source_branch"
|
||||
fi
|
||||
|
||||
echo "📥 正在拉取 '$source_branch' 的最新代码..."
|
||||
git pull origin "$source_branch"
|
||||
|
||||
echo "📥 正在拉取目标分支 '$target_branch' 的最新代码..."
|
||||
git fetch origin "$target_branch"
|
||||
|
||||
echo "🔄 正在合并 '$target_branch' 到 '$source_branch'"
|
||||
git merge "origin/$target_branch" -m "Merge branch '$target_branch' into $source_branch"
|
||||
|
||||
# 如果有冲突,需要手动解决,脚本会在此处中断
|
||||
|
||||
echo "📤 正在推送合并后的代码到远程 '$source_branch'"
|
||||
git push origin "$source_branch"
|
||||
|
||||
echo "✅ 合并完成并已推送到远程分支 '$source_branch'"
|
||||
11
tools/mergetemp.sh
Executable file
11
tools/mergetemp.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
source_branch=$0
|
||||
dest_branch=$1
|
||||
path=$3
|
||||
cd $path
|
||||
git reset --hard
|
||||
git checkout $dest_branch
|
||||
git pull
|
||||
git checkout $source_branch
|
||||
git merge $dest_branch
|
||||
65
tools/view_feather.py
Normal file
65
tools/view_feather.py
Normal file
@ -0,0 +1,65 @@
|
||||
import argparse
|
||||
import pandas as pd
|
||||
|
||||
def analyze_candlestick_data(file_path):
|
||||
# 读取feather文件
|
||||
df = pd.read_feather(file_path)
|
||||
|
||||
# 查看数据集行数和列数
|
||||
rows, columns = df.shape
|
||||
|
||||
if rows < 500:
|
||||
# 短表数据(行数少于500)查看全量数据信息
|
||||
print('数据全部内容信息:')
|
||||
print(df.to_csv(sep='\t', na_rep='nan'))
|
||||
else:
|
||||
# 长表数据查看数据前几行信息
|
||||
print('数据前几行内容信息:')
|
||||
print(df.head().to_csv(sep='\t', na_rep='nan'))
|
||||
|
||||
# 查看数据的基本信息
|
||||
print('数据基本信息:')
|
||||
df.info()
|
||||
|
||||
# 查看数据集行数和列数
|
||||
rows, columns = df.shape
|
||||
|
||||
if columns < 10 and rows < 500:
|
||||
# 短表窄数据(列少于10且行数少于500)查看全量统计信息
|
||||
print('数据全部内容描述性统计信息:')
|
||||
print(df.describe(include='all', percentiles=[.25, .5, .75]).to_csv(sep='\t', na_rep='nan'))
|
||||
else:
|
||||
# 长表数据查看数据前几行统计信息
|
||||
print('数据前几行描述性统计信息:')
|
||||
print(df.head().describe(include='all', percentiles=[.25, .5, .75]).to_csv(sep='\t', na_rep='nan'))
|
||||
|
||||
# 计算时间跨度
|
||||
min_date = df['date'].min()
|
||||
max_date = df['date'].max()
|
||||
time_span = max_date - min_date
|
||||
|
||||
# 检查时间序列完整性
|
||||
df = df.sort_values('date') # 确保数据按时间排序
|
||||
df['time_diff'] = df['date'].diff().dt.total_seconds() # 计算相邻时间点的差值(秒)
|
||||
expected_freq = df['time_diff'].mode()[0] # 使用最常见的间隔作为预期频率
|
||||
missing_intervals = df[df['time_diff'] > expected_freq] # 找出间隔大于预期的位置
|
||||
|
||||
print(f"\n数据时间跨度:{time_span}")
|
||||
print(f"开始时间:{min_date}")
|
||||
print(f"结束时间:{max_date}")
|
||||
|
||||
if missing_intervals.empty:
|
||||
print("数据完整性:完整,未发现缺失的蜡烛图数据")
|
||||
else:
|
||||
print(f"数据完整性:不完整,发现 {len(missing_intervals)} 处可能的缺失")
|
||||
print("缺失位置示例:")
|
||||
for _, row in missing_intervals.head(5).iterrows(): # 显示前5个缺失示例
|
||||
gap_duration = pd.Timedelta(seconds=row['time_diff'])
|
||||
print(f" - 在 {row['date']} 之前缺失了 {gap_duration}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='分析Freqtrade蜡烛图Feather文件')
|
||||
parser.add_argument('--path', required=True, help='Feather文件路径')
|
||||
args = parser.parse_args()
|
||||
|
||||
analyze_candlestick_data(args.path)
|
||||
Loading…
x
Reference in New Issue
Block a user