Add config and strategies to the repo
This commit is contained in:
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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])
|
||||||
@@ -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])
|
||||||
@@ -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])
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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])
|
||||||
@@ -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])
|
||||||
@@ -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])
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user