From 42e12672487a06fb8c4317a703c7f16cc34706d6 Mon Sep 17 00:00:00 2001 From: DogsTailFarmer Date: Wed, 3 Apr 2024 18:45:20 +0300 Subject: [PATCH] 3.0.2 --- CHANGELOG.md | 10 ++++ martin_binance/__init__.py | 2 +- martin_binance/backtest/exchange_simulator.py | 44 ++++++++-------- martin_binance/backtest/optimizer.py | 9 ++-- martin_binance/executor.py | 12 ++--- martin_binance/lib.py | 4 +- martin_binance/strategy_base.py | 52 +++++++++---------- pyproject.toml | 2 +- requirements.txt | 2 +- 9 files changed, 74 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0ddb8e..5b99ef5 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 3.0.2 - 2024-04-03 +### Fix +* `Backtesting`: updating the balances at slippage +* `class PrivateTrade:` convert trade_id to int() +* `Backtest control`: orjson.JSONDecodeError: unexpected character: line 1 column 1 (char 0) + +### Update +* Up requirements for exchanges-wrapper==2.1.7 +* Some minor improvement + ## 3.0.1 - 2024-03-31 ### Refined and added new features * Project architecture diff --git a/martin_binance/__init__.py b/martin_binance/__init__.py index e5e44e9..96e9be3 100755 --- a/martin_binance/__init__.py +++ b/martin_binance/__init__.py @@ -6,7 +6,7 @@ __author__ = "Jerry Fedorenko" __copyright__ = "Copyright © 2021 Jerry Fedorenko aka VM" __license__ = "MIT" -__version__ = "3.0.1" +__version__ = "3.0.2" __maintainer__ = "Jerry Fedorenko" __contact__ = "https://github.com/DogsTailFarmer" diff --git a/martin_binance/backtest/exchange_simulator.py b/martin_binance/backtest/exchange_simulator.py index e6f4bfc..70da1ae 100644 --- a/martin_binance/backtest/exchange_simulator.py +++ b/martin_binance/backtest/exchange_simulator.py @@ -6,7 +6,7 @@ __author__ = "Jerry Fedorenko" __copyright__ = "Copyright © 2021 Jerry Fedorenko aka VM" __license__ = "MIT" -__version__ = "3.0.1" +__version__ = "3.0.2" __maintainer__ = "Jerry Fedorenko" __contact__ = "https://github.com/DogsTailFarmer" @@ -49,13 +49,14 @@ def on_order_canceled(self, side: str, amount: Decimal, price: Decimal): self.base['free'] += amount self.base['locked'] -= amount - def on_order_filled(self, side: str, amount: Decimal, price: Decimal, fee: Decimal): + def on_order_filled(self, side: str, amount: Decimal, price: Decimal, last_price: Decimal, fee: Decimal): if side == 'BUY': self.base['free'] += amount - fee * amount / 100 self.quote['locked'] -= amount * price + self.quote['free'] += amount * (price - last_price) else: self.base['locked'] -= amount - self.quote['free'] += amount * price - fee * (amount * price) / 100 + self.quote['free'] += amount * last_price - fee * (amount * last_price) / 100 class Order: @@ -184,7 +185,6 @@ def create_order( # Market event self.market_ids.append(order_id) - # print(f"create_order.order: {vars(order)}") return {'symbol': order.symbol, 'orderId': order.order_id, 'orderListId': order.order_list_id, @@ -218,23 +218,23 @@ def cancel_order(self, order_id: int, ts: int): self.grid_sell[ts] = self.orders_sell except Exception as ex: raise UserWarning(f"Order {order_id} not active: {ex}") from ex - else: - self.orders[order_id] = order - self.funds.on_order_canceled(order.side, order.orig_qty, order.price) - return {'symbol': order.symbol, - 'origClientOrderId': order.client_order_id, - 'orderId': order.order_id, - 'orderListId': order.order_list_id, - 'clientOrderId': 'qwert', - 'price': str(order.price), - 'origQty': str(order.orig_qty), - 'executedQty': str(order.executed_qty), - 'cummulativeQuoteQty': str(order.cummulative_quote_qty), - 'status': order.status, - 'timeInForce': order.time_in_force, - 'type': order.type, - 'side': order.side, - 'selfTradePreventionMode': order.self_trade_prevention_mode} + + self.orders[order_id] = order + self.funds.on_order_canceled(order.side, order.orig_qty - order.executed_qty, order.price) + return {'symbol': order.symbol, + 'origClientOrderId': order.client_order_id, + 'orderId': order.order_id, + 'orderListId': order.order_list_id, + 'clientOrderId': 'qwert', + 'price': str(order.price), + 'origQty': str(order.orig_qty), + 'executedQty': str(order.executed_qty), + 'cummulativeQuoteQty': str(order.cummulative_quote_qty), + 'status': order.status, + 'timeInForce': order.time_in_force, + 'type': order.type, + 'side': order.side, + 'selfTradePreventionMode': order.self_trade_prevention_mode} def on_ticker_update(self, ticker: {}, ts: int) -> [dict]: filled_buy_id = [] @@ -330,9 +330,11 @@ def on_ticker_update(self, ticker: {}, ts: int) -> [dict]: } # orders_filled.append(res) + self.funds.on_order_filled( order.side, order.last_executed_quantity, + order.price, order.last_executed_price, self.fee_taker if order_id in self.market_ids else self.fee_maker ) diff --git a/martin_binance/backtest/optimizer.py b/martin_binance/backtest/optimizer.py index c401bae..236d037 100755 --- a/martin_binance/backtest/optimizer.py +++ b/martin_binance/backtest/optimizer.py @@ -101,6 +101,7 @@ async def run_optimize(*args): logger.addHandler(fh) # prm_best = json.loads(sys.argv[5]) + logger.info(f"Previous best params: {prm_best}") try: study = optimize( sys.argv[1], @@ -115,11 +116,11 @@ async def run_optimize(*args): logger.info(f"optimizer: {ex}") else: new_value = round(study.best_value, ndigits=6) - logger.info(f"Optimal parameters: {study.best_params} for get {new_value}") + bp = {k: int(any2str(v)) if isinstance(v, int) else float(any2str(v)) for k, v in study.best_params.items()} + logger.info(f"Optimal parameters: {bp} for get {new_value}") _value = round(study.get_trials()[0].value, ndigits=6) if not prm_best or new_value > _value: - res = study.best_params - res |= {'new_value': any2str(new_value), '_value': any2str(_value)} - print(json.dumps(res)) + bp |= {'new_value': any2str(new_value), '_value': any2str(_value)} + print(json.dumps(bp)) else: print(json.dumps({})) diff --git a/martin_binance/executor.py b/martin_binance/executor.py index 65dd11f..f4646af 100755 --- a/martin_binance/executor.py +++ b/martin_binance/executor.py @@ -4,7 +4,7 @@ __author__ = "Jerry Fedorenko" __copyright__ = "Copyright © 2021 Jerry Fedorenko aka VM" __license__ = "MIT" -__version__ = "3.0.1" +__version__ = "3.0.2" __maintainer__ = "Jerry Fedorenko" __contact__ = 'https://github.com/DogsTailFarmer' ################################################################## @@ -124,7 +124,6 @@ def __init__(self, call_super=True): # schedule.every(5).minutes.do(self.event_grid_update) schedule.every(5).seconds.do(self.event_processing) - schedule.every(1).minutes.do(self.event_grid_only_release) schedule.every().minute.at(":30").do(self.event_grid_only_release) schedule.every().minute.at(":35").do(self.event_update_tp) schedule.every(2).seconds.do(self.event_exec_command) @@ -416,7 +415,6 @@ def event_processing(self): self.wait_wss_refresh['allow_grid_shift'], self.wait_wss_refresh['additional_grid'], self.wait_wss_refresh['grid_update']) - self.event_update_tp() if self.wait_refunding_for_start or self.tp_order_hold or self.grid_hold: self.get_buffered_funds() if self.reverse_hold: @@ -443,10 +441,10 @@ def event_processing(self): self.start_reverse_time = self.get_time() def event_update_tp(self): - if ADAPTIVE_TRADE_CONDITION and self.stable_state(): - if self.tp_order_id and not self.tp_part_amount_first and self.get_time() - self.tp_order[3] > 60 * 15: - self.message_log("Update TP order", color=Style.B_WHITE) - self.place_profit_order() + if ADAPTIVE_TRADE_CONDITION and self.stable_state() \ + and self.tp_order_id and not self.tp_part_amount_first and self.get_time() - self.tp_order[3] > 60 * 15: + self.message_log("Update TP order", color=Style.B_WHITE) + self.place_profit_order() def event_grid_only_release(self): if self.grid_only_restart and START_ON_BUY and AMOUNT_FIRST: diff --git a/martin_binance/lib.py b/martin_binance/lib.py index 8db6370..30c9b6d 100644 --- a/martin_binance/lib.py +++ b/martin_binance/lib.py @@ -4,7 +4,7 @@ __author__ = "Jerry Fedorenko" __copyright__ = "Copyright © 2021 Jerry Fedorenko aka VM" __license__ = "MIT" -__version__ = "3.0.1" +__version__ = "3.0.2" __maintainer__ = "Jerry Fedorenko" __contact__ = "https://github.com/DogsTailFarmer" @@ -234,7 +234,7 @@ def __init__(self, _trade: {}) -> None: self.amount = Decimal(_trade["qty"]) self.buy = _trade.get('isBuyer', False) self.is_maker = _trade.get('isMaker', False) - self.id = _trade["id"] + self.id = int(_trade["id"]) self.order_id = int(_trade["orderId"]) self.price = Decimal(_trade["price"]) self.commission = Decimal(_trade.get('commission', "0")) diff --git a/martin_binance/strategy_base.py b/martin_binance/strategy_base.py index 6316459..33cb8a7 100644 --- a/martin_binance/strategy_base.py +++ b/martin_binance/strategy_base.py @@ -4,7 +4,7 @@ __author__ = "Jerry Fedorenko" __copyright__ = "Copyright © 2021 Jerry Fedorenko aka VM" __license__ = "MIT" -__version__ = "3.0.1" +__version__ = "3.0.2" __maintainer__ = "Jerry Fedorenko" __contact__ = "https://github.com/DogsTailFarmer" @@ -15,7 +15,6 @@ import queue import os import random -import shutil import sqlite3 import time import traceback @@ -23,7 +22,7 @@ from datetime import datetime, timedelta, timezone from decimal import Decimal from pathlib import Path -from shutil import rmtree, copy +from shutil import rmtree, copy, make_archive from typing import Dict, List import jsonpickle @@ -338,31 +337,32 @@ async def backtest_control(self): except Exception as err: self.message_log(f"Backtest control: {err}", log_level=logging.ERROR) self.message_log(f"Exception traceback: {traceback.format_exc()}", log_level=logging.DEBUG) - else: - storage_name.replace(storage_name.with_name('study.db')) - if prm_best: - _prm_best = dict(prm_best) - self.message_log( - f"Updating parameters from backtest," - f" predicted value {prm_best.pop('_value')} -> {prm_best.pop('new_value')}", - color=Style.B_WHITE, - tlg=True - ) - for key, value in prm_best.items(): - self.message_log(f"{key}: {getattr(prm, key)} -> {value}") - setattr( - prm, key, - value if isinstance(value, int) or key in PARAMS_FLOAT else Decimal(f"{value}") - ) - l_m = str( - datetime.now(timezone.utc).replace(tzinfo=None) - _ts + timedelta(seconds=prm.SAVE_PERIOD) - ).rsplit('.')[0] + break + # + storage_name.replace(storage_name.with_name('study.db')) + if prm_best: + _prm_best = dict(prm_best) self.message_log( - f"Strategy parameters are optimal now. Optimization cycle duration {l_m}", + f"Updating parameters from backtest," + f" predicted value {prm_best.pop('_value')} -> {prm_best.pop('new_value')}", color=Style.B_WHITE, tlg=True ) - restart = True + for key, value in prm_best.items(): + self.message_log(f"{key}: {getattr(prm, key)} -> {value}") + setattr( + prm, key, + value if isinstance(value, int) or key in PARAMS_FLOAT else Decimal(f"{value}") + ) + l_m = str( + datetime.now(timezone.utc).replace(tzinfo=None) - _ts + timedelta(seconds=prm.SAVE_PERIOD) + ).rsplit('.')[0] + self.message_log( + f"Strategy parameters are optimal now. Optimization cycle duration {l_m}", + color=Style.B_WHITE, + tlg=True + ) + restart = True else: break @@ -442,7 +442,7 @@ def session_data_handler(self): df_grid_buy.index = pd.to_datetime(df_grid_buy.index, unit='ms') df_grid_buy.to_pickle(Path(session_data, "buy.pkl")) - shutil.make_archive(str(Path(self.session_root, "raw_bak")), 'zip', self.session_root, 'raw') + make_archive(str(Path(self.session_root, "raw_bak")), 'zip', self.session_root, 'raw') self.message_log(f"Stream data for backtesting saved to {self.session_root}") def parquet_declare(self, raw_path): @@ -1704,7 +1704,7 @@ def get_sum_profit(self): raise NotImplementedError @abstractmethod - def get_free_assets(self, **kwargs): + def get_free_assets(self, *args, **kwargs): raise NotImplementedError @abstractmethod diff --git a/pyproject.toml b/pyproject.toml index 33c7c2e..281bf8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ dynamic = ["version", "description"] requires-python = ">=3.9" dependencies = [ - "exchanges-wrapper==2.1.6", + "exchanges-wrapper==2.1.7", "jsonpickle==3.0.2", "psutil==5.9.6", "requests==2.31.0", diff --git a/requirements.txt b/requirements.txt index 7a08708..bd6c403 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -exchanges-wrapper==2.1.6 +exchanges-wrapper==2.1.7 jsonpickle==3.0.2 psutil==5.9.6 requests==2.31.0