2023-10-28 18:01:24 +02:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2023-11-08 20:35:14 +01:00
|
|
|
RSpec.describe Rex::Book::LimitOrderBook do
|
2023-10-28 18:01:24 +02:00
|
|
|
let(:instance) { described_class.new }
|
|
|
|
|
|
|
|
describe "#add_order" do
|
2023-10-28 23:06:39 +02:00
|
|
|
let(:order_a) { build(:order, id: nil, is_buy: true, price: 100) }
|
|
|
|
let(:order_b) { build(:order, id: nil, is_buy: true, price: 101) }
|
2023-10-28 18:01:24 +02:00
|
|
|
|
|
|
|
it "adds the order to the order book" do
|
|
|
|
instance.add_order(order_a)
|
|
|
|
|
|
|
|
expect(instance.best_buy_price).to eq(100)
|
|
|
|
end
|
|
|
|
|
2023-10-28 23:06:39 +02:00
|
|
|
it "assigns an order id" do
|
|
|
|
expect { instance.add_order(order_a) }.to change(order_a, :id).from(nil).to(1)
|
|
|
|
expect { instance.add_order(order_b) }.to change(order_b, :id).from(nil).to(2)
|
|
|
|
end
|
|
|
|
|
2023-10-28 18:01:24 +02:00
|
|
|
context "with multiple orders at the same price" do
|
|
|
|
let(:order_b) { build(:order, is_buy: true, price: 100) }
|
|
|
|
|
|
|
|
it "regards the new order as the best buy price" do
|
|
|
|
instance.add_order(order_a)
|
|
|
|
instance.add_order(order_b)
|
|
|
|
|
|
|
|
expect(instance.best_buy_price).to eq(100)
|
|
|
|
expect(instance.highest_buy_order).to eq(order_a)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "with multiple orders at the different price levels" do
|
|
|
|
let(:order_b) { build(:order, is_buy: true, price: 120) }
|
|
|
|
|
|
|
|
it "regards the new order as the best buy price" do
|
|
|
|
instance.add_order(order_b)
|
|
|
|
instance.add_order(order_a)
|
|
|
|
|
|
|
|
expect(instance.best_buy_price).to eq(120)
|
|
|
|
expect(instance.highest_buy_order).to eq(order_b)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "with multiple orders on different sides" do
|
|
|
|
let(:order_b) { build(:order, is_buy: false, price: 120) }
|
|
|
|
|
|
|
|
it "returns the correct prices for both sides" do
|
|
|
|
instance.add_order(order_a)
|
|
|
|
instance.add_order(order_b)
|
|
|
|
|
|
|
|
expect(instance.best_buy_price).to eq(100)
|
|
|
|
expect(instance.best_sell_price).to eq(120)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-10-28 23:00:09 +02:00
|
|
|
describe "add_and_match_order" do
|
|
|
|
context "when matcher is given" do
|
2023-11-08 20:33:49 +01:00
|
|
|
let(:matcher) { instance_double(Rex::Book::Matcher) }
|
2023-10-28 23:00:09 +02:00
|
|
|
let(:instance) { described_class.new(matcher: matcher) }
|
|
|
|
let(:order) { build(:order, is_buy: true, price: 100) }
|
|
|
|
|
|
|
|
before do
|
|
|
|
allow(matcher).to receive(:match).with(instance)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "adds the order" do
|
|
|
|
expect(instance).to receive(:add_order).with(order)
|
|
|
|
instance.add_and_match_order(order)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "calls the matcher" do
|
|
|
|
expect(matcher).to receive(:match).with(instance)
|
|
|
|
instance.add_and_match_order(order)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-10-28 18:01:24 +02:00
|
|
|
describe "#highest_buy_order" do
|
|
|
|
context "when there is nothing in the book" do
|
|
|
|
it "returns nil " do
|
|
|
|
expect(instance.highest_buy_order).to eq(nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-10-28 23:22:14 +02:00
|
|
|
describe "#best_sell_price" do
|
|
|
|
context "when the order book is empty" do
|
|
|
|
it "returns nil" do
|
|
|
|
expect(instance.best_sell_price).to eq(nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-11-08 20:33:49 +01:00
|
|
|
describe "#lowest_sell_order" do
|
2023-10-28 18:01:24 +02:00
|
|
|
context "when there is nothing in the book" do
|
|
|
|
it "returns nil " do
|
|
|
|
expect(instance.highest_buy_order).to eq(nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-02-03 21:29:27 +01:00
|
|
|
describe "#orders_for_user" do
|
|
|
|
context "when user does not have any orders" do
|
|
|
|
it "returns an empty list" do
|
|
|
|
expect(instance.orders_for_user("unknown_id")).to eq([])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "when user has orders" do
|
|
|
|
let(:order) { build(:order, id: nil, is_buy: true, price: 100) }
|
|
|
|
|
|
|
|
before do
|
|
|
|
instance.add_order(order)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns a list of the user's orders" do
|
|
|
|
expect(instance.orders_for_user(order.user_id)).to eq([order])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "when the user had orders" do
|
|
|
|
let(:order) { build(:order, id: nil, is_buy: true, price: 100) }
|
|
|
|
|
|
|
|
before do
|
|
|
|
instance.add_order(order)
|
|
|
|
instance.cancel_order(order.id)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns an empty list" do
|
|
|
|
expect(instance.orders_for_user(order.user_id)).to eq([])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-10-28 18:01:24 +02:00
|
|
|
describe "#cancel_order" do
|
|
|
|
let(:buy_order) { build(:order, is_buy: true, price: 100) }
|
|
|
|
let(:sell_order) { build(:order, is_buy: false, price: 121) }
|
|
|
|
|
|
|
|
before do
|
|
|
|
instance.add_order(buy_order)
|
|
|
|
end
|
|
|
|
|
2024-01-21 19:00:17 +01:00
|
|
|
it "returns the cancelled order" do
|
|
|
|
order = instance.cancel_order(buy_order.id)
|
|
|
|
|
|
|
|
expect(order).to eq(buy_order)
|
|
|
|
end
|
|
|
|
|
2023-10-28 23:22:14 +02:00
|
|
|
context "when the order id is unknown" do
|
|
|
|
it "returns nil" do
|
|
|
|
expect(instance.cancel_order(-1)).to eq(nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-10-28 18:01:24 +02:00
|
|
|
context "when it is the last order for the given price" do
|
|
|
|
it "removes the order from the side" do
|
|
|
|
instance.cancel_order(buy_order.id)
|
|
|
|
|
|
|
|
expect(instance.best_buy_price).to eq(nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-10-28 23:22:14 +02:00
|
|
|
context "when it is not the last order for the given price" do
|
|
|
|
let(:another_buy_order) { build(:order, is_buy: true, price: 100) }
|
|
|
|
|
|
|
|
before do
|
|
|
|
instance.add_order(another_buy_order)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "removes the order from the side" do
|
|
|
|
instance.cancel_order(buy_order.id)
|
|
|
|
|
|
|
|
expect(instance.best_buy_price).to eq(100)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-10-28 18:01:24 +02:00
|
|
|
context "an order on the sell side is removed" do
|
|
|
|
before do
|
|
|
|
instance.add_order(buy_order)
|
|
|
|
instance.add_order(sell_order)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "does not affect the buy side" do
|
|
|
|
instance.cancel_order(sell_order.id)
|
|
|
|
|
|
|
|
expect(instance.best_buy_price).to eq(100)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|