Implement matching system
This commit is contained in:
parent
57436c9eb0
commit
37435355ae
@ -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
40
lib/rex/matcher.rb
Normal 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
|
@ -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
19
lib/rex/trade.rb
Normal 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
53
spec/matcher_spec.rb
Normal 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
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user