heisencoin 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/heisencoin/arbitrage.rb +100 -0
- data/lib/heisencoin/exchange.rb +26 -0
- data/lib/heisencoin/market.rb +56 -0
- data/lib/heisencoin/offer.rb +37 -0
- data/lib/heisencoin/plan.rb +44 -0
- data/lib/heisencoin/trade.rb +37 -0
- data/tests/arbitrage.rb +2 -2
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 326b290fabe0c62d7550a2dfb52874ed1ca31e30
|
|
4
|
+
data.tar.gz: c573f496f45fa8286c49ab654204c967df72493a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 199af64a408a1f9008b380d330fea5358b0776b03d5c74ea4665b08831d9d8d31fc37802c52aeb1095e738e6fd2bfdf06f1f70fb6d258b7fc268c3a96f351cfe
|
|
7
|
+
data.tar.gz: 67d94d62b442509717b2eccfe5dfa4e982f3a6756fc7ec1d86f88d6f905bebb73a8d37705ab8e91e6e2a756118a4d2e12accdebb567aea3115fa409706ccf9c1
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
module Heisencoin
|
|
2
|
+
class Arbitrage
|
|
3
|
+
attr_accessor :exchanges, :bids, :asks
|
|
4
|
+
|
|
5
|
+
def initialize
|
|
6
|
+
@exchanges = []
|
|
7
|
+
@asks = Market.new(:ask)
|
|
8
|
+
@bids = Market.new(:bid)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def add_exchanges(exchanges)
|
|
12
|
+
@exchanges += exchanges
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def exchange(name)
|
|
16
|
+
exchanges.select{|e| e.name == name}.first
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def add_depth(exchange, depth)
|
|
20
|
+
@asks.drop_offers(exchange)
|
|
21
|
+
@asks.import(exchange, depth["asks"])
|
|
22
|
+
@bids.drop_offers(exchange)
|
|
23
|
+
@bids.import(exchange, depth["bids"])
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def best_price(market1, market2)
|
|
27
|
+
level = nil
|
|
28
|
+
market1.offers.each do |offer|
|
|
29
|
+
good = market2.offers.any?{|offer_other| yield offer.price, offer_other.price}
|
|
30
|
+
if good
|
|
31
|
+
level = offer.price
|
|
32
|
+
break
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
level
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def profitable_asks(highwater=nil)
|
|
39
|
+
highwater ||= best_price(@bids, @asks){|offer, other| offer > other}
|
|
40
|
+
if highwater
|
|
41
|
+
@asks.offers_better_than(highwater)
|
|
42
|
+
else
|
|
43
|
+
[]
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def profitable_bids(lowwater=nil)
|
|
48
|
+
lowwater ||= best_price(@asks, @bids){|offer, other| offer < other}
|
|
49
|
+
if lowwater
|
|
50
|
+
@bids.offers_better_than(lowwater)
|
|
51
|
+
else
|
|
52
|
+
[]
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def trade_all(asks, bids)
|
|
57
|
+
working_bids = bids.dup
|
|
58
|
+
trades = []
|
|
59
|
+
asks.each do |ask|
|
|
60
|
+
step_trades = consume_ask(working_bids, ask, ask.price*(1-ask.exchange.fee))
|
|
61
|
+
trades += step_trades
|
|
62
|
+
qty_traded = step_trades.reduce(0){|memo, trade| memo += trade.quantity} #sum
|
|
63
|
+
break if qty_traded < ask.quantity #out of coins
|
|
64
|
+
end
|
|
65
|
+
trades
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def consume_ask(bids, ask, price_limit)
|
|
69
|
+
trades = []
|
|
70
|
+
price = price_limit
|
|
71
|
+
quantity = ask.quantity
|
|
72
|
+
near_zero = 0.0001 #floats
|
|
73
|
+
bids.each do |bid|
|
|
74
|
+
if (bid.price*(1+bid.exchange.fee)) >= price
|
|
75
|
+
if bid.quantity > near_zero
|
|
76
|
+
trade_quantity = [bid.quantity, quantity].min
|
|
77
|
+
trades << Trade.new({'from_offer' => ask.to_simple,
|
|
78
|
+
'to_offer' => bid.to_simple,
|
|
79
|
+
'quantity' => trade_quantity})
|
|
80
|
+
bid.quantity -= trade_quantity
|
|
81
|
+
quantity -= trade_quantity
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
break if quantity < near_zero
|
|
85
|
+
end
|
|
86
|
+
trades
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def plan
|
|
90
|
+
#buy sell plan
|
|
91
|
+
plan = Plan.new
|
|
92
|
+
plan << trade_all(profitable_asks, profitable_bids)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def spread
|
|
96
|
+
@asks.offers.first.price - @bids.offers.first.price
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'time'
|
|
2
|
+
|
|
3
|
+
module Heisencoin
|
|
4
|
+
class Exchange
|
|
5
|
+
attr_accessor :name, :fee, :time
|
|
6
|
+
|
|
7
|
+
def initialize(simple = nil)
|
|
8
|
+
from_simple(simple) if simple
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def from_simple(simple)
|
|
12
|
+
@name = simple['name']
|
|
13
|
+
@fee = simple['fee']
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_simple
|
|
17
|
+
{'name' => name,
|
|
18
|
+
'fee' => fee}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def ==(other)
|
|
22
|
+
self.name == other.name
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Heisencoin
|
|
2
|
+
class Market
|
|
3
|
+
attr_accessor :offers
|
|
4
|
+
|
|
5
|
+
def initialize(half)
|
|
6
|
+
@offers = []
|
|
7
|
+
if half == :bid || half == :ask
|
|
8
|
+
@half = half
|
|
9
|
+
else
|
|
10
|
+
raise "Bad market type parameter"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def import(exchange, offers)
|
|
15
|
+
offers.each do |raw_offer|
|
|
16
|
+
offer = Offer.from_array(exchange, [raw_offer.first, raw_offer.last])
|
|
17
|
+
sorted_insert(@offers, offer) {|offer| offer.price}
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def drop_offers(exchange)
|
|
22
|
+
offers.reject!{|o| o.exchange == exchange}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def sorted_insert(array, element)
|
|
26
|
+
value = yield element
|
|
27
|
+
if array.length == 0 || better_than(value, (yield array[0]))
|
|
28
|
+
array.unshift(element)
|
|
29
|
+
else
|
|
30
|
+
last_index = array.length-1
|
|
31
|
+
for idx in 0..last_index
|
|
32
|
+
if not better_than((yield array[idx]), value)
|
|
33
|
+
array.insert(idx, element)
|
|
34
|
+
break
|
|
35
|
+
end
|
|
36
|
+
if idx == last_index
|
|
37
|
+
array.push(element)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def better_than(a,b)
|
|
44
|
+
if @half == :ask
|
|
45
|
+
a < b
|
|
46
|
+
elsif @half == :bid
|
|
47
|
+
a > b
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def offers_better_than(price)
|
|
52
|
+
offers.select{|offer| better_than(offer.price, price)}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Heisencoin
|
|
2
|
+
class Offer
|
|
3
|
+
attr_accessor :exchange, :price, :quantity
|
|
4
|
+
|
|
5
|
+
def initialize(simple = nil)
|
|
6
|
+
from_simple(simple) if simple
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def from_simple(simple)
|
|
10
|
+
@exchange = Exchange.new(simple['exchange'])
|
|
11
|
+
@price = simple['price']
|
|
12
|
+
@quantity = simple['quantity']
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_simple
|
|
16
|
+
{'exchange' => exchange.to_simple,
|
|
17
|
+
'price' => price,
|
|
18
|
+
'quantity' => quantity}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def ==(other)
|
|
22
|
+
exchange.name == other.exchange.name
|
|
23
|
+
price == other.price
|
|
24
|
+
quantity = other.quantity
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def cost(quantity)
|
|
28
|
+
quantity * price
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.from_array(exchange, raw_offer)
|
|
32
|
+
Offer.new({'exchange' => exchange.to_simple,
|
|
33
|
+
'price' => raw_offer[0],
|
|
34
|
+
'quantity' => raw_offer[1]})
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Heisencoin
|
|
2
|
+
class Plan
|
|
3
|
+
attr_accessor :steps, :state, :purse, :spent
|
|
4
|
+
|
|
5
|
+
@@states = ["planning", "buying", "moving-in", "selling", "moving-out"]
|
|
6
|
+
|
|
7
|
+
def initialize(simple = nil)
|
|
8
|
+
@steps = []
|
|
9
|
+
@state = @@states.first
|
|
10
|
+
from_simple(simple) if simple
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def from_simple(simple)
|
|
14
|
+
@state = simple["state"]
|
|
15
|
+
@purse = simple["purse"]
|
|
16
|
+
@spent = simple["spent"]
|
|
17
|
+
@steps = simple["steps"].map{|trade_simple| Trade.new(trade_simple)}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_simple
|
|
21
|
+
{"state" => @state,
|
|
22
|
+
"purse" => @purse,
|
|
23
|
+
"spent" => @spent,
|
|
24
|
+
"steps" => @steps.map{|step| step.to_simple}}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def <<(trades)
|
|
28
|
+
@steps += trades
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def quantity
|
|
33
|
+
@steps.reduce(0){|sum,trade|sum+trade.quantity}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def profit
|
|
37
|
+
@steps.reduce(0){|total, trade| total+trade.profit}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def cost
|
|
41
|
+
@steps.reduce(0){|total, trade| total+trade.cost}
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Heisencoin
|
|
2
|
+
class Trade
|
|
3
|
+
attr_accessor :from_offer, :to_offer, :quantity
|
|
4
|
+
|
|
5
|
+
def initialize(simple = nil)
|
|
6
|
+
from_simple(simple) if simple
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def from_simple(simple)
|
|
10
|
+
@from_offer = Offer.new(simple['from_offer'])
|
|
11
|
+
@to_offer = Offer.new(simple['to_offer'])
|
|
12
|
+
@quantity = simple['quantity']
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_simple
|
|
16
|
+
{'from_offer' => @from_offer.to_simple,
|
|
17
|
+
'to_offer' => @to_offer.to_simple,
|
|
18
|
+
'quantity' => @quantity}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def ==(other)
|
|
22
|
+
@from_offer == other.from_offer
|
|
23
|
+
@to_offer == other.to_offer
|
|
24
|
+
@quantity == other.quantity
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def cost
|
|
28
|
+
from_offer.cost(@quantity)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def profit
|
|
32
|
+
earned = to_offer.cost(@quantity)
|
|
33
|
+
earned - cost
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
end
|
data/tests/arbitrage.rb
CHANGED
|
@@ -52,8 +52,8 @@ class TestMeme < Minitest::Test
|
|
|
52
52
|
before do
|
|
53
53
|
# full setup
|
|
54
54
|
@arby = Heisencoin::Arbitrage.new
|
|
55
|
-
@ex1 = Heisencoin::Exchange.new({'name' =>'btcx'})
|
|
56
|
-
@ex2 = Heisencoin::Exchange.new({'name' =>'crytpsy'})
|
|
55
|
+
@ex1 = Heisencoin::Exchange.new({'name' =>'btcx', 'fee' => 0.2})
|
|
56
|
+
@ex2 = Heisencoin::Exchange.new({'name' =>'crytpsy', 'fee' => 0.3})
|
|
57
57
|
@arby.add_exchanges([@ex1, @ex2])
|
|
58
58
|
# ex1 has a 14.1 and 14.2 bid above ex2's 14 ask
|
|
59
59
|
depth = {"asks" => [ [16,1],
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: heisencoin
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Don Park
|
|
@@ -17,6 +17,12 @@ extensions: []
|
|
|
17
17
|
extra_rdoc_files: []
|
|
18
18
|
files:
|
|
19
19
|
- lib/heisencoin.rb
|
|
20
|
+
- lib/heisencoin/trade.rb
|
|
21
|
+
- lib/heisencoin/market.rb
|
|
22
|
+
- lib/heisencoin/exchange.rb
|
|
23
|
+
- lib/heisencoin/plan.rb
|
|
24
|
+
- lib/heisencoin/offer.rb
|
|
25
|
+
- lib/heisencoin/arbitrage.rb
|
|
20
26
|
- tests/trade.rb
|
|
21
27
|
- tests/offer.rb
|
|
22
28
|
- tests/arbitrage.rb
|