gekko 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/README.md +9 -0
- data/lib/gekko.rb +12 -0
- data/lib/gekko/book.rb +105 -0
- data/lib/gekko/book_side.rb +56 -0
- data/lib/gekko/errors.rb +6 -0
- data/lib/gekko/order.rb +83 -0
- data/lib/gekko/tape.rb +27 -0
- data/lib/gekko/version.rb +6 -0
- metadata +111 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5d4ac3dfd5190f9f36de0f900b461fa2c5d77682
|
4
|
+
data.tar.gz: 8e19f4f203d0ffbc4444a516d89476bd12ab9328
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4c398ee4e020fc3f5d3f35786a4d4161d884fb10830a220ad425ae62ad93069196ec3d842daab673b3ae5f4b0b37ef7679fadc7224592c5e3f467e8531e8ae4f
|
7
|
+
data.tar.gz: 6c45b8eef3dfc3b4d479b6ca0ea0aa29adee4bb80da1b9e6e28846e073ff8b9559f9abf2131bf21c5db234a06315055bd4d3d48ba22a6e8ed6521104de42a176
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 David FRANCOIS
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
Gekko [![Build Status](https://secure.travis-ci.org/Paymium/gekko.png?branch=master)](http://travis-ci.org/Paymium/gekko) [![Coverage Status](https://img.shields.io/coveralls/Paymium/gekko.svg)](https://coveralls.io/r/Paymium/gekko?branch=master) [![Gem Version](https://badge.fury.io/rb/gekko.svg)](http://badge.fury.io/rb/gekko)
|
2
|
+
=
|
3
|
+
|
4
|
+
## What Gekko is
|
5
|
+
Gekko is intended to become a high-performance in-memory trade order matching engine.
|
6
|
+
|
7
|
+
## What Gekko is not
|
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
|
+
|
data/lib/gekko.rb
ADDED
data/lib/gekko/book.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'gekko/book_side'
|
2
|
+
require 'gekko/tape'
|
3
|
+
require 'gekko/errors'
|
4
|
+
|
5
|
+
module Gekko
|
6
|
+
|
7
|
+
#
|
8
|
+
# An order book consisting of a bid side and an ask side
|
9
|
+
#
|
10
|
+
class Book
|
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
|
20
|
+
|
21
|
+
def initialize(pair, opts = {})
|
22
|
+
self.pair = pair
|
23
|
+
self.bids = BookSide.new(:bid)
|
24
|
+
self.asks = BookSide.new(:ask)
|
25
|
+
self.tape = Tape.new(opts[:logger])
|
26
|
+
|
27
|
+
self.tick_size = opts[:tick_size] || DEFAULT_TICK_SIZE
|
28
|
+
raise "Tick size must be a positive integer if provided" if tick_size && (!tick_size.is_a?(Fixnum) || tick_size <= 0)
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Receives an order and executes it
|
33
|
+
#
|
34
|
+
# @param order [Order] The order to execute
|
35
|
+
#
|
36
|
+
def receive_order(order)
|
37
|
+
|
38
|
+
raise Gekko::TickSizeMismatch unless (order.price % tick_size).zero?
|
39
|
+
|
40
|
+
order_side = order.bid? ? bids : asks
|
41
|
+
opposite_side = order.bid? ? asks : bids
|
42
|
+
next_match = opposite_side.first
|
43
|
+
|
44
|
+
while !order.filled? && order.crosses?(next_match)
|
45
|
+
|
46
|
+
trade_price = next_match.price
|
47
|
+
base_size = [next_match.remaining, order.remaining].min
|
48
|
+
|
49
|
+
quoted_size = base_size / trade_price
|
50
|
+
|
51
|
+
tape << {
|
52
|
+
type: :execution,
|
53
|
+
price: trade_price,
|
54
|
+
base_size: base_size,
|
55
|
+
quoted_size: quoted_size,
|
56
|
+
maker_order_id: next_match.id,
|
57
|
+
taker_order_id: order.id,
|
58
|
+
time: Time.now.to_f,
|
59
|
+
tick: order.bid? ? :up : :down
|
60
|
+
}
|
61
|
+
|
62
|
+
order.remaining -= base_size
|
63
|
+
next_match.remaining -= base_size
|
64
|
+
|
65
|
+
if next_match.filled?
|
66
|
+
tape << opposite_side.shift.message(:done, reason: :filled)
|
67
|
+
next_match = opposite_side.first
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
if order.filled?
|
72
|
+
tape << order.message(:done, reason: :filled)
|
73
|
+
else
|
74
|
+
order_side.insert_order(order)
|
75
|
+
tape << order.message(:open)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# Returns the current best ask price or +nil+ if there
|
81
|
+
# are currently no asks
|
82
|
+
#
|
83
|
+
def ask
|
84
|
+
asks.top
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# Returns the current best bid price or +nil+ if there
|
89
|
+
# are currently no bids
|
90
|
+
#
|
91
|
+
def bid
|
92
|
+
bids.top
|
93
|
+
end
|
94
|
+
|
95
|
+
#
|
96
|
+
# Returns the current spread if at least a bid and an ask
|
97
|
+
# are present, returns +nil+ otherwise
|
98
|
+
#
|
99
|
+
def spread
|
100
|
+
ask && bid && (ask - bid)
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Gekko
|
2
|
+
|
3
|
+
#
|
4
|
+
# A side of the order book
|
5
|
+
#
|
6
|
+
class BookSide < Array
|
7
|
+
|
8
|
+
# TODO: Insert orders more smartly by using a dichotomy search
|
9
|
+
|
10
|
+
attr_accessor :side
|
11
|
+
|
12
|
+
def initialize(side)
|
13
|
+
raise "Incorrect side <#{side}>" unless [:bid, :ask].include?(side)
|
14
|
+
@side = side
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# Inserts an order in the order book so that it remains sort by ascending
|
19
|
+
# or descending price, depending on what side of the complete book this is.
|
20
|
+
#
|
21
|
+
# @param order [Order] The order to insert
|
22
|
+
#
|
23
|
+
def insert_order(order)
|
24
|
+
raise "Can't insert a #{order.side} order on the #{side} side" unless (side == order.side)
|
25
|
+
|
26
|
+
idx = find_index do |ord|
|
27
|
+
(bid_side? && (ord.price < order.price)) ||
|
28
|
+
(ask_side? && (ord.price > order.price))
|
29
|
+
end
|
30
|
+
|
31
|
+
insert((idx || -1), order)
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# Returns the first order price, or +nil+ if there's no order
|
36
|
+
#
|
37
|
+
def top
|
38
|
+
first && first.price
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Returns +true if this is the ask side
|
43
|
+
#
|
44
|
+
def ask_side?
|
45
|
+
side == :ask
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Returns true if this is the bid side
|
50
|
+
#
|
51
|
+
def bid_side?
|
52
|
+
side == :bid
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
data/lib/gekko/errors.rb
ADDED
data/lib/gekko/order.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
module Gekko
|
2
|
+
|
3
|
+
#
|
4
|
+
# Represents a trade order. Trade orders can be either buy (bid) or sell (ask) orders.
|
5
|
+
# All orders are identified by an UUID, must specify a size, a price, and an optional
|
6
|
+
# expiration timestamp.
|
7
|
+
#
|
8
|
+
class Order
|
9
|
+
|
10
|
+
attr_accessor :id, :side, :size, :remaining, :price, :expiration, :created_at
|
11
|
+
|
12
|
+
def initialize(side, id, size, price, expiration = nil)
|
13
|
+
@id = id
|
14
|
+
@side = side
|
15
|
+
@size = size
|
16
|
+
@remaining = @size
|
17
|
+
@price = price
|
18
|
+
@expiration = expiration
|
19
|
+
@created_at = Time.now.to_f
|
20
|
+
|
21
|
+
raise 'Orders must have an UUID' unless @id && @id.is_a?(UUID)
|
22
|
+
raise 'Side must be either :bid or :ask' unless [:bid, :ask].include?(side)
|
23
|
+
raise 'Price must be a positive integer or be omitted' if (@price && (!@price.is_a?(Fixnum) || (@price <= 0)))
|
24
|
+
raise 'Size must be a positive integer' if (@size && (!@size.is_a?(Fixnum) || @size <= 0))
|
25
|
+
raise 'Expiration must be omitted or be an integer' unless (@expiration.nil? || (@expiration.is_a?(Fixnum) && @expiration > 0))
|
26
|
+
raise 'The order creation timestamp can''t be nil' if !@created_at
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# Returns +true+ if this order can execute against +other_order+
|
31
|
+
#
|
32
|
+
# @param other [Order] The other order against which we want
|
33
|
+
# to know if an execution is possible
|
34
|
+
#
|
35
|
+
def crosses?(other)
|
36
|
+
if other && (bid? ^ other.bid?)
|
37
|
+
(bid? && (price >= other.price)) || (ask? && (price <= other.price))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Returns +true+ if the order is filled
|
43
|
+
#
|
44
|
+
def filled?
|
45
|
+
remaining.zero?
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Creates a message in order to print it ont the tape
|
50
|
+
#
|
51
|
+
# @param type [Symbol] The type of message we're printing
|
52
|
+
# @param extra_attrs [Hash] The extra attributes we're including
|
53
|
+
# in the message
|
54
|
+
#
|
55
|
+
# @return [Hash] The message we'll print on the tape
|
56
|
+
#
|
57
|
+
def message(type, extra_attrs = {})
|
58
|
+
{
|
59
|
+
type: type,
|
60
|
+
order_id: id,
|
61
|
+
side: side,
|
62
|
+
size: size,
|
63
|
+
remaining: remaining,
|
64
|
+
price: price
|
65
|
+
}.merge(extra_attrs)
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
# Returns +true+ if this order is a buy order
|
70
|
+
#
|
71
|
+
def bid?
|
72
|
+
side == :bid
|
73
|
+
end
|
74
|
+
|
75
|
+
#
|
76
|
+
# Returns +true+ if this order is a sell order
|
77
|
+
#
|
78
|
+
def ask?
|
79
|
+
!bid?
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
data/lib/gekko/tape.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Gekko
|
2
|
+
|
3
|
+
#
|
4
|
+
# Records the trading engine messages sequentially
|
5
|
+
#
|
6
|
+
class Tape
|
7
|
+
|
8
|
+
attr_accessor :events, :logger
|
9
|
+
|
10
|
+
def initialize(logger = nil)
|
11
|
+
@events = []
|
12
|
+
@logger = logger
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
# Prints a message on the tape
|
17
|
+
#
|
18
|
+
# @param message [Hash] The message to record
|
19
|
+
#
|
20
|
+
def <<(message)
|
21
|
+
message[:sequence] = events.length
|
22
|
+
logger && logger.info(message)
|
23
|
+
events << message
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gekko
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David FRANCOIS
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: uuidtools
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: simplecov
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Gekko is a bare-bones order matcher whose task is to accept orders and
|
70
|
+
maintain an order book.
|
71
|
+
email:
|
72
|
+
- david.francois@paymium.com
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- LICENSE
|
78
|
+
- README.md
|
79
|
+
- lib/gekko.rb
|
80
|
+
- lib/gekko/book.rb
|
81
|
+
- lib/gekko/book_side.rb
|
82
|
+
- lib/gekko/errors.rb
|
83
|
+
- lib/gekko/order.rb
|
84
|
+
- lib/gekko/tape.rb
|
85
|
+
- lib/gekko/version.rb
|
86
|
+
homepage: https://paymium.com
|
87
|
+
licenses:
|
88
|
+
- MIT
|
89
|
+
metadata: {}
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.3.6
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 2.4.2
|
107
|
+
signing_key:
|
108
|
+
specification_version: 4
|
109
|
+
summary: An in-memory order matching engine.
|
110
|
+
test_files: []
|
111
|
+
has_rdoc:
|