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.
- checksums.yaml +7 -0
 - data/.gitignore +18 -0
 - data/.ruby-gemset +1 -0
 - data/.ruby-version +1 -0
 - data/.travis.yml +7 -0
 - data/Gemfile +10 -0
 - data/Gemfile.devtools +55 -0
 - data/LICENSE.txt +22 -0
 - data/README.md +28 -0
 - data/Rakefile +4 -0
 - data/bitbot-trader.gemspec +22 -0
 - data/config/devtools.yml +4 -0
 - data/config/flay.yml +3 -0
 - data/config/flog.yml +2 -0
 - data/config/mutant.yml +3 -0
 - data/config/reek.yml +103 -0
 - data/config/yardstick.yml +2 -0
 - data/examples/account_info.rb +16 -0
 - data/examples/open_orders.rb +16 -0
 - data/lib/bitbot/trader.rb +32 -0
 - data/lib/bitbot/trader/account.rb +29 -0
 - data/lib/bitbot/trader/amount.rb +15 -0
 - data/lib/bitbot/trader/api_methods.rb +33 -0
 - data/lib/bitbot/trader/open_order.rb +29 -0
 - data/lib/bitbot/trader/price.rb +15 -0
 - data/lib/bitbot/trader/provider.rb +19 -0
 - data/lib/bitbot/trader/providers/bitstamp.rb +29 -0
 - data/lib/bitbot/trader/providers/bitstamp/http_client.rb +101 -0
 - data/lib/bitbot/trader/providers/bitstamp/open_orders_parser.rb +33 -0
 - data/lib/bitbot/trader/providers/bitstamp/open_orders_request.rb +25 -0
 - data/lib/bitbot/trader/providers/mt_gox.rb +29 -0
 - data/lib/bitbot/trader/providers/mt_gox/account_info_parser.rb +45 -0
 - data/lib/bitbot/trader/providers/mt_gox/account_info_request.rb +24 -0
 - data/lib/bitbot/trader/providers/mt_gox/http_client.rb +98 -0
 - data/lib/bitbot/trader/providers/mt_gox/http_client/hmac_middleware.rb +118 -0
 - data/lib/bitbot/trader/providers/mt_gox/open_orders_parser.rb +35 -0
 - data/lib/bitbot/trader/providers/mt_gox/open_orders_request.rb +25 -0
 - data/lib/bitbot/trader/providers/mt_gox/value_with_currency_coercer.rb +28 -0
 - data/lib/bitbot/trader/request.rb +41 -0
 - data/lib/bitbot/trader/utils/nonce_generator.rb +21 -0
 - data/lib/bitbot/trader/version.rb +5 -0
 - data/lib/bitbot/trader/wallet.rb +25 -0
 - data/spec/integration/bitstamp/open_orders_spec.rb +28 -0
 - data/spec/integration/mt_gox/account_spec.rb +28 -0
 - data/spec/integration/mt_gox/open_orders_spec.rb +29 -0
 - data/spec/spec_helper.rb +18 -0
 - data/spec/support/big_decimal_matcher.rb +5 -0
 - data/spec/support/http_connection_helpers.rb +15 -0
 - data/spec/support/http_request_mock.rb +7 -0
 - data/spec/support/provider_mock.rb +5 -0
 - data/spec/unit/bitbot/trader/account_spec.rb +28 -0
 - data/spec/unit/bitbot/trader/api_methods_spec.rb +43 -0
 - data/spec/unit/bitbot/trader/open_order_spec.rb +19 -0
 - data/spec/unit/bitbot/trader/provider_spec.rb +18 -0
 - data/spec/unit/bitbot/trader/providers/bitstamp/http_client_spec.rb +75 -0
 - data/spec/unit/bitbot/trader/providers/bitstamp/open_order_parser_spec.rb +69 -0
 - data/spec/unit/bitbot/trader/providers/bitstamp/open_orders_request_spec.rb +24 -0
 - data/spec/unit/bitbot/trader/providers/bitstamp_spec.rb +45 -0
 - data/spec/unit/bitbot/trader/providers/mt_gox/account_info_parser_spec.rb +58 -0
 - data/spec/unit/bitbot/trader/providers/mt_gox/account_info_request_spec.rb +24 -0
 - data/spec/unit/bitbot/trader/providers/mt_gox/http_client/hmac_middleware_spec.rb +55 -0
 - data/spec/unit/bitbot/trader/providers/mt_gox/http_client_spec.rb +100 -0
 - data/spec/unit/bitbot/trader/providers/mt_gox/open_order_parser_spec.rb +95 -0
 - data/spec/unit/bitbot/trader/providers/mt_gox/open_orders_request_spec.rb +24 -0
 - data/spec/unit/bitbot/trader/providers/mt_gox/value_with_currency_coercer_spec.rb +21 -0
 - data/spec/unit/bitbot/trader/providers/mt_gox_spec.rb +26 -0
 - data/spec/unit/bitbot/trader/request_spec.rb +39 -0
 - data/spec/unit/bitbot/trader/utils/nonce_generator_spec.rb +19 -0
 - metadata +166 -0
 
| 
         @@ -0,0 +1,118 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "faraday"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "base64"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "digest/sha2"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "digest/hmac"
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Bitbot
         
     | 
| 
      
 7 
     | 
    
         
            +
              module Trader
         
     | 
| 
      
 8 
     | 
    
         
            +
                module Providers
         
     | 
| 
      
 9 
     | 
    
         
            +
                  class MtGox
         
     | 
| 
      
 10 
     | 
    
         
            +
                    class HttpClient
         
     | 
| 
      
 11 
     | 
    
         
            +
                      # Signs request with hmac signature
         
     | 
| 
      
 12 
     | 
    
         
            +
                      #
         
     | 
| 
      
 13 
     | 
    
         
            +
                      class HmacMiddleware < Faraday::Middleware
         
     | 
| 
      
 14 
     | 
    
         
            +
                        # Extracts headers, path and body
         
     | 
| 
      
 15 
     | 
    
         
            +
                        #
         
     | 
| 
      
 16 
     | 
    
         
            +
                        # @param [#[]] env
         
     | 
| 
      
 17 
     | 
    
         
            +
                        #   must respond to request_headers, url and body
         
     | 
| 
      
 18 
     | 
    
         
            +
                        #
         
     | 
| 
      
 19 
     | 
    
         
            +
                        # @return [Array]
         
     | 
| 
      
 20 
     | 
    
         
            +
                        #
         
     | 
| 
      
 21 
     | 
    
         
            +
                        # @api private
         
     | 
| 
      
 22 
     | 
    
         
            +
                        #
         
     | 
| 
      
 23 
     | 
    
         
            +
                        def self.extract_params(env)
         
     | 
| 
      
 24 
     | 
    
         
            +
                          headers = env[:request_headers]
         
     | 
| 
      
 25 
     | 
    
         
            +
                          path = env[:url].to_s.split("/2/").last
         
     | 
| 
      
 26 
     | 
    
         
            +
                          body = env[:body]
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                          [headers, path, body]
         
     | 
| 
      
 29 
     | 
    
         
            +
                        end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                        # API key
         
     | 
| 
      
 32 
     | 
    
         
            +
                        #
         
     | 
| 
      
 33 
     | 
    
         
            +
                        # @return [String]
         
     | 
| 
      
 34 
     | 
    
         
            +
                        #
         
     | 
| 
      
 35 
     | 
    
         
            +
                        # @api private
         
     | 
| 
      
 36 
     | 
    
         
            +
                        #
         
     | 
| 
      
 37 
     | 
    
         
            +
                        attr_reader :key
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                        # API secret
         
     | 
| 
      
 40 
     | 
    
         
            +
                        #
         
     | 
| 
      
 41 
     | 
    
         
            +
                        # @return [String]
         
     | 
| 
      
 42 
     | 
    
         
            +
                        #
         
     | 
| 
      
 43 
     | 
    
         
            +
                        # @api private
         
     | 
| 
      
 44 
     | 
    
         
            +
                        #
         
     | 
| 
      
 45 
     | 
    
         
            +
                        attr_reader :secret
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                        # Initializes HmacMiddleware
         
     | 
| 
      
 48 
     | 
    
         
            +
                        #
         
     | 
| 
      
 49 
     | 
    
         
            +
                        # @param [Object] app
         
     | 
| 
      
 50 
     | 
    
         
            +
                        # @param [Hash] options
         
     | 
| 
      
 51 
     | 
    
         
            +
                        #
         
     | 
| 
      
 52 
     | 
    
         
            +
                        # @return [undefined]
         
     | 
| 
      
 53 
     | 
    
         
            +
                        #
         
     | 
| 
      
 54 
     | 
    
         
            +
                        # @api private
         
     | 
| 
      
 55 
     | 
    
         
            +
                        #
         
     | 
| 
      
 56 
     | 
    
         
            +
                        def initialize(app, options)
         
     | 
| 
      
 57 
     | 
    
         
            +
                          super(app)
         
     | 
| 
      
 58 
     | 
    
         
            +
                          @key    = options.fetch(:key)
         
     | 
| 
      
 59 
     | 
    
         
            +
                          @secret = options.fetch(:secret)
         
     | 
| 
      
 60 
     | 
    
         
            +
                        end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                        # Adds signature to current request
         
     | 
| 
      
 63 
     | 
    
         
            +
                        #
         
     | 
| 
      
 64 
     | 
    
         
            +
                        # @param [Object] env
         
     | 
| 
      
 65 
     | 
    
         
            +
                        #
         
     | 
| 
      
 66 
     | 
    
         
            +
                        # @return [Object]
         
     | 
| 
      
 67 
     | 
    
         
            +
                        #
         
     | 
| 
      
 68 
     | 
    
         
            +
                        # @api private
         
     | 
| 
      
 69 
     | 
    
         
            +
                        #
         
     | 
| 
      
 70 
     | 
    
         
            +
                        def call(env)
         
     | 
| 
      
 71 
     | 
    
         
            +
                          headers, path, body = self.class.extract_params(env)
         
     | 
| 
      
 72 
     | 
    
         
            +
                          prepare_headers(headers, path, body)
         
     | 
| 
      
 73 
     | 
    
         
            +
                          @app.call(env)
         
     | 
| 
      
 74 
     | 
    
         
            +
                        end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                        private
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                        # Adds signature to headers
         
     | 
| 
      
 79 
     | 
    
         
            +
                        #
         
     | 
| 
      
 80 
     | 
    
         
            +
                        # @param [Hash] headers
         
     | 
| 
      
 81 
     | 
    
         
            +
                        # @param [String] path
         
     | 
| 
      
 82 
     | 
    
         
            +
                        # @param [String] body
         
     | 
| 
      
 83 
     | 
    
         
            +
                        #
         
     | 
| 
      
 84 
     | 
    
         
            +
                        # @return [undefined]
         
     | 
| 
      
 85 
     | 
    
         
            +
                        #
         
     | 
| 
      
 86 
     | 
    
         
            +
                        # @api private
         
     | 
| 
      
 87 
     | 
    
         
            +
                        #
         
     | 
| 
      
 88 
     | 
    
         
            +
                        def prepare_headers(headers, path, body)
         
     | 
| 
      
 89 
     | 
    
         
            +
                          headers.merge!(
         
     | 
| 
      
 90 
     | 
    
         
            +
                            "Rest-Key"   => @key,
         
     | 
| 
      
 91 
     | 
    
         
            +
                            "Rest-Sign"  => generate_signature(path, body),
         
     | 
| 
      
 92 
     | 
    
         
            +
                            "User-Agent" => "bitbot-trader"
         
     | 
| 
      
 93 
     | 
    
         
            +
                          )
         
     | 
| 
      
 94 
     | 
    
         
            +
                        end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                        # Generates signature
         
     | 
| 
      
 97 
     | 
    
         
            +
                        #
         
     | 
| 
      
 98 
     | 
    
         
            +
                        # @param [String] path
         
     | 
| 
      
 99 
     | 
    
         
            +
                        # @param [String] body
         
     | 
| 
      
 100 
     | 
    
         
            +
                        #
         
     | 
| 
      
 101 
     | 
    
         
            +
                        # @return [String] singature
         
     | 
| 
      
 102 
     | 
    
         
            +
                        #
         
     | 
| 
      
 103 
     | 
    
         
            +
                        # @api private
         
     | 
| 
      
 104 
     | 
    
         
            +
                        #
         
     | 
| 
      
 105 
     | 
    
         
            +
                        def generate_signature(path, body)
         
     | 
| 
      
 106 
     | 
    
         
            +
                          secret = Base64.strict_decode64(@secret)
         
     | 
| 
      
 107 
     | 
    
         
            +
                          data = path + 0.chr + body
         
     | 
| 
      
 108 
     | 
    
         
            +
                          hmac = Digest::HMAC.digest(data, secret, Digest::SHA512)
         
     | 
| 
      
 109 
     | 
    
         
            +
                          Base64.strict_encode64(hmac)
         
     | 
| 
      
 110 
     | 
    
         
            +
                        end
         
     | 
| 
      
 111 
     | 
    
         
            +
                      end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                      Faraday::Request.register_middleware hmac: HmacMiddleware
         
     | 
| 
      
 114 
     | 
    
         
            +
                    end
         
     | 
| 
      
 115 
     | 
    
         
            +
                  end
         
     | 
| 
      
 116 
     | 
    
         
            +
                end
         
     | 
| 
      
 117 
     | 
    
         
            +
              end
         
     | 
| 
      
 118 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,35 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Bitbot
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Trader
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Providers
         
     | 
| 
      
 4 
     | 
    
         
            +
                  class MtGox
         
     | 
| 
      
 5 
     | 
    
         
            +
                    # Parses raw open orders
         
     | 
| 
      
 6 
     | 
    
         
            +
                    #
         
     | 
| 
      
 7 
     | 
    
         
            +
                    class OpenOrderParser
         
     | 
| 
      
 8 
     | 
    
         
            +
                      include Virtus.model
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                      attribute :oid,              String
         
     | 
| 
      
 11 
     | 
    
         
            +
                      attribute :currency,         String
         
     | 
| 
      
 12 
     | 
    
         
            +
                      attribute :type,             String
         
     | 
| 
      
 13 
     | 
    
         
            +
                      attribute :effective_amount, Amount, coercer: ValueWithCurrencyCoercer
         
     | 
| 
      
 14 
     | 
    
         
            +
                      attribute :price,            Price,  coercer: ValueWithCurrencyCoercer
         
     | 
| 
      
 15 
     | 
    
         
            +
                      attribute :type,             String
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                      # Makes raw open order hash into OpenOrder object
         
     | 
| 
      
 18 
     | 
    
         
            +
                      #
         
     | 
| 
      
 19 
     | 
    
         
            +
                      # @return [OpenOrder]
         
     | 
| 
      
 20 
     | 
    
         
            +
                      #
         
     | 
| 
      
 21 
     | 
    
         
            +
                      # @api private
         
     | 
| 
      
 22 
     | 
    
         
            +
                      #
         
     | 
| 
      
 23 
     | 
    
         
            +
                      def parse
         
     | 
| 
      
 24 
     | 
    
         
            +
                        OpenOrder.new(
         
     | 
| 
      
 25 
     | 
    
         
            +
                          id: oid,
         
     | 
| 
      
 26 
     | 
    
         
            +
                          price: price,
         
     | 
| 
      
 27 
     | 
    
         
            +
                          amount: effective_amount,
         
     | 
| 
      
 28 
     | 
    
         
            +
                          bid: type == "bid"
         
     | 
| 
      
 29 
     | 
    
         
            +
                        )
         
     | 
| 
      
 30 
     | 
    
         
            +
                      end
         
     | 
| 
      
 31 
     | 
    
         
            +
                    end
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,25 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Bitbot
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Trader
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Providers
         
     | 
| 
      
 4 
     | 
    
         
            +
                  class MtGox
         
     | 
| 
      
 5 
     | 
    
         
            +
                    # POST request to /money/orders
         
     | 
| 
      
 6 
     | 
    
         
            +
                    #
         
     | 
| 
      
 7 
     | 
    
         
            +
                    # @see https://en.bitcoin.it/wiki/MtGox/API/HTTP/v1
         
     | 
| 
      
 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("money/orders")["data"].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,28 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Bitbot
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Trader
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Providers
         
     | 
| 
      
 4 
     | 
    
         
            +
                  class MtGox
         
     | 
| 
      
 5 
     | 
    
         
            +
                    # Helps managing values with currencies
         
     | 
| 
      
 6 
     | 
    
         
            +
                    #
         
     | 
| 
      
 7 
     | 
    
         
            +
                    class ValueWithCurrencyCoercer < Virtus::Attribute
         
     | 
| 
      
 8 
     | 
    
         
            +
                      # Parses value with currency hash
         
     | 
| 
      
 9 
     | 
    
         
            +
                      #
         
     | 
| 
      
 10 
     | 
    
         
            +
                      # @param [Hash] data
         
     | 
| 
      
 11 
     | 
    
         
            +
                      #
         
     | 
| 
      
 12 
     | 
    
         
            +
                      # @param [Integer] decimal_point
         
     | 
| 
      
 13 
     | 
    
         
            +
                      #   MtGox sends integer values. We use this to divide that value.
         
     | 
| 
      
 14 
     | 
    
         
            +
                      #
         
     | 
| 
      
 15 
     | 
    
         
            +
                      # @return [Hash]
         
     | 
| 
      
 16 
     | 
    
         
            +
                      #
         
     | 
| 
      
 17 
     | 
    
         
            +
                      # @api private
         
     | 
| 
      
 18 
     | 
    
         
            +
                      #
         
     | 
| 
      
 19 
     | 
    
         
            +
                      def self.call(data)
         
     | 
| 
      
 20 
     | 
    
         
            +
                        value, currency = data.values_at("value_int", "currency")
         
     | 
| 
      
 21 
     | 
    
         
            +
                        decimal_point = currency == "BTC" ? 8 : 5
         
     | 
| 
      
 22 
     | 
    
         
            +
                        {value: BigDecimal(value) / (10 ** decimal_point), currency: currency}
         
     | 
| 
      
 23 
     | 
    
         
            +
                      end
         
     | 
| 
      
 24 
     | 
    
         
            +
                    end
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,41 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Bitbot
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Trader
         
     | 
| 
      
 3 
     | 
    
         
            +
                # Request class for different api methods
         
     | 
| 
      
 4 
     | 
    
         
            +
                #
         
     | 
| 
      
 5 
     | 
    
         
            +
                # @abstract
         
     | 
| 
      
 6 
     | 
    
         
            +
                #
         
     | 
| 
      
 7 
     | 
    
         
            +
                class Request
         
     | 
| 
      
 8 
     | 
    
         
            +
                  # Object that communicates with external API
         
     | 
| 
      
 9 
     | 
    
         
            +
                  #
         
     | 
| 
      
 10 
     | 
    
         
            +
                  # @return [Object]
         
     | 
| 
      
 11 
     | 
    
         
            +
                  #
         
     | 
| 
      
 12 
     | 
    
         
            +
                  # @api private
         
     | 
| 
      
 13 
     | 
    
         
            +
                  #
         
     | 
| 
      
 14 
     | 
    
         
            +
                  attr_reader :client
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  # Initializes request object
         
     | 
| 
      
 17 
     | 
    
         
            +
                  #
         
     | 
| 
      
 18 
     | 
    
         
            +
                  # @param [Object] client
         
     | 
| 
      
 19 
     | 
    
         
            +
                  #
         
     | 
| 
      
 20 
     | 
    
         
            +
                  # @return [undefined]
         
     | 
| 
      
 21 
     | 
    
         
            +
                  #
         
     | 
| 
      
 22 
     | 
    
         
            +
                  # @api private
         
     | 
| 
      
 23 
     | 
    
         
            +
                  #
         
     | 
| 
      
 24 
     | 
    
         
            +
                  def initialize(client)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    @client = client
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  # Every request object implements call
         
     | 
| 
      
 29 
     | 
    
         
            +
                  #
         
     | 
| 
      
 30 
     | 
    
         
            +
                  # @abstract
         
     | 
| 
      
 31 
     | 
    
         
            +
                  #
         
     | 
| 
      
 32 
     | 
    
         
            +
                  # @return [undefined]
         
     | 
| 
      
 33 
     | 
    
         
            +
                  #
         
     | 
| 
      
 34 
     | 
    
         
            +
                  # @api private
         
     | 
| 
      
 35 
     | 
    
         
            +
                  #
         
     | 
| 
      
 36 
     | 
    
         
            +
                  def call(*)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    raise NotImplementedError
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
              end
         
     | 
| 
      
 41 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,21 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Bitbot
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Trader
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Utils
         
     | 
| 
      
 4 
     | 
    
         
            +
                  # Generates a nonce
         
     | 
| 
      
 5 
     | 
    
         
            +
                  #
         
     | 
| 
      
 6 
     | 
    
         
            +
                  # Some http requests use it to prevent double http requests
         
     | 
| 
      
 7 
     | 
    
         
            +
                  #
         
     | 
| 
      
 8 
     | 
    
         
            +
                  class NonceGenerator
         
     | 
| 
      
 9 
     | 
    
         
            +
                    # Generates a nonce from current time
         
     | 
| 
      
 10 
     | 
    
         
            +
                    #
         
     | 
| 
      
 11 
     | 
    
         
            +
                    # @return [Fixnum] nonce
         
     | 
| 
      
 12 
     | 
    
         
            +
                    #
         
     | 
| 
      
 13 
     | 
    
         
            +
                    # @api private
         
     | 
| 
      
 14 
     | 
    
         
            +
                    #
         
     | 
| 
      
 15 
     | 
    
         
            +
                    def self.generate(time_class = Time)
         
     | 
| 
      
 16 
     | 
    
         
            +
                      time_class.now.to_i
         
     | 
| 
      
 17 
     | 
    
         
            +
                    end
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
              end
         
     | 
| 
      
 21 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,25 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "virtus"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Bitbot
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Trader
         
     | 
| 
      
 5 
     | 
    
         
            +
                # User USD/BTC/etc wallet
         
     | 
| 
      
 6 
     | 
    
         
            +
                #
         
     | 
| 
      
 7 
     | 
    
         
            +
                class Wallet
         
     | 
| 
      
 8 
     | 
    
         
            +
                  include Virtus.model
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  attribute :value, BigDecimal
         
     | 
| 
      
 11 
     | 
    
         
            +
                  attribute :currency, String
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  # Shows wallet balance
         
     | 
| 
      
 14 
     | 
    
         
            +
                  #
         
     | 
| 
      
 15 
     | 
    
         
            +
                  # @example
         
     | 
| 
      
 16 
     | 
    
         
            +
                  #   wallet.balance #=> <BigDecimal>
         
     | 
| 
      
 17 
     | 
    
         
            +
                  #
         
     | 
| 
      
 18 
     | 
    
         
            +
                  # @return [BigDecimal]
         
     | 
| 
      
 19 
     | 
    
         
            +
                  #
         
     | 
| 
      
 20 
     | 
    
         
            +
                  # @api public
         
     | 
| 
      
 21 
     | 
    
         
            +
                  #
         
     | 
| 
      
 22 
     | 
    
         
            +
                  alias :balance :value
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
      
 25 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,28 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "spec_helper"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe Providers::Bitstamp, "#open_orders" do
         
     | 
| 
      
 4 
     | 
    
         
            +
              let(:result) {
         
     | 
| 
      
 5 
     | 
    
         
            +
                %![{"price": "20.10", "amount": "10.00000000", "type": 0, "id": 2826860, "datetime": "2013-04-20 12:12:14"}]!
         
     | 
| 
      
 6 
     | 
    
         
            +
              }
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              let(:provider) { described_class.new({}, client) }
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              let(:client) { described_class::HttpClient.new(connection, "double", "double") }
         
     | 
| 
      
 11 
     | 
    
         
            +
              let(:connection) { mock_connection("/open_orders/", result) }
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              it "parses open orders" do
         
     | 
| 
      
 14 
     | 
    
         
            +
                open_orders = provider.open_orders
         
     | 
| 
      
 15 
     | 
    
         
            +
                expect(open_orders.size).to be(1)
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                order = open_orders.first
         
     | 
| 
      
 18 
     | 
    
         
            +
                expect(order.id).to eq("2826860")
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                amount = order.amount
         
     | 
| 
      
 21 
     | 
    
         
            +
                expect(amount.value).to be_big_decimal(10)
         
     | 
| 
      
 22 
     | 
    
         
            +
                expect(amount.currency).to eq("BTC")
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                price = order.price
         
     | 
| 
      
 25 
     | 
    
         
            +
                expect(price.value).to be_big_decimal(20.10)
         
     | 
| 
      
 26 
     | 
    
         
            +
                expect(price.currency).to eq("USD")
         
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,28 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "spec_helper"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe Providers::MtGox, "#account" do
         
     | 
| 
      
 4 
     | 
    
         
            +
              let(:result) {
         
     | 
| 
      
 5 
     | 
    
         
            +
                %!{"result":"success","data":{"Login":"johndoe","Index":"193629","Id":"9b46b62e-4fd3-4cfa-9445-96f49dee846c","Rights":["get_info"],"Language":"en_US","Created":"2012-08-30 21:35:49","Last_Login":"2013-04-22 14:16:11","Wallets":{"BTC":{"Balance":{"value":"0.00000000","value_int":"0","display":"0.00000000\u00a0BTC","display_short":"0.00\u00a0BTC","currency":"BTC"},"Operations":164,"Daily_Withdraw_Limit":{"value":"100.00000000","value_int":"10000000000","display":"100.00000000\u00a0BTC","display_short":"100.00\u00a0BTC","currency":"BTC"},"Monthly_Withdraw_Limit":null,"Max_Withdraw":{"value":"100.00000000","value_int":"10000000000","display":"100.00000000\u00a0BTC","display_short":"100.00\u00a0BTC","currency":"BTC"},"Open_Orders":{"value":"0.00000000","value_int":"0","display":"0.00000000\u00a0BTC","display_short":"0.00\u00a0BTC","currency":"BTC"}},"EUR":{"Balance":{"value":"0.12314","value_int":"12314","display":"0.12314\u00a0\u20ac","display_short":"0.12\u00a0\u20ac","currency":"EUR"},"Operations":3,"Daily_Withdraw_Limit":{"value":"1000.00000","value_int":"100000000","display":"1,000.00000\u00a0\u20ac","display_short":"1,000.00\u00a0\u20ac","currency":"EUR"},"Monthly_Withdraw_Limit":{"value":"10000.00000","value_int":"1000000000","display":"10,000.00000\u00a0\u20ac","display_short":"10,000.00\u00a0\u20ac","currency":"EUR"},"Max_Withdraw":{"value":"1000.00000","value_int":"100000000","display":"1,000.00000\u00a0\u20ac","display_short":"1,000.00\u00a0\u20ac","currency":"EUR"},"Open_Orders":{"value":"0.00000","value_int":"0","display":"0.00000\u00a0\u20ac","display_short":"0.00\u00a0\u20ac","currency":"EUR"}},"USD":{"Balance":{"value":"3533.02728","value_int":"353302728","display":"$3,533.02728","display_short":"$3,533.03","currency":"USD"},"Operations":155,"Daily_Withdraw_Limit":{"value":"1000.00000","value_int":"100000000","display":"$1,000.00000","display_short":"$1,000.00","currency":"USD"},"Monthly_Withdraw_Limit":{"value":"10000.00000","value_int":"1000000000","display":"$10,000.00000","display_short":"$10,000.00","currency":"USD"},"Max_Withdraw":{"value":"1000.00000","value_int":"100000000","display":"$1,000.00000","display_short":"$1,000.00","currency":"USD"},"Open_Orders":{"value":"1053.50000","value_int":"105350000","display":"$1,053.50000","display_short":"$1,053.50","currency":"USD"}}},"Monthly_Volume":{"value":"413.48602792","value_int":"41348602792","display":"413.48602792\u00a0BTC","display_short":"413.49\u00a0BTC","currency":"BTC"},"Trade_Fee":0.53}}!
         
     | 
| 
      
 6 
     | 
    
         
            +
              }
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              let(:provider) { described_class.new(options, client) }
         
     | 
| 
      
 9 
     | 
    
         
            +
              let(:options)  { {key: "double", secret: "double"} }
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              let(:client) { described_class::HttpClient.new(connection) }
         
     | 
| 
      
 12 
     | 
    
         
            +
              let(:connection) { mock_connection("/money/info", result) }
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              it "parses account info" do
         
     | 
| 
      
 15 
     | 
    
         
            +
                account = provider.account
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                expect(account.fee).to eq(0.53)
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                usd_wallet = account.wallet("USD")
         
     | 
| 
      
 20 
     | 
    
         
            +
                expect(usd_wallet.balance).to eq(3533.02728)
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                eur_wallet = account.wallet("EUR")
         
     | 
| 
      
 23 
     | 
    
         
            +
                expect(eur_wallet.balance).to eq(0.12314)
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                btc_wallet = account.wallet("BTC")
         
     | 
| 
      
 26 
     | 
    
         
            +
                expect(btc_wallet.balance).to eq(0)
         
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,29 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "spec_helper"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe Providers::MtGox, "#open_orders" do
         
     | 
| 
      
 4 
     | 
    
         
            +
              let(:result) {
         
     | 
| 
      
 5 
     | 
    
         
            +
                %!{"result":"success","data":[{"oid":"7c6d2237-52fb-4af4-b6ec-75e42f50c331","currency":"USD","item":"BTC","type":"bid","amount":{"value":"35.00000000","value_int":"3500000000","display":"35.00000000\u00a0BTC","display_short":"35.00\u00a0BTC","currency":"BTC"},"effective_amount":{"value":"35.00000000","value_int":"3500000000","display":"35.00000000\u00a0BTC","display_short":"35.00\u00a0BTC","currency":"BTC"},"price":{"value":"30.10000","value_int":"3010000","display":"$30.10000","display_short":"$30.10","currency":"USD"},"status":"open","date":1365886868,"priority":"1365886868485232","actions":[]}]}!
         
     | 
| 
      
 6 
     | 
    
         
            +
              }
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              let(:provider) { described_class.new(options, client) }
         
     | 
| 
      
 9 
     | 
    
         
            +
              let(:options)  { {key: "double", secret: "double"} }
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              let(:client) { described_class::HttpClient.new(connection) }
         
     | 
| 
      
 12 
     | 
    
         
            +
              let(:connection) { mock_connection("/money/orders", result) }
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              it "parses open orders" do
         
     | 
| 
      
 15 
     | 
    
         
            +
                open_orders = provider.open_orders
         
     | 
| 
      
 16 
     | 
    
         
            +
                expect(open_orders.size).to be(1)
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                order = open_orders.first
         
     | 
| 
      
 19 
     | 
    
         
            +
                expect(order.id).to eq("7c6d2237-52fb-4af4-b6ec-75e42f50c331")
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                amount = order.amount
         
     | 
| 
      
 22 
     | 
    
         
            +
                expect(amount.value).to eq(35)
         
     | 
| 
      
 23 
     | 
    
         
            +
                expect(amount.currency).to eq("BTC")
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                price = order.price
         
     | 
| 
      
 26 
     | 
    
         
            +
                expect(price.value).to eq(30.1)
         
     | 
| 
      
 27 
     | 
    
         
            +
                expect(price.currency).to eq("USD")
         
     | 
| 
      
 28 
     | 
    
         
            +
              end
         
     | 
| 
      
 29 
     | 
    
         
            +
            end
         
     | 
    
        data/spec/spec_helper.rb
    ADDED
    
    | 
         @@ -0,0 +1,18 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            if ENV["COVERAGE"] == "true"
         
     | 
| 
      
 2 
     | 
    
         
            +
              require "simplecov"
         
     | 
| 
      
 3 
     | 
    
         
            +
              require "coveralls"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
         
     | 
| 
      
 6 
     | 
    
         
            +
                SimpleCov::Formatter::HTMLFormatter,
         
     | 
| 
      
 7 
     | 
    
         
            +
                Coveralls::SimpleCov::Formatter
         
     | 
| 
      
 8 
     | 
    
         
            +
              ]
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              SimpleCov.start do
         
     | 
| 
      
 11 
     | 
    
         
            +
                add_filter "spec"
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
            end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            require "bitbot/trader"
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            include Bitbot::Trader
         
     | 
| 
      
 18 
     | 
    
         
            +
            require "devtools/spec_helper"
         
     | 
| 
         @@ -0,0 +1,15 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module HttpConnectionHelpers
         
     | 
| 
      
 2 
     | 
    
         
            +
              def mock_connection(path, result)
         
     | 
| 
      
 3 
     | 
    
         
            +
                Faraday.new { |builder|
         
     | 
| 
      
 4 
     | 
    
         
            +
                  builder.adapter :test do |stub|
         
     | 
| 
      
 5 
     | 
    
         
            +
                    stub.post(path) { [200, {}, result] }
         
     | 
| 
      
 6 
     | 
    
         
            +
                  end
         
     | 
| 
      
 7 
     | 
    
         
            +
                }
         
     | 
| 
      
 8 
     | 
    
         
            +
              end
         
     | 
| 
      
 9 
     | 
    
         
            +
            end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            RSpec.configure do |config|
         
     | 
| 
      
 12 
     | 
    
         
            +
              config.include HttpConnectionHelpers, type: :integration, example_group: {
         
     | 
| 
      
 13 
     | 
    
         
            +
                file_path: /spec\/integration/
         
     | 
| 
      
 14 
     | 
    
         
            +
              }
         
     | 
| 
      
 15 
     | 
    
         
            +
            end
         
     |