MidasBot: bot trading crypto IA + stratégies Ichimoku validées
- Infra: Freqtrade (futures dry-run) + Redis + dashboard + Docker Compose - Couche IA: ai_analyzer (Claude via abonnement, MCP TradingView, backfill biais) - Stratégies: SampleStrategy, AiBiasStrategy, IchimokuLS (long/short, validée train/test + données vierges + walk-forward), MTFIchimoku, variantes hyperopt - Arbitrage CEX (dry-run), backtesting, walk-forward, volatility targeting - IchimokuLS en dry-run live (config_live.json) Claude-Session: https://claude.ai/code/session_01VHETcFacdnDhQzthLpdYFR
This commit is contained in:
5
freqtrade/Dockerfile
Normal file
5
freqtrade/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
# Image Freqtrade + client Redis (pour qu'AiBiasStrategy lise les biais IA en live/dry-run).
|
||||
FROM freqtradeorg/freqtrade:stable
|
||||
|
||||
# Installé en tant qu'utilisateur ftuser (pattern recommandé par Freqtrade).
|
||||
RUN pip install --user --no-cache-dir redis>=5.0
|
||||
80
freqtrade/user_data/config.json
Normal file
80
freqtrade/user_data/config.json
Normal file
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"$schema": "https://schema.freqtrade.io/schema.json",
|
||||
"bot_name": "MidasBot",
|
||||
"max_open_trades": 3,
|
||||
"stake_currency": "USDT",
|
||||
"stake_amount": 100,
|
||||
"tradable_balance_ratio": 0.99,
|
||||
"fiat_display_currency": "EUR",
|
||||
"dry_run": true,
|
||||
"dry_run_wallet": 1000,
|
||||
"cancel_open_orders_on_exit": false,
|
||||
"trading_mode": "spot",
|
||||
"margin_mode": "",
|
||||
"timeframe": "1h",
|
||||
"unfilledtimeout": {
|
||||
"entry": 10,
|
||||
"exit": 10,
|
||||
"exit_timeout_count": 0,
|
||||
"unit": "minutes"
|
||||
},
|
||||
"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": "same",
|
||||
"use_order_book": true,
|
||||
"order_book_top": 1
|
||||
},
|
||||
"exchange": {
|
||||
"name": "binance",
|
||||
"key": "",
|
||||
"secret": "",
|
||||
"ccxt_config": {},
|
||||
"ccxt_async_config": {},
|
||||
"pair_whitelist": [
|
||||
"BTC/USDT",
|
||||
"ETH/USDT",
|
||||
"SOL/USDT",
|
||||
"BNB/USDT"
|
||||
],
|
||||
"pair_blacklist": [
|
||||
".*(BULL|BEAR|UP|DOWN)/.*",
|
||||
".*(USDC|TUSD|BUSD|DAI|FDUSD)/.*"
|
||||
]
|
||||
},
|
||||
"pairlists": [
|
||||
{
|
||||
"method": "StaticPairList"
|
||||
}
|
||||
],
|
||||
"telegram": {
|
||||
"enabled": false,
|
||||
"token": "",
|
||||
"chat_id": ""
|
||||
},
|
||||
"api_server": {
|
||||
"enabled": true,
|
||||
"listen_ip_address": "0.0.0.0",
|
||||
"listen_port": 8080,
|
||||
"verbosity": "error",
|
||||
"enable_openapi": true,
|
||||
"jwt_secret_key": "set-via-env-FREQTRADE__API_SERVER__JWT_SECRET_KEY",
|
||||
"ws_token": "set-via-env-FREQTRADE__API_SERVER__WS_TOKEN",
|
||||
"CORS_origins": [],
|
||||
"username": "set-via-env",
|
||||
"password": "set-via-env"
|
||||
},
|
||||
"initial_state": "running",
|
||||
"force_entry_enable": false,
|
||||
"internals": {
|
||||
"process_throttle_secs": 5
|
||||
}
|
||||
}
|
||||
80
freqtrade/user_data/config_futures.json
Normal file
80
freqtrade/user_data/config_futures.json
Normal file
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"$schema": "https://schema.freqtrade.io/schema.json",
|
||||
"bot_name": "MidasBot",
|
||||
"max_open_trades": 3,
|
||||
"stake_currency": "USDT",
|
||||
"stake_amount": 100,
|
||||
"tradable_balance_ratio": 0.99,
|
||||
"fiat_display_currency": "EUR",
|
||||
"dry_run": true,
|
||||
"dry_run_wallet": 1000,
|
||||
"cancel_open_orders_on_exit": false,
|
||||
"trading_mode": "futures",
|
||||
"margin_mode": "isolated",
|
||||
"timeframe": "1h",
|
||||
"unfilledtimeout": {
|
||||
"entry": 10,
|
||||
"exit": 10,
|
||||
"exit_timeout_count": 0,
|
||||
"unit": "minutes"
|
||||
},
|
||||
"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": "same",
|
||||
"use_order_book": true,
|
||||
"order_book_top": 1
|
||||
},
|
||||
"exchange": {
|
||||
"name": "binance",
|
||||
"key": "",
|
||||
"secret": "",
|
||||
"ccxt_config": {},
|
||||
"ccxt_async_config": {},
|
||||
"pair_whitelist": [
|
||||
"BTC/USDT:USDT",
|
||||
"ETH/USDT:USDT",
|
||||
"SOL/USDT:USDT",
|
||||
"BNB/USDT:USDT"
|
||||
],
|
||||
"pair_blacklist": [
|
||||
".*(BULL|BEAR|UP|DOWN)/.*",
|
||||
".*(USDC|TUSD|BUSD|DAI|FDUSD)/.*"
|
||||
]
|
||||
},
|
||||
"pairlists": [
|
||||
{
|
||||
"method": "StaticPairList"
|
||||
}
|
||||
],
|
||||
"telegram": {
|
||||
"enabled": false,
|
||||
"token": "",
|
||||
"chat_id": ""
|
||||
},
|
||||
"api_server": {
|
||||
"enabled": false,
|
||||
"listen_ip_address": "0.0.0.0",
|
||||
"listen_port": 8080,
|
||||
"verbosity": "error",
|
||||
"enable_openapi": true,
|
||||
"jwt_secret_key": "set-via-env-FREQTRADE__API_SERVER__JWT_SECRET_KEY",
|
||||
"ws_token": "set-via-env-FREQTRADE__API_SERVER__WS_TOKEN",
|
||||
"CORS_origins": [],
|
||||
"username": "set-via-env",
|
||||
"password": "set-via-env"
|
||||
},
|
||||
"initial_state": "running",
|
||||
"force_entry_enable": false,
|
||||
"internals": {
|
||||
"process_throttle_secs": 5
|
||||
}
|
||||
}
|
||||
88
freqtrade/user_data/config_futures_multi.json
Normal file
88
freqtrade/user_data/config_futures_multi.json
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"$schema": "https://schema.freqtrade.io/schema.json",
|
||||
"bot_name": "MidasBot",
|
||||
"max_open_trades": 9,
|
||||
"stake_currency": "USDT",
|
||||
"stake_amount": 100,
|
||||
"tradable_balance_ratio": 0.99,
|
||||
"fiat_display_currency": "EUR",
|
||||
"dry_run": true,
|
||||
"dry_run_wallet": 1000,
|
||||
"cancel_open_orders_on_exit": false,
|
||||
"trading_mode": "futures",
|
||||
"margin_mode": "isolated",
|
||||
"timeframe": "1h",
|
||||
"unfilledtimeout": {
|
||||
"entry": 10,
|
||||
"exit": 10,
|
||||
"exit_timeout_count": 0,
|
||||
"unit": "minutes"
|
||||
},
|
||||
"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": "same",
|
||||
"use_order_book": true,
|
||||
"order_book_top": 1
|
||||
},
|
||||
"exchange": {
|
||||
"name": "binance",
|
||||
"key": "",
|
||||
"secret": "",
|
||||
"ccxt_config": {},
|
||||
"ccxt_async_config": {},
|
||||
"pair_whitelist": [
|
||||
"BTC/USDT:USDT",
|
||||
"ETH/USDT:USDT",
|
||||
"SOL/USDT:USDT",
|
||||
"BNB/USDT:USDT",
|
||||
"XRP/USDT:USDT",
|
||||
"ADA/USDT:USDT",
|
||||
"AVAX/USDT:USDT",
|
||||
"DOGE/USDT:USDT",
|
||||
"LINK/USDT:USDT",
|
||||
"DOT/USDT:USDT",
|
||||
"LTC/USDT:USDT",
|
||||
"TRX/USDT:USDT"
|
||||
],
|
||||
"pair_blacklist": [
|
||||
".*(BULL|BEAR|UP|DOWN)/.*",
|
||||
".*(USDC|TUSD|BUSD|DAI|FDUSD)/.*"
|
||||
]
|
||||
},
|
||||
"pairlists": [
|
||||
{
|
||||
"method": "StaticPairList"
|
||||
}
|
||||
],
|
||||
"telegram": {
|
||||
"enabled": false,
|
||||
"token": "",
|
||||
"chat_id": ""
|
||||
},
|
||||
"api_server": {
|
||||
"enabled": false,
|
||||
"listen_ip_address": "0.0.0.0",
|
||||
"listen_port": 8080,
|
||||
"verbosity": "error",
|
||||
"enable_openapi": true,
|
||||
"jwt_secret_key": "set-via-env-FREQTRADE__API_SERVER__JWT_SECRET_KEY",
|
||||
"ws_token": "set-via-env-FREQTRADE__API_SERVER__WS_TOKEN",
|
||||
"CORS_origins": [],
|
||||
"username": "set-via-env",
|
||||
"password": "set-via-env"
|
||||
},
|
||||
"initial_state": "running",
|
||||
"force_entry_enable": false,
|
||||
"internals": {
|
||||
"process_throttle_secs": 5
|
||||
}
|
||||
}
|
||||
80
freqtrade/user_data/config_ich.json
Normal file
80
freqtrade/user_data/config_ich.json
Normal file
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"$schema": "https://schema.freqtrade.io/schema.json",
|
||||
"bot_name": "MidasBot",
|
||||
"max_open_trades": 3,
|
||||
"stake_currency": "USDT",
|
||||
"stake_amount": "unlimited",
|
||||
"tradable_balance_ratio": 0.99,
|
||||
"fiat_display_currency": "EUR",
|
||||
"dry_run": true,
|
||||
"dry_run_wallet": 100,
|
||||
"cancel_open_orders_on_exit": false,
|
||||
"trading_mode": "futures",
|
||||
"margin_mode": "isolated",
|
||||
"timeframe": "1h",
|
||||
"unfilledtimeout": {
|
||||
"entry": 10,
|
||||
"exit": 10,
|
||||
"exit_timeout_count": 0,
|
||||
"unit": "minutes"
|
||||
},
|
||||
"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": "same",
|
||||
"use_order_book": true,
|
||||
"order_book_top": 1
|
||||
},
|
||||
"exchange": {
|
||||
"name": "binance",
|
||||
"key": "",
|
||||
"secret": "",
|
||||
"ccxt_config": {},
|
||||
"ccxt_async_config": {},
|
||||
"pair_whitelist": [
|
||||
"BTC/USDT:USDT",
|
||||
"ETH/USDT:USDT",
|
||||
"SOL/USDT:USDT",
|
||||
"BNB/USDT:USDT"
|
||||
],
|
||||
"pair_blacklist": [
|
||||
".*(BULL|BEAR|UP|DOWN)/.*",
|
||||
".*(USDC|TUSD|BUSD|DAI|FDUSD)/.*"
|
||||
]
|
||||
},
|
||||
"pairlists": [
|
||||
{
|
||||
"method": "StaticPairList"
|
||||
}
|
||||
],
|
||||
"telegram": {
|
||||
"enabled": false,
|
||||
"token": "",
|
||||
"chat_id": ""
|
||||
},
|
||||
"api_server": {
|
||||
"enabled": false,
|
||||
"listen_ip_address": "0.0.0.0",
|
||||
"listen_port": 8080,
|
||||
"verbosity": "error",
|
||||
"enable_openapi": true,
|
||||
"jwt_secret_key": "set-via-env-FREQTRADE__API_SERVER__JWT_SECRET_KEY",
|
||||
"ws_token": "set-via-env-FREQTRADE__API_SERVER__WS_TOKEN",
|
||||
"CORS_origins": [],
|
||||
"username": "set-via-env",
|
||||
"password": "set-via-env"
|
||||
},
|
||||
"initial_state": "running",
|
||||
"force_entry_enable": false,
|
||||
"internals": {
|
||||
"process_throttle_secs": 5
|
||||
}
|
||||
}
|
||||
80
freqtrade/user_data/config_ich15.json
Normal file
80
freqtrade/user_data/config_ich15.json
Normal file
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"$schema": "https://schema.freqtrade.io/schema.json",
|
||||
"bot_name": "MidasBot",
|
||||
"max_open_trades": 3,
|
||||
"stake_currency": "USDT",
|
||||
"stake_amount": "unlimited",
|
||||
"tradable_balance_ratio": 0.99,
|
||||
"fiat_display_currency": "EUR",
|
||||
"dry_run": true,
|
||||
"dry_run_wallet": 100,
|
||||
"cancel_open_orders_on_exit": false,
|
||||
"trading_mode": "futures",
|
||||
"margin_mode": "isolated",
|
||||
"timeframe": "15m",
|
||||
"unfilledtimeout": {
|
||||
"entry": 10,
|
||||
"exit": 10,
|
||||
"exit_timeout_count": 0,
|
||||
"unit": "minutes"
|
||||
},
|
||||
"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": "same",
|
||||
"use_order_book": true,
|
||||
"order_book_top": 1
|
||||
},
|
||||
"exchange": {
|
||||
"name": "binance",
|
||||
"key": "",
|
||||
"secret": "",
|
||||
"ccxt_config": {},
|
||||
"ccxt_async_config": {},
|
||||
"pair_whitelist": [
|
||||
"BTC/USDT:USDT",
|
||||
"ETH/USDT:USDT",
|
||||
"SOL/USDT:USDT",
|
||||
"BNB/USDT:USDT"
|
||||
],
|
||||
"pair_blacklist": [
|
||||
".*(BULL|BEAR|UP|DOWN)/.*",
|
||||
".*(USDC|TUSD|BUSD|DAI|FDUSD)/.*"
|
||||
]
|
||||
},
|
||||
"pairlists": [
|
||||
{
|
||||
"method": "StaticPairList"
|
||||
}
|
||||
],
|
||||
"telegram": {
|
||||
"enabled": false,
|
||||
"token": "",
|
||||
"chat_id": ""
|
||||
},
|
||||
"api_server": {
|
||||
"enabled": false,
|
||||
"listen_ip_address": "0.0.0.0",
|
||||
"listen_port": 8080,
|
||||
"verbosity": "error",
|
||||
"enable_openapi": true,
|
||||
"jwt_secret_key": "set-via-env-FREQTRADE__API_SERVER__JWT_SECRET_KEY",
|
||||
"ws_token": "set-via-env-FREQTRADE__API_SERVER__WS_TOKEN",
|
||||
"CORS_origins": [],
|
||||
"username": "set-via-env",
|
||||
"password": "set-via-env"
|
||||
},
|
||||
"initial_state": "running",
|
||||
"force_entry_enable": false,
|
||||
"internals": {
|
||||
"process_throttle_secs": 5
|
||||
}
|
||||
}
|
||||
80
freqtrade/user_data/config_live.json
Normal file
80
freqtrade/user_data/config_live.json
Normal file
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"$schema": "https://schema.freqtrade.io/schema.json",
|
||||
"bot_name": "MidasBot-Ichimoku",
|
||||
"max_open_trades": 3,
|
||||
"stake_currency": "USDT",
|
||||
"stake_amount": 100,
|
||||
"tradable_balance_ratio": 0.99,
|
||||
"fiat_display_currency": "EUR",
|
||||
"dry_run": true,
|
||||
"dry_run_wallet": 1000,
|
||||
"cancel_open_orders_on_exit": false,
|
||||
"trading_mode": "futures",
|
||||
"margin_mode": "isolated",
|
||||
"timeframe": "1h",
|
||||
"unfilledtimeout": {
|
||||
"entry": 10,
|
||||
"exit": 10,
|
||||
"exit_timeout_count": 0,
|
||||
"unit": "minutes"
|
||||
},
|
||||
"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": "same",
|
||||
"use_order_book": true,
|
||||
"order_book_top": 1
|
||||
},
|
||||
"exchange": {
|
||||
"name": "binance",
|
||||
"key": "",
|
||||
"secret": "",
|
||||
"ccxt_config": {},
|
||||
"ccxt_async_config": {},
|
||||
"pair_whitelist": [
|
||||
"BTC/USDT:USDT",
|
||||
"ETH/USDT:USDT",
|
||||
"SOL/USDT:USDT",
|
||||
"BNB/USDT:USDT"
|
||||
],
|
||||
"pair_blacklist": [
|
||||
".*(BULL|BEAR|UP|DOWN)/.*",
|
||||
".*(USDC|TUSD|BUSD|DAI|FDUSD)/.*"
|
||||
]
|
||||
},
|
||||
"pairlists": [
|
||||
{
|
||||
"method": "StaticPairList"
|
||||
}
|
||||
],
|
||||
"telegram": {
|
||||
"enabled": false,
|
||||
"token": "",
|
||||
"chat_id": ""
|
||||
},
|
||||
"api_server": {
|
||||
"enabled": true,
|
||||
"listen_ip_address": "0.0.0.0",
|
||||
"listen_port": 8080,
|
||||
"verbosity": "error",
|
||||
"enable_openapi": true,
|
||||
"jwt_secret_key": "set-via-env-FREQTRADE__API_SERVER__JWT_SECRET_KEY",
|
||||
"ws_token": "set-via-env-FREQTRADE__API_SERVER__WS_TOKEN",
|
||||
"CORS_origins": [],
|
||||
"username": "set-via-env",
|
||||
"password": "set-via-env"
|
||||
},
|
||||
"initial_state": "running",
|
||||
"force_entry_enable": false,
|
||||
"internals": {
|
||||
"process_throttle_secs": 5
|
||||
}
|
||||
}
|
||||
110
freqtrade/user_data/config_nfi.json
Normal file
110
freqtrade/user_data/config_nfi.json
Normal file
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"$schema": "https://schema.freqtrade.io/schema.json",
|
||||
"bot_name": "MidasBot",
|
||||
"max_open_trades": 6,
|
||||
"stake_currency": "USDT",
|
||||
"stake_amount": "unlimited",
|
||||
"tradable_balance_ratio": 0.99,
|
||||
"fiat_display_currency": "EUR",
|
||||
"dry_run": true,
|
||||
"dry_run_wallet": 1000,
|
||||
"cancel_open_orders_on_exit": false,
|
||||
"trading_mode": "spot",
|
||||
"margin_mode": "",
|
||||
"timeframe": "5m",
|
||||
"unfilledtimeout": {
|
||||
"entry": 10,
|
||||
"exit": 10,
|
||||
"exit_timeout_count": 0,
|
||||
"unit": "minutes"
|
||||
},
|
||||
"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": "same",
|
||||
"use_order_book": true,
|
||||
"order_book_top": 1
|
||||
},
|
||||
"exchange": {
|
||||
"name": "binance",
|
||||
"key": "",
|
||||
"secret": "",
|
||||
"ccxt_config": {},
|
||||
"ccxt_async_config": {},
|
||||
"pair_whitelist": [
|
||||
"BTC/USDT",
|
||||
"ETH/USDT",
|
||||
"SOL/USDT",
|
||||
"BNB/USDT",
|
||||
"XRP/USDT",
|
||||
"ADA/USDT",
|
||||
"AVAX/USDT",
|
||||
"DOGE/USDT",
|
||||
"LINK/USDT",
|
||||
"DOT/USDT",
|
||||
"LTC/USDT",
|
||||
"TRX/USDT",
|
||||
"ATOM/USDT",
|
||||
"NEAR/USDT",
|
||||
"APT/USDT",
|
||||
"ARB/USDT",
|
||||
"OP/USDT",
|
||||
"FIL/USDT",
|
||||
"INJ/USDT",
|
||||
"SUI/USDT",
|
||||
"UNI/USDT",
|
||||
"AAVE/USDT",
|
||||
"ETC/USDT",
|
||||
"XLM/USDT",
|
||||
"ALGO/USDT",
|
||||
"VET/USDT",
|
||||
"HBAR/USDT",
|
||||
"RUNE/USDT",
|
||||
"SAND/USDT",
|
||||
"GALA/USDT"
|
||||
],
|
||||
"pair_blacklist": [
|
||||
".*(BULL|BEAR|UP|DOWN)/.*",
|
||||
".*(USDC|TUSD|BUSD|DAI|FDUSD)/.*"
|
||||
]
|
||||
},
|
||||
"pairlists": [
|
||||
{
|
||||
"method": "StaticPairList"
|
||||
}
|
||||
],
|
||||
"telegram": {
|
||||
"enabled": false,
|
||||
"token": "",
|
||||
"chat_id": ""
|
||||
},
|
||||
"api_server": {
|
||||
"enabled": false,
|
||||
"listen_ip_address": "0.0.0.0",
|
||||
"listen_port": 8080,
|
||||
"verbosity": "error",
|
||||
"enable_openapi": true,
|
||||
"jwt_secret_key": "set-via-env-FREQTRADE__API_SERVER__JWT_SECRET_KEY",
|
||||
"ws_token": "set-via-env-FREQTRADE__API_SERVER__WS_TOKEN",
|
||||
"CORS_origins": [],
|
||||
"username": "set-via-env",
|
||||
"password": "set-via-env"
|
||||
},
|
||||
"initial_state": "running",
|
||||
"force_entry_enable": false,
|
||||
"internals": {
|
||||
"process_throttle_secs": 5
|
||||
},
|
||||
"use_exit_signal": true,
|
||||
"exit_profit_only": false,
|
||||
"ignore_roi_if_entry_signal": true,
|
||||
"position_adjustment_enable": true
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"AAVE/USDT":{"rate":73.39,"profit":-0.06942172132549185,"sell_reason":"exit_long_tc_stoploss_doom","time_profit_reached":"2026-06-23T14:45:00+00:00"},"NEAR/USDT":{"rate":1.996,"profit":-0.04442309198882848,"sell_reason":"exit_long_tc_stoploss_doom","time_profit_reached":"2026-06-23T14:45:00+00:00"}}
|
||||
171
freqtrade/user_data/strategies/AiBiasStrategy.py
Normal file
171
freqtrade/user_data/strategies/AiBiasStrategy.py
Normal file
@@ -0,0 +1,171 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, too-few-public-methods
|
||||
"""
|
||||
AiBiasStrategy — stratégie directionnelle pilotée par le biais IA (Phase 3).
|
||||
|
||||
Base technique : croisement EMA + RSI (comme SampleStrategy).
|
||||
Surcouche IA : un biais de marché produit par Claude (service ai_analyzer) est lu
|
||||
depuis Redis et filtre/renforce les entrées.
|
||||
|
||||
- Live / dry-run : lit le biais COURANT de la paire à chaque nouvelle bougie.
|
||||
- Backtest : Redis ne contient pas d'historique de biais -> repli `neutral` (la
|
||||
surcouche IA est neutralisée, seule la base technique joue). Backtester finement
|
||||
l'IA nécessiterait d'enregistrer les biais historiques (amélioration future).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.enums import RunMode
|
||||
from freqtrade.strategy import IStrategy
|
||||
|
||||
# Répertoire de l'historique des biais (monté dans le conteneur freqtrade).
|
||||
_HISTORY_DIR = os.environ.get(
|
||||
"AI_HISTORY_DIR", "/freqtrade/user_data/ai_bias_history"
|
||||
)
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _redis_client():
|
||||
"""Client Redis paresseux et tolérant aux pannes (None si indisponible)."""
|
||||
try:
|
||||
import redis # importé ici pour ne pas casser si le paquet manque
|
||||
|
||||
url = os.environ.get("REDIS_URL", "redis://redis:6379/0")
|
||||
client = redis.Redis.from_url(
|
||||
url, decode_responses=True, socket_connect_timeout=1, socket_timeout=1
|
||||
)
|
||||
client.ping()
|
||||
return client
|
||||
except Exception: # noqa: BLE001
|
||||
return None
|
||||
|
||||
|
||||
def _read_bias(pair: str) -> dict:
|
||||
"""Renvoie {'direction', 'confidence'} COURANT pour la paire (live/dry-run)."""
|
||||
client = _redis_client()
|
||||
if client is None:
|
||||
return {"direction": "neutral", "confidence": 0.0}
|
||||
try:
|
||||
raw = client.get(f"bias:{pair}")
|
||||
if not raw:
|
||||
return {"direction": "neutral", "confidence": 0.0}
|
||||
data = json.loads(raw)
|
||||
return {
|
||||
"direction": data.get("direction", "neutral"),
|
||||
"confidence": float(data.get("confidence", 0.0)),
|
||||
}
|
||||
except Exception: # noqa: BLE001
|
||||
return {"direction": "neutral", "confidence": 0.0}
|
||||
|
||||
|
||||
def _load_history(pair: str) -> pd.DataFrame:
|
||||
"""Charge l'historique horodaté des biais d'une paire (vide si absent)."""
|
||||
path = Path(_HISTORY_DIR) / f"{pair.replace('/', '_')}.csv"
|
||||
if not path.exists():
|
||||
return pd.DataFrame(columns=["timestamp", "direction", "confidence"])
|
||||
try:
|
||||
hist = pd.read_csv(path)
|
||||
hist["timestamp"] = pd.to_datetime(hist["timestamp"], utc=True)
|
||||
hist["confidence"] = pd.to_numeric(hist["confidence"], errors="coerce").fillna(0.0)
|
||||
return hist.sort_values("timestamp").reset_index(drop=True)
|
||||
except Exception: # noqa: BLE001 — historique corrompu : on l'ignore
|
||||
return pd.DataFrame(columns=["timestamp", "direction", "confidence"])
|
||||
|
||||
|
||||
def _merge_history(dataframe: DataFrame, pair: str) -> DataFrame:
|
||||
"""Associe à chaque bougie le dernier biais connu À CETTE DATE (merge_asof)."""
|
||||
hist = _load_history(pair)
|
||||
if hist.empty:
|
||||
dataframe["ai_direction"] = "neutral"
|
||||
dataframe["ai_confidence"] = 0.0
|
||||
return dataframe
|
||||
# Aligner la résolution temporelle (Freqtrade=ms, pandas parse=us) sinon merge_asof échoue.
|
||||
hist = hist.copy()
|
||||
hist["timestamp"] = hist["timestamp"].astype(dataframe["date"].dtype)
|
||||
# Le dataframe Freqtrade est déjà trié par date croissante (prérequis merge_asof).
|
||||
merged = pd.merge_asof(
|
||||
dataframe[["date"]],
|
||||
hist[["timestamp", "direction", "confidence"]],
|
||||
left_on="date",
|
||||
right_on="timestamp",
|
||||
direction="backward",
|
||||
)
|
||||
dataframe["ai_direction"] = merged["direction"].fillna("neutral").values
|
||||
dataframe["ai_confidence"] = merged["confidence"].fillna(0.0).values
|
||||
return dataframe
|
||||
|
||||
|
||||
class AiBiasStrategy(IStrategy):
|
||||
INTERFACE_VERSION = 3
|
||||
|
||||
timeframe = "1h"
|
||||
|
||||
minimal_roi = {"0": 0.05, "120": 0.03, "360": 0.01, "720": 0}
|
||||
stoploss = -0.10
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.02
|
||||
trailing_stop_positive_offset = 0.03
|
||||
trailing_only_offset_is_reached = True
|
||||
|
||||
startup_candle_count: int = 50
|
||||
process_only_new_candles = True
|
||||
use_exit_signal = True
|
||||
|
||||
# Confiance minimale du biais IA pour qu'il influence les décisions.
|
||||
ai_min_confidence: float = float(os.environ.get("AI_MIN_CONFIDENCE", "0.6"))
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe["ema_fast"] = ta.EMA(dataframe, timeperiod=9)
|
||||
dataframe["ema_slow"] = ta.EMA(dataframe, timeperiod=21)
|
||||
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
|
||||
|
||||
# Source du biais IA selon le mode :
|
||||
# - backtest/hyperopt/plot : historique horodaté (biais valide à chaque bougie)
|
||||
# - dry-run/live : biais COURANT depuis Redis (dernière bougie)
|
||||
runmode = self.dp.runmode if self.dp is not None else RunMode.OTHER
|
||||
if runmode in (RunMode.BACKTEST, RunMode.HYPEROPT, RunMode.PLOT):
|
||||
dataframe = _merge_history(dataframe, metadata["pair"])
|
||||
else:
|
||||
bias = _read_bias(metadata["pair"])
|
||||
dataframe["ai_direction"] = bias["direction"]
|
||||
dataframe["ai_confidence"] = bias["confidence"]
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
base_long = (
|
||||
(dataframe["ema_fast"] > dataframe["ema_slow"])
|
||||
& (dataframe["ema_fast"].shift(1) <= dataframe["ema_slow"].shift(1))
|
||||
& (dataframe["rsi"] < 70)
|
||||
& (dataframe["volume"] > 0)
|
||||
)
|
||||
|
||||
# Filtre IA : si le biais est suffisamment confiant, on bloque les longs
|
||||
# quand il est baissier ; on les laisse passer sinon (neutre/haussier).
|
||||
ai_blocks_long = (dataframe["ai_direction"] == "bearish") & (
|
||||
dataframe["ai_confidence"] >= self.ai_min_confidence
|
||||
)
|
||||
|
||||
dataframe.loc[base_long & (~ai_blocks_long), "enter_long"] = 1
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
base_exit = (
|
||||
(dataframe["ema_fast"] < dataframe["ema_slow"])
|
||||
& (dataframe["ema_fast"].shift(1) >= dataframe["ema_slow"].shift(1))
|
||||
& (dataframe["volume"] > 0)
|
||||
)
|
||||
|
||||
# Sortie anticipée si l'IA devient nettement baissière.
|
||||
ai_exit = (dataframe["ai_direction"] == "bearish") & (
|
||||
dataframe["ai_confidence"] >= self.ai_min_confidence
|
||||
)
|
||||
|
||||
dataframe.loc[base_exit | ai_exit, "exit_long"] = 1
|
||||
return dataframe
|
||||
82
freqtrade/user_data/strategies/BBMeanRev.py
Normal file
82
freqtrade/user_data/strategies/BBMeanRev.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, too-few-public-methods
|
||||
"""
|
||||
BBMeanRev — 2e moteur : mean-reversion (Bollinger + RSI), long/short futures.
|
||||
|
||||
Logique CONTRAIRE au trend-follower (Ichimoku) → décorrélée par construction :
|
||||
LONG quand le prix casse SOUS la bande basse + RSI survendu (rebond attendu).
|
||||
SHORT quand le prix casse AU-DESSUS de la bande haute + RSI suracheté (repli attendu).
|
||||
Sortie : retour à la moyenne (bande médiane) + ROI/stop.
|
||||
|
||||
But : gagner dans les marchés en range (là où l'Ichimoku perd), pour qu'en
|
||||
combinaison le ratio rendement/risque monte. Paramétrable pour hyperopt.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy import IStrategy, IntParameter
|
||||
|
||||
|
||||
class BBMeanRev(IStrategy):
|
||||
INTERFACE_VERSION = 3
|
||||
timeframe = "1h"
|
||||
can_short = True
|
||||
|
||||
# Mean-reversion = sorties rapides ; valeurs par défaut, surchargées par hyperopt.
|
||||
minimal_roi = {"0": 0.03, "60": 0.02, "180": 0.01, "360": 0}
|
||||
stoploss = -0.05
|
||||
trailing_stop = False
|
||||
|
||||
startup_candle_count: int = 50
|
||||
process_only_new_candles = True
|
||||
use_exit_signal = True
|
||||
|
||||
buy_rsi = IntParameter(10, 40, default=30, space="buy", optimize=True)
|
||||
sell_rsi = IntParameter(60, 90, default=70, space="sell", optimize=True)
|
||||
|
||||
def leverage(self, pair, current_time, current_rate, proposed_leverage,
|
||||
max_leverage, entry_tag, side, **kwargs) -> float:
|
||||
return 1.0
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
bb = ta.BBANDS(dataframe, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
|
||||
dataframe["bb_lower"] = bb["lowerband"]
|
||||
dataframe["bb_mid"] = bb["middleband"]
|
||||
dataframe["bb_upper"] = bb["upperband"]
|
||||
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
# LONG : survente (RSI bas) dans la zone basse des bandes
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe["rsi"] < self.buy_rsi.value)
|
||||
& (dataframe["close"] < dataframe["bb_mid"])
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"enter_long",
|
||||
] = 1
|
||||
# SHORT : surchauffe (RSI haut) dans la zone haute des bandes
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe["rsi"] > self.sell_rsi.value)
|
||||
& (dataframe["close"] > dataframe["bb_mid"])
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"enter_short",
|
||||
] = 1
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
# Sortie LONG : RSI revenu vers la moyenne (réversion réalisée)
|
||||
dataframe.loc[
|
||||
((dataframe["rsi"] > 50) & (dataframe["volume"] > 0)),
|
||||
"exit_long",
|
||||
] = 1
|
||||
# Sortie SHORT : RSI revenu vers la moyenne
|
||||
dataframe.loc[
|
||||
((dataframe["rsi"] < 50) & (dataframe["volume"] > 0)),
|
||||
"exit_short",
|
||||
] = 1
|
||||
return dataframe
|
||||
34
freqtrade/user_data/strategies/HyperStrategy.json
Normal file
34
freqtrade/user_data/strategies/HyperStrategy.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"strategy_name": "HyperStrategy",
|
||||
"params": {
|
||||
"max_open_trades": {
|
||||
"max_open_trades": 3
|
||||
},
|
||||
"buy": {
|
||||
"buy_adx_min": 25,
|
||||
"buy_require_macd": true,
|
||||
"buy_require_trend": false,
|
||||
"buy_rsi_max": 78
|
||||
},
|
||||
"sell": {
|
||||
"sell_rsi_min": 47
|
||||
},
|
||||
"roi": {
|
||||
"0": 0.459,
|
||||
"187": 0.06,
|
||||
"693": 0.032,
|
||||
"1425": 0
|
||||
},
|
||||
"stoploss": {
|
||||
"stoploss": -0.137
|
||||
},
|
||||
"trailing": {
|
||||
"trailing_stop": true,
|
||||
"trailing_stop_positive": 0.016,
|
||||
"trailing_stop_positive_offset": 0.053,
|
||||
"trailing_only_offset_is_reached": true
|
||||
}
|
||||
},
|
||||
"ft_stratparam_v": 1,
|
||||
"export_time": "2026-06-23 14:08:47.078605+00:00"
|
||||
}
|
||||
82
freqtrade/user_data/strategies/HyperStrategy.py
Normal file
82
freqtrade/user_data/strategies/HyperStrategy.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, too-few-public-methods
|
||||
"""
|
||||
HyperStrategy — stratégie paramétrable pour optimisation (hyperopt).
|
||||
|
||||
Indicateurs fixes (EMA/RSI/ADX/MACD), conditions d'entrée et gestion de sortie
|
||||
PARAMÉTRÉES → Freqtrade peut optimiser les seuils, le ROI, le stoploss et le trailing.
|
||||
Méthode honnête : on optimise sur une période d'entraînement puis on VALIDE sur une
|
||||
période hors-échantillon (test) pour détecter le sur-apprentissage.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy import (
|
||||
IStrategy,
|
||||
IntParameter,
|
||||
BooleanParameter,
|
||||
)
|
||||
|
||||
|
||||
class HyperStrategy(IStrategy):
|
||||
INTERFACE_VERSION = 3
|
||||
timeframe = "1h"
|
||||
|
||||
# Valeurs par défaut — surchargées par les résultats d'hyperopt.
|
||||
minimal_roi = {"0": 0.10, "240": 0.05, "720": 0.02, "1440": 0}
|
||||
stoploss = -0.08
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.02
|
||||
trailing_stop_positive_offset = 0.04
|
||||
trailing_only_offset_is_reached = True
|
||||
|
||||
startup_candle_count: int = 60
|
||||
process_only_new_candles = True
|
||||
use_exit_signal = True
|
||||
|
||||
# --- Paramètres optimisables (espace "buy") ---
|
||||
buy_rsi_max = IntParameter(60, 80, default=70, space="buy", optimize=True)
|
||||
buy_adx_min = IntParameter(15, 40, default=25, space="buy", optimize=True)
|
||||
buy_require_trend = BooleanParameter(default=True, space="buy", optimize=True)
|
||||
buy_require_macd = BooleanParameter(default=True, space="buy", optimize=True)
|
||||
|
||||
# --- Paramètres optimisables (espace "sell") ---
|
||||
sell_rsi_min = IntParameter(20, 50, default=35, space="sell", optimize=True)
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe["ema_fast"] = ta.EMA(dataframe, timeperiod=9)
|
||||
dataframe["ema_slow"] = ta.EMA(dataframe, timeperiod=21)
|
||||
dataframe["ema_trend"] = ta.EMA(dataframe, timeperiod=50)
|
||||
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
|
||||
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
|
||||
macd = ta.MACD(dataframe)
|
||||
dataframe["macd"] = macd["macd"]
|
||||
dataframe["macdsignal"] = macd["macdsignal"]
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
cond = (
|
||||
(dataframe["ema_fast"] > dataframe["ema_slow"])
|
||||
& (dataframe["ema_fast"].shift(1) <= dataframe["ema_slow"].shift(1))
|
||||
& (dataframe["rsi"] < self.buy_rsi_max.value)
|
||||
& (dataframe["adx"] > self.buy_adx_min.value)
|
||||
& (dataframe["volume"] > 0)
|
||||
)
|
||||
if self.buy_require_trend.value:
|
||||
cond &= dataframe["close"] > dataframe["ema_trend"]
|
||||
if self.buy_require_macd.value:
|
||||
cond &= dataframe["macd"] > dataframe["macdsignal"]
|
||||
dataframe.loc[cond, "enter_long"] = 1
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe["ema_fast"] < dataframe["ema_slow"])
|
||||
& (dataframe["rsi"] < self.sell_rsi_min.value)
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"exit_long",
|
||||
] = 1
|
||||
return dataframe
|
||||
31
freqtrade/user_data/strategies/IchimokuChop.json
Normal file
31
freqtrade/user_data/strategies/IchimokuChop.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"strategy_name": "IchimokuChop",
|
||||
"params": {
|
||||
"max_open_trades": {
|
||||
"max_open_trades": 3
|
||||
},
|
||||
"buy": {
|
||||
"buy_adx_min": 30,
|
||||
"buy_chop_max": 43,
|
||||
"buy_cloud_min_pct": 0.57,
|
||||
"require_tk_cross": false
|
||||
},
|
||||
"roi": {
|
||||
"0": 0.207,
|
||||
"345": 0.144,
|
||||
"1052": 0.048,
|
||||
"2483": 0
|
||||
},
|
||||
"stoploss": {
|
||||
"stoploss": -0.236
|
||||
},
|
||||
"trailing": {
|
||||
"trailing_stop": true,
|
||||
"trailing_stop_positive": 0.099,
|
||||
"trailing_stop_positive_offset": 0.162,
|
||||
"trailing_only_offset_is_reached": false
|
||||
}
|
||||
},
|
||||
"ft_stratparam_v": 1,
|
||||
"export_time": "2026-06-23 16:58:02.232138+00:00"
|
||||
}
|
||||
115
freqtrade/user_data/strategies/IchimokuChop.py
Normal file
115
freqtrade/user_data/strategies/IchimokuChop.py
Normal file
@@ -0,0 +1,115 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, too-few-public-methods
|
||||
"""
|
||||
IchimokuChop — IchimokuHyper + FILTRE DE RÉGIME (Choppiness Index).
|
||||
|
||||
Hypothèse : les folds perdants du walk-forward sont des marchés en RANGE (chop),
|
||||
où un trend-follower se fait whipsaw. On ajoute un filtre Choppiness Index :
|
||||
- CI élevé (>~61) = consolidation/range → on NE trade PAS.
|
||||
- CI bas (<~38) = tendance forte → on trade.
|
||||
Le seuil est optimisable. On valide ensuite en WALK-FORWARD (pas en hindsight).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy import (
|
||||
IStrategy,
|
||||
IntParameter,
|
||||
DecimalParameter,
|
||||
BooleanParameter,
|
||||
)
|
||||
|
||||
|
||||
class IchimokuChop(IStrategy):
|
||||
INTERFACE_VERSION = 3
|
||||
timeframe = "1h"
|
||||
can_short = True
|
||||
|
||||
minimal_roi = {"0": 0.08, "240": 0.04, "720": 0.02, "1440": 0}
|
||||
stoploss = -0.08
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.02
|
||||
trailing_stop_positive_offset = 0.03
|
||||
trailing_only_offset_is_reached = True
|
||||
|
||||
startup_candle_count: int = 220
|
||||
process_only_new_candles = True
|
||||
use_exit_signal = True
|
||||
|
||||
buy_adx_min = IntParameter(15, 40, default=25, space="buy", optimize=True)
|
||||
buy_cloud_min_pct = DecimalParameter(0.0, 2.0, default=0.3, decimals=2, space="buy", optimize=True)
|
||||
require_tk_cross = BooleanParameter(default=False, space="buy", optimize=True)
|
||||
# Filtre de régime : ne trade que si Choppiness Index < seuil (= marché qui tend)
|
||||
buy_chop_max = IntParameter(35, 70, default=61, space="buy", optimize=True)
|
||||
|
||||
def leverage(self, pair, current_time, current_rate, proposed_leverage,
|
||||
max_leverage, entry_tag, side, **kwargs) -> float:
|
||||
return 1.0
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
high, low, close = dataframe["high"], dataframe["low"], dataframe["close"]
|
||||
tenkan = (high.rolling(9).max() + low.rolling(9).min()) / 2
|
||||
kijun = (high.rolling(26).max() + low.rolling(26).min()) / 2
|
||||
dataframe["tenkan"] = tenkan
|
||||
dataframe["kijun"] = kijun
|
||||
dataframe["senkou_a"] = ((tenkan + kijun) / 2).shift(26)
|
||||
dataframe["senkou_b"] = ((high.rolling(52).max() + low.rolling(52).min()) / 2).shift(26)
|
||||
dataframe["cloud_top"] = dataframe[["senkou_a", "senkou_b"]].max(axis=1)
|
||||
dataframe["cloud_bot"] = dataframe[["senkou_a", "senkou_b"]].min(axis=1)
|
||||
dataframe["cloud_width_pct"] = (dataframe["cloud_top"] - dataframe["cloud_bot"]) / close * 100
|
||||
dataframe["close_prev26"] = close.shift(26)
|
||||
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
|
||||
|
||||
# --- Choppiness Index (14) ---
|
||||
n = 14
|
||||
atr1 = ta.ATR(dataframe, timeperiod=1)
|
||||
tr_sum = atr1.rolling(n).sum()
|
||||
rng = high.rolling(n).max() - low.rolling(n).min()
|
||||
dataframe["chop"] = 100 * np.log10(tr_sum / rng) / np.log10(n)
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
adx_min = self.buy_adx_min.value
|
||||
cloud_min = self.buy_cloud_min_pct.value
|
||||
not_choppy = dataframe["chop"] < self.buy_chop_max.value # ← filtre régime
|
||||
|
||||
long_cond = (
|
||||
(dataframe["close"] > dataframe["cloud_top"])
|
||||
& (dataframe["tenkan"] > dataframe["kijun"])
|
||||
& (dataframe["close"] > dataframe["close_prev26"])
|
||||
& (dataframe["adx"] > adx_min)
|
||||
& (dataframe["cloud_width_pct"] > cloud_min)
|
||||
& not_choppy
|
||||
& (dataframe["volume"] > 0)
|
||||
)
|
||||
short_cond = (
|
||||
(dataframe["close"] < dataframe["cloud_bot"])
|
||||
& (dataframe["tenkan"] < dataframe["kijun"])
|
||||
& (dataframe["close"] < dataframe["close_prev26"])
|
||||
& (dataframe["adx"] > adx_min)
|
||||
& (dataframe["cloud_width_pct"] > cloud_min)
|
||||
& not_choppy
|
||||
& (dataframe["volume"] > 0)
|
||||
)
|
||||
if self.require_tk_cross.value:
|
||||
long_cond &= dataframe["tenkan"].shift(1) <= dataframe["kijun"].shift(1)
|
||||
short_cond &= dataframe["tenkan"].shift(1) >= dataframe["kijun"].shift(1)
|
||||
|
||||
dataframe.loc[long_cond, "enter_long"] = 1
|
||||
dataframe.loc[short_cond, "enter_short"] = 1
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(((dataframe["tenkan"] < dataframe["kijun"]) | (dataframe["close"] < dataframe["cloud_bot"]))
|
||||
& (dataframe["volume"] > 0)),
|
||||
"exit_long",
|
||||
] = 1
|
||||
dataframe.loc[
|
||||
(((dataframe["tenkan"] > dataframe["kijun"]) | (dataframe["close"] > dataframe["cloud_top"]))
|
||||
& (dataframe["volume"] > 0)),
|
||||
"exit_short",
|
||||
] = 1
|
||||
return dataframe
|
||||
30
freqtrade/user_data/strategies/IchimokuHyper.best.json
Normal file
30
freqtrade/user_data/strategies/IchimokuHyper.best.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"strategy_name": "IchimokuHyper",
|
||||
"params": {
|
||||
"max_open_trades": {
|
||||
"max_open_trades": 3
|
||||
},
|
||||
"buy": {
|
||||
"buy_adx_min": 29,
|
||||
"buy_cloud_min_pct": 0.4,
|
||||
"require_tk_cross": false
|
||||
},
|
||||
"roi": {
|
||||
"0": 0.139,
|
||||
"206": 0.071,
|
||||
"326": 0.025,
|
||||
"1563": 0
|
||||
},
|
||||
"stoploss": {
|
||||
"stoploss": -0.299
|
||||
},
|
||||
"trailing": {
|
||||
"trailing_stop": true,
|
||||
"trailing_stop_positive": 0.014,
|
||||
"trailing_stop_positive_offset": 0.042,
|
||||
"trailing_only_offset_is_reached": true
|
||||
}
|
||||
},
|
||||
"ft_stratparam_v": 1,
|
||||
"export_time": "2026-06-23 15:52:37.295448+00:00"
|
||||
}
|
||||
30
freqtrade/user_data/strategies/IchimokuHyper.json
Normal file
30
freqtrade/user_data/strategies/IchimokuHyper.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"strategy_name": "IchimokuHyper",
|
||||
"params": {
|
||||
"max_open_trades": {
|
||||
"max_open_trades": 3
|
||||
},
|
||||
"buy": {
|
||||
"buy_adx_min": 30,
|
||||
"buy_cloud_min_pct": 1.37,
|
||||
"require_tk_cross": false
|
||||
},
|
||||
"roi": {
|
||||
"0": 0.465,
|
||||
"302": 0.171,
|
||||
"728": 0.08,
|
||||
"2097": 0
|
||||
},
|
||||
"stoploss": {
|
||||
"stoploss": -0.174
|
||||
},
|
||||
"trailing": {
|
||||
"trailing_stop": true,
|
||||
"trailing_stop_positive": 0.043,
|
||||
"trailing_stop_positive_offset": 0.115,
|
||||
"trailing_only_offset_is_reached": true
|
||||
}
|
||||
},
|
||||
"ft_stratparam_v": 1,
|
||||
"export_time": "2026-06-23 16:43:16.595085+00:00"
|
||||
}
|
||||
118
freqtrade/user_data/strategies/IchimokuHyper.py
Normal file
118
freqtrade/user_data/strategies/IchimokuHyper.py
Normal file
@@ -0,0 +1,118 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, too-few-public-methods
|
||||
"""
|
||||
IchimokuHyper — version paramétrable d'IchimokuStrategy pour hyperopt (futures, long/short).
|
||||
|
||||
Paramètres optimisables :
|
||||
- buy_adx_min : force de tendance minimale
|
||||
- buy_cloud_min_pct : largeur minimale du nuage (anti-chop) en % du prix
|
||||
- require_tk_cross : exiger le croisement Tenkan/Kijun (sinon simple position vs nuage)
|
||||
+ espaces ROI / stoploss / trailing.
|
||||
|
||||
Anti-lookahead : Senkou décalés de +26 ; confirmation via close vs close[-26].
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy import (
|
||||
IStrategy,
|
||||
IntParameter,
|
||||
DecimalParameter,
|
||||
BooleanParameter,
|
||||
)
|
||||
|
||||
|
||||
class IchimokuHyper(IStrategy):
|
||||
INTERFACE_VERSION = 3
|
||||
timeframe = "1h"
|
||||
can_short = True
|
||||
|
||||
minimal_roi = {"0": 0.06, "240": 0.03, "720": 0.01, "1440": 0}
|
||||
stoploss = -0.08
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.025
|
||||
trailing_stop_positive_offset = 0.04
|
||||
trailing_only_offset_is_reached = True
|
||||
|
||||
startup_candle_count: int = 120
|
||||
process_only_new_candles = True
|
||||
use_exit_signal = True
|
||||
|
||||
# --- Paramètres optimisables (espace "buy", appliqués long ET short) ---
|
||||
buy_adx_min = IntParameter(15, 40, default=20, space="buy", optimize=True)
|
||||
buy_cloud_min_pct = DecimalParameter(
|
||||
0.0, 2.0, default=0.0, decimals=2, space="buy", optimize=True
|
||||
)
|
||||
require_tk_cross = BooleanParameter(default=True, space="buy", optimize=True)
|
||||
|
||||
def leverage(self, pair, current_time, current_rate, proposed_leverage,
|
||||
max_leverage, entry_tag, side, **kwargs) -> float:
|
||||
return 1.0
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
high, low, close = dataframe["high"], dataframe["low"], dataframe["close"]
|
||||
tenkan = (high.rolling(9).max() + low.rolling(9).min()) / 2
|
||||
kijun = (high.rolling(26).max() + low.rolling(26).min()) / 2
|
||||
dataframe["tenkan"] = tenkan
|
||||
dataframe["kijun"] = kijun
|
||||
dataframe["senkou_a"] = ((tenkan + kijun) / 2).shift(26)
|
||||
dataframe["senkou_b"] = (
|
||||
(high.rolling(52).max() + low.rolling(52).min()) / 2
|
||||
).shift(26)
|
||||
dataframe["cloud_top"] = dataframe[["senkou_a", "senkou_b"]].max(axis=1)
|
||||
dataframe["cloud_bot"] = dataframe[["senkou_a", "senkou_b"]].min(axis=1)
|
||||
# Largeur du nuage en % du prix (filtre anti-chop)
|
||||
dataframe["cloud_width_pct"] = (
|
||||
(dataframe["cloud_top"] - dataframe["cloud_bot"]) / close * 100
|
||||
)
|
||||
dataframe["close_prev26"] = close.shift(26)
|
||||
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
adx_min = self.buy_adx_min.value
|
||||
cloud_min = self.buy_cloud_min_pct.value
|
||||
|
||||
long_cond = (
|
||||
(dataframe["close"] > dataframe["cloud_top"])
|
||||
& (dataframe["tenkan"] > dataframe["kijun"])
|
||||
& (dataframe["close"] > dataframe["close_prev26"])
|
||||
& (dataframe["adx"] > adx_min)
|
||||
& (dataframe["cloud_width_pct"] > cloud_min)
|
||||
& (dataframe["volume"] > 0)
|
||||
)
|
||||
short_cond = (
|
||||
(dataframe["close"] < dataframe["cloud_bot"])
|
||||
& (dataframe["tenkan"] < dataframe["kijun"])
|
||||
& (dataframe["close"] < dataframe["close_prev26"])
|
||||
& (dataframe["adx"] > adx_min)
|
||||
& (dataframe["cloud_width_pct"] > cloud_min)
|
||||
& (dataframe["volume"] > 0)
|
||||
)
|
||||
if self.require_tk_cross.value:
|
||||
long_cond &= dataframe["tenkan"].shift(1) <= dataframe["kijun"].shift(1)
|
||||
short_cond &= dataframe["tenkan"].shift(1) >= dataframe["kijun"].shift(1)
|
||||
|
||||
dataframe.loc[long_cond, "enter_long"] = 1
|
||||
dataframe.loc[short_cond, "enter_short"] = 1
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
((dataframe["tenkan"] < dataframe["kijun"])
|
||||
| (dataframe["close"] < dataframe["cloud_bot"]))
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"exit_long",
|
||||
] = 1
|
||||
dataframe.loc[
|
||||
(
|
||||
((dataframe["tenkan"] > dataframe["kijun"])
|
||||
| (dataframe["close"] > dataframe["cloud_top"]))
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"exit_short",
|
||||
] = 1
|
||||
return dataframe
|
||||
31
freqtrade/user_data/strategies/IchimokuHyper2.json
Normal file
31
freqtrade/user_data/strategies/IchimokuHyper2.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"strategy_name": "IchimokuHyper2",
|
||||
"params": {
|
||||
"max_open_trades": {
|
||||
"max_open_trades": 3
|
||||
},
|
||||
"buy": {
|
||||
"buy_adx_min": 31,
|
||||
"buy_cloud_min_pct": 0.48,
|
||||
"require_tk_cross": true,
|
||||
"use_macro": false
|
||||
},
|
||||
"roi": {
|
||||
"0": 0.088,
|
||||
"20": 0.066,
|
||||
"63": 0.053,
|
||||
"180": 0
|
||||
},
|
||||
"stoploss": {
|
||||
"stoploss": -0.095
|
||||
},
|
||||
"trailing": {
|
||||
"trailing_stop": true,
|
||||
"trailing_stop_positive": 0.046,
|
||||
"trailing_stop_positive_offset": 0.08,
|
||||
"trailing_only_offset_is_reached": true
|
||||
}
|
||||
},
|
||||
"ft_stratparam_v": 1,
|
||||
"export_time": "2026-06-23 15:17:56.904527+00:00"
|
||||
}
|
||||
158
freqtrade/user_data/strategies/IchimokuHyper2.py
Normal file
158
freqtrade/user_data/strategies/IchimokuHyper2.py
Normal file
@@ -0,0 +1,158 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, too-few-public-methods
|
||||
"""
|
||||
IchimokuHyper2 — Ichimoku long/short hyperoptable, AVEC contraintes de risque.
|
||||
|
||||
Améliorations vs IchimokuHyper :
|
||||
- Filtre macro EMA200 optimisable (use_macro).
|
||||
- STOP-LOSS CONTRAINT à une plage réaliste [-10% .. -2%] (le -23% précédent était le
|
||||
vrai point faible : trop large pour envisager le levier).
|
||||
- ROI plafonné à des valeurs réalistes.
|
||||
Optimisation orientée robustesse (Sharpe) + train/test strict pour éviter l'overfit.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy import (
|
||||
IStrategy,
|
||||
IntParameter,
|
||||
DecimalParameter,
|
||||
BooleanParameter,
|
||||
)
|
||||
from freqtrade.optimize.space import Categorical, Dimension, Integer, SKDecimal
|
||||
|
||||
|
||||
class IchimokuHyper2(IStrategy):
|
||||
INTERFACE_VERSION = 3
|
||||
timeframe = "1h"
|
||||
can_short = True
|
||||
|
||||
# Défauts (surchargés par hyperopt)
|
||||
minimal_roi = {"0": 0.08, "240": 0.04, "720": 0.02, "1440": 0}
|
||||
stoploss = -0.06
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.02
|
||||
trailing_stop_positive_offset = 0.03
|
||||
trailing_only_offset_is_reached = True
|
||||
|
||||
startup_candle_count: int = 220
|
||||
process_only_new_candles = True
|
||||
use_exit_signal = True
|
||||
|
||||
buy_adx_min = IntParameter(15, 40, default=25, space="buy", optimize=True)
|
||||
buy_cloud_min_pct = DecimalParameter(
|
||||
0.0, 2.0, default=0.3, decimals=2, space="buy", optimize=True
|
||||
)
|
||||
require_tk_cross = BooleanParameter(default=False, space="buy", optimize=True)
|
||||
use_macro = BooleanParameter(default=True, space="buy", optimize=True)
|
||||
|
||||
# --- Espaces hyperopt CONTRAINTS (le coeur de cette optimisation) ---
|
||||
class HyperOpt:
|
||||
@staticmethod
|
||||
def stoploss_space() -> list[Dimension]:
|
||||
# Stop réaliste : entre -2% et -10% (fini le -23%).
|
||||
return [SKDecimal(-0.10, -0.02, decimals=3, name="stoploss")]
|
||||
|
||||
@staticmethod
|
||||
def roi_space() -> list[Dimension]:
|
||||
return [
|
||||
Integer(0, 120, name="roi_t1"),
|
||||
Integer(0, 60, name="roi_t2"),
|
||||
Integer(0, 30, name="roi_t3"),
|
||||
SKDecimal(0.02, 0.12, decimals=3, name="roi_p1"),
|
||||
SKDecimal(0.01, 0.06, decimals=3, name="roi_p2"),
|
||||
SKDecimal(0.005, 0.03, decimals=3, name="roi_p3"),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def generate_roi_table(params: dict) -> dict[int, float]:
|
||||
roi = {}
|
||||
roi[0] = params["roi_p1"] + params["roi_p2"] + params["roi_p3"]
|
||||
roi[params["roi_t3"]] = params["roi_p1"] + params["roi_p2"]
|
||||
roi[params["roi_t3"] + params["roi_t2"]] = params["roi_p1"]
|
||||
roi[params["roi_t3"] + params["roi_t2"] + params["roi_t1"]] = 0
|
||||
return roi
|
||||
|
||||
@staticmethod
|
||||
def trailing_space() -> list[Dimension]:
|
||||
return [
|
||||
Categorical([True], name="trailing_stop"),
|
||||
SKDecimal(0.01, 0.06, decimals=3, name="trailing_stop_positive"),
|
||||
SKDecimal(0.005, 0.05, decimals=3, name="trailing_stop_positive_offset_p1"),
|
||||
Categorical([True, False], name="trailing_only_offset_is_reached"),
|
||||
]
|
||||
|
||||
def leverage(self, pair, current_time, current_rate, proposed_leverage,
|
||||
max_leverage, entry_tag, side, **kwargs) -> float:
|
||||
return 1.0
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
high, low, close = dataframe["high"], dataframe["low"], dataframe["close"]
|
||||
tenkan = (high.rolling(9).max() + low.rolling(9).min()) / 2
|
||||
kijun = (high.rolling(26).max() + low.rolling(26).min()) / 2
|
||||
dataframe["tenkan"] = tenkan
|
||||
dataframe["kijun"] = kijun
|
||||
dataframe["senkou_a"] = ((tenkan + kijun) / 2).shift(26)
|
||||
dataframe["senkou_b"] = (
|
||||
(high.rolling(52).max() + low.rolling(52).min()) / 2
|
||||
).shift(26)
|
||||
dataframe["cloud_top"] = dataframe[["senkou_a", "senkou_b"]].max(axis=1)
|
||||
dataframe["cloud_bot"] = dataframe[["senkou_a", "senkou_b"]].min(axis=1)
|
||||
dataframe["cloud_width_pct"] = (
|
||||
(dataframe["cloud_top"] - dataframe["cloud_bot"]) / close * 100
|
||||
)
|
||||
dataframe["close_prev26"] = close.shift(26)
|
||||
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
|
||||
dataframe["ema200"] = ta.EMA(dataframe, timeperiod=200)
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
adx_min = self.buy_adx_min.value
|
||||
cloud_min = self.buy_cloud_min_pct.value
|
||||
|
||||
long_cond = (
|
||||
(dataframe["close"] > dataframe["cloud_top"])
|
||||
& (dataframe["tenkan"] > dataframe["kijun"])
|
||||
& (dataframe["close"] > dataframe["close_prev26"])
|
||||
& (dataframe["adx"] > adx_min)
|
||||
& (dataframe["cloud_width_pct"] > cloud_min)
|
||||
& (dataframe["volume"] > 0)
|
||||
)
|
||||
short_cond = (
|
||||
(dataframe["close"] < dataframe["cloud_bot"])
|
||||
& (dataframe["tenkan"] < dataframe["kijun"])
|
||||
& (dataframe["close"] < dataframe["close_prev26"])
|
||||
& (dataframe["adx"] > adx_min)
|
||||
& (dataframe["cloud_width_pct"] > cloud_min)
|
||||
& (dataframe["volume"] > 0)
|
||||
)
|
||||
if self.require_tk_cross.value:
|
||||
long_cond &= dataframe["tenkan"].shift(1) <= dataframe["kijun"].shift(1)
|
||||
short_cond &= dataframe["tenkan"].shift(1) >= dataframe["kijun"].shift(1)
|
||||
if self.use_macro.value:
|
||||
long_cond &= dataframe["close"] > dataframe["ema200"]
|
||||
short_cond &= dataframe["close"] < dataframe["ema200"]
|
||||
|
||||
dataframe.loc[long_cond, "enter_long"] = 1
|
||||
dataframe.loc[short_cond, "enter_short"] = 1
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
((dataframe["tenkan"] < dataframe["kijun"])
|
||||
| (dataframe["close"] < dataframe["cloud_bot"]))
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"exit_long",
|
||||
] = 1
|
||||
dataframe.loc[
|
||||
(
|
||||
((dataframe["tenkan"] > dataframe["kijun"])
|
||||
| (dataframe["close"] > dataframe["cloud_top"]))
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"exit_short",
|
||||
] = 1
|
||||
return dataframe
|
||||
36
freqtrade/user_data/strategies/IchimokuHyper3.json
Normal file
36
freqtrade/user_data/strategies/IchimokuHyper3.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"strategy_name": "IchimokuHyper3",
|
||||
"params": {
|
||||
"max_open_trades": {
|
||||
"max_open_trades": 3
|
||||
},
|
||||
"buy": {
|
||||
"buy_adx_min": 29,
|
||||
"buy_cloud_min_pct": 0.56,
|
||||
"p_kijun": 29,
|
||||
"p_mom": 27,
|
||||
"p_senkou": 72,
|
||||
"p_shift": 23,
|
||||
"p_tenkan": 14,
|
||||
"require_tk_cross": false,
|
||||
"use_macro": false
|
||||
},
|
||||
"roi": {
|
||||
"0": 0.353,
|
||||
"401": 0.176,
|
||||
"925": 0.079,
|
||||
"2340": 0
|
||||
},
|
||||
"stoploss": {
|
||||
"stoploss": -0.209
|
||||
},
|
||||
"trailing": {
|
||||
"trailing_stop": true,
|
||||
"trailing_stop_positive": 0.287,
|
||||
"trailing_stop_positive_offset": 0.326,
|
||||
"trailing_only_offset_is_reached": true
|
||||
}
|
||||
},
|
||||
"ft_stratparam_v": 1,
|
||||
"export_time": "2026-06-23 15:41:09.034742+00:00"
|
||||
}
|
||||
117
freqtrade/user_data/strategies/IchimokuHyper3.py
Normal file
117
freqtrade/user_data/strategies/IchimokuHyper3.py
Normal file
@@ -0,0 +1,117 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, too-few-public-methods
|
||||
"""
|
||||
IchimokuHyper3 — espace de paramètres ÉLARGI (périodes Ichimoku optimisables).
|
||||
|
||||
Round 3 de la boucle d'optimisation : on donne plus de liberté à l'optimiseur
|
||||
(Tenkan/Kijun/Senkou B + lookback momentum + filtres) pour pousser le gain.
|
||||
⚠️ Plus de paramètres = plus de capacité d'overfitting (le gain in-sample monte,
|
||||
mais l'OOS sera à surveiller).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy import (
|
||||
IStrategy,
|
||||
IntParameter,
|
||||
DecimalParameter,
|
||||
BooleanParameter,
|
||||
)
|
||||
|
||||
|
||||
class IchimokuHyper3(IStrategy):
|
||||
INTERFACE_VERSION = 3
|
||||
timeframe = "1h"
|
||||
can_short = True
|
||||
|
||||
minimal_roi = {"0": 0.08, "240": 0.04, "720": 0.02, "1440": 0}
|
||||
stoploss = -0.08
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.02
|
||||
trailing_stop_positive_offset = 0.03
|
||||
trailing_only_offset_is_reached = True
|
||||
|
||||
startup_candle_count: int = 260
|
||||
process_only_new_candles = True
|
||||
use_exit_signal = True
|
||||
|
||||
# Périodes Ichimoku optimisables
|
||||
p_tenkan = IntParameter(5, 20, default=9, space="buy", optimize=True)
|
||||
p_kijun = IntParameter(15, 45, default=26, space="buy", optimize=True)
|
||||
p_senkou = IntParameter(40, 90, default=52, space="buy", optimize=True)
|
||||
p_shift = IntParameter(15, 40, default=26, space="buy", optimize=True)
|
||||
p_mom = IntParameter(10, 40, default=26, space="buy", optimize=True)
|
||||
# Filtres
|
||||
buy_adx_min = IntParameter(10, 40, default=20, space="buy", optimize=True)
|
||||
buy_cloud_min_pct = DecimalParameter(0.0, 2.0, default=0.3, decimals=2, space="buy", optimize=True)
|
||||
require_tk_cross = BooleanParameter(default=False, space="buy", optimize=True)
|
||||
use_macro = BooleanParameter(default=False, space="buy", optimize=True)
|
||||
|
||||
def leverage(self, pair, current_time, current_rate, proposed_leverage,
|
||||
max_leverage, entry_tag, side, **kwargs) -> float:
|
||||
return 1.0
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
high, low, close = dataframe["high"], dataframe["low"], dataframe["close"]
|
||||
t, k, s, sh = (
|
||||
self.p_tenkan.value, self.p_kijun.value,
|
||||
self.p_senkou.value, self.p_shift.value,
|
||||
)
|
||||
tenkan = (high.rolling(t).max() + low.rolling(t).min()) / 2
|
||||
kijun = (high.rolling(k).max() + low.rolling(k).min()) / 2
|
||||
dataframe["tenkan"] = tenkan
|
||||
dataframe["kijun"] = kijun
|
||||
dataframe["senkou_a"] = ((tenkan + kijun) / 2).shift(sh)
|
||||
dataframe["senkou_b"] = ((high.rolling(s).max() + low.rolling(s).min()) / 2).shift(sh)
|
||||
dataframe["cloud_top"] = dataframe[["senkou_a", "senkou_b"]].max(axis=1)
|
||||
dataframe["cloud_bot"] = dataframe[["senkou_a", "senkou_b"]].min(axis=1)
|
||||
dataframe["cloud_width_pct"] = (
|
||||
(dataframe["cloud_top"] - dataframe["cloud_bot"]) / close * 100
|
||||
)
|
||||
dataframe["close_prev"] = close.shift(self.p_mom.value)
|
||||
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
|
||||
dataframe["ema200"] = ta.EMA(dataframe, timeperiod=200)
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
adx_min = self.buy_adx_min.value
|
||||
cloud_min = self.buy_cloud_min_pct.value
|
||||
long_cond = (
|
||||
(dataframe["close"] > dataframe["cloud_top"])
|
||||
& (dataframe["tenkan"] > dataframe["kijun"])
|
||||
& (dataframe["close"] > dataframe["close_prev"])
|
||||
& (dataframe["adx"] > adx_min)
|
||||
& (dataframe["cloud_width_pct"] > cloud_min)
|
||||
& (dataframe["volume"] > 0)
|
||||
)
|
||||
short_cond = (
|
||||
(dataframe["close"] < dataframe["cloud_bot"])
|
||||
& (dataframe["tenkan"] < dataframe["kijun"])
|
||||
& (dataframe["close"] < dataframe["close_prev"])
|
||||
& (dataframe["adx"] > adx_min)
|
||||
& (dataframe["cloud_width_pct"] > cloud_min)
|
||||
& (dataframe["volume"] > 0)
|
||||
)
|
||||
if self.require_tk_cross.value:
|
||||
long_cond &= dataframe["tenkan"].shift(1) <= dataframe["kijun"].shift(1)
|
||||
short_cond &= dataframe["tenkan"].shift(1) >= dataframe["kijun"].shift(1)
|
||||
if self.use_macro.value:
|
||||
long_cond &= dataframe["close"] > dataframe["ema200"]
|
||||
short_cond &= dataframe["close"] < dataframe["ema200"]
|
||||
dataframe.loc[long_cond, "enter_long"] = 1
|
||||
dataframe.loc[short_cond, "enter_short"] = 1
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(((dataframe["tenkan"] < dataframe["kijun"]) | (dataframe["close"] < dataframe["cloud_bot"]))
|
||||
& (dataframe["volume"] > 0)),
|
||||
"exit_long",
|
||||
] = 1
|
||||
dataframe.loc[
|
||||
(((dataframe["tenkan"] > dataframe["kijun"]) | (dataframe["close"] > dataframe["cloud_top"]))
|
||||
& (dataframe["volume"] > 0)),
|
||||
"exit_short",
|
||||
] = 1
|
||||
return dataframe
|
||||
30
freqtrade/user_data/strategies/IchimokuHyperVol.json
Normal file
30
freqtrade/user_data/strategies/IchimokuHyperVol.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"strategy_name": "IchimokuHyperVol",
|
||||
"params": {
|
||||
"max_open_trades": {
|
||||
"max_open_trades": 3
|
||||
},
|
||||
"buy": {
|
||||
"buy_adx_min": 34,
|
||||
"buy_cloud_min_pct": 0.41,
|
||||
"require_tk_cross": false
|
||||
},
|
||||
"roi": {
|
||||
"0": 0.435,
|
||||
"342": 0.209,
|
||||
"509": 0.055,
|
||||
"1579": 0
|
||||
},
|
||||
"stoploss": {
|
||||
"stoploss": -0.192
|
||||
},
|
||||
"trailing": {
|
||||
"trailing_stop": true,
|
||||
"trailing_stop_positive": 0.251,
|
||||
"trailing_stop_positive_offset": 0.255,
|
||||
"trailing_only_offset_is_reached": false
|
||||
}
|
||||
},
|
||||
"ft_stratparam_v": 1,
|
||||
"export_time": "2026-06-23 17:08:14.826029+00:00"
|
||||
}
|
||||
126
freqtrade/user_data/strategies/IchimokuHyperVol.py
Normal file
126
freqtrade/user_data/strategies/IchimokuHyperVol.py
Normal file
@@ -0,0 +1,126 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, too-few-public-methods
|
||||
"""
|
||||
IchimokuHyperVol — IchimokuHyper + VOLATILITY TARGETING (sizing dynamique).
|
||||
|
||||
Même logique d'entrée/sortie qu'IchimokuHyper. Seule différence : la TAILLE de
|
||||
position s'ajuste à l'inverse de la volatilité récente (ATR%) :
|
||||
vol haute → position réduite → pertes bornées (notamment dans le chop volatil)
|
||||
vol normale→ pleine taille → gains préservés en tendance
|
||||
|
||||
Contrôle de risque DYNAMIQUE et non-prédictif (pas de filtre de régime overfit).
|
||||
Référence de vol = médiane glissante de l'ATR% (trailing → pas de fuite du futur).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy import (
|
||||
IStrategy,
|
||||
IntParameter,
|
||||
DecimalParameter,
|
||||
BooleanParameter,
|
||||
)
|
||||
|
||||
|
||||
class IchimokuHyperVol(IStrategy):
|
||||
INTERFACE_VERSION = 3
|
||||
timeframe = "1h"
|
||||
can_short = True
|
||||
|
||||
minimal_roi = {"0": 0.08, "240": 0.04, "720": 0.02, "1440": 0}
|
||||
stoploss = -0.08
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.02
|
||||
trailing_stop_positive_offset = 0.03
|
||||
trailing_only_offset_is_reached = True
|
||||
|
||||
startup_candle_count: int = 520 # médiane ATR% sur 500 + Ichimoku
|
||||
process_only_new_candles = True
|
||||
use_exit_signal = True
|
||||
|
||||
buy_adx_min = IntParameter(15, 40, default=25, space="buy", optimize=True)
|
||||
buy_cloud_min_pct = DecimalParameter(0.0, 2.0, default=0.3, decimals=2, space="buy", optimize=True)
|
||||
require_tk_cross = BooleanParameter(default=False, space="buy", optimize=True)
|
||||
|
||||
# Bornes du facteur de sizing (réduit jusqu'à 0.4x, augmente jusqu'à 1.4x)
|
||||
VOL_FACTOR_MIN = 0.4
|
||||
VOL_FACTOR_MAX = 1.4
|
||||
|
||||
def leverage(self, pair, current_time, current_rate, proposed_leverage,
|
||||
max_leverage, entry_tag, side, **kwargs) -> float:
|
||||
return 1.0
|
||||
|
||||
def custom_stake_amount(self, pair, current_time, current_rate, proposed_stake,
|
||||
min_stake, max_stake, leverage, entry_tag, side, **kwargs):
|
||||
df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||
if df is None or len(df) == 0:
|
||||
return proposed_stake
|
||||
factor = df["vol_factor"].iat[-1]
|
||||
if factor is None or not np.isfinite(factor):
|
||||
return proposed_stake
|
||||
stake = proposed_stake * float(factor)
|
||||
if min_stake is not None:
|
||||
stake = max(stake, min_stake)
|
||||
return min(stake, max_stake)
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
high, low, close = dataframe["high"], dataframe["low"], dataframe["close"]
|
||||
tenkan = (high.rolling(9).max() + low.rolling(9).min()) / 2
|
||||
kijun = (high.rolling(26).max() + low.rolling(26).min()) / 2
|
||||
dataframe["tenkan"] = tenkan
|
||||
dataframe["kijun"] = kijun
|
||||
dataframe["senkou_a"] = ((tenkan + kijun) / 2).shift(26)
|
||||
dataframe["senkou_b"] = ((high.rolling(52).max() + low.rolling(52).min()) / 2).shift(26)
|
||||
dataframe["cloud_top"] = dataframe[["senkou_a", "senkou_b"]].max(axis=1)
|
||||
dataframe["cloud_bot"] = dataframe[["senkou_a", "senkou_b"]].min(axis=1)
|
||||
dataframe["cloud_width_pct"] = (dataframe["cloud_top"] - dataframe["cloud_bot"]) / close * 100
|
||||
dataframe["close_prev26"] = close.shift(26)
|
||||
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
|
||||
|
||||
# --- Volatility targeting ---
|
||||
atr_pct = ta.ATR(dataframe, timeperiod=14) / close * 100
|
||||
atr_ref = atr_pct.rolling(500).median() # vol "normale" (trailing)
|
||||
factor = (atr_ref / atr_pct).clip(self.VOL_FACTOR_MIN, self.VOL_FACTOR_MAX)
|
||||
dataframe["vol_factor"] = factor.fillna(1.0)
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
adx_min = self.buy_adx_min.value
|
||||
cloud_min = self.buy_cloud_min_pct.value
|
||||
long_cond = (
|
||||
(dataframe["close"] > dataframe["cloud_top"])
|
||||
& (dataframe["tenkan"] > dataframe["kijun"])
|
||||
& (dataframe["close"] > dataframe["close_prev26"])
|
||||
& (dataframe["adx"] > adx_min)
|
||||
& (dataframe["cloud_width_pct"] > cloud_min)
|
||||
& (dataframe["volume"] > 0)
|
||||
)
|
||||
short_cond = (
|
||||
(dataframe["close"] < dataframe["cloud_bot"])
|
||||
& (dataframe["tenkan"] < dataframe["kijun"])
|
||||
& (dataframe["close"] < dataframe["close_prev26"])
|
||||
& (dataframe["adx"] > adx_min)
|
||||
& (dataframe["cloud_width_pct"] > cloud_min)
|
||||
& (dataframe["volume"] > 0)
|
||||
)
|
||||
if self.require_tk_cross.value:
|
||||
long_cond &= dataframe["tenkan"].shift(1) <= dataframe["kijun"].shift(1)
|
||||
short_cond &= dataframe["tenkan"].shift(1) >= dataframe["kijun"].shift(1)
|
||||
dataframe.loc[long_cond, "enter_long"] = 1
|
||||
dataframe.loc[short_cond, "enter_short"] = 1
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(((dataframe["tenkan"] < dataframe["kijun"]) | (dataframe["close"] < dataframe["cloud_bot"]))
|
||||
& (dataframe["volume"] > 0)),
|
||||
"exit_long",
|
||||
] = 1
|
||||
dataframe.loc[
|
||||
(((dataframe["tenkan"] > dataframe["kijun"]) | (dataframe["close"] > dataframe["cloud_top"]))
|
||||
& (dataframe["volume"] > 0)),
|
||||
"exit_short",
|
||||
] = 1
|
||||
return dataframe
|
||||
108
freqtrade/user_data/strategies/IchimokuLS.py
Normal file
108
freqtrade/user_data/strategies/IchimokuLS.py
Normal file
@@ -0,0 +1,108 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, too-few-public-methods
|
||||
"""
|
||||
IchimokuLS — Ichimoku long/short avec FILTRE DE TENDANCE MACRO (EMA200).
|
||||
|
||||
Reprend les paramètres optimisés d'IchimokuHyper (figés), et ajoute :
|
||||
- LONG uniquement si close > EMA200 (tendance de fond haussière)
|
||||
- SHORT uniquement si close < EMA200 (tendance de fond baissière)
|
||||
|
||||
But : réparer le côté long, qui perdait en entrant à contre-tendance macro.
|
||||
On compare A/B contre IchimokuHyper (mêmes params, sans le filtre).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy import IStrategy
|
||||
|
||||
|
||||
class IchimokuLS(IStrategy):
|
||||
INTERFACE_VERSION = 3
|
||||
timeframe = "1h"
|
||||
can_short = True
|
||||
|
||||
# --- Paramètres figés (issus de l'hyperopt d'IchimokuHyper) ---
|
||||
minimal_roi = {"0": 0.488, "213": 0.136, "639": 0.05, "2021": 0}
|
||||
stoploss = -0.232
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.341
|
||||
trailing_stop_positive_offset = 0.441
|
||||
trailing_only_offset_is_reached = False
|
||||
|
||||
buy_adx_min = 36
|
||||
buy_cloud_min_pct = 0.56
|
||||
|
||||
startup_candle_count: int = 220 # EMA200 + marge
|
||||
process_only_new_candles = True
|
||||
use_exit_signal = True
|
||||
|
||||
def leverage(self, pair, current_time, current_rate, proposed_leverage,
|
||||
max_leverage, entry_tag, side, **kwargs) -> float:
|
||||
return 1.0
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
high, low, close = dataframe["high"], dataframe["low"], dataframe["close"]
|
||||
tenkan = (high.rolling(9).max() + low.rolling(9).min()) / 2
|
||||
kijun = (high.rolling(26).max() + low.rolling(26).min()) / 2
|
||||
dataframe["tenkan"] = tenkan
|
||||
dataframe["kijun"] = kijun
|
||||
dataframe["senkou_a"] = ((tenkan + kijun) / 2).shift(26)
|
||||
dataframe["senkou_b"] = (
|
||||
(high.rolling(52).max() + low.rolling(52).min()) / 2
|
||||
).shift(26)
|
||||
dataframe["cloud_top"] = dataframe[["senkou_a", "senkou_b"]].max(axis=1)
|
||||
dataframe["cloud_bot"] = dataframe[["senkou_a", "senkou_b"]].min(axis=1)
|
||||
dataframe["cloud_width_pct"] = (
|
||||
(dataframe["cloud_top"] - dataframe["cloud_bot"]) / close * 100
|
||||
)
|
||||
dataframe["close_prev26"] = close.shift(26)
|
||||
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
|
||||
dataframe["ema200"] = ta.EMA(dataframe, timeperiod=200) # filtre macro
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe["close"] > dataframe["cloud_top"])
|
||||
& (dataframe["tenkan"] > dataframe["kijun"])
|
||||
& (dataframe["close"] > dataframe["close_prev26"])
|
||||
& (dataframe["adx"] > self.buy_adx_min)
|
||||
& (dataframe["cloud_width_pct"] > self.buy_cloud_min_pct)
|
||||
& (dataframe["close"] > dataframe["ema200"]) # ← filtre macro LONG
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"enter_long",
|
||||
] = 1
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe["close"] < dataframe["cloud_bot"])
|
||||
& (dataframe["tenkan"] < dataframe["kijun"])
|
||||
& (dataframe["close"] < dataframe["close_prev26"])
|
||||
& (dataframe["adx"] > self.buy_adx_min)
|
||||
& (dataframe["cloud_width_pct"] > self.buy_cloud_min_pct)
|
||||
& (dataframe["close"] < dataframe["ema200"]) # ← filtre macro SHORT
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"enter_short",
|
||||
] = 1
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
((dataframe["tenkan"] < dataframe["kijun"])
|
||||
| (dataframe["close"] < dataframe["cloud_bot"]))
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"exit_long",
|
||||
] = 1
|
||||
dataframe.loc[
|
||||
(
|
||||
((dataframe["tenkan"] > dataframe["kijun"])
|
||||
| (dataframe["close"] > dataframe["cloud_top"]))
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"exit_short",
|
||||
] = 1
|
||||
return dataframe
|
||||
117
freqtrade/user_data/strategies/IchimokuStrategy.py
Normal file
117
freqtrade/user_data/strategies/IchimokuStrategy.py
Normal file
@@ -0,0 +1,117 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, too-few-public-methods
|
||||
"""
|
||||
IchimokuStrategy — long/short basé sur Ichimoku Kinko Hyo (futures).
|
||||
|
||||
Composants :
|
||||
Tenkan-sen (9), Kijun-sen (26), Senkou A/B (nuage/Kumo), confirmation type Chikou.
|
||||
|
||||
Signaux :
|
||||
LONG : prix au-dessus du nuage + croisement Tenkan>Kijun + momentum (close > close[-26]).
|
||||
SHORT : miroir exact (prix sous le nuage + Tenkan<Kijun + close < close[-26]).
|
||||
|
||||
⚠️ Anti-lookahead : les Senkou sont décalés de +26 (donnée passée projetée au présent),
|
||||
la confirmation "Chikou" utilise close vs close d'il y a 26 bougies (pas de futur).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy import IStrategy
|
||||
|
||||
|
||||
class IchimokuStrategy(IStrategy):
|
||||
INTERFACE_VERSION = 3
|
||||
timeframe = "1h"
|
||||
can_short = True
|
||||
|
||||
minimal_roi = {"0": 0.06, "240": 0.03, "720": 0.01, "1440": 0}
|
||||
stoploss = -0.08
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.025
|
||||
trailing_stop_positive_offset = 0.04
|
||||
trailing_only_offset_is_reached = True
|
||||
|
||||
startup_candle_count: int = 120 # 52 (Senkou B) + 26 (décalage) + marge
|
||||
process_only_new_candles = True
|
||||
use_exit_signal = True
|
||||
|
||||
def leverage(self, pair, current_time, current_rate, proposed_leverage,
|
||||
max_leverage, entry_tag, side, **kwargs) -> float:
|
||||
return 1.0 # edge directionnel pur
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
high, low, close = dataframe["high"], dataframe["low"], dataframe["close"]
|
||||
|
||||
tenkan = (high.rolling(9).max() + low.rolling(9).min()) / 2
|
||||
kijun = (high.rolling(26).max() + low.rolling(26).min()) / 2
|
||||
dataframe["tenkan"] = tenkan
|
||||
dataframe["kijun"] = kijun
|
||||
|
||||
# Senkou décalés de +26 : valeur du nuage au présent issue de données passées.
|
||||
dataframe["senkou_a"] = ((tenkan + kijun) / 2).shift(26)
|
||||
dataframe["senkou_b"] = (
|
||||
(high.rolling(52).max() + low.rolling(52).min()) / 2
|
||||
).shift(26)
|
||||
|
||||
# Bornes du nuage
|
||||
dataframe["cloud_top"] = dataframe[["senkou_a", "senkou_b"]].max(axis=1)
|
||||
dataframe["cloud_bot"] = dataframe[["senkou_a", "senkou_b"]].min(axis=1)
|
||||
|
||||
# Confirmation momentum type Chikou (close vs close d'il y a 26 bougies)
|
||||
dataframe["close_prev26"] = close.shift(26)
|
||||
|
||||
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
# LONG : au-dessus du nuage + croisement TK haussier + momentum
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe["close"] > dataframe["cloud_top"])
|
||||
& (dataframe["tenkan"] > dataframe["kijun"])
|
||||
& (dataframe["tenkan"].shift(1) <= dataframe["kijun"].shift(1))
|
||||
& (dataframe["close"] > dataframe["close_prev26"])
|
||||
& (dataframe["adx"] > 20)
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"enter_long",
|
||||
] = 1
|
||||
# SHORT : sous le nuage + croisement TK baissier + momentum baissier
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe["close"] < dataframe["cloud_bot"])
|
||||
& (dataframe["tenkan"] < dataframe["kijun"])
|
||||
& (dataframe["tenkan"].shift(1) >= dataframe["kijun"].shift(1))
|
||||
& (dataframe["close"] < dataframe["close_prev26"])
|
||||
& (dataframe["adx"] > 20)
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"enter_short",
|
||||
] = 1
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
# Sortie LONG : Tenkan repasse sous Kijun, ou prix replonge dans le nuage
|
||||
dataframe.loc[
|
||||
(
|
||||
(
|
||||
(dataframe["tenkan"] < dataframe["kijun"])
|
||||
| (dataframe["close"] < dataframe["cloud_bot"])
|
||||
)
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"exit_long",
|
||||
] = 1
|
||||
# Sortie SHORT : Tenkan repasse au-dessus de Kijun, ou prix remonte dans le nuage
|
||||
dataframe.loc[
|
||||
(
|
||||
(
|
||||
(dataframe["tenkan"] > dataframe["kijun"])
|
||||
| (dataframe["close"] > dataframe["cloud_top"])
|
||||
)
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"exit_short",
|
||||
] = 1
|
||||
return dataframe
|
||||
64
freqtrade/user_data/strategies/LeveragedStrategy.py
Normal file
64
freqtrade/user_data/strategies/LeveragedStrategy.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, too-few-public-methods
|
||||
"""
|
||||
LeveragedStrategy — DÉMONSTRATION du risque du levier (NE PAS utiliser en réel).
|
||||
|
||||
Même logique technique que SampleStrategy, mais en futures avec levier 10x.
|
||||
But : montrer empiriquement que le levier crée des semaines à +10 % ET des semaines
|
||||
catastrophiques — donc « +10 % chaque semaine » reste impossible, et le levier ruine.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy import IStrategy
|
||||
|
||||
|
||||
class LeveragedStrategy(IStrategy):
|
||||
INTERFACE_VERSION = 3
|
||||
timeframe = "1h"
|
||||
can_short = False
|
||||
|
||||
minimal_roi = {"0": 0.05, "120": 0.03, "360": 0.01, "720": 0}
|
||||
stoploss = -0.10
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.02
|
||||
trailing_stop_positive_offset = 0.03
|
||||
trailing_only_offset_is_reached = True
|
||||
|
||||
startup_candle_count: int = 50
|
||||
process_only_new_candles = True
|
||||
use_exit_signal = True
|
||||
|
||||
def leverage(self, pair, current_time, current_rate, proposed_leverage,
|
||||
max_leverage, entry_tag, side, **kwargs) -> float:
|
||||
return min(5.0, max_leverage) # 5x
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe["ema_fast"] = ta.EMA(dataframe, timeperiod=9)
|
||||
dataframe["ema_slow"] = ta.EMA(dataframe, timeperiod=21)
|
||||
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe["ema_fast"] > dataframe["ema_slow"])
|
||||
& (dataframe["ema_fast"].shift(1) <= dataframe["ema_slow"].shift(1))
|
||||
& (dataframe["rsi"] < 70)
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"enter_long",
|
||||
] = 1
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe["ema_fast"] < dataframe["ema_slow"])
|
||||
& (dataframe["ema_fast"].shift(1) >= dataframe["ema_slow"].shift(1))
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"exit_long",
|
||||
] = 1
|
||||
return dataframe
|
||||
94
freqtrade/user_data/strategies/LongShortStrategy.py
Normal file
94
freqtrade/user_data/strategies/LongShortStrategy.py
Normal file
@@ -0,0 +1,94 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, too-few-public-methods
|
||||
"""
|
||||
LongShortStrategy — stratégie symétrique (futures) qui gagne dans les deux sens.
|
||||
|
||||
- LONG quand la tendance est haussière (EMA fast>slow, prix>EMA50, momentum +).
|
||||
- SHORT quand la tendance est baissière (miroir exact).
|
||||
But : ne plus subir les marchés baissiers — profiter de la baisse comme de la hausse.
|
||||
Levier 1x par défaut (on isole l'edge directionnel ; le levier viendra après si robuste).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy import IStrategy
|
||||
|
||||
|
||||
class LongShortStrategy(IStrategy):
|
||||
INTERFACE_VERSION = 3
|
||||
timeframe = "1h"
|
||||
can_short = True # futures requis
|
||||
|
||||
minimal_roi = {"0": 0.05, "120": 0.03, "360": 0.01, "720": 0}
|
||||
stoploss = -0.08
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.02
|
||||
trailing_stop_positive_offset = 0.03
|
||||
trailing_only_offset_is_reached = True
|
||||
|
||||
startup_candle_count: int = 60
|
||||
process_only_new_candles = True
|
||||
use_exit_signal = True
|
||||
|
||||
def leverage(self, pair, current_time, current_rate, proposed_leverage,
|
||||
max_leverage, entry_tag, side, **kwargs) -> float:
|
||||
return 1.0 # 1x — edge directionnel pur, sans amplification
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe["ema_fast"] = ta.EMA(dataframe, timeperiod=9)
|
||||
dataframe["ema_slow"] = ta.EMA(dataframe, timeperiod=21)
|
||||
dataframe["ema_trend"] = ta.EMA(dataframe, timeperiod=50)
|
||||
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
|
||||
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
# LONG : croisement haussier + tendance MT haussière + tendance forte
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe["ema_fast"] > dataframe["ema_slow"])
|
||||
& (dataframe["ema_fast"].shift(1) <= dataframe["ema_slow"].shift(1))
|
||||
& (dataframe["close"] > dataframe["ema_trend"])
|
||||
& (dataframe["adx"] > 20)
|
||||
& (dataframe["rsi"] > 45)
|
||||
& (dataframe["rsi"] < 75)
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"enter_long",
|
||||
] = 1
|
||||
# SHORT : miroir exact
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe["ema_fast"] < dataframe["ema_slow"])
|
||||
& (dataframe["ema_fast"].shift(1) >= dataframe["ema_slow"].shift(1))
|
||||
& (dataframe["close"] < dataframe["ema_trend"])
|
||||
& (dataframe["adx"] > 20)
|
||||
& (dataframe["rsi"] < 55)
|
||||
& (dataframe["rsi"] > 25)
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"enter_short",
|
||||
] = 1
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
# Sortie LONG : la tendance courte se retourne à la baisse
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe["ema_fast"] < dataframe["ema_slow"])
|
||||
& (dataframe["ema_fast"].shift(1) >= dataframe["ema_slow"].shift(1))
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"exit_long",
|
||||
] = 1
|
||||
# Sortie SHORT : la tendance courte se retourne à la hausse
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe["ema_fast"] > dataframe["ema_slow"])
|
||||
& (dataframe["ema_fast"].shift(1) <= dataframe["ema_slow"].shift(1))
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"exit_short",
|
||||
] = 1
|
||||
return dataframe
|
||||
33
freqtrade/user_data/strategies/MTFIchimoku.json
Normal file
33
freqtrade/user_data/strategies/MTFIchimoku.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"strategy_name": "MTFIchimoku",
|
||||
"params": {
|
||||
"max_open_trades": {
|
||||
"max_open_trades": 3
|
||||
},
|
||||
"buy": {
|
||||
"buy_adx_min": 32,
|
||||
"buy_pullback_pct": 0.008,
|
||||
"buy_rsi_max": 52
|
||||
},
|
||||
"sell": {
|
||||
"sell_rsi_min": 39
|
||||
},
|
||||
"roi": {
|
||||
"0": 0.264,
|
||||
"49": 0.057,
|
||||
"170": 0.035,
|
||||
"468": 0
|
||||
},
|
||||
"stoploss": {
|
||||
"stoploss": -0.154
|
||||
},
|
||||
"trailing": {
|
||||
"trailing_stop": true,
|
||||
"trailing_stop_positive": 0.232,
|
||||
"trailing_stop_positive_offset": 0.309,
|
||||
"trailing_only_offset_is_reached": false
|
||||
}
|
||||
},
|
||||
"ft_stratparam_v": 1,
|
||||
"export_time": "2026-06-23 16:09:25.675598+00:00"
|
||||
}
|
||||
135
freqtrade/user_data/strategies/MTFIchimoku.py
Normal file
135
freqtrade/user_data/strategies/MTFIchimoku.py
Normal file
@@ -0,0 +1,135 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, too-few-public-methods
|
||||
"""
|
||||
MTFIchimoku — Ichimoku MULTI-TIMEFRAME (technique S/R inter-unités).
|
||||
|
||||
Idée (méthode de l'utilisateur) : trader sur une unité basse (15m) en utilisant
|
||||
les Tenkan/Kijun de l'unité SUPÉRIEURE (1h) comme supports/résistances.
|
||||
|
||||
Règles :
|
||||
- Tendance 1h donnée par tenkan_1h vs kijun_1h.
|
||||
- LONG : en tendance 1h haussière, le prix 15m RECLAIME le support (croise au-dessus
|
||||
de la Kijun 1h) → rebond sur support.
|
||||
- SHORT : en tendance 1h baissière, le prix 15m CASSE le support (croise sous la
|
||||
Kijun 1h) → rejet sur résistance.
|
||||
- Sortie : perte du niveau (close repasse de l'autre côté de la Kijun 1h) + ROI/stop.
|
||||
|
||||
La Kijun 1h sert de S/R principal (niveau lent/fort), la Tenkan 1h de filtre de tendance.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy import (
|
||||
IStrategy,
|
||||
informative,
|
||||
IntParameter,
|
||||
DecimalParameter,
|
||||
)
|
||||
|
||||
|
||||
class MTFIchimoku(IStrategy):
|
||||
INTERFACE_VERSION = 3
|
||||
timeframe = "15m" # unité de trading
|
||||
can_short = True
|
||||
|
||||
# Paramètres optimisables
|
||||
buy_adx_min = IntParameter(15, 40, default=25, space="buy", optimize=True)
|
||||
buy_pullback_pct = DecimalParameter(0.004, 0.03, default=0.012, decimals=3, space="buy", optimize=True)
|
||||
buy_rsi_max = IntParameter(50, 72, default=60, space="buy", optimize=True)
|
||||
sell_rsi_min = IntParameter(28, 50, default=40, space="sell", optimize=True)
|
||||
|
||||
minimal_roi = {"0": 0.02, "60": 0.012, "180": 0.006, "360": 0}
|
||||
stoploss = -0.025
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.008
|
||||
trailing_stop_positive_offset = 0.014
|
||||
trailing_only_offset_is_reached = True
|
||||
|
||||
startup_candle_count: int = 240
|
||||
process_only_new_candles = True
|
||||
use_exit_signal = True
|
||||
|
||||
def leverage(self, pair, current_time, current_rate, proposed_leverage,
|
||||
max_leverage, entry_tag, side, **kwargs) -> float:
|
||||
return 1.0
|
||||
|
||||
@informative("1h")
|
||||
def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
# Tenkan / Kijun sur l'unité supérieure (1h) → deviendront tenkan_1h / kijun_1h.
|
||||
high, low = dataframe["high"], dataframe["low"]
|
||||
dataframe["tenkan"] = (high.rolling(9).max() + low.rolling(9).min()) / 2
|
||||
dataframe["kijun"] = (high.rolling(26).max() + low.rolling(26).min()) / 2
|
||||
return dataframe
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
# tenkan_1h / kijun_1h sont injectés automatiquement (forward-fill sur le 15m).
|
||||
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
|
||||
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
|
||||
# Plus haut/bas récents (pour exiger un VRAI pullback vers le niveau)
|
||||
dataframe["hh8"] = dataframe["high"].rolling(8).max()
|
||||
dataframe["ll8"] = dataframe["low"].rolling(8).min()
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
kijun = dataframe["kijun_1h"]
|
||||
tenkan = dataframe["tenkan_1h"]
|
||||
close, op, low, high = (
|
||||
dataframe["close"], dataframe["open"], dataframe["low"], dataframe["high"]
|
||||
)
|
||||
|
||||
rsi = dataframe["rsi"]
|
||||
adx = dataframe["adx"]
|
||||
adx_min = self.buy_adx_min.value
|
||||
pb = self.buy_pullback_pct.value
|
||||
uptrend_1h = (tenkan > kijun) & (close > kijun) # biais haussier 1h
|
||||
downtrend_1h = (tenkan < kijun) & (close < kijun) # biais baissier 1h
|
||||
|
||||
# LONG : en tendance forte, VRAI pullback vers le support Kijun 1h puis rebond + momentum.
|
||||
dataframe.loc[
|
||||
(
|
||||
uptrend_1h
|
||||
& (adx > adx_min) # tendance forte
|
||||
& (dataframe["hh8"] > kijun * (1 + pb)) # le prix venait nettement au-dessus
|
||||
& (low <= kijun * 1.001) # mèche teste le support
|
||||
& (close > kijun) # clôture au-dessus (support tient)
|
||||
& (close > op) # bougie de rebond
|
||||
& (rsi > rsi.shift(1)) # momentum qui se retourne à la hausse
|
||||
& (rsi < self.buy_rsi_max.value) # pas déjà suracheté
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"enter_long",
|
||||
] = 1
|
||||
|
||||
# SHORT : miroir exact (rejet sur résistance en tendance baissière forte).
|
||||
dataframe.loc[
|
||||
(
|
||||
downtrend_1h
|
||||
& (adx > adx_min)
|
||||
& (dataframe["ll8"] < kijun * (1 - pb))
|
||||
& (high >= kijun * 0.999)
|
||||
& (close < kijun)
|
||||
& (close < op)
|
||||
& (rsi < rsi.shift(1))
|
||||
& (rsi > self.sell_rsi_min.value)
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"enter_short",
|
||||
] = 1
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
kijun = dataframe["kijun_1h"]
|
||||
tenkan = dataframe["tenkan_1h"]
|
||||
close = dataframe["close"]
|
||||
# Sortie LONG : cassure NETTE du support (clôture sous la Kijun ET sous la Tenkan 1h)
|
||||
dataframe.loc[
|
||||
((close < kijun) & (close < tenkan) & (dataframe["volume"] > 0)),
|
||||
"exit_long",
|
||||
] = 1
|
||||
# Sortie SHORT : reprise NETTE au-dessus de la résistance
|
||||
dataframe.loc[
|
||||
((close > kijun) & (close > tenkan) & (dataframe["volume"] > 0)),
|
||||
"exit_short",
|
||||
] = 1
|
||||
return dataframe
|
||||
75
freqtrade/user_data/strategies/SampleStrategy.py
Normal file
75
freqtrade/user_data/strategies/SampleStrategy.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, too-few-public-methods
|
||||
"""
|
||||
SampleStrategy — stratégie de base MidasBot (Phase 1).
|
||||
|
||||
Croisement de moyennes mobiles exponentielles (EMA) avec filtre RSI.
|
||||
Sert à valider le pipeline Freqtrade (dry-run, backtesting, FreqUI) avant
|
||||
d'ajouter la couche IA (cf. AiBiasStrategy, Phase 3).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy import IStrategy
|
||||
|
||||
|
||||
class SampleStrategy(IStrategy):
|
||||
INTERFACE_VERSION = 3
|
||||
|
||||
# Timeframe d'analyse
|
||||
timeframe = "1h"
|
||||
|
||||
# Take-profit échelonné (ROI minimal par durée, en minutes)
|
||||
minimal_roi = {
|
||||
"0": 0.05,
|
||||
"120": 0.03,
|
||||
"360": 0.01,
|
||||
"720": 0,
|
||||
}
|
||||
|
||||
# Stop-loss dur
|
||||
stoploss = -0.10
|
||||
|
||||
# Trailing stop
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.02
|
||||
trailing_stop_positive_offset = 0.03
|
||||
trailing_only_offset_is_reached = True
|
||||
|
||||
# Nombre de bougies nécessaires avant de produire un signal
|
||||
startup_candle_count: int = 50
|
||||
|
||||
# Ordres
|
||||
process_only_new_candles = True
|
||||
use_exit_signal = True
|
||||
exit_profit_only = False
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe["ema_fast"] = ta.EMA(dataframe, timeperiod=9)
|
||||
dataframe["ema_slow"] = ta.EMA(dataframe, timeperiod=21)
|
||||
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe["ema_fast"] > dataframe["ema_slow"])
|
||||
& (dataframe["ema_fast"].shift(1) <= dataframe["ema_slow"].shift(1))
|
||||
& (dataframe["rsi"] < 70)
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"enter_long",
|
||||
] = 1
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe["ema_fast"] < dataframe["ema_slow"])
|
||||
& (dataframe["ema_fast"].shift(1) >= dataframe["ema_slow"].shift(1))
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"exit_long",
|
||||
] = 1
|
||||
return dataframe
|
||||
68
freqtrade/user_data/strategies/TrendMomentumStrategy.py
Normal file
68
freqtrade/user_data/strategies/TrendMomentumStrategy.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, too-few-public-methods
|
||||
"""
|
||||
TrendMomentumStrategy — tentative d'amélioration du rendement (vs SampleStrategy).
|
||||
|
||||
Idée : n'entrer que sur des tendances confirmées et fortes (filtre EMA50 + ADX),
|
||||
laisser courir les gains (ROI plus haut + trailing), couper vite les perdants.
|
||||
Objectif : meilleur rendement ajusté du risque — PAS de promesse de +10 %/semaine.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy import IStrategy
|
||||
|
||||
|
||||
class TrendMomentumStrategy(IStrategy):
|
||||
INTERFACE_VERSION = 3
|
||||
timeframe = "1h"
|
||||
|
||||
# Laisser courir : on vise des gains plus gros, on attend plus longtemps.
|
||||
minimal_roi = {"0": 0.12, "240": 0.06, "720": 0.03, "1440": 0}
|
||||
stoploss = -0.06 # on coupe vite les perdants
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.025
|
||||
trailing_stop_positive_offset = 0.04
|
||||
trailing_only_offset_is_reached = True
|
||||
|
||||
startup_candle_count: int = 60
|
||||
process_only_new_candles = True
|
||||
use_exit_signal = True
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe["ema_fast"] = ta.EMA(dataframe, timeperiod=9)
|
||||
dataframe["ema_slow"] = ta.EMA(dataframe, timeperiod=21)
|
||||
dataframe["ema_trend"] = ta.EMA(dataframe, timeperiod=50)
|
||||
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
|
||||
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
|
||||
macd = ta.MACD(dataframe)
|
||||
dataframe["macd"] = macd["macd"]
|
||||
dataframe["macdsignal"] = macd["macdsignal"]
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe["ema_fast"] > dataframe["ema_slow"]) # tendance courte haussière
|
||||
& (dataframe["close"] > dataframe["ema_trend"]) # au-dessus tendance MT
|
||||
& (dataframe["adx"] > 25) # tendance forte
|
||||
& (dataframe["macd"] > dataframe["macdsignal"]) # momentum haussier
|
||||
& (dataframe["rsi"] > 50)
|
||||
& (dataframe["rsi"] < 75) # pas en surachat extrême
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"enter_long",
|
||||
] = 1
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe["macd"] < dataframe["macdsignal"]) # momentum se retourne
|
||||
& (dataframe["close"] < dataframe["ema_slow"])
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
"exit_long",
|
||||
] = 1
|
||||
return dataframe
|
||||
Reference in New Issue
Block a user