bitex_bot 0.3.7 → 0.4.0
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/.circleci/config.yml +63 -0
- data/.rubocop.yml +33 -0
- data/Gemfile +1 -1
- data/Rakefile +1 -1
- data/bin/bitex_bot +1 -1
- data/bitex_bot.gemspec +34 -34
- data/lib/bitex_bot/database.rb +67 -67
- data/lib/bitex_bot/models/api_wrappers/api_wrapper.rb +142 -0
- data/lib/bitex_bot/models/api_wrappers/bitstamp/bitstamp_api_wrapper.rb +137 -0
- data/lib/bitex_bot/models/api_wrappers/itbit/itbit_api_wrapper.rb +116 -0
- data/lib/bitex_bot/models/api_wrappers/kraken/kraken_api_wrapper.rb +111 -0
- data/lib/bitex_bot/models/api_wrappers/kraken/kraken_order.rb +117 -0
- data/lib/bitex_bot/models/buy_closing_flow.rb +23 -16
- data/lib/bitex_bot/models/buy_opening_flow.rb +48 -54
- data/lib/bitex_bot/models/close_buy.rb +2 -2
- data/lib/bitex_bot/models/closing_flow.rb +98 -79
- data/lib/bitex_bot/models/open_buy.rb +11 -10
- data/lib/bitex_bot/models/open_sell.rb +11 -10
- data/lib/bitex_bot/models/opening_flow.rb +157 -99
- data/lib/bitex_bot/models/order_book_simulator.rb +62 -67
- data/lib/bitex_bot/models/sell_closing_flow.rb +25 -20
- data/lib/bitex_bot/models/sell_opening_flow.rb +47 -54
- data/lib/bitex_bot/models/store.rb +3 -1
- data/lib/bitex_bot/robot.rb +203 -176
- data/lib/bitex_bot/settings.rb +71 -12
- data/lib/bitex_bot/version.rb +1 -1
- data/lib/bitex_bot.rb +40 -16
- data/settings.rb.sample +43 -66
- data/spec/bitex_bot/settings_spec.rb +87 -15
- data/spec/factories/bitex_buy.rb +3 -3
- data/spec/factories/bitex_sell.rb +3 -3
- data/spec/factories/buy_opening_flow.rb +1 -1
- data/spec/factories/open_buy.rb +12 -10
- data/spec/factories/open_sell.rb +12 -10
- data/spec/factories/sell_opening_flow.rb +1 -1
- data/spec/models/api_wrappers/bitstamp_api_wrapper_spec.rb +200 -0
- data/spec/models/api_wrappers/itbit_api_wrapper_spec.rb +176 -0
- data/spec/models/api_wrappers/kraken_api_wrapper_spec.rb +209 -0
- data/spec/models/bitex_api_spec.rb +1 -1
- data/spec/models/buy_closing_flow_spec.rb +140 -71
- data/spec/models/buy_opening_flow_spec.rb +126 -56
- data/spec/models/order_book_simulator_spec.rb +10 -10
- data/spec/models/robot_spec.rb +61 -47
- data/spec/models/sell_closing_flow_spec.rb +130 -62
- data/spec/models/sell_opening_flow_spec.rb +129 -60
- data/spec/spec_helper.rb +19 -16
- data/spec/support/bitex_stubs.rb +13 -14
- data/spec/support/bitstamp/bitstamp_api_wrapper_stubs.rb +35 -0
- data/spec/support/bitstamp/bitstamp_stubs.rb +91 -0
- metadata +60 -42
- data/lib/bitex_bot/models/bitfinex_api_wrapper.rb +0 -118
- data/lib/bitex_bot/models/bitstamp_api_wrapper.rb +0 -82
- data/lib/bitex_bot/models/itbit_api_wrapper.rb +0 -68
- data/lib/bitex_bot/models/kraken_api_wrapper.rb +0 -188
- data/spec/models/bitfinex_api_wrapper_spec.rb +0 -17
- data/spec/models/bitstamp_api_wrapper_spec.rb +0 -15
- data/spec/models/itbit_api_wrapper_spec.rb +0 -15
- data/spec/support/bitstamp_stubs.rb +0 -110
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '027380d0b5310a8656d1e4ea0357617f1c750c1a'
|
4
|
+
data.tar.gz: 66974805bb9b7b130181e737f688512d8f234d17
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d70e171eb1bae487ab0866d441c7c7ac3711d129976a4bf0351d1a1848834dbb9b4f2cb9227af7d2f73df7a3aa200052dc48028b8a8b0c0fa753d775ba21070a
|
7
|
+
data.tar.gz: 4bc5ae223b7ddde9b07b8397ce2c5fcbc25b89b289084b57bc357448c8d6c822e301a0814f657ccf89a9d12a57f6d56d5cabdd1d2d88f7393a36b00e18c1480b
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Ruby CircleCI 2.0 configuration file
|
2
|
+
#
|
3
|
+
# Check https://circleci.com/docs/2.0/language-ruby/ for more details
|
4
|
+
#
|
5
|
+
version: 2
|
6
|
+
jobs:
|
7
|
+
build:
|
8
|
+
docker:
|
9
|
+
# specify the version you desire here
|
10
|
+
- image: circleci/ruby:2.4.1-node-browsers
|
11
|
+
|
12
|
+
# Specify service dependencies here if necessary
|
13
|
+
# CircleCI maintains a library of pre-built images
|
14
|
+
# documented at https://circleci.com/docs/2.0/circleci-images/
|
15
|
+
# - image: circleci/postgres:9.4
|
16
|
+
|
17
|
+
working_directory: ~/repo
|
18
|
+
|
19
|
+
steps:
|
20
|
+
- checkout
|
21
|
+
|
22
|
+
# Download and cache dependencies
|
23
|
+
- restore_cache:
|
24
|
+
keys:
|
25
|
+
- v1-dependencies-{{ checksum "bitex_bot.gemspec" }}-{{ checksum "Gemfile" }}
|
26
|
+
# fallback to using the latest cache if no exact match is found
|
27
|
+
- v1-dependencies-
|
28
|
+
|
29
|
+
- run:
|
30
|
+
name: install dependencies
|
31
|
+
command: |
|
32
|
+
bundle install --jobs=4 --retry=3 --path vendor/bundle
|
33
|
+
|
34
|
+
- save_cache:
|
35
|
+
paths:
|
36
|
+
- ./vendor/bundle
|
37
|
+
key: v1-dependencies-{{ checksum "bitex_bot.gemspec" }}-{{ checksum "Gemfile" }}
|
38
|
+
|
39
|
+
# run tests!
|
40
|
+
- run:
|
41
|
+
name: run tests
|
42
|
+
command: |
|
43
|
+
mkdir /tmp/test-results
|
44
|
+
TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"
|
45
|
+
|
46
|
+
bundle exec rspec --format progress \
|
47
|
+
--format RspecJunitFormatter \
|
48
|
+
--out /tmp/test-results/rspec.xml \
|
49
|
+
--format progress \
|
50
|
+
$TEST_FILES
|
51
|
+
|
52
|
+
# run lints
|
53
|
+
- run:
|
54
|
+
name: run lints
|
55
|
+
command: |
|
56
|
+
bundle exec rubocop
|
57
|
+
|
58
|
+
# collect reports
|
59
|
+
- store_test_results:
|
60
|
+
path: /tmp/test-results
|
61
|
+
- store_artifacts:
|
62
|
+
path: /tmp/test-results
|
63
|
+
destination: test-results
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
Style/FrozenStringLiteralComment:
|
2
|
+
EnforcedStyle: never
|
3
|
+
|
4
|
+
Metrics/AbcSize:
|
5
|
+
Max: 16
|
6
|
+
|
7
|
+
Metrics/ClassLength:
|
8
|
+
Max: 130
|
9
|
+
|
10
|
+
Metrics/LineLength:
|
11
|
+
Max: 130
|
12
|
+
|
13
|
+
Metrics/MethodLength:
|
14
|
+
Max: 25
|
15
|
+
|
16
|
+
Metrics/ParameterLists:
|
17
|
+
Max: 7
|
18
|
+
|
19
|
+
AllCops:
|
20
|
+
TargetRubyVersion: 2.4
|
21
|
+
Exclude:
|
22
|
+
- 'Gemfile'
|
23
|
+
- 'Gemfile.lock'
|
24
|
+
- 'LICENSE.txt'
|
25
|
+
- 'README.md'
|
26
|
+
- 'Rakefile'
|
27
|
+
- 'bin/*'
|
28
|
+
- 'bitex_bot.gemspec'
|
29
|
+
- 'settings.rb.sample'
|
30
|
+
- 'spec/**/*'
|
31
|
+
- 'vendor/**/*'
|
32
|
+
- 'lib/bitex_bot/database.rb'
|
33
|
+
- 'lib/bitex_bot/version.rb'
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
data/bin/bitex_bot
CHANGED
data/bitex_bot.gemspec
CHANGED
@@ -1,44 +1,44 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
3
|
require 'bitex_bot/version'
|
5
4
|
|
6
5
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
6
|
+
spec.name = 'bitex_bot'
|
8
7
|
spec.version = BitexBot::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
11
|
-
spec.description = %
|
12
|
-
|
13
|
-
|
14
|
-
spec.
|
15
|
-
|
16
|
-
spec.homepage = ""
|
17
|
-
spec.license = "MIT"
|
8
|
+
spec.authors = %w[Nubis Eromirou]
|
9
|
+
spec.email = %w[nb@bitex.la tr@bitex.la]
|
10
|
+
spec.description = %(Both a trading robot and a library to build trading robots. The bitex-bot lets you buy cheap on bitex
|
11
|
+
and sell on another exchange and vice versa.)
|
12
|
+
spec.summary = %(A trading robot to do arbitrage between bitex.la and other exchanges!)
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'MIT'
|
18
15
|
|
19
|
-
spec.files = `git ls-files`.split(
|
16
|
+
spec.files = `git ls-files`.split($RS)
|
20
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
21
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
22
|
-
spec.require_paths = [
|
23
|
-
|
24
|
-
spec.add_dependency "activerecord", "~> 4.2"
|
25
|
-
spec.add_dependency "sqlite3"
|
26
|
-
spec.add_dependency "bitstamp"
|
27
|
-
spec.add_dependency "bitex", "0.3"
|
28
|
-
spec.add_dependency "itbit", "0.0.6"
|
29
|
-
spec.add_dependency "bitfinex-rb", "0.0.6"
|
30
|
-
spec.add_dependency "kraken_client", "~> 1.2.1"
|
31
|
-
spec.add_dependency "mail"
|
32
|
-
spec.add_dependency "hashie", "~> 3.5.4"
|
19
|
+
spec.require_paths = %w[lib]
|
33
20
|
|
34
|
-
spec.
|
35
|
-
spec.
|
36
|
-
spec.
|
37
|
-
spec.
|
38
|
-
|
39
|
-
spec.
|
40
|
-
spec.
|
41
|
-
spec.
|
42
|
-
spec.
|
43
|
-
|
21
|
+
spec.add_dependency 'activerecord', '~> 4.2'
|
22
|
+
spec.add_dependency 'hashie', '~> 3.5.4'
|
23
|
+
spec.add_dependency 'mail'
|
24
|
+
spec.add_dependency 'sqlite3'
|
25
|
+
|
26
|
+
spec.add_dependency 'bitex', '0.5'
|
27
|
+
spec.add_dependency 'bitstamp'
|
28
|
+
spec.add_dependency 'itbit', '0.0.6'
|
29
|
+
spec.add_dependency 'kraken_client', '~> 1.2.1'
|
30
|
+
|
31
|
+
spec.add_development_dependency 'bundler'
|
32
|
+
spec.add_development_dependency 'rake'
|
33
|
+
|
34
|
+
spec.add_development_dependency 'byebug'
|
35
|
+
spec.add_development_dependency 'database_cleaner'
|
36
|
+
spec.add_development_dependency 'factory_bot'
|
37
|
+
spec.add_development_dependency 'rspec'
|
38
|
+
spec.add_development_dependency 'rspec-mocks'
|
39
|
+
spec.add_development_dependency 'rspec_junit_formatter'
|
40
|
+
spec.add_development_dependency 'rubocop'
|
41
|
+
spec.add_development_dependency 'shoulda-matchers'
|
42
|
+
spec.add_development_dependency 'timecop'
|
43
|
+
spec.add_development_dependency 'webmock'
|
44
44
|
end
|
data/lib/bitex_bot/database.rb
CHANGED
@@ -1,113 +1,113 @@
|
|
1
1
|
module BitexBot
|
2
2
|
module Database
|
3
|
-
#ActiveRecord::Base.logger = Logger.new(File.open('database.log', 'w'))
|
4
3
|
ActiveRecord::Base.establish_connection(Settings.database)
|
5
4
|
|
6
5
|
ActiveRecord::Schema.define(version: 1) do
|
7
6
|
if ActiveRecord::Base.connection.tables.empty?
|
8
7
|
create_table :buy_opening_flows do |t|
|
9
|
-
t.decimal
|
10
|
-
t.decimal
|
11
|
-
t.decimal
|
12
|
-
t.integer
|
13
|
-
t.string
|
14
|
-
t.index
|
15
|
-
t.timestamps
|
8
|
+
t.decimal :price, precision: 30, scale: 15
|
9
|
+
t.decimal :value_to_use, precision: 30, scale: 15
|
10
|
+
t.decimal :suggested_closing_price, precision: 30, scale: 15
|
11
|
+
t.integer :order_id
|
12
|
+
t.string :status, null: false, default: 'executing'
|
13
|
+
t.index :status
|
14
|
+
t.timestamps null: true
|
16
15
|
end
|
17
16
|
add_index :buy_opening_flows, :order_id
|
18
17
|
|
19
18
|
create_table :sell_opening_flows do |t|
|
20
|
-
t.decimal
|
21
|
-
t.decimal
|
22
|
-
t.decimal
|
23
|
-
t.integer
|
24
|
-
t.string
|
25
|
-
t.index
|
26
|
-
t.timestamps
|
19
|
+
t.decimal :price, precision: 30, scale: 15
|
20
|
+
t.decimal :value_to_use, precision: 30, scale: 15
|
21
|
+
t.decimal :suggested_closing_price, precision: 30, scale: 15
|
22
|
+
t.integer :order_id
|
23
|
+
t.string :status, null: false, default: 'executing'
|
24
|
+
t.index :status
|
25
|
+
t.timestamps null: true
|
27
26
|
end
|
28
27
|
add_index :sell_opening_flows, :order_id
|
29
|
-
|
28
|
+
|
30
29
|
create_table :open_buys do |t|
|
31
30
|
t.belongs_to :opening_flow
|
32
31
|
t.belongs_to :closing_flow
|
33
|
-
t.decimal
|
34
|
-
t.decimal
|
35
|
-
t.decimal
|
36
|
-
t.integer
|
37
|
-
t.timestamps
|
32
|
+
t.decimal :price, precision: 30, scale: 15
|
33
|
+
t.decimal :amount, precision: 30, scale: 15
|
34
|
+
t.decimal :quantity, precision: 30, scale: 15
|
35
|
+
t.integer :transaction_id
|
36
|
+
t.timestamps null: true
|
38
37
|
end
|
39
38
|
add_index :open_buys, :transaction_id
|
40
39
|
|
41
40
|
create_table :open_sells do |t|
|
42
41
|
t.belongs_to :opening_flow
|
43
42
|
t.belongs_to :closing_flow
|
44
|
-
t.decimal
|
45
|
-
t.decimal
|
46
|
-
t.decimal
|
47
|
-
t.integer
|
48
|
-
t.timestamps
|
43
|
+
t.decimal :price, precision: 30, scale: 15
|
44
|
+
t.decimal :quantity, precision: 30, scale: 15
|
45
|
+
t.decimal :amount, precision: 30, scale: 15
|
46
|
+
t.integer :transaction_id
|
47
|
+
t.timestamps null: true
|
49
48
|
end
|
50
49
|
add_index :open_sells, :transaction_id
|
51
|
-
|
50
|
+
|
52
51
|
create_table :buy_closing_flows do |t|
|
53
|
-
t.decimal
|
54
|
-
t.decimal
|
55
|
-
t.decimal
|
56
|
-
t.boolean
|
57
|
-
t.decimal
|
58
|
-
t.decimal
|
59
|
-
t.
|
52
|
+
t.decimal :desired_price, precision: 30, scale: 15
|
53
|
+
t.decimal :quantity, precision: 30, scale: 15
|
54
|
+
t.decimal :amount, precision: 30, scale: 15
|
55
|
+
t.boolean :done, null: false, default: false
|
56
|
+
t.decimal :btc_profit, precision: 30, scale: 15
|
57
|
+
t.decimal :fiat_profit, precision: 30, scale: 15
|
58
|
+
t.decimal :fx_rate, precision: 20, scale: 8
|
59
|
+
t.timestamps null: true
|
60
60
|
end
|
61
61
|
|
62
62
|
create_table :sell_closing_flows do |t|
|
63
|
-
t.decimal
|
64
|
-
t.decimal
|
65
|
-
t.decimal
|
66
|
-
t.boolean
|
67
|
-
t.decimal
|
68
|
-
t.decimal
|
69
|
-
t.
|
63
|
+
t.decimal :desired_price, precision: 30, scale: 15
|
64
|
+
t.decimal :quantity, precision: 30, scale: 15
|
65
|
+
t.decimal :amount, precision: 30, scale: 15
|
66
|
+
t.boolean :done, null: false, default: false
|
67
|
+
t.decimal :btc_profit, precision: 30, scale: 15
|
68
|
+
t.decimal :fiat_profit, precision: 30, scale: 15
|
69
|
+
t.decimal :fx_rate, precision: 20, scale: 8
|
70
|
+
t.timestamps null: true
|
70
71
|
end
|
71
|
-
|
72
|
+
|
72
73
|
create_table :close_buys do |t|
|
73
74
|
t.belongs_to :closing_flow
|
74
|
-
t.decimal
|
75
|
-
t.decimal
|
76
|
-
t.string
|
77
|
-
t.timestamps
|
75
|
+
t.decimal :amount, precision: 30, scale: 15
|
76
|
+
t.decimal :quantity, precision: 30, scale: 15
|
77
|
+
t.string :order_id
|
78
|
+
t.timestamps null: true
|
78
79
|
end
|
79
80
|
add_index :close_buys, :order_id
|
80
81
|
|
81
82
|
create_table :close_sells do |t|
|
82
83
|
t.belongs_to :closing_flow
|
83
|
-
t.decimal
|
84
|
-
t.decimal
|
85
|
-
t.string
|
86
|
-
t.timestamps
|
84
|
+
t.decimal :amount, precision: 30, scale: 15
|
85
|
+
t.decimal :quantity, precision: 30, scale: 15
|
86
|
+
t.string :order_id
|
87
|
+
t.timestamps null: true
|
87
88
|
end
|
88
89
|
add_index :close_sells, :order_id
|
89
90
|
end
|
90
91
|
|
91
92
|
unless ActiveRecord::Base.connection.table_exists?('stores')
|
92
|
-
create_table
|
93
|
-
t.decimal
|
94
|
-
t.decimal
|
95
|
-
t.boolean
|
96
|
-
t.text
|
97
|
-
t.decimal
|
98
|
-
t.decimal
|
99
|
-
t.decimal
|
100
|
-
t.decimal
|
101
|
-
t.datetime
|
102
|
-
t.decimal
|
103
|
-
t.decimal
|
104
|
-
t.decimal
|
105
|
-
t.decimal
|
106
|
-
t.
|
93
|
+
create_table :stores, force: true do |t|
|
94
|
+
t.decimal :taker_fiat, precision: 20, scale: 8
|
95
|
+
t.decimal :taker_btc, precision: 20, scale: 8
|
96
|
+
t.boolean :hold, default: false
|
97
|
+
t.text :log
|
98
|
+
t.decimal :fiat_stop, precision: 20, scale: 8
|
99
|
+
t.decimal :fiat_warning, precision: 20, scale: 8
|
100
|
+
t.decimal :btc_stop, precision: 20, scale: 8
|
101
|
+
t.decimal :btc_warning, precision: 20, scale: 8
|
102
|
+
t.datetime :last_warning
|
103
|
+
t.decimal :buying_profit, precision: 20, scale: 8
|
104
|
+
t.decimal :selling_profit, precision: 20, scale: 8
|
105
|
+
t.decimal :buying_amount_to_spend_per_order, precision: 20, scale: 8
|
106
|
+
t.decimal :selling_quantity_to_sell_per_order, precision: 20, scale: 8
|
107
|
+
t.decimal :fx_rate, precision: 20, scale: 8
|
108
|
+
t.timestamps null: true
|
107
109
|
end
|
108
110
|
end
|
109
111
|
end
|
110
|
-
|
111
112
|
end
|
112
113
|
end
|
113
|
-
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# This class represents the general behaviour for trading platform wrappers.
|
2
|
+
class ApiWrapper
|
3
|
+
MIN_AMOUNT = 5
|
4
|
+
|
5
|
+
Transaction = Struct.new(
|
6
|
+
:id, # Integer
|
7
|
+
:price, # Decimal
|
8
|
+
:amount, # Decimal
|
9
|
+
:timestamp # Epoch Integer
|
10
|
+
)
|
11
|
+
|
12
|
+
Order = Struct.new(
|
13
|
+
:id, # String
|
14
|
+
:type, # Symbol
|
15
|
+
:price, # Decimal
|
16
|
+
:amount, # Decimal
|
17
|
+
:timestamp, # Integer
|
18
|
+
:raw_order # Actual order object
|
19
|
+
) do
|
20
|
+
def method_missing(method_name, *args, &block)
|
21
|
+
raw_order.respond_to?(method_name) ? raw_order.send(method_name, *args, &block) : super
|
22
|
+
end
|
23
|
+
|
24
|
+
def respond_to_missing?(method_name, include_private = false)
|
25
|
+
raw_order.respond_to?(method_name) || super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
OrderBook = Struct.new(
|
30
|
+
:timestamp, # Integer
|
31
|
+
:bids, # [OrderSummary]
|
32
|
+
:asks # [OrderSummary]
|
33
|
+
)
|
34
|
+
|
35
|
+
OrderSummary = Struct.new(
|
36
|
+
:price, # Decimal
|
37
|
+
:quantity # Decimal
|
38
|
+
)
|
39
|
+
|
40
|
+
BalanceSummary = Struct.new(
|
41
|
+
:btc, # Balance
|
42
|
+
:usd, # Balance
|
43
|
+
:fee # Decimal
|
44
|
+
)
|
45
|
+
|
46
|
+
Balance = Struct.new(
|
47
|
+
:total, # Decimal
|
48
|
+
:reserved, # Decimal
|
49
|
+
:available # Decimal
|
50
|
+
)
|
51
|
+
|
52
|
+
UserTransaction = Struct.new(
|
53
|
+
:order_id, # Integer
|
54
|
+
:usd, # Decimal
|
55
|
+
:btc, # Decimal
|
56
|
+
:btc_usd, # Decimal
|
57
|
+
:fee, # Decimal
|
58
|
+
:type, # Integer
|
59
|
+
:timestamp # Epoch Integer
|
60
|
+
)
|
61
|
+
|
62
|
+
# @return [Void]
|
63
|
+
def self.setup(_settings)
|
64
|
+
raise 'self subclass responsibility'
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [Array<Transaction>]
|
68
|
+
def self.transactions
|
69
|
+
raise 'self subclass responsibility'
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [OrderBook]
|
73
|
+
def self.order_book(_retries = 20)
|
74
|
+
raise 'self subclass responsibility'
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [BalanceSummary]
|
78
|
+
def self.balance
|
79
|
+
raise 'self subclass responsibility'
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [nil]
|
83
|
+
def self.cancel
|
84
|
+
raise 'self subclass responsibility'
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [Array<Order>]
|
88
|
+
def self.orders
|
89
|
+
raise 'self subclass responsibility'
|
90
|
+
end
|
91
|
+
|
92
|
+
# @return [UserTransaction]
|
93
|
+
def self.user_transacitions
|
94
|
+
raise 'self subclass responsibility'
|
95
|
+
end
|
96
|
+
|
97
|
+
# @param type
|
98
|
+
# @param price
|
99
|
+
# @param quantity
|
100
|
+
def self.place_order(type, price, quantity)
|
101
|
+
order = send_order(type, price, quantity)
|
102
|
+
return order unless order.nil? || order.id.nil?
|
103
|
+
|
104
|
+
BitexBot::Robot.log(:debug, "Captured error when placing order on #{self.class.name}")
|
105
|
+
# Order may have gone through and be stuck somewhere in Wrapper's pipeline.
|
106
|
+
# We just sleep for a bit and then look for the order.
|
107
|
+
20.times do
|
108
|
+
BitexBot::Robot.sleep_for(10)
|
109
|
+
order = find_lost(type, price, quantity)
|
110
|
+
return order if order.present?
|
111
|
+
end
|
112
|
+
raise OrderNotFound, "Closing: #{type} order not found for #{quantity} BTC @ $#{price}. #{order}"
|
113
|
+
end
|
114
|
+
|
115
|
+
# Hook Method - arguments could not be used in their entirety by the subclasses
|
116
|
+
def self.send_order(_type, _price, _quantity)
|
117
|
+
raise 'self subclass responsibility'
|
118
|
+
end
|
119
|
+
|
120
|
+
# @param order_method [String] buy|sell
|
121
|
+
# @param price [Decimal]
|
122
|
+
#
|
123
|
+
# Hook Method - arguments could not be used in their entirety by the subclasses
|
124
|
+
def self.find_lost(_type, _price, _quantity)
|
125
|
+
raise 'self subclass responsibility'
|
126
|
+
end
|
127
|
+
|
128
|
+
# @param order_id
|
129
|
+
# @param transactions
|
130
|
+
#
|
131
|
+
# @return [Array<Decimal, Decimal>]
|
132
|
+
def self.amount_and_quantity(_order_id, _transactions)
|
133
|
+
raise 'self subclass responsibility'
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.enough_order_size?(quantity, price)
|
137
|
+
(quantity * price) > MIN_AMOUNT
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class OrderNotFound < StandardError; end
|
142
|
+
class ApiWrapperError < StandardError; end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# Wrapper implementation for Bitstamp API.
|
2
|
+
# https://www.bitstamp.net/api/
|
3
|
+
class BitstampApiWrapper < ApiWrapper
|
4
|
+
def self.setup(settings)
|
5
|
+
Bitstamp.setup do |config|
|
6
|
+
config.key = settings.api_key
|
7
|
+
config.secret = settings.secret
|
8
|
+
config.client_id = settings.client_id
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.amount_and_quantity(order_id, transactions)
|
13
|
+
closes = transactions.select { |t| t.order_id.to_s == order_id }
|
14
|
+
amount = closes.map { |c| c.usd.to_d }.sum.abs
|
15
|
+
quantity = closes.map { |c| c.btc.to_d }.sum.abs
|
16
|
+
|
17
|
+
[amount, quantity]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.balance
|
21
|
+
balance_summary_parser(Bitstamp.balance.symbolize_keys)
|
22
|
+
rescue StandardError => e
|
23
|
+
raise ApiWrapperError, "Bitstamp balance failed: #{e.message}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.cancel(order)
|
27
|
+
Bitstamp::Order.new(id: order.id).cancel!
|
28
|
+
rescue StandardError => e
|
29
|
+
raise ApiWrapperError, "Bitstamp cancel! failed: #{e.message}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.find_lost(type, price, _quantity)
|
33
|
+
orders.find { |o| o.type == type && o.price == price && o.timestamp >= 5.minutes.ago.to_i }
|
34
|
+
end
|
35
|
+
|
36
|
+
# rubocop:disable Metrics/AbcSize
|
37
|
+
def self.order_book(retries = 20)
|
38
|
+
book = Bitstamp.order_book.deep_symbolize_keys
|
39
|
+
age = Time.now.to_i - book[:timestamp].to_i
|
40
|
+
return order_book_parser(book) if age <= 300
|
41
|
+
|
42
|
+
BitexBot::Robot.log(:info, "Refusing to continue as orderbook is #{age} seconds old")
|
43
|
+
order_book(retries)
|
44
|
+
rescue StandardError
|
45
|
+
raise if retries.zero?
|
46
|
+
|
47
|
+
BitexBot::Robot.log(:info, "Bitstamp order book failed, retrying #{retries} more times")
|
48
|
+
BitexBot::Robot.sleep_for 1
|
49
|
+
order_book(retries - 1)
|
50
|
+
end
|
51
|
+
# rubocop:enable Metrics/AbcSize
|
52
|
+
|
53
|
+
def self.orders
|
54
|
+
Bitstamp.orders.all.map { |o| order_parser(o) }
|
55
|
+
rescue StandardError => e
|
56
|
+
raise ApiWrapperError, "Bitstamp orders failed: #{e.message}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.send_order(type, price, quantity)
|
60
|
+
Bitstamp.orders.send(type, amount: quantity.round(4), price: price.round(2))
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.transactions
|
64
|
+
Bitstamp.transactions.map { |t| transaction_parser(t) }
|
65
|
+
rescue StandardError => e
|
66
|
+
raise ApiWrapperError, "Bitstamp transactions failed: #{e.message}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.user_transactions
|
70
|
+
Bitstamp.user_transactions.all.map { |ut| user_transaction_parser(ut) }
|
71
|
+
rescue StandardError => e
|
72
|
+
raise ApiWrapperError, "Bitstamp user_transactions failed: #{e.message}"
|
73
|
+
end
|
74
|
+
|
75
|
+
private_class_method
|
76
|
+
|
77
|
+
# {
|
78
|
+
# btc_reserved: '0', btc_available: '0', btc_balance: '0',
|
79
|
+
# usd_reserved: '1.02, usd_available: '6952.05', usd_balance: '6953.07',
|
80
|
+
# fee: '0.4000'
|
81
|
+
# }
|
82
|
+
def self.balance_summary_parser(balances)
|
83
|
+
BalanceSummary.new(balance_parser(balances, :btc), balance_parser(balances, :usd), balances[:fee].to_d)
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.balance_parser(balances, currency)
|
87
|
+
Balance.new(
|
88
|
+
balances["#{currency}_balance".to_sym].to_d,
|
89
|
+
balances["#{currency}_reserved".to_sym].to_d,
|
90
|
+
balances["#{currency}_available".to_sym].to_d
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
# {
|
95
|
+
# timestamp: '1380237884',
|
96
|
+
# bids: [['124.55', '1.58057006'], ['124.40', '14.91779125']],
|
97
|
+
# asks: [['124.56', '0.81888247'], ['124.57', '0.81078911']]
|
98
|
+
# }
|
99
|
+
def self.order_book_parser(book)
|
100
|
+
OrderBook.new(book[:timestamp].to_i, order_summary_parser(book[:bids]), order_summary_parser(book[:asks]))
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.order_is_done?(order)
|
104
|
+
order.nil?
|
105
|
+
end
|
106
|
+
|
107
|
+
# <Bitstamp::Order @id=76, @type=0, @price='1.1', @amount='1.0', @datetime='2013-09-26 23:15:04'>
|
108
|
+
def self.order_parser(order)
|
109
|
+
type = order.type.zero? ? :buy : :sell
|
110
|
+
Order.new(order.id.to_s, type, order.price.to_d, order.amount.to_d, order.datetime.to_datetime.to_i, order)
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.order_summary_parser(orders)
|
114
|
+
orders.map { |order| OrderSummary.new(order[0].to_d, order[1].to_d) }
|
115
|
+
end
|
116
|
+
|
117
|
+
# <Bitstamp::Transactions: @tid=1469074, @price='126.95', @amount='1.10000000', @date='1380648951'>
|
118
|
+
def self.transaction_parser(transaction)
|
119
|
+
Transaction.new(transaction.tid, transaction.price.to_d, transaction.amount.to_d, transaction.date.to_i)
|
120
|
+
end
|
121
|
+
|
122
|
+
# <Bitstamp::UserTransaction:
|
123
|
+
# @usd='-373.51', @btc='3.00781124', @btc_usd='124.18', @order_id=7623942, @fee='1.50', @type=2, @id=1444404,
|
124
|
+
# @datetime='2013-09-26 13:28:55'
|
125
|
+
# >
|
126
|
+
def self.user_transaction_parser(user_transaction)
|
127
|
+
UserTransaction.new(
|
128
|
+
user_transaction.order_id,
|
129
|
+
user_transaction.usd.to_d,
|
130
|
+
user_transaction.btc.to_d,
|
131
|
+
user_transaction.btc_usd.to_d,
|
132
|
+
user_transaction.fee.to_d,
|
133
|
+
user_transaction.type,
|
134
|
+
Time.new(user_transaction.datetime).to_i
|
135
|
+
)
|
136
|
+
end
|
137
|
+
end
|