Add config and strategies to the repo

This commit is contained in:
jackyu66git
2025-04-24 19:35:36 +08:00
parent 9cb242f706
commit 656484e0ca
23 changed files with 4378 additions and 0 deletions
+84
View File
@@ -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
}
}
+84
View File
@@ -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
}
}
+84
View File
@@ -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
}
}
+125
View File
@@ -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
}
}
+103
View File
@@ -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
}
}
+83
View File
@@ -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
}
}
+84
View File
@@ -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
}
}
+84
View File
@@ -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
}
}
+84
View File
@@ -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
}
}
+84
View File
@@ -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
}
}
+84
View File
@@ -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
}
}
+84
View File
@@ -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
}
}
+93
View File
@@ -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
}
}
+323
View File
@@ -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])
+207
View File
@@ -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])
+410
View File
@@ -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])
+560
View File
@@ -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
+274
View File
@@ -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
+196
View File
@@ -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])
+390
View File
@@ -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])
+196
View File
@@ -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])
+91
View File
@@ -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
+571
View File
@@ -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