Skip to content

Commit

Permalink
3.0.20 (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
DogsTailFarmer authored Feb 7, 2025
1 parent 267062c commit 9abda1c
Show file tree
Hide file tree
Showing 14 changed files with 86 additions and 71 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 3.0.20 - 2025-02-06
### Update
* `Bybit`: checking for the minimum/maximum Buy/Sell orders price is [excluded](https://announcements.bybit.com/article/title-adjustments-to-bybit-s-spot-trading-limit-order-mechanism-blt786c0c5abf865983/)
* Bump requirements
* Some minor improvements

## 3.0.19 - 2025-01-27
### Fix
* `on_balance_update_ex`: calculating initial balance for opposite coin in Reverse cycle
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ All risks and possible losses associated with use of this strategy lie with you.
Strongly recommended that you test the strategy in the demo mode before using real bidding.

## Important notices
* After update to `3.0.20` the config files `cli_XX_AAABBB.py` must be updated from [template](https://github.com/DogsTailFarmer/martin-binance/tree/public/martin_binance/templates)
* After update to `3.0.17`, the configuration file `exch_srv_cfg.toml` for [exchanges-wrapper](https://github.com/DogsTailFarmer/exchanges-wrapper) must be updated. [Use templates for reference.](https://github.com/DogsTailFarmer/exchanges-wrapper/blob/master/exchanges_wrapper/exch_srv_cfg.toml.template)
* After update to `3.0.17`, the configuration file `ms_cfg.toml` must be updated. [Use templates for reference.](https://github.com/DogsTailFarmer/martin-binance/blob/f0a0e5f9a7ceba3919ea0087f1b9f4e0d1bc95b6/martin_binance/templates/ms_cfg.toml)
* The config files `cli_XX_AAABBB.py` also must be updated from [template](https://github.com/DogsTailFarmer/martin-binance/tree/public/martin_binance/templates)
Expand Down
2 changes: 1 addition & 1 deletion martin_binance/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
__author__ = "Jerry Fedorenko"
__copyright__ = "Copyright © 2021 Jerry Fedorenko aka VM"
__license__ = "MIT"
__version__ = "3.0.19"
__version__ = "3.0.20"
__maintainer__ = "Jerry Fedorenko"
__contact__ = "https://github.com/DogsTailFarmer"

Expand Down
57 changes: 30 additions & 27 deletions martin_binance/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
__author__ = "Jerry Fedorenko"
__copyright__ = "Copyright © 2021-2025 Jerry Fedorenko aka VM"
__license__ = "MIT"
__version__ = "3.0.19"
__version__ = "3.0.20"
__maintainer__ = "Jerry Fedorenko"
__contact__ = 'https://github.com/DogsTailFarmer'
##################################################################
Expand Down Expand Up @@ -129,6 +129,7 @@ def __init__(self, call_super=True):
self.tp_part_free = False # + Can use TP part amount for converting to grid orders
self.ts_grid_update = self.get_time() # - When updated grid
self.wait_wss_refresh = {} # -
self.place_grid_part_after_tp = True # -
#
schedule.every(5).minutes.do(self.event_grid_update)
schedule.every(5).seconds.do(self.event_processing)
Expand Down Expand Up @@ -460,8 +461,13 @@ def event_processing(self):
self.start_reverse_time = self.get_time()

def event_update_tp(self):
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:
if (
ADAPTIVE_TRADE_CONDITION
and self.stable_state()
and self.tp_order_id
and self.get_time() - self.tp_order[3] > TP_REFRESH
and not self.tp_part_amount_first
):
self.message_log("Update TP order", color=Style.B_WHITE)
self.place_profit_order()

Expand Down Expand Up @@ -502,6 +508,7 @@ def _common_stable_conditions(self):
and not self.start_after_shift
and not self.tp_hold
and not self.tp_order_hold
and not self.tp_wait_id
and not self.orders_init
and self.command != 'stopped'
)
Expand Down Expand Up @@ -620,10 +627,24 @@ def restore_strategy_state(self, strategy_state: Dict[str, str] = None, restore=
self.message_log("Continue update grid", tlg=True)
self.grid_remove = True
self.cancel_grid()
elif not self.orders_grid and not self.orders_hold and not self.orders_save and not self.tp_order_id:
elif (
not self.orders_grid
and not self.orders_hold
and not self.orders_save
and not self.orders_init
and not self.tp_order_id
and not self.tp_wait_id
):
self.message_log("Restore, Restart", tlg=True)
self.start()
if not self.tp_order_id and self.stable_state():
if self.orders_init:
for order_id in self.orders_init.get_id_list():
self.message_log("Restore, wait grid orders", tlg=True)
self.check_created_order(order_id, "Grid order event was missed into reload")
if self.tp_wait_id:
self.message_log("Restore, wait TP order", tlg=True)
self.check_created_order(self.tp_wait_id, "TP order event was missed into reload")
elif not self.tp_order_id and self.stable_state():
self.message_log("Restore, no TP order, replace", tlg=True)
self.place_profit_order()

Expand Down Expand Up @@ -1857,8 +1878,7 @@ def place_grid_part(self) -> None:
i['buy'],
i['amount'],
i['price'],
check=True,
price_limit_rules=True
check=True
)
if waiting_order_id:
self.orders_init.append_order(waiting_order_id, i['buy'], i['amount'], i['price'])
Expand Down Expand Up @@ -2137,30 +2157,12 @@ def check_min_amount(self, amount=O_DEC, price=O_DEC, for_tp=True, by_market=Fal
_amount = self.deposit_first
return self.round_truncate(_amount, base=True) >= min_trade_amount

def place_limit_order_check(
self,
buy: bool,
amount: Decimal,
price: Decimal,
check=False,
price_limit_rules=False
) -> int:
def place_limit_order_check(self, buy: bool, amount: Decimal, price: Decimal, check=False) -> int:
"""
Before place limit order checking trade conditions and correct price
"""
if self.command == 'stopped':
return 0
if price_limit_rules:
tcm = self.get_trading_capability_manager()
_price = self.get_buffered_ticker().last_price or self.avg_rate
if ((buy and price < tcm.get_min_buy_price(_price)) or
(not buy and price > tcm.get_max_sell_price(_price))):
self.message_log(
f"{'Buy' if buy else 'Sell'} price {price} is out of trading range, will try later",
log_level=logging.WARNING,
color=Style.YELLOW
)
return 0
_price = price
if check:
order_book = self.get_buffered_order_book()
Expand Down Expand Up @@ -2572,7 +2574,7 @@ def on_place_order_success(self, place_order_id: int, order: Order) -> None:
if self.tp_hold or self.tp_cancel or self.tp_cancel_from_grid_handler:
self.cancel_order_id = self.tp_order_id
self.cancel_order(self.tp_order_id)
else:
elif self.place_grid_part_after_tp:
self.place_grid_part()
else:
self.message_log(f"Did not have waiting order {place_order_id}", logging.ERROR)
Expand All @@ -2585,6 +2587,7 @@ def on_place_order_error(self, place_order_id: int, error: str) -> None:
self.orders_init.remove(place_order_id)
self.orders_hold.orders_list.append(_order)
self.orders_hold.sort(self.cycle_buy)
self.place_grid_part_after_tp = False
if self.cancel_grid_hold:
self.message_log('Continue remove grid orders', color=Style.B_WHITE)
self.cancel_grid_hold = False
Expand Down
12 changes: 4 additions & 8 deletions martin_binance/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
__author__ = "Jerry Fedorenko"
__copyright__ = "Copyright © 2021 Jerry Fedorenko aka VM"
__license__ = "MIT"
__version__ = "3.0.2"
__version__ = "3.0.20"
__maintainer__ = "Jerry Fedorenko"
__contact__ = "https://github.com/DogsTailFarmer"

Expand Down Expand Up @@ -354,7 +354,7 @@ class TradingCapabilityManager:
"max_price",
)

def __init__(self, _exchange_info_symbol, price_limit_rules):
def __init__(self, _exchange_info_symbol):
self.base_asset_precision = int(_exchange_info_symbol.get('baseAssetPrecision'))
self.quote_asset_precision = int(_exchange_info_symbol.get('quoteAssetPrecision'))
self.min_qty = Decimal(_exchange_info_symbol['filters']['lotSize']['minQty'])
Expand All @@ -367,12 +367,8 @@ def __init__(self, _exchange_info_symbol, price_limit_rules):
self.tick_size = Decimal(_exchange_info_symbol['filters']['priceFilter']['tickSize'].rstrip('0'))
self.min_price = Decimal(_exchange_info_symbol['filters']['priceFilter']['minPrice'])
self.max_price = Decimal(_exchange_info_symbol['filters']['priceFilter']['maxPrice'])
if price_limit_rules:
self.multiplier_up = 1 + price_limit_rules / 100
self.multiplier_down = 1 - price_limit_rules / 100
else:
self.multiplier_up = Decimal(_exchange_info_symbol['filters']['percentPrice']['multiplierUp'])
self.multiplier_down = Decimal(_exchange_info_symbol['filters']['percentPrice']['multiplierDown'])
self.multiplier_up = Decimal(_exchange_info_symbol['filters']['percentPrice']['multiplierUp'])
self.multiplier_down = Decimal(_exchange_info_symbol['filters']['percentPrice']['multiplierDown'])

def __call__(self):
return self
Expand Down
8 changes: 4 additions & 4 deletions martin_binance/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
__author__ = "Jerry Fedorenko"
__copyright__ = "Copyright © 2021-2025 Jerry Fedorenko aka VM"
__license__ = "MIT"
__version__ = "3.0.17"
__version__ = "3.0.20"
__maintainer__ = "Jerry Fedorenko"
__contact__ = "https://github.com/DogsTailFarmer"

Expand All @@ -15,9 +15,9 @@
__all__ = [
'SYMBOL', 'EXCHANGE', 'ID_EXCHANGE', 'FEE_MAKER', 'FEE_TAKER', 'FEE_FIRST', 'FEE_SECOND', 'FEE_BNB',
'SAVE_ASSET', 'GRID_MAX_COUNT', 'START_ON_BUY', 'AMOUNT_FIRST', 'USE_ALL_FUND', 'AMOUNT_SECOND',
'PRICE_SHIFT', 'PRICE_LIMIT_RULES', 'ROUND_BASE', 'ROUND_QUOTE', 'PROFIT', 'PROFIT_MAX', 'OVER_PRICE', 'ORDER_Q',
'PRICE_SHIFT', 'ROUND_BASE', 'ROUND_QUOTE', 'PROFIT', 'PROFIT_MAX', 'OVER_PRICE', 'ORDER_Q',
'MARTIN', 'SHIFT_GRID_DELAY', 'GRID_UPDATE_INTERVAL', 'STATUS_DELAY', 'GRID_ONLY', 'LOG_LEVEL',
'HOLD_TP_ORDER_TIMEOUT', 'COLLECT_ASSETS', 'GRID_ONLY_DELAY', 'ADAPTIVE_TRADE_CONDITION',
'HOLD_TP_ORDER_TIMEOUT', 'COLLECT_ASSETS', 'GRID_ONLY_DELAY', 'TP_REFRESH', 'ADAPTIVE_TRADE_CONDITION',
'BB_CANDLE_SIZE_IN_MINUTES', 'BB_NUMBER_OF_CANDLES', 'KBB', 'LINEAR_GRID_K', 'ADX_CANDLE_SIZE_IN_MINUTES',
'ADX_NUMBER_OF_CANDLES', 'ADX_PERIOD', 'ADX_THRESHOLD', 'ADX_PRICE_THRESHOLD', 'REVERSE', 'REVERSE_TARGET_AMOUNT',
'REVERSE_INIT_AMOUNT', 'REVERSE_STOP', 'HEAD_VERSION', 'LOAD_LAST_STATE', 'LAST_STATE_FILE', 'VPS_NAME', 'PARAMS',
Expand Down Expand Up @@ -48,7 +48,6 @@
USE_ALL_FUND = bool()
AMOUNT_SECOND = Decimal()
PRICE_SHIFT = Decimal()
PRICE_LIMIT_RULES = Decimal()
# Round pattern
ROUND_BASE = str()
ROUND_QUOTE = str()
Expand All @@ -67,6 +66,7 @@
HOLD_TP_ORDER_TIMEOUT = 30
COLLECT_ASSETS = bool()
GRID_ONLY_DELAY = 150 # sec delay before try restart GRID_ONLY cycle
TP_REFRESH = 60 * 10 # sec between TP refresh
#
ADAPTIVE_TRADE_CONDITION = bool()
BB_CANDLE_SIZE_IN_MINUTES = int()
Expand Down
42 changes: 26 additions & 16 deletions martin_binance/strategy_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
__author__ = "Jerry Fedorenko"
__copyright__ = "Copyright © 2021-2025 Jerry Fedorenko aka VM"
__license__ = "MIT"
__version__ = "3.0.19"
__version__ = "3.0.20"
__maintainer__ = "Jerry Fedorenko"
__contact__ = "https://github.com/DogsTailFarmer"

Expand Down Expand Up @@ -262,6 +262,9 @@ def place_limit_order(self, buy: bool, amount: Decimal, price: Decimal) -> int:
def cancel_order(self, order_id: int, cancel_all=False) -> None:
self.tasks_manage(self.cancel_order_call(order_id, cancel_all))

def check_created_order(self, order_id: int, msg: str) -> None:
self.tasks_manage(self.fetch_created_order(order_id, msg))

def message_log(self, msg: str, log_level=logging.INFO, tlg=False, color=Style.WHITE, tlg_inline=False) -> None:
if prm.LOGGING:
if tlg and color == Style.WHITE:
Expand All @@ -277,8 +280,8 @@ def message_log(self, msg: str, log_level=logging.INFO, tlg=False, color=Style.W
tqdm.write(f"{datetime.fromtimestamp(self.get_time()).strftime('%H:%M:%S.%f')[:-3]} {color_msg}")
if prm.MODE in ('T', 'TC'):
logger.log(log_level, msg)
self.status_time = self.get_time()
if tlg and self.tlg_client:
self.status_time = self.get_time()
self.tasks_manage(
self.tlg_client.post_message(msg, inline_buttons=tlg_inline and prm.TLG_INLINE)
)
Expand Down Expand Up @@ -1013,7 +1016,7 @@ async def get_exchange_info(self, _request, _symbol):
self.message_log(f"Exception get_exchange_info: {_ex}")
else:
self.info_symbol = _exchange_info_symbol.to_pydict()
self.tcm = TradingCapabilityManager(self.info_symbol, prm.PRICE_LIMIT_RULES)
self.tcm = TradingCapabilityManager(self.info_symbol)
if prm.MODE == 'S':
break
await asyncio.sleep(600)
Expand Down Expand Up @@ -1131,15 +1134,18 @@ async def create_limit_order(self, _id: int, buy: bool, amount: str, price: str)
finally:
if prm.MODE in ('T', 'TC') and _fetch_order:
await asyncio.sleep(HEARTBEAT)
res = await self.fetch_order(0, str(_id), _filled_update_call=True)
if res.get('status') in ('NEW', 'PARTIALLY_FILLED', 'FILLED'):
await self.create_order_handler(_id, res)
else:
self.on_place_order_error(_id, msg)
await self.fetch_created_order(_id, msg)

async def fetch_created_order(self, _id, msg):
res = await self.fetch_order(0, str(_id), _filled_update_call=True)
if res.get('status') in ('NEW', 'PARTIALLY_FILLED', 'FILLED'):
await self.create_order_handler(_id, res)
else:
self.on_place_order_error(_id, msg)

async def create_order_handler(self, _id, result):
# print(f"create_order_handler.result: {result}")
if self.order_init_exist(_id) and not self.order_exist(result['orderId']):
if self.order_init_exist(_id): # and not self.order_exist(result['orderId']):
order = Order(result)
self.orders[order.id] = order
self.on_place_order_success(_id, order)
Expand Down Expand Up @@ -1437,8 +1443,13 @@ async def buffered_orders(self):
self.message_log(f"Trying set RATE_LIMITER to {self.rate_limiter}s", log_level=logging.WARNING)
await asyncio.sleep(ORDER_TIMEOUT)
try:
await self.send_request(self.stub.reset_rate_limit, mr.OpenClientConnectionId,
rate_limiter=self.rate_limiter)
res = await self.send_request(
self.stub.reset_rate_limit,
mr.OpenClientConnectionId,
rate_limiter=self.rate_limiter
)
if res and res.success:
self.message_log(f"RATE_LIMITER was set to {self.rate_limiter}s", log_level=logging.INFO)
except Exception as ex_4:
self.message_log(f"Exception buffered_orders 4: Set RATE_LIMITER failed: {ex_4}",
log_level=logging.WARNING)
Expand Down Expand Up @@ -1505,7 +1516,7 @@ async def wss_init(self):
await self.wss_wait_init()
else:
self.message_log("Init WSS failed, retry", log_level=logging.WARNING)
await asyncio.sleep(random.randint(HEARTBEAT, HEARTBEAT * 5)) # /NOSONAR
await asyncio.sleep(random.randint(HEARTBEAT, HEARTBEAT * 5)) # NOSONAR python:S2245
self.wss_fire_up = True

def wss_cancel_tasks(self):
Expand Down Expand Up @@ -1560,8 +1571,8 @@ async def main(self, _symbol): # /NOSONAR
else:
active_orders = list(map(json.loads, _active_orders.orders))
for order in active_orders:
print(f"Order: {order['orderId']}, side: {order['side']}, amount: {order['origQty']}"
f" price:{order['price']}, status: {order['status']}")
print(f"Order: {order['orderId']}({order['clientOrderId']}), side: {order['side']},"
f" amount: {order['origQty']}, price:{order['price']}, status: {order['status']}")
# Try load last strategy state from saved files
last_state = load_last_state(prm.LAST_STATE_FILE)
restore_state = bool(last_state)
Expand Down Expand Up @@ -1696,9 +1707,8 @@ async def main(self, _symbol): # /NOSONAR
)

self.orders = jsonpickle.decode(last_state.pop(MS_ORDERS, '{}'), keys=True)
orders_keys = self.orders.keys()
for _id in exch_orders_ids:
if _id not in orders_keys:
if _id not in self.orders.keys():
_order = next((_o for _o in active_orders if int(_o["orderId"]) == _id))
self.orders[_id] = Order(_order)
self.message_log(
Expand Down
9 changes: 8 additions & 1 deletion martin_binance/telegram_proxy/tlg_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
__author__ = "Jerry Fedorenko"
__copyright__ = "Copyright © 2025 Jerry Fedorenko aka VM"
__license__ = "MIT"
__version__ = "3.0.17"
__version__ = "3.0.20"
__maintainer__ = "Jerry Fedorenko"
__contact__ = "https://github.com/DogsTailFarmer"

Expand Down Expand Up @@ -107,6 +107,9 @@ async def connect(self):
delay += random.randint(1, 15) # NOSONAR python:S2245
logger.warning(f"Try connecting to Telegram proxy, retrying in {delay} second... ")
await asyncio.sleep(delay)
except ssl.SSLCertVerificationError as e:
logger.error(f"Connect to Telegram proxy failed: {e}")
break

async def post_message(self, text, inline_buttons=False, reraise=False) -> tlg.Response:
try:
Expand All @@ -125,6 +128,8 @@ async def post_message(self, text, inline_buttons=False, reraise=False) -> tlg.R
self.tasks_manage(self.connect())
elif reraise:
raise
except ssl.SSLCertVerificationError as e:
logger.error(f"Post message to Telegram proxy failed: {e}")
except (asyncio.CancelledError, KeyboardInterrupt):
pass # user interrupt

Expand All @@ -141,6 +146,8 @@ async def get_update(self) -> tlg.Response:
self.tasks_manage(self.connect())
except (asyncio.CancelledError, KeyboardInterrupt):
pass # user interrupt
except ssl.SSLCertVerificationError as e:
logger.error(f"Get update from Telegram proxy failed: {e}")

def close(self):
self.channel.close()
Expand Down
4 changes: 1 addition & 3 deletions martin_binance/templates/cli_0_BTCUSDT.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
__author__ = "Jerry Fedorenko"
__copyright__ = "Copyright © 2021-2025 Jerry Fedorenko aka VM"
__license__ = "MIT"
__version__ = "3.0.17"
__version__ = "3.0.20"
__maintainer__ = "Jerry Fedorenko"
__contact__ = "https://github.com/DogsTailFarmer"
"""
Expand Down Expand Up @@ -57,8 +57,6 @@
ex.USE_ALL_FUND = False # Use all available fund for initial cycle or alltime for GRID_ONLY
ex.AMOUNT_SECOND = Decimal('1000.0') # Deposit for Buy cycle in second currency
ex.PRICE_SHIFT = Decimal('0.01') # 'No market' shift price in % from current bid/ask price
# Search next parameter on Bybit https://www.bybit.com/en/announcement-info/spot-trading-rules/
ex.PRICE_LIMIT_RULES = Decimal('0') # +-% from last ticker price. Use on Bybit only. 0 - disable
# Round pattern, set pattern 1.0123456789 or if not set used exchange settings
ex.ROUND_BASE = str()
ex.ROUND_QUOTE = str()
Expand Down
Loading

0 comments on commit 9abda1c

Please sign in to comment.