Skip to content

Commit

Permalink
Merge pull request #480 from enarjord/v7.2.10
Browse files Browse the repository at this point in the history
V7.2.10 multi exchange backtest and optimize
  • Loading branch information
enarjord authored Dec 7, 2024
2 parents eed4f13 + 2ba61cb commit 7aa3f07
Show file tree
Hide file tree
Showing 11 changed files with 271 additions and 195 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

:warning: **Used at one's own risk** :warning:

v7.2.9
v7.2.10


## Overview
Expand Down
2 changes: 1 addition & 1 deletion configs/template.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{"backtest": {"base_dir": "backtests",
"compress_cache": true,
"end_date": "now",
"exchange": "binance",
"exchanges": ["binance", "bybit"],
"start_date": "2021-05-01",
"starting_balance": 100000.0},
"bot": {"long": {"close_grid_markup_range": 0.0013425,
Expand Down
6 changes: 3 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ Here follows an overview of the parameters found in `config/template.json`.
- `base_dir`: Location to save backtest results.
- `compress_cache`: set to true to save disk space. Set to false to load faster.
- `end_date`: End date of backtest, e.g., 2024-06-23. Set to 'now' to use today's date as end date.
- `exchange`: Exchange from which to fetch 1m OHLCV data. Default is Binance.
- `exchanges`: Exchanges from which to fetch 1m OHLCV data for backtesting and optimizing.
- `start_date`: Start date of backtest.
- `starting_balance`: Starting balance in USD at the beginning of backtest.
- `symbols`: Coins which were backtested. Note: coins for backtesting are live.approved_coins minus live.ignored_coins.
- `symbols`: Coins which were backtested for each exchange. Note: coins for backtesting are live.approved_coins minus live.ignored_coins.

## Bot Settings

Expand Down Expand Up @@ -200,7 +200,7 @@ When optimizing, parameter values are within the lower and upper bounds.

### Optimization Limits

The optimizer will penalize backtests whose metrics exceed the given values.
The optimizer will penalize backtests whose metrics exceed the given values. If multiple exchanges are optimized, it will select the worst of them.

- `lower_bound_drawdown_worst`: Lowest drawdown during backtest.
- `lower_bound_equity_balance_diff_mean`: Mean of the difference between equity and balance.
Expand Down
58 changes: 33 additions & 25 deletions src/backtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,15 +142,19 @@ def check_nested(d0, d1):
return check_nested(dict0, dict1)


def get_cache_hash(config):
to_hash = {k: config["backtest"][k] for k in ["end_date", "exchange", "start_date", "symbols"]}
to_hash["end_date"] = format_end_date(to_hash["end_date"])
def get_cache_hash(config, exchange):
to_hash = {
"symbols": config["backtest"]["symbols"][exchange],
"end_date": format_end_date(config["backtest"]["end_date"]),
"start_date": config["backtest"]["start_date"],
"exchange": exchange,
}
to_hash["minimum_coin_age_days"] = config["live"]["minimum_coin_age_days"]
return calc_hash(to_hash)


def load_symbols_hlcvs_from_cache(config):
cache_hash = get_cache_hash(config)
def load_symbols_hlcvs_from_cache(config, exchange):
cache_hash = get_cache_hash(config, exchange)
cache_dir = Path("caches") / "hlcvs_data" / cache_hash[:16]
if os.path.exists(cache_dir):
symbols = json.load(open(cache_dir / "symbols.json"))
Expand All @@ -166,8 +170,8 @@ def load_symbols_hlcvs_from_cache(config):
return cache_dir, symbols, hlcvs


def save_symbols_hlcvs_to_cache(config, symbols, hlcvs):
cache_hash = get_cache_hash(config)
def save_symbols_hlcvs_to_cache(config, symbols, hlcvs, exchange):
cache_hash = get_cache_hash(config, exchange)
cache_dir = Path("caches") / "hlcvs_data" / cache_hash[:16]
cache_dir.mkdir(parents=True, exist_ok=True)
if all([os.path.exists(cache_dir / x) for x in ["symbols.json", "hlcvs.npy"]]):
Expand Down Expand Up @@ -201,10 +205,10 @@ def save_symbols_hlcvs_to_cache(config, symbols, hlcvs):
return cache_dir


async def prepare_hlcvs_mss(config):
async def prepare_hlcvs_mss(config, exchange):
results_path = oj(
config["backtest"]["base_dir"],
config["backtest"]["exchange"],
exchange,
"",
)
mss_path = oj(
Expand All @@ -213,7 +217,7 @@ async def prepare_hlcvs_mss(config):
)
try:
sts = utc_ms()
result = load_symbols_hlcvs_from_cache(config)
result = load_symbols_hlcvs_from_cache(config, exchange)
if result:
logging.info(f"Seconds to load cache: {(utc_ms() - sts) / 1000:.4f}")
cache_dir, symbols, hlcvs = result
Expand All @@ -223,7 +227,7 @@ async def prepare_hlcvs_mss(config):
except:
logging.info(f"Unable to load hlcvs data from cache. Fetching...")
try:
mss = fetch_market_specific_settings_multi(exchange=config["backtest"]["exchange"])
mss = fetch_market_specific_settings_multi(exchange=exchange)
json.dump(mss, open(make_get_filepath(mss_path), "w"))
except Exception as e:
logging.error(f"failed to fetch market specific settings {e}")
Expand All @@ -233,19 +237,19 @@ async def prepare_hlcvs_mss(config):
except:
raise Exception("failed to load market specific settings from cache")

symbols, timestamps, hlcvs = await prepare_hlcvs(config)
symbols, timestamps, hlcvs = await prepare_hlcvs(config, exchange)
logging.info(f"Finished preparing hlcvs data. Shape: {hlcvs.shape}")
try:
cache_dir = save_symbols_hlcvs_to_cache(config, symbols, hlcvs)
cache_dir = save_symbols_hlcvs_to_cache(config, symbols, hlcvs, exchange)
except Exception as e:
logging.error(f"failed to save hlcvs to cache {e}")
traceback.print_exc()
cache_dir = ""
return symbols, hlcvs, mss, results_path, cache_dir


def prep_backtest_args(config, mss, exchange_params=None, backtest_params=None):
symbols = sorted(set(config["backtest"]["symbols"])) # sort for consistency
def prep_backtest_args(config, mss, exchange, exchange_params=None, backtest_params=None):
symbols = sorted(set(config["backtest"]["symbols"][exchange])) # sort for consistency
bot_params = {k: config["bot"][k].copy() for k in ["long", "short"]}
for pside in bot_params:
bot_params[pside]["wallet_exposure_limit"] = (
Expand All @@ -267,8 +271,8 @@ def prep_backtest_args(config, mss, exchange_params=None, backtest_params=None):
return bot_params, exchange_params, backtest_params


def run_backtest(hlcvs, mss, config: dict):
bot_params, exchange_params, backtest_params = prep_backtest_args(config, mss)
def run_backtest(hlcvs, mss, config: dict, exchange: str):
bot_params, exchange_params, backtest_params = prep_backtest_args(config, mss, exchange)
logging.info(f"Backtesting...")
sts = utc_ms()

Expand All @@ -286,11 +290,13 @@ def run_backtest(hlcvs, mss, config: dict):
return fills, equities, analysis


def post_process(config, hlcvs, fills, equities, analysis, results_path):
def post_process(config, hlcvs, fills, equities, analysis, results_path, exchange):
sts = utc_ms()
fdf = process_forager_fills(fills)
equities = pd.Series(equities)
analysis_py, bal_eq = analyze_fills_forager(config["backtest"]["symbols"], hlcvs, fdf, equities)
analysis_py, bal_eq = analyze_fills_forager(
config["backtest"]["symbols"][exchange], hlcvs, fdf, equities
)
for k in analysis_py:
if k not in analysis:
analysis[k] = analysis_py[k]
Expand All @@ -305,7 +311,7 @@ def post_process(config, hlcvs, fills, equities, analysis, results_path):
fdf.to_csv(f"{results_path}fills.csv")
bal_eq.to_csv(oj(results_path, "balance_and_equity.csv"))
if not config["disable_plotting"]:
plot_forager(results_path, config["backtest"]["symbols"], fdf, bal_eq, hlcvs)
plot_forager(results_path, config["backtest"]["symbols"][exchange], fdf, bal_eq, hlcvs)


def plot_forager(results_path, symbols: [str], fdf: pd.DataFrame, bal_eq, hlcvs):
Expand Down Expand Up @@ -363,11 +369,13 @@ async def main():
update_config_with_args(config, args)
config = format_config(config)
config["disable_plotting"] = args.disable_plotting
symbols, hlcvs, mss, results_path, cache_dir = await prepare_hlcvs_mss(config)
config["backtest"]["symbols"] = symbols
config["backtest"]["cache_dir"] = str(cache_dir)
fills, equities, analysis = run_backtest(hlcvs, mss, config)
post_process(config, hlcvs, fills, equities, analysis, results_path)
config["backtest"]["cache_dir"] = {}
for exchange in config["backtest"]["exchanges"]:
symbols, hlcvs, mss, results_path, cache_dir = await prepare_hlcvs_mss(config, exchange)
config["backtest"]["symbols"][exchange] = symbols
config["backtest"]["cache_dir"][exchange] = str(cache_dir)
fills, equities, analysis = run_backtest(hlcvs, mss, config, exchange)
post_process(config, hlcvs, fills, equities, analysis, results_path, exchange)


if __name__ == "__main__":
Expand Down
28 changes: 14 additions & 14 deletions src/downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,12 +423,11 @@ async def load_hlcvs(symbol, start_date, end_date, exchange="binance"):
return df[["timestamp", "high", "low", "close", "volume"]].values


async def prepare_hlcvs(config: dict):
symbols = sorted(set(config["backtest"]["symbols"]))
async def prepare_hlcvs(config: dict, exchange: str):
symbols = sorted(set(config["backtest"]["symbols"][exchange]))
start_date = config["backtest"]["start_date"]
end_date = format_end_date(config["backtest"]["end_date"])
end_ts = date_to_ts2(end_date)
exchange = config["backtest"]["exchange"]
minimum_coin_age_days = config["live"]["minimum_coin_age_days"]
interval_ms = 60000

Expand Down Expand Up @@ -660,17 +659,18 @@ async def main():
config = load_config(args.config_path)
update_config_with_args(config, args)
config = format_config(config)
for symbol in config["backtest"]["symbols"]:
try:
data = await load_hlcvs(
symbol,
config["backtest"]["start_date"],
config["backtest"]["end_date"],
exchange=config["backtest"]["exchange"],
)
except Exception as e:
logging.error(f"Error with {symbol} {e}")
traceback.print_exc()
for exchange in config["backtest"]["exchanges"]:
for symbol in config["backtest"]["symbols"][exchange]:
try:
data = await load_hlcvs(
symbol,
config["backtest"]["start_date"],
config["backtest"]["end_date"],
exchange=exchange,
)
except Exception as e:
logging.error(f"Error with {symbol} {e}")
traceback.print_exc()


if __name__ == "__main__":
Expand Down
4 changes: 2 additions & 2 deletions src/exchanges/binance.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ async def print_new_user_suggestion(self):
print(front_pad + "#" * (max_len + 2) + back_pad)
print("\n\n")

async def hourly_cycle(self, verbose=True):
async def init_markets(self, verbose=True):
await self.print_new_user_suggestion()
await super().hourly_cycle(verbose=verbose)
await super().init_markets(verbose=verbose)

def set_market_specific_settings(self):
super().set_market_specific_settings()
Expand Down
Loading

0 comments on commit 7aa3f07

Please sign in to comment.