Files
econ_emt/econ/exchange.py
2023-01-26 14:58:41 +01:00

252 lines
8.7 KiB
Python

_bal="balance"
from lightmatchingengine.lightmatchingengine import LightMatchingEngine,Side,Trade,Order,OrderBook
class Exchange():
"""
Basic Commodity exchange.
"""
def __init__(self) -> None:
self.reset()
pass
def reset(self):
self.account={}
self.escrow={}
self.lme=LightMatchingEngine()
self.order_account_map={}
self.orders={}
self.executed_trades=[]
self.order_trades_map={}
self.market_rate={}
self.best_ask={}
self.best_bid={}
self.total_demand={}
self.demand={}
self.total_supply={}
self.supply={}
def add_to_account(self,account_id,resource,amount):
"""
Adds resources to account escrow
"""
#check if account exists
if account_id not in self.account:
self.account[account_id]={_bal: 0}
#check if ressource exists
if resource not in self.account[account_id]:
self.account[account_id][resource]=0
self.account[account_id][resource]+=amount
def remove_from_account(self,account_id,resource,amount) -> int:
"""
Remove resources from account. Returns amount retrieved
"""
#check if account exists
if account_id not in self.account:
self.account[account_id]={_bal: 0}
#check if ressource exists
if resource not in self.account[account_id]:
self.account[account_id][resource]=0
ret=amount
remain=self.account[account_id][resource]-amount
if remain<0:
ret=self.account[account_id][resource]
self.account[account_id][resource]=0
else:
self.account[account_id][resource]-=amount
return ret
def _move_to_escrow(self,account_id,resource,amount):
"""
Adds resources from account to escrow. Return true if done.
"""
if not self.check_account_resource(account_id,resource,amount):
return False
#check if account exists
if account_id not in self.escrow:
self.escrow[account_id]={_bal: 0}
#check if ressource exists
if resource not in self.escrow[account_id]:
self.escrow[account_id][resource]=0
self.escrow[account_id][resource]+=amount
self.account[account_id][resource]-=amount
return True
def _move_to_account(self,account_id,resource,amount):
"""
Move resources from escrow to account.
"""
ret=amount
self.escrow[account_id][resource]-=amount
if resource not in self.account[account_id]:
self.account[account_id][resource]=0
self.account[account_id][resource]+=amount
def check_account_resource(self,account_id,resource,amount):
"""
Checks if account has sufficient resources in inventory
"""
if account_id not in self.account:
self.account[account_id]={_bal: 0}
#check if ressource exists
if resource not in self.account[account_id]:
self.account[account_id][resource]=0
if self.account[account_id][resource]>=amount:
return True
return False
def submit_order(self,account_id,resource,amount,price,side :int) -> Order:
"""
Submits an order to the exchange if resources are sufficent.
Price is price per single item.
Returns an order if the order is active. Returns None if submit has failed.
"""
# calculate price for complete order fullfilment
full_price=price*amount
# Move resources into escrow
if side==Side.BUY:
escrow=self._move_to_escrow(account_id,_bal,full_price)
else:
escrow=self._move_to_escrow(account_id,resource,amount)
if not escrow:
# no sufficient resources
return None
# create order and execude any trades
order,trades=self.lme.add_order(resource,price,amount,side)
self.orders[order.order_id]=order
self.order_account_map[order.order_id]=account_id
self._execute_trades(trades)
self.calculate_resource_metrics(resource)
return order
def cancel_order(self,order_id):
"""
Cancel open order.
Returns True if order has been canceled.
"""
if not order_id in self.orders:
return False
order=self.orders[order_id]
if self._is_order_complete(order):
return False
order=self.lme.cancel_order(order_id,order.instmt)
if order_id in self.order_trades_map: # has order any kind of trades
trades=self.order_trades_map[order_id]
# return assets from escrow to account
if order.side==Side.BUY:
total_payed=0
total_submitted=order.price*order.qty
for trade in trades:
total_payed+=trade.trade_price*trade.trade_qty
total_diff=total_submitted-total_payed
self._move_to_account(self.order_account_map[order_id],_bal,total_diff)
else:
qty_traded=0
qty_submitted=order.qty
for trade in trades:
qty_traded+=trade.trade_qty
qty_diff=qty_submitted-qty_traded
self._move_to_account(self.order_account_map[order_id],order.instmt,qty_diff)
return True
def _execute_trades(self, trades):
for i in trades:
self._execute_trade(i)
def _is_order_complete(self,order: Order):
return order.leaves_qty==0
def _execute_trade(self, trade: Trade):
account_id=self.order_account_map[trade.order_id]
# calculate price for trade fullfilment
full_price=trade.trade_price*trade.trade_qty
exprected_price=self.orders[trade.order_id].price*trade.trade_qty
price_diff=exprected_price-full_price
# save trade to order trade map
if trade.order_id not in self.order_trades_map:
self.order_trades_map[trade.order_id]=[]
self.order_trades_map[trade.order_id].append(trade)
# init escrow if needet
if trade.instmt not in self.escrow[account_id]:
self.escrow[account_id][trade.instmt]=0
# first edit escrow
if trade.trade_side==Side.BUY:
self.escrow[account_id][trade.instmt]+=trade.trade_qty
self.escrow[account_id][_bal]-=full_price
# move new resources to account
self._move_to_account(account_id,trade.instmt,trade.trade_qty)
# if deal was better then expected move money back
self._move_to_account(account_id,_bal,price_diff)
else:
self.escrow[account_id][trade.instmt]-=trade.trade_qty
self.escrow[account_id][_bal]+=full_price
# move new bal into account
self._move_to_account(account_id,_bal,full_price)
self.executed_trades.append(trade)
#update market rate
self.market_rate[trade.instmt]=trade.trade_price
def calculate_resource_metrics(self, resource):
order_book = self.lme.order_books.setdefault(resource, OrderBook())
best_bid = max(order_book.bids.keys()) if len(order_book.bids) > 0 else None
best_ask = max(order_book.asks.keys()) if len(order_book.asks) > 0 else None
self.total_demand[resource]=0
self.demand[resource]={}
for k,v in order_book.bids.items():
self.demand[resource][k]=0
for o in v:
self.demand[resource][k]+=o.qty
self.total_demand[resource]+=o.qty
self.supply[resource]={}
self.total_supply[resource]=0
for k,v in order_book.asks.items():
self.supply[resource][k]=0
for o in v:
self.supply[resource][k]+=o.qty
self.total_supply[resource]+=o.qty
self.best_ask[resource]=best_ask
self.best_bid[resource]=best_bid
def get_account_resource_amount(self,account_id,resource):
"""
Returns the amount of selected resource in account
"""
if account_id in self.account:
if resource in self.account[account_id]:
return self.account[account_id][resource]
return 0
def get_total_demand(self,resource):
if resource in self.total_demand:
return self.total_demand[resource]
return 0
def get_total_supply(self,resource):
if resource in self.total_supply:
return self.total_supply[resource]
return 0