gekko 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,12 @@
1
+ require 'uuidtools'
2
+
3
+ # UUID shortcut
4
+ UUID = UUIDTools::UUID
5
+
6
+ require 'gekko/order'
7
+ require 'gekko/book'
8
+
9
+ # The Gekko top-level module
10
+ module Gekko
11
+ end
12
+
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
@@ -0,0 +1,6 @@
1
+ module Gekko
2
+
3
+ # Raised when the price isn't a multiple of the tick size
4
+ class TickSizeMismatch < RuntimeError; end
5
+
6
+ end
@@ -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
@@ -0,0 +1,6 @@
1
+ module Gekko
2
+
3
+ # The Gekko version string
4
+ VERSION = '0.0.1'
5
+
6
+ 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: