def __init__(self, pos=None, balance=None, price=None, side=None): assert pos is None or type(pos) in (str, Decimal, int) assert balance is None or type(balance) in (str, Decimal, int) assert price is None or type(price) in (str, Decimal, int) assert side is None or side in Side.sides if pos: pos = Decimal(pos) if balance: balance = Decimal(balance) if price: price = Decimal(price) if balance is None and price is None: raise RuntimeError if balance and price and pos: raise RuntimeError if balance is not None and pos is not None: self.pos = pos self.balance = balance elif pos is not None and price is not None: self.pos = pos self.balance = pos * price elif price is not None and balance is not None: self.pos = balance / price self.balance = balance if side is not None: self.pos = abs(self.pos) * Side.sign(side) self.balance = Side.opposite_sign(side) * abs(self.balance)
def exit_market(self): Side.apply_sides(lambda side: self.engine.broker.cancel(0, side)) exit_side = Side.opposite_side(self.engine.pnl.position()) price = calc_price(self.engine.book.quote(exit_side), MMParams.liq_behind_exit) self.engine.broker.request(1, exit_side, price, self.engine.pnl.abs_position)
def specific_margin_price(entry_price, entry_side, margin, entry_commisiion=0, exit_commision=0): return entry_price \ + Side.sign(entry_side) * (margin + entry_commisiion) \ - Side.sign(entry_side) * exit_commision
def take_pnl(self): if self.pos.position() == 0: return Decimal('0') exit_side = Side.opposite_side(self.position()) take_price = self.nbbo.side(Side.side(self.position())) take_order = Position(pos=self.pos.abs_position(), price=take_price, side=exit_side) take_pos = self.pos + take_order + take_order.fee_pos(self.fee) return take_pos.balance
def enter_hedge(pnl: PNL, book: Book, side, cfg: HedgeConfig, vc: VenueConfig): quote = book.quote(side) side = quote.side pos = pnl.pos order_size = cfg.order_size.side(side) theo = (book.quote(Side.BID).price + book.quote(Side.ASK).price) / 2 if pos.abs_position() < vc.min_order_size: # depth or ema order_size = adjusted_size(order_size, side, pos.abs_position()) method, price = depth_ema_price(cfg, order_size, pnl, quote, side, vc) return Position(pos=order_size, side=side, price=price), method elif Side.opposite( pos.side()) == side and pos.abs_position() >= vc.min_order_size: # exit order # depth or zero method, price = depth_ema_price(cfg, order_size, pnl, quote, side, vc) add_pos = pos.oppoiste_with_price(price) min_margin = pos.opposite_with_margin(vc.tick_size) min_margin = bound_pos_to_lower_quote(quote, min_margin, vc.tick_size) if (pos + add_pos).balance > 0: return add_pos, "QUOTE" else: return min_margin, "MIN PROFIT" elif pos.side( ) == side and cfg.max_pos - pos.abs_position() >= vc.min_order_size: #depth #hedge #order_size = adjusted_size(order_size, side, pos.abs_position()) method, price_depth = depth_ema_price(cfg, order_size, pnl, quote, side, vc) order_size = min(order_size, cfg.max_pos - pos.abs_position()) depth_pos = Position(pos=order_size, side=side, price=price_depth) sign = Side.sign(pos.side()) target_price = theo - Side.sign(pos.side()) * theo * cfg.hedge_perc hedge_pos = hedge_positon_size(pos, Decimal(target_price), order_size) hedge_pos = bound_pos_to_lower_quote(quote, hedge_pos, vc.tick_size) #return hedge_pos, "HEDGE HEDGE" if (side == Side.BID and hedge_pos.price() < depth_pos.price()) \ or (side == Side.ASK and hedge_pos.price() > depth_pos.price()): return hedge_pos, "HEDGE HEDGE" else: return depth_pos, "HEDGE DEPTH" else: #zero return Position(side=side, pos=0, balance=0), "CANCEL"
def stop_loss_exit_strategy(book: Book, pnl: PNL, ac: MarketmakerConfig, vc: VenueConfig, loss=False): def volume_behind_order(min_pos: Position): sign = Side.sign(min_pos.side()) return sum([ level.volume() for level in book.quote(min_pos.side()) if sign * level.price > sign * min_pos.price() ]) pos = pnl.pos exit_side = Side.opposite(pos.side()) price = price_on_a_depth(book.quote(exit_side), pos.abs_position(), ac, vc) add_pos = pos.oppoiste_with_price(price) min_margin = pos.opposite_with_margin(ac.min_profit) remove_pos = pos.oppoiste_with_price(book.quote(pos.side()).price) # if (pos + remove_pos + remove_pos.fee_pos(pnl.fee)).balance > pnl.closed_pnl: # return remove_pos, "REMOVE" if (pos + add_pos).balance > 0 or loss: return add_pos, "QUOTE" # elif volume_behind_order(min_margin) >= config.buried_volume: # return add_pos, "STOP LOSS" else: return min_margin, "MIN PROFIT"
def before(self, level): if level.side != self.side: raise RuntimeError if self.price == level.price: raise RuntimeError return (self.price - level.price) / abs(self.price - level.price) == Side.sign(self.side)
def calc_price_for_depth(quote: Level, liq_behind): quote_liq = quote.size while quote_liq < liq_behind: quote = quote.next_level quote_liq += quote.size return quote.price + Side.side(quote.side) * Decimal('0.0001')
def price(self): if self.pos == 0: return Decimal('0') prec = Decimal('10000') price, reminder = divmod(prec * abs(self.balance), abs(self.pos)) price /= prec if reminder != 0: price += Side.sign(self.side()) * Decimal('0.0001') return round(price, 4)
def gen_prices(side): sign = -Side.sign(side) prev_price = Decimal(median + sign * spread / 2) for i in range(0, levels): next_price = prev_price + Decimal( sign * randrange(1, 1000, 1) / 1000) final_next_price = round(Decimal(next_price), 4) size = round(Decimal(randrange(1, 100, 1) / 100), 2) yield final_next_price, size prev_price = final_next_price
def calc_price_between_levels(quote, liq_behind, min_step, place_to_spread=False): quote_liq = quote.size dt = abs(0 - quote.price) if place_to_spread else 0 while quote_liq < liq_behind: dt = abs(quote.price - quote.next_level.price) quote = quote.next_level quote_liq += quote.size if dt > min_step: return quote.price + Side.sign(quote.side) * min_step else: return quote.price
def test_with_params(pos, enter_price): pnl = PNL('0.3') book = Book() book.quote_subscribers.append(pnl) config = MMParams({ "min_levels": "5", "liq_behind_exit": "0.02", "liq_behind_entry": { "BID": "0.41", "ASK": "0.41" }, "order_sizes": { "BID": "0.07", "ASK": "0.07" }, "min_profit": "0.01", "min_order_size": "0.01", "buried_volume": "10", "taker_exit_profit": "0.1", "price_tolerance": "0.0005" }) pnl.execution(Side.side(pos), abs(pos), abs(enter_price)) median = 1000 for i in range(0, 5): book.increment_level(Side.ASK, Decimal(median + i), Decimal(i / 100)) book.increment_level(Side.BID, Decimal(median - i), Decimal(i / 100)) book.quote_changed(Side.BID) book.quote_changed(Side.ASK) print_book_and_orders(book, Broker(OrderManager())) exit_price = hold_exit_price_strategy(book, pnl, config) pnl.execution(Side.opposite(pnl.position_side()), abs(pos), exit_price) return pnl.closed_pnl
def enter_ema(quote: Level, ema: Decimal, ema_work_perc, vc: VenueConfig): sign = Decimal(Side.sign(quote.side)) def calc_ema_price(): return round(Decimal(ema - sign * (ema / 100 * ema_work_perc)), 4) def stick_to_quote(price): try: under_price = next( (x.price for x in quote if sign * x.price - sign * price <= 0)) return under_price + sign * vc.tick_size if under_price != price else under_price except StopIteration: return price return stick_to_quote(calc_ema_price())
def price_on_a_depth(top_quote, size, ac: MarketmakerConfig, vc: VenueConfig): quote_array = [] quote_liq = Decimal('0') side = top_quote.side liq_adj = ac.liq_behind.side(side) - size prev_price = Decimal('0') for quote in top_quote: quote_liq += quote.size quote_array.append((quote.price, quote.size, quote_liq, abs(prev_price - quote.price))) prev_price = quote.price if quote_liq >= liq_adj: break last_quote = quote_array[-1] if last_quote[2] > liq_adj: return last_quote[0] + Side.sign(side) * vc.tick_size else: return last_quote[0]
def remove_exit_price_strategy(book: Book, pos: Position, config: MarketmakerConfig, fee=Decimal(0.3)): # pos, last_price = remove_price(book.quote(pos.side()), pos) # if pos.balance > 0: # remove_pos = pos.oppoiste_with_price(last_price) # print("fee " + str(pos * Decimal('0.3'))) remove_pos = pos.oppoiste_with_price(book.quote(pos.side()).price) remove_pos_wfee = Position(pos=remove_pos.position(), balance=remove_pos.balance + (remove_pos.balance / 100) * fee) add_pos = pos.oppoiste_with_price( book.quote(Side.opposite(pos.side())).price) fee_ = remove_pos * Decimal(fee / 100) fin_pos = pos + remove_pos if (pos + remove_pos_wfee).balance > 0: return remove_pos elif (pos + add_pos).balance > 0: return add_pos else: return pos.opposite_with_margin(config.min_profit)
def nbbo_pnl(self): exit_side = Side.opposite_side(self.position()) nbbo_price = self.nbbo.side(exit_side) return (self.pos + Position(pos=self.pos.abs_position(), price=nbbo_price, side=exit_side)).balance
def __init__(self, side, price, size): Side.check_fail(side) self.side = side self.price = price self.size = size self.next_level = None
def price_not_better_than(calc_price, ema_price, side): sign = (calc_price - ema_price) / abs(calc_price - ema_price) if sign == Side.sign(side): return ema_price else: return calc_price
def adjusted_size(order_size, order_side, pos): pos_side = Side.side(pos) if pos_side == order_side: return order_size - abs(pos) else: return order_size + abs(pos)
def side(self): return Side.side(self.pos)
def test_closer_to_quote(): assert Side.closer_to_quote(Side.BID, 10, 11) == 11 assert Side.closer_to_quote(Side.ASK, 10, 11) == 10 assert Side.closer_to_quote(Side.ASK, 10, 10) == 10 assert Side.closer_to_quote(Side.BID, 11, 11) == 11 assert Side.closer_to_quote(Side.BID, 10.01, 11.01) == 11.01 assert Side.closer_to_quote(Side.ASK, 10.01, 11.01) == 10.01 assert Side.closer_to_quote(Side.ASK, 10.01, 10.01) == 10.01 assert Side.closer_to_quote(Side.BID, 11.01, 11.01) == 11.01 assert Side.closer_to_quote(Side.BID, Decimal(10.01), Decimal(11.01)) == 11.01 assert Side.closer_to_quote(Side.ASK, Decimal(10.01), Decimal(11.01)) == 10.01 assert Side.closer_to_quote(Side.ASK, Decimal(10.01), Decimal(10.01)) == 10.01 assert Side.closer_to_quote(Side.BID, Decimal(11.01), Decimal(11.01)) == 11.01
def oppoiste_with_price(self, price): return Position(pos=self.pos, price=price, side=Side.opposite(self.side()))
def calc_target_price(theo: Decimal, pos: Position, hedge_perc: Decimal): #exit_side = Side.opposite(pos.side()) sign = Side.sign(pos.side()) theo_target = theo - Side.sign(pos.side()) * theo * hedge_perc pos_target = pos.price() - sign * theo * hedge_perc return theo_target
def position_side(self): return Side.side(self.position())
def should_update_price(side, current_price: Decimal, new_price: Decimal, barrier: Decimal): return current_price - new_price > barrier or Side.closer_to_quote( side, current_price, new_price) == new_price
def volume_behind_order(min_pos: Position): sign = Side.sign(min_pos.side()) return sum([ level.volume() for level in book.quote(min_pos.side()) if sign * level.price > sign * min_pos.price() ])
def ema_constraint(depth_price, ema_price, side): sign = Side.sign(side) # ema 100 price 101 side bid delta = sign * ema_price - sign * depth_price return (ema_price, "EMA") if delta < 0 else (depth_price, "ENTER")
def epsilon_from_theo(theo, perc, side): return theo - Side.sign(side) * theo * perc