101 lines
3.7 KiB
Ruby
101 lines
3.7 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe Rex::Book::Matcher do
|
|
let(:instance) { described_class.new }
|
|
|
|
describe "#match" do
|
|
let(:order_book) { Rex::Book::LimitOrderBook.new }
|
|
let(:buy_order) { build(:order, price: 100, is_buy: true, quantity: 100, remaining_quantity: 100) }
|
|
let(:cheaper_sell_order) { build(:order, price: 99, is_buy: false, quantity: 50, remaining_quantity: 50) }
|
|
let(:pricier_sell_order) { build(:order, price: 100, is_buy: false, quantity: 70, remaining_quantity: 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].quantity).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].quantity).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_quantity).to eq(20)
|
|
end
|
|
end
|
|
|
|
context "when order book is empty" do
|
|
it "returns an empty list" do
|
|
expect(instance.match(Rex::Book::LimitOrderBook.new)).to eq([])
|
|
end
|
|
end
|
|
|
|
# https://stackoverflow.com/a/18524231/3200224
|
|
# Testing a common example with a proven to be correct solution
|
|
context "stack overflow scenario" do
|
|
let(:order_1) { build(:order, price: 2030, is_buy: false, quantity: 100) }
|
|
let(:order_2) { build(:order, price: 2025, is_buy: false, quantity: 100) }
|
|
let(:order_3) { build(:order, price: 2030, is_buy: false, quantity: 200) }
|
|
let(:order_4) { build(:order, price: 2015, is_buy: true, quantity: 100) }
|
|
let(:order_5) { build(:order, price: 2020, is_buy: true, quantity: 200) }
|
|
let(:order_6) { build(:order, price: 2015, is_buy: true, quantity: 200) }
|
|
|
|
let(:crossing_order) { build(:order, is_buy: true, quantity: 250, price: 2035) }
|
|
|
|
before do
|
|
order_book.add_order(order_1)
|
|
order_book.add_order(order_2)
|
|
order_book.add_order(order_3)
|
|
order_book.add_order(order_4)
|
|
order_book.add_order(order_5)
|
|
order_book.add_order(order_6)
|
|
|
|
order_book.add_order(crossing_order)
|
|
end
|
|
|
|
it "matches the orders accordingly" do
|
|
trades = instance.match(order_book)
|
|
|
|
expect(trades.length).to eq(3)
|
|
|
|
expect(trades[0].id).to eq(1)
|
|
expect(trades[0].price).to eq(2025)
|
|
expect(trades[0].quantity).to eq(100)
|
|
expect(trades[0].buy_order).to eq(crossing_order)
|
|
expect(trades[0].sell_order).to eq(order_2)
|
|
|
|
expect(trades[1].id).to eq(2)
|
|
expect(trades[1].price).to eq(2030)
|
|
expect(trades[1].quantity).to eq(100)
|
|
expect(trades[1].buy_order).to eq(crossing_order)
|
|
expect(trades[1].sell_order).to eq(order_1)
|
|
|
|
expect(trades[2].id).to eq(3)
|
|
expect(trades[2].price).to eq(2030)
|
|
expect(trades[2].quantity).to eq(50)
|
|
expect(trades[2].buy_order).to eq(crossing_order)
|
|
expect(trades[2].sell_order).to eq(order_3)
|
|
end
|
|
end
|
|
end
|
|
end
|