added in MyLibs from private Mt5 repo
This commit is contained in:
@@ -0,0 +1 @@
|
||||
.8448951187990853
|
||||
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from time import sleep
|
||||
import pandas as pd
|
||||
from binance import Client
|
||||
from forex_python.converter import CurrencyRates
|
||||
import telegram
|
||||
import schedule
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(levelname)s - %(message)s",
|
||||
)
|
||||
|
||||
# Environment variables for sensitive information
|
||||
API_KEY = os.getenv("BINANCE_API_KEY")
|
||||
API_SECRET = os.getenv("BINANCE_API_SECRET")
|
||||
BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
|
||||
BOT_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID")
|
||||
|
||||
# Constants
|
||||
MAX_TRADES = 5
|
||||
RESET_STAKE_AMOUNT = 10 # minutes
|
||||
DATA_DIR = "data"
|
||||
|
||||
# Ensure data directory exists
|
||||
os.makedirs(DATA_DIR, exist_ok=True)
|
||||
|
||||
|
||||
def telegram_send_message(bot_token, bot_chat_id, message):
|
||||
"""
|
||||
Send a message via Telegram bot.
|
||||
"""
|
||||
try:
|
||||
bot = telegram.Bot(token=bot_token)
|
||||
bot.send_message(chat_id=bot_chat_id, text=message)
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to send Telegram message: {e}")
|
||||
|
||||
|
||||
def update_df(gbp, usd, btc, exchange_rate):
|
||||
"""
|
||||
Update the HDF5 file with new balance data.
|
||||
"""
|
||||
try:
|
||||
file_path = os.path.join(DATA_DIR, "balances.h5")
|
||||
time_now = pd.to_datetime(datetime.now().replace(microsecond=0))
|
||||
new_data = pd.DataFrame({
|
||||
"date_time": [time_now],
|
||||
"£": [gbp],
|
||||
"$": [usd],
|
||||
"BTC": [btc],
|
||||
"Ex-rate": [exchange_rate],
|
||||
})
|
||||
|
||||
with pd.HDFStore(file_path) as store:
|
||||
if "df" in store:
|
||||
df = store["df"]
|
||||
df = pd.concat([df, new_data]).drop_duplicates(subset="date_time", keep="first")
|
||||
else:
|
||||
df = new_data.set_index("date_time")
|
||||
store["df"] = df
|
||||
|
||||
logging.info("Updated DataFrame successfully.")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Could not update DataFrame: {e}")
|
||||
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, "Could not update DataFrame.")
|
||||
|
||||
|
||||
def trim_df():
|
||||
"""
|
||||
Trim the DataFrame to the last 24 hours.
|
||||
"""
|
||||
try:
|
||||
file_path = os.path.join(DATA_DIR, "balances.h5")
|
||||
cut_before = datetime.now() - timedelta(hours=25)
|
||||
|
||||
with pd.HDFStore(file_path) as store:
|
||||
if "df" in store:
|
||||
df = store["df"]
|
||||
df = df[df.index >= cut_before]
|
||||
store["df"] = df
|
||||
|
||||
logging.info("Trimmed DataFrame to the last 24 hours.")
|
||||
except Exception as e:
|
||||
logging.error(f"Could not trim DataFrame: {e}")
|
||||
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, "Could not trim DataFrame.")
|
||||
|
||||
|
||||
def update_exchange_rate():
|
||||
"""
|
||||
Fetch the latest USD to GBP exchange rate and store it.
|
||||
"""
|
||||
cr = CurrencyRates()
|
||||
file_path = os.path.join(DATA_DIR, "USDGBP_exchange_rate.txt")
|
||||
|
||||
try:
|
||||
exchange_rate = cr.get_rate("USD", "GBP")
|
||||
with open(file_path, "w") as f:
|
||||
f.write(str(exchange_rate))
|
||||
logging.info("Updated USD to GBP exchange rate.")
|
||||
except Exception as e:
|
||||
logging.error(f"Could not update exchange rate: {e}")
|
||||
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, "Could not update exchange rate.")
|
||||
|
||||
|
||||
def convert_usd_to_gbp(usd):
|
||||
"""
|
||||
Convert USD to GBP using the stored exchange rate.
|
||||
"""
|
||||
file_path = os.path.join(DATA_DIR, "USDGBP_exchange_rate.txt")
|
||||
|
||||
try:
|
||||
with open(file_path, "r") as f:
|
||||
exchange_rate = float(f.read())
|
||||
gbp = usd * exchange_rate
|
||||
return gbp, exchange_rate
|
||||
except Exception as e:
|
||||
logging.error(f"Could not convert USD to GBP: {e}")
|
||||
return usd, 1.0 # Fallback to 1:1 conversion
|
||||
|
||||
|
||||
def get_balance():
|
||||
"""
|
||||
Retrieve account balances from Binance and update the stake amount.
|
||||
"""
|
||||
try:
|
||||
client = Client(API_KEY, API_SECRET)
|
||||
account_info = client.get_account()
|
||||
balances = account_info["balances"]
|
||||
|
||||
usdt = 0.0
|
||||
for balance in balances:
|
||||
asset = balance["asset"]
|
||||
free = float(balance["free"])
|
||||
locked = float(balance["locked"])
|
||||
total = free + locked
|
||||
|
||||
if total > 0:
|
||||
if asset == "USDT":
|
||||
usdt += total
|
||||
else:
|
||||
try:
|
||||
price = float(client.get_symbol_ticker(symbol=f"{asset}USDT")["price"])
|
||||
usdt += total * price
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
btc_price = float(client.get_symbol_ticker(symbol="BTCUSDT")["price"])
|
||||
btc = usdt / btc_price
|
||||
gbp, exchange_rate = convert_usd_to_gbp(usdt)
|
||||
stake_amount = round(usdt / MAX_TRADES)
|
||||
|
||||
with open(os.path.join(DATA_DIR, "stake_amount.txt"), "w") as f:
|
||||
f.write(str(stake_amount))
|
||||
|
||||
update_df(gbp, usdt, btc, exchange_rate)
|
||||
except Exception as e:
|
||||
logging.error(f"Could not fetch balances: {e}")
|
||||
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, "Could not fetch Binance balances.")
|
||||
|
||||
|
||||
def run_code():
|
||||
"""
|
||||
Main function to schedule tasks and run the bot.
|
||||
"""
|
||||
update_exchange_rate()
|
||||
get_balance()
|
||||
schedule.every(RESET_STAKE_AMOUNT).minutes.do(get_balance)
|
||||
schedule.every().day.at("11:45").do(update_exchange_rate)
|
||||
schedule.every(4).hours.do(trim_df)
|
||||
|
||||
while True:
|
||||
schedule.run_pending()
|
||||
sleep(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_code()
|
||||
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import time
|
||||
import shutil
|
||||
import warnings
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt # pip install matplotlib
|
||||
import matplotlib.dates as mdates
|
||||
from datetime import datetime, timedelta, date
|
||||
import telegram
|
||||
import schedule # pip install schedule
|
||||
|
||||
warnings.simplefilter(action='ignore', category=FutureWarning)
|
||||
|
||||
# Constants
|
||||
LONG_PLOT_DAYS = -1 # 60 # -1 all days in df
|
||||
LONG_PLOT_CURRENCY = '£'
|
||||
PLOT_CURRENCY = '$'
|
||||
|
||||
# Environment variables for sensitive information
|
||||
API_KEY = os.getenv("BINANCE_API_KEY")
|
||||
API_SECRET = os.getenv("BINANCE_API_SECRET")
|
||||
BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
|
||||
BOT_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID")
|
||||
|
||||
|
||||
date_today = date.today()
|
||||
TODAY = date_today.strftime("%Y_%m_%d")
|
||||
|
||||
|
||||
def telegram_send_image(bot_token, bot_chat_id, image_path):
|
||||
"""Send an image to the specified Telegram chat."""
|
||||
bot = telegram.Bot(token=bot_token)
|
||||
with open(image_path, 'rb') as photo:
|
||||
bot.send_photo(chat_id=bot_chat_id, photo=photo)
|
||||
return ()
|
||||
|
||||
|
||||
def telegram_send_message(bot_token, bot_chat_id, message):
|
||||
"""Send a message to the specified Telegram chat."""
|
||||
bot = telegram.Bot(token=bot_token)
|
||||
bot.send_message(chat_id=bot_chat_id, text=message)
|
||||
return ()
|
||||
|
||||
|
||||
def plot_and_send_image(df, currency, plot_title, file_name):
|
||||
"""Generate plot and send it as an image to Telegram."""
|
||||
plt.rcParams.update({'font.size': 14, 'font.family': 'STIXGeneral', 'mathtext.fontset': 'stix'})
|
||||
fig, axs = plt.subplots(figsize=(7, 4))
|
||||
axs.xaxis.set_major_formatter(mdates.DateFormatter("%d %b"))
|
||||
df[currency].plot.line(ax=axs, color="darkgreen", linewidth=1.50)
|
||||
delta_y = int(df[currency].max()) - int(df[currency].min())
|
||||
y_min = int(df[currency].min()) - (delta_y * 0.05)
|
||||
y_max = int(df[currency].max()) + (delta_y * 0.05)
|
||||
x_max = datetime.now()
|
||||
x_min = datetime.now() - timedelta(days=len(df))
|
||||
axs.set_title(plot_title)
|
||||
axs.set_ylim(y_min, y_max)
|
||||
axs.set_xlim(x_min, x_max)
|
||||
axs.set_ylabel("")
|
||||
axs.set_xlabel("")
|
||||
axs.grid(color='grey', alpha=0.5, linestyle='dashed', linewidth=0.5)
|
||||
axs.yaxis.set_major_formatter(f"{currency} {{x:1.0f}}")
|
||||
|
||||
plt.savefig(file_name)
|
||||
plt.cla()
|
||||
plt.close(fig)
|
||||
|
||||
# Send image to Telegram
|
||||
telegram_send_image(BOT_TOKEN, BOT_CHAT_ID, file_name)
|
||||
return ()
|
||||
|
||||
|
||||
def plot_long(period=LONG_PLOT_DAYS, currency=LONG_PLOT_CURRENCY):
|
||||
"""Plot long-term data."""
|
||||
try:
|
||||
if os.path.exists('balances_24h.h5'):
|
||||
balances_24h = pd.HDFStore('balances_24h.h5')
|
||||
df = balances_24h['df_24h'].iloc[1:, :]
|
||||
balances_24h.close()
|
||||
if period == -1:
|
||||
no_of_days = len(df)
|
||||
else:
|
||||
no_of_days = period
|
||||
df = df.tail(no_of_days)
|
||||
plot_and_send_image(df, currency, f"{no_of_days} days plot", "plot_long.png")
|
||||
else:
|
||||
message = "No balances_24h.h5 file"
|
||||
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
|
||||
except Exception:
|
||||
message = "Could not generate long plot"
|
||||
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
|
||||
return ()
|
||||
|
||||
|
||||
def plot_30days(currency=PLOT_CURRENCY):
|
||||
"""Plot 30 days data."""
|
||||
try:
|
||||
if os.path.exists('balances_4h.h5'):
|
||||
balances_4h = pd.HDFStore('balances_4h.h5')
|
||||
df = balances_4h['df_4h'].iloc[1:, :]
|
||||
balances_4h.close()
|
||||
df = df.tail(180)
|
||||
plot_and_send_image(df, currency, "30 Day Balances", "30_days.png")
|
||||
else:
|
||||
message = "No balances_4h.h5 file"
|
||||
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
|
||||
except Exception:
|
||||
message = "Could not generate 30 day plot"
|
||||
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
|
||||
return ()
|
||||
|
||||
|
||||
def plot_7days(currency=PLOT_CURRENCY):
|
||||
"""Plot 7 days data."""
|
||||
try:
|
||||
if os.path.exists('balances_1h.h5'):
|
||||
balances_1h = pd.HDFStore('balances_1h.h5')
|
||||
df = balances_1h['df_1h']
|
||||
end_date = datetime.now().replace(microsecond=0)
|
||||
cut_before_date = end_date - timedelta(days=7)
|
||||
df = df.loc[df.index >= cut_before_date]
|
||||
plot_and_send_image(df, currency, "7 Day Balances", "7_days.png")
|
||||
else:
|
||||
message = "No balances_1h.h5 file"
|
||||
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
|
||||
except Exception:
|
||||
message = "Could not generate 7 day plot"
|
||||
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
|
||||
return ()
|
||||
|
||||
|
||||
def plot_24h(currency=PLOT_CURRENCY):
|
||||
"""Plot 24 hours data."""
|
||||
try:
|
||||
if os.path.exists('balances.h5'):
|
||||
balances = pd.HDFStore('balances.h5')
|
||||
df = balances['df'].iloc[1:, :]
|
||||
df['date_time'] = pd.to_datetime(df['date_time'])
|
||||
balances.close()
|
||||
df = df.set_index('date_time')
|
||||
plot_and_send_image(df, currency, TODAY, "24_hour.png")
|
||||
else:
|
||||
message = "No balances.h5 file"
|
||||
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
|
||||
except Exception:
|
||||
message = "Could not generate 24h plot"
|
||||
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
|
||||
return ()
|
||||
|
||||
|
||||
def send_plots():
|
||||
"""Send all generated plots to Telegram."""
|
||||
try:
|
||||
balances = pd.HDFStore('balances.h5')
|
||||
df = balances['df']
|
||||
balances.close()
|
||||
GBP = df['£'].iloc[-1]
|
||||
USDT = df['$'].iloc[-1]
|
||||
BTC = df['BTC'].iloc[-1]
|
||||
|
||||
message = f"Balance:\n GBP £ {round(GBP, 2)}\n USD $ {round(USDT, 2)}\n BTC {round(BTC, 6)}"
|
||||
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
|
||||
|
||||
# Send all plots
|
||||
for file_name in ["plot_long.png", "30_days.png", "7_days.png", "24_hour.png"]:
|
||||
if os.path.exists(file_name):
|
||||
telegram_send_image(BOT_TOKEN, BOT_CHAT_ID, file_name)
|
||||
else:
|
||||
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, f"Could not find {file_name}")
|
||||
|
||||
print(f"{datetime.now().replace(microsecond=0)} - Sent plots to Telegram.")
|
||||
except Exception:
|
||||
message = "Could not send plots."
|
||||
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
|
||||
return ()
|
||||
|
||||
|
||||
def resample_data():
|
||||
"""Resample data to 1h, 4h, and 24h."""
|
||||
try:
|
||||
if not os.path.exists("balances_1h.h5"):
|
||||
balances = pd.HDFStore('balances.h5')
|
||||
df = balances['df']
|
||||
df['date_time'] = pd.to_datetime(df['date_time'])
|
||||
df = df.set_index('date_time')
|
||||
balances.close()
|
||||
|
||||
# Resample to 1h
|
||||
data_1h = df.resample("H").mean()
|
||||
balances_1h = pd.HDFStore('balances_1h.h5')
|
||||
balances_1h['df_1h'] = data_1h
|
||||
balances_1h.close()
|
||||
|
||||
if not os.path.exists("balances_4h.h5"):
|
||||
balances_1h = pd.HDFStore('balances_1h.h5')
|
||||
df_1h = balances_1h['df_1h']
|
||||
balances_1h.close()
|
||||
|
||||
# Resample to 4h
|
||||
data_4h = df_1h.resample("4H").mean()
|
||||
balances_4h = pd.HDFStore('balances_4h.h5')
|
||||
balances_4h['df_4h'] = data_4h
|
||||
balances_4h.close()
|
||||
|
||||
if not os.path.exists("balances_24h.h5"):
|
||||
balances_4h = pd.HDFStore('balances_4h.h5')
|
||||
df_4h = balances_4h['df_4h']
|
||||
balances_4h.close()
|
||||
|
||||
# Resample to 24h
|
||||
data_24h = df_4h.resample("24H").mean()
|
||||
balances_24h = pd.HDFStore('balances_24h.h5')
|
||||
balances_24h['df_24h'] = data_24h
|
||||
balances_24h.close()
|
||||
|
||||
except Exception:
|
||||
message = "Error in resampling data"
|
||||
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
|
||||
return ()
|
||||
|
||||
|
||||
def delete_old_files():
|
||||
"""Delete old files in the directory."""
|
||||
try:
|
||||
directory = "path_to_your_directory"
|
||||
for file_name in os.listdir(directory):
|
||||
file_path = os.path.join(directory, file_name)
|
||||
if os.path.getmtime(file_path) < time.time() - 7 * 86400:
|
||||
os.remove(file_path)
|
||||
except Exception:
|
||||
message = "Error in deleting old files"
|
||||
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
|
||||
return ()
|
||||
|
||||
|
||||
def archive_data():
|
||||
"""Archive old data to a zip file."""
|
||||
try:
|
||||
archive_name = f"archive_data_{TODAY}.zip"
|
||||
if not os.path.exists('archive_data'):
|
||||
os.makedirs('archive_data')
|
||||
shutil.make_archive(f'archive_data/{archive_name}', 'zip', 'path_to_your_directory')
|
||||
except Exception:
|
||||
message = "Error in archiving data"
|
||||
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
|
||||
return ()
|
||||
|
||||
|
||||
# Scheduling tasks
|
||||
schedule.every().day.at("00:00").do(archive_data)
|
||||
schedule.every().day.at("01:00").do(delete_old_files)
|
||||
schedule.every().day.at("02:00").do(resample_data)
|
||||
schedule.every().day.at("02:30").do(plot_long)
|
||||
schedule.every().day.at("03:00").do(plot_30days)
|
||||
schedule.every().day.at("03:30").do(plot_7days)
|
||||
schedule.every().day.at("04:00").do(plot_24h)
|
||||
schedule.every().day.at("05:00").do(send_plots)
|
||||
|
||||
# Main loop
|
||||
while True:
|
||||
schedule.run_pending()
|
||||
time.sleep(1)
|
||||
@@ -0,0 +1 @@
|
||||
4164
|
||||
@@ -0,0 +1,699 @@
|
||||
"""
|
||||
MattC - 2025
|
||||
This code is a working prototype and is intended for initial testing and development purposes. Some Python
|
||||
standards, including but not limited to PEP 8 compliance, error handling, and code optimization, are yet to be fully
|
||||
implemented. Further refactoring and enhancements are planned to improve readability, maintainability,
|
||||
and efficiency.
|
||||
"""
|
||||
|
||||
import os
|
||||
from os import walk
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
import pandas as pd
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
import logging
|
||||
import matplotlib.pyplot as plt # pip install matplotlib
|
||||
import matplotlib.dates as mdates
|
||||
import warnings
|
||||
import urllib
|
||||
from urllib.request import urlopen
|
||||
import time
|
||||
|
||||
warnings.simplefilter(action='ignore', category=FutureWarning)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# TODO sort out download data
|
||||
class WalkForward(object):
|
||||
|
||||
def __init__(self, strategy_path, strategy, config, output_dir, wf_start, wf_finish, anchored_start, min_trades,
|
||||
in_sample_days, out_sample_days, loss_function, cpu, epochs, wallet="2500", fee="0.002",
|
||||
pre_live=False, re_opt=False, re_opt_t="5m 1h 1d"):
|
||||
|
||||
self.in_sample_days = in_sample_days
|
||||
self.out_sample_days = out_sample_days
|
||||
self.loss_function = loss_function
|
||||
self.re_opt = re_opt
|
||||
self.output_dir = self.create_run_dir(output_dir)
|
||||
self.config = self.copy_input_config(config)
|
||||
self.strategy_path = strategy_path
|
||||
self.strategy_name = strategy
|
||||
self.strategy = self.copy_input_strategy(strategy_path, self.strategy_name)
|
||||
self.wf_start = wf_start
|
||||
self.wf_finish = wf_finish
|
||||
self.anchored_start = anchored_start
|
||||
self.is_start = self.in_sample_start()
|
||||
self.min_trades = min_trades
|
||||
self.cpu = cpu
|
||||
self.epochs = epochs
|
||||
self.wallet = wallet
|
||||
self.fee = fee
|
||||
self.pre_live = pre_live
|
||||
self.re_opt_t = re_opt_t
|
||||
self.start_log()
|
||||
|
||||
def run_walk_forward(self):
|
||||
|
||||
stages = self.generate_wf_stages()
|
||||
no_stages = len(stages)
|
||||
start_wallet = self.wallet
|
||||
|
||||
start_time = datetime.now()
|
||||
logger.info(f'Running Walk-forward for {no_stages} stages')
|
||||
|
||||
for count, value in enumerate(stages, 1):
|
||||
stage_start_time = datetime.now()
|
||||
logger.info(f'{"-" * 79}')
|
||||
hyperopt_time = value[0]
|
||||
backtest_time = value[1]
|
||||
full_time_period = f"{hyperopt_time.split('-')[0]}-{backtest_time.split('-')[1]}"
|
||||
logger.info(f'Walk-forward optimization for stage {count} of {no_stages}')
|
||||
stage_dir = self.create_stage_dir(self.output_dir, count, full_time_period)
|
||||
|
||||
# Run Hyperopt and process required data
|
||||
logger.info(f'Hyperopting for {hyperopt_time}')
|
||||
cpu = self.set_cpu(hyperopt_time)
|
||||
self.run_hyperopt(hyperopt_time, stage_dir, self.epochs, cpu)
|
||||
hy_start = datetime.strptime(hyperopt_time.split('-')[0], "%Y%m%d")
|
||||
hy_finish = datetime.strptime(hyperopt_time.split('-')[1], "%Y%m%d")
|
||||
hy_delta = hy_start - hy_finish
|
||||
hy_days = int(hy_delta.days)
|
||||
logger.info(f'Running Hyperopt Backtest for period {hyperopt_time} ({hy_days} days)')
|
||||
result_op, bt_file_op = self.run_backtest(hyperopt_time, "op_bt", start_wallet, stage_dir)
|
||||
self.save_bt_file_data(stage_dir, bt_file_op, "op_bt")
|
||||
df_op = self.update_df(result_op, "op_bt", count, hyperopt_time, start_wallet)
|
||||
|
||||
# Walk forward testing constant starting wallet:
|
||||
logger.info(f'Running WF Backtest for period {backtest_time} ({self.out_sample_days} days)')
|
||||
result_bt, bt_file_bt = self.run_backtest(backtest_time, "wf_bt", start_wallet, stage_dir)
|
||||
self.save_bt_file_data(stage_dir, bt_file_bt, "wf_bt")
|
||||
df_wf = self.update_df(result_bt, "wf_bt", count, backtest_time, start_wallet)
|
||||
plot_equity_curve(df_wf, self.output_dir, save_fig=True)
|
||||
|
||||
self.combine_data(hyperopt_time, df_op, df_wf)
|
||||
stage_run_time = str(datetime.now() - stage_start_time)
|
||||
logger.info(f'Stage {count} walk-forward analysis Duration: {stage_run_time.split(".")[0]}')
|
||||
|
||||
if self.pre_live:
|
||||
self.pre_live_optimise()
|
||||
|
||||
if self.re_opt:
|
||||
self.re_optimise()
|
||||
|
||||
run_time = str(datetime.now() - start_time)
|
||||
logger.info(f'Total walk-forward analysis Duration: {run_time.split(".")[0]}')
|
||||
|
||||
def re_optimise(self):
|
||||
# Download data
|
||||
# time_periods = "5m 15m 1h 4h 12h 1d"
|
||||
time_periods = self.re_opt_t
|
||||
self.download_data(time_periods)
|
||||
|
||||
end_date = datetime.strptime(self.wf_finish, "%Y%m%d")
|
||||
start_date = end_date - timedelta(int(self.in_sample_days))
|
||||
t1 = start_date.strftime("%Y%m%d")
|
||||
t2 = end_date.strftime("%Y%m%d")
|
||||
hyperopt_time = t1 + "-" + t2
|
||||
full_time_period = f"{hyperopt_time}"
|
||||
|
||||
stage_dir = self.create_stage_dir(self.output_dir, "re_optimise", full_time_period)
|
||||
epochs = f"{int(self.epochs)}"
|
||||
self.run_hyperopt(hyperopt_time, stage_dir, epochs, self.cpu)
|
||||
logger.info(f'Running Hyperopt Backtest for period {hyperopt_time} ({self.in_sample_days} days)')
|
||||
_ = self.run_backtest(hyperopt_time, "re_optimise", self.wallet, stage_dir)
|
||||
|
||||
def save_bt_file_data(self, stage_dir, bt_file_op, file_id):
|
||||
|
||||
with open(f'{stage_dir}/{bt_file_op}') as f:
|
||||
data1 = json.load(f)
|
||||
df1 = pd.DataFrame.from_dict(data1["strategy"][self.strategy_name]["results_per_pair"])
|
||||
csv_filepath = f"{stage_dir}/{file_id}_bt_results.csv"
|
||||
df1.to_csv(csv_filepath)
|
||||
|
||||
def update_df(self, result, file_id, wf_stage, time_range, start_wallet):
|
||||
|
||||
h5_string = f"{self.output_dir}/{file_id}.h5"
|
||||
if not os.path.exists(h5_string):
|
||||
cols = ["profit_mean", "profit_mean_pct", "profit_sum", "profit_sum_pct", "profit_total_abs",
|
||||
"profit_total_pct" "profit_total", "wins", "draws", "losses", "wf-stage", "bt_time_period",
|
||||
"start-balance", "final-balance", "%_profit_pa", "acc-start", "acc-finish"]
|
||||
df = pd.DataFrame(columns=cols)
|
||||
df.to_hdf(h5_string, 'data')
|
||||
|
||||
df = pd.read_hdf(h5_string, 'data')
|
||||
|
||||
if file_id == "op_bt":
|
||||
is_start = datetime.strptime(time_range.split('-')[0], "%Y%m%d")
|
||||
is_finish = datetime.strptime(time_range.split('-')[1], "%Y%m%d")
|
||||
delta = is_start - is_finish
|
||||
sample_days = int(delta.days)
|
||||
else:
|
||||
sample_days = self.out_sample_days
|
||||
|
||||
data = result[0]
|
||||
data.pop('key')
|
||||
data["%_profit_pa"] = data["profit_total_pct"] / float(sample_days) * 365 # percent profit year
|
||||
data["wf-stage"] = wf_stage
|
||||
data["start-balance"] = start_wallet
|
||||
data["final-balance"] = float(start_wallet) + float(data["profit_total_abs"])
|
||||
data["bt_time_period"] = time_range
|
||||
|
||||
if len(df) == 0:
|
||||
data["acc-start"] = float(self.wallet)
|
||||
else:
|
||||
data["acc-start"] = df["acc-finish"].iloc[-1]
|
||||
|
||||
data["acc-finish"] = (data["acc-start"] * data["profit_total_pct"] / 100) + data["acc-start"]
|
||||
df2 = pd.DataFrame(data, index=[0])
|
||||
df_new = df.copy()
|
||||
df_new = df_new.append([df2], ignore_index=True)
|
||||
|
||||
csv_filepath = f"{self.output_dir}/{file_id}.csv"
|
||||
df_new.to_csv(csv_filepath)
|
||||
df_new.to_hdf(h5_string, 'data')
|
||||
|
||||
return df_new
|
||||
|
||||
def pre_live_optimise(self):
|
||||
|
||||
end_date = datetime.strptime(self.wf_finish, "%Y%m%d")
|
||||
start_date = end_date - timedelta(int(self.in_sample_days))
|
||||
t1 = start_date.strftime("%Y%m%d")
|
||||
t2 = end_date.strftime("%Y%m%d")
|
||||
hyperopt_time = t1 + "-" + t2
|
||||
full_time_period = f"{hyperopt_time}"
|
||||
|
||||
stage_dir = self.create_stage_dir(self.output_dir, "pre_live", full_time_period)
|
||||
epochs = f"{int(self.epochs) * 2}"
|
||||
self.run_hyperopt(hyperopt_time, stage_dir, epochs, self.cpu)
|
||||
logger.info(f'Running Hyperopt Backtest for period {hyperopt_time} ({self.in_sample_days} days)')
|
||||
result_op = self.run_backtest(hyperopt_time, "pre_live", self.wallet, stage_dir)
|
||||
|
||||
def combine_data(self, hyp_time_range, df_op, df_wf):
|
||||
|
||||
is_start = datetime.strptime(hyp_time_range.split('-')[0], "%Y%m%d")
|
||||
is_finish = datetime.strptime(hyp_time_range.split('-')[1], "%Y%m%d")
|
||||
delta = is_start - is_finish
|
||||
days_in = int(delta.days)
|
||||
|
||||
days_out = int(self.out_sample_days)
|
||||
|
||||
try:
|
||||
a = pd.Series(df_op["profit_mean_pct"], name='op_profit_av')
|
||||
b = pd.Series(df_wf["profit_mean_pct"], name='wf_profit_av')
|
||||
c = pd.Series(df_op["wins"] / df_op["losses"], name='op_wl%')
|
||||
d = pd.Series(df_wf["wins"] / df_wf["losses"], name='wf_wl%')
|
||||
e = pd.Series(df_op['trades'].apply(lambda x: x / days_in), name='op_trades_per_day')
|
||||
f = pd.Series(df_wf['trades'].apply(lambda x: x / days_out), name='wf_trades_per_day')
|
||||
g = pd.Series(df_op["profit_total"].apply(lambda x: (x / days_in) * 365), name='op_ppa')
|
||||
h = pd.Series(df_wf["profit_total"].apply(lambda x: (x / days_out) * 365), name='wf_ppa')
|
||||
i = pd.Series(df_op["%_profit_pa"], name='op_%ppa')
|
||||
j = pd.Series(df_wf["%_profit_pa"], name='wf_%ppa')
|
||||
|
||||
df_combined = pd.concat([a, b, c, d, e, f, g, h, i, j], axis=1)
|
||||
filepath = f"{self.output_dir}/combined.csv"
|
||||
df_combined.to_csv(filepath)
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
return
|
||||
|
||||
def generate_wf_stages(self):
|
||||
|
||||
is_start = datetime.strptime(self.is_start, "%Y%m%d")
|
||||
oos_start = datetime.strptime(self.wf_start, "%Y%m%d")
|
||||
end_date = datetime.strptime(self.wf_finish, "%Y%m%d")
|
||||
oos_end = oos_start + timedelta(int(self.out_sample_days))
|
||||
stages = []
|
||||
while True:
|
||||
|
||||
t1 = is_start.strftime("%Y%m%d")
|
||||
t2 = oos_start.strftime("%Y%m%d")
|
||||
t3 = oos_end.strftime("%Y%m%d")
|
||||
|
||||
# define stage:
|
||||
insample_timframe = t1 + "-" + t2
|
||||
outsample_timframe = t2 + "-" + t3
|
||||
stage = [insample_timframe, outsample_timframe]
|
||||
stages.append(stage)
|
||||
|
||||
oos_start = oos_start + timedelta(int(self.out_sample_days))
|
||||
oos_end = oos_start + timedelta(int(self.out_sample_days))
|
||||
|
||||
if not self.in_sample_days == "anchored":
|
||||
is_start = is_start + timedelta(int(self.out_sample_days))
|
||||
|
||||
if oos_end > end_date:
|
||||
break
|
||||
|
||||
return stages
|
||||
|
||||
def run_hyperopt(self, time_range, stage_dir, epochs, cpu):
|
||||
"""
|
||||
:param stage_number:
|
||||
:param time_range:
|
||||
:param epochs:
|
||||
:param loss_function: SortinoHyperOptLoss,
|
||||
:param fee:
|
||||
:param cpu:
|
||||
:return:
|
||||
"""
|
||||
self.wait_for_internet_connection()
|
||||
start_time = datetime.now()
|
||||
os.system(
|
||||
"freqtrade hyperopt" +
|
||||
" --min-trades " + self.min_trades +
|
||||
" -j " + cpu +
|
||||
" -e " + epochs +
|
||||
" --spaces buy " +
|
||||
" --fee " + self.fee +
|
||||
" --logfile " + stage_dir + "/op_log" +
|
||||
" --timerange " + time_range +
|
||||
" --hyperopt-loss " + self.loss_function +
|
||||
" --strategy " + self.strategy +
|
||||
" --strategy-path " + self.output_dir +
|
||||
" --config " + self.config +
|
||||
" --dry-run-wallet " + self.wallet
|
||||
)
|
||||
|
||||
self.wait_for_internet_connection()
|
||||
os.system(
|
||||
"freqtrade hyperopt-list" +
|
||||
" --no-details " +
|
||||
" --export-csv " + stage_dir + "/op.csv"
|
||||
)
|
||||
|
||||
# # Copy the best optimisation results to "stage output directory":
|
||||
src = Path(f"{self.output_dir}/{self.strategy}.json")
|
||||
dst = f"{stage_dir}/op_result.json"
|
||||
shutil.copyfile(str(src), dst)
|
||||
|
||||
run_time = str(datetime.now() - start_time)
|
||||
logger.info(f'Hyperopt Duration: {run_time.split(".")[0]}')
|
||||
|
||||
with open(dst) as f:
|
||||
data = json.load(f)
|
||||
|
||||
results = data["params"]["buy"]
|
||||
for i in results:
|
||||
logger.info(f'Hyperopt result: {i}: {results[i]}')
|
||||
|
||||
def run_backtest(self, time_range, file_id, wallet, stage_dir):
|
||||
self.wait_for_internet_connection()
|
||||
start_time = datetime.now()
|
||||
os.system(
|
||||
"freqtrade backtesting" +
|
||||
" --export trades " +
|
||||
" --fee " + self.fee +
|
||||
f" --logfile {stage_dir}/{file_id}_log.txt"
|
||||
" --timerange " + time_range +
|
||||
" --strategy " + self.strategy +
|
||||
" --strategy-path " + self.output_dir +
|
||||
" --config " + self.config +
|
||||
" --dry-run-wallet " + wallet +
|
||||
f" --export-filename {stage_dir}/{file_id}_result.json"
|
||||
)
|
||||
|
||||
result, bt_file = self.get_backtest_data(stage_dir, file_id)
|
||||
run_time = str(datetime.now() - start_time)
|
||||
if not file_id == "wf_acc_bt":
|
||||
logger.info(f'Backtest Duration: {run_time.split(".")[0]}')
|
||||
|
||||
self.log_bt_results(time_range, result, wallet, file_id)
|
||||
self.plot_bt_profit(time_range, stage_dir, bt_file, file_id)
|
||||
|
||||
return result, bt_file
|
||||
|
||||
def download_data(self, time_periods, days="4000"):
|
||||
self.wait_for_internet_connection()
|
||||
logger.info(f'downloading data')
|
||||
os.system(
|
||||
"freqtrade download-data" +
|
||||
" -t " + time_periods +
|
||||
" --exchange binance " +
|
||||
" --pairs .*/USDT " +
|
||||
" --new-pairs-days " + days +
|
||||
" --include-inactive-pairs "
|
||||
)
|
||||
logger.info(f'finished downloading data')
|
||||
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def wait_for_internet_connection():
|
||||
start_time = datetime.now()
|
||||
switch = True
|
||||
while True:
|
||||
try:
|
||||
urlopen('https://www.google.com', timeout=1)
|
||||
if not switch:
|
||||
offline_time = str(datetime.now() - start_time)
|
||||
logger.warning(f"Disconnected time: {offline_time.split('.')[0]}")
|
||||
logger.warning("#############################")
|
||||
return
|
||||
|
||||
except urllib.error.URLError:
|
||||
|
||||
if switch:
|
||||
logger.warning("#############################")
|
||||
logger.warning("NO INTERNET")
|
||||
|
||||
switch = False
|
||||
time.sleep(2)
|
||||
|
||||
pass
|
||||
|
||||
def log_bt_results(self, time_range, result, wallet, file_id):
|
||||
|
||||
data = result[0].copy()
|
||||
|
||||
final_balance = float(wallet) + data["profit_total_abs"]
|
||||
w_start = round(float(wallet), 2)
|
||||
w_finish = round(final_balance, 2)
|
||||
percent_prof = round(data["profit_total_pct"], 1)
|
||||
|
||||
if file_id == "op_bt":
|
||||
|
||||
is_start = datetime.strptime(time_range.split('-')[0], "%Y%m%d")
|
||||
is_finish = datetime.strptime(time_range.split('-')[1], "%Y%m%d")
|
||||
delta = is_start - is_finish
|
||||
sample_days = int(delta.days)
|
||||
|
||||
else:
|
||||
sample_days = self.out_sample_days
|
||||
|
||||
pppa = round((percent_prof / float(sample_days) * 365), 2) # percent profit per year
|
||||
logger.info(f'Backtest result - Balance £{w_start} --> £{w_finish} ({percent_prof}%): {pppa} %profit pa')
|
||||
|
||||
# Trade stats
|
||||
win = data['wins']
|
||||
loss = data['losses']
|
||||
draw = data['draws']
|
||||
trades = data['trades']
|
||||
logger.info(f"Backtest result - Wins: {win}, Draws: {draw}, Losses: {loss}, trades: {trades}")
|
||||
|
||||
# Average tade profits:
|
||||
mean_p = round(float(data['profit_mean']), 2)
|
||||
mp_percent = round(float(data['profit_mean_pct']), 2)
|
||||
logger.info(f"Backtest result - mean trade profit £{mean_p}, {mp_percent}%")
|
||||
|
||||
# Account draw-down:
|
||||
dd_percent = round((data['max_drawdown_account'] * 100), 2)
|
||||
dd_abs = round(float(data['max_drawdown_abs']), 2)
|
||||
logger.info(f"Backtest result - Max dd: {dd_percent}%, £{dd_abs}")
|
||||
|
||||
def plot_bt_profit(self, time_range, stage_dir, bt_file, file_id):
|
||||
os.system(
|
||||
"freqtrade plot-profit "
|
||||
" --timeframe 1d "
|
||||
" --timerange " + time_range +
|
||||
" --strategy " + self.strategy +
|
||||
" --strategy-path " + self.output_dir +
|
||||
" --config " + self.config +
|
||||
f" --export-filename {stage_dir}/{bt_file}"
|
||||
)
|
||||
|
||||
# TODO relative path required:
|
||||
src = Path(f"/home/matt/freqtrade/user_data/plot/freqtrade-profit-plot.html")
|
||||
dst = f"{stage_dir}/{file_id}_profit-plot.html"
|
||||
shutil.copyfile(str(src), dst)
|
||||
|
||||
@staticmethod
|
||||
def get_backtest_data(stage_dir, file_id):
|
||||
f = []
|
||||
for (dirpath, dirnames, filenames) in walk(stage_dir):
|
||||
f.extend(filenames)
|
||||
break
|
||||
|
||||
# Find the backtest file for wf stage
|
||||
for file in f:
|
||||
|
||||
# not meta.json:
|
||||
if file[-9:-5] != "meta":
|
||||
|
||||
my_file = file_id + "_result"
|
||||
if file.split("-")[0] == my_file:
|
||||
# change to while open:
|
||||
f = open(stage_dir + "/" + file)
|
||||
data = json.load(f)
|
||||
result = data["strategy_comparison"]
|
||||
backtest_file = file
|
||||
|
||||
return result, backtest_file
|
||||
|
||||
def create_run_dir(self, path):
|
||||
|
||||
_time = datetime.now().strftime('%Y%m%d_%I:%M%p')
|
||||
if self.re_opt:
|
||||
directory = f"{path}/{_time}_{self.loss_function}_re-optimise_{self.in_sample_days}"
|
||||
else:
|
||||
directory = f"{path}/{_time}_{self.loss_function}_in_{self.in_sample_days}_out_{self.out_sample_days}"
|
||||
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
|
||||
try:
|
||||
os.makedirs(directory)
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
return directory
|
||||
|
||||
@staticmethod
|
||||
def create_stage_dir(path, stage, full_time_period):
|
||||
dir_string = f"{path}/stage_{stage}_{full_time_period}"
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
|
||||
try:
|
||||
os.makedirs(dir_string)
|
||||
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
return dir_string
|
||||
|
||||
def copy_input_config(self, in_config):
|
||||
# Copy config:
|
||||
config = f"{in_config}"
|
||||
dst_config = f"{self.output_dir}/{config.split('/')[-1]}"
|
||||
shutil.copyfile(config, dst_config)
|
||||
return dst_config
|
||||
|
||||
def copy_input_strategy(self, in_strategy_path, strategy):
|
||||
# Copy Strategy
|
||||
src = f"{in_strategy_path}/{strategy}.py"
|
||||
dst_strategy = f"{self.output_dir}/{strategy}.py"
|
||||
shutil.copyfile(src, dst_strategy)
|
||||
return strategy
|
||||
|
||||
def in_sample_start(self):
|
||||
|
||||
oos_start = datetime.strptime(self.wf_start, "%Y%m%d")
|
||||
|
||||
if self.in_sample_days == "anchored":
|
||||
is_start = datetime.strptime(self.anchored_start, "%Y%m%d") # + oos days
|
||||
|
||||
else:
|
||||
is_start = oos_start - timedelta(int(self.in_sample_days))
|
||||
|
||||
is_start = is_start.strftime("%Y%m%d")
|
||||
|
||||
return is_start
|
||||
|
||||
def set_cpu(self, hyperopt_time):
|
||||
|
||||
is_start = datetime.strptime(hyperopt_time.split('-')[0], "%Y%m%d")
|
||||
is_finish = datetime.strptime(hyperopt_time.split('-')[1], "%Y%m%d")
|
||||
delta = is_finish - is_start
|
||||
days = int(delta.days)
|
||||
|
||||
if self.in_sample_days == "anchored":
|
||||
if days < 50:
|
||||
cpu = "-1"
|
||||
if days > 50:
|
||||
cpu = "-2"
|
||||
if days > 100:
|
||||
cpu = "-4"
|
||||
if days > 150:
|
||||
cpu = "-6"
|
||||
if days > 200:
|
||||
cpu = "-8"
|
||||
if days > 250:
|
||||
cpu = "-10"
|
||||
if days > 300:
|
||||
cpu = "-11"
|
||||
if days > 350:
|
||||
cpu = "-12"
|
||||
if days > 400:
|
||||
cpu = "-13"
|
||||
if days > 450:
|
||||
cpu = "-14"
|
||||
if days > 500:
|
||||
cpu = "-14"
|
||||
if days > 550:
|
||||
cpu = "-16"
|
||||
if days > 600:
|
||||
cpu = "-16"
|
||||
if days > 650:
|
||||
cpu = "-17"
|
||||
if days > 700:
|
||||
cpu = "-17"
|
||||
if days > 750:
|
||||
cpu = "-17"
|
||||
if days > 800:
|
||||
cpu = "-18"
|
||||
if days > 850:
|
||||
cpu = "-18"
|
||||
if days > 900:
|
||||
cpu = "-18"
|
||||
if days > 950:
|
||||
cpu = "-19"
|
||||
if days > 1000:
|
||||
cpu = "-19"
|
||||
if days > 1100:
|
||||
cpu = "-20"
|
||||
else:
|
||||
d1 = datetime.strptime("20220601", "%Y%m%d")
|
||||
d2 = datetime.strptime("20210101", "%Y%m%d")
|
||||
d3 = datetime.strptime("20200101", "%Y%m%d")
|
||||
d4 = datetime.strptime("20190101", "%Y%m%d")
|
||||
d5 = datetime.strptime("20180101", "%Y%m%d")
|
||||
|
||||
if is_finish > d1:
|
||||
cpu = self.cpu
|
||||
|
||||
if d2 < is_finish < d1:
|
||||
cpu = int(self.cpu) + 1
|
||||
|
||||
if d3 < is_finish < d2:
|
||||
cpu = int(self.cpu) + 2
|
||||
|
||||
if d4 < is_finish < d3:
|
||||
cpu = int(self.cpu) + 3
|
||||
|
||||
if d5 < is_finish < d4:
|
||||
cpu = int(self.cpu) + 4
|
||||
|
||||
if is_finish < d5:
|
||||
cpu = int(self.cpu) + 5
|
||||
|
||||
return str(cpu)
|
||||
|
||||
def start_log(self):
|
||||
# Set format and level:
|
||||
logger.setLevel(logging.INFO)
|
||||
formatter = logging.Formatter('%(asctime)s - WF - %(levelname)s - %(message)s')
|
||||
|
||||
# Remove old handlers:
|
||||
while logger.handlers:
|
||||
logger.handlers.pop()
|
||||
|
||||
# Define file handler:
|
||||
file_handler = logging.FileHandler(f'{self.output_dir}/wf.log')
|
||||
file_handler.setFormatter(formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
# Define console handler:
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setFormatter(formatter)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
# Startup logs:
|
||||
_cpu = 20 + int(self.cpu) + 1
|
||||
start_date = datetime.strptime(self.is_start, '%Y%m%d').date()
|
||||
end_date = datetime.strptime(self.wf_finish, '%Y%m%d').date()
|
||||
run_time = str(end_date - start_date).split(",")[0]
|
||||
logger.info('Code Initiated')
|
||||
logger.info(f'Strategy:{self.strategy_path}/{self.strategy}')
|
||||
logger.info(f'Config:{self.config}')
|
||||
logger.info(f'Loss Function: {self.loss_function}')
|
||||
logger.info(f'Time Frame:{self.is_start}-{self.wf_finish} ({run_time})')
|
||||
logger.info(f'IS-days:{self.in_sample_days}, OOS-days:{self.out_sample_days}')
|
||||
logger.info(f'CPUs:{_cpu}, Epochs:{self.epochs}, Wallet:{"2500"}, Fee:{"0.002"}, Min-trades:{self.min_trades}')
|
||||
|
||||
|
||||
def plot_equity_curve(df, output_dir, save_fig=False):
|
||||
df = df.copy()
|
||||
pd.set_option('display.max_columns', None)
|
||||
fig, axs = plt.subplots(figsize=(7, 4))
|
||||
axs.xaxis.set_major_formatter(mdates.DateFormatter("%d %b"))
|
||||
df['dates'] = df["bt_time_period"].apply(lambda i: i.split("-")[1])
|
||||
df['dt'] = df['dates'].apply(lambda i: datetime.strptime(i, '%Y%m%d'))
|
||||
df.set_index('dt')
|
||||
df.plot(ax=axs, x="dt", y="acc-finish")
|
||||
dela_y = int(df["acc-finish"].max()) - int(df["acc-finish"].min())
|
||||
y_min = int(df["acc-finish"].min()) - (dela_y * 0.05)
|
||||
y_max = int(df["acc-finish"].max()) + (dela_y * 0.05)
|
||||
axs.set_title('Walk-Forward equity curve')
|
||||
axs.set_ylim(y_min, y_max)
|
||||
axs.set_ylabel("")
|
||||
axs.set_xlabel("")
|
||||
axs.grid(color='grey', alpha=0.5, linestyle='dashed', linewidth=0.5)
|
||||
axs.yaxis.set_major_formatter("£" + '{x:1.0f}')
|
||||
# plt.show()
|
||||
|
||||
if save_fig:
|
||||
try:
|
||||
plt.savefig(f"{output_dir}/wf_equity_curve.png")
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def walk_forward(path, strategy, config, output_dir, wf_start, wf_finish, anchored_start, pre_live=False, re_opt=False):
|
||||
is_list = ["730"]
|
||||
oos_list = ["30"]
|
||||
n_trades = ["100"]
|
||||
cpu_list = ["-15"]
|
||||
|
||||
ep = "100"
|
||||
loss_f = "SharpeHyperOptLoss"
|
||||
|
||||
for count, is_days in enumerate(is_list):
|
||||
oos_days = oos_list[count]
|
||||
nt = n_trades[count]
|
||||
cores = cpu_list[count]
|
||||
|
||||
wf = WalkForward(strategy_path=path, strategy=strategy, config=config, output_dir=output_dir,
|
||||
wf_start=wf_start, wf_finish=wf_finish, anchored_start=anchored_start, epochs=ep,
|
||||
loss_function=loss_f, in_sample_days=is_days, out_sample_days=oos_days, min_trades=nt,
|
||||
cpu=cores, pre_live=pre_live, re_opt=re_opt)
|
||||
|
||||
wf.run_walk_forward()
|
||||
return
|
||||
|
||||
|
||||
def re_optimise(path, strategy, config, output_dir, cpu="-19"):
|
||||
today = datetime.now().strftime("%Y%m%d")
|
||||
loss_f = "SortinoHyperOptLoss"
|
||||
in_sample_days = "730"
|
||||
n_trades = "100"
|
||||
ep = "200"
|
||||
download_data_t = "5m 1h 1d"
|
||||
|
||||
wf = WalkForward(strategy_path=path, strategy=strategy, config=config, output_dir=output_dir, wf_start=today,
|
||||
wf_finish=today, anchored_start=today, epochs=ep, loss_function=loss_f,
|
||||
in_sample_days=in_sample_days, out_sample_days="1", min_trades=n_trades, cpu=cpu,
|
||||
pre_live=False, re_opt=True, re_opt_t=download_data_t)
|
||||
|
||||
wf.re_optimise()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
WF_START = "20200101" # 2023-03-25
|
||||
WF_END = "20230910"
|
||||
ANCHORED_START = "20200101" # only used if in_sample_days == "anchored"
|
||||
STRATEGY_PATH_THOR = "/home/matt/freqtrade/user_data/strategies/Thor"
|
||||
STRATEGY_THOR = "Optimise_Thor_BuySig_RiskReward"
|
||||
CONFIG_THOR = "/home/matt/freqtrade/user_data/strategies/Thor/config_Thor_WF.json"
|
||||
OUTPUT_DIR_THOR = "/home/matt/freqtrade/user_data/strategies/Thor/walk_forward"
|
||||
|
||||
# -------------------------------------------------------------
|
||||
walk_forward(STRATEGY_PATH_THOR, STRATEGY_THOR, CONFIG_THOR, OUTPUT_DIR_THOR, WF_START, WF_END, ANCHORED_START)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
re_optimise(STRATEGY_PATH_THOR, STRATEGY_THOR, CONFIG_THOR, OUTPUT_DIR_THOR)
|
||||
Reference in New Issue
Block a user