Implement matching system

This commit is contained in:
Tim Kächele 2023-10-28 22:42:19 +02:00
parent 57436c9eb0
commit 37435355ae
6 changed files with 127 additions and 0 deletions

View File

@ -3,7 +3,9 @@
require_relative "rex/version"
require_relative "rex/limit"
require_relative "rex/order"
require_relative "rex/trade"
require_relative "rex/order_book"
require_relative "rex/matcher"
module Rex
class Error < StandardError; end

40
lib/rex/matcher.rb Normal file
View File

@ -0,0 +1,40 @@
module Rex
class Matcher
def match(order_book)
trades = []
highest_buy_order = order_book.highest_buy_order
lowest_sell_order = order_book.lowest_sell_order
return trades if highest_buy_order.nil? || lowest_sell_order.nil?
while highest_buy_order&.price&.>= lowest_sell_order&.price
max_amount = [highest_buy_order.remaining_amount, lowest_sell_order.remaining_amount].min
highest_buy_order.remaining_amount -= max_amount
lowest_sell_order.remaining_amount -= max_amount
trades << Trade.new(
id: order_book.next_trade_id,
buy_order: highest_buy_order,
sell_order: lowest_sell_order,
amount: max_amount,
price: lowest_sell_order.price
)
remove_if_filled(highest_buy_order, order_book)
remove_if_filled(lowest_sell_order, order_book)
# Go for the next run
highest_buy_order = order_book.highest_buy_order
lowest_sell_order = order_book.lowest_sell_order
return trades if highest_buy_order.nil? || lowest_sell_order.nil?
end
end
private
def remove_if_filled(order, order_book)
return unless order.filled?
order_book.remove_order(order.id)
end
end
end

View File

@ -6,6 +6,7 @@ module Rex
@sell_side = RBTree.new
@buy_side = RBTree.new
@order_ids = {} # order_id => order
@current_trade_id = 0
end
def add_order(order)
@ -49,6 +50,10 @@ module Rex
sell_side.first&.[](0)
end
def next_trade_id
@current_trade_id += 1
end
private
attr_reader :order_ids, :buy_side, :sell_side

19
lib/rex/trade.rb Normal file
View File

@ -0,0 +1,19 @@
module Rex
class Trade
attr_accessor(
:id,
:buy_order,
:sell_order,
:amount,
:price
)
def initialize(attributes = {})
@id = attributes[:id]
@buy_order = attributes[:buy_order]
@sell_order = attributes[:sell_order]
@amount = attributes[:amount]
@price = attributes[:price]
end
end
end

53
spec/matcher_spec.rb Normal file
View File

@ -0,0 +1,53 @@
# frozen_string_literal: true
RSpec.describe Rex::Matcher do
let(:instance) { described_class.new }
describe "#match" do
let(:order_book) { Rex::OrderBook.new }
let(:buy_order) { build(:order, price: 100, is_buy: true, amount: 100, remaining_amount: 100) }
let(:cheaper_sell_order) { build(:order, price: 99, is_buy: false, amount: 50, remaining_amount: 50) }
let(:pricier_sell_order) { build(:order, price: 100, is_buy: false, amount: 70, remaining_amount: 70) }
context "when order book has unmatched orders" do
before do
order_book.add_order(buy_order)
order_book.add_order(cheaper_sell_order)
order_book.add_order(pricier_sell_order)
end
it "returns proper trades" do
trades = instance.match(order_book)
expect(trades.length).to eq(2)
expect(trades[0].id).to eq(1)
expect(trades[0].buy_order).to eq(buy_order)
expect(trades[0].sell_order).to eq(cheaper_sell_order)
expect(trades[0].price).to eq(99)
expect(trades[0].amount).to eq(50)
expect(trades[1].id).to eq(2)
expect(trades[1].buy_order).to eq(buy_order)
expect(trades[1].sell_order).to eq(pricier_sell_order)
expect(trades[1].price).to eq(100)
expect(trades[1].amount).to eq(50)
end
it "removes filled orders from the order book" do
instance.match(order_book)
expect(order_book.highest_buy_order).to eq(nil)
expect(order_book.lowest_sell_order).to eq(pricier_sell_order)
expect(order_book.lowest_sell_order.remaining_amount).to eq(20)
end
end
context "when order book is empty" do
it "returns an empty list" do
expect(instance.match(Rex::OrderBook.new)).to eq([])
end
end
end
end

View File

@ -94,4 +94,12 @@ RSpec.describe Rex::OrderBook do
end
end
end
describe "#next_trade_id" do
it 'returns an increasing trade id' do
expect(instance.next_trade_id).to eq(1)
expect(instance.next_trade_id).to eq(2)
expect(instance.next_trade_id).to eq(3)
end
end
end