Class: Gekko::Book
- Inherits:
-
Object
- Object
- Gekko::Book
- Extended by:
- Forwardable
- Includes:
- Serialization
- Defined in:
- lib/gekko/book.rb
Overview
An order book consisting of a bid side and an ask side
Instance Attribute Summary collapse
-
#asks ⇒ Object
Returns the value of attribute asks.
-
#base_precision ⇒ Object
Returns the value of attribute base_precision.
-
#bids ⇒ Object
Returns the value of attribute bids.
-
#multiplier ⇒ Object
Returns the value of attribute multiplier.
-
#pair ⇒ Object
Returns the value of attribute pair.
-
#received ⇒ Object
Returns the value of attribute received.
-
#tape ⇒ Object
Returns the value of attribute tape.
Class Method Summary collapse
-
.from_hash(hsh) ⇒ Gekko::Book
Loads the book from a hash.
Instance Method Summary collapse
-
#ask ⇒ Object
Returns the current best ask price or
nil
if there are currently no asks. -
#bid ⇒ Object
Returns the current best bid price or
nil
if there are currently no bids. -
#cancel(order_id) ⇒ Object
Cancels an order given an ID.
-
#execute_trade(maker, taker) ⇒ Object
Executes a trade between two orders.
-
#initialize(pair, opts = {}) ⇒ Book
constructor
A new instance of Book.
-
#receive_order(order) ⇒ Object
Receives an order and executes it.
-
#remove_expired! ⇒ Object
Removes all expired orders from the book.
-
#spread ⇒ Object
Returns the current spread if at least a bid and an ask are present, returns
nil
otherwise. -
#tick! ⇒ Object
Emits a ticker on the tape.
-
#ticker ⇒ Hash
Returns the current ticker.
-
#to_hash ⇒ Hash
Returns a
Hash
representation of thisBook
instance.
Methods included from Serialization
Constructor Details
#initialize(pair, opts = {}) ⇒ Book
Returns a new instance of Book.
19 20 21 22 23 24 25 26 27 |
# File 'lib/gekko/book.rb', line 19 def initialize(pair, opts = {}) self.pair = opts[:pair] || pair self.bids = opts[:bids] || BookSide.new(:bid) self.asks = opts[:asks] || BookSide.new(:ask) self.tape = opts[:tape] || Tape.new({ logger: opts[:logger] }) self.base_precision = opts[:base_precision] || 8 self.multiplier = BigDecimal(10 ** base_precision) self.received = opts[:received] || {} end |
Instance Attribute Details
#asks ⇒ Object
Returns the value of attribute asks.
15 16 17 |
# File 'lib/gekko/book.rb', line 15 def asks @asks end |
#base_precision ⇒ Object
Returns the value of attribute base_precision.
15 16 17 |
# File 'lib/gekko/book.rb', line 15 def base_precision @base_precision end |
#bids ⇒ Object
Returns the value of attribute bids.
15 16 17 |
# File 'lib/gekko/book.rb', line 15 def bids @bids end |
#multiplier ⇒ Object
Returns the value of attribute multiplier.
15 16 17 |
# File 'lib/gekko/book.rb', line 15 def multiplier @multiplier end |
#pair ⇒ Object
Returns the value of attribute pair.
15 16 17 |
# File 'lib/gekko/book.rb', line 15 def pair @pair end |
#received ⇒ Object
Returns the value of attribute received.
15 16 17 |
# File 'lib/gekko/book.rb', line 15 def received @received end |
#tape ⇒ Object
Returns the value of attribute tape.
15 16 17 |
# File 'lib/gekko/book.rb', line 15 def tape @tape end |
Class Method Details
.from_hash(hsh) ⇒ Gekko::Book
Loads the book from a hash
252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/gekko/book.rb', line 252 def self.from_hash(hsh) book = Book.new(hsh[:pair], { bids: BookSide.new(:bid, orders: hsh[:bids].map { |o| symbolize_keys(o) }), asks: BookSide.new(:ask, orders: hsh[:asks].map { |o| symbolize_keys(o) }) }) [:bids, :asks].each { |s| book.send(s).each { |ord| book.received[ord.id.to_s] = ord } } book.tape = Tape.from_hash(symbolize_keys(hsh[:tape])) if hsh[:tape] book end |
Instance Method Details
#ask ⇒ Object
Returns the current best ask price or nil
if there are currently no asks
181 182 183 |
# File 'lib/gekko/book.rb', line 181 def ask asks.top end |
#bid ⇒ Object
Returns the current best bid price or nil
if there are currently no bids
189 190 191 |
# File 'lib/gekko/book.rb', line 189 def bid bids.top end |
#cancel(order_id) ⇒ Object
Cancels an order given an ID
147 148 149 150 151 152 153 154 155 156 |
# File 'lib/gekko/book.rb', line 147 def cancel(order_id) prev_bid = bid prev_ask = ask order = received[order_id.to_s] dels = order.bid? ? bids.delete(order) : asks.delete(order) dels && tape << order.(:done, reason: :canceled) tick! if (prev_bid != bid) || (prev_ask != ask) end |
#execute_trade(maker, taker) ⇒ Object
Executes a trade between two orders
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/gekko/book.rb', line 98 def execute_trade(maker, taker) trade_price = maker.price max_quote_size = nil # Rounding direction depends on the takers direction rounding = (taker.bid? ? :floor : :ceil) if taker.is_a?(MarketOrder) max_size_with_quote_margin = taker.remaining_quote_margin && (taker.remaining_quote_margin * multiplier / trade_price).send(rounding) end base_size = [ maker.remaining, taker.remaining, max_size_with_quote_margin ].compact.min if taker.is_a?(LimitOrder) quote_size = (base_size * trade_price) / multiplier elsif taker.is_a?(MarketOrder) if base_size == max_size_with_quote_margin taker.max_precision = true end quote_size = [(trade_price * base_size / multiplier).round, taker.remaining_quote_margin].compact.min taker.remaining_quote_margin -= quote_size if taker.quote_margin end tape << { type: :execution, price: trade_price, base_size: base_size, quote_size: quote_size, maker_id: maker.id.to_s, taker_id: taker.id.to_s, tick: taker.bid? ? :up : :down } taker.remaining -= base_size if taker.remaining maker.remaining -= base_size end |
#receive_order(order) ⇒ Object
Receives an order and executes it
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/gekko/book.rb', line 34 def receive_order(order) raise 'Order must be a Gekko::LimitOrder or a Gekko::MarketOrder' unless [LimitOrder, MarketOrder].include?(order.class) if received.has_key?(order.id.to_s) tape << order.(:reject, reason: :duplicate_id) elsif order.expired? tape << order.(:reject, reason: :expired) else old_ticker = ticker self.received[order.id.to_s] = order tape << order.(:received) order_side = order.bid? ? bids : asks opposite_side = order.bid? ? asks : bids next_match = opposite_side.first prev_match_id = nil while !order.done? && order.crosses?(next_match) # If we match against the same order twice in a row, something went seriously # wrong, we'd rather noisily die at this point. raise 'Infinite matching loop detected !!' if (prev_match_id == next_match.id) prev_match_id = next_match.id if next_match.expired? tape << opposite_side.shift.(:done, reason: :expired) next_match = opposite_side.first elsif order.uid == next_match.uid # Same user/account associated to order, we cancel the next match tape << opposite_side.shift.(:done, reason: :canceled) next_match = opposite_side.first else execute_trade(next_match, order) if next_match.filled? tape << opposite_side.shift.(:done, reason: :filled) next_match = opposite_side.first end end end if order.filled? tape << order.(:done, reason: :filled) elsif order.fill_or_kill? tape << order.(:done, reason: :killed) else order_side.insert_order(order) tape << order.(:open) end tick! unless (ticker == old_ticker) end end |
#remove_expired! ⇒ Object
Removes all expired orders from the book
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/gekko/book.rb', line 161 def remove_expired! prev_bid = bid prev_ask = ask [bids, asks].each do |bs| bs.reject! do |order| if order.expired? tape << order.(:done, reason: :expired) true end end end tick! if (prev_bid != bid) || (prev_ask != ask) end |
#spread ⇒ Object
Returns the current spread if at least a bid and an ask are present, returns nil
otherwise
197 198 199 |
# File 'lib/gekko/book.rb', line 197 def spread ask && bid && (ask - bid) end |
#tick! ⇒ Object
Emits a ticker on the tape
204 205 206 |
# File 'lib/gekko/book.rb', line 204 def tick! tape << { type: :ticker }.merge(ticker) end |
#ticker ⇒ Hash
Returns the current ticker
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/gekko/book.rb', line 213 def ticker v24h = tape.volume_24h { last: tape.last_trade_price, bid: bid, ask: ask, high_24h: tape.high_24h, low_24h: tape.low_24h, spread: spread, volume_24h: v24h, # We'd like to return +nil+, not +false+ when we don't have any volume vwap_24h: ((v24h > 0) && (tape.quote_volume_24h * multiplier / v24h).to_i) || nil } end |
#to_hash ⇒ Hash
Returns a Hash
representation of this Book
instance
234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/gekko/book.rb', line 234 def to_hash { time: Time.now.to_f, bids: bids.to_hash, asks: asks.to_hash, pair: pair, tape: tape.to_hash, received: received, base_precision: base_precision } end |