diff --git a/config/BTC_Chan.json b/config/BTC_Chan.json new file mode 100644 index 0000000..326a8b9 --- /dev/null +++ b/config/BTC_Chan.json @@ -0,0 +1,84 @@ + +{ + "$schema": "https://schema.freqtrade.io/schema.json", + "max_open_trades": 1, + "stake_currency": "USDT", + "stake_amount": "unlimited", + "tradable_balance_ratio": 0.99, + "fiat_display_currency": "USD", + "dry_run": true, + "db_url": "sqlite:///tradesv3.btc_chan.sqlite", + "dry_run_wallet": 1000, + "cancel_open_orders_on_exit": true, + "trading_mode": "futures", + "margin_mode": "isolated", + "can_short" : true, + "timeframe" : "1m", + "process_only_new_candles" : false, + "unfilledtimeout": { + "entry": 1, + "exit": 1, + "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": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8", + "secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l", + "ccxt_config": {}, + "ccxt_async_config": {}, + "pair_whitelist": [ + "SOL/USDT:USDT", + ], + "pair_blacklist": [ + "BNB/.*" + ] + }, + "pairlists": [ + { + "method": "StaticPairList", + "number_assets": 1, + "sort_key": "quoteVolume", + "min_value": 0, + "refresh_period": 1800, + } + ], + "telegram": { + "enabled": false, + "token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y", + "chat_id": "580807463" + }, + "api_server": { + "enabled": true, + "listen_ip_address": "127.0.0.1", + "listen_port": 8882, + "verbosity": "error", + "enable_openapi": false, + "jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d", + "ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg", + "CORS_origins": [], + "username": "freqtrader", + "password": "FreqTrade007" + }, + "bot_name": "freqtrade", + "initial_state": "running", + "force_entry_enable": false, + "internals": { + "process_throttle_secs": 2 + } +} \ No newline at end of file diff --git a/config/Chan.json b/config/Chan.json new file mode 100644 index 0000000..635fa5d --- /dev/null +++ b/config/Chan.json @@ -0,0 +1,84 @@ + +{ + "$schema": "https://schema.freqtrade.io/schema.json", + "max_open_trades": 1, + "stake_currency": "USDT", + "stake_amount": "unlimited", + "tradable_balance_ratio": 0.99, + "fiat_display_currency": "USD", + "dry_run": true, + "db_url": "sqlite:///tradesv3.chan.sqlite", + "dry_run_wallet": 1000, + "cancel_open_orders_on_exit": true, + "trading_mode": "futures", + "margin_mode": "isolated", + "can_short" : true, + "timeframe" : "1m", + "process_only_new_candles" : false, + "unfilledtimeout": { + "entry": 1, + "exit": 1, + "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": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8", + "secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l", + "ccxt_config": {}, + "ccxt_async_config": {}, + "pair_whitelist": [ + "BTC/USDT:USDT", + ], + "pair_blacklist": [ + "BNB/.*" + ] + }, + "pairlists": [ + { + "method": "StaticPairList", + "number_assets": 1, + "sort_key": "quoteVolume", + "min_value": 0, + "refresh_period": 1800, + } + ], + "telegram": { + "enabled": false, + "token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y", + "chat_id": "580807463" + }, + "api_server": { + "enabled": true, + "listen_ip_address": "127.0.0.1", + "listen_port": 8800, + "verbosity": "error", + "enable_openapi": false, + "jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d", + "ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg", + "CORS_origins": [], + "username": "freqtrader", + "password": "FreqTrade007" + }, + "bot_name": "freqtrade", + "initial_state": "running", + "force_entry_enable": false, + "internals": { + "process_throttle_secs": 2 + } +} \ No newline at end of file diff --git a/config/ChanLun_SOL.json b/config/ChanLun_SOL.json new file mode 100644 index 0000000..7aea049 --- /dev/null +++ b/config/ChanLun_SOL.json @@ -0,0 +1,84 @@ + +{ + "$schema": "https://schema.freqtrade.io/schema.json", + "max_open_trades": 1, + "stake_currency": "USDT", + "stake_amount": "unlimited", + "tradable_balance_ratio": 0.99, + "fiat_display_currency": "USD", + "dry_run": true, + "db_url": "sqlite:///tradesv3.chanlun_sol.sqlite", + "dry_run_wallet": 1000, + "cancel_open_orders_on_exit": true, + "trading_mode": "futures", + "margin_mode": "isolated", + "can_short" : true, + "timeframe" : "1m", + "process_only_new_candles" : false, + "unfilledtimeout": { + "entry": 1, + "exit": 1, + "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": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8", + "secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l", + "ccxt_config": {}, + "ccxt_async_config": {}, + "pair_whitelist": [ + "BTC/USDT:USDT", + ], + "pair_blacklist": [ + "BNB/.*" + ] + }, + "pairlists": [ + { + "method": "StaticPairList", + "number_assets": 1, + "sort_key": "quoteVolume", + "min_value": 0, + "refresh_period": 1800, + } + ], + "telegram": { + "enabled": false, + "token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y", + "chat_id": "580807463" + }, + "api_server": { + "enabled": true, + "listen_ip_address": "127.0.0.1", + "listen_port": 8811, + "verbosity": "error", + "enable_openapi": false, + "jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d", + "ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg", + "CORS_origins": [], + "username": "freqtrader", + "password": "FreqTrade007" + }, + "bot_name": "freqtrade", + "initial_state": "running", + "force_entry_enable": false, + "internals": { + "process_throttle_secs": 2 + } +} \ No newline at end of file diff --git a/config/ChanLun_XGB.json b/config/ChanLun_XGB.json new file mode 100644 index 0000000..2bb2916 --- /dev/null +++ b/config/ChanLun_XGB.json @@ -0,0 +1,125 @@ +{ + "$schema": "https://schema.freqtrade.io/schema.json", + "max_open_trades": 1, + "stake_currency": "USDT", + "stake_amount": "unlimited", + "tradable_balance_ratio": 0.99, + "fiat_display_currency": "USD", + "dry_run": true, + "db_url": "sqlite:///tradesv3.chanlun_sol.sqlite", + "dry_run_wallet": 1000, + "cancel_open_orders_on_exit": true, + "trading_mode": "futures", + "margin_mode": "isolated", + "can_short" : true, + "timeframe" : "1m", + "process_only_new_candles" : true, + "unfilledtimeout": { + "entry": 1, + "exit": 1, + "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": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8", + "secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l", + "ccxt_config": {}, + "ccxt_async_config": {}, + "pair_whitelist": [ + "SOL/USDT:USDT" + ], + "pair_blacklist": [ + "BNB/.*" + ] + }, + "freqai": { + "enabled": true, + "purge_old_models": true, + "train_period_days": 30, + "backtest_period_days": 7, + "identifier": "chanLun1", + "live_retrain_hours": 1, + "expiration_hours": 48, + "fit_live_predictions_candles": 0, + "data_kitchen_thread_count": 4, + "save_backtest_models": true, + "save_metadata": true, + "feature_parameters": { + "include_timeframes": [ + "1m", + "5m", + "15m" + ], + "include_corr_pairlist": [ + "BTC/USDT:USDT", + "ETH/USDT:USDT" + ], + "label_period_candles": 24, + "include_shifted_candles": 2, + "indicator_periods_candles": [10, 20, 30], + "allow_duplicate_train": true + }, + "data_split_parameters": { + "test_size": 0.25 + }, + "model_training_parameters": { + "n_estimators": 100, + "learning_rate": 0.1, + "max_depth": 5, + "subsample": 0.8, + "colsample_bytree": 0.8, + "use_label_for_weight": true, + "booster": "gbtree", + "num_class": 2 + } + }, + "freqaimodel": "XGBoostClassifier", + "pairlists": [ + { + "method": "StaticPairList", + "number_assets": 1, + "sort_key": "quoteVolume", + "min_value": 0, + "refresh_period": 1800 + } + ], + "telegram": { + "enabled": false, + "token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y", + "chat_id": "580807463" + }, + "api_server": { + "enabled": true, + "listen_ip_address": "127.0.0.1", + "listen_port": 8811, + "verbosity": "error", + "enable_openapi": false, + "jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d", + "ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg", + "CORS_origins": [], + "username": "freqtrader", + "password": "FreqTrade007" + }, + "bot_name": "freqtrade", + "initial_state": "running", + "force_entry_enable": false, + "internals": { + "process_throttle_secs": 2 + } +} \ No newline at end of file diff --git a/config/Chan_5m.json b/config/Chan_5m.json new file mode 100644 index 0000000..7c954fc --- /dev/null +++ b/config/Chan_5m.json @@ -0,0 +1,103 @@ + +{ + "$schema": "https://schema.freqtrade.io/schema.json", + "max_open_trades": 3, + "stake_currency": "USDT", + "stake_amount": "unlimited", + "tradable_balance_ratio": 0.99, + "fiat_display_currency": "USD", + "dry_run": true, + "db_url": "sqlite:///tradesv3.btc_chan.sqlite", + "dry_run_wallet": 1000, + "cancel_open_orders_on_exit": true, + "trading_mode": "futures", + "margin_mode": "isolated", + "can_short" : true, + "timeframe" : "30m", + "process_only_new_candles" : false, + "unfilledtimeout": { + "entry": 1, + "exit": 1, + "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": "ocQUqAPSD9PDhIL2lTMlMan0wMFwvvu5Fv8eYF3wUM8yPytm2jBgz51cgiHXw7J6", + "secret": "yHIc6FOnSoOI2FvygpRKKku4FKaZGI5DSwC83Ip4wRfUcxszennF6hy2vhbVuLYJ", + "ccxt_config": {}, + "ccxt_async_config": {}, + "pair_whitelist": [ + "BTC/USDT:USDT", + "ETH/USDT:USDT", + "SOL/USDT:USDT", + "WIF/USDT:USDT", + "1000PEPE/USDT:USDT", + "DOGS/USDT:USDT", + "ORDI/USDT:USDT", + "AAVE/USDT:USDT", + "REEF/USDT:USDT", + "1000SATS/USDT:USDT", + "SUI/USDT:USDT", + "1INCH/USDT:USDT", + "DOGE/USDT:USDT", + "TON/USDT:USDT", + "UNI/USDT:USDT", + "XRP/USDT:USDT", + "SUN/USDT:USDT", + "NOT/USDT:USDT", + "RARE/USDT:USDT", + "RDNT/USDT:USDT" + ], + "pair_blacklist": [ + "BNB/.*" + ] + }, + "pairlists": [ + { + "method": "VolumePairList", + "number_assets": 10, + "sort_key": "quoteVolume", + "min_value": 0, + "refresh_period": 1800 + } + ], + "telegram": { + "enabled": true, + "token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y", + "chat_id": "580807463" + }, + "api_server": { + "enabled": true, + "listen_ip_address": "127.0.0.1", + "listen_port": 8088, + "verbosity": "error", + "enable_openapi": false, + "jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d", + "ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg", + "CORS_origins": [], + "username": "freqtrader", + "password": "FreqTrade007" + }, + "bot_name": "freqtrade", + "initial_state": "running", + "force_entry_enable": false, + "internals": { + "process_throttle_secs": 2 + } +} \ No newline at end of file diff --git a/config/Chan_BTC_30m.json b/config/Chan_BTC_30m.json new file mode 100644 index 0000000..2bc2860 --- /dev/null +++ b/config/Chan_BTC_30m.json @@ -0,0 +1,83 @@ + +{ + "$schema": "https://schema.freqtrade.io/schema.json", + "max_open_trades": 1, + "stake_currency": "USDT", + "stake_amount": "unlimited", + "tradable_balance_ratio": 0.99, + "fiat_display_currency": "USD", + "dry_run": true, + "dry_run_wallet": 1000, + "cancel_open_orders_on_exit": true, + "trading_mode": "futures", + "margin_mode": "isolated", + "can_short" : true, + "timeframe" : "5m", + "process_only_new_candles" : false, + "unfilledtimeout": { + "entry": 1, + "exit": 1, + "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": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8", + "secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l", + "ccxt_config": {}, + "ccxt_async_config": {}, + "pair_whitelist": [ + "BTC/USDT:USDT", + ], + "pair_blacklist": [ + "BNB/.*" + ] + }, + "pairlists": [ + { + "method": "StaticPairList", + "number_assets": 1, + "sort_key": "quoteVolume", + "min_value": 0, + "refresh_period": 1800 + } + ], + "telegram": { + "enabled": false, + "token": "5985766683:AAEx2Nm_4y2IC0Tj4Hhz7djVRJRso0JKaj0", + "chat_id": "580807463" + }, + "api_server": { + "enabled": true, + "listen_ip_address": "127.0.0.1", + "listen_port": 8088, + "verbosity": "error", + "enable_openapi": false, + "jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d", + "ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg", + "CORS_origins": [], + "username": "freqtrader", + "password": "FreqTrade007" + }, + "bot_name": "freqtrade", + "initial_state": "running", + "force_entry_enable": false, + "internals": { + "process_throttle_secs": 2 + } +} \ No newline at end of file diff --git a/config/Chan_ETH.json b/config/Chan_ETH.json new file mode 100644 index 0000000..cd4575f --- /dev/null +++ b/config/Chan_ETH.json @@ -0,0 +1,84 @@ + +{ + "$schema": "https://schema.freqtrade.io/schema.json", + "max_open_trades": 1, + "stake_currency": "USDT", + "stake_amount": "unlimited", + "tradable_balance_ratio": 0.99, + "fiat_display_currency": "USD", + "dry_run": true, + "db_url": "sqlite:///tradesv3.btc_chan.sqlite", + "dry_run_wallet": 1000, + "cancel_open_orders_on_exit": true, + "trading_mode": "futures", + "margin_mode": "isolated", + "can_short" : true, + "timeframe" : "1m", + "process_only_new_candles" : false, + "unfilledtimeout": { + "entry": 1, + "exit": 1, + "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": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8", + "secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l", + "ccxt_config": {}, + "ccxt_async_config": {}, + "pair_whitelist": [ + "SOL/USDT:USDT", + ], + "pair_blacklist": [ + "BNB/.*" + ] + }, + "pairlists": [ + { + "method": "StaticPairList", + "number_assets": 1, + "sort_key": "quoteVolume", + "min_value": 0, + "refresh_period": 1800, + } + ], + "telegram": { + "enabled": true, + "token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y", + "chat_id": "580807463" + }, + "api_server": { + "enabled": false, + "listen_ip_address": "127.0.0.1", + "listen_port": 8888, + "verbosity": "error", + "enable_openapi": false, + "jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d", + "ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg", + "CORS_origins": [], + "username": "freqtrader", + "password": "FreqTrade007" + }, + "bot_name": "freqtrade", + "initial_state": "running", + "force_entry_enable": false, + "internals": { + "process_throttle_secs": 2 + } +} \ No newline at end of file diff --git a/config/Chan_SOL.json b/config/Chan_SOL.json new file mode 100644 index 0000000..e3e31cc --- /dev/null +++ b/config/Chan_SOL.json @@ -0,0 +1,84 @@ +{ + "$schema": "https://schema.freqtrade.io/schema.json", + "max_open_trades": 1, + "stake_currency": "USDT", + "stake_amount": "unlimited", + "tradable_balance_ratio": 0.99, + "fiat_display_currency": "USD", + "dry_run": true, + "strategy": "ChanStrategy", + "db_url": "sqlite:///tradesv3.btc_chan.sqlite", + "dry_run_wallet": 1000, + "cancel_open_orders_on_exit": true, + "trading_mode": "futures", + "margin_mode": "isolated", + "can_short" : true, + "timeframe" : "1m", + "process_only_new_candles" : false, + "unfilledtimeout": { + "entry": 1, + "exit": 1, + "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": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8", + "secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l", + "ccxt_config": {}, + "ccxt_async_config": {}, + "pair_whitelist": [ + "SOL/USDT:USDT" + ], + "pair_blacklist": [ + "BNB/.*" + ] + }, + "pairlists": [ + { + "method": "StaticPairList", + "number_assets": 1, + "sort_key": "quoteVolume", + "min_value": 0, + "refresh_period": 1800 + } + ], + "telegram": { + "enabled": false, + "token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y", + "chat_id": "580807463" + }, + "api_server": { + "enabled": false, + "listen_ip_address": "127.0.0.1", + "listen_port": 8818, + "verbosity": "error", + "enable_openapi": false, + "jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d", + "ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg", + "CORS_origins": [], + "username": "freqtrader", + "password": "FreqTrade007" + }, + "bot_name": "freqtrade", + "initial_state": "running", + "force_entry_enable": false, + "internals": { + "process_throttle_secs": 2 + } +} \ No newline at end of file diff --git a/config/Chan_SOL_15.json b/config/Chan_SOL_15.json new file mode 100644 index 0000000..e6ca87b --- /dev/null +++ b/config/Chan_SOL_15.json @@ -0,0 +1,84 @@ + +{ + "$schema": "https://schema.freqtrade.io/schema.json", + "max_open_trades": 1, + "stake_currency": "USDT", + "stake_amount": "unlimited", + "tradable_balance_ratio": 0.99, + "fiat_display_currency": "USD", + "dry_run": true, + "db_url": "sqlite:///tradesv3.chan.sqlite", + "dry_run_wallet": 1000, + "cancel_open_orders_on_exit": true, + "trading_mode": "futures", + "margin_mode": "isolated", + "can_short" : true, + "timeframe" : "1m", + "process_only_new_candles" : false, + "unfilledtimeout": { + "entry": 1, + "exit": 1, + "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": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8", + "secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l", + "ccxt_config": {}, + "ccxt_async_config": {}, + "pair_whitelist": [ + "SOL/USDT:USDT", + ], + "pair_blacklist": [ + "BNB/.*" + ] + }, + "pairlists": [ + { + "method": "StaticPairList", + "number_assets": 1, + "sort_key": "quoteVolume", + "min_value": 0, + "refresh_period": 1800, + } + ], + "telegram": { + "enabled": false, + "token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y", + "chat_id": "580807463" + }, + "api_server": { + "enabled": true, + "listen_ip_address": "127.0.0.1", + "listen_port": 8815, + "verbosity": "error", + "enable_openapi": false, + "jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d", + "ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg", + "CORS_origins": [], + "username": "freqtrader", + "password": "FreqTrade007" + }, + "bot_name": "freqtrade", + "initial_state": "running", + "force_entry_enable": false, + "internals": { + "process_throttle_secs": 2 + } +} \ No newline at end of file diff --git a/config/Chan_SOL_15_HL.json b/config/Chan_SOL_15_HL.json new file mode 100644 index 0000000..72b3f3d --- /dev/null +++ b/config/Chan_SOL_15_HL.json @@ -0,0 +1,84 @@ + +{ + "$schema": "https://schema.freqtrade.io/schema.json", + "max_open_trades": 1, + "stake_currency": "USDC", + "stake_amount": "unlimited", + "tradable_balance_ratio": 0.99, + "fiat_display_currency": "USD", + "dry_run": true, + "db_url": "sqlite:///tradesv3.chan.sqlite", + "dry_run_wallet": 1000, + "cancel_open_orders_on_exit": true, + "trading_mode": "futures", + "margin_mode": "isolated", + "can_short" : true, + "timeframe" : "1m", + "process_only_new_candles" : false, + "unfilledtimeout": { + "entry": 1, + "exit": 1, + "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": "hyperliquid", + "walletAddress": "0xA834b6d3Fa1D8A55ea8e502685ef5cbD2b2D3343", + "privateKey": "0xa399cea4c01be67c16b88e1d2121ed6e72bab6b4e8d81684ed03ce78f8b4f827", + "ccxt_config": {}, + "ccxt_async_config": {}, + "pair_whitelist": [ + "PURR/USDC:USDC", + ], + "pair_blacklist": [ + "BNB/.*" + ] + }, + "pairlists": [ + { + "method": "StaticPairList", + "number_assets": 1, + "sort_key": "quoteVolume", + "min_value": 0, + "refresh_period": 1800, + } + ], + "telegram": { + "enabled": false, + "token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y", + "chat_id": "580807463" + }, + "api_server": { + "enabled": true, + "listen_ip_address": "127.0.0.1", + "listen_port": 8815, + "verbosity": "error", + "enable_openapi": false, + "jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d", + "ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg", + "CORS_origins": [], + "username": "freqtrader", + "password": "FreqTrade007" + }, + "bot_name": "freqtrade", + "initial_state": "running", + "force_entry_enable": false, + "internals": { + "process_throttle_secs": 2 + } +} \ No newline at end of file diff --git a/config/Chan_SOL_30.json b/config/Chan_SOL_30.json new file mode 100644 index 0000000..c8d25cd --- /dev/null +++ b/config/Chan_SOL_30.json @@ -0,0 +1,84 @@ + +{ + "$schema": "https://schema.freqtrade.io/schema.json", + "max_open_trades": 1, + "stake_currency": "USDT", + "stake_amount": "unlimited", + "tradable_balance_ratio": 0.99, + "fiat_display_currency": "USD", + "dry_run": true, + "db_url": "sqlite:///tradesv3.chan_sol_30.sqlite", + "dry_run_wallet": 1000, + "cancel_open_orders_on_exit": true, + "trading_mode": "futures", + "margin_mode": "isolated", + "can_short" : true, + "timeframe" : "1m", + "process_only_new_candles" : false, + "unfilledtimeout": { + "entry": 1, + "exit": 1, + "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": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8", + "secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l", + "ccxt_config": {}, + "ccxt_async_config": {}, + "pair_whitelist": [ + "SOL/USDT:USDT", + ], + "pair_blacklist": [ + "BNB/.*" + ] + }, + "pairlists": [ + { + "method": "StaticPairList", + "number_assets": 1, + "sort_key": "quoteVolume", + "min_value": 0, + "refresh_period": 1800, + } + ], + "telegram": { + "enabled": false, + "token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y", + "chat_id": "580807463" + }, + "api_server": { + "enabled": true, + "listen_ip_address": "127.0.0.1", + "listen_port": 8818, + "verbosity": "error", + "enable_openapi": false, + "jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d", + "ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg", + "CORS_origins": [], + "username": "freqtrader", + "password": "FreqTrade007" + }, + "bot_name": "freqtrade", + "initial_state": "running", + "force_entry_enable": false, + "internals": { + "process_throttle_secs": 2 + } +} \ No newline at end of file diff --git a/config/Chan_SOL_Day.json b/config/Chan_SOL_Day.json new file mode 100644 index 0000000..49b824c --- /dev/null +++ b/config/Chan_SOL_Day.json @@ -0,0 +1,84 @@ + +{ + "$schema": "https://schema.freqtrade.io/schema.json", + "max_open_trades": 1, + "stake_currency": "USDT", + "stake_amount": "unlimited", + "tradable_balance_ratio": 0.99, + "fiat_display_currency": "USD", + "dry_run": true, + "db_url": "sqlite:///tradesv3.chan.sqlite", + "dry_run_wallet": 1000, + "cancel_open_orders_on_exit": true, + "trading_mode": "futures", + "margin_mode": "isolated", + "can_short" : true, + "timeframe" : "1m", + "process_only_new_candles" : false, + "unfilledtimeout": { + "entry": 1, + "exit": 1, + "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": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8", + "secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l", + "ccxt_config": {}, + "ccxt_async_config": {}, + "pair_whitelist": [ + "BTC/USDT:USDT", + ], + "pair_blacklist": [ + "BNB/.*" + ] + }, + "pairlists": [ + { + "method": "StaticPairList", + "number_assets": 1, + "sort_key": "quoteVolume", + "min_value": 0, + "refresh_period": 1800, + } + ], + "telegram": { + "enabled": false, + "token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y", + "chat_id": "580807463" + }, + "api_server": { + "enabled": true, + "listen_ip_address": "127.0.0.1", + "listen_port": 8801, + "verbosity": "error", + "enable_openapi": false, + "jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d", + "ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg", + "CORS_origins": [], + "username": "freqtrader", + "password": "FreqTrade007" + }, + "bot_name": "freqtrade", + "initial_state": "running", + "force_entry_enable": false, + "internals": { + "process_throttle_secs": 2 + } +} \ No newline at end of file diff --git a/config/Deepseek_Trader.json b/config/Deepseek_Trader.json new file mode 100644 index 0000000..8c28f46 --- /dev/null +++ b/config/Deepseek_Trader.json @@ -0,0 +1,93 @@ + +{ + "$schema": "https://schema.freqtrade.io/schema.json", + "max_open_trades": 3, + "stake_currency": "USDT", + "stake_amount": "unlimited", + "tradable_balance_ratio": 0.99, + "fiat_display_currency": "USD", + "db_url": "sqlite:///tradesv3.deepseek_trader.sqlite", + "dry_run": true, + "dry_run_wallet": 10000, + "cancel_open_orders_on_exit": true, + "trading_mode": "futures", + "margin_mode": "isolated", + "can_short" : true, + "timeframe" : "1m", + "process_only_new_candles" : false, + "unfilledtimeout": { + "entry": 1, + "exit": 1, + "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": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8", + "secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l", + "ccxt_config": {}, + "ccxt_async_config": {}, + "pair_whitelist": [ + "BTC/USDT:USDT", + "ETH/USDT:USDT", + "SOL/USDT:USDT", + "WIF/USDT:USDT", + "1000PEPE/USDT:USDT", + "DOGS/USDT:USDT", + "ORDI/USDT:USDT", + "AAVE/USDT:USDT", + "REEF/USDT:USDT", + "1000SATS/USDT:USDT", + ], + "pair_blacklist": [ + "BNB/.*" + ] + }, + "pairlists": [ + { + "method": "StaticPairList", + "number_assets": 10, + "sort_key": "quoteVolume", + "min_value": 0, + "refresh_period": 1800 + } + ], + "telegram": { + "enabled": true, + "token": "5985766683:AAEx2Nm_4y2IC0Tj4Hhz7djVRJRso0JKaj0", + "chat_id": "580807463" + }, + "api_server": { + "enabled": true, + "listen_ip_address": "127.0.0.1", + "listen_port": 8001, + "verbosity": "error", + "enable_openapi": false, + "jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d", + "ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg", + "CORS_origins": [], + "username": "freqtrader", + "password": "FreqTrade007" + }, + "bot_name": "freqtrade", + "initial_state": "running", + "force_entry_enable": true, + "internals": { + "process_throttle_secs": 15 + } +} \ No newline at end of file diff --git a/strategies/ChanLun_SOL.py b/strategies/ChanLun_SOL.py new file mode 100644 index 0000000..8823435 --- /dev/null +++ b/strategies/ChanLun_SOL.py @@ -0,0 +1,323 @@ +# --- Do not remove these libs --- +from freqtrade.strategy import IStrategy +from typing import Dict, List +from functools import reduce +from pandas import DataFrame, pandas +import freqtrade.vendor.qtpylib.indicators as qtpylib + +import sys +import os +#sys.setrecursionlimit(1000000) #例如这里设置为一百万 +#sys.path.append(os.path.abspath("/freqtrade/user_data/Chan")) +#sys.path.append(os.path.abspath("/Users/jack/Project/freqtrade/user_data/Chan")) +sys.path.append(os.path.abspath("/Users/jack/Documents/GitHub/freqtrade/user_data/Chan")) +from ChanLun import ChanLun +# -------------------------------- +from technical.util import resample_to_interval, resampled_merge +import talib.abstract as ta +import freqtrade.vendor.qtpylib.indicators as qtpylib +from datetime import datetime, timedelta, timezone +from freqtrade.persistence import Trade, Order +from typing import Optional +import logging +logger = logging.getLogger(__name__) + +### Now you can use logger.info('asfd') to log + +# freqtrade trade -c ./user_data/ChanLun_SOL.json --strategy ChanLun_SOL --strategy-path ./user_data/strategies +# freqtrade backtesting -c ./user_data/ChanLun_SOL.json --strategy ChanLun_SOL --strategy-path ./user_data/strategies --timerange=20250309- +# freqtrade download-data -c ./user_data/ChanLun_SOL.json -t 1m --pairs SOL/USDT:USDT --timerange=20250101- +# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss --strategy ChanLun_SOL --strategy-path ./user_data/strategies -c ./user_data/ChanLun_SOL.json -e 200 --timerange=20241101-20250201 + +# sudo docker compose run --rm chan_btc backtesting -c ./user_data/ChanLun_SOL.json --strategy ChanLun_SOL --strategy-path ./user_data/strategies --timerange=20250101- +# sudo docker compose run --rm chan_btc download-data -c ./user_data/ChanLun_SOL.json --pairs SOL/USDT:USDT -t 1m --timerange 20240101- +# sudo docker compose run --rm chan_btc trade -c ./user_data/ChanLun_SOL.json --strategy ChanLun_SOL --strategy-path ./user_data/strategies + +class ChanLun_SOL(IStrategy): + INTERFACE_VERSION: int = 3 + # Minimal ROI designed for the strategy. + # This attribute will be overridden if the config file contains "minimal_roi" + # 30m and 1h + minimal_roi = { + "0": 0.253, + "480": 0.159, + "960": 0.052, + "1440": 0 + } + # 5m and 15m + minimal_roi_1 = { + "0": 0.253, + "60": 0.159, + "120": 0.052, + "240": 0 + } + # 15m and 30m + minimal_roi_1 = { + "0": 0.253, + "120": 0.159, + "240": 0.052, + "360": 0 + } + can_short = True + # Optimal stoploss designed for the strategy + # This attribute will be overridden if the config file contains "stoploss" + stoploss = -0.21 + + trailing_stop = False + trailing_stop_positive = 0.015 + trailing_stop_positive_offset = 0.043 + trailing_only_offset_is_reached = False + + + position_adjustment_enable = True + # Example specific variables + max_entry_position_adjustment = 3 + # This number is explained a bit further down + max_dca_multiplier = 5.5 + + + # Optimal timeframe for the strategy + # timeframe = '15m' + startup_candle_count = 2000 + + time5 = 5 + time15 = 15 + time30 = 30 + time60 = 60 + time4h = 240 + last_time = datetime.now() + big_size = 0 + big_state = "00" + big_state_list = [] + chan = ChanLun() + small_size = 0 + small_state = "00" + small_state_list = [] + + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + + # resample our dataframes + dataframe_5 = resample_to_interval(dataframe, self.get_ticker_indicator() * 5) + dataframe_15 = resample_to_interval(dataframe, self.get_ticker_indicator() * 15) + dataframe_30 = resample_to_interval(dataframe, self.get_ticker_indicator() * 30) + dataframe_60 = resample_to_interval(dataframe, self.get_ticker_indicator() * 60) + #dataframe_4h = resample_to_interval(dataframe, self.get_ticker_indicator() * 240) + + #dataframe_1d = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1d') + #dataframe_1w = resample_to_interval(dataframe_1d, self.get_ticker_indicator() * 10080) + #dataframe_1m = resample_to_interval(dataframe_1d, self.get_ticker_indicator() * 43200) + + #dataframe_1d = resample_to_interval(dataframe, self.get_ticker_indicator() * 1440) + #dataframe_1w = resample_to_interval(dataframe, self.get_ticker_indicator() * 10080) + #dataframe_1m = resample_to_interval(dataframe, self.get_ticker_indicator() * 43200) + + #dataframe_5['state'] = self.chan.cal_klu_state(dataframe_5) + #dataframe_15['state'] = self.chan.cal_klu_state(dataframe_15) + #dataframe_30['state'] = self.chan.cal_klu_state(dataframe_30) + dataframe_60['state'] = self.chan.cal_klu_state(dataframe_60) + #dataframe_4h['state'] = self.chan.resample_klc_list(dataframe_4h) + #dataframe_5['bsps'], dataframe_5['updown'], dataframe_5['bi_sure'] = self.chanpy.get_bsps(dataframe_5) + if self.last_time + timedelta(minutes=1) < datetime.now(): + #print(informative.iloc[-1]) + self.print_fx(dataframe, 1) + self.print_fx(dataframe_5, 5) + self.print_fx(dataframe_15, 15) + self.print_fx(dataframe_30, 30) + #self.print_fx(dataframe_60, 60) + print("-------------------------------------------------------------------------------") + self.last_time = datetime.now() + #self.print_fx(dataframe_4h, "4h") + #self.print_fx_list(dataframe_15) + #self.print_fx(dataframe_30, 30) + #self.print_fx_list(dataframe_5) + #print(dataframe_60['high'].rolling(window).max()) + #print(dataframe_60['low'].rolling(window).min()) + #for index in range(0, len(dataframe_5)): + #print(dataframe_5[dataframe_5['bi_sure'] == 1][dataframe_5['state'] == "-10"]) + #print(dataframe_5[dataframe_5['bi_sure'] == 1][dataframe_5['state'] == "10"]) + #dataframe = resampled_merge(dataframe, dataframe_5) + #dataframe = resampled_merge(dataframe, dataframe_15) + #dataframe = resampled_merge(dataframe, dataframe_30) + dataframe = resampled_merge(dataframe, dataframe_60) + #dataframe = resampled_merge(dataframe, dataframe_4h) + return dataframe + # This is called when placing the initial order (opening trade) + def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, + proposed_stake: float, min_stake: float | None, max_stake: float, + leverage: float, entry_tag: str | None, side: str, + **kwargs) -> float: + + # We need to leave most of the funds for possible further DCA orders + # This also applies to fixed stakes + return proposed_stake / self.max_dca_multiplier + def adjust_trade_position(self, trade: Trade, current_time: datetime, + current_rate: float, current_profit: float, + min_stake: float | None, max_stake: float, + current_entry_rate: float, current_exit_rate: float, + current_entry_profit: float, current_exit_profit: float, + **kwargs + ) -> float | None | tuple[float | None, str | None]: + """ + Custom trade adjustment logic, returning the stake amount that a trade should be + increased or decreased. + This means extra entry or exit orders with additional fees. + Only called when `position_adjustment_enable` is set to True. + + For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/ + + When not implemented by a strategy, returns None + + :param trade: trade object. + :param current_time: datetime object, containing the current datetime + :param current_rate: Current entry rate (same as current_entry_profit) + :param current_profit: Current profit (as ratio), calculated based on current_rate + (same as current_entry_profit). + :param min_stake: Minimal stake size allowed by exchange (for both entries and exits) + :param max_stake: Maximum stake allowed (either through balance, or by exchange limits). + :param current_entry_rate: Current rate using entry pricing. + :param current_exit_rate: Current rate using exit pricing. + :param current_entry_profit: Current profit using entry pricing. + :param current_exit_profit: Current profit using exit pricing. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + :return float: Stake amount to adjust your trade, + Positive values to increase position, Negative values to decrease position. + Return None for no action. + Optionally, return a tuple with a 2nd element with an order reason + """ + #if trade.has_open_orders: + # Only act if no orders are open + #return + + #if current_profit > 0.05 and trade.nr_of_successful_exits == 0: + # Take half of the profit at +5% + #return -(trade.stake_amount / 2), "half_profit_5%" + + #if current_profit > -0.05: + #return None + + # Obtain pair dataframe (just to show how to access it) + dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe) + # Only buy when not actively falling price. + #last_candle = dataframe.iloc[-1].squeeze() + #previous_candle = dataframe.iloc[-2].squeeze() + #if last_candle["close"] < previous_candle["close"]: + #return None + filled_entries = trade.select_filled_orders(trade.entry_side) + last_entry = filled_entries[-1] + count_of_entries = trade.nr_of_successful_entries + # Allow up to 3 additional increasingly larger buys (4 in total) + # Initial buy is 1x + # If that falls to -5% profit, we buy 1.25x more, average profit should increase to roughly -2.2% + # If that falls down to -5% again, we buy 1.5x more + # If that falls once again down to -5%, we buy 1.75x more + # Total stake for this trade would be 1 + 1.25 + 1.5 + 1.75 = 5.5x of the initial allowed stake. + # That is why max_dca_multiplier is 5.5 + # Hope you have a deep wallet! + # This returns first order stake size + #print(dataframe.iloc[-1]['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)]) + + # This returns first order stake size + stake_amount = filled_entries[0].stake_amount + # This then calculates current safety order size + stake_amount = stake_amount * (1 + (count_of_entries * 0.5)) + dataframe_date = dataframe.iloc[-1]['date'] + #print(stake_amount, "---------------------------------------------------") + if last_entry.order_filled_utc + timedelta(minutes=60) < dataframe_date: + if dataframe.iloc[-self.time60]['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "-10" and last_entry.side == "buy": + print(dataframe.iloc[-self.time60]) + print(stake_amount) + return stake_amount, "1/3rd_increase" + if last_entry.order_filled_utc + timedelta(minutes=60) < dataframe_date: + if dataframe.iloc[-self.time60]['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10" and last_entry.side == "sell": + print(dataframe.iloc[-self.time60]) + print(stake_amount) + return stake_amount, "1/3rd_increase" + return None + + def print_fx(self, df, label=5): + fx_list = self.chan.get_bsp_list(self.chan.get_klc_list(df)) + fx1 = fx_list[-1] + fx2 = fx_list[-2] + fx3 = fx_list[-3] + fx4 = fx_list[-4] + fx5 = fx_list[-5] + #print(fx1.end_time, fx1.fx, fx2.end_time, fx2.fx, fx3.end_time, fx3.fx) + logger.info(f'\n{fx5.end_time} {fx5.state} {fx4.end_time} {fx4.state} {fx3.end_time} {fx3.state} {fx2.end_time} {fx2.state} {fx1.end_time} {fx1.state} TF: {label}') + def print_fx_list(self, df): + bsp_list = self.chan.get_bsp_list(self.chan.get_klc_list(df)) + for bsp in bsp_list: + logger.info(f'{bsp.start_time}, {bsp.end_time}, {bsp.fx}') + def print_df(self, df): + for index in range(0, len(df)): + state = 'state' + rsi = 'rsi' + logger.info(f'{df[state][index]}, {df[rsi][index]}, {df[state][index]}') + def print_resample_df(self, df, time): + for index in range(0, len(df)): + cn1 = 'resample_{}_date'.format(self.get_ticker_indicator()*time) + cn2 = 'resample_{}_rsi'.format(self.get_ticker_indicator()*time) + cn3 = 'resample_{}_state'.format(self.get_ticker_indicator()*time) + logger.info(f'{df[cn1][index]}, {df[cn2][index]}, {df[cn3][index]}') + def local_print(self, df): + fast = 7 + slow = 14 + macd = ta.MACD(df, fast=fast, slow=slow) + df['macd'] = macd['macd'] + df['macdsignal'] = macd['macdsignal'] + df['macdhist'] = macd['macdhist'] + df['ema26'] = ta.EMA(df, timeperiod=26) + df['ema52'] = ta.EMA(df, timeperiod=52) + df['ma5'] = ta.MA(df, timeperiod=5) + df['ma10'] = ta.MA(df, timeperiod=10) + df['masub'] = df['ma5'].subtract(df['ma10']) + #for index in range(0, len(df)): + #print(df['date'][index], df['macdhist'][index], df['macd'][index], df['macdsignal'][index], df['masub'][index]) + # (1,1) = 1, (1,0) = 2, (-1,1) = 3, (-1, 0) = 4, (0,0) = 0 + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + + dataframe.loc[ + ( + #(dataframe['state'].shift(1) == "-10") & + #((dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)] == "-11") | + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "-10") & + (dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)].shift(self.time60) == "-10") + #(qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal'])) + ), + ['enter_long', 'enter_tag']] = (1, 'long_signal_chan') + dataframe.loc[ + ( + #(dataframe['state'].shift(1) == "10") & + #((dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)] == "11") | + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") & + (dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)].shift(self.time60) == "10") + #(qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal'])) + ), + ['enter_short', 'enter_tag']] = (1, 'short_signal_chan') + return dataframe + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe.loc[ + ( + #(dataframe['state'].shift(1) == "10") & + (dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)].shift(self.time60) == "10") + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10") + ), + ['exit_long', 'exit_tag']] = (1, 'long_close_signal_chan') + dataframe.loc[ + ( + #(dataframe['state'].shift(1) == "-10") & + (dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)].shift(self.time60) == "-10") + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "-10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "-10") + ), + ['exit_short', 'exit_tag']] = (1, 'short_close_signal_chan') + return dataframe + def leverage(self, pair: str, current_time: datetime, current_rate: float, + proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str, + **kwargs) -> float: + return 1.0 + + def get_ticker_indicator(self): + return int(self.timeframe[:-1]) \ No newline at end of file diff --git a/strategies/ChanLun_SOL_1.py b/strategies/ChanLun_SOL_1.py new file mode 100644 index 0000000..f42b21b --- /dev/null +++ b/strategies/ChanLun_SOL_1.py @@ -0,0 +1,207 @@ +# --- Do not remove these libs --- +from freqtrade.strategy import IStrategy +from typing import Dict, List +from functools import reduce +from pandas import DataFrame, pandas +import freqtrade.vendor.qtpylib.indicators as qtpylib + +import sys +import os +#sys.setrecursionlimit(1000000) #例如这里设置为一百万 +#sys.path.append(os.path.abspath("/freqtrade/user_data/Chan")) +#sys.path.append(os.path.abspath("/Users/jack/Project/freqtrade/user_data/Chan")) +sys.path.append(os.path.abspath("/Users/jack/Documents/GitHub/freqtrade/user_data/Chan")) +from ChanLun import ChanLun +# -------------------------------- +from technical.util import resample_to_interval, resampled_merge +import talib.abstract as ta +import freqtrade.vendor.qtpylib.indicators as qtpylib +from datetime import datetime, timedelta, timezone +from freqtrade.persistence import Trade, Order +from typing import Optional +import logging +logger = logging.getLogger(__name__) + +### Now you can use logger.info('asfd') to log + +# freqtrade trade -c ./user_data/ChanLun_SOL.json --strategy ChanLun_SOL_1 --strategy-path ./user_data/strategies +# freqtrade backtesting -c ./user_data/ChanLun_SOL.json --strategy ChanLun_SOL --strategy-path ./user_data/strategies --timerange=20250309- +# freqtrade download-data -c ./user_data/ChanLun_SOL.json -t 1m --pairs SOL/USDT:USDT --timerange=20240101- +# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss --strategy ChanLun_SOL --strategy-path ./user_data/strategies -c ./user_data/ChanLun_SOL.json -e 200 --timerange=20241101-20250201 + +# sudo docker compose run --rm chan_btc backtesting -c ./user_data/ChanLun_SOL.json --strategy ChanLun_SOL --strategy-path ./user_data/strategies --timerange=20250101- +# sudo docker compose run --rm chan_btc download-data -c ./user_data/ChanLun_SOL.json --pairs SOL/USDT:USDT -t 1m --timerange 20240101- +# sudo docker compose run --rm chan_btc trade -c ./user_data/ChanLun_SOL.json --strategy ChanLun_SOL --strategy-path ./user_data/strategies + +class ChanLun_SOL_1(IStrategy): + INTERFACE_VERSION: int = 3 + # Minimal ROI designed for the strategy. + # This attribute will be overridden if the config file contains "minimal_roi" + minimal_roi = { + "0": 0.253, + "480": 0.159, + "960": 0.052, + "1440": 0 + } + can_short = True + # Optimal stoploss designed for the strategy + # This attribute will be overridden if the config file contains "stoploss" + stoploss = -0.21 + + trailing_stop = False + trailing_stop_positive = 0.015 + trailing_stop_positive_offset = 0.043 + trailing_only_offset_is_reached = False + + # Optimal timeframe for the strategy + # timeframe = '15m' + startup_candle_count = 2000 + + time5 = 5 + time15 = 15 + time30 = 30 + time60 = 60 + time4h = 240 + last_time = datetime.now() + big_size = 0 + big_state = "00" + big_state_list = [] + chan = ChanLun() + small_size = 0 + small_state = "00" + small_state_list = [] + + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + + # resample our dataframes + dataframe_5 = resample_to_interval(dataframe, self.get_ticker_indicator() * 5) + dataframe_15 = resample_to_interval(dataframe, self.get_ticker_indicator() * 15) + dataframe_30 = resample_to_interval(dataframe, self.get_ticker_indicator() * 30) + dataframe_60 = resample_to_interval(dataframe, self.get_ticker_indicator() * 60) + #dataframe_4h = resample_to_interval(dataframe, self.get_ticker_indicator() * 240) + + #dataframe_1d = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1d') + #dataframe_1w = resample_to_interval(dataframe_1d, self.get_ticker_indicator() * 10080) + #dataframe_1m = resample_to_interval(dataframe_1d, self.get_ticker_indicator() * 43200) + + #dataframe_1d = resample_to_interval(dataframe, self.get_ticker_indicator() * 1440) + #dataframe_1w = resample_to_interval(dataframe, self.get_ticker_indicator() * 10080) + #dataframe_1m = resample_to_interval(dataframe, self.get_ticker_indicator() * 43200) + + dataframe_5['state'] = self.chan.cal_klu_state(dataframe_5) + dataframe_15['state'] = self.chan.cal_klu_state(dataframe_15) + dataframe_30['state'] = self.chan.cal_klu_state(dataframe_30) + dataframe_60['state'] = self.chan.cal_klu_state(dataframe_60) + #dataframe_4h['state'] = self.chan.resample_klc_list(dataframe_4h) + #dataframe_5['bsps'], dataframe_5['updown'], dataframe_5['bi_sure'] = self.chanpy.get_bsps(dataframe_5) + if self.last_time + timedelta(minutes=1) < datetime.now(): + #print(informative.iloc[-1]) + self.print_fx(dataframe, 1) + self.print_fx(dataframe_5, 5) + self.print_fx(dataframe_15, 15) + self.print_fx(dataframe_30, 30) + #self.print_fx(dataframe_60, 60) + print("-------------------------------------------------------------------------------") + self.last_time = datetime.now() + #self.print_fx(dataframe_4h, "4h") + #self.print_fx_list(dataframe_15) + #self.print_fx(dataframe_30, 30) + #self.print_fx_list(dataframe_5) + #print(dataframe_60['high'].rolling(window).max()) + #print(dataframe_60['low'].rolling(window).min()) + #for index in range(0, len(dataframe_5)): + #print(dataframe_5[dataframe_5['bi_sure'] == 1][dataframe_5['state'] == "-10"]) + #print(dataframe_5[dataframe_5['bi_sure'] == 1][dataframe_5['state'] == "10"]) + #dataframe = resampled_merge(dataframe, dataframe_5) + #dataframe = resampled_merge(dataframe, dataframe_15) + dataframe = resampled_merge(dataframe, dataframe_30) + dataframe = resampled_merge(dataframe, dataframe_60) + #dataframe = resampled_merge(dataframe, dataframe_4h) + return dataframe + def print_fx(self, df, label=5): + fx_list = self.chan.get_bsp_list(self.chan.get_klc_list(df)) + fx1 = fx_list[-1] + fx2 = fx_list[-2] + fx3 = fx_list[-3] + fx4 = fx_list[-4] + fx5 = fx_list[-5] + #print(fx1.end_time, fx1.fx, fx2.end_time, fx2.fx, fx3.end_time, fx3.fx) + logger.info(f'\n{fx5.end_time} {fx5.state} {fx4.end_time} {fx4.state} {fx3.end_time} {fx3.state} {fx2.end_time} {fx2.state} {fx1.end_time} {fx1.state} TF: {label}') + def print_fx_list(self, df): + bsp_list = self.chan.get_bsp_list(self.chan.get_klc_list(df)) + for bsp in bsp_list: + logger.info(f'{bsp.start_time}, {bsp.end_time}, {bsp.fx}') + def print_df(self, df): + for index in range(0, len(df)): + state = 'state' + rsi = 'rsi' + logger.info(f'{df[state][index]}, {df[rsi][index]}, {df[state][index]}') + def print_resample_df(self, df, time): + for index in range(0, len(df)): + cn1 = 'resample_{}_date'.format(self.get_ticker_indicator()*time) + cn2 = 'resample_{}_rsi'.format(self.get_ticker_indicator()*time) + cn3 = 'resample_{}_state'.format(self.get_ticker_indicator()*time) + logger.info(f'{df[cn1][index]}, {df[cn2][index]}, {df[cn3][index]}') + def local_print(self, df): + fast = 7 + slow = 14 + macd = ta.MACD(df, fast=fast, slow=slow) + df['macd'] = macd['macd'] + df['macdsignal'] = macd['macdsignal'] + df['macdhist'] = macd['macdhist'] + df['ema26'] = ta.EMA(df, timeperiod=26) + df['ema52'] = ta.EMA(df, timeperiod=52) + df['ma5'] = ta.MA(df, timeperiod=5) + df['ma10'] = ta.MA(df, timeperiod=10) + df['masub'] = df['ma5'].subtract(df['ma10']) + #for index in range(0, len(df)): + #print(df['date'][index], df['macdhist'][index], df['macd'][index], df['macdsignal'][index], df['masub'][index]) + # (1,1) = 1, (1,0) = 2, (-1,1) = 3, (-1, 0) = 4, (0,0) = 0 + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + + dataframe.loc[ + ( + #(dataframe['state'].shift(1) == "-10") & + #((dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)] == "-11") | + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "-10") & + (dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)].shift(self.time30) == "-100") + #(qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal'])) + ), + ['enter_long', 'enter_tag']] = (1, 'long_signal_chan') + dataframe.loc[ + ( + #(dataframe['state'].shift(1) == "10") & + #((dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)] == "11") | + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") & + (dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)].shift(self.time30) == "100") + #(qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal'])) + ), + ['enter_short', 'enter_tag']] = (1, 'short_signal_chan') + return dataframe + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe.loc[ + ( + #(dataframe['state'].shift(1) == "10") & + (dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)].shift(self.time60) == "10") + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10") + ), + ['exit_long', 'exit_tag']] = (1, 'long_close_signal_chan') + dataframe.loc[ + ( + #(dataframe['state'].shift(1) == "-10") & + (dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)].shift(self.time60) == "-10") + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "-10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "-10") + ), + ['exit_short', 'exit_tag']] = (1, 'short_close_signal_chan') + return dataframe + def leverage(self, pair: str, current_time: datetime, current_rate: float, + proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str, + **kwargs) -> float: + return 1 + + def get_ticker_indicator(self): + return int(self.timeframe[:-1]) \ No newline at end of file diff --git a/strategies/ChanLun_SOL_5.py b/strategies/ChanLun_SOL_5.py new file mode 100644 index 0000000..d63b18c --- /dev/null +++ b/strategies/ChanLun_SOL_5.py @@ -0,0 +1,410 @@ +# --- Do not remove these libs --- +from freqtrade.strategy import IStrategy +import sys +import os +#sys.setrecursionlimit(1000000) #例如这里设置为一百万 +#sys.path.append(os.path.abspath("/freqtrade/user_data/Chan")) +#sys.path.append(os.path.abspath("/Users/jack/Project/freqtrade/user_data/Chan")) +sys.path.append(os.path.abspath("/Users/jack/Documents/GitHub/freqtrade/user_data/Chan")) +from ChanLun import ChanLun +from ChanLun_Classifier import ChanLunClassifier +from ChanEnum import Chan_FX_TYPE +# -------------------------------- +from technical.util import resample_to_interval, resampled_merge +import talib.abstract as ta +from pandas import DataFrame +from datetime import datetime, timedelta +from freqtrade.persistence import Trade +from typing import Optional +import logging +logger = logging.getLogger(__name__) +### Now you can use logger.info('asfd') to log +# freqtrade plot-dataframe --strategy ChanLun_SOL_5 --datadir user_data/data/binance -c ./user_data/ChanLun_SOL.json --timerange=20250309- + +# freqtrade trade -c ./user_data/ChanLun_SOL.json --strategy ChanLun_SOL_5 --strategy-path ./user_data/strategies +# freqtrade backtesting -c ./user_data/ChanLun_SOL.json --strategy ChanLun_SOL_5 --strategy-path ./user_data/strategies --timerange=20250416- +# freqtrade download-data -c ./user_data/ChanLun_SOL.json -t 1m --pairs SOL/USDT:USDT --timerange=20250405- +# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss --strategy ChanLun_SOL_5 --strategy-path ./user_data/strategies -c ./user_data/ChanLun_SOL.json -e 200 --timerange=20250201-20250401 + +# sudo docker compose run --rm chan_btc backtesting -c ./user_data/ChanLun_SOL.json --strategy ChanLun_SOL --strategy-path ./user_data/strategies --timerange=20250101- +# sudo docker compose run --rm chan_btc download-data -c ./user_data/ChanLun_SOL.json --pairs SOL/USDT:USDT -t 1m --timerange 20240101- +# sudo docker compose run --rm chan_btc trade -c ./user_data/ChanLun_SOL.json --strategy ChanLun_SOL --strategy-path ./user_data/strategies + +class ChanLun_SOL_5(IStrategy): + INTERFACE_VERSION: int = 3 + # Minimal ROI designed for the strategy. + # This attribute will be overridden if the config file contains "minimal_roi" + # 30m and 1h + minimal_roi = { + "0": 0.10, + "360": 0.05, + "640": 0.025, + "1200": 0 + } + # 5m and 15m + minimal_roi_1 = { + "0": 0.253, + "60": 0.159, + "120": 0.052, + "240": 0 + } + # 15m and 30m + minimal_roi_1 = { + "0": 0.253, + "120": 0.159, + "240": 0.052, + "360": 0 + } + can_short = True + stoploss = -0.20 + trailing_stop = False + trailing_stop_positive = 0.015 + trailing_stop_positive_offset = 0.043 + trailing_only_offset_is_reached = False + + position_adjustment_enable = True + max_entry_position_adjustment = 3 + max_dca_multiplier = 5.5 + startup_candle_count = 600 + + time5 = 5 + time15 = 15 + time30 = 30 + time60 = 60 + time4h = 240 + time5 = 30 + last_time = datetime.now() + big_size = 0 + big_state = "00" + big_state_list = [] + chan = ChanLun() + small_size = 0 + small_state = "00" + small_state_list = [] + classifier = ChanLunClassifier(None) + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + + # resample our dataframes + dataframe_5 = resample_to_interval(dataframe, self.get_ticker_indicator() * 5) + dataframe_15 = resample_to_interval(dataframe, self.get_ticker_indicator() * 15) + dataframe_30 = resample_to_interval(dataframe, self.get_ticker_indicator() * 30) + dataframe_60 = resample_to_interval(dataframe, self.get_ticker_indicator() * 60) + dataframe_4h = resample_to_interval(dataframe, self.get_ticker_indicator() * 240) + + #dataframe_1d = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1d') + #dataframe_1w = resample_to_interval(dataframe_1d, self.get_ticker_indicator() * 10080) + #dataframe_1m = resample_to_interval(dataframe_1d, self.get_ticker_indicator() * 43200) + + dataframe_1d = resample_to_interval(dataframe, self.get_ticker_indicator() * 1440) + #dataframe_1w = resample_to_interval(dataframe, self.get_ticker_indicator() * 10080) + #dataframe_1m = resample_to_interval(dataframe, self.get_ticker_indicator() * 43200) + dataframe = self.add_indicators(dataframe) + dataframe_5 = self.add_indicators(dataframe_5) + dataframe_30 = self.add_indicators(dataframe_30) + dataframe_60 = self.add_indicators(dataframe_60) + dataframe_4h = self.add_indicators(dataframe_4h) + dataframe_1d = self.add_indicators(dataframe_1d) + dataframe['state'] = self.chan.plot_dataframe(dataframe) + #dataframe_5['state'] = self.chan.plot_dataframe(dataframe_5) + #self.chan.print_data(dataframe_5) + #self.chan.cal_qjt(dataframe, dataframe_5) + #self.classifier.train_model(dataframe_4h, model_name="4h_model") + """ + self.classifier.train_model(dataframe_5, model_name="5m_model") + self.classifier.train_model(dataframe_30, model_name="30m_model") + self.classifier.train_model(dataframe_60, model_name="1h_model") + self.classifier.train_model(dataframe_4h, model_name="4h_model") + self.classifier.train_model(dataframe, model_name="1m_model") + self.classifier.train_model(dataframe_1d, model_name="1d_model") + """ + + """ + if self.classifier.model is None: + self.classifier.train_model(dataframe_30, model_name="30m_model") + self.classifier.load_model(model_name="30m_model") + klc_list = self.chan.get_klc_list(dataframe_30) + top_avg = 0 + bottom_avg = 0 + top_count = 0 + bottom_count = 0 + for index in range(int(len(klc_list) * 0.8), len(klc_list)): + klc = klc_list[index] + if self.classifier.predict(klc) > 0.01 and klc.fx == Chan_FX_TYPE.BOTTOM: + features = klc.get_feature_data() + print(klc.end_time, klc.fx, self.classifier.predict(klc), features['klc_macd'], features['klc_macd_hist'], features['klc_rsi'], features['klc_macd_signal']) + bottom_avg += self.classifier.predict(klc) + bottom_count += 1 + if self.classifier.predict(klc) > 0.05 and klc.fx == Chan_FX_TYPE.TOP: + features = klc.get_feature_data() + print(klc.end_time, klc.fx, self.classifier.predict(klc), features['klc_macd'], features['klc_macd_hist'], features['klc_rsi'], features['klc_macd_signal']) + top_avg += self.classifier.predict(klc) + top_count += 1 + bottom_avg /= bottom_count + top_avg /= top_count + print(bottom_avg, top_avg) + print("-------------------------------------------------------------------------------") + """ + """ + self.print_xgb(dataframe, "1m_model") + self.print_xgb(dataframe_5, "5m_model") + self.print_xgb(dataframe_30, "30m_model") + self.print_xgb(dataframe_60, "1h_model") + self.print_xgb(dataframe_4h, "4h_model") + print("-------------------------------------------------------------------------------") + """ + + #classifier.find_best_params(dataframe) + #classifier.train_model(use_cv=False) + #classifier.validate_model(dataframe) + #self.chan.get_bsp_list(dataframe) + #dataframe_5['state'] = self.chan.cal_klu_state(dataframe_5) + #dataframe_15['state'] = self.chan.cal_klu_state(dataframe_15) + #dataframe_30['state'] = self.chan.cal_klu_state(dataframe_30) + #dataframe_60['state'] = self.chan.cal_klu_state(dataframe_60) + #dataframe_4h['state'] = self.chan.resample_klc_list(dataframe_4h) + + self.chan.plot_dual(dataframe_30, dataframe_60) + dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) + #self.print_macd_div_list(dataframe) + #self.print_resample_df(dataframe, 1, 50) + self.chan.get_bi_list(dataframe_30) + if self.last_time + timedelta(minutes=1) < datetime.now(): + #print(informative.iloc[-1]) + #self.log_macd_div_list(dataframe) + #self.print_xgb(dataframe, "1m_model") + #self.print_xgb(dataframe_5, "5m_model") + #self.print_xgb(dataframe_30, "30m_model") + #self.print_xgb(dataframe_60, "1h_model") + #self.print_xgb(dataframe_4h, "4h_model") + print("-------------------------------------------------------------------------------") + self.last_time = datetime.now() + #dataframe = resampled_merge(dataframe, dataframe_5) + #dataframe = resampled_merge(dataframe, dataframe_15) + #dataframe = resampled_merge(dataframe, dataframe_30) + #dataframe = resampled_merge(dataframe, dataframe_60) + #dataframe = resampled_merge(dataframe, dataframe_4h) + return dataframe + # This is called when placing the initial order (opening trade) + def print_xgb(self, dataframe, model_name): + self.classifier.load_model(model_name) + klc_list = self.chan.get_klc_list(dataframe) + klc1 = klc_list[-1] + klc2 = klc_list[-2] + klc3 = klc_list[-3] + if klc1.end_time == klc2.start_time: + print(model_name, klc1.end_time, klc1.fx, self.classifier.predict(klc1)) + else: + print(model_name, klc1.start_time, klc1.fx, self.classifier.predict(klc1)) + print(model_name, klc2.end_time, klc2.fx, self.classifier.predict(klc2)) + print(model_name, klc3.end_time, klc3.fx, self.classifier.predict(klc3)) + def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, + proposed_stake: float, min_stake: float | None, max_stake: float, + leverage: float, entry_tag: str | None, side: str, + **kwargs) -> float: + + # We need to leave most of the funds for possible further DCA orders + # This also applies to fixed stakes + return proposed_stake / self.max_dca_multiplier + def adjust_trade_position1(self, trade: Trade, current_time: datetime, + current_rate: float, current_profit: float, + min_stake: float | None, max_stake: float, + current_entry_rate: float, current_exit_rate: float, + current_entry_profit: float, current_exit_profit: float, + **kwargs + ) -> float | None | tuple[float | None, str | None]: + """ + Custom trade adjustment logic, returning the stake amount that a trade should be + increased or decreased. + This means extra entry or exit orders with additional fees. + Only called when `position_adjustment_enable` is set to True. + + For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/ + + When not implemented by a strategy, returns None + + :param trade: trade object. + :param current_time: datetime object, containing the current datetime + :param current_rate: Current entry rate (same as current_entry_profit) + :param current_profit: Current profit (as ratio), calculated based on current_rate + (same as current_entry_profit). + :param min_stake: Minimal stake size allowed by exchange (for both entries and exits) + :param max_stake: Maximum stake allowed (either through balance, or by exchange limits). + :param current_entry_rate: Current rate using entry pricing. + :param current_exit_rate: Current rate using exit pricing. + :param current_entry_profit: Current profit using entry pricing. + :param current_exit_profit: Current profit using exit pricing. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + :return float: Stake amount to adjust your trade, + Positive values to increase position, Negative values to decrease position. + Return None for no action. + Optionally, return a tuple with a 2nd element with an order reason + """ + #if trade.has_open_orders: + # Only act if no orders are open + #return + + #if current_profit > 0.05 and trade.nr_of_successful_exits == 0: + # Take half of the profit at +5% + #return -(trade.stake_amount / 2), "half_profit_5%" + + #if current_profit > -0.05: + #return None + + # Obtain pair dataframe (just to show how to access it) + dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe) + # Only buy when not actively falling price. + #last_candle = dataframe.iloc[-1].squeeze() + #previous_candle = dataframe.iloc[-2].squeeze() + #if last_candle["close"] < previous_candle["close"]: + #return None + filled_entries = trade.select_filled_orders(trade.entry_side) + last_entry = filled_entries[-1] + count_of_entries = trade.nr_of_successful_entries + # Allow up to 3 additional increasingly larger buys (4 in total) + # Initial buy is 1x + # If that falls to -5% profit, we buy 1.25x more, average profit should increase to roughly -2.2% + # If that falls down to -5% again, we buy 1.5x more + # If that falls once again down to -5%, we buy 1.75x more + # Total stake for this trade would be 1 + 1.25 + 1.5 + 1.75 = 5.5x of the initial allowed stake. + # That is why max_dca_multiplier is 5.5 + # Hope you have a deep wallet! + # This returns first order stake size + #print(dataframe.iloc[-1]['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)]) + + # This returns first order stake size + stake_amount = filled_entries[0].stake_amount + # This then calculates current safety order size + stake_amount = stake_amount * (1 + (count_of_entries * 0.5)) + dataframe_date = dataframe.iloc[-1]['date'] + #print(stake_amount, "---------------------------------------------------") + if last_entry.order_filled_utc + timedelta(minutes=self.time5) < dataframe_date: + if dataframe.iloc[-self.time5]['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)] == "-10" and last_entry.side == "buy": + #print(dataframe.iloc[-self.time5]) + #print(stake_amount) + return stake_amount, "1/3rd_increase" + if last_entry.order_filled_utc + timedelta(minutes=self.time5) < dataframe_date: + if dataframe.iloc[-self.time5]['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)] == "10" and last_entry.side == "sell": + #print(dataframe.iloc[-self.time5]) + #print(stake_amount) + return stake_amount, "1/3rd_increase" + return None + def log_macd_div_list(self, dataframe): + bi_macd_div_list, bi_list, seg_macd_div_list, seg_list = self.chan.get_macd_div_list(dataframe) + logger.info(f"BI MACD DIV LIST") + for index in range(len(bi_list)-5, len(bi_list)): + bi = bi_list[index] + logger.info(f'{bi.start_time}, {bi.high}, {bi.low}, {bi.dir}, {bi.macd_div}') + logger.info(f"SEG MACD DIV LIST") + for index in range(len(seg_list)-5, len(seg_list)): + seg = seg_list[index] + logger.info(f'{seg.start_bi.start_time}, {seg.high}, {seg.low}, {seg.dir}, {seg.macd_div}') + def print_fx(self, df, label=5): + fx_list = self.chan.get_bsp_list(self.chan.get_klc_list(df)) + fx1 = fx_list[-1] + fx2 = fx_list[-2] + fx3 = fx_list[-3] + fx4 = fx_list[-4] + fx5 = fx_list[-5] + #print(fx1.end_time, fx1.fx, fx2.end_time, fx2.fx, fx3.end_time, fx3.fx) + logger.info(f'\n{fx5.end_time} {fx5.state} {fx4.end_time} {fx4.state} {fx3.end_time} {fx3.state} {fx2.end_time} {fx2.state} {fx1.end_time} {fx1.state} TF: {label}') + def print_fx_list(self, df): + bsp_list = self.chan.get_bsp_list(self.chan.get_klc_list(df)) + for bsp in bsp_list: + logger.info(f'{bsp.start_time}, {bsp.end_time}, {bsp.fx}') + def print_df(self, df): + for index in range(0, len(df)): + state = 'state' + rsi = 'rsi' + logger.info(f'{df[state][index]}, {df[rsi][index]}, {df[state][index]}') + def print_resample_df(self, dataframe, time, limit=10): + df = dataframe.tail(limit) + if limit > 0: + if time == 1: + for index in range(len(dataframe) - limit, len(dataframe)): + cn1 = 'date' + cn2 = 'rsi' + cn3 = 'state' + logger.info(f'{df[cn1][index]}, {df[cn2][index]}, {df[cn3][index]}') + else: + for index in range(0, limit): + cn1 = 'resample_{}_date'.format(self.get_ticker_indicator()*time) + cn2 = 'resample_{}_rsi'.format(self.get_ticker_indicator()*time) + cn3 = 'resample_{}_state'.format(self.get_ticker_indicator()*time) + logger.info(f'{df[cn1][index]}, {df[cn2][index]}, {df[cn3][index]}') + def add_indicators(self, df): + fast = 8 + slow = 16 + period = 6 + macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period) + df['macd'] = macd['macd'] + df['macdsignal'] = macd['macdsignal'] + df['macdhist'] = macd['macdhist'] + df['ma5'] = ta.MA(df, timeperiod=5) + df['ma10'] = ta.MA(df, timeperiod=10) + df['ma30'] = ta.EMA(df, timeperiod=30) + df['ma250'] = ta.MA(df, timeperiod=250) + df['rsi'] = ta.RSI(df, timeperiod=14) + return df + def local_print(self, df): + fast = 7 + slow = 14 + macd = ta.MACD(df, fast=fast, slow=slow) + df['macd'] = macd['macd'] + df['macdsignal'] = macd['macdsignal'] + df['macdhist'] = macd['macdhist'] + df['ema26'] = ta.EMA(df, timeperiod=26) + df['ema52'] = ta.EMA(df, timeperiod=52) + df['ma5'] = ta.MA(df, timeperiod=5) + df['ma10'] = ta.MA(df, timeperiod=10) + df['masub'] = df['ma5'].subtract(df['ma10']) + #for index in range(0, len(df)): + #print(df['date'][index], df['macdhist'][index], df['macd'][index], df['macdsignal'][index], df['masub'][index]) + # (1,1) = 1, (1,0) = 2, (-1,1) = 3, (-1, 0) = 4, (0,0) = 0 + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + + dataframe.loc[ + ( + (dataframe['state'] == "-30") + #((dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)] == "-11") | + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "-10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") + #(qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal'])) + ), + ['enter_long', 'enter_tag']] = (1, 'long_signal_chan') + dataframe.loc[ + ( + (dataframe['state'] == "30") + #((dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)] == "11") | + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "10") + #(qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal'])) + ), + ['enter_short', 'enter_tag']] = (1, 'short_signal_chan') + return dataframe + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe.loc[ + ( + (dataframe['state']== "30") + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "10") + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10") + ), + ['exit_long', 'exit_tag']] = (1, 'long_close_signal_chan') + dataframe.loc[ + ( + (dataframe['state'] == "-30") + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "-10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "-10") + ), + ['exit_short', 'exit_tag']] = (1, 'short_close_signal_chan') + return dataframe + def leverage(self, pair: str, current_time: datetime, current_rate: float, + proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str, + **kwargs) -> float: + return 1.0 + + def get_ticker_indicator(self): + return int(self.timeframe[:-1]) \ No newline at end of file diff --git a/strategies/ChanLun_XGB.py b/strategies/ChanLun_XGB.py new file mode 100644 index 0000000..85aa7cf --- /dev/null +++ b/strategies/ChanLun_XGB.py @@ -0,0 +1,560 @@ +import logging +from functools import reduce +from typing import Dict +import numpy as np +import talib.abstract as ta +from pandas import DataFrame +from technical import qtpylib +from talib import MACD, RSI +from datetime import datetime +import pandas as pd +import uuid + +from freqtrade.strategy import IStrategy + +logger = logging.getLogger(__name__) + +# freqtrade backtesting --config user_data/ChanLun_XGB.json --strategy ChanLun_XGB --freqaimodel XGBoostClassifier --timerange=20250401-20250421 + +class ChanLun_XGB1(IStrategy): + minimal_roi = {"0": 0.1, "240": -1} + plot_config = { + "main_plot": {}, + "subplots": { + "&-s_close": {"&-s_close": {"color": "blue"}}, + "do_predict": {"do_predict": {"color": "brown"}}, + }, + } + process_only_new_candles = True + stoploss = -0.05 + use_exit_signal = True + startup_candle_count: int = 40 + can_short = True + + freqai_info = { + "feature_parameters": { + "label_period_candles": 24 + } + } + + def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: Dict, **kwargs) -> DataFrame: + """Basic technical indicators for various periods.""" + logger.info("Starting feature_engineering_expand_all") + dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period) + dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period) + dataframe["%-adx-period"] = ta.ADX(dataframe, timeperiod=period) + dataframe["%-sma-period"] = ta.SMA(dataframe, timeperiod=period) + dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period) + + bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=period, stds=2.2) + dataframe["bb_lowerband-period"] = bollinger["lower"] + dataframe["bb_middleband-period"] = bollinger["mid"] + dataframe["bb_upperband-period"] = bollinger["upper"] + + dataframe["%-bb_width-period"] = ( + (dataframe["bb_upperband-period"] - dataframe["bb_lowerband-period"]) / dataframe["bb_middleband-period"] + ) + dataframe["%-close-bb_lower-period"] = dataframe["close"] / dataframe["bb_lowerband-period"] + dataframe["%-roc-period"] = ta.ROC(dataframe, timeperiod=period) + dataframe["%-relative_volume-period"] = dataframe["volume"] / dataframe["volume"].rolling(period).mean() + + logger.info("Completed feature_engineering_expand_all") + return dataframe.fillna(0) + + def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame: + """Basic price and volume features.""" + logger.info("Starting feature_engineering_expand_basic") + dataframe["%-pct-change"] = dataframe["close"].pct_change() + dataframe["%-raw_volume"] = dataframe["volume"] + dataframe["%-raw_price"] = dataframe["close"] + logger.info("Completed feature_engineering_expand_basic") + return dataframe.fillna(0) + + def feature_engineering_standard(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame: + """Advanced feature engineering with Chan Lun and technical indicators.""" + logger.info(f"Starting feature_engineering_standard for pair {metadata.get('pair', 'unknown')}") + if dataframe.empty: + return dataframe + + try: + df = dataframe.copy() + + # Time-based features + df["%-day_of_week"] = df["date"].dt.dayofweek + df["%-hour_of_day"] = df["date"].dt.hour + + # Fractal detection + df = self.detect_fractals(df) + logger.info("Fractal detection completed") + + # MACD and RSI + macd, signal, hist = MACD(df['close'], fastperiod=12, slowperiod=26, signalperiod=9) + df['macd'] = macd + df['macd_signal'] = signal + df['macd_hist'] = hist + df['macd_hist_sum'] = df['macd_hist'].rolling(5).sum() + df["%-rsi-14"] = RSI(df['close'], timeperiod=14) + logger.info("MACD and RSI calculated") + + # Candlestick features + df["%-close_open_diff"] = (df["close"] - df["open"]) / df["open"].replace(0, np.nan) + df["%-body_length"] = abs(df["close"] - df["open"]) / df["close"].replace(0, np.nan) + df["%-upper_shadow"] = (df["high"] - df[["open", "close"]].max(axis=1)) / df["close"].replace(0, np.nan) + df["%-lower_shadow"] = (df[["open", "close"]].min(axis=1) - df["low"]) / df["close"].replace(0, np.nan) + + # Candle color and trend + df["%-candle_color"] = (df["close"] > df["open"]).astype(int) * 2 - 1 + df["%-consec_same_color"] = df["%-candle_color"].groupby((df["%-candle_color"] != df["%-candle_color"].shift()).cumsum()).cumcount() + 1 + + # Fractal strength + df["%-bottom_strength"], df["%-top_strength"] = self.calculate_fractal_strength(df) + logger.info("Fractal strength calculated") + + # Price relationships + for i in [1, 2, 3]: + high_shift = df['high'].shift(i).replace(0, df['high'].mean()) + low_shift = df['low'].shift(i).replace(0, df['low'].mean()) + df[f"%-high_ratio_{i}"] = df['high'] / high_shift + df[f"%-low_ratio_{i}"] = df['low'] / low_shift + + # MACD divergence + df["%-macd_bottom_div"] = ((df['low'] < df['low'].rolling(5).min().shift(1)) & (df['macd'] > df['macd'].rolling(5).min().shift(1))).astype(int) + df["%-macd_top_div"] = ((df['high'] > df['high'].rolling(5).max().shift(1)) & (df['macd'] < df['macd'].rolling(5).max().shift(1))).astype(int) + + # Additional features + df["%-volume_change"] = df['volume'].pct_change() + df["%-volatility"] = df['close'].rolling(5).std() + df["%-price_range_20"] = (df['high'].rolling(5).max() - df['low'].rolling(5).min()) / df['close'].replace(0, np.nan) + df["%-potential_top"] = (df['is_top'] & (df["%-rsi-14"] > 70) & (df["%-macd_top_div"] == 1) & + (df['volume'] > df['volume'].rolling(20).mean()) & (df['close'] < df['open'])).astype(int) + df["%-potential_bottom"] = (df['is_bottom'] & (df["%-rsi-14"] < 30) & (df["%-macd_bottom_div"] == 1)).astype(int) + + # Fractal distance + df["%-last_fractal_distance"] = self.calculate_fractal_distance(df) + + # Advanced features + df["%-macd_hist_change"] = df['macd_hist_sum'].pct_change().replace([np.inf, -np.inf], 0) + df["%-volume_divergence"] = (df['close'].pct_change() - df['volume'].pct_change()).abs() + df["%-breakout_high"] = (df['high'] > df['high'].shift(1).rolling(20).max()).astype(int) + df["%-breakout_low"] = (df['low'] < df['low'].shift(1).rolling(20).min()).astype(int) + df["%-top_prominence"] = (df['high'] - df['high'].shift(1).rolling(5).mean()) / (df['high'].shift(1).rolling(5).std() + 1e-6) + df["%-top_prominence"] = df["%-top_prominence"].clip(-100, 100) + df["%-macd_hist_decline"] = df['macd_hist'].rolling(3).apply( + lambda x: 1 if all(x[i] > x[i+1] for i in range(len(x)-1)) else 0, raw=True) + df["%-top_candle_pattern"] = ((df['close'].shift(1) > df['open'].shift(1)) & + (df['close'] < df['open']) & + (df['close'] < df['open'].shift(1))).astype(int) + df["%-bottom_combo"] = (df['is_bottom'] & (df["%-rsi-14"] < 40) & (df['macd_hist'] > 0) & + (df['volume'] > df['volume'].rolling(20).mean())).astype(int) + df["%-top_combo"] = (df['is_top'] & (df["%-rsi-14"] > 60) & (df['macd_hist'] < 0) & + (df['volume'] > df['volume'].rolling(20).mean())).astype(int) + df["%-post_top_decline"] = self.calculate_post_top_decline(df) + df["%-resistance_distance"] = self.calculate_resistance_distance(df) + + # Stroke and pivot features + strokes = self.detect_strokes(df) + df["%-macd_hist_dynamic"], df["%-pivot_distance"], df["%-buy_signal"], df["%-sell_signal"] = self.process_strokes_and_pivots(df, strokes) + logger.info("Stroke and pivot features completed") + + # Clean up + num_columns = df.select_dtypes(include=[np.number]).columns + df[num_columns] = df[num_columns].replace([np.inf, -np.inf], 0).fillna(0) + logger.info(f"Completed feature_engineering_standard. Columns: {list(df.columns)}") + return df + + except Exception as e: + logger.error(f"Error in feature_engineering_standard: {e}") + # 发生错误时返回原始数据框 + return dataframe + + def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame: + """Set prediction targets for FreqAI.""" + logger.info("Setting FreqAI targets") + label_period = self.freqai_info["feature_parameters"]["label_period_candles"] + + # Calculate future return + future_return = ( + dataframe["close"].shift(-label_period).rolling(label_period).mean() / dataframe["close"] - 1 + ) + + # Discretize into categorical labels: 1 (buy), 2 (sell) + dataframe["&-s_close"] = pd.Series(0, index=dataframe.index) # Default: no trade + dataframe.loc[future_return > 0.01, "&-s_close"] = 1 # Buy if return > 1% + dataframe.loc[future_return < -0.01, "&-s_close"] = 0 # Sell if return < -1% + + return dataframe.fillna(0) + + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """Populate indicators, leveraging FreqAI if available.""" + logger.info(f"Starting populate_indicators for pair {metadata.get('pair', 'unknown')}") + # Always run feature_engineering_standard to ensure custom features + dataframe = self.feature_engineering_standard(dataframe, metadata) + + if hasattr(self, "freqai"): + logger.info("Running FreqAI pipeline") + # Preserve custom features + custom_features = [col for col in dataframe.columns if col.startswith('%-')] + temp_df = dataframe[custom_features + ['date', 'close', 'open', 'high', 'low', 'volume']] + # Run FreqAI + freqai_df = self.freqai.start(dataframe, metadata, self) + # Merge back custom features + freqai_df = freqai_df.combine_first(temp_df) + dataframe = freqai_df + logger.info(f"Completed populate_indicators. Columns: {list(dataframe.columns)}") + return dataframe + + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """Define entry signals.""" + logger.info(f"Starting populate_entry_trend for pair {metadata.get('pair', 'unknown')}") + df = self.ensure_columns(dataframe, ['%-buy_signal', '%-bottom_strength', '%-sell_signal', '%-top_strength', '%-macd_top_div', '%-rsi-14']) + + enter_long_conditions = [ + df["do_predict"] == 1, + df["&-s_close"] == 1, # 上涨预测 + df["%-buy_signal"] > 0, + df["%-bottom_strength"] > 2.0, + df["%-rsi-14"] < 40, + ] + if enter_long_conditions: + df.loc[reduce(lambda x, y: x & y, enter_long_conditions), ["enter_long", "enter_tag"]] = (1, "long") + + enter_short_conditions = [ + df["do_predict"] == 1, + df["&-s_close"] == 0, # 下跌预测 + df["%-sell_signal"] > 0, + df["%-top_strength"] > 2.5, + df["%-rsi-14"] > 60, + df["%-macd_top_div"] > 0, + ] + if enter_short_conditions: + df.loc[reduce(lambda x, y: x & y, enter_short_conditions), ["enter_short", "enter_tag"]] = (1, "short") + + logger.info("Completed populate_entry_trend") + return df + + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """Define exit signals.""" + logger.info(f"Starting populate_exit_trend for pair {metadata.get('pair', 'unknown')}") + df = self.ensure_columns(dataframe, ['%-top_combo', '%-top_candle_pattern', '%-bottom_combo', '%-bottom_strength', '%-rsi-14']) + + exit_long_conditions = [ + df["do_predict"] == 1, + df["&-s_close"] == 0, # 下跌预测时退出多头 + (df["%-top_combo"] > 0) | (df["%-top_candle_pattern"] > 0) + ] + if exit_long_conditions: + df.loc[reduce(lambda x, y: x & y, exit_long_conditions), "exit_long"] = 1 + + exit_short_conditions = [ + df["do_predict"] == 1, + df["&-s_close"] == 1, # 上涨预测时退出空头 + (df["%-bottom_combo"] > 0) | (df["%-bottom_strength"] > 3.0) + ] + if exit_short_conditions: + df.loc[reduce(lambda x, y: x & y, exit_short_conditions), "exit_short"] = 1 + + logger.info("Completed populate_exit_trend") + return df + + def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, time_in_force: str, + current_time, entry_tag, side: str, **kwargs) -> bool: + """Confirm trade entry with additional checks.""" + logger.info(f"Confirming trade entry for {pair}, side: {side}") + df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) + last_candle = df.iloc[-1].squeeze() + df = self.ensure_columns(df, ['%-bottom_strength', '%-buy_signal', '%-top_strength', '%-sell_signal', '%-rsi-14']) + + if side == "long": + if rate > (last_candle["close"] * 1.0025): + logger.debug(f"Long entry rejected: rate {rate} exceeds threshold") + return False + return last_candle["%-bottom_strength"] > 1.5 or last_candle["%-buy_signal"] > 0 + else: + if rate < (last_candle["close"] * 0.9975): + logger.debug(f"Short entry rejected: rate {rate} below threshold") + return False + return last_candle["%-top_strength"] > 2.0 or last_candle["%-sell_signal"] > 0 + + def detect_fractals(self, df: DataFrame) -> DataFrame: + """Detect top and bottom fractals.""" + logger.debug("Detecting fractals") + df['is_top'] = ( + (df['high'] > df['high'].shift(1)) & (df['high'] > df['high'].shift(2)) & + (df['high'] > df['high'].shift(-1)) & (df['high'] > df['high'].shift(-2)) + ) + df['is_bottom'] = ( + (df['low'] < df['low'].shift(1)) & (df['low'] < df['low'].shift(2)) & + (df['low'] < df['low'].shift(-1)) & (df['low'] < df['low'].shift(-2)) + ) + return df.fillna({'is_top': False, 'is_bottom': False}) + + def calculate_fractal_strength(self, df: DataFrame) -> tuple: + """Calculate strength of fractals.""" + logger.debug("Calculating fractal strength") + bottom_strength = np.zeros(len(df)) + top_strength = np.zeros(len(df)) + + for i in range(2, len(df) - 2): + if df['is_bottom'].iloc[i]: + strength = 0.0 + pre_decline = (df['low'].iloc[i-2:i].min() - df['low'].iloc[i]) / df['low'].iloc[i] + post_rise = (df['high'].iloc[i+1:i+3].max() - df['high'].iloc[i]) / df['high'].iloc[i] + strength += min(pre_decline * 10, 1.0) + min(post_rise * 10, 1.0) + vol_surge = df['volume'].iloc[i] / df['volume'].iloc[i-3:i].mean() if df['volume'].iloc[i-3:i].mean() > 0 else 1 + strength += min(vol_surge / 3, 1.0) + if any(abs(df['low'].iloc[j] - df['low'].iloc[i]) / df['low'].iloc[i] < 0.01 for j in range(max(0, i-20), i)): + strength += 1.0 + bottom_strength[i] = min(strength, 5.0) + + if df['is_top'].iloc[i]: + strength = 0.0 + pre_rise = (df['high'].iloc[i] - df['high'].iloc[i-2:i].max()) / df['high'].iloc[i] + post_decline = (df['low'].iloc[i] - df['low'].iloc[i+1:i+3].min()) / df['low'].iloc[i] + strength += min(pre_rise * 10, 1.0) + min(post_decline * 10, 1.0) + vol_surge = df['volume'].iloc[i] / df['volume'].iloc[i-3:i].mean() if df['volume'].iloc[i-3:i].mean() > 0 else 1 + strength += min(vol_surge / 3, 1.0) + if any(abs(df['high'].iloc[j] - df['high'].iloc[i]) / df['high'].iloc[i] < 0.01 for j in range(max(0, i-20), i)): + strength += 1.0 + bearish_count = sum(df['close'].iloc[i:i+3] < df['open'].iloc[i:i+3]) + strength += min(bearish_count * 0.5, 1.5) + top_strength[i] = min(strength, 6.0) + + return bottom_strength, top_strength + + def calculate_fractal_distance(self, df: DataFrame) -> pd.Series: + """Calculate distance to last fractal.""" + logger.debug("Calculating fractal distance") + fractal_indices = df[df['is_top'] | df['is_bottom']].index + distances = pd.Series(0, index=df.index) + + for i in range(1, len(df)): + if fractal_indices[fractal_indices < df.index[i]].size > 0: + last_fractal_idx = fractal_indices[fractal_indices < df.index[i]][-1] + if isinstance(df.index[i], pd.Timestamp) and isinstance(last_fractal_idx, pd.Timestamp): + time_diff = (df.index[i] - last_fractal_idx).total_seconds() / 60 + else: + time_diff = i - df.index.get_loc(last_fractal_idx) + distances.iloc[i] = time_diff + return distances + + def calculate_post_top_decline(self, df: DataFrame) -> pd.Series: + """Calculate post-top decline.""" + logger.debug("Calculating post-top decline") + post_top_decline = pd.Series(0.0, index=df.index) + for i in range(2, len(df)-3): + if df['is_top'].iloc[i]: + decline = (df['high'].iloc[i] - df['low'].iloc[i+1:i+4].min()) / df['high'].iloc[i] + post_top_decline.iloc[i] = min(decline * 10, 5.0) + return post_top_decline + + def calculate_resistance_distance(self, df: DataFrame) -> pd.Series: + """Calculate resistance distance.""" + logger.debug("Calculating resistance distance") + resistance_distance = pd.Series(0.0, index=df.index) + for i in range(20, len(df)): + if df['is_top'].iloc[i]: + price_high = df['high'].iloc[i] + resistance_levels = df['high'].iloc[i-20:i].rolling(5).max() + distance = (price_high - resistance_levels.min()) / price_high if resistance_levels.min() > 0 else 0 + resistance_distance.iloc[i] = min(distance * 10, 5.0) + return resistance_distance + + def detect_strokes(self, df: DataFrame) -> list: + """Detect strokes based on fractals.""" + logger.debug("Detecting strokes") + strokes = [] + last_fractal, last_price, last_index = None, None, None + + for i in range(len(df)): + if df['is_top'].iloc[i] or df['is_bottom'].iloc[i]: + current_fractal = 'top' if df['is_top'].iloc[i] else 'bottom' + current_price = df['high'].iloc[i] if current_fractal == 'top' else df['low'].iloc[i] + + if last_fractal is None: + last_fractal, last_price, last_index = current_fractal, current_price, df.index[i] + continue + + if not isinstance(current_price, (int, float)) or not isinstance(last_price, (int, float)): + continue + + price_change = abs(current_price - last_price) / last_price + if price_change < 0.005: + continue + + if (last_fractal == 'top' and current_fractal == 'bottom' and current_price < last_price) or \ + (last_fractal == 'bottom' and current_fractal == 'top' and current_price > last_price): + strokes.append({ + 'start_time': last_index, + 'end_time': df.index[i], + 'start_price': last_price, + 'end_price': current_price, + 'type': 'down' if current_fractal == 'bottom' else 'up' + }) + + last_fractal, last_price, last_index = current_fractal, current_price, df.index[i] + + logger.debug(f"Detected {len(strokes)} strokes") + return strokes + + def detect_pivots(self, strokes: list) -> list: + """Detect pivots based on strokes.""" + logger.debug("Detecting pivots") + pivots = [] + if len(strokes) < 3: + logger.debug("Insufficient strokes for pivot detection") + return pivots + + for i in range(2, len(strokes)): + high1, low1 = max(strokes[i-2]['start_price'], strokes[i-2]['end_price']), min(strokes[i-2]['start_price'], strokes[i-2]['end_price']) + high2, low2 = max(strokes[i-1]['start_price'], strokes[i-1]['end_price']), min(strokes[i-1]['start_price'], strokes[i-1]['end_price']) + high3, low3 = max(strokes[i]['start_price'], strokes[i]['end_price']), min(strokes[i]['start_price'], strokes[i]['end_price']) + if max(low1, low2, low3) < min(high1, high2, high3): + pivots.append({ + 'start_time': strokes[i-2]['start_time'], + 'end_time': strokes[i]['end_time'], + 'high': min(high1, high2, high3), + 'low': max(low1, low2, low3) + }) + logger.debug(f"Detected {len(pivots)} pivots") + return pivots + + def add_pivot_distance(self, df: DataFrame, pivots: list) -> DataFrame: + """Add pivot distance feature.""" + logger.debug("Adding pivot distance") + df = df.copy() + df['pivot_distance'] = 0.0 + + if not pivots: + logger.debug("No pivots detected, returning default pivot_distance") + return df + + for pivot in pivots: + try: + start_time = pivot['start_time'] + end_time = pivot['end_time'] + if start_time not in df.index or end_time not in df.index: + logger.debug(f"Invalid pivot times: {start_time} to {end_time}") + continue + mask = (df.index >= start_time) & (df.index <= end_time) + denominator = pivot['high'] - pivot['low'] + if denominator > 0: + df.loc[mask, 'pivot_distance'] = (df['close'] - pivot['low']) / denominator + else: + logger.debug(f"Zero denominator for pivot {pivot}") + except Exception as e: + logger.error(f"Error in pivot distance calculation: {e}") + continue + + df['pivot_distance'] = df['pivot_distance'].clip(-10, 10).fillna(0.0) + logger.debug("Completed pivot distance calculation") + return df + + def detect_back_divergence(self, df: DataFrame, strokes: list) -> DataFrame: + """Detect back divergence for buy/sell signals.""" + logger.debug("Detecting back divergence") + df = df.copy() + df['buy_signal'] = False + df['sell_signal'] = False + + if len(strokes) < 2: + logger.debug("Insufficient strokes for divergence detection") + return df + + for i in range(1, len(strokes)): + current_hist = self.safe_get_value(df, strokes[i]['end_time'], 'macd_hist_sum') + previous_hist = self.safe_get_value(df, strokes[i-1]['end_time'], 'macd_hist_sum') + + if current_hist is None or previous_hist is None: + continue + + if strokes[i]['type'] == strokes[i-1]['type'] == 'up': + if strokes[i]['end_price'] > strokes[i-1]['end_price'] and current_hist < previous_hist: + closest_time = df.index[df.index <= strokes[i]['end_time']] + if len(closest_time) > 0: + df.loc[closest_time[-1], 'sell_signal'] = True + + elif strokes[i]['type'] == strokes[i-1]['type'] == 'down': + if strokes[i]['end_price'] < strokes[i-1]['end_price'] and current_hist < previous_hist: + closest_time = df.index[df.index <= strokes[i]['end_time']] + if len(closest_time) > 0: + df.loc[closest_time[-1], 'buy_signal'] = True + + logger.debug("Completed back divergence detection") + return df + + def process_strokes_and_pivots(self, df: DataFrame, strokes: list) -> tuple: + """Process strokes and pivots for advanced features.""" + logger.debug("Processing strokes and pivots") + macd_hist_dynamic = pd.Series(0.0, index=df.index) + pivot_distance = pd.Series(0.0, index=df.index) + buy_signal = pd.Series(0, index=df.index) + sell_signal = pd.Series(0, index=df.index) + + if strokes: + try: + for stroke in strokes[1:]: + start_time, end_time = stroke['start_time'], stroke['end_time'] + if start_time not in df.index or end_time not in df.index: + logger.debug(f"Invalid stroke times: {start_time} to {end_time}") + continue + + window = self.calculate_window(df, start_time, end_time) + start_idx, end_idx = df.index.get_loc(start_time), df.index.get_loc(end_time) + 1 + hist_values = df['macd_hist'].iloc[start_idx:end_idx].rolling(window, min_periods=1).sum().fillna(0) + macd_hist_dynamic.iloc[start_idx:end_idx] = hist_values + except Exception as e: + logger.warning(f"Dynamic MACD calculation error: {e}") + + try: + pivots = self.detect_pivots(strokes) + if pivots: + df_with_pivot = self.add_pivot_distance(df, pivots) + pivot_distance = df_with_pivot['pivot_distance'] + else: + logger.debug("No pivots detected") + except Exception as e: + logger.warning(f"Pivot detection error: {e}") + + try: + df_with_divergence = self.detect_back_divergence(df, strokes) + buy_signal = df_with_divergence['buy_signal'].astype(int) + sell_signal = df_with_divergence['sell_signal'].astype(int) + except Exception as e: + logger.warning(f"Back divergence detection error: {e}") + + logger.debug("Completed stroke and pivot processing") + return macd_hist_dynamic, pivot_distance, buy_signal, sell_signal + + def ensure_columns(self, df: DataFrame, columns: list) -> DataFrame: + """Ensure required columns exist with default values.""" + logger.debug(f"Ensuring columns: {columns}") + for col in columns: + if col not in df.columns: + logger.warning(f"Column {col} missing, using default value") + df[col] = 50 if col == '%-rsi-14' else 0 + return df + + def safe_get_value(self, df: DataFrame, timestamp, column: str): + """Safely get value from DataFrame.""" + try: + if timestamp in df.index: + return df.loc[timestamp, column] + closest_idx = df.index[df.index <= timestamp] + return df.loc[closest_idx[-1], column] if len(closest_idx) > 0 else None + except Exception as e: + logger.debug(f"Error getting value for {column} at {timestamp}: {e}") + return None + + def calculate_window(self, df: DataFrame, start_time, end_time) -> int: + """Calculate window size for dynamic features.""" + try: + if isinstance(start_time, pd.Timestamp) and isinstance(end_time, pd.Timestamp): + window = int((end_time - start_time).total_seconds() / 60) + else: + start_idx = df.index.get_loc(start_time) + end_idx = df.index.get_loc(end_time) + window = end_idx - start_idx + return max(window, 1) + except (TypeError, AttributeError, KeyError) as e: + logger.debug(f"Window calculation error: {e}") + return 1 \ No newline at end of file diff --git a/strategies/ChanPY.py b/strategies/ChanPY.py new file mode 100644 index 0000000..c5372c3 --- /dev/null +++ b/strategies/ChanPY.py @@ -0,0 +1,274 @@ +import sys +import os +#sys.path.append(os.path.abspath("/Users/jack/Documents/GitHub/chan.py")) +sys.path.append(os.path.abspath("/Users/jack/Project/chan.py")) +from Chan import CChan +from BuySellPoint.BS_Point import CBS_Point +from ChanConfig import CChanConfig +from Common.CEnum import AUTYPE, DATA_SRC, KL_TYPE, DATA_FIELD, BSP_TYPE, FX_TYPE, BI_DIR, KLINE_DIR, SEG_DIR +from KLine.KLine_Unit import CKLine_Unit +from Common.CTime import CTime +from Common.func_util import kltype_lt_day, str2float +from Bi.Bi import CBi +from typing import Dict, List +from functools import reduce +from pandas import DataFrame +from datetime import datetime, timedelta, timezone + + +def GetColumnNameFromFieldList(fileds: str): + _dict = { + "time": DATA_FIELD.FIELD_TIME, + "open": DATA_FIELD.FIELD_OPEN, + "high": DATA_FIELD.FIELD_HIGH, + "low": DATA_FIELD.FIELD_LOW, + "close": DATA_FIELD.FIELD_CLOSE, + "volume": DATA_FIELD.FIELD_VOLUME + } + return [_dict[x] for x in fileds.split(",")] +class ChanPY(): + k_type = KL_TYPE.K_5M + config = CChanConfig({ + "bi_strict": True, + "bi_algo": "fx", + "trigger_step": True, + "skip_step": 0, + "divergence_rate": 0.9, + "bsp2_follow_1": False, + "bsp3_follow_1": False, + "min_zs_cnt": 1, + "bs1_peak": False, + "macd_algo": "peak", + "bs_type": '1,2,3a,1p,2s,3b', + "print_warning": True, + "zs_algo": "normal", + }) + chan = CChan( + code="BTC/USDT:USDT", + data_src=DATA_SRC.CCXT, + lv_list=[k_type], + config=config, + autype=AUTYPE.QFQ, + ) + klu_list = [] + chanIn = True + #def __init__(self, dataframe): + #self.klu_list = self.get_kl_data(dataframe) + #for klu in self.klu_list: + #self.chan.trigger_load({self.k_type: [klu]}) + def add_klu(self, klu): + if klu: + self.chan.trigger_load({self.k_type: [klu]}) + self.klu_list.append(klu) + def add_klu_from_dataframe(self, dataframe): + if len(dataframe) > len(self.klu_list): + klu = self.get_last_klu(dataframe) + self.chan.trigger_load({self.k_type: [klu]}) + self.klu_list.append(klu) + def parse_time_column(self, inp): + if len(inp) == 10: + year = int(inp[:4]) + month = int(inp[5:7]) + day = int(inp[8:10]) + hour = minute = 0 + elif len(inp) == 17: + year = int(inp[:4]) + month = int(inp[4:6]) + day = int(inp[6:8]) + hour = int(inp[8:10]) + minute = int(inp[10:12]) + elif len(inp) == 19: + year = int(inp[:4]) + month = int(inp[5:7]) + day = int(inp[8:10]) + hour = int(inp[11:13]) + minute = int(inp[14:16]) + else: + raise Exception(f"unknown time column from TradingView:{inp}") + return CTime(year, month, day, hour, minute, auto=not kltype_lt_day(self.k_type)) + + def create_item_dict(self, data, column_name): + for i in range(len(data)): + data[i] = self.parse_time_column(data[i]) if i == 0 else str2float(data[i]) + return dict(zip(column_name, data)) + def get_last_klu(self, dataframe:DataFrame): + fields = "time,open,high,low,close,volume" + item = dataframe.iloc[-1] + date = item['date'] + o = item['open'] + h = item['high'] + l = item['low'] + c = item['close'] + v = item['volume'] + #time_obj = date.fromtimestamp(date) + time_str = date.strftime('%Y-%m-%d %H:%M:%S') + item_data = [ + time_str, + o, + h, + l, + c, + v + ] + klu = CKLine_Unit(self.create_item_dict(item_data, GetColumnNameFromFieldList(fields)), autofix=True) + klu.set_idx(len(dataframe)-1) + return klu + def get_kl_data(self, dataframe:DataFrame): + fields = "time,open,high,low,close,volume" + klu_list = [] + for i in range(0, len(dataframe)): + item = dataframe.iloc[i] + date = item['date'] + o = item['open'] + h = item['high'] + l = item['low'] + c = item['close'] + v = item['volume'] + #time_obj = date.fromtimestamp(date) + time_str = date.strftime('%Y-%m-%d %H:%M:%S') + item_data = [ + time_str, + o, + h, + l, + c, + v + ] + klu = CKLine_Unit(self.create_item_dict(item_data, GetColumnNameFromFieldList(fields)), autofix=True) + klu.set_idx(i) + klu_list.append(klu) + return klu_list + def get_bsp_type(self, bsp_type, is_buy): + if is_buy: + if bsp_type == BSP_TYPE.T1: + return 1 + if bsp_type == BSP_TYPE.T1P: + return 2 + if bsp_type == BSP_TYPE.T2: + return 3 + if bsp_type == BSP_TYPE.T2S: + return 4 + if bsp_type == BSP_TYPE.T3A: + return 5 + if bsp_type == BSP_TYPE.T3B: + return 6 + else: + if bsp_type == BSP_TYPE.T1: + return -1 + if bsp_type == BSP_TYPE.T1P: + return -2 + if bsp_type == BSP_TYPE.T2: + return -3 + if bsp_type == BSP_TYPE.T2S: + return -4 + if bsp_type == BSP_TYPE.T3A: + return -5 + if bsp_type == BSP_TYPE.T3B: + return -6 + def get_bsps(self, dataframe:DataFrame): + bsps = [] + updown = [] + bi_sure = [] + if self.chanIn: + kl_data = self.get_kl_data(dataframe) + bsp_list = [] + bsp_list_pre_len = 0 + last_bsp_value = 0 + last_updown = -1 + bi_list_pre_len = 0 + pre_bi = None + zs_list_pre_len = 0 + pre_zs = None + for klu in kl_data: # 获取单根K线 + self.chan.trigger_load({self.k_type: [klu]}) # 喂给CChan新增k线 + self.last_kline = klu + bsp_list = self.chan.get_bsp() + kl_datas = self.chan.kl_datas[self.k_type] + bi_list = kl_datas.bi_list + lst = kl_datas.lst + if len(bsp_list) > 0: + last_bsp = bsp_list[-1] + #print(klu.time, klu.idx, last_bsp.klu.time, last_bsp.klu.idx, last_bsp_value, lst[-2].fx, bi_list[-1].dir, bi_list[-1].is_sure,klu.close) + if bsp_list_pre_len > len(bsp_list): + bsps.append(99) + #print(klu.time, klu.idx, last_bsp.klu.time, last_bsp.klu.idx, last_bsp_value, 99) + else: + if bsp_list_pre_len == len(bsp_list): + if klu.idx == last_bsp.klu.idx: + last_bsp_value = self.get_bsp_type(last_bsp.type[0], last_bsp.is_buy) + bsps.append(last_bsp_value) + #print(klu.time, klu.idx, last_bsp.klu.time, last_bsp.klu.idx, last_bsp_value) + else: + bsps.append(0) + else: + last_bsp_value = self.get_bsp_type(last_bsp.type[0], last_bsp.is_buy) + bsps.append(last_bsp_value) + #print(klu.time, klu.idx, last_bsp.klu.time, last_bsp.klu.idx, last_bsp_value) + else: + bsps.append(0) + bsp_list_pre_len = len(bsp_list) + + #Check zs ----------------------------------- + zs_list = kl_datas.zs_list + if len(zs_list) > 0: + zs = zs_list[-1] + #if zs_list_pre_len > len(zs_list): + #print("No zs", zs.begin.time) + #if len(zs_list) > zs_list_pre_len: + #print(zs.begin.time, zs.end.time, zs.end.idx, zs.high, zs.low, zs.peak_high, zs.peak_low) + zs_list_pre_len = len(zs_list) + pre_zs = zs + + #Check Bi ----------------------------------- + if len(bi_list) > 0: + last_bi = bi_list[-1] + if len(bi_list) == 1: + if last_bi.dir == BI_DIR.UP: + updown.append(1) + last_updown = 1 + else: + updown.append(-1) + last_updown = -1 + else: + if last_updown == 1: + if last_bi.dir == BI_DIR.UP: + updown.append(0) + else: + updown.append(-1) + last_updown = -1 + else: + if last_bi.dir == BI_DIR.DOWN: + updown.append(0) + else: + updown.append(1) + last_updown = 1 + else: + updown.append(0) + bi_list = kl_datas.bi_list + if len(bi_list) > 0: + last_bi = bi_list[-1] + #if bi_list_pre_len > len(bi_list): + #print("Bi ", klu.time, pre_bi.idx, pre_bi.is_sure, bi_list[-1].idx, bi_list[-1].is_sure) + if last_bi.is_sure: + bi_sure.append(1) + #print(klu.time, last_bi.is_sure) + else: + bi_sure.append(0) + pre_bi = bi_list[-1] + bi_list_pre_len = len(bi_list) + else: + bi_sure.append(0) + #if bsps[-1] != 0 or updown[-1] != 0: + #print(klu.time, bsps[-1], updown[-1], bi_list[-1].is_sure) + self.chanIn = False + else: + klu = CKLine_Unit(self.create_item_dict(self.get_last_item_data(dataframe), GetColumnNameFromFieldList(fields)), autofix=True) + if self.last_kline.time < klu.time: + self.chan.trigger_load({self.k_type: [klu]}) # 喂给CChan新增k线 + self.last_kline = klu + #print(bsps) + #print(updown) + kl_datas = self.chan.kl_datas[self.k_type] + #for zs in kl_datas.zs_list: + #print(zs.begin.time, zs.end.time) + return bsps, updown, bi_sure \ No newline at end of file diff --git a/strategies/Chan_SOL_2.py b/strategies/Chan_SOL_2.py new file mode 100644 index 0000000..ce03d64 --- /dev/null +++ b/strategies/Chan_SOL_2.py @@ -0,0 +1,196 @@ +# --- Do not remove these libs --- +from freqtrade.strategy import IStrategy +from typing import Dict, List +from functools import reduce +from pandas import DataFrame, pandas +import freqtrade.vendor.qtpylib.indicators as qtpylib + +import sys +import os +#sys.setrecursionlimit(1000000) #例如这里设置为一百万 +#sys.path.append(os.path.abspath("/freqtrade/user_data/Chan")) +sys.path.append(os.path.abspath("/Users/jack/Project/freqtrade/user_data/Chan")) +#sys.path.append(os.path.abspath("/Users/jack/Documents/GitHub/freqtrade/user_data/Chan")) +from ChanLun import ChanLun +# -------------------------------- +from technical.util import resample_to_interval, resampled_merge +import talib.abstract as ta +import freqtrade.vendor.qtpylib.indicators as qtpylib +from datetime import datetime, timedelta, timezone +from freqtrade.persistence import Trade, Order +from typing import Optional +from ChanPY import ChanPY +import logging +logger = logging.getLogger(__name__) + +### Now you can use logger.info('asfd') to log + +# freqtrade trade -c ./user_data/Chan.json --strategy Chan_SOL_2 --strategy-path ./user_data/strategies +# freqtrade backtesting -c ./user_data/Chan.json --strategy Chan_SOL_2 --strategy-path ./user_data/strategies --timerange=20250309- +# freqtrade download-data -c ./user_data/Chan.json -t 1m --pairs SOL/USDT:USDT --timerange=20240101- +# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss --strategy Chan_SOL_2 --strategy-path ./user_data/strategies -c ./user_data/Chan.json -e 200 --timerange=20250101-20250215 + +# sudo docker compose run --rm chan_btc backtesting -c ./user_data/Chan.json --strategy Chan_SOL_2 --strategy-path ./user_data/strategies --timerange=20250101- +# sudo docker compose run --rm chan_btc download-data -c ./user_data/Chan.json --pairs SOL/USDT:USDT -t 1m --timerange 20240101- +# sudo docker compose run --rm chan_btc trade -c ./user_data/Chan.json --strategy Chan_SOL_2 --strategy-path ./user_data/strategies + +class Chan_SOL_2(IStrategy): + INTERFACE_VERSION: int = 3 + # Minimal ROI designed for the strategy. + # This attribute will be overridden if the config file contains "minimal_roi" + minimal_roi = { + "0": 0.253, + "120": 0.159, + "240": 0.052, + "360": 0 + } + can_short = True + # Optimal stoploss designed for the strategy + # This attribute will be overridden if the config file contains "stoploss" + stoploss = -0.21 + + trailing_stop = False + trailing_stop_positive = 0.015 + trailing_stop_positive_offset = 0.043 + trailing_only_offset_is_reached = False + + # Optimal timeframe for the strategy + # timeframe = '15m' + startup_candle_count = 600 + + time5 = 5 + time15 = 15 + time30 = 30 + time60 = 60 + time240 = 240 + last_time = datetime.now() + big_size = 0 + big_state = "00" + big_state_list = [] + chanpy = ChanPY() + chan = ChanLun() + small_size = 0 + small_state = "00" + small_state_list = [] + + def informative_pairs(self): + + # get access to all pairs available in whitelist. + pairs = self.dp.current_whitelist() + # Assign tf to each pair so they can be downloaded and cached for strategy. + informative_pairs = [(pair, '1h') for pair in pairs] + # Optionally Add additional "static" pairs + #informative_pairs += [("ETH/USDT:USDT", "5m"),("ETH/USDT:USDT", "15m"),] + return informative_pairs + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + + # resample our dataframes + dataframe_5 = resample_to_interval(dataframe, self.get_ticker_indicator() * 5) + #dataframe_15 = resample_to_interval(dataframe, self.get_ticker_indicator() * 15) + #dataframe_30 = resample_to_interval(dataframe, self.get_ticker_indicator() * 30) + dataframe_60 = resample_to_interval(dataframe, self.get_ticker_indicator() * 60) + #dataframe_4h = resample_to_interval(dataframe, self.get_ticker_indicator() * 240) + + #dataframe_1d = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1d') + #dataframe_1w = resample_to_interval(dataframe_1d, self.get_ticker_indicator() * 10080) + #dataframe_1m = resample_to_interval(dataframe_1d, self.get_ticker_indicator() * 43200) + + #dataframe_1d = resample_to_interval(dataframe, self.get_ticker_indicator() * 1440) + #dataframe_1w = resample_to_interval(dataframe, self.get_ticker_indicator() * 10080) + #dataframe_1m = resample_to_interval(dataframe, self.get_ticker_indicator() * 43200) + self.local_print(dataframe_5) + + dataframe_5['state'] = self.chan.resample_klc_list(dataframe_5) + #dataframe_15['state'] = self.chan.resample_klc_list(dataframe_15) + #dataframe_30['state'] = self.chan.resample_klc_list(dataframe_30) + dataframe_60['state'] = self.chan.resample_klc_list(dataframe_60) + #dataframe_4h['state'] = self.chan.resample_klc_list(dataframe_4h) + #dataframe_5['bsps'], dataframe_5['updown'], dataframe_5['bi_sure'] = self.chanpy.get_bsps(dataframe_5) + print("===================================================") + #print(dataframe_60['high'].rolling(window).max()) + #print(dataframe_60['low'].rolling(window).min()) + #for index in range(0, len(dataframe_5)): + #print(dataframe_5[dataframe_5['bi_sure'] == 1][dataframe_5['state'] == "-10"]) + #print(dataframe_5[dataframe_5['bi_sure'] == 1][dataframe_5['state'] == "10"]) + dataframe = resampled_merge(dataframe, dataframe_5) + #dataframe = resampled_merge(dataframe, dataframe_15) + #dataframe = resampled_merge(dataframe, dataframe_30) + dataframe = resampled_merge(dataframe, dataframe_60) + #dataframe = resampled_merge(dataframe, dataframe_4h) + + return dataframe + def print_df(self, df): + for index in range(0, len(df)): + print(df['date'][index], df['rsi'][index], df['state'][index]) + def print_resample_df(self, df, time): + for index in range(0, len(df)): + cn1 = 'resample_{}_date'.format(self.get_ticker_indicator()*time) + cn2 = 'resample_{}_rsi'.format(self.get_ticker_indicator()*time) + cn3 = 'resample_{}_state'.format(self.get_ticker_indicator()*time) + print(df[cn1][index], df[cn2][index], df[cn3][index]) + def local_print(self, df): + fast = 7 + slow = 14 + macd = ta.MACD(df, fast=fast, slow=slow) + df['macd'] = macd['macd'] + df['macdsignal'] = macd['macdsignal'] + df['macdhist'] = macd['macdhist'] + df['ema26'] = ta.EMA(df, timeperiod=26) + df['ema52'] = ta.EMA(df, timeperiod=52) + df['ma5'] = ta.MA(df, timeperiod=5) + df['ma10'] = ta.MA(df, timeperiod=10) + df['masub'] = df['ma5'].subtract(df['ma10']) + for index in range(0, len(df)): + print(df['date'][index], df['macdhist'][index], df['macd'][index], df['macdsignal'][index], df['masub'][index]) + # (1,1) = 1, (1,0) = 2, (-1,1) = 3, (-1, 0) = 4, (0,0) = 0 + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + + dataframe.loc[ + ( + #(dataframe['state'].shift(1) == "-10") & + #((dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)] == "-11") | + (dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "-10") & + (dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)].shift(self.time60) == "11") & + (dataframe['resample_{}_bsps'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) > 0) & + (dataframe['resample_{}_bsps'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) < 10) + #(qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal'])) + ), + ['enter_long', 'enter_tag']] = (1, 'long_signal_chan') + dataframe.loc[ + ( + #(dataframe['state'].shift(1) == "10") & + #((dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)] == "11") | + (dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") & + (dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)].shift(self.time60) == "-11") & + (dataframe['resample_{}_bsps'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) < 0) + #(qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal'])) + ), + ['enter_short', 'enter_tag']] = (1, 'short_signal_chan') + return dataframe + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe.loc[ + ( + #(dataframe['state'].shift(1) == "10") & + (dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)].shift(self.time60) == "10") + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10") + ), + ['exit_long', 'exit_tag']] = (1, 'long_close_signal_chan') + dataframe.loc[ + ( + #(dataframe['state'].shift(1) == "-10") & + (dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)].shift(self.time60) == "-10") + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "-10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "-10") + ), + ['exit_short', 'exit_tag']] = (1, 'short_close_signal_chan') + return dataframe + def leverage(self, pair: str, current_time: datetime, current_rate: float, + proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str, + **kwargs) -> float: + return 1.0 + + def get_ticker_indicator(self): + return int(self.timeframe[:-1]) \ No newline at end of file diff --git a/strategies/Chan_SOL_60.py b/strategies/Chan_SOL_60.py new file mode 100644 index 0000000..fd4bc14 --- /dev/null +++ b/strategies/Chan_SOL_60.py @@ -0,0 +1,390 @@ +# --- Do not remove these libs --- +from freqtrade.strategy import IStrategy +from typing import Dict, List +from functools import reduce +from pandas import DataFrame, pandas +import freqtrade.vendor.qtpylib.indicators as qtpylib + +import sys +import os +#sys.setrecursionlimit(1000000) #例如这里设置为一百万 +#sys.path.append(os.path.abspath("/freqtrade/user_data/Chan")) +sys.path.append(os.path.abspath("/Users/jack/Project/freqtrade/user_data/Chan")) +#sys.path.append(os.path.abspath("/Users/jack/Documents/GitHub/freqtrade/user_data/Chan")) + +from ChanEnum import Chan_AUTYPE, Chan_DATA_FIELD, Chan_FX_TYPE, Chan_KLINE_DIR, Chan_KL_TYPE, Chan_BI_DIR +from ChanKLU import ChanKLU +from ChanCTime import ChanCTime +from ChanKLC import ChanKLC +from ChanBI import ChanBI +# -------------------------------- +from technical.util import resample_to_interval, resampled_merge +import talib.abstract as ta +import freqtrade.vendor.qtpylib.indicators as qtpylib +from datetime import datetime, timedelta, timezone +from freqtrade.persistence import Trade, Order +from typing import Optional + +import logging + +logger = logging.getLogger(__name__) + +### Now you can use logger.info('asfd') to log + +# freqtrade trade -c ./user_data/Chan.json --strategy Chan_SOL_60 --strategy-path ./user_data/strategies +# freqtrade backtesting -c ./user_data/Chan.json --strategy Chan_SOL_60 --strategy-path ./user_data/strategies --timerange=20250309- +# freqtrade download-data -c ./user_data/Chan.json -t 1m --pairs SOL/USDT:USDT --timerange=20240101- +# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss --strategy Chan_SOL_60 --strategy-path ./user_data/strategies -c ./user_data/Chan.json -e 200 --timerange=20241111-20241231 + +# sudo docker compose run --rm chan_btc backtesting -c ./user_data/Chan.json --strategy Chan_SOL_60 --strategy-path ./user_data/strategies --timerange=20250101- +# sudo docker compose run --rm chan_btc download-data -c ./user_data/Chan.json --pairs SOL/USDT:USDT -t 1m --timerange 20240101- +# sudo docker compose run --rm chan_btc trade -c ./user_data/Chan.json --strategy Chan_SOL_60 --strategy-path ./user_data/strategies + +class Chan_SOL_60(IStrategy): + INTERFACE_VERSION: int = 3 + # Minimal ROI designed for the strategy. + # This attribute will be overridden if the config file contains "minimal_roi" + minimal_roi = { + "0": 0.253, + "120": 0.159, + "240": 0.052, + "360": 0 + } + can_short = True + # Optimal stoploss designed for the strategy + # This attribute will be overridden if the config file contains "stoploss" + stoploss = -0.21 + + trailing_stop = False + trailing_stop_positive = 0.015 + trailing_stop_positive_offset = 0.043 + trailing_only_offset_is_reached = False + + # Optimal timeframe for the strategy + # timeframe = '15m' + startup_candle_count = 600 + + time5 = 5 + time30 = 30 + time60 = 60 + last_time = datetime.now() + big_size = 0 + big_state = "00" + big_state_list = [] + + small_size = 0 + small_state = "00" + small_state_list = [] + def informative_pairs(self): + + # get access to all pairs available in whitelist. + pairs = self.dp.current_whitelist() + # Assign tf to each pair so they can be downloaded and cached for strategy. + informative_pairs = [(pair, '1h') for pair in pairs] + # Optionally Add additional "static" pairs + #informative_pairs += [("ETH/USDT:USDT", "5m"),("ETH/USDT:USDT", "15m"),] + return informative_pairs + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + #macd = ta.MACD(dataframe) + #dataframe['macd'] = macd['macd'] + #dataframe['macdsignal'] = macd['macdsignal'] + #dataframe['macdhist'] = macd['macdhist'] + #dataframe['ema26'] = ta.EMA(dataframe, timeperiod=26) + #dataframe['ema52'] = ta.EMA(dataframe, timeperiod=52) + #dataframe['bsps'], dataframe['updown'], dataframe['bi_sure'] = self.get_bsps(dataframe) + if not self.dp: + # Don't do anything if DataProvider is not available. + return dataframe + + inf_tf = '1h' + # Get the informative pair + #informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=inf_tf) + # resample our dataframes + dataframe_5 = resample_to_interval(dataframe, self.get_ticker_indicator() * 5) + #dataframe_15 = resample_to_interval(dataframe, self.get_ticker_indicator() * 15) + #dataframe_30 = resample_to_interval(dataframe, self.get_ticker_indicator() * 30) + dataframe_60 = resample_to_interval(dataframe, self.get_ticker_indicator() * 60) + #dataframe_4h = resample_to_interval(dataframe, self.get_ticker_indicator() * 240) + #self.local_print(dataframe_60) + dataframe_60['rsi'] = ta.RSI(dataframe_60, timeperiod=14) + + #dataframe_1d = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1d') + #dataframe_1w = resample_to_interval(dataframe_1d, self.get_ticker_indicator() * 10080) + #dataframe_1m = resample_to_interval(dataframe_1d, self.get_ticker_indicator() * 43200) + + #dataframe_1d = resample_to_interval(dataframe, self.get_ticker_indicator() * 1440) + #dataframe_1w = resample_to_interval(dataframe, self.get_ticker_indicator() * 10080) + #dataframe_1m = resample_to_interval(dataframe, self.get_ticker_indicator() * 43200) + + #klc_list, klu_list = self.get_klc_list(dataframe) + #dataframe['state'] = self.resample_klc(self.cal_trend(klc_list), len(dataframe)) + #klc_list, klu_list = self.get_klc_list(dataframe) + #klc_list = self.copy_klu_to_klc(klu_list) + #dataframe['state'] = self.resample_klc(self.cal_klc_state(klc_list), len(dataframe)) + + + klc_list_5, klu_list_5 = self.get_klc_list(dataframe_5) + #klc_list_5 = self.copy_klu_to_klc(klu_list_5) + dataframe_5['state'] = self.resample_klc_list(self.cal_klc_state(klc_list_5), len(dataframe_5)) + + #klc_list_15, klu_list_15 = self.get_klc_list(dataframe_15) + #dataframe_15['state'] = self.resample_klc(self.cal_trend(klc_list_15), len(dataframe_15)) + + #klc_list_30, klu_list_30 = self.get_klc_list(dataframe_30) + #dataframe_30['state'] = self.resample_klc(self.cal_klc_state(klc_list_30), len(dataframe_30)) + + klc_list_60, klu_list_60 = self.get_klc_list(dataframe_60) + #klc_list_60 = self.copy_klu_to_klc(klu_list_60) + klc_list_60 = self.copy_klu_to_klc(self.get_kl_data(dataframe_60)) + dataframe_60['state'] = self.resample_klc_list(self.cal_klc_state(klc_list_60), len(dataframe_60)) + #for klc in klc_list_60: + #print(klc.time, klc.state, klc.start_klu.time) + #dataframe_5['state'] = self.resample_klc_list(self.cal_klc_state(self.copy_klu_to_klc(self.get_kl_data(dataframe_5))), len(dataframe_5)) + #dataframe_60['state'] = self.resample_klc_list(self.cal_klc_state(self.copy_klu_to_klc(self.get_kl_data(dataframe_60))), len(dataframe_60)) + #klc_list_4h, klu_list = self.get_klc_list(dataframe_4h) + #dataframe_4h['state'] = self.resample_klc(self.cal_state(klc_list_4h), len(dataframe_4h)) + #print(big_dataframe.iloc[-2]) + + #self.print_df(dataframe_60) + dataframe = resampled_merge(dataframe, dataframe_5) + #dataframe = resampled_merge(dataframe, dataframe_15) + #dataframe = resampled_merge(dataframe, dataframe_30) + dataframe = resampled_merge(dataframe, dataframe_60) + #dataframe = resampled_merge(dataframe, dataframe_4h) + #self.print_resample_df(dataframe, self.time60) + return dataframe + + def print_df(self, df): + for index in range(0, len(df)): + print(df['date'][index], df['rsi'][index], df['state'][index]) + def print_resample_df(self, df, time): + for index in range(0, len(df)): + cn1 = 'resample_{}_date'.format(self.get_ticker_indicator()*time) + cn2 = 'resample_{}_rsi'.format(self.get_ticker_indicator()*time) + cn3 = 'resample_{}_state'.format(self.get_ticker_indicator()*time) + print(df[cn1][index], df[cn2][index], df[cn3][index]) + def local_print(self, df): + fast = 7 + slow = 14 + macd = ta.MACD(df, fast=fast, slow=slow) + df['rsi'] = ta.RSI(df, timeperiod=14) + df['macd'] = macd['macd'] + df['macdsignal'] = macd['macdsignal'] + df['macdhist'] = macd['macdhist'] + df['ema26'] = ta.EMA(df, timeperiod=26) + df['ema52'] = ta.EMA(df, timeperiod=52) + df['ma5'] = ta.MA(df, timeperiod=5) + df['ma10'] = ta.MA(df, timeperiod=10) + df['masub'] = df['ma5'].subtract(df['ma10']) + for index in range(0, len(df)): + print(df['date'][index], df['macdhist'][index], df['macd'][index], df['macdsignal'][index], df['masub'][index], df['rsi'][index]) + def cal_dataframes(self, dataframe, big_dataframe, time): + df_list = self.copy_klu_to_klc(self.get_kl_data(dataframe)) + big_df_list = self.copy_klu_to_klc(self.get_kl_data(big_dataframe)) + big_df_state_list = [] + big_df_state_list.append("00") + for index in range(1, len(big_df_list)-1): + k1 = big_df_list[index-1] + k2 = big_df_list[index] + k3 = df_list[(index+1)*time] + #print(k2.time, k2.high, k2.low, k3.time, k3.high, k3.low) + self.get_klc_state(k1, k2, k3) + big_df_state_list.append(k2.state) + big_df_state_list.append("00") + return big_df_state_list + + # (1,1) = 1, (1,0) = 2, (-1,1) = 3, (-1, 0) = 4, (0,0) = 0 + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe.loc[ + ( + #(dataframe['state'].shift(1) == "-10") & + #((dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)] == "-11") | + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "-10") & + (dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "-10") + (dataframe['resample_{}_rsi'.format(self.get_ticker_indicator()*self.time60)] < 30) + #(qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal'])) + ), + ['enter_long', 'enter_tag']] = (1, 'long_signal_chan') + dataframe.loc[ + ( + #(dataframe['state'].shift(1) == "10") & + #((dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)] == "11") | + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") & + (dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10") + (dataframe['resample_{}_rsi'.format(self.get_ticker_indicator()*self.time60)] > 60) + #(qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal'])) + ), + ['enter_short', 'enter_tag']] = (1, 'short_signal_chan') + return dataframe + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe.loc[ + ( + #(dataframe['state'].shift(1) == "10") & + (dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10") + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10") + ), + ['exit_long', 'exit_tag']] = (1, 'long_close_signal_chan') + dataframe.loc[ + ( + #(dataframe['state'].shift(1) == "-10") & + (dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "-10") + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "-10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "-10") + ), + ['exit_short', 'exit_tag']] = (1, 'short_close_signal_chan') + return dataframe + def leverage(self, pair: str, current_time: datetime, current_rate: float, + proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str, + **kwargs) -> float: + return 1.0 + # append when the last klu is not included + def resample_klc_list(self, klc_list, length): + re_klc_list = [] + klc_index = 0 + klc = None + for index in range(0, length): + if klc_index == len(klc_list): + klc_index -= 1 + klc = klc_list[klc_index] + if klc.end_klu and index == klc.end_klu.idx: + re_klc_list.append(klc.state) + klc_index += 1 + else: + re_klc_list.append("00") + #print(index, klc.time, klc.state, klc.fx, klc.high, klc.low) + #if self.last_time + timedelta(minutes=1) < datetime.now(): + #print(klc.time, klc.state, klc.fx, re_klc_list[-1], re_klc_list[-2], re_klc_list[-3], re_klc_list[-4], re_klc_list[-5]) + return re_klc_list + def cal_klc_state(self, klc_list): + index = 0 + for index in range(1, len(klc_list)-1): + k1 = klc_list[index-1] + k2 = klc_list[index] + k3 = klc_list[index+1] + self.cal_pattern(k1, k2, k3) + if k2.fx == Chan_FX_TYPE.TOP: + k2.set_state("10") + if k2.fx == Chan_FX_TYPE.BOTTOM: + k2.set_state("-10") + if k2.fx == Chan_FX_TYPE.UP: + k2.set_state("11") + if k2.fx == Chan_FX_TYPE.DOWN: + k2.set_state("-11") + #print(index, k2.time, k2.fx) + + #for klc in klc_list: + #print(klc.time, klc.fx) + #print(klc_list[len(klc_list)-2].time, klc_list[len(klc_list)-2].fx, klc_list[len(klc_list)-2].state, klc_list[-2].time) + return klc_list + def get_klc_state(self, k1, k2, k3): + self.cal_pattern(k1, k2, k3) + if k2.fx == Chan_FX_TYPE.TOP: + k2.set_state("10") + if k2.fx == Chan_FX_TYPE.BOTTOM: + k2.set_state("-10") + if k2.fx == Chan_FX_TYPE.UP: + k2.set_state("11") + if k2.fx == Chan_FX_TYPE.DOWN: + k2.set_state("-11") + def cal_pattern(self, k1, k2, k3): + if k2.high >= k1.high and k2.high >= k3.high: + k2.set_fx(Chan_FX_TYPE.TOP) + else: + if k2.low <= k1.low and k2.low <= k3.low: + k2.set_fx(Chan_FX_TYPE.BOTTOM) + #print(k1.time, k2.time, k3.time, k1.low, k2.low, k3.low, k3.open, k3.close, "k2") + else: + if k1.high >= k2.high and k2.high >= k3.high: + k2.set_fx(Chan_FX_TYPE.DOWN) + else: + if k1.high <= k2.high and k2.high <= k3.high: + k2.set_fx(Chan_FX_TYPE.UP) + if k2.fx == Chan_FX_TYPE.UNKNOWN: + if k2.close >= k1.close and k2.close >= k3.close: + k2.set_fx(Chan_FX_TYPE.TOP) + else: + if k2.close <= k1.close and k2.close <= k3.close: + k2.set_fx(Chan_FX_TYPE.BOTTOM) + #print(k2.time, "close") + else: + if k2.close >= k1.close and k2.close <= k3.close: + k2.set_fx(Chan_FX_TYPE.UP) + else: + if k2.close <= k1.close and k2.close >= k3.close: + k2.set_fx(Chan_FX_TYPE.DOWN) + + # 根据结合律,合并K线 + def get_klc_list(self, dataframe): + klu_list = self.get_kl_data(dataframe) + klc_list = [] + last_klu = None + for klu in klu_list: + if len(klc_list) > 0: + last_klc = klc_list[-1] + included = last_klc.check_klu_included(klu) + if not included: + dir = Chan_KLINE_DIR.DOWN + if last_klc.high < klu.high: + dir = Chan_KLINE_DIR.UP + klc = ChanKLC(klu, index=len(klc_list), dir=dir) + klc_list.append(klc) + last_klc.set_next(klc) + klc.set_pre(last_klc) + last_klc.set_end_klu(last_klu) + else: + dir = Chan_KLINE_DIR.UP + if klu.open > klu.close: + dir = Chan_KLINE_DIR.DOWN + klc = ChanKLC(klu, 0, dir) + klc_list.append(klc) + last_klu = klu + return klc_list, klu_list + def copy_klu_to_klc(self, klu_list): + klc_list = [] + for klu in klu_list: + if len(klc_list) > 0: + last_klc = klc_list[-1] + dir = Chan_KLINE_DIR.DOWN + if last_klc.high < klu.high: + dir = Chan_KLINE_DIR.UP + klc = ChanKLC(klu, index=len(klc_list), dir=dir) + klc.set_end_klu(klu) + klc_list.append(klc) + last_klc.set_next(klc) + klc.set_pre(last_klc) + else: + klc = ChanKLC(klu, 0) + klc_list.append(klc) + klc.set_end_klu(klu) + return klc_list + def get_kl_data(self, dataframe:DataFrame): + fields = "time,open,high,low,close,volume" + klu_list = [] + for i in range(0, len(dataframe)): + item = dataframe.iloc[i] + date = item['date'] + o = item['open'] + h = item['high'] + l = item['low'] + c = item['close'] + v = item['volume'] + #time_obj = date.fromtimestamp(date) + time_str = date.strftime('%Y-%m-%d %H:%M:%S') + item_data = [ + time_str, + o, + h, + l, + c, + v + ] + #klu = KLU(self.create_item_dict(item_data, GetColumnNameFromFieldList(fields))) + klu = ChanKLU(time_str, o, h, l, c, v) + klu.set_idx(i) + klu_list.append(klu) + return klu_list + def get_ticker_indicator(self): + return int(self.timeframe[:-1]) \ No newline at end of file diff --git a/strategies/Deepseek_BTC.py b/strategies/Deepseek_BTC.py new file mode 100644 index 0000000..fa66db6 --- /dev/null +++ b/strategies/Deepseek_BTC.py @@ -0,0 +1,196 @@ +# --- Do not remove these libs --- +from freqtrade.strategy import IStrategy +from typing import Dict, List +from functools import reduce +from pandas import DataFrame, pandas +import freqtrade.vendor.qtpylib.indicators as qtpylib + +import sys +import os +#sys.setrecursionlimit(1000000) #例如这里设置为一百万 +#sys.path.append(os.path.abspath("/freqtrade/user_data/Chan")) +sys.path.append(os.path.abspath("/Users/jack/Project/freqtrade/user_data/Chan")) +#sys.path.append(os.path.abspath("/Users/jack/Documents/GitHub/freqtrade/user_data/Chan")) +from ChanLun import ChanLun +# -------------------------------- +from technical.util import resample_to_interval, resampled_merge +import talib.abstract as ta +import freqtrade.vendor.qtpylib.indicators as qtpylib +from datetime import datetime, timedelta, timezone +from freqtrade.persistence import Trade, Order +from typing import Optional +from ChanPY import ChanPY +import logging +logger = logging.getLogger(__name__) +from openai import OpenAI +### Now you can use logger.info('asfd') to log + +# freqtrade trade -c ./user_data/Deepseek_BTC.json --strategy Deepseek_BTC --strategy-path ./user_data/strategies +# freqtrade backtesting -c ./user_data/Deepseek_BTC.json --strategy Deepseek_BTC --strategy-path ./user_data/strategies --timerange=20250309- +# freqtrade download-data -c ./user_data/Deepseek_BTC.json -t 1m --pairs BTC/USDT:USDT --timerange=20240101- +# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss --strategy Deepseek_BTC --strategy-path ./user_data/strategies -c ./user_data/Chan.json -e 200 --timerange=20250101-20250215 + +# sudo docker compose run --rm chan_btc backtesting -c ./user_data/Chan.json --strategy Deepseek_BTC --strategy-path ./user_data/strategies --timerange=20250101- +# sudo docker compose run --rm chan_btc download-data -c ./user_data/Deepseek_BTC.json --pairs SOL/USDT:USDT -t 1m --timerange 20240101- +# sudo docker compose run --rm chan_btc trade -c ./user_data/Chan.json --strategy Deepseek_BTC --strategy-path ./user_data/strategies +def get_ai(): + client = OpenAI(api_key="sk-d802019a175a4a34ac73c4690ce0a291", base_url="https://api.deepseek.com") + return client +class Deepseek_BTC(IStrategy): + INTERFACE_VERSION: int = 3 + # Minimal ROI designed for the strategy. + # This attribute will be overridden if the config file contains "minimal_roi" + minimal_roi = { + "0": 0.253, + "120": 0.159, + "240": 0.052, + "360": 0 + } + can_short = True + # Optimal stoploss designed for the strategy + # This attribute will be overridden if the config file contains "stoploss" + stoploss = -0.21 + + trailing_stop = False + trailing_stop_positive = 0.015 + trailing_stop_positive_offset = 0.043 + trailing_only_offset_is_reached = False + + # Optimal timeframe for the strategy + # timeframe = '15m' + startup_candle_count = 100 + df_size = 0 + state_list = [] + last_time = datetime.now() + client = get_ai() + + def get_ai_state(self, dataframe): + # 获取账户数据、订单历史和当前订单 + current_data = {} + + # 获取账户余额信息 + current_data['balance'] = self.wallets.get_all_balances() + + # 获取交易历史 + try: + # 尝试新的API方式获取已关闭的交易 + closed_trades = Trade.get_trades_proxy(is_open=False) + trade_history = [] + for trade in closed_trades: + trade_history.append({ + 'pair': trade.pair, + 'open_date': str(trade.open_date), + 'close_date': str(trade.close_date), + 'open_rate': float(trade.open_rate), + 'close_rate': float(trade.close_rate), + 'stake_amount': float(trade.stake_amount), + 'amount': float(trade.amount), + 'profit_ratio': float(trade.profit_ratio) if trade.profit_ratio else 0, + 'profit_abs': float(trade.profit_abs) if trade.profit_abs else 0, + 'trade_duration': trade.close_date.timestamp() - trade.open_date.timestamp() if trade.close_date else 0, + 'is_short': trade.is_short + }) + current_data['trade_history'] = trade_history + except Exception as e: + logger.error(f"获取交易历史时出错: {e}") + current_data['trade_history'] = [] + + # 获取当前正在进行的订单 + try: + # 尝试新的API方式获取开放的交易 + open_trades = Trade.get_trades_proxy(is_open=True) + current_trades = [] + for trade in open_trades: + current_trades.append({ + 'pair': trade.pair, + 'open_date': str(trade.open_date), + 'open_rate': float(trade.open_rate), + 'stake_amount': float(trade.stake_amount), + 'amount': float(trade.amount), + 'current_rate': float(self.dp.get_ticker(trade.pair)['close']) if self.dp else 0, + 'current_profit_ratio': float(trade.calc_profit_ratio(self.dp.get_ticker(trade.pair)['close'])) if self.dp else 0, + 'trade_duration': datetime.now(timezone.utc).timestamp() - trade.open_date.timestamp(), + 'is_short': trade.is_short, + 'open_orders': [{'order_id': order.order_id, 'order_type': order.ft_order_side} for order in trade.orders] + }) + current_data['current_trades'] = current_trades + except Exception as e: + logger.error(f"获取当前订单时出错: {e}") + current_data['current_trades'] = [] + + # 获取交易所限制和状态 + if hasattr(self, 'exchange'): + current_data['exchange_info'] = { + 'name': self.exchange.name if hasattr(self.exchange, 'name') else '', + 'trading_mode': self.config.get('trading_mode', ''), + 'stake_currency': self.config.get('stake_currency', ''), + 'dry_run': self.config.get('dry_run', True) + } + + response = self.client.chat.completions.create( + model="deepseek-reasoner", + messages=[ + {"role": "system", "content": "你是缠论高手"}, + {"role": "user", "content": f"我们交易的是币安的比特币合约, 数据格式是json, 数据包括现有的持仓, 仓位历史, 账户余额, 你用缠论分析之后, 给出以下分析, 最近的一个中枢在哪里,现在的趋势是什么,现在是否是买卖点,如果是,是那一类买卖点,应该进行何种操作。交易数据: {current_data}\n图表数据: {dataframe}"}, + ], + stream=False + ) + res = response.choices[0].message.content + print(res) + return res + def get_state(self, dataframe): + if self.df_size == 0: + for index in range(0, len(dataframe)): + self.state_list.append('0') + self.df_size = len(dataframe) + elif self.df_size < len(dataframe): + state = self.get_ai_state(dataframe) + self.state_list.append('0') + self.df_size = len(dataframe) + dataframe['state'] = self.state_list + #print(dataframe.tail(10)) + return dataframe + def informative_pairs(self): + # get access to all pairs available in whitelist. + pairs = self.dp.current_whitelist() + # Assign tf to each pair so they can be downloaded and cached for strategy. + informative_pairs = [(pair, '1h') for pair in pairs] + # Optionally Add additional "static" pairs + #informative_pairs += [("ETH/USDT:USDT", "5m"),("ETH/USDT:USDT", "15m"),] + return informative_pairs + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe = self.get_state(dataframe) + return dataframe + # (1,1) = 1, (1,0) = 2, (-1,1) = 3, (-1, 0) = 4, (0,0) = 0 + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + + dataframe.loc[ + ( + (dataframe['state'] == "10") + ), + ['enter_long', 'enter_tag']] = (1, 'long_signal_chan') + dataframe.loc[ + ( + (dataframe['state'] == "-10") + ), + ['enter_short', 'enter_tag']] = (1, 'short_signal_chan') + return dataframe + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe.loc[ + ( + (dataframe['state'] == "99") + ), + ['exit_long', 'exit_tag']] = (1, 'long_close_signal_chan') + dataframe.loc[ + ( + (dataframe['state'] == "99") + ), + ['exit_short', 'exit_tag']] = (1, 'short_close_signal_chan') + return dataframe + def leverage(self, pair: str, current_time: datetime, current_rate: float, + proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str, + **kwargs) -> float: + return 1.0 + + def get_ticker_indicator(self): + return int(self.timeframe[:-1]) \ No newline at end of file diff --git a/strategies/Deepseek_Trader.py b/strategies/Deepseek_Trader.py new file mode 100644 index 0000000..e2644a5 --- /dev/null +++ b/strategies/Deepseek_Trader.py @@ -0,0 +1,91 @@ +import logging +from freqtrade.strategy import IStrategy +from pandas import DataFrame + +logger = logging.getLogger(__name__) + +# freqtrade trade -c ./user_data/Deepseek_Trader.json --strategy Deepseek_Trader --strategy-path ./user_data/strategies +# freqtrade backtesting -c ./user_data/Deepseek_Trader.json --strategy Deepseek_Trader --strategy-path ./user_data/strategies --timerange=20250309- +# freqtrade download-data -c ./user_data/Deepseek_Trader.json -t 1m --pairs BTC/USDT:USDT --timerange=20240101- + + + +class Deepseek_Trader(IStrategy): + """ + 空的ChanStrategy策略类 + 这个策略只是作为Freqtrade的接口,实际交易决策由DeepSeek LLM完成 + """ + + # 策略界面选项 + minimal_roi = { + "120": 0.2, + "240": 0.15, + "360": 0.1, + "480": 0.5, + "960": 0.2, + "1440": 0.0 + } + + # 止损设置 + stoploss = -0.2 + # 交易超时设置 + timeframe = '1m' + + # 仅在新K线时执行策略 + process_only_new_candles = False + + # 可以做空 + can_short = True + + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + 为每个交易对添加指标 + """ + # 不添加任何指标,由DeepSeek LLM决策 + return dataframe + + def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + 基于指标生成买入信号 + 这里我们不生成任何实际交易信号,但需要设置buy列以确保兼容性 + 默认不进行交易,由DeepSeek LLM决策 + """ + # 添加必要的buy列,但默认不触发信号(值为0) + dataframe['buy'] = 0 + + return dataframe + + def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + 基于指标生成卖出信号 + 这里我们不生成任何实际交易信号,但需要设置sell列以确保兼容性 + 默认不进行交易,由DeepSeek LLM决策 + """ + # 添加必要的sell列,但默认不触发信号(值为0) + dataframe['sell'] = 0 + + return dataframe + + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + 基于指标生成多空信号 + 这里我们不生成任何实际交易信号,但需要设置enter_long列以避免错误 + 默认不进行交易,由DeepSeek LLM决策 + """ + # 添加必要的enter_long列,但默认不触发信号(值为0) + dataframe['enter_long'] = 0 + dataframe['enter_short'] = 0 + + return dataframe + + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + 基于指标生成多空平仓信号 + 这里我们不生成任何实际交易信号,但需要设置exit_long列以避免错误 + 默认不进行交易,由DeepSeek LLM决策 + """ + # 添加必要的exit_long列,但默认不触发信号(值为0) + dataframe['exit_long'] = 0 + dataframe['exit_short'] = 0 + + return dataframe \ No newline at end of file diff --git a/strategies/XGB.py b/strategies/XGB.py new file mode 100644 index 0000000..52ef864 --- /dev/null +++ b/strategies/XGB.py @@ -0,0 +1,571 @@ +import logging +from functools import reduce +from typing import Dict +import numpy as np +import talib.abstract as ta +from pandas import DataFrame +from technical import qtpylib +from talib import MACD, RSI +from datetime import datetime +import pandas as pd +import uuid +from freqtrade.strategy import IStrategy +logger = logging.getLogger(__name__) +# freqtrade backtesting --config user_data/ChanLun_XGB.json --strategy ChanLun_XGB --freqaimodel XGBoostClassifier --timerange=20250401-20250421 + +class ChanLun_XGB(IStrategy): + minimal_roi = {"0": 0.1, "240": -1} + plot_config = { + "main_plot": {}, + "subplots": { + "&-s_close": {"&-s_close": {"color": "blue"}}, + "do_predict": {"do_predict": {"color": "brown"}}, + }, + } + process_only_new_candles = True + stoploss = -0.05 + use_exit_signal = True + startup_candle_count: int = 100 # Ensure sufficient data for pivots + can_short = True + + freqai_info = { + "feature_parameters": { + "label_period_candles": 24 + } + } + + def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: Dict, **kwargs) -> DataFrame: + """Basic technical indicators for various periods.""" + logger.debug("Starting feature_engineering_expand_all") + dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period) + dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period) + dataframe["%-adx-period"] = ta.ADX(dataframe, timeperiod=period) + dataframe["%-sma-period"] = ta.SMA(dataframe, timeperiod=period) + dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period) + + bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=period, stds=2.2) + dataframe["bb_lowerband-period"] = bollinger["lower"] + dataframe["bb_middleband-period"] = bollinger["mid"] + dataframe["bb_upperband-period"] = bollinger["upper"] + + dataframe["%-bb_width-period"] = ( + (dataframe["bb_upperband-period"] - dataframe["bb_lowerband-period"]) / dataframe["bb_middleband-period"] + ) + dataframe["%-close-bb_lower-period"] = dataframe["close"] / dataframe["bb_lowerband-period"] + dataframe["%-roc-period"] = ta.ROC(dataframe, timeperiod=period) + dataframe["%-relative_volume-period"] = dataframe["volume"] / dataframe["volume"].rolling(period).mean() + + return dataframe.fillna(0) + + def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame: + """Basic price and volume features.""" + logger.debug("Starting feature_engineering_expand_basic") + dataframe["%-pct-change"] = dataframe["close"].pct_change() + dataframe["%-raw_volume"] = dataframe["volume"] + dataframe["%-raw_price"] = dataframe["close"] + return dataframe.fillna(0) + + def feature_engineering_standard(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame: + """Advanced feature engineering with Chan Lun and technical indicators.""" + logger.info(f"Starting feature_engineering_standard for pair {metadata.get('pair', 'unknown')}") + if dataframe.empty or len(dataframe) < self.startup_candle_count: + logger.error(f"Input DataFrame is empty or too small: {len(dataframe)} candles") + return self.ensure_columns(dataframe, [ + '%-buy_signal', '%-bottom_strength', '%-sell_signal', '%-top_strength', + '%-macd_top_div', '%-rsi-14', '%-top_combo', '%-top_candle_pattern', '%-bottom_combo' + ]) + + df = dataframe.copy() + if df['close'].isna().any(): + logger.warning(f"Missing data: {df[['open', 'high', 'low', 'close', 'volume']].isna().sum()}") + + # Time-based features + df["%-day_of_week"] = df["date"].dt.dayofweek + df["%-hour_of_day"] = df["date"].dt.hour + + # Fractal detection + df = self.detect_fractals(df) + logger.debug("Fractal detection completed") + + # MACD and RSI + macd, signal, hist = MACD(df['close'], fastperiod=12, slowperiod=26, signalperiod=9) + df['macd'] = macd + df['macd_signal'] = signal + df['macd_hist'] = hist + df['macd_hist_sum'] = df['macd_hist'].rolling(5).sum() + df["%-rsi-14"] = RSI(df['close'], timeperiod=14) + logger.debug("MACD and RSI calculated") + + # Candlestick features + df["%-close_open_diff"] = (df["close"] - df["open"]) / df["open"].replace(0, np.nan) + df["%-body_length"] = abs(df["close"] - df["open"]) / df["close"].replace(0, np.nan) + df["%-upper_shadow"] = (df["high"] - df[["open", "close"]].max(axis=1)) / df["close"].replace(0, np.nan) + df["%-lower_shadow"] = (df[["open", "close"]].min(axis=1) - df["low"]) / df["close"].replace(0, np.nan) + + # Candle color and trend + df["%-candle_color"] = (df["close"] > df["open"]).astype(int) * 2 - 1 + df["%-consec_same_color"] = df["%-candle_color"].groupby((df["%-candle_color"] != df["%-candle_color"].shift()).cumsum()).cumcount() + 1 + + # Fractal strength + df["%-bottom_strength"], df["%-top_strength"] = self.calculate_fractal_strength(df) + logger.debug("Fractal strength calculated") + + # Price relationships + for i in [1, 2, 3]: + high_shift = df['high'].shift(i).replace(0, df['high'].mean()) + low_shift = df['low'].shift(i).replace(0, df['low'].mean()) + df[f"%-high_ratio_{i}"] = df['high'] / high_shift + df[f"%-low_ratio_{i}"] = df['low'] / low_shift + + # MACD divergence + df["%-macd_bottom_div"] = ((df['low'] < df['low'].rolling(5).min().shift(1)) & (df['macd'] > df['macd'].rolling(5).min().shift(1))).astype(int) + df["%-macd_top_div"] = ((df['high'] > df['high'].rolling(5).max().shift(1)) & (df['macd'] < df['macd'].rolling(5).max().shift(1))).astype(int) + + # Additional features + df["%-volume_change"] = df['volume'].pct_change() + df["%-volatility"] = df['close'].rolling(5).std() + df["%-price_range_20"] = (df['high'].rolling(5).max() - df['low'].rolling(5).min()) / df['close'].replace(0, np.nan) + df["%-potential_top"] = (df['is_top'] & (df["%-rsi-14"] > 70) & (df["%-macd_top_div"] == 1) & + (df['volume'] > df['volume'].rolling(20).mean()) & (df['close'] < df['open'])).astype(int) + df["%-potential_bottom"] = (df['is_bottom'] & (df["%-rsi-14"] < 30) & (df["%-macd_bottom_div"] == 1)).astype(int) + + # Fractal distance + df["%-last_fractal_distance"] = self.calculate_fractal_distance(df) + + # Advanced features + df["%-macd_hist_change"] = df['macd_hist_sum'].pct_change().replace([np.inf, -np.inf], 0) + df["%-volume_divergence"] = (df['close'].pct_change() - df['volume'].pct_change()).abs() + df["%-breakout_high"] = (df['high'] > df['high'].shift(1).rolling(20).max()).astype(int) + df["%-breakout_low"] = (df['low'] < df['low'].shift(1).rolling(20).min()).astype(int) + df["%-top_prominence"] = (df['high'] - df['high'].shift(1).rolling(5).mean()) / (df['high'].shift(1).rolling(5).std() + 1e-6) + df["%-top_prominence"] = df["%-top_prominence"].clip(-100, 100) + df["%-macd_hist_decline"] = df['macd_hist'].rolling(3).apply( + lambda x: 1 if all(x[i] > x[i+1] for i in range(len(x)-1)) else 0, raw=True) + df["%-top_candle_pattern"] = ((df['close'].shift(1) > df['open'].shift(1)) & + (df['close'] < df['open']) & + (df['close'] < df['open'].shift(1))).astype(int) + df["%-bottom_combo"] = (df['is_bottom'] & (df["%-rsi-14"] < 40) & (df['macd_hist'] > 0) & + (df['volume'] > df['volume'].rolling(20).mean())).astype(int) + df["%-top_combo"] = (df['is_top'] & (df["%-rsi-14"] > 60) & (df['macd_hist'] < 0) & + (df['volume'] > df['volume'].rolling(20).mean())).astype(int) + df["%-post_top_decline"] = self.calculate_post_top_decline(df) + df["%-resistance_distance"] = self.calculate_resistance_distance(df) + + # Stroke and pivot features + strokes = self.detect_strokes(df) + df["%-macd_hist_dynamic"], df["%-pivot_distance"], df["%-buy_signal"], df["%-sell_signal"] = self.process_strokes_and_pivots(df, strokes) + logger.debug("Stroke and pivot features completed") + + # Validate required columns + required_columns = [ + '%-buy_signal', '%-bottom_strength', '%-sell_signal', '%-top_strength', + '%-macd_top_div', '%-rsi-14', '%-top_combo', '%-top_candle_pattern', '%-bottom_combo' + ] + missing_columns = [col for col in required_columns if col not in df.columns] + if missing_columns: + logger.error(f"Missing required columns: {missing_columns}") + df = self.ensure_columns(df, missing_columns) + + # Clean up + num_columns = df.select_dtypes(include=[np.number]).columns + df[num_columns] = df[num_columns].replace([np.inf, -np.inf], 0).fillna(0) + logger.info(f"Completed feature_engineering_standard. Columns: {list(df.columns)}") + return df + + def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame: + """Set prediction targets for FreqAI.""" + logger.debug(f"Setting FreqAI targets for pair {metadata.get('pair', 'unknown')}") + label_period = self.freqai_info["feature_parameters"]["label_period_candles"] + + # Calculate future return + future_return = ( + dataframe["close"].shift(-label_period).rolling(label_period).mean() / dataframe["close"] - 1 + ) + + # 二分类标签:1(买入/上涨),0(卖出/下跌) - 更简单且不易出错 + dataframe["&-s_close"] = np.where(future_return > 0.01, 1, 0).astype(int) + + logger.debug(f"Label distribution: {dataframe['&-s_close'].value_counts().to_dict()}") + return dataframe.fillna(0) + + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """Populate indicators, leveraging FreqAI if available.""" + logger.info(f"Starting populate_indicators for pair {metadata.get('pair', 'unknown')}") + # Always run feature_engineering_standard to ensure custom features + dataframe = self.feature_engineering_standard(dataframe, metadata) + + if hasattr(self, "freqai"): + logger.info("Running FreqAI pipeline") + # Preserve custom features + custom_features = [col for col in dataframe.columns if col.startswith('%-')] + base_columns = ['date', 'close', 'open', 'high', 'low', 'volume'] + # 仅保存确定存在的列 + preserve_columns = custom_features + [col for col in base_columns if col in dataframe.columns] + temp_df = dataframe[preserve_columns] + + # 设置FreqAI目标 + dataframe = self.set_freqai_targets(dataframe, metadata) + + # Run FreqAI + try: + freqai_df = self.freqai.start(dataframe, metadata, self) + # Merge back custom features + freqai_df = freqai_df.combine_first(temp_df) + dataframe = freqai_df + except Exception as e: + logger.error(f"FreqAI pipeline failed: {e}") + # Fallback to custom features + logger.info(f"Completed populate_indicators. Columns: {list(dataframe.columns)}") + return dataframe + + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """Generate entry signals for the strategy.""" + dataframe.loc[:, "enter_long"] = 0 + dataframe.loc[:, "enter_short"] = 0 + + # 确保数据框中的列存在 + dataframe = self.ensure_columns(dataframe) + + # 只有当do_predict列存在时才使用它 + if "do_predict" in dataframe.columns: + # 根据AI预测值设置交易信号 - 使用二分类结果 + mask = (dataframe["do_predict"] == 1) + dataframe.loc[mask, "enter_long"] = 1 + + # 设置空头信号 (如果策略支持空头) + if self.can_short: + mask = (dataframe["do_predict"] == 0) + dataframe.loc[mask, "enter_short"] = 1 + + # 将NaN值替换为0 + dataframe = dataframe.fillna(0) + + return dataframe + + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """Generate exit signals for the strategy.""" + dataframe.loc[:, "exit_long"] = 0 + dataframe.loc[:, "exit_short"] = 0 + + # 确保数据框中的列存在 + dataframe = self.ensure_columns(dataframe) + + # 只有当do_predict列存在时才使用它 + if "do_predict" in dataframe.columns: + # 多头平仓信号 + mask = (dataframe["do_predict"] == 0) + dataframe.loc[mask, "exit_long"] = 1 + + # 空头平仓信号 + if self.can_short: + mask = (dataframe["do_predict"] == 1) + dataframe.loc[mask, "exit_short"] = 1 + + # 将NaN值替换为0 + dataframe = dataframe.fillna(0) + + return dataframe + + def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, time_in_force: str, + current_time, entry_tag, side: str, **kwargs) -> bool: + """Confirm trade entry with additional checks.""" + logger.debug(f"Confirming trade entry for {pair}, side: {side}") + df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) + last_candle = df.iloc[-1].squeeze() + df = self.ensure_columns(df, ['%-bottom_strength', '%-buy_signal', '%-top_strength', '%-sell_signal', '%-rsi-14']) + + if side == "long": + if rate > (last_candle["close"] * 1.0025): + logger.debug(f"Long entry rejected: rate {rate} exceeds threshold") + return False + return last_candle["%-bottom_strength"] > 1.5 or last_candle["%-buy_signal"] > 0 + else: + if rate < (last_candle["close"] * 0.9975): + logger.debug(f"Short entry rejected: rate {rate} below threshold") + return False + return last_candle["%-top_strength"] > 2.0 or last_candle["%-sell_signal"] > 0 + + def detect_fractals(self, df: DataFrame) -> DataFrame: + """Detect top and bottom fractals.""" + logger.debug("Detecting fractals") + df['is_top'] = ( + (df['high'] > df['high'].shift(1)) & (df['high'] > df['high'].shift(2)) & + (df['high'] > df['high'].shift(-1)) & (df['high'] > df['high'].shift(-2)) + ) + df['is_bottom'] = ( + (df['low'] < df['low'].shift(1)) & (df['low'] < df['low'].shift(2)) & + (df['low'] < df['low'].shift(-1)) & (df['low'] < df['low'].shift(-2)) + ) + return df.fillna({'is_top': False, 'is_bottom': False}) + + def calculate_fractal_strength(self, df: DataFrame) -> tuple: + """Calculate strength of fractals.""" + logger.debug("Calculating fractal strength") + bottom_strength = np.zeros(len(df)) + top_strength = np.zeros(len(df)) + + for i in range(2, len(df) - 2): + if df['is_bottom'].iloc[i]: + strength = 0.0 + pre_decline = (df['low'].iloc[i-2:i].min() - df['low'].iloc[i]) / df['low'].iloc[i] + post_rise = (df['high'].iloc[i+1:i+3].max() - df['high'].iloc[i]) / df['high'].iloc[i] + strength += min(pre_decline * 10, 1.0) + min(post_rise * 10, 1.0) + vol_surge = df['volume'].iloc[i] / df['volume'].iloc[i-3:i].mean() if df['volume'].iloc[i-3:i].mean() > 0 else 1 + strength += min(vol_surge / 3, 1.0) + if any(abs(df['low'].iloc[j] - df['low'].iloc[i]) / df['low'].iloc[i] < 0.01 for j in range(max(0, i-20), i)): + strength += 1.0 + bottom_strength[i] = min(strength, 5.0) + + if df['is_top'].iloc[i]: + strength = 0.0 + pre_rise = (df['high'].iloc[i] - df['high'].iloc[i-2:i].max()) / df['high'].iloc[i] + post_decline = (df['low'].iloc[i] - df['low'].iloc[i+1:i+3].min()) / df['low'].iloc[i] + strength += min(pre_rise * 10, 1.0) + min(post_decline * 10, 1.0) + vol_surge = df['volume'].iloc[i] / df['volume'].iloc[i-3:i].mean() if df['volume'].iloc[i-3:i].mean() > 0 else 1 + strength += min(vol_surge / 3, 1.0) + if any(abs(df['high'].iloc[j] - df['high'].iloc[i]) / df['high'].iloc[i] < 0.01 for j in range(max(0, i-20), i)): + strength += 1.0 + bearish_count = sum(df['close'].iloc[i:i+3] < df['open'].iloc[i:i+3]) + strength += min(bearish_count * 0.5, 1.5) + top_strength[i] = min(strength, 6.0) + + return bottom_strength, top_strength + + def calculate_fractal_distance(self, df: DataFrame) -> pd.Series: + """Calculate distance to last fractal.""" + logger.debug("Calculating fractal distance") + fractal_indices = df[df['is_top'] | df['is_bottom']].index + distances = pd.Series(0, index=df.index) + + for i in range(1, len(df)): + if fractal_indices[fractal_indices < df.index[i]].size > 0: + last_fractal_idx = fractal_indices[fractal_indices < df.index[i]][-1] + if isinstance(df.index[i], pd.Timestamp) and isinstance(last_fractal_idx, pd.Timestamp): + time_diff = (df.index[i] - last_fractal_idx).total_seconds() / 60 + else: + time_diff = i - df.index.get_loc(last_fractal_idx) + distances.iloc[i] = time_diff + return distances + + def calculate_post_top_decline(self, df: DataFrame) -> pd.Series: + """Calculate post-top decline.""" + logger.debug("Calculating post-top decline") + post_top_decline = pd.Series(0.0, index=df.index) + for i in range(2, len(df)-3): + if df['is_top'].iloc[i]: + decline = (df['high'].iloc[i] - df['low'].iloc[i+1:i+4].min()) / df['high'].iloc[i] + post_top_decline.iloc[i] = min(decline * 10, 5.0) + return post_top_decline + + def calculate_resistance_distance(self, df: DataFrame) -> pd.Series: + """Calculate resistance distance.""" + logger.debug("Calculating resistance distance") + resistance_distance = pd.Series(0.0, index=df.index) + for i in range(20, len(df)): + if df['is_top'].iloc[i]: + price_high = df['high'].iloc[i] + resistance_levels = df['high'].iloc[i-20:i].rolling(5).max() + distance = (price_high - resistance_levels.min()) / price_high if resistance_levels.min() > 0 else 0 + resistance_distance.iloc[i] = min(distance * 10, 5.0) + return resistance_distance + + def detect_strokes(self, df: DataFrame) -> list: + """Detect strokes based on fractals.""" + logger.debug("Detecting strokes") + strokes = [] + last_fractal, last_price, last_index = None, None, None + + for i in range(len(df)): + if df['is_top'].iloc[i] or df['is_bottom'].iloc[i]: + current_fractal = 'top' if df['is_top'].iloc[i] else 'bottom' + current_price = df['high'].iloc[i] if current_fractal == 'top' else df['low'].iloc[i] + + if last_fractal is None: + last_fractal, last_price, last_index = current_fractal, current_price, df.index[i] + continue + + if not isinstance(current_price, (int, float)) or not isinstance(last_price, (int, float)): + continue + + price_change = abs(current_price - last_price) / last_price + if price_change < 0.005: + continue + + if (last_fractal == 'top' and current_fractal == 'bottom' and current_price < last_price) or \ + (last_fractal == 'bottom' and current_fractal == 'top' and current_price > last_price): + strokes.append({ + 'start_time': last_index, + 'end_time': df.index[i], + 'start_price': last_price, + 'end_price': current_price, + 'type': 'down' if current_fractal == 'bottom' else 'up' + }) + + last_fractal, last_price, last_index = current_fractal, current_price, df.index[i] + + logger.debug(f"Detected {len(strokes)} strokes") + return strokes + + def detect_pivots(self, strokes: list) -> list: + """Detect pivots based on strokes.""" + logger.debug("Detecting pivots") + pivots = [] + if len(strokes) < 3: + logger.debug("Insufficient strokes for pivot detection") + return pivots + + for i in range(2, len(strokes)): + high1, low1 = max(strokes[i-2]['start_price'], strokes[i-2]['end_price']), min(strokes[i-2]['start_price'], strokes[i-2]['end_price']) + high2, low2 = max(strokes[i-1]['start_price'], strokes[i-1]['end_price']), min(strokes[i-1]['start_price'], strokes[i-1]['end_price']) + high3, low3 = max(strokes[i]['start_price'], strokes[i]['end_price']), min(strokes[i]['start_price'], strokes[i]['end_price']) + if max(low1, low2, low3) < min(high1, high2, high3): + pivots.append({ + 'start_time': strokes[i-2]['start_time'], + 'end_time': strokes[i]['end_time'], + 'high': min(high1, high2, high3), + 'low': max(low1, low2, low3) + }) + logger.debug(f"Detected {len(pivots)} pivots") + return pivots + + def add_pivot_distance(self, df: DataFrame, pivots: list) -> DataFrame: + """Add pivot distance feature.""" + logger.debug("Adding pivot distance") + df = df.copy() + df['pivot_distance'] = 0.0 + + if not pivots: + logger.debug("No pivots detected, returning default pivot_distance") + return df + + for pivot in pivots: + try: + start_time = pivot['start_time'] + end_time = pivot['end_time'] + if start_time not in df.index or end_time not in df.index: + logger.debug(f"Invalid pivot times: {start_time} to {end_time}") + continue + mask = (df.index >= start_time) & (df.index <= end_time) + denominator = pivot['high'] - pivot['low'] + if denominator > 0: + df.loc[mask, 'pivot_distance'] = (df['close'] - pivot['low']) / denominator + else: + logger.debug(f"Zero denominator for pivot {pivot}") + except Exception as e: + logger.error(f"Error in pivot distance calculation: {e}") + continue + + df['pivot_distance'] = df['pivot_distance'].clip(-10, 10).fillna(0.0) + logger.debug("Completed pivot distance calculation") + return df + + def detect_back_divergence(self, df: DataFrame, strokes: list) -> DataFrame: + """Detect back divergence for buy/sell signals.""" + logger.debug("Detecting back divergence") + df = df.copy() + df['buy_signal'] = False + df['sell_signal'] = False + + if len(strokes) < 2: + logger.debug("Insufficient strokes for divergence detection") + return df + + for i in range(1, len(strokes)): + current_hist = self.safe_get_value(df, strokes[i]['end_time'], 'macd_hist_sum') + previous_hist = self.safe_get_value(df, strokes[i-1]['end_time'], 'macd_hist_sum') + + if current_hist is None or previous_hist is None: + continue + + if strokes[i]['type'] == strokes[i-1]['type'] == 'up': + if strokes[i]['end_price'] > strokes[i-1]['end_price'] and current_hist < previous_hist: + closest_time = df.index[df.index <= strokes[i]['end_time']] + if len(closest_time) > 0: + df.loc[closest_time[-1], 'sell_signal'] = True + + elif strokes[i]['type'] == strokes[i-1]['type'] == 'down': + if strokes[i]['end_price'] < strokes[i-1]['end_price'] and current_hist < previous_hist: + closest_time = df.index[df.index <= strokes[i]['end_time']] + if len(closest_time) > 0: + df.loc[closest_time[-1], 'buy_signal'] = True + + logger.debug("Completed back divergence detection") + return df + + def process_strokes_and_pivots(self, df: DataFrame, strokes: list) -> tuple: + """Process strokes and pivots for advanced features.""" + logger.debug("Processing strokes and pivots") + macd_hist_dynamic = pd.Series(0.0, index=df.index) + pivot_distance = pd.Series(0.0, index=df.index) + buy_signal = pd.Series(0, index=df.index) + sell_signal = pd.Series(0, index=df.index) + + if strokes: + try: + for stroke in strokes[1:]: + start_time, end_time = stroke['start_time'], stroke['end_time'] + if start_time not in df.index or end_time not in df.index: + logger.debug(f"Invalid stroke times: {start_time} to {end_time}") + continue + + window = self.calculate_window(df, start_time, end_time) + start_idx, end_idx = df.index.get_loc(start_time), df.index.get_loc(end_time) + 1 + hist_values = df['macd_hist'].iloc[start_idx:end_idx].rolling(window, min_periods=1).sum().fillna(0) + macd_hist_dynamic.iloc[start_idx:end_idx] = hist_values + logger.debug("Dynamic MACD calculated") + except Exception as e: + logger.warning(f"Dynamic MACD calculation error: {e}") + + try: + pivots = self.detect_pivots(strokes) + if pivots: + df_with_pivot = self.add_pivot_distance(df, pivots) + pivot_distance = df_with_pivot['pivot_distance'] + else: + logger.debug("No pivots detected") + except Exception as e: + logger.warning(f"Pivot detection error: {e}") + + try: + df_with_divergence = self.detect_back_divergence(df, strokes) + buy_signal = df_with_divergence['buy_signal'].astype(int) + sell_signal = df_with_divergence['sell_signal'].astype(int) + logger.debug("Back divergence signals calculated") + except Exception as e: + logger.warning(f"Back divergence detection error: {e}") + + logger.debug("Completed stroke and pivot processing") + return macd_hist_dynamic, pivot_distance, buy_signal, sell_signal + + def ensure_columns(self, dataframe: DataFrame) -> DataFrame: + """确保数据框中包含必要的列""" + # 不要尝试创建do_predict和&-s_close列,它们由FreqAI生成 + for col in ["enter_long", "enter_short", "exit_long", "exit_short"]: + if col not in dataframe.columns: + dataframe[col] = 0 + return dataframe + + def safe_get_value(self, df: DataFrame, timestamp, column: str): + """Safely get value from DataFrame.""" + try: + if timestamp in df.index: + return df.loc[timestamp, column] + closest_idx = df.index[df.index <= timestamp] + return df.loc[closest_idx[-1], column] if len(closest_idx) > 0 else None + except Exception as e: + logger.debug(f"Error getting value for {column} at {timestamp}: {e}") + return None + + def calculate_window(self, df: DataFrame, start_time, end_time) -> int: + """Calculate window size for dynamic features.""" + try: + if isinstance(start_time, pd.Timestamp) and isinstance(end_time, pd.Timestamp): + window = int((end_time - start_time).total_seconds() / 60) + else: + start_idx = df.index.get_loc(start_time) + end_idx = df.index.get_loc(end_time) + window = end_idx - start_idx + return max(window, 1) + except (TypeError, AttributeError, KeyError) as e: + logger.debug(f"Window calculation error: {e}") + return 1 \ No newline at end of file