gekko 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c22a43388f516eb29f65b1c7439018b7d0b62b2d
4
- data.tar.gz: f5b5b3011d40a9dfa97b3573b166af0c772023cd
3
+ metadata.gz: 95df87c9c162b780e408b2761062ff2230542f22
4
+ data.tar.gz: 4468b3d3bc4f53aa31316321ce9908f63c899704
5
5
  SHA512:
6
- metadata.gz: 2c486804dc2109f896f2d98cb160b099e73dbe157c465f21bf61f7cfa56f36794ceac3886677ebd802102bc90d0c5e0059aeaa354c590047b152a39746dd6bac
7
- data.tar.gz: 5c303f98396a43ba09640630f8bb8f649b064819f05a5b76c4fc1e75f7bdc0d2e58411bb8fc186d18b96006e5b1aa916750ce60d9915fb5e9fb64eae5baceed3
6
+ metadata.gz: 5d66764b99cc061c8fc8f6c9750fbb64f478c4d817b04cefbff70a718589225eb5fc3df0fbd935b9f06b36fa5618817eda6c685d0a764e959377b71d7795fc95
7
+ data.tar.gz: dffa0324039b7c5aa10772087af12e763a5de8f25e1f9ff7ff906ecd31002d005b723e0b736a690c71a62c60f734226bc252b052c5b89444c4f84cce5cbaeacf
data/README.md CHANGED
@@ -7,3 +7,10 @@ Gekko is intended to become a high-performance in-memory trade order matching en
7
7
  ## What Gekko is not
8
8
  Gekko is not intended to maintain an accounting database, it just matches trade orders associated to accounts, and returns the necessary data for an accounting system to record the trades (usually against some sort of RDBMS).
9
9
 
10
+ ## Left to do
11
+ The following items are left to do and will need to be implemented before gekko is considered production-ready.
12
+
13
+ - Persistence and failure recovery
14
+ - Add and enforce minimum and maximum order sizes
15
+ - Correctly handle order expiration
16
+
data/lib/gekko/book.rb CHANGED
@@ -9,25 +9,15 @@ module Gekko
9
9
  #
10
10
  class Book
11
11
 
12
- # TODO: Add order size limits
13
- # TODO: Add order expiration
14
- # TODO: Test for rounding issues
15
-
16
- # The default minimum price increment accepted for placed orders
17
- DEFAULT_TICK_SIZE = 1000
18
-
19
- attr_accessor :pair, :bids, :asks, :tape, :tick_size, :received, :base_precision
12
+ attr_accessor :pair, :bids, :asks, :tape, :received, :base_precision
20
13
 
21
14
  def initialize(pair, opts = {})
22
15
  self.pair = pair
23
16
  self.bids = BookSide.new(:bid)
24
17
  self.asks = BookSide.new(:ask)
25
18
  self.tape = Tape.new(opts[:logger])
26
- self.base_precision = 8
19
+ self.base_precision = opts[:base_precision] || 8
27
20
  self.received = {}
28
-
29
- self.tick_size = opts[:tick_size] || DEFAULT_TICK_SIZE
30
- raise "Tick size must be a positive integer if provided" if tick_size && (!tick_size.is_a?(Fixnum) || tick_size <= 0)
31
21
  end
32
22
 
33
23
  #
@@ -39,12 +29,12 @@ module Gekko
39
29
 
40
30
  raise 'Order must be a Gekko::LimitOrder or a Gekko::MarketOrder' unless [LimitOrder, MarketOrder].include?(order.class)
41
31
 
42
- raise Gekko::TickSizeMismatch unless (order.is_a?(MarketOrder) || (order.price % tick_size).zero?)
43
-
44
32
  if received.has_key?(order.id.to_s)
45
33
  tape << order.message(:reject, reason: "Duplicate ID <#{order.id.to_s}>")
46
34
 
47
35
  else
36
+ old_ticker = ticker
37
+
48
38
  self.received[order.id.to_s] = order
49
39
  tape << order.message(:received)
50
40
 
@@ -70,7 +60,6 @@ module Gekko
70
60
  end
71
61
  end
72
62
 
73
-
74
63
  tape << {
75
64
  type: :execution,
76
65
  price: trade_price,
@@ -99,9 +88,27 @@ module Gekko
99
88
  order_side.insert_order(order)
100
89
  tape << order.message(:open)
101
90
  end
91
+
92
+ tick! unless (ticker == old_ticker)
102
93
  end
103
94
  end
104
95
 
96
+ #
97
+ # Cancels an order given an ID
98
+ #
99
+ # @param order_id [String] The ID of the order to cancel
100
+ #
101
+ def cancel(order_id)
102
+ prev_bid = bid
103
+ prev_ask = ask
104
+
105
+ order = received[order_id.to_s]
106
+ dels = order.bid? ? bids.delete(order) : asks.delete(order)
107
+ dels && tape << order.message(:done, reason: :cancelled)
108
+
109
+ tick! if (prev_bid != bid) || (prev_ask != ask)
110
+ end
111
+
105
112
  #
106
113
  # Returns the current best ask price or +nil+ if there
107
114
  # are currently no asks
@@ -126,6 +133,13 @@ module Gekko
126
133
  ask && bid && (ask - bid)
127
134
  end
128
135
 
136
+ #
137
+ # Emits a ticker on the tape
138
+ #
139
+ def tick!
140
+ tape << { type: :ticker }.merge(ticker)
141
+ end
142
+
129
143
  #
130
144
  # Returns the current ticker
131
145
  #
@@ -137,6 +151,8 @@ module Gekko
137
151
  last: tape.last_trade_price,
138
152
  bid: bid,
139
153
  ask: ask,
154
+ high_24h: tape.high_24h,
155
+ low_24h: tape.low_24h,
140
156
  spread: spread,
141
157
  volume_24h: v24h,
142
158
  vwap_24h: (v24h > 0) && (tape.quote_volume_24h * (10 ** base_precision)/ v24h)
data/lib/gekko/errors.rb CHANGED
@@ -1,6 +1,3 @@
1
1
  module Gekko
2
2
 
3
- # Raised when the price isn't a multiple of the tick size
4
- class TickSizeMismatch < RuntimeError; end
5
-
6
3
  end
data/lib/gekko/tape.rb CHANGED
@@ -24,17 +24,11 @@ module Gekko
24
24
  message[:sequence] = length
25
25
  logger && logger.info(message)
26
26
 
27
- if message[:type] == :execution
28
- # Keep last price up to date
29
- @last_trade_price = message[:price]
27
+ super(message)
30
28
 
31
- # Keep 24h volume up to date
32
- @volume_24h += message[:base_size]
33
- @quote_volume_24h += message[:quote_size]
34
- move_24h_cursor!
29
+ if message[:type] == :execution
30
+ update_ticker(message)
35
31
  end
36
-
37
- super(message)
38
32
  end
39
33
 
40
34
  #
@@ -60,6 +54,47 @@ module Gekko
60
54
  @volume_24h
61
55
  end
62
56
 
57
+ #
58
+ # Returns the highest trade price that occurred during the last 24h
59
+ #
60
+ # @return [Fixnum] The last 24h high
61
+ #
62
+ def high_24h
63
+ move_24h_cursor!
64
+ @high_24h
65
+ end
66
+
67
+ #
68
+ # Returns the lowest trade price that occurred during the last 24h
69
+ #
70
+ # @return [Fixnum] The last 24h low
71
+ #
72
+ def low_24h
73
+ move_24h_cursor!
74
+ @low_24h
75
+ end
76
+
77
+ #
78
+ # Recalculates the previous 24h high and low
79
+ #
80
+ def recalc_high_low_24h!
81
+ @high_24h = nil
82
+ @low_24h = nil
83
+
84
+ # Work backwards from current position until the cursor points to an event
85
+ # that's older than 24h
86
+ tmp_cursor = (length - 1)
87
+ evt = self[tmp_cursor]
88
+
89
+ while (evt && (evt[:time] >= time_24h_ago)) do
90
+ @high_24h = ((@high_24h.nil? || (evt[:price] > @high_24h)) && evt[:price]) || @high_24h
91
+ @low_24h = ((@low_24h.nil? || (evt[:price] < @low_24h)) && evt[:price]) || @low_24h
92
+
93
+ tmp_cursor -= 1
94
+ evt = (tmp_cursor >= 0) && self[tmp_cursor]
95
+ end
96
+ end
97
+
63
98
  #
64
99
  # Returns the traded amount of quote currency in the last 24h
65
100
  #
@@ -70,22 +105,67 @@ module Gekko
70
105
  @quote_volume_24h
71
106
  end
72
107
 
108
+ #
109
+ # Updates the ticker after an execution has been recorded
110
+ #
111
+ def update_ticker(execution)
112
+ price = execution[:price]
113
+
114
+ # Keep last price up to date
115
+ @last_trade_price = price
116
+
117
+ # Keep 24h volume up to date
118
+ @volume_24h += execution[:base_size]
119
+ @quote_volume_24h += execution[:quote_size]
120
+
121
+ # Record new high/lows
122
+ if @high_24h.nil? || (@high_24h < price)
123
+ @high_24h = price
124
+ end
125
+
126
+ if @low_24h.nil? || (price < @low_24h)
127
+ @low_24h = price
128
+ end
129
+
130
+ move_24h_cursor!
131
+ end
132
+
133
+ #
134
+ # Returns the float timestamp of 24h ago
135
+ #
136
+ # @return [Float] Yesterday's cut-off timestamp
137
+ #
138
+ def time_24h_ago
139
+ Time.now.to_f - 24*3600
140
+ end
141
+
73
142
  #
74
143
  # Moves the cursor pointing to the first trade that happened during
75
- # the last 24h and updates the volume along the way
144
+ # the last 24h. Every execution getting out of the 24h rolling window is
145
+ # passed to Tape#fall_out_of_24h_window
76
146
  #
77
147
  def move_24h_cursor!
78
- time_24h_ago = Time.now.to_f - 24*3600
79
-
80
148
  while(self[@cursor_24h] && ((self[@cursor_24h][:type] != :execution) || (self[@cursor_24h][:time] < time_24h_ago)))
81
- if self[@cursor_24h] && self[@cursor_24h][:type] == :execution
82
- @volume_24h -= self[@cursor_24h][:base_size]
83
- @quote_volume_24h -= self[@cursor_24h][:quote_size]
149
+ if self[@cursor_24h][:type] == :execution
150
+ fall_out_of_24h_window(self[@cursor_24h])
84
151
  end
85
152
 
86
153
  @cursor_24h += 1
87
154
  end
88
155
  end
156
+
157
+ #
158
+ # Updates the low, high, and volumes when an execution falls out of the rolling
159
+ # previous 24h window
160
+ #
161
+ def fall_out_of_24h_window(execution)
162
+ @volume_24h -= execution[:base_size]
163
+ @quote_volume_24h -= execution[:quote_size]
164
+
165
+ if [@high_24h, @low_24h].include?(execution[:price])
166
+ recalc_high_low_24h!
167
+ end
168
+ end
89
169
  end
90
170
  end
91
171
 
data/lib/gekko/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Gekko
2
2
 
3
3
  # The Gekko version string
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.1'
5
5
 
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gekko
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David FRANCOIS
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-03 00:00:00.000000000 Z
11
+ date: 2015-02-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: uuidtools