Implement limit volume tracking

This commit is contained in:
Tim Kächele 2023-10-29 19:42:03 +01:00
parent 87f56e6e9b
commit 79be3b534c
4 changed files with 65 additions and 12 deletions

View File

@ -7,11 +7,8 @@ module Rex
return trades if highest_buy_order.nil? || lowest_sell_order.nil? return trades if highest_buy_order.nil? || lowest_sell_order.nil?
while highest_buy_order.price >= lowest_sell_order.price while highest_buy_order.price >= lowest_sell_order.price
max_quantity = [highest_buy_order.remaining_quantity, lowest_sell_order.remaining_quantity].min max_quantity = min(highest_buy_order.remaining_quantity, lowest_sell_order.remaining_quantity)
highest_buy_order.remaining_quantity -= max_quantity trade = Trade.new(
lowest_sell_order.remaining_quantity -= max_quantity
trades << Trade.new(
id: order_book.next_trade_id, id: order_book.next_trade_id,
buy_order: highest_buy_order, buy_order: highest_buy_order,
sell_order: lowest_sell_order, sell_order: lowest_sell_order,
@ -19,8 +16,8 @@ module Rex
price: lowest_sell_order.price price: lowest_sell_order.price
) )
remove_if_filled(highest_buy_order, order_book) order_book.process_trade(trade)
remove_if_filled(lowest_sell_order, order_book) trades.push(trade)
# Go for the next run # Go for the next run
highest_buy_order = order_book.highest_buy_order highest_buy_order = order_book.highest_buy_order
@ -34,9 +31,9 @@ module Rex
private private
def remove_if_filled(order, order_book) def min(a, b)
return unless order.filled? return a if a < b
order_book.remove_order(order.id) b
end end
end end
end end

View File

@ -2,6 +2,10 @@ require "rbtree"
module Rex module Rex
class OrderBook class OrderBook
attr_reader(
:buy_limit_volumes,
:sell_limit_volumes
)
def initialize(matcher: Matcher.new) def initialize(matcher: Matcher.new)
@matcher = matcher @matcher = matcher
@sell_side = RBTree.new @sell_side = RBTree.new
@ -9,14 +13,19 @@ module Rex
@order_ids = {} # order_id => order @order_ids = {} # order_id => order
@current_trade_id = 0 @current_trade_id = 0
@current_order_id = 0 @current_order_id = 0
@buy_limit_volumes = {} # price => volume
@sell_limit_volumes = {} # price => volume
end end
def add_order(order) def add_order(order)
side = side_for_order(order) side = side_for_order(order)
limit_volumes = limit_volume_for_order_side(order)
order.id = next_order_id order.id = next_order_id
side[order.price] ||= Limit.new(order.price) side[order.price] ||= Limit.new(order.price)
side[order.price].add_order(order) side[order.price].add_order(order)
limit_volumes[order.price] ||= 0
limit_volumes[order.price] += order.remaining_quantity
order_ids[order.id] = order order_ids[order.id] = order
end end
@ -30,18 +39,32 @@ module Rex
return nil if order.nil? return nil if order.nil?
side = side_for_order(order) side = side_for_order(order)
limit = side[order.price] limit = side[order.price]
limit.remove_order(order) limit.remove_order(order)
if limit.empty? if limit.empty?
side.delete(limit.price) side.delete(limit.price)
end end
limit_volumes = limit_volume_for_order_side(order)
limit_volumes[order.price] -= order.remaining_quantity
order_ids.delete(order.id) order_ids.delete(order.id)
end end
alias_method :cancel_order, :remove_order alias_method :cancel_order, :remove_order
def process_trade(trade)
trade.buy_order.remaining_quantity -= trade.quantity
trade.sell_order.remaining_quantity -= trade.quantity
buy_limit_volumes[trade.buy_order.price] -= trade.quantity
sell_limit_volumes[trade.sell_order.price] -= trade.quantity
remove_order(trade.buy_order.id) if trade.buy_order.filled?
remove_order(trade.sell_order.id) if trade.sell_order.filled?
end
def highest_buy_order def highest_buy_order
buy_side.last&.[](1)&.peek_first_order buy_side.last&.[](1)&.peek_first_order
end end
@ -64,12 +87,24 @@ module Rex
private private
attr_reader :order_ids, :buy_side, :sell_side attr_reader(
:order_ids,
:buy_side,
:sell_side
)
def next_order_id def next_order_id
@current_order_id += 1 @current_order_id += 1
end end
def limit_volume_for_order_side(order)
if order.is_buy
@buy_limit_volumes
else
@sell_limit_volumes
end
end
def side_for_order(order) def side_for_order(order)
if order.is_buy if order.is_buy
buy_side buy_side

View File

@ -41,6 +41,13 @@ RSpec.describe Rex::Matcher do
expect(order_book.lowest_sell_order).to eq(pricier_sell_order) expect(order_book.lowest_sell_order).to eq(pricier_sell_order)
expect(order_book.lowest_sell_order.remaining_quantity).to eq(20) expect(order_book.lowest_sell_order.remaining_quantity).to eq(20)
end end
it "adjusts the limit volumes" do
instance.match(order_book)
expect(order_book.buy_limit_volumes).to eq({100 => 0})
expect(order_book.sell_limit_volumes).to eq({99 => 0, 100 => 20})
end
end end
context "when order book is empty" do context "when order book is empty" do

View File

@ -13,6 +13,13 @@ RSpec.describe Rex::OrderBook do
expect(instance.best_buy_price).to eq(100) expect(instance.best_buy_price).to eq(100)
end end
it "adjusts the limit volume on the correct side" do
order = build(:order, is_buy: true, quantity: 100, remaining_quantity: 99)
instance.add_order(order)
expect(instance.buy_limit_volumes[order.price]).to eq(99)
end
it "assigns an order id" do it "assigns an order id" do
expect { instance.add_order(order_a) }.to change(order_a, :id).from(nil).to(1) expect { instance.add_order(order_a) }.to change(order_a, :id).from(nil).to(1)
expect { instance.add_order(order_b) }.to change(order_b, :id).from(nil).to(2) expect { instance.add_order(order_b) }.to change(order_b, :id).from(nil).to(2)
@ -143,6 +150,13 @@ RSpec.describe Rex::OrderBook do
instance.add_order(sell_order) instance.add_order(sell_order)
end end
it "adjusts the limit's volume" do
expect do
instance.cancel_order(sell_order.id)
end.to(change { instance.sell_limit_volumes[sell_order.price] }
.by(-sell_order.remaining_quantity))
end
it "does not affect the buy side" do it "does not affect the buy side" do
instance.cancel_order(sell_order.id) instance.cancel_order(sell_order.id)