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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +63 -0
  3. data/.rubocop.yml +33 -0
  4. data/Gemfile +1 -1
  5. data/Rakefile +1 -1
  6. data/bin/bitex_bot +1 -1
  7. data/bitex_bot.gemspec +34 -34
  8. data/lib/bitex_bot/database.rb +67 -67
  9. data/lib/bitex_bot/models/api_wrappers/api_wrapper.rb +142 -0
  10. data/lib/bitex_bot/models/api_wrappers/bitstamp/bitstamp_api_wrapper.rb +137 -0
  11. data/lib/bitex_bot/models/api_wrappers/itbit/itbit_api_wrapper.rb +116 -0
  12. data/lib/bitex_bot/models/api_wrappers/kraken/kraken_api_wrapper.rb +111 -0
  13. data/lib/bitex_bot/models/api_wrappers/kraken/kraken_order.rb +117 -0
  14. data/lib/bitex_bot/models/buy_closing_flow.rb +23 -16
  15. data/lib/bitex_bot/models/buy_opening_flow.rb +48 -54
  16. data/lib/bitex_bot/models/close_buy.rb +2 -2
  17. data/lib/bitex_bot/models/closing_flow.rb +98 -79
  18. data/lib/bitex_bot/models/open_buy.rb +11 -10
  19. data/lib/bitex_bot/models/open_sell.rb +11 -10
  20. data/lib/bitex_bot/models/opening_flow.rb +157 -99
  21. data/lib/bitex_bot/models/order_book_simulator.rb +62 -67
  22. data/lib/bitex_bot/models/sell_closing_flow.rb +25 -20
  23. data/lib/bitex_bot/models/sell_opening_flow.rb +47 -54
  24. data/lib/bitex_bot/models/store.rb +3 -1
  25. data/lib/bitex_bot/robot.rb +203 -176
  26. data/lib/bitex_bot/settings.rb +71 -12
  27. data/lib/bitex_bot/version.rb +1 -1
  28. data/lib/bitex_bot.rb +40 -16
  29. data/settings.rb.sample +43 -66
  30. data/spec/bitex_bot/settings_spec.rb +87 -15
  31. data/spec/factories/bitex_buy.rb +3 -3
  32. data/spec/factories/bitex_sell.rb +3 -3
  33. data/spec/factories/buy_opening_flow.rb +1 -1
  34. data/spec/factories/open_buy.rb +12 -10
  35. data/spec/factories/open_sell.rb +12 -10
  36. data/spec/factories/sell_opening_flow.rb +1 -1
  37. data/spec/models/api_wrappers/bitstamp_api_wrapper_spec.rb +200 -0
  38. data/spec/models/api_wrappers/itbit_api_wrapper_spec.rb +176 -0
  39. data/spec/models/api_wrappers/kraken_api_wrapper_spec.rb +209 -0
  40. data/spec/models/bitex_api_spec.rb +1 -1
  41. data/spec/models/buy_closing_flow_spec.rb +140 -71
  42. data/spec/models/buy_opening_flow_spec.rb +126 -56
  43. data/spec/models/order_book_simulator_spec.rb +10 -10
  44. data/spec/models/robot_spec.rb +61 -47
  45. data/spec/models/sell_closing_flow_spec.rb +130 -62
  46. data/spec/models/sell_opening_flow_spec.rb +129 -60
  47. data/spec/spec_helper.rb +19 -16
  48. data/spec/support/bitex_stubs.rb +13 -14
  49. data/spec/support/bitstamp/bitstamp_api_wrapper_stubs.rb +35 -0
  50. data/spec/support/bitstamp/bitstamp_stubs.rb +91 -0
  51. metadata +60 -42
  52. data/lib/bitex_bot/models/bitfinex_api_wrapper.rb +0 -118
  53. data/lib/bitex_bot/models/bitstamp_api_wrapper.rb +0 -82
  54. data/lib/bitex_bot/models/itbit_api_wrapper.rb +0 -68
  55. data/lib/bitex_bot/models/kraken_api_wrapper.rb +0 -188
  56. data/spec/models/bitfinex_api_wrapper_spec.rb +0 -17
  57. data/spec/models/bitstamp_api_wrapper_spec.rb +0 -15
  58. data/spec/models/itbit_api_wrapper_spec.rb +0 -15
  59. data/spec/support/bitstamp_stubs.rb +0 -110
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c1bcc53a7b4d8a6382173bdd864dd306b3a2bdc5
4
- data.tar.gz: e4254d0ffe025a95c5eba3429814b2b60405abb7
3
+ metadata.gz: '027380d0b5310a8656d1e4ea0357617f1c750c1a'
4
+ data.tar.gz: 66974805bb9b7b130181e737f688512d8f234d17
5
5
  SHA512:
6
- metadata.gz: 0b64dae4070f6fb5ce595bfb0d5cac5c88d9d853264b948ae62241e812a1946ed83e9f3af344b4fa8fb7a215d61f9cc8c2b960c388b820d2b880f3dc72cc32b7
7
- data.tar.gz: a8ac2e4ace3edaa00b529d6aafd1808d4bb8cfb8139b6588789a80e8455b38505be31afb6a813048905d9f8c32f3738b979c23c45a1ab1340a49454180347ecc
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
@@ -2,4 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in bitex_bot.gemspec
4
4
  gemspec
5
- gem 'bitstamp', git: 'https://github.com/bitex-la/bitstamp.git'
5
+ gem 'bitstamp', github: 'bitex-la/bitstamp'
data/Rakefile CHANGED
@@ -1 +1 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
data/bin/bitex_bot CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "bitex_bot/settings"
3
+ require 'bitex_bot/settings'
4
4
  BitexBot::Settings.load_default
5
5
 
6
6
  require 'bitex_bot'
data/bitex_bot.gemspec CHANGED
@@ -1,44 +1,44 @@
1
- # coding: utf-8
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 = "bitex_bot"
6
+ spec.name = 'bitex_bot'
8
7
  spec.version = BitexBot::VERSION
9
- spec.authors = ["Nubis", "Eromirou"]
10
- spec.email = ["nb@bitex.la", "tr@bitex.la"]
11
- spec.description = %q{Both a trading robot and a library to build trading
12
- robots. The bitex-bot lets you buy cheap on bitex and
13
- sell on another exchange and vice versa.}
14
- spec.summary = %q{A trading robot to do arbitrage between bitex.la and
15
- other exchanges!}
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 = ["lib"]
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.add_development_dependency "bundler"
35
- spec.add_development_dependency "byebug"
36
- spec.add_development_dependency "rake"
37
- spec.add_development_dependency "rspec"
38
- spec.add_development_dependency "rspec-mocks"
39
- spec.add_development_dependency "database_cleaner"
40
- spec.add_development_dependency "factory_girl"
41
- spec.add_development_dependency "timecop"
42
- spec.add_development_dependency "shoulda-matchers"
43
- spec.add_development_dependency "webmock"
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
@@ -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 :price, precision: 30, scale: 15
10
- t.decimal :value_to_use, precision: 30, scale: 15
11
- t.decimal :suggested_closing_price, precision: 30, scale: 15
12
- t.integer :order_id
13
- t.string :status, null: false, default: 'executing'
14
- t.index :status
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 :price, precision: 30, scale: 15
21
- t.decimal :value_to_use, precision: 30, scale: 15
22
- t.decimal :suggested_closing_price, precision: 30, scale: 15
23
- t.integer :order_id
24
- t.string :status, null: false, default: 'executing'
25
- t.index :status
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 :price, precision: 30, scale: 15
34
- t.decimal :amount, precision: 30, scale: 15
35
- t.decimal :quantity, precision: 30, scale: 15
36
- t.integer :transaction_id
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 :price, precision: 30, scale: 15
45
- t.decimal :quantity, precision: 30, scale: 15
46
- t.decimal :amount, precision: 30, scale: 15
47
- t.integer :transaction_id
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 :desired_price, precision: 30, scale: 15
54
- t.decimal :quantity, precision: 30, scale: 15
55
- t.decimal :amount, precision: 30, scale: 15
56
- t.boolean :done, null: false, default: false
57
- t.decimal :btc_profit, precision: 30, scale: 15
58
- t.decimal :usd_profit, precision: 30, scale: 15
59
- t.timestamps
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 :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 :usd_profit, precision: 30, scale: 15
69
- t.timestamps
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 :amount, precision: 30, scale: 15
75
- t.decimal :quantity, precision: 30, scale: 15
76
- t.string :order_id
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 :amount, precision: 30, scale: 15
84
- t.decimal :quantity, precision: 30, scale: 15
85
- t.string :order_id
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 "stores", force: true do |t|
93
- t.decimal "taker_usd", precision: 20, scale: 8
94
- t.decimal "taker_btc", precision: 20, scale: 8
95
- t.boolean "hold", default: false
96
- t.text "log"
97
- t.decimal "usd_stop", precision: 20, scale: 8
98
- t.decimal "usd_warning", precision: 20, scale: 8
99
- t.decimal "btc_stop", precision: 20, scale: 8
100
- t.decimal "btc_warning", precision: 20, scale: 8
101
- t.datetime "last_warning"
102
- t.decimal "buying_profit", precision: 20, scale: 8
103
- t.decimal "selling_profit", precision: 20, scale: 8
104
- t.decimal "buying_amount_to_spend_per_order", precision: 20, scale: 8
105
- t.decimal "selling_quantity_to_sell_per_order", precision: 20, scale: 8
106
- t.timestamps
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