bitbot-trader 0.0.1

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 (69) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +7 -0
  6. data/Gemfile +10 -0
  7. data/Gemfile.devtools +55 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +28 -0
  10. data/Rakefile +4 -0
  11. data/bitbot-trader.gemspec +22 -0
  12. data/config/devtools.yml +4 -0
  13. data/config/flay.yml +3 -0
  14. data/config/flog.yml +2 -0
  15. data/config/mutant.yml +3 -0
  16. data/config/reek.yml +103 -0
  17. data/config/yardstick.yml +2 -0
  18. data/examples/account_info.rb +16 -0
  19. data/examples/open_orders.rb +16 -0
  20. data/lib/bitbot/trader.rb +32 -0
  21. data/lib/bitbot/trader/account.rb +29 -0
  22. data/lib/bitbot/trader/amount.rb +15 -0
  23. data/lib/bitbot/trader/api_methods.rb +33 -0
  24. data/lib/bitbot/trader/open_order.rb +29 -0
  25. data/lib/bitbot/trader/price.rb +15 -0
  26. data/lib/bitbot/trader/provider.rb +19 -0
  27. data/lib/bitbot/trader/providers/bitstamp.rb +29 -0
  28. data/lib/bitbot/trader/providers/bitstamp/http_client.rb +101 -0
  29. data/lib/bitbot/trader/providers/bitstamp/open_orders_parser.rb +33 -0
  30. data/lib/bitbot/trader/providers/bitstamp/open_orders_request.rb +25 -0
  31. data/lib/bitbot/trader/providers/mt_gox.rb +29 -0
  32. data/lib/bitbot/trader/providers/mt_gox/account_info_parser.rb +45 -0
  33. data/lib/bitbot/trader/providers/mt_gox/account_info_request.rb +24 -0
  34. data/lib/bitbot/trader/providers/mt_gox/http_client.rb +98 -0
  35. data/lib/bitbot/trader/providers/mt_gox/http_client/hmac_middleware.rb +118 -0
  36. data/lib/bitbot/trader/providers/mt_gox/open_orders_parser.rb +35 -0
  37. data/lib/bitbot/trader/providers/mt_gox/open_orders_request.rb +25 -0
  38. data/lib/bitbot/trader/providers/mt_gox/value_with_currency_coercer.rb +28 -0
  39. data/lib/bitbot/trader/request.rb +41 -0
  40. data/lib/bitbot/trader/utils/nonce_generator.rb +21 -0
  41. data/lib/bitbot/trader/version.rb +5 -0
  42. data/lib/bitbot/trader/wallet.rb +25 -0
  43. data/spec/integration/bitstamp/open_orders_spec.rb +28 -0
  44. data/spec/integration/mt_gox/account_spec.rb +28 -0
  45. data/spec/integration/mt_gox/open_orders_spec.rb +29 -0
  46. data/spec/spec_helper.rb +18 -0
  47. data/spec/support/big_decimal_matcher.rb +5 -0
  48. data/spec/support/http_connection_helpers.rb +15 -0
  49. data/spec/support/http_request_mock.rb +7 -0
  50. data/spec/support/provider_mock.rb +5 -0
  51. data/spec/unit/bitbot/trader/account_spec.rb +28 -0
  52. data/spec/unit/bitbot/trader/api_methods_spec.rb +43 -0
  53. data/spec/unit/bitbot/trader/open_order_spec.rb +19 -0
  54. data/spec/unit/bitbot/trader/provider_spec.rb +18 -0
  55. data/spec/unit/bitbot/trader/providers/bitstamp/http_client_spec.rb +75 -0
  56. data/spec/unit/bitbot/trader/providers/bitstamp/open_order_parser_spec.rb +69 -0
  57. data/spec/unit/bitbot/trader/providers/bitstamp/open_orders_request_spec.rb +24 -0
  58. data/spec/unit/bitbot/trader/providers/bitstamp_spec.rb +45 -0
  59. data/spec/unit/bitbot/trader/providers/mt_gox/account_info_parser_spec.rb +58 -0
  60. data/spec/unit/bitbot/trader/providers/mt_gox/account_info_request_spec.rb +24 -0
  61. data/spec/unit/bitbot/trader/providers/mt_gox/http_client/hmac_middleware_spec.rb +55 -0
  62. data/spec/unit/bitbot/trader/providers/mt_gox/http_client_spec.rb +100 -0
  63. data/spec/unit/bitbot/trader/providers/mt_gox/open_order_parser_spec.rb +95 -0
  64. data/spec/unit/bitbot/trader/providers/mt_gox/open_orders_request_spec.rb +24 -0
  65. data/spec/unit/bitbot/trader/providers/mt_gox/value_with_currency_coercer_spec.rb +21 -0
  66. data/spec/unit/bitbot/trader/providers/mt_gox_spec.rb +26 -0
  67. data/spec/unit/bitbot/trader/request_spec.rb +39 -0
  68. data/spec/unit/bitbot/trader/utils/nonce_generator_spec.rb +19 -0
  69. metadata +166 -0
@@ -0,0 +1,29 @@
1
+ require "virtus"
2
+
3
+ module Bitbot
4
+ module Trader
5
+ # Value object for user open order
6
+ #
7
+ class OpenOrder
8
+ include Virtus.model
9
+
10
+ attribute :id, String
11
+ attribute :price, Price
12
+ attribute :amount, Amount
13
+ attribute :bid, Boolean
14
+
15
+ # Checks if order is a ask order
16
+ #
17
+ # @example
18
+ # OpenOrder.new(bid: true).ask? #=> false
19
+ #
20
+ # @return [Boolean]
21
+ #
22
+ # @api public
23
+ #
24
+ def ask?
25
+ !bid?
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ require "virtus"
2
+
3
+ module Bitbot
4
+ module Trader
5
+ # Price value object. Every price has a value and a currency.
6
+ # e.g 5.6 USD
7
+ #
8
+ class Price
9
+ include Virtus.model
10
+
11
+ attribute :value, BigDecimal
12
+ attribute :currency, String
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ module Bitbot
2
+ module Trader
3
+ # Common Provider methods
4
+ #
5
+ # @abstract
6
+ #
7
+ class Provider
8
+ include ApiMethods
9
+
10
+ # Object that communicates with external API
11
+ #
12
+ # @return [Object]
13
+ #
14
+ # @api private
15
+ #
16
+ attr_reader :client
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ require "virtus"
2
+
3
+ module Bitbot
4
+ module Trader
5
+ module Providers
6
+ # Provider for Bitstamp API
7
+ #
8
+ # @see https://www.bitstamp.net/api/
9
+ #
10
+ class Bitstamp < Provider
11
+ # Initializes Bitstamp provider
12
+ #
13
+ # @param [Hash] options
14
+ # @option options [String] :username
15
+ # @option options [String] :password
16
+ #
17
+ # @param [HttpClient] client
18
+ #
19
+ # @return [undefined]
20
+ #
21
+ # @api public
22
+ #
23
+ def initialize(options, client = HttpClient.build(options))
24
+ @client = client
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,101 @@
1
+ require "faraday"
2
+ require "json"
3
+
4
+ module Bitbot
5
+ module Trader
6
+ module Providers
7
+ class Bitstamp
8
+ # Bitstamp specific http client
9
+ #
10
+ class HttpClient
11
+ HOST = "https://www.bitstamp.net/api/"
12
+
13
+ # Build a new HttpClient object
14
+ #
15
+ # @param [Hash] options
16
+ # @option options [String] :username
17
+ # @option options [String] :password
18
+ #
19
+ # @return [HttpClient]
20
+ #
21
+ # @api private
22
+ #
23
+ def self.build(options)
24
+ username = options.fetch(:username)
25
+ password = options.fetch(:password)
26
+
27
+ connection = make_connection
28
+ new(connection, username, password)
29
+ end
30
+
31
+ # Creates a new faraday connection object
32
+ #
33
+ # @return [Faraday]
34
+ #
35
+ # @api private
36
+ #
37
+ def self.make_connection
38
+ Faraday.new(url: HOST)
39
+ end
40
+
41
+ # Return connection
42
+ #
43
+ # @return [Object]
44
+ #
45
+ # @api private
46
+ #
47
+ attr_reader :connection
48
+
49
+ # API username
50
+ #
51
+ # @return [String]
52
+ #
53
+ # @api private
54
+ #
55
+ attr_reader :username
56
+
57
+ # API passsword
58
+ #
59
+ # @return [String]
60
+ #
61
+ # @api private
62
+ #
63
+ attr_reader :password
64
+
65
+ # Initializes HttpClient object
66
+ #
67
+ # @param [#post] connection
68
+ # @param [String] username
69
+ # @param [String] password
70
+ #
71
+ # @return [undefined]
72
+ #
73
+ # @api private
74
+ #
75
+ def initialize(connection, username, password)
76
+ @connection = connection
77
+ @username = username
78
+ @password = password
79
+ end
80
+
81
+ # Sends post request to given path
82
+ #
83
+ # @param [String] path
84
+ # e.g. "open_orders"
85
+ # @param [Hash] options
86
+ # post request parameters
87
+ #
88
+ # @return [Hash]
89
+ #
90
+ # @api private
91
+ #
92
+ def post(path, options = {})
93
+ options = options.merge(user: @username, password: @password)
94
+ result = @connection.post("#{path}/", options).body
95
+ JSON.parse(result)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,33 @@
1
+ module Bitbot
2
+ module Trader
3
+ module Providers
4
+ class Bitstamp
5
+ # Parses raw open orders
6
+ #
7
+ class OpenOrderParser
8
+ include Virtus.model
9
+
10
+ attribute :id, Integer
11
+ attribute :price, BigDecimal
12
+ attribute :amount, BigDecimal
13
+ attribute :type, Integer
14
+
15
+ # Makes raw open order hash into OpenOrder object
16
+ #
17
+ # @return [OpenOrder]
18
+ #
19
+ # @api private
20
+ #
21
+ def parse
22
+ OpenOrder.new(
23
+ id: id,
24
+ price: {value: price, currency: "USD"},
25
+ amount: {value: amount, currency: "BTC"},
26
+ bid: type == 0
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,25 @@
1
+ module Bitbot
2
+ module Trader
3
+ module Providers
4
+ class Bitstamp
5
+ # POST request to /open_orders/
6
+ #
7
+ # @see https://www.bitstamp.net/api/
8
+ #
9
+ class OpenOrdersRequest < Request
10
+ # Fetches user's open orders
11
+ #
12
+ # @return [Array<OpenOrder>]
13
+ #
14
+ # @api private
15
+ #
16
+ def call
17
+ client.post("open_orders").map { |raw_order|
18
+ OpenOrderParser.new(raw_order).parse
19
+ }
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ require "virtus"
2
+
3
+ module Bitbot
4
+ module Trader
5
+ module Providers
6
+ # Provider for MtGox API (version 2)
7
+ #
8
+ # @see https://en.bitcoin.it/wiki/MtGox/API/HTTP/v2
9
+ #
10
+ class MtGox < Provider
11
+ # Initializes MtGox provider
12
+ #
13
+ # @param [Hash] options
14
+ # @option options [String] :key
15
+ # @option options [String] :secret
16
+ #
17
+ # @param [HttpClient] client
18
+ #
19
+ # @return [undefined]
20
+ #
21
+ # @api public
22
+ #
23
+ def initialize(options, client = HttpClient.build(options))
24
+ @client = client
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,45 @@
1
+ module Bitbot
2
+ module Trader
3
+ module Providers
4
+ class MtGox
5
+ # Parses raw open orders
6
+ #
7
+ class AccountInfoParser
8
+ include Virtus.model
9
+
10
+ attribute :Trade_Fee, Float
11
+ attribute :Wallets, Hash
12
+
13
+ # Makes raw account info hash into Account object
14
+ #
15
+ # @return [Account]
16
+ #
17
+ # @api private
18
+ #
19
+ def parse
20
+ parsed = attributes
21
+
22
+ Account.new(
23
+ fee: parsed[:Trade_Fee],
24
+ wallets: self.class.parse_wallets(parsed[:Wallets])
25
+ )
26
+ end
27
+
28
+ # Parses raw wallets
29
+ #
30
+ # @param [Hash<String, Hash>] raw_wallets
31
+ #
32
+ # @return [Array<Hash>]
33
+ #
34
+ # @api private
35
+ #
36
+ def self.parse_wallets(raw_wallets)
37
+ raw_wallets.map { |_key, raw_wallet|
38
+ ValueWithCurrencyCoercer.call(raw_wallet["Balance"])
39
+ }
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,24 @@
1
+ module Bitbot
2
+ module Trader
3
+ module Providers
4
+ class MtGox
5
+ # POST request to /money/info
6
+ #
7
+ # @see https://en.bitcoin.it/wiki/MtGox/API/HTTP/v1
8
+ #
9
+ class AccountInfoRequest < Request
10
+ # Fetches user's account info
11
+ #
12
+ # @return [Account]
13
+ #
14
+ # @api private
15
+ #
16
+ def call
17
+ raw_account = client.post("money/info")["data"]
18
+ AccountInfoParser.new(raw_account).parse
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,98 @@
1
+ require "faraday"
2
+ require "json"
3
+
4
+ module Bitbot
5
+ module Trader
6
+ module Providers
7
+ class MtGox
8
+ # MtGox specific http client
9
+ #
10
+ class HttpClient
11
+ HOST = "https://data.mtgox.com/api/2/"
12
+
13
+ # Builds a new HttpClient object
14
+ #
15
+ # @param [Hash] options
16
+ # @option options [String] :username
17
+ # @option options [String] :password
18
+ #
19
+ # @return [HttpClient]
20
+ #
21
+ # @api private
22
+ #
23
+ def self.build(options)
24
+ key = options.fetch(:key)
25
+ secret = options.fetch(:secret)
26
+
27
+ connection = make_connection(key, secret)
28
+ new(connection)
29
+ end
30
+
31
+ # Creates a new faraday connection object
32
+ #
33
+ # @param [String] key
34
+ # @param [String] secret
35
+ #
36
+ # @return [Faraday::Connection]
37
+ #
38
+ # @api private
39
+ #
40
+ def self.make_connection(key, secret)
41
+ Faraday.new(url: HOST) do |faraday|
42
+ faraday.request :url_encoded
43
+ faraday.request :hmac, key: key, secret: secret
44
+ faraday.adapter :net_http
45
+ end
46
+ end
47
+
48
+ # Return connection
49
+ #
50
+ # @return [Object]
51
+ #
52
+ # @api private
53
+ #
54
+ attr_reader :connection
55
+
56
+ # Return nonce generator
57
+ #
58
+ # @return [Object]
59
+ #
60
+ # @api private
61
+ attr_reader :nonce_generator
62
+
63
+ # Initializes HttpClient object
64
+ #
65
+ # @param [#post] connection
66
+ # @param [#generate] nonce_generator
67
+ # must return a uniq number
68
+ #
69
+ # @return [undefined]
70
+ #
71
+ # @api private
72
+ #
73
+ def initialize(connection, nonce_generator = Utils::NonceGenerator)
74
+ @connection = connection
75
+ @nonce_generator = nonce_generator
76
+ end
77
+
78
+ # Sends post request to given path
79
+ #
80
+ # @param [String] path
81
+ # e.g. "money/orders"
82
+ # @param [Hash] options
83
+ # post request parameters
84
+ #
85
+ # @return [Hash]
86
+ #
87
+ # @api private
88
+ #
89
+ def post(path, options = {})
90
+ options = options.merge(nonce: @nonce_generator.generate)
91
+ result = @connection.post(path, options).body
92
+ JSON.parse(result)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end