diff --git a/margin_req.txt b/margin_req.txt index 18eea44..7f5157e 100644 --- a/margin_req.txt +++ b/margin_req.txt @@ -19,4 +19,4 @@ unicodedata2==14.0.0 unittest2==1.1.0 wheel==0.31.1 wmi==1.5.1 -martin-binance==1.2.6 \ No newline at end of file +martin-binance==1.2.6 diff --git a/martin_binance/__init__.py b/martin_binance/__init__.py index 80b9cdd..7f1a0e4 100644 --- a/martin_binance/__init__.py +++ b/martin_binance/__init__.py @@ -4,7 +4,7 @@ __author__ = "Jerry Fedorenko" __copyright__ = "Copyright © 2021 Jerry Fedorenko aka VM" __license__ = "MIT" -__version__ = "1.2.6-15" +__version__ = "1.2.6-16" __maintainer__ = "Jerry Fedorenko" __contact__ = "https://github.com/DogsTailFarmer" diff --git a/martin_binance/executor.py b/martin_binance/executor.py index 7b9950a..b145f2f 100644 --- a/martin_binance/executor.py +++ b/martin_binance/executor.py @@ -640,7 +640,7 @@ class Strategy(StrategyBase): all_order_exist = StrategyBase.all_order_exist ############################################################## - # strategy logic methods + # strategy control methods ############################################################## def __init__(self): super().__init__() @@ -779,76 +779,34 @@ def init(self, check_funds: bool = True) -> None: # skipcq: PYL-W0221 if last_price: print('Last ticker price: ', last_price) self.avg_rate = f2d(last_price) - df = self.get_buffered_funds().get(self.f_currency, 0) - df = df.available if df else 0 - if USE_ALL_FIRST_FUND and df and self.cycle_buy: - self.message_log('Check USE_ALL_FIRST_FUND parameter. You may have loss on Reverse cycle', - color=Style.B_WHITE) - if self.cycle_buy: - ds = self.get_buffered_funds().get(self.s_currency, 0) - ds = ds.available if ds else 0 - if check_funds and self.deposit_second > f2d(ds): - self.message_log('Not enough second coin for Buy cycle!', color=Style.B_RED) - if STANDALONE: - raise SystemExit(1) - first_order_vlm = self.deposit_second * 1 * (1 - self.martin) / (1 - self.martin**ORDER_Q) - first_order_vlm /= self.avg_rate - amount_min = tcm.get_min_buy_amount(last_price) - else: - if USE_ALL_FIRST_FUND: - self.deposit_first = f2d(df) - else: - if check_funds and self.deposit_first > f2d(df): - self.message_log('Not enough first coin for Sell cycle!', color=Style.B_RED) - if STANDALONE: - raise SystemExit(1) - first_order_vlm = self.deposit_first * 1 * (1 - self.martin) / (1 - pow(self.martin, ORDER_Q)) - amount_min = tcm.get_min_sell_amount(last_price) - if not GRID_ONLY and PROFIT_MAX < 100: - # Calculate the recommended size of the first grid order depending on the step_size - step_size = tcm.get_minimal_amount_change(amount_min) - k_m = 1 - float(PROFIT_MAX) / 100 - amount_first_grid = (step_size * last_price / ((1 / k_m) - 1)) - # For Bitfinex test accounts correction - if (amount_first_grid >= float(self.deposit_second) if self.cycle_buy else float(self.deposit_first) or - amount_first_grid >= tcm.get_max_sell_amount(0)): - amount_first_grid /= ORDER_Q - # + if self.first_run and check_funds: + df = self.get_buffered_funds().get(self.f_currency, 0) + df = df.available if df else 0 + if USE_ALL_FIRST_FUND and df and self.cycle_buy: + self.message_log('Check USE_ALL_FIRST_FUND parameter. You may have loss on Reverse cycle', + color=Style.B_WHITE) if self.cycle_buy: - if amount_first_grid > 80 * self.deposit_second / 100: - self.message_log(f"Recommended size of the first grid order {amount_first_grid:f} too large for" - f" a small deposit {self.deposit_second}", log_level=LogLevel.ERROR) - if STANDALONE and self.first_run: + ds = self.get_buffered_funds().get(self.s_currency, 0) + ds = ds.available if ds else 0 + if self.deposit_second > f2d(ds): + self.message_log('Not enough second coin for Buy cycle!', color=Style.B_RED) + if STANDALONE: raise SystemExit(1) - elif amount_first_grid > 20 * self.deposit_second / 100: - self.message_log(f"Recommended size of the first grid order {amount_first_grid:f} it is rather" - f" big for a small deposit {self.deposit_second}", log_level=LogLevel.WARNING) + depo = self.deposit_second else: - amount_first_grid /= last_price - if amount_first_grid > 80 * self.deposit_first / 100: - self.message_log(f"Recommended size of the first grid order {amount_first_grid:f} too large for" - f" a small deposit {self.deposit_first}", log_level=LogLevel.ERROR) - if STANDALONE and self.first_run: - raise SystemExit(1) - elif amount_first_grid > 20 * self.deposit_first / 100: - self.message_log(f"Recommended size of the first grid order {amount_first_grid:f} it is rather" - f" big for a small deposit {self.deposit_first}", log_level=LogLevel.WARNING) - # - if self.cycle_buy and first_order_vlm < tcm.get_min_buy_amount(last_price): - self.message_log(f"Total deposit {AMOUNT_SECOND}{self.s_currency}" - f" not enough for min amount for {ORDER_Q} orders.", color=Style.B_RED) - if STANDALONE and self.first_run: - raise SystemExit(1) - elif not self.cycle_buy and first_order_vlm < tcm.get_min_sell_amount(last_price): - self.message_log(f"Total deposit {self.deposit_first}{self.f_currency}" - f" not enough for min amount for {ORDER_Q} orders.", color=Style.B_RED) - if STANDALONE and self.first_run: - raise SystemExit(1) - buy_amount = tcm.get_min_buy_amount(last_price) - sell_amount = tcm.get_min_sell_amount(last_price) - print(f"buy_amount: {buy_amount}, sell_amount: {sell_amount}") + if USE_ALL_FIRST_FUND: + self.deposit_first = f2d(df) + else: + if self.deposit_first > f2d(df): + self.message_log('Not enough first coin for Sell cycle!', color=Style.B_RED) + if STANDALONE: + raise SystemExit(1) + depo = self.deposit_first + self.place_grid(self.cycle_buy, depo, self.reverse_target_amount, init_calc_only=True) else: - print('Actual price not received, initialization checks skipped') + print("Can't get actual price, initialization checks stopped") + if STANDALONE: + raise SystemExit(1) # self.message_log('End Init section') @staticmethod @@ -1504,6 +1462,39 @@ def unsuspend(self) -> None: print('Unsuspend') self.start_process() + def init_warning(self, _amount_first_grid: Decimal): + if self.cycle_buy: + depo = self.deposit_second + else: + depo = self.deposit_first + if ADAPTIVE_TRADE_CONDITION: + if self.first_run and self.order_q < 3: + self.message_log(f"Depo amount {depo} not enough to set the grid with 3 or more orders", + log_level=LogLevel.ERROR) + if STANDALONE: + raise SystemExit(1) + + _amount_first_grid = _amount_first_grid if self.cycle_buy else (_amount_first_grid / self.avg_rate) + + if _amount_first_grid > 80 * depo / 100: + self.message_log(f"Recommended size of the first grid order {_amount_first_grid:f} too large for" + f" a small deposit {self.deposit_second}", log_level=LogLevel.ERROR) + if STANDALONE and self.first_run: + raise SystemExit(1) + elif _amount_first_grid > 20 * depo / 100: + self.message_log(f"Recommended size of the first grid order {_amount_first_grid:f} it is rather" + f" big for a small deposit {self.deposit_second}", log_level=LogLevel.WARNING) + else: + first_order_vlm = depo * 1 * (1 - self.martin) / (1 - self.martin ** ORDER_Q) + + first_order_vlm = (first_order_vlm / self.avg_rate) if self.cycle_buy else first_order_vlm + + if first_order_vlm < _amount_first_grid: + self.message_log(f"Depo amount {depo}{self.s_currency} not enough for {ORDER_Q} orders", + color=Style.B_RED) + if STANDALONE and self.first_run: + raise SystemExit(1) + ############################################################## # strategy function ############################################################## @@ -1514,12 +1505,14 @@ def place_grid(self, reverse_target_amount: Decimal, allow_grid_shift: bool = True, additional_grid: bool = False, - grid_update: bool = False) -> None: - self.message_log(f"place_grid: buy_side: {buy_side}, depo: {depo}," - f" reverse_target_amount: {reverse_target_amount}," - f" allow_grid_shift: {allow_grid_shift}," - f" additional_grid: {additional_grid}," - f" grid_update: {grid_update}", log_level=LogLevel.DEBUG) + grid_update: bool = False, + init_calc_only: bool = False) -> None: + if not init_calc_only: + self.message_log(f"place_grid: buy_side: {buy_side}, depo: {depo}," + f" reverse_target_amount: {reverse_target_amount}," + f" allow_grid_shift: {allow_grid_shift}," + f" additional_grid: {additional_grid}," + f" grid_update: {grid_update}", log_level=LogLevel.DEBUG) self.grid_hold.clear() self.last_shift_time = None funds = self.get_buffered_funds() @@ -1565,11 +1558,17 @@ def place_grid(self, amount_first_grid = amount_min_dec if self.order_q > 1: self.message_log(f"For{' Reverse' if self.reverse else ''} {'Buy' if buy_side else 'Sell'}" - f" cycle set {self.order_q} orders for {self.over_price:.4f}% over price", tlg=False) + f" cycle{' will be' if init_calc_only else ''} set {self.order_q} orders" + f" for {self.over_price:.4f}% over price", tlg=False) else: self.message_log(f"For{' Reverse' if self.reverse else ''} {'Buy' if buy_side else 'Sell'}" f" cycle set 1 order{' for additional grid' if additional_grid else ''}", tlg=False) + # + if init_calc_only: + self.init_warning(amount_first_grid) + return + # if self.order_q > 1: delta_price = self.over_price * base_price_dec / (100 * (self.order_q - 1)) else: @@ -1903,16 +1902,6 @@ def set_trade_conditions(self, self.message_log(f"set_trade_conditions: buy_side: {buy_side}, depo: {float(depo):f}, base_price: {base_price}," f" reverse_target_amount: {reverse_target_amount}, amount_min: {amount_min}," f" step_size: {step_size}, delta_min: {delta_min}", LogLevel.DEBUG) - if additional_grid or grid_update: - grid_min = 1 - else: - if FEE_FTX and not self.reverse: - grid_min = GRID_MAX_COUNT - else: - grid_min = ORDER_Q - over_price_min = 100 * delta_min * (grid_min + 1) / base_price - self.message_log(f"set_trade_conditions.grid_min: {grid_min}, over_price_min: {float(over_price_min):f}", - LogLevel.DEBUG) depo_c = (depo / base_price) if buy_side else depo if not additional_grid and not grid_update and not GRID_ONLY and PROFIT_MAX < 100: k_m = 1 - PROFIT_MAX / 100 @@ -1939,7 +1928,7 @@ def set_trade_conditions(self, else: tbb = bb.get('tbb') over_price = 100 * (f2d(tbb) - base_price) / base_price - self.over_price = max(over_price, over_price_min) + self.over_price = max(over_price, OVER_PRICE) # Adapt grid orders quantity for current over price order_q = int(self.over_price * ORDER_Q / OVER_PRICE) depo_c = (depo / base_price) if buy_side else depo @@ -1948,8 +1937,7 @@ def set_trade_conditions(self, self.message_log(f"set_trade_conditions: depo: {float(depo):f}, order_q: {order_q}," f" amount_first_grid: {amount_first_grid:f}, amount_2: {amnt_2:f}," f" q_max: {q_max}, coarse overprice: {float(self.over_price):f}", LogLevel.DEBUG) - q_max = max(q_max, grid_min) - while q_max > grid_min: + while q_max > 3: delta_price = self.over_price * base_price / (100 * (q_max - 1)) if LINEAR_GRID_K >= 0: price_k = f2d(1 - math.log(q_max - 1, q_max + LINEAR_GRID_K)) @@ -1960,13 +1948,7 @@ def set_trade_conditions(self, break q_max -= 1 # - if order_q > q_max: - self.order_q = q_max - else: - if order_q >= grid_min: - self.order_q = order_q - else: - self.order_q = grid_min + self.order_q = q_max if order_q > q_max else order_q # Correction over_price after change quantity of orders if self.reverse and self.order_q > 1: over_price = self.calc_over_price(buy_side, @@ -1976,7 +1958,7 @@ def set_trade_conditions(self, delta_min, amount_first_grid, amount_min) - self.over_price = max(over_price, over_price_min) + self.over_price = max(over_price, OVER_PRICE) return amount_first_grid def set_profit(self) -> Decimal: @@ -2787,6 +2769,9 @@ def on_new_ticker(self, ticker: Ticker) -> None: def on_new_order_book(self, order_book: OrderBook) -> None: # print(f"on_new_order_book: max_bids: {order_book.bids[0].price}, min_asks: {order_book.asks[0].price}") pass + ############################################################## + # private update methods + ############################################################## def on_new_funds(self, funds: Dict[str, FundsEntry]) -> None: # print(f"on_new_funds.funds: {funds}") @@ -2824,10 +2809,6 @@ def on_new_funds(self, funds: Dict[str, FundsEntry]) -> None: self.grid_hold['additional_grid'], self.grid_hold['grid_update']) - ############################################################## - # private update methods - ############################################################## - def on_order_update(self, update: OrderUpdate) -> None: # self.message_log(f"Order {update.original_order.id}: {update.status}", log_level=LogLevel.DEBUG) if update.status in [OrderUpdate.ADAPTED, diff --git a/pyproject.toml b/pyproject.toml index 78c6977..969b877 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ dynamic = ["version", "description"] requires-python = ">=3.8" dependencies = [ - "exchanges-wrapper>=1.2.4-5", + "exchanges-wrapper>=1.2.4-15", "margin-strategy-sdk==0.0.11", "aiohttp>=3.8.1", "grpcio>=1.47.0", diff --git a/requirements.txt b/requirements.txt index e0130d9..f1e8ee1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -exchanges-wrapper>=1.2.4-5 +exchanges-wrapper>=1.2.4-15 margin-strategy-sdk==0.0.11 aiohttp>=3.8.1 grpcio>=1.47.0