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