From 188f45d4c4a5056dfc762a0d0e31f45512b1a2e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20K=C3=A4chele?= Date: Wed, 8 Nov 2023 22:47:08 +0100 Subject: [PATCH] Implement a limit volume tracker Implement the limit volume tracker that keeps track of the limit volumes and emits change events that can be used by clients to update their copy of the order book --- lib/rex/book.rb | 1 + lib/rex/book/limit_volume_tracker.rb | 63 +++++++++++++++++++++++ spec/book/limit_volume_tracker_spec.rb | 71 ++++++++++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 lib/rex/book/limit_volume_tracker.rb create mode 100644 spec/book/limit_volume_tracker_spec.rb diff --git a/lib/rex/book.rb b/lib/rex/book.rb index 916be26..5e883d7 100644 --- a/lib/rex/book.rb +++ b/lib/rex/book.rb @@ -5,6 +5,7 @@ require_relative "book/trade" require_relative "book/order" require_relative "book/limit_order_book" require_relative "book/matcher" +require_relative "book/limit_volume_tracker" module Rex module Book diff --git a/lib/rex/book/limit_volume_tracker.rb b/lib/rex/book/limit_volume_tracker.rb new file mode 100644 index 0000000..be61cb8 --- /dev/null +++ b/lib/rex/book/limit_volume_tracker.rb @@ -0,0 +1,63 @@ +module Rex + module Book + class LimitVolumeTracker + LimitVolumeChange = Struct.new(:side, :price, :new_volume) + + attr_reader :volumes + + def initialize + @volumes = Hash.new { 0 } + end + + def add_order(order) + volumes[[order.is_buy, order.price]] += order.remaining_quantity + + [ + LimitVolumeChange.new( + side(order), + order.price, + volumes[[order.is_buy, order.price]] + ) + ] + end + + def remove_order(order) + volumes[[order.is_buy, order.price]] -= order.remaining_quantity + + [ + LimitVolumeChange.new( + side(order), + order.price, + volumes[[order.is_buy, order.price]] + ) + ] + end + + def process_trade(trade) + buy_order = trade.buy_order + sell_order = trade.sell_order + + volumes[[buy_order.is_buy, buy_order.price]] -= trade.quantity + volumes[[sell_order.is_buy, sell_order.price]] -= trade.quantity + [ + LimitVolumeChange.new( + :buy, + buy_order.price, + volumes[[buy_order.is_buy, buy_order.price]] + ), + LimitVolumeChange.new( + :sell, + sell_order.price, + volumes[[sell_order.is_buy, sell_order.price]] + ) + ] + end + + private + + def side(order) + order.is_buy ? :buy : :sell + end + end + end +end diff --git a/spec/book/limit_volume_tracker_spec.rb b/spec/book/limit_volume_tracker_spec.rb new file mode 100644 index 0000000..ae2e20a --- /dev/null +++ b/spec/book/limit_volume_tracker_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +RSpec.describe Rex::Book::LimitVolumeTracker do + let(:instance) { described_class.new } + let(:sell_order) { build(:order, is_buy: false, price: 100, quantity: 250) } + let(:buy_order) { build(:order, is_buy: true, price: 100, quantity: 200) } + + describe "#add_order" do + it "adds the quantity to the limit volume" do + expect { instance.add_order(sell_order) }.to( + change { instance.volumes[[false, 100]] }.to(250) + ) + end + + it "returns the changes limit volumes as a struct" do + expect(instance.add_order(sell_order)).to eq([ + Rex::Book::LimitVolumeTracker::LimitVolumeChange.new(:sell, 100, 250) + ]) + end + end + + describe "#remove_order" do + before do + instance.add_order(sell_order) + end + + it "removes the quantity from the limit volume" do + expect { instance.remove_order(sell_order) }.to( + change { instance.volumes[[false, 100]] }.from(250).to(0) + ) + end + + it "returns the changes limit volumes as a struct" do + expect(instance.remove_order(sell_order)).to eq([ + Rex::Book::LimitVolumeTracker::LimitVolumeChange.new(:sell, 100, 0) + ]) + end + end + + describe "#process_trade" do + let(:trade) do + Rex::Book::Trade.new( + buy_order: buy_order, + sell_order: sell_order, + price: 100, + quantity: 200 + ) + end + + before do + instance.add_order(sell_order) + instance.add_order(buy_order) + end + + it "adjusts the volumes of the involved limits" do + expect { instance.process_trade(trade) }.to( + change { instance.volumes[[false, 100]] }.from(250).to(50) + .and( + change { instance.volumes[[true, 100]] }.from(200).to(0) + ) + ) + end + + it "returns the changes limit volumes as a struct" do + expect(instance.process_trade(trade)).to eq([ + Rex::Book::LimitVolumeTracker::LimitVolumeChange.new(:buy, 100, 0), + Rex::Book::LimitVolumeTracker::LimitVolumeChange.new(:sell, 100, 50) + ]) + end + end +end