saving
This commit is contained in:
808
main.py
Normal file
808
main.py
Normal file
@@ -0,0 +1,808 @@
|
||||
import asyncio
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from typing import AsyncContextManager
|
||||
import traceback
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import requests
|
||||
import talib
|
||||
import valkey
|
||||
from dotenv import load_dotenv
|
||||
from py_clob_client.clob_types import (
|
||||
OrderArgs,
|
||||
OrderType,
|
||||
PartialCreateOrderOptions,
|
||||
PostOrdersArgs,
|
||||
BalanceAllowanceParams
|
||||
)
|
||||
from py_clob_client.order_builder.constants import BUY, SELL
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
from functools import wraps
|
||||
import modules.api as api
|
||||
|
||||
### Custom Order Args ###
|
||||
@dataclass
|
||||
class Custom_OrderArgs(OrderArgs):
|
||||
max_price: float = 0.00
|
||||
|
||||
|
||||
### Database ###
|
||||
CLIENT = None
|
||||
CON: AsyncContextManager | None = None
|
||||
VAL_KEY = None
|
||||
|
||||
### Logging ###
|
||||
load_dotenv()
|
||||
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%)
|
||||
DEFAULT_ORDER_SIZE = 10 # In USDe
|
||||
MIN_ORDER_SIZE = 5 # In USDe
|
||||
TGT_PROFIT_CENTS = 0.02
|
||||
CHASE_TO_BUY_CENTS = 0.05
|
||||
MAX_ALLOWED_POLY_PX = 0.90
|
||||
|
||||
### GLOBALS ###
|
||||
ORDER_LOCK = 0
|
||||
|
||||
SLUG_END_TIME = 0
|
||||
|
||||
FREE_CASH: float = 0
|
||||
|
||||
POLY_BINANCE = {}
|
||||
POLY_REF = {}
|
||||
POLY_CLOB = {}
|
||||
POLY_CLOB_DOWN = {}
|
||||
USER_TRADES = {}
|
||||
USER_ORDERS = {}
|
||||
SLOPE_HIST = []
|
||||
|
||||
LOCAL_ACTIVE_ORDERS = []
|
||||
LOCAL_TOKEN_BALANCES = {}
|
||||
# LOCAL_ACTIVE_POSITIONS = []
|
||||
|
||||
ACTIVE_BALANCES_EXIST = False ### REMOVE
|
||||
|
||||
|
||||
|
||||
### Decorators ###
|
||||
def async_timeit(func):
|
||||
@wraps(func)
|
||||
async def wrapper(*args, **kwargs):
|
||||
start_time = time.perf_counter()
|
||||
try:
|
||||
return await func(*args, **kwargs)
|
||||
finally:
|
||||
end_time = time.perf_counter()
|
||||
total_time = (end_time - start_time)*1000
|
||||
print(f"Function '{func.__name__}' executed in {total_time:.4f} ms")
|
||||
|
||||
return wrapper
|
||||
|
||||
### Database Funcs ###
|
||||
# @async_timeit
|
||||
async def create_executions_orders_table(
|
||||
CON: AsyncContextManager,
|
||||
engine: str = 'mysql', # mysql | duckdb
|
||||
) -> None:
|
||||
if CON is None:
|
||||
logging.info("NO DB CONNECTION, SKIPPING Create Statements")
|
||||
else:
|
||||
if engine == 'mysql':
|
||||
logging.info('Creating Table if Does Not Exist: executions_orders')
|
||||
await CON.execute(text("""
|
||||
CREATE TABLE IF NOT EXISTS executions_orders (
|
||||
timestamp_sent BIGINT,
|
||||
token_id VARCHAR(100),
|
||||
limit_price DOUBLE,
|
||||
size DOUBLE,
|
||||
side VARCHAR(8),
|
||||
order_type VARCHAR(8),
|
||||
post_only BOOL,
|
||||
resp_errorMsg VARCHAR(100),
|
||||
resp_orderID VARCHAR(100),
|
||||
resp_takingAmount DOUBLE,
|
||||
resp_makingAmount DOUBLE,
|
||||
resp_status VARCHAR(20),
|
||||
resp_success BOOL
|
||||
);
|
||||
"""))
|
||||
await CON.commit()
|
||||
else:
|
||||
raise ValueError('Only MySQL engine is implemented')
|
||||
|
||||
# @async_timeit
|
||||
async def insert_executions_orders_table(
|
||||
timestamp_sent: int,
|
||||
token_id: str,
|
||||
limit_price: float,
|
||||
size: float,
|
||||
side: str,
|
||||
order_type: str,
|
||||
post_only: bool,
|
||||
resp_errorMsg: str,
|
||||
resp_orderID: str,
|
||||
resp_takingAmount: float,
|
||||
resp_makingAmount: float,
|
||||
resp_status: str,
|
||||
resp_success: bool,
|
||||
CON: AsyncContextManager,
|
||||
engine: str = 'mysql', # mysql | duckdb
|
||||
) -> None:
|
||||
params={
|
||||
'timestamp_sent': timestamp_sent,
|
||||
'token_id': token_id,
|
||||
'limit_price': limit_price,
|
||||
'size': size,
|
||||
'side': side,
|
||||
'order_type': order_type,
|
||||
'post_only': post_only,
|
||||
'resp_errorMsg': resp_errorMsg,
|
||||
'resp_orderID': resp_orderID,
|
||||
'resp_takingAmount': resp_takingAmount,
|
||||
'resp_makingAmount': resp_makingAmount,
|
||||
'resp_status': resp_status,
|
||||
'resp_success': resp_success,
|
||||
}
|
||||
if CON is None:
|
||||
logging.info("NO DB CONNECTION, SKIPPING Insert Statements")
|
||||
else:
|
||||
if engine == 'mysql':
|
||||
await CON.execute(text("""
|
||||
INSERT INTO executions_orders
|
||||
(
|
||||
timestamp_sent,
|
||||
token_id,
|
||||
limit_price,
|
||||
size,
|
||||
side,
|
||||
order_type,
|
||||
post_only,
|
||||
resp_errorMsg,
|
||||
resp_orderID,
|
||||
resp_takingAmount,
|
||||
resp_makingAmount,
|
||||
resp_status,
|
||||
resp_success
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
:timestamp_sent,
|
||||
:token_id,
|
||||
:limit_price,
|
||||
:size,
|
||||
:side,
|
||||
:order_type,
|
||||
:post_only,
|
||||
:resp_errorMsg,
|
||||
:resp_orderID,
|
||||
:resp_takingAmount,
|
||||
:resp_makingAmount,
|
||||
:resp_status,
|
||||
:resp_success
|
||||
)
|
||||
"""),
|
||||
parameters=params
|
||||
)
|
||||
await CON.commit()
|
||||
else:
|
||||
raise ValueError('Only MySQL engine is implemented')
|
||||
|
||||
### Functions ###
|
||||
# @async_timeit
|
||||
async def slope_decision() -> list[bool, str]:
|
||||
hist_trades = np.array(POLY_BINANCE.get('hist_trades', []))
|
||||
|
||||
if ( np.max(hist_trades[:, 0] )*1000 ) - ( np.min(hist_trades[:, 0])*1000 ) < 5:
|
||||
logging.info('Max - Min Trade In History is < 5 Seconds Apart')
|
||||
return False, ''
|
||||
|
||||
last_px = POLY_BINANCE['value']
|
||||
last_px_ts = POLY_BINANCE['timestamp_value']
|
||||
|
||||
ts_min_1_sec = last_px_ts - 1000
|
||||
price_min_1_sec_index = (np.abs(hist_trades[:, 0] - ts_min_1_sec)).argmin()
|
||||
price_min_1_sec = hist_trades[:, 1][price_min_1_sec_index]
|
||||
|
||||
ts_min_5_sec = last_px_ts - 5000
|
||||
price_min_5_sec_index = (np.abs(hist_trades[:, 0] - ts_min_5_sec)).argmin()
|
||||
price_min_5_sec = hist_trades[:, 1][price_min_5_sec_index]
|
||||
|
||||
slope = (last_px - price_min_1_sec) / price_min_1_sec
|
||||
slope_5 = (last_px - price_min_5_sec) / price_min_5_sec
|
||||
SLOPE_HIST.append(slope)
|
||||
|
||||
# print(f'Avg Binance: {np.mean(hist_trades[:, 1])}')
|
||||
# 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%}')
|
||||
slope_1_buy = abs(slope) >= ( SLOPE_YES_THRESH / 100)
|
||||
slope_5_buy = abs(slope_5) >= ( SLOPE_YES_THRESH / 100)
|
||||
|
||||
print(f'SLOPE_1: {slope:.4%} == {slope_1_buy}; SLOPE_5: {slope_5:.4%} == {slope_5_buy};')
|
||||
|
||||
### DECISION ###
|
||||
if slope_1_buy and slope_5_buy:
|
||||
print(f'🤑🤑🤑🤑🤑🤑🤑🤑🤑🤑 Slope: {slope_5:.4%};')
|
||||
side = 'UP' if slope > 0.00 else 'DOWN'
|
||||
return True, side
|
||||
else:
|
||||
return False, ''
|
||||
|
||||
# @async_timeit
|
||||
async def cancel_all_orders(CLIENT):
|
||||
logging.info('Attempting to Cancel All Orders')
|
||||
cxl_resp = CLIENT.cancel_all()
|
||||
if bool(cxl_resp.get('not_canceled', True)):
|
||||
logging.warning(f'*** Cancel Request FAILED, trying again and shutting down: {cxl_resp}')
|
||||
cxl_resp = CLIENT.cancel_all()
|
||||
raise Exception('*** Cancel Request FAILED')
|
||||
logging.info(f'Cancel Successful: {cxl_resp}')
|
||||
|
||||
# @async_timeit
|
||||
async def cancel_single_order_by_id(CLIENT, order_id):
|
||||
global LOCAL_ACTIVE_ORDERS
|
||||
|
||||
logging.info(f'Attempting to Cancel Single Order: {order_id}')
|
||||
cxl_resp = CLIENT.cancel(order_id=order_id)
|
||||
|
||||
for idx, o in enumerate(LOCAL_ACTIVE_ORDERS):
|
||||
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":
|
||||
logging.info(f'Cancel request failed b/c already matched: {cxl_resp}')
|
||||
return False
|
||||
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
|
||||
else:
|
||||
logging.warning(f'*** Cancel Request FAILED, shutting down: {cxl_resp}')
|
||||
raise Exception('*** Cancel Request FAILED - SHUTDONW')
|
||||
else:
|
||||
LOCAL_ACTIVE_ORDERS.pop(idx)
|
||||
logging.info(f'Cancel Successful: {cxl_resp}')
|
||||
return False
|
||||
|
||||
# @async_timeit
|
||||
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)
|
||||
|
||||
### Submit orders to flatten outstanding balances ###
|
||||
if up > MIN_ORDER_SIZE:
|
||||
logging.info(f'Flattening Up Position: {up}')
|
||||
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_up,
|
||||
price=float(POLY_CLOB['price'])-0.01,
|
||||
size=up,
|
||||
side=SELL,
|
||||
)]
|
||||
)
|
||||
if down > MIN_ORDER_SIZE:
|
||||
logging.info(f'Flattening Down Position: {down}')
|
||||
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_down,
|
||||
price=float(POLY_CLOB_DOWN['price'])-0.01,
|
||||
size=down,
|
||||
side=SELL,
|
||||
|
||||
)]
|
||||
)
|
||||
|
||||
# @async_timeit
|
||||
async def get_balance_by_token_id(CLIENT, token_id):
|
||||
collateral = CLIENT.get_balance_allowance(
|
||||
BalanceAllowanceParams(
|
||||
asset_type='CONDITIONAL',
|
||||
token_id=token_id,
|
||||
)
|
||||
)
|
||||
return int(collateral['balance']) / 1_000_000
|
||||
|
||||
# @async_timeit
|
||||
async def get_usde_balance(CLIENT):
|
||||
collateral = CLIENT.get_balance_allowance(
|
||||
BalanceAllowanceParams(
|
||||
asset_type='COLLATERAL'
|
||||
)
|
||||
)
|
||||
return int(collateral['balance']) / 1_000_000
|
||||
|
||||
@async_timeit
|
||||
async def check_for_open_positions(CLIENT, token_id_up, token_id_down):
|
||||
global LOCAL_TOKEN_BALANCES
|
||||
|
||||
if token_id_up is None or token_id_down is None:
|
||||
logging.critical('Token Id is None, Exiting')
|
||||
raise ValueError('Token Id is None, Exiting')
|
||||
# return False
|
||||
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)
|
||||
|
||||
LOCAL_TOKEN_BALANCES = {
|
||||
token_id_up: up if up else 0,
|
||||
token_id_down: down if down else 0,
|
||||
}
|
||||
|
||||
logging.info(f'LOCAL_TOKEN_BALANCES: {LOCAL_TOKEN_BALANCES}')
|
||||
|
||||
if ( abs(up) > 0 ) or ( abs(down) > 0 ):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@async_timeit
|
||||
async def post_order(CLIENT, OrderArgs_list: list[Custom_OrderArgs], tick_size: float | str, neg_risk: bool):
|
||||
global LOCAL_ACTIVE_ORDERS
|
||||
global LOCAL_TOKEN_BALANCES
|
||||
|
||||
orders = []
|
||||
for oa in OrderArgs_list:
|
||||
orders.append(
|
||||
PostOrdersArgs(
|
||||
order=CLIENT.create_order(
|
||||
order_args=oa,
|
||||
options=PartialCreateOrderOptions(
|
||||
tick_size=str(tick_size),
|
||||
neg_risk=neg_risk
|
||||
),
|
||||
),
|
||||
orderType=OrderType.GTC,
|
||||
postOnly=False,
|
||||
),
|
||||
)
|
||||
|
||||
### POST
|
||||
response = CLIENT.post_orders(orders)
|
||||
for idx, d in enumerate(response):
|
||||
if d['errorMsg'] == '':
|
||||
d['token_id'] = OrderArgs_list[idx].token_id
|
||||
d['price'] = OrderArgs_list[idx].price
|
||||
d['size'] = OrderArgs_list[idx].size
|
||||
d['side'] = str(OrderArgs_list[idx].side).upper()
|
||||
|
||||
if d['status'].upper() =='MATCHED':
|
||||
### 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':
|
||||
current_balance = float(LOCAL_TOKEN_BALANCES.get(d['token_id'], 0.00))
|
||||
if d['side'] == 'BUY':
|
||||
size = float(d['size'])
|
||||
else:
|
||||
size = float(d['size']) * -1
|
||||
|
||||
LOCAL_TOKEN_BALANCES[d['token_id']] = current_balance + size
|
||||
print('******** TRADE FILLED, BAL UPDATED ********* ')
|
||||
else:
|
||||
print('******** ORDER APPEND TO LOCAL - LIVE ********* ')
|
||||
LOCAL_ACTIVE_ORDERS.append(d)
|
||||
else:
|
||||
raise ValueError(f'Order entry failed: {d}')
|
||||
|
||||
logging.info(f'Order Posted Resp: {response}')
|
||||
print(f'Order Posted Resp: {response}')
|
||||
|
||||
|
||||
### Routes ###
|
||||
async def no_orders_no_positions_route():
|
||||
global ORDER_LOCK
|
||||
|
||||
### Check for Price Bands ###
|
||||
up_px = float(POLY_CLOB.get('price', 0))
|
||||
down_px = float(POLY_CLOB_DOWN.get('price', 0))
|
||||
|
||||
if (up_px > MAX_ALLOWED_POLY_PX) or (down_px > MAX_ALLOWED_POLY_PX):
|
||||
logging.info(f'Outside max allowed px: {MAX_ALLOWED_POLY_PX}')
|
||||
return False
|
||||
|
||||
### Check for Index vs. Target Px ###
|
||||
tgt_px = float(POLY_CLOB.get('target_price', 0))
|
||||
ref_px = float(POLY_REF.get('value'))
|
||||
tgt_px_diff_to_index = ( abs( tgt_px - ref_px ) / tgt_px)
|
||||
if tgt_px_diff_to_index > (TGT_PX_INDEX_DIFF_THRESH / 100):
|
||||
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)
|
||||
|
||||
|
||||
### 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
|
||||
)
|
||||
|
||||
### ADD CHECK FOR MKT MOVED AWAY FROM OPPORTNITY ###
|
||||
|
||||
if ORDER_LOCK:
|
||||
logging.info(f'BUY ORDER BLOCKED BY LOCK: {order}')
|
||||
|
||||
else:
|
||||
logging.info(f'Attempting BUY Order {order}')
|
||||
await post_order(
|
||||
CLIENT = CLIENT,
|
||||
tick_size = POLY_CLOB['tick_size'],
|
||||
neg_risk = POLY_CLOB['neg_risk'],
|
||||
OrderArgs_list = [order]
|
||||
)
|
||||
# ORDER_LOCK = ORDER_LOCK + 1
|
||||
|
||||
async def active_orders_no_positions_route():
|
||||
if len(LOCAL_ACTIVE_ORDERS) > 2:
|
||||
logging.critical('More than two active orders, shutting down')
|
||||
await kill_algo()
|
||||
b_c = 0
|
||||
s_c = 0
|
||||
for o in LOCAL_ACTIVE_ORDERS:
|
||||
if o['side'] == 'BUY':
|
||||
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()
|
||||
|
||||
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')
|
||||
|
||||
elif o.get('status').upper() == 'FAILED':
|
||||
raise ValueError(f'Trade FAILED 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'])
|
||||
|
||||
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:
|
||||
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})")
|
||||
|
||||
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']
|
||||
|
||||
)]
|
||||
)
|
||||
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'])
|
||||
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})")
|
||||
|
||||
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():
|
||||
'''
|
||||
Succesful Buy, now neeed to take profit and exit
|
||||
'''
|
||||
global LOCAL_TOKEN_BALANCES
|
||||
|
||||
OrderArgs_list = []
|
||||
|
||||
logging.warning(f'LOCAL_TOKEN_BALANCES: {LOCAL_TOKEN_BALANCES}')
|
||||
|
||||
for k, v in LOCAL_TOKEN_BALANCES.items():
|
||||
size = await get_balance_by_token_id(CLIENT=CLIENT, token_id=k)
|
||||
if size >= MIN_ORDER_SIZE:
|
||||
if POLY_CLOB['token_id_up'] == k:
|
||||
clob_px = float(POLY_CLOB['price'])
|
||||
else:
|
||||
clob_px = float(POLY_CLOB_DOWN['price'])
|
||||
|
||||
OrderArgs_list.append(
|
||||
Custom_OrderArgs(
|
||||
token_id=k,
|
||||
price=clob_px + TGT_PROFIT_CENTS,
|
||||
size=size,
|
||||
side='SELL',
|
||||
)
|
||||
)
|
||||
else:
|
||||
LOCAL_TOKEN_BALANCES[k] = 0.00
|
||||
logging.info(f'Wants to flatten small amount, skipping: {v}')
|
||||
|
||||
if OrderArgs_list:
|
||||
logging.info(f'Posting orders to close: {OrderArgs_list}')
|
||||
await post_order(
|
||||
CLIENT = CLIENT,
|
||||
tick_size = POLY_CLOB['tick_size'],
|
||||
neg_risk = POLY_CLOB['neg_risk'],
|
||||
OrderArgs_list = OrderArgs_list
|
||||
)
|
||||
|
||||
async def active_orders_active_positions_route():
|
||||
pass
|
||||
|
||||
async def kill_algo():
|
||||
logging.info('Killing algo...')
|
||||
await cancel_all_orders(CLIENT=CLIENT)
|
||||
await flatten_open_positions(
|
||||
CLIENT=CLIENT,
|
||||
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')
|
||||
|
||||
async def run_algo():
|
||||
global POLY_BINANCE
|
||||
global POLY_REF
|
||||
global POLY_CLOB
|
||||
global POLY_CLOB_DOWN
|
||||
global USER_TRADES
|
||||
global USER_ORDERS
|
||||
|
||||
global SLOPE_HIST
|
||||
global ACTIVE_BALANCES_EXIST
|
||||
|
||||
global LOCAL_ACTIVE_ORDERS
|
||||
global LOCAL_TOKEN_BALANCES
|
||||
# global LOCAL_ACTIVE_POSITIONS
|
||||
|
||||
|
||||
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'))
|
||||
|
||||
ACTIVE_BALANCES_EXIST = await check_for_open_positions(
|
||||
CLIENT=CLIENT,
|
||||
token_id_up=POLY_CLOB.get('token_id_up', None),
|
||||
token_id_down=POLY_CLOB.get('token_id_down', None),
|
||||
)
|
||||
|
||||
try:
|
||||
while True:
|
||||
loop_start = time.time()
|
||||
print('__________Start___________')
|
||||
POLY_BINANCE = json.loads(VAL_KEY.get('poly_binance_btcusd'))
|
||||
POLY_REF = json.loads(VAL_KEY.get('poly_rtds_cl_btcusd'))
|
||||
POLY_CLOB = json.loads(VAL_KEY.get('poly_5min_btcusd'))
|
||||
POLY_CLOB_DOWN = json.loads(VAL_KEY.get('poly_5min_btcusd_down'))
|
||||
USER_TRADES = json.loads(VAL_KEY.get('poly_user_trades'))
|
||||
USER_ORDERS = VAL_KEY.get('poly_user_orders')
|
||||
USER_ORDERS = json.loads(USER_ORDERS) if USER_ORDERS is not None else []
|
||||
|
||||
### CHANGE METHOD FROM BUY-SELL TO BUY UP - BUY DOWN
|
||||
### DO THIS TO AVOID DELAY WITH FILL CONFIRMS
|
||||
|
||||
### Manage Local vs User Stream Orders ###
|
||||
print(f'LOCAL_ACTIVE_ORDERS: {LOCAL_ACTIVE_ORDERS}')
|
||||
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}')
|
||||
|
||||
if user_trade is not None:
|
||||
trade_status = str(user_trade['status']).upper()
|
||||
logging.info(f'Updated Trade Status: {o['status']} --> {trade_status}; {o['orderID']}')
|
||||
if trade_status == 'CONFIRMED':
|
||||
LOCAL_ACTIVE_ORDERS.pop(idx)
|
||||
|
||||
token_id = user_trade['asset_id']
|
||||
current_balance = float(LOCAL_TOKEN_BALANCES.get(token_id, 0.00))
|
||||
|
||||
if user_trade['side'] == 'BUY':
|
||||
size = float(user_trade['size'])
|
||||
else:
|
||||
size = float(user_trade['size']) * -1
|
||||
|
||||
LOCAL_TOKEN_BALANCES[token_id] = current_balance + size
|
||||
|
||||
# px = user_trade['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!')
|
||||
elif trade_status == 'MATCHED':
|
||||
logging.info(f'Order Matched...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()
|
||||
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
|
||||
|
||||
# # 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':
|
||||
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
|
||||
|
||||
token_id_up = POLY_CLOB.get('token_id_up', 0)
|
||||
token_id_down = POLY_CLOB.get('token_id_down', 0)
|
||||
|
||||
if (token_id_up is None) or (token_id_down is None):
|
||||
print('Missing Token Ids for Market, sleeping 1 sec and retrying...')
|
||||
time.sleep(1)
|
||||
ACTIVE_BALANCES_EXIST = {}
|
||||
continue
|
||||
else:
|
||||
if (LOCAL_TOKEN_BALANCES.get(token_id_up) is None):
|
||||
LOCAL_TOKEN_BALANCES[token_id_up] = 0.00
|
||||
if (LOCAL_TOKEN_BALANCES.get(token_id_down) is None):
|
||||
LOCAL_TOKEN_BALANCES[token_id_down] = 0.00
|
||||
ACTIVE_BALANCES_EXIST = (LOCAL_TOKEN_BALANCES.get(token_id_up) > 0) or (LOCAL_TOKEN_BALANCES.get(token_id_down) > 0)
|
||||
|
||||
### Check for Endtime Buffer ###
|
||||
if ENDTIME_BUFFER_SEC > POLY_CLOB.get('sec_remaining', 0):
|
||||
if LOCAL_ACTIVE_ORDERS:
|
||||
print('buffer zone - orders cancel')
|
||||
await cancel_all_orders(CLIENT=CLIENT)
|
||||
if ACTIVE_BALANCES_EXIST:
|
||||
print('buffer zone - flatten positions')
|
||||
await flatten_open_positions(
|
||||
CLIENT=CLIENT,
|
||||
token_id_up = POLY_CLOB.get('token_id_up', None),
|
||||
token_id_down = POLY_CLOB.get('token_id_down', None),
|
||||
)
|
||||
|
||||
print('buffer zone, sleeping until next session')
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
### 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()
|
||||
|
||||
### Open Orders Route ###
|
||||
elif LOCAL_ACTIVE_ORDERS and not(ACTIVE_BALANCES_EXIST): # Orders, No Positions
|
||||
print('ROUTE: active_orders_no_positions_route')
|
||||
await active_orders_no_positions_route()
|
||||
|
||||
### 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()
|
||||
|
||||
### Open Orders and Open Positions Route ###
|
||||
else:
|
||||
print('ROUTE: active_orders_active_positions_route')
|
||||
await active_orders_no_positions_route() # Orders and Positions
|
||||
|
||||
print(f'__________________________ (Algo Engine ms: {(time.time() - loop_start)*1000})')
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
print('...algo stopped')
|
||||
await cancel_all_orders(CLIENT=CLIENT)
|
||||
except Exception as e:
|
||||
logging.critical(f'*** ALGO ENGINE CRASHED: {e}')
|
||||
logging.error(traceback.format_exc())
|
||||
await cancel_all_orders(CLIENT=CLIENT)
|
||||
|
||||
|
||||
async def main():
|
||||
global CLIENT
|
||||
global VAL_KEY
|
||||
global CON
|
||||
|
||||
CLIENT = api.create_client()
|
||||
VAL_KEY = valkey.Valkey(host='localhost', port=6379, db=0, decode_responses=True)
|
||||
engine = create_async_engine('mysql+asyncmy://root:pwd@localhost/polymarket')
|
||||
|
||||
async with engine.connect() as CON:
|
||||
await create_executions_orders_table(CON=CON)
|
||||
await run_algo()
|
||||
|
||||
if __name__ == '__main__':
|
||||
START_TIME = round(datetime.now().timestamp()*1000)
|
||||
|
||||
logging.info(f'Log FilePath: {LOG_FILEPATH}')
|
||||
|
||||
logging.basicConfig(
|
||||
force=True,
|
||||
filename=LOG_FILEPATH,
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
filemode='w'
|
||||
)
|
||||
logging.info(f"STARTED: {START_TIME}")
|
||||
|
||||
asyncio.run(main())
|
||||
|
||||
Reference in New Issue
Block a user