diff --git a/main.py b/main.py index 6ae4a8a..ac6e97f 100644 --- a/main.py +++ b/main.py @@ -19,7 +19,8 @@ from py_clob_client.clob_types import ( OrderType, PartialCreateOrderOptions, PostOrdersArgs, - BalanceAllowanceParams + BalanceAllowanceParams, + OpenOrderParams ) from py_clob_client.order_builder.constants import BUY, SELL from sqlalchemy import text @@ -31,6 +32,7 @@ import modules.api as api @dataclass class Custom_OrderArgs(OrderArgs): max_price: float = 0.00 + post_only: bool = False ### Database ### @@ -45,10 +47,10 @@ LOG_FILEPATH: str = os.getenv("LOGS_PATH") + '/Polymarket_5min_Algo.log' ### ALGO CONFIG / CONSTANTS ### SLOPE_YES_THRESH = 0.01 # In Percent % Chg (e.g. 0.02 == 0.02%) ENDTIME_BUFFER_SEC = 30 # Stop trading, cancel all open orders and exit positions this many seconds before mkt settles. -TGT_PX_INDEX_DIFF_THRESH = 0.1 # In Percent % Chg (e.g. 0.02 == 0.02%) +TGT_PX_INDEX_DIFF_THRESH = 0.05 # In Percent % Chg (e.g. 0.02 == 0.02%) DEFAULT_ORDER_SIZE = 10 # In USDe MIN_ORDER_SIZE = 5 # In USDe -TGT_PROFIT_CENTS = 0.02 +TGT_PROFIT_CENTS = 0.04 CHASE_TO_BUY_CENTS = 0.05 MAX_ALLOWED_POLY_PX = 0.90 @@ -226,9 +228,9 @@ async def slope_decision() -> list[bool, str]: # print(f'Len Hist : {len(hist_trades[:, 1])}') # print(f'First Hist : {pd.to_datetime(np.min(hist_trades[:, 0]), unit='ms')}') # print(f'Latest Hist: {pd.to_datetime(np.max(hist_trades[:, 0]), unit='ms')}') - print(f'Slope Hist Avg: {np.mean(SLOPE_HIST):.4%}') - print(f'Slope Hist Max: {np.max(SLOPE_HIST):.4%}') - print(f'Slope Hist Std: {np.std(SLOPE_HIST):.4%}') + # print(f'Slope Hist Avg: {np.mean(SLOPE_HIST):.4%}') + # print(f'Slope Hist Max: {np.max(SLOPE_HIST):.4%}') + # print(f'Slope Hist Std: {np.std(SLOPE_HIST):.4%}') slope_1_buy = abs(slope) >= ( SLOPE_YES_THRESH / 100) slope_5_buy = abs(slope_5) >= ( SLOPE_YES_THRESH / 100) @@ -263,12 +265,25 @@ async def cancel_single_order_by_id(CLIENT, order_id): if o.get('orderID') == order_id: if bool(cxl_resp.get('not_canceled', True)): if cxl_resp.get('not_canceled', {}).get(order_id, None) == "matched orders can't be canceled": + LOCAL_ACTIVE_ORDERS[idx]['status'] = 'MATCHED' logging.info(f'Cancel request failed b/c already matched: {cxl_resp}') - return False + return True elif cxl_resp.get('not_canceled', {}).get(order_id, None) == "order can't be found - already canceled or matched": logging.info(f'Cancel request failed b/c already matched or cancelled: {cxl_resp}') - LOCAL_ACTIVE_ORDERS.pop(idx) - return False + # GET ORDER STATUS + order_status = CLIENT.get_orders( + OpenOrderParams(id=o['orderID']) + )[0]['status'].upper() + logging.info(f'Fetched status from CLOB: {order_status} for order: {o['orderID']}') + if order_status == 'MATCHED': + logging.info('Order is MATCHED') + return True + elif order_status == 'CANCELED': + logging.info('Order is CANCELED') + LOCAL_ACTIVE_ORDERS.pop(idx) + return False + else: + raise ValueError(f'ORDER CXL FAILED AND ORDER STILL SHOWS AS LIVE: {cxl_resp}; STATUS: {order_status}; ID: {o.get('orderID')}') else: logging.warning(f'*** Cancel Request FAILED, shutting down: {cxl_resp}') raise Exception('*** Cancel Request FAILED - SHUTDONW') @@ -282,8 +297,12 @@ async def flatten_open_positions(CLIENT, token_id_up, token_id_down): up = await get_balance_by_token_id(CLIENT=CLIENT, token_id=token_id_up) down = await get_balance_by_token_id(CLIENT=CLIENT, token_id=token_id_down) + logging.info('*********FLATTENING*********') + logging.info(f'UP BALANCE = {up}') + logging.info(f'DOWN BALANCE = {down}') + ### Submit orders to flatten outstanding balances ### - if up > MIN_ORDER_SIZE: + if abs(up) > MIN_ORDER_SIZE: logging.info(f'Flattening Up Position: {up}') await post_order( CLIENT = CLIENT, @@ -291,12 +310,12 @@ async def flatten_open_positions(CLIENT, token_id_up, token_id_down): neg_risk = POLY_CLOB['neg_risk'], OrderArgs_list = [Custom_OrderArgs( token_id=token_id_up, - price=float(POLY_CLOB['price'])-0.01, + price=float(POLY_CLOB['price'])-0.05, size=up, side=SELL, )] ) - if down > MIN_ORDER_SIZE: + if abs(down) > MIN_ORDER_SIZE: logging.info(f'Flattening Down Position: {down}') await post_order( CLIENT = CLIENT, @@ -304,7 +323,7 @@ async def flatten_open_positions(CLIENT, token_id_up, token_id_down): neg_risk = POLY_CLOB['neg_risk'], OrderArgs_list = [Custom_OrderArgs( token_id=token_id_down, - price=float(POLY_CLOB_DOWN['price'])-0.01, + price=float(POLY_CLOB_DOWN['price'])-0.05, size=down, side=SELL, @@ -354,7 +373,7 @@ async def check_for_open_positions(CLIENT, token_id_up, token_id_down): return False @async_timeit -async def post_order(CLIENT, OrderArgs_list: list[Custom_OrderArgs], tick_size: float | str, neg_risk: bool): +async def post_order(CLIENT, OrderArgs_list: list[Custom_OrderArgs], tick_size: float | str, neg_risk: bool) -> list[dict]: # Returns order response dict global LOCAL_ACTIVE_ORDERS global LOCAL_TOKEN_BALANCES @@ -370,7 +389,7 @@ async def post_order(CLIENT, OrderArgs_list: list[Custom_OrderArgs], tick_size: ), ), orderType=OrderType.GTC, - postOnly=False, + postOnly=oa.post_only, ), ) @@ -379,7 +398,15 @@ async def post_order(CLIENT, OrderArgs_list: list[Custom_OrderArgs], tick_size: for idx, d in enumerate(response): if d['errorMsg'] == '': d['token_id'] = OrderArgs_list[idx].token_id + if d['token_id'] == POLY_CLOB['token_id_up']: + d['outcome'] = "UP" + elif d['token_id'] == POLY_CLOB['token_id_down']: + d['outcome'] = "DOWN" + else: + d['outcome'] = "UNKNOWN" + d['price'] = OrderArgs_list[idx].price + d['max_price'] = OrderArgs_list[idx].max_price d['size'] = OrderArgs_list[idx].size d['side'] = str(OrderArgs_list[idx].side).upper() @@ -387,8 +414,7 @@ async def post_order(CLIENT, OrderArgs_list: list[Custom_OrderArgs], tick_size: ### Order Immediately Matched, Can Put in Offsetting Order Depending on State ### print('******** ORDER APPEND TO LOCAL - MATCHED ********* ') LOCAL_ACTIVE_ORDERS.append(d) - - if d['status'].upper() == 'CONFIRMED': + elif d['status'].upper() == 'CONFIRMED': current_balance = float(LOCAL_TOKEN_BALANCES.get(d['token_id'], 0.00)) if d['side'] == 'BUY': size = float(d['size']) @@ -400,15 +426,17 @@ async def post_order(CLIENT, OrderArgs_list: list[Custom_OrderArgs], tick_size: else: print('******** ORDER APPEND TO LOCAL - LIVE ********* ') LOCAL_ACTIVE_ORDERS.append(d) + elif d['errorMsg'] == "invalid post-only order: order crosses book": + logging.info(f'invalid post-only order: order crosses book. posted: {OrderArgs_list[idx].price}') else: raise ValueError(f'Order entry failed: {d}') logging.info(f'Order Posted Resp: {response}') print(f'Order Posted Resp: {response}') - + return response ### Routes ### -async def no_orders_no_positions_route(): +async def no_orders(entry_or_exit: str = 'ENTRY'): global ORDER_LOCK ### Check for Price Bands ### @@ -427,122 +455,217 @@ async def no_orders_no_positions_route(): logging.info(f'Tgt Diff to Index Outside Limit ({TGT_PX_INDEX_DIFF_THRESH}%); Diff {tgt_px_diff_to_index:.4%}; Index: {ref_px:.2f}; Tgt: {tgt_px:.2f}') return False - ### Check Slope ### slope_bool, slope_side = await slope_decision() if not slope_bool: logging.info('Failed Slope Check') return False - token_id = POLY_CLOB.get('token_id_up', None) if slope_side=='UP' else POLY_CLOB.get('token_id_down', None) - + + token_id_up = POLY_CLOB.get('token_id_up', None) + token_id_down = POLY_CLOB.get('token_id_down', None) ### Order Entry ### - px = float(POLY_CLOB['price'])+0.01 - order = Custom_OrderArgs( - token_id=token_id, - price=px, - size=DEFAULT_ORDER_SIZE, - side=BUY, - max_price = px + CHASE_TO_BUY_CENTS + if slope_side == 'UP': + if entry_or_exit == 'ENTRY': + side = BUY + size = DEFAULT_ORDER_SIZE + up_px = up_px + 0.01 + down_px = down_px - TGT_PROFIT_CENTS + up_post_only = False + down_post_only = False # T + else: # entry_or_exit == 'EXIT' + side = SELL + size = await get_balance_by_token_id(CLIENT=CLIENT, token_id=token_id_up) + up_px = up_px + TGT_PROFIT_CENTS + down_px = down_px - 0.01 + up_post_only = False # T + down_post_only = False + else: # slope_side == 'DOWN' + if entry_or_exit == 'ENTRY': + side = BUY + size = DEFAULT_ORDER_SIZE + up_px = up_px - TGT_PROFIT_CENTS + down_px = down_px + 0.01 + up_post_only = False # T + down_post_only = False + else: + side = SELL + size = await get_balance_by_token_id(CLIENT=CLIENT, token_id=token_id_up) + up_px = up_px - 0.01 + down_px = down_px + TGT_PROFIT_CENTS + up_post_only = False + down_post_only = False # T + + buy_up_leg = Custom_OrderArgs( + token_id=token_id_up, + price=up_px, + size=size, + side=side, + max_price = 0.99, + post_only=up_post_only ) + buy_down_leg = Custom_OrderArgs( + token_id=token_id_down, + price=down_px, + size=size, + side=side, + max_price = 0.99, + post_only=down_post_only + ) + order_list = [buy_up_leg, buy_down_leg] ### ADD CHECK FOR MKT MOVED AWAY FROM OPPORTNITY ### if ORDER_LOCK: - logging.info(f'BUY ORDER BLOCKED BY LOCK: {order}') + logging.info(f'BUY ORDER BLOCKED BY LOCK: {order_list}') else: - logging.info(f'Attempting BUY Order {order}') + logging.info(f'Attempting BUY Order {order_list}') await post_order( CLIENT = CLIENT, tick_size = POLY_CLOB['tick_size'], neg_risk = POLY_CLOB['neg_risk'], - OrderArgs_list = [order] + OrderArgs_list = order_list ) # ORDER_LOCK = ORDER_LOCK + 1 async def active_orders_no_positions_route(): + global LOCAL_ACTIVE_ORDERS + if len(LOCAL_ACTIVE_ORDERS) > 2: logging.critical('More than two active orders, shutting down') await kill_algo() b_c = 0 s_c = 0 + + active_buy_up = False + active_buy_down = False + + active_sell_up = False + active_sell_down = False + for o in LOCAL_ACTIVE_ORDERS: if o['side'] == 'BUY': + if o['token_id']==POLY_CLOB['token_id_up']: + active_buy_up = True + else: + active_buy_down = True + b_c = b_c + 1 elif o['side'] == 'SELL': - s_c = s_c + 1 - if (b_c > 1) or (s_c > 1): - logging.critical(f'More than one active buy or more than one active sell: b_c {b_c}; s_c{s_c}') - await kill_algo() + if o['token_id']==POLY_CLOB['token_id_up']: + active_sell_up = True + else: + active_sell_down = True + s_c = s_c + 1 + + if (b_c > 2) or (s_c > 2): + logging.critical(f'More than two active buys or more than two active sells: b_c {b_c}; s_c{s_c}') + await kill_algo() + for o in LOCAL_ACTIVE_ORDERS: logging.info(f'Working on order ({o['side']}): {o['orderID']}') if o.get('status').upper() == 'MATCHED': - logging.info('Order is matched, awaiting confirm or kickback') - + # logging.info('Order is matched, awaiting confirm or kickback') + if active_buy_up and active_buy_down: + logging.info('BUY UP AND BUY DOWN ACTIVE/MATCHED - WAITING FOR CONFIRMS') + continue + logging.info('Order is matched, ordering inverse side') + order_matched=True elif o.get('status').upper() == 'FAILED': + order_matched=True raise ValueError(f'Trade FAILED after matching: {o}') + elif o.get('status').upper() == 'RETRYING': + order_matched=True + raise ValueError(f'Trade RETRYING after matching: {o}') else: - orig_px = float(o['price']) - orig_size = float(o['size']) - if o['side'] == 'BUY': - if POLY_CLOB['token_id_up'] == o['token_id']: - clob_px = float(POLY_CLOB['price']) - else: - clob_px = float(POLY_CLOB_DOWN['price']) + order_matched = False + + orig_px = float(o['price']) + orig_size = float(o['size']) + + ### BUY + if o['side'] == 'BUY': + if POLY_CLOB['token_id_up'] == o['token_id']: + clob_px = float(POLY_CLOB['price']) + token_id_inverse = POLY_CLOB['token_id_down'] + else: + clob_px = float(POLY_CLOB_DOWN['price']) + token_id_inverse = POLY_CLOB['token_id_up'] - if clob_px >= orig_px: + if (clob_px >= orig_px) or order_matched: + if (clob_px >= orig_px): logging.info(f"Market px: ({clob_px} is above buy order px: {orig_px:.2f})") - if o.get('max_price', 0) > clob_px: + + if (o.get('max_price', 0) > clob_px) or order_matched: + if (o.get('max_price', 0) > clob_px): logging.info(f"Market px: ({clob_px} has moved too far away from original target, cancelling and resetting algo: {o.get('max_price', 0) :.2f})") + if not order_matched: order_matched = await cancel_single_order_by_id(CLIENT=CLIENT, order_id=o['orderID']) - if order_matched: - o['status'] = 'MATCHED' - else: - px = orig_px+0.01 - - await post_order( - CLIENT = CLIENT, - tick_size = POLY_CLOB['tick_size'], - neg_risk = POLY_CLOB['neg_risk'], - OrderArgs_list = [Custom_OrderArgs( - token_id=o['token_id'], - price=px, - size=orig_size, - side=BUY, - max_price=o['max_price'] - - )] - ) + if order_matched: + o['status'] = 'MATCHED' + if order_matched and (not (active_buy_up and active_buy_down)): + logging.info('BUY Order Matched Immediately, Ordering Inverse BUY') + token_id = token_id_inverse + px = 1 - (orig_px+TGT_PROFIT_CENTS) + if clob_px > px: + px = clob_px + 0.01 + # max_price = px + CHASE_TO_BUY_CENTS + max_price = px + post_only = False + elif order_matched and (active_buy_up and active_buy_down): + logging.info('BUY UP AND BUY DOWN MATCHED - WAITING FOR CONFIRMS (IN LOOP)') + continue else: - await cancel_single_order_by_id(CLIENT=CLIENT, order_id=o['orderID']) - elif o['side'] == 'SELL': - if POLY_CLOB['token_id_up'] == o['token_id']: - clob_px = float(POLY_CLOB['price']) + token_id = o['token_id'] + px = clob_px+0.01 + max_price = o['max_price'] + post_only = False + + await post_order( + CLIENT = CLIENT, + tick_size = POLY_CLOB['tick_size'], + neg_risk = POLY_CLOB['neg_risk'], + OrderArgs_list = [Custom_OrderArgs( + token_id=token_id, + price=px, + size=orig_size, + side=BUY, + max_price=max_price, + post_only=post_only + + )] + ) else: - clob_px = float(POLY_CLOB_DOWN['price']) + await cancel_single_order_by_id(CLIENT=CLIENT, order_id=o['orderID']) + ### SELL + elif o['side'] == 'SELL': + if POLY_CLOB['token_id_up'] == o['token_id']: + clob_px = float(POLY_CLOB['price']) + else: + clob_px = float(POLY_CLOB_DOWN['price']) + + if clob_px <= orig_px: + logging.info(f"Market px: ({clob_px} is below sell order px: {orig_px:.2f})") - if clob_px <= orig_px: - logging.info(f"Market px: ({clob_px} is below sell order px: {orig_px:.2f})") - - order_filled = await cancel_single_order_by_id(CLIENT=CLIENT, order_id=o['orderID']) - if not order_filled: - await post_order( - CLIENT = CLIENT, - tick_size = POLY_CLOB['tick_size'], - neg_risk = POLY_CLOB['neg_risk'], - OrderArgs_list = [Custom_OrderArgs( - token_id=o['token_id'], - price=orig_px-0.01, - size=orig_size, - side=SELL, - max_price = 0.00 - )] - ) + order_filled = await cancel_single_order_by_id(CLIENT=CLIENT, order_id=o['orderID']) + if not order_filled: + await post_order( + CLIENT = CLIENT, + tick_size = POLY_CLOB['tick_size'], + neg_risk = POLY_CLOB['neg_risk'], + OrderArgs_list = [Custom_OrderArgs( + token_id=o['token_id'], + price=orig_px-0.01, + size=orig_size, + side=SELL, + max_price = 0.00 + )] + ) async def no_orders_active_positions_route(): @@ -587,7 +710,7 @@ async def no_orders_active_positions_route(): async def active_orders_active_positions_route(): pass -async def kill_algo(): +async def kill_algo(msg: str = 'No kill msg provided'): logging.info('Killing algo...') await cancel_all_orders(CLIENT=CLIENT) await flatten_open_positions( @@ -595,8 +718,8 @@ async def kill_algo(): token_id_up = POLY_CLOB.get('token_id_up', None), token_id_down = POLY_CLOB.get('token_id_down', None), ) - logging.info('...algo killed') - raise Exception('Algo Killed') + logging.info(f'...algo killed: {msg}') + raise Exception(f'Algo Killed: {msg}') async def run_algo(): global POLY_BINANCE @@ -617,8 +740,13 @@ async def run_algo(): print(f'token_id_up: {POLY_CLOB.get('token_id_up', None)}') print(f'token_id_down: {POLY_CLOB.get('token_id_down', None)}') + POLY_CLOB = json.loads(VAL_KEY.get('poly_5min_btcusd')) + ### Check for missing target px (Poly 5min Target BTC Px Target) ### + if POLY_CLOB.get('target_price', 0) <= 1.00: + kill_algo('') + ACTIVE_BALANCES_EXIST = await check_for_open_positions( CLIENT=CLIENT, token_id_up=POLY_CLOB.get('token_id_up', None), @@ -641,12 +769,13 @@ async def run_algo(): ### DO THIS TO AVOID DELAY WITH FILL CONFIRMS ### Manage Local vs User Stream Orders ### - print(f'LOCAL_ACTIVE_ORDERS: {LOCAL_ACTIVE_ORDERS}') + # print(f'LOCAL_ACTIVE_ORDERS: {LOCAL_ACTIVE_ORDERS}') + # print(f'USER_TRADES: {USER_TRADES}') for idx, o in enumerate(LOCAL_ACTIVE_ORDERS): user_order = next((item for item in USER_ORDERS if item["id"] == o['orderID']), None) user_trade = next( ( item for item in USER_TRADES if ( o['orderID'] == item['taker_order_id'] ) or ( o["orderID"] == json.loads(item['maker_orders'])[0]['order_id'] ) ), None ) - print(f'USER TRADE: {user_trade}') + print(f'*****USER TRADE: {user_trade}') if user_trade is not None: trade_status = str(user_trade['status']).upper() @@ -676,43 +805,24 @@ async def run_algo(): logging.info('Order FILLED!') elif trade_status == 'MATCHED': logging.info(f'Order Matched...awaiting confirm: {trade_status}') + elif trade_status == 'MINED': + logging.info(f'Order Mined ...awaiting confirm: {trade_status}') else: logging.info(f'Trade status but not filled: trade= {user_trade}; order={o}') elif user_order is not None: order_status = str(user_order['status']).upper() + o['status'] = order_status logging.info(f'Updated Order Status: {o['status']} --> {order_status}; {o['orderID']}') - # LOCAL_ACTIVE_ORDERS[idx]['status'] = order_status - # if order_status == 'MATCHED': - # LOCAL_ACTIVE_ORDERS.pop(idx) - - # token_id = user_order['asset_id'] - # current_balance = float(LOCAL_TOKEN_BALANCES.get(token_id, 0.00)) - - # if user_order['side'] == 'BUY': - # size = float(user_order['size_matched']) - # else: - # size = float(user_order['size_matched']) * -1 - # LOCAL_TOKEN_BALANCES[token_id] = current_balance + size + if order_status == 'MATCHED': + logging.info('Order MATCHED, awaiting confirm') - # # px = user_order['price'] - # # LOCAL_ACTIVE_POSITIONS.append({ - # # 'token_id': token_id, - # # 'order_id': o['orderID'], - # # 'associate_trades': user_order['associate_trades'], - # # 'size_matched': user_order['size_matched'], - # # 'price': px, - # # 'timestamp_value': user_order['timestamp'], - # # }) - # logging.info('Order FILLED!') - if order_status == 'CANCELED': + elif order_status == 'CANCELED': LOCAL_ACTIVE_ORDERS.pop(idx) logging.info('Order Canceled') else: - logging.info('Order Live or Trade Awaiting Confirm') - - ### UPDATES CAN COME THRU EITHER ORDER OR TRADE CHANNELS - NEED TO UPDATE TO HANDLE TRADE CHANNLE + logging.info('Order Live') token_id_up = POLY_CLOB.get('token_id_up', 0) token_id_down = POLY_CLOB.get('token_id_down', 0) @@ -749,7 +859,7 @@ async def run_algo(): ### Execution Route ### if not(LOCAL_ACTIVE_ORDERS) and not(ACTIVE_BALANCES_EXIST): # No Orders, No Positions print('ROUTE: no_orders_no_positions_route') - await no_orders_no_positions_route() + await no_orders(entry_or_exit='ENTRY') ### Open Orders Route ### elif LOCAL_ACTIVE_ORDERS and not(ACTIVE_BALANCES_EXIST): # Orders, No Positions @@ -758,13 +868,13 @@ async def run_algo(): ### Open Positions Route ### elif not(LOCAL_ACTIVE_ORDERS) and ACTIVE_BALANCES_EXIST: # No Orders, Positions - print('ROUTE: no_orders_no_positions_route') - await no_orders_active_positions_route() + print('ROUTE: no_orders_route_active_positions_route') + await no_orders(entry_or_exit='EXIT') ### Open Orders and Open Positions Route ### else: - print('ROUTE: active_orders_active_positions_route') - await active_orders_no_positions_route() # Orders and Positions + print('ROUTE: active_orders_active_positions_route - OFF') + # await active_orders_no_positions_route() # Orders and Positions print(f'__________________________ (Algo Engine ms: {(time.time() - loop_start)*1000})') time.sleep(1) diff --git a/ws_user.py b/ws_user.py index fbdda9e..0d68d20 100644 --- a/ws_user.py +++ b/ws_user.py @@ -299,7 +299,7 @@ async def polymarket_stream(): event_type = data.get('event_type', None) match event_type: case 'trade': - logging.info(f'TRADE: {data}') + # logging.info(f'TRADE: {data}') # trade_status = data.get('status') # match trade_status: # Raise TELEGRAM ALERT ??? # case 'MATCHED': @@ -326,9 +326,9 @@ async def polymarket_stream(): LOOKBACK_MIN_TS_MS = ts_arrival-LOCAL_RECENT_TRADES_LOOKBACK_SEC*1000 LOCAL_RECENT_TRADES = [t for t in LOCAL_RECENT_TRADES if t.get('timestamp_arrival', 0) >= LOOKBACK_MIN_TS_MS] - print("---------------------") - print(LOCAL_RECENT_TRADES) - print("---------------------") + # print("---------------------") + # print(LOCAL_RECENT_TRADES) + # print("---------------------") VAL_KEY_OBJ = json.dumps(LOCAL_RECENT_TRADES) # VAL_KEY.publish(VK_CHANNEL, VAL_KEY_OBJ)