Implement matching system
This commit is contained in:
parent
57436c9eb0
commit
37435355ae
@ -3,7 +3,9 @@
|
|||||||
require_relative "rex/version"
|
require_relative "rex/version"
|
||||||
require_relative "rex/limit"
|
require_relative "rex/limit"
|
||||||
require_relative "rex/order"
|
require_relative "rex/order"
|
||||||
|
require_relative "rex/trade"
|
||||||
require_relative "rex/order_book"
|
require_relative "rex/order_book"
|
||||||
|
require_relative "rex/matcher"
|
||||||
|
|
||||||
module Rex
|
module Rex
|
||||||
class Error < StandardError; end
|
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
|
@sell_side = RBTree.new
|
||||||
@buy_side = RBTree.new
|
@buy_side = RBTree.new
|
||||||
@order_ids = {} # order_id => order
|
@order_ids = {} # order_id => order
|
||||||
|
@current_trade_id = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_order(order)
|
def add_order(order)
|
||||||
@ -49,6 +50,10 @@ module Rex
|
|||||||
sell_side.first&.[](0)
|
sell_side.first&.[](0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def next_trade_id
|
||||||
|
@current_trade_id += 1
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
attr_reader :order_ids, :buy_side, :sell_side
|
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
|
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
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user