├── .github ├── ISSUE_TEMPLATE └── PULL_REQUEST_TEMPLATE ├── .gitignore ├── .rspec ├── CHANGELOG ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bitfinex-rb.gemspec ├── docs ├── .keep └── events.md ├── examples ├── models │ ├── funding_offer.rb │ └── order_book.rb ├── rest │ ├── v1 │ │ ├── account_info.rb │ │ ├── deposit.rb │ │ ├── funding_book.rb │ │ ├── new_order.rb │ │ ├── order_book.rb │ │ ├── orders.rb │ │ ├── positions.rb │ │ ├── proxy.rb │ │ ├── stats.rb │ │ ├── symbol_details.rb │ │ ├── symbols.rb │ │ ├── ticker.rb │ │ └── trades.rb │ └── v2 │ │ ├── candles.rb │ │ ├── funding.rb │ │ ├── margin_info.rb │ │ ├── orders.rb │ │ ├── positions.rb │ │ ├── pulse.rb │ │ ├── submit_order.rb │ │ ├── tickers.rb │ │ └── wallets.rb └── ws │ └── v2 │ ├── auth.rb │ ├── candles.rb │ ├── connect.rb │ ├── order_book.rb │ ├── order_book_checksums.rb │ ├── orders.rb │ ├── public_trades.rb │ ├── sequence_verification.rb │ └── ticker.rb ├── lib ├── bitfinex.rb ├── errors.rb ├── faraday │ └── adapter │ │ └── net_http_socks.rb ├── models │ ├── alert.rb │ ├── balance_info.rb │ ├── candle.rb │ ├── currency.rb │ ├── funding_credit.rb │ ├── funding_info.rb │ ├── funding_loan.rb │ ├── funding_offer.rb │ ├── funding_ticker.rb │ ├── funding_trade.rb │ ├── ledger_entry.rb │ ├── margin_info.rb │ ├── model.rb │ ├── movement.rb │ ├── notification.rb │ ├── order.rb │ ├── order_book.rb │ ├── position.rb │ ├── public_trade.rb │ ├── pulse.rb │ ├── pulse_profile.rb │ ├── trade.rb │ ├── trading_ticker.rb │ ├── user_info.rb │ └── wallet.rb ├── rest │ ├── rest_client.rb │ ├── v1.rb │ ├── v1 │ │ ├── account_info.rb │ │ ├── deposit.rb │ │ ├── funding_book.rb │ │ ├── historical_data.rb │ │ ├── lends.rb │ │ ├── margin_funding.rb │ │ ├── order_book.rb │ │ ├── orders.rb │ │ ├── positions.rb │ │ ├── stats.rb │ │ ├── symbols.rb │ │ ├── ticker.rb │ │ ├── trades.rb │ │ └── wallet.rb │ ├── v2.rb │ └── v2 │ │ ├── funding.rb │ │ ├── margin.rb │ │ ├── orders.rb │ │ ├── personal.rb │ │ ├── positions.rb │ │ ├── pulse.rb │ │ ├── stats.rb │ │ ├── ticker.rb │ │ ├── trading.rb │ │ ├── utils.rb │ │ └── wallet.rb └── ws │ └── ws2.rb └── spec ├── integration ├── v1_account_info_spec.rb ├── v1_proxy_account_info.rb ├── v2_orders_spec.rb └── ws_connect_spec.rb └── spec_helper.rb /.github/ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | #### Issue type 2 | - [ ] bug 3 | - [ ] missing functionality 4 | - [ ] performance 5 | - [ ] feature request 6 | 7 | #### Brief description 8 | 9 | #### Steps to reproduce 10 | - 11 | 12 | ##### Additional Notes: 13 | - 14 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | ### Description: 2 | ... 3 | 4 | ### Breaking changes: 5 | - [ ] 6 | 7 | ### New features: 8 | - [ ] 9 | 10 | ### Fixes: 11 | - [ ] 12 | 13 | ### PR status: 14 | - [ ] Version bumped 15 | - [ ] Change-log updated 16 | - [ ] Documentation updated 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | *.bundle 19 | *.so 20 | *.o 21 | *.a 22 | mkmf.log 23 | .env 24 | todo 25 | *.sw? 26 | .ruby-version 27 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 1.1.0 2 | - Bump dependencies for ruby 3.x 3 | - Replace faraday_adapter_socks with custom class 4 | 5 | 1.0.11 6 | - Fixes nonce issue to be compatible with other libraries 7 | 8 | 1.0.10 9 | - Adds function rest/v2 get_pulse_profile 10 | - Adds function rest/v2 get_public_pulse_history 11 | - Adds function rest/v2 submit_pulse 12 | - Adds function rest/v2 delete_pulse 13 | - Adds function rest/v2 get_private_pulse_history 14 | - Adds function rest/v2 submit_pulse_comment 15 | 16 | 1.0.9 17 | 18 | - Fixes bug with API v2 transfer 19 | 20 | 1.0.8 21 | 22 | - Fixes warning about BigDecimal.new deprecation 23 | 24 | 1.0.7 25 | 26 | - Fixes issue with RESTv2 client initialization. 27 | 28 | 1.0.6 29 | - Adds order affiliate code support to rest & ws v2 transports 30 | 31 | 1.0.5 32 | 33 | - Adds function rest/v2 submit_order 34 | - Adds function rest/v2 update_order 35 | - Adds function rest/v2 cancel_order 36 | - Adds function rest/v2 submit_funding_offer 37 | - Adds function rest/v2 cancel_funding_offer 38 | - Adds function rest/v2 close_funding 39 | - Adds function rest/v2 submit_funding_auto 40 | - Adds function rest/v2 transfer 41 | - Adds function rest/v2 deposit_address 42 | - Adds function rest/v2 create_deposit_address 43 | - Adds function rest/v2 withdraw 44 | - Adds function rest/v2 claim_position 45 | - Updates order model to allow lev field 46 | - Updates position model to accept more data fields 47 | 48 | 1.0.4 49 | 50 | - Adds PR/Issue templates 51 | - Adds travis linting 52 | - bump json dep 2.1.0 -> 2.2.0 53 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | gem 'dotenv' 6 | gem 'emittr' 7 | gem 'zlib' 8 | 9 | group :development, :test do 10 | gem 'rspec' 11 | gem 'simplecov', require: false 12 | gem 'webmock', '~> 3.14.0' 13 | end 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 iFinex INC 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bitfinex Trading API for Ruby - Bitcoin, Ethereum, Ripple and more 2 | 3 | [![Code Climate](https://codeclimate.com/repos/56db27e5b86182573b0045ed/badges/bd763083d70114379a41/gpa.svg)](https://codeclimate.com/repos/56db27e5b86182573b0045ed/feed) 4 | 5 | A Ruby reference implementation of the Bitfinex REST & WebSocket APIs. 6 | 7 | This repo is primarily made up of 3 classes: RESTv1, RESTv2, and WSv2, which implement their respective versions of the Bitfinex API. It is recommended that you use the REST APIs for reading data, and the WebSocket API for submitting orders and interacting with the Bitfinex platform. 8 | 9 | Check the [Bitfinex API documentation](http://docs.bitfinex.com/) for more information. 10 | 11 | ### Features 12 | * Official implementation 13 | * REST API v1 14 | * REST API v2 15 | * WebSockets API version 2 16 | 17 | ## Installation 18 | 19 | Add this line to your application's Gemfile: 20 | 21 | ```ruby 22 | gem 'bitfinex-rb', :require => "bitfinex" 23 | ``` 24 | 25 | And then execute: 26 | ```bash 27 | bundle 28 | ``` 29 | 30 | Or install it yourself as: 31 | ```bash 32 | gem install bitfinex-rb 33 | ``` 34 | 35 | ### Quickstart 36 | ```ruby 37 | client = Bitfinex::WSv2.new({ 38 | :api_key => ENV['API_KEY'], 39 | :api_secret => ENV['API_SECRET'], 40 | :transform => true, # provide models as event data instead of arrays 41 | }) 42 | 43 | client.on(:open) do 44 | client.auth! 45 | end 46 | 47 | client.on(:auth) do 48 | puts 'succesfully authenticated' 49 | 50 | o = Bitfinex::Models::Order.new({ 51 | :type => 'EXCHANGE LIMIT', 52 | :price => 3.0152235, 53 | :amount => 2.0235235263262, 54 | :symbol => 'tEOSUSD' 55 | }) 56 | 57 | client.submit_order(o) 58 | end 59 | ``` 60 | 61 | ### Docs 62 | 63 | [Refer to `docs/events.md`](/docs/events.md) for a list of available events which can be consumed. Official API docs pending. 64 | 65 | For ready to run examples, see the [`examples/` folder](/examples). 66 | 67 | ### Examples 68 | #### Usage of RESTv1/RESTv2 69 | 70 | To use the REST APIs, construct a new API client with your account credentials: 71 | 72 | ```ruby 73 | client = Bitfinex::RESTv2.new({ 74 | :api_key => '...', 75 | :api_secret => '...', 76 | }) 77 | ``` 78 | 79 | Then use it to submit queries, i.e. `client.balances` 80 | 81 | #### Usage of WSv2 82 | To use version 2 of the WS API, construct a new client with your credentials, bind listeners to react to stream events, and open the connection: 83 | 84 | ```ruby 85 | client = Bitfinex::WSv2.new({ 86 | :url => ENV['WS_URL'], 87 | :api_key => ENV['API_KEY'], 88 | :api_secret => ENV['API_SECRET'], 89 | :transform => true, # provide models as event data instead of arrays 90 | :seq_audit => true, # enable and audit sequence numbers 91 | :manage_order_books => true, # allows for OB checksum verification 92 | :checksum_audit => true # enables OB checksum verification (needs manage_order_books) 93 | }) 94 | 95 | client.on(:open) do 96 | client.auth! 97 | end 98 | 99 | client.on(:auth) do 100 | puts 'succesfully authenticated' 101 | 102 | o = Bitfinex::Models::Order.new({ 103 | :type => 'EXCHANGE LIMIT', 104 | :price => 3.0152235, 105 | :amount => 2.0235235263262, 106 | :symbol => 'tEOSUSD' 107 | }) 108 | 109 | client.submit_order(o) 110 | end 111 | 112 | client.on(:notification) do |n| 113 | puts 'received notification: %s' % [n] 114 | end 115 | 116 | client.on(:order_new) do |msg| 117 | puts 'recv order new: %s' % [msg] 118 | end 119 | 120 | client.open! 121 | ``` 122 | 123 | #### Order Manipulation 124 | Three methods are provided for dealing with orders: `submit_order`, `update_order` and `cancel_order`. All methods support callback blocks, which are triggered upon receiving the relevant confirmation notifications. Example: 125 | 126 | ```ruby 127 | o = Bitfinex::Models::Order.new({ 128 | :type => 'EXCHANGE LIMIT', 129 | :price => 3.0152235, 130 | :amount => 2.0235235263262, 131 | :symbol => 'tEOSUSD' 132 | }) 133 | 134 | client.submit_order(o) do |order_packet| 135 | p "recv order confirmation packet with ID #{order_packet.id}" 136 | 137 | client.update_order({ 138 | :id => order_packet.id, 139 | :price => '3.0' 140 | }) do |update_packet| 141 | p "updated order #{update_packet.id} with price #{update_packet.price}" 142 | 143 | client.cancel_order(order_packet) do |canceled_order| 144 | p "canceled order with ID #{canceled_order[0]}" 145 | end 146 | end 147 | end 148 | ``` 149 | 150 | ### Contributing 151 | 152 | 1. Fork it 153 | 2. Create your feature branch (`git checkout -b my-new-feature`) 154 | 3. Commit your changes (`git commit -am 'Add some feature'`) 155 | 4. Push to the branch (`git push origin my-new-feature`) 156 | 5. Create a new Pull Request 157 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | desc "Preloaded Ruby Shell" 4 | task :console do 5 | sh "irb -rubygems -I lib -r bitfinex.rb" 6 | end 7 | 8 | -------------------------------------------------------------------------------- /bitfinex-rb.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'bitfinex-rb' 7 | spec.version = '1.1.0' 8 | spec.authors = ['Bitfinex'] 9 | spec.email = ['developers@bitfinex.com'] 10 | spec.summary = %q{Bitfinex API Wrapper} 11 | spec.description = %q{Official Bitfinex API ruby wrapper} 12 | spec.homepage = 'https://www.bitfinex.com/' 13 | spec.license = 'MIT' 14 | 15 | spec.files = Dir['lib/**/*'] 16 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 17 | spec.require_paths = ['lib'] 18 | 19 | spec.add_runtime_dependency 'faraday', '~> 1.10.3' 20 | spec.add_runtime_dependency 'eventmachine', '~> 1.2.7' 21 | spec.add_runtime_dependency 'faraday-detailed_logger', '~> 2.5.0' 22 | spec.add_runtime_dependency 'faye-websocket', '~> 0.11.3' 23 | spec.add_runtime_dependency 'json', '~> 2.0', '>= 2.2.0' 24 | spec.add_runtime_dependency 'faraday_middleware', '~> 1.2.0' 25 | spec.add_runtime_dependency 'emittr', '~> 0.1.0' 26 | spec.add_runtime_dependency 'dotenv', '~> 2.7.6' 27 | spec.add_runtime_dependency 'socksify', '~> 1.7.1' 28 | spec.add_runtime_dependency 'zlib', '~> 1.0.0' 29 | end 30 | -------------------------------------------------------------------------------- /docs/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfinexcom/bitfinex-api-rb/ad0960e93253ffda79deccc457bdfc3db676c3b7/docs/.keep -------------------------------------------------------------------------------- /docs/events.md: -------------------------------------------------------------------------------- 1 | ### Available Events 2 | #### Lifecycle Events 3 | * `:open` 4 | * `:close` 5 | * `:error` 6 | * `:auth:` 7 | 8 | #### Info Events 9 | * `:server_restart` 10 | * `:server_maintenance_start` 11 | * `:server_maintenance_end` 12 | * `:unsubscribed` 13 | * `:subscribed` 14 | 15 | #### Data Events 16 | * `:ticker` 17 | * `:public_trades` 18 | * `:public_trade_entry` 19 | * `:public_trade_update` 20 | * `:candles` 21 | * `:checksum` 22 | * `:order_book` 23 | * `:notification` 24 | * `:trade_entry` 25 | * `:trade_update` 26 | * `:order_snapshot` 27 | * `:order_update` 28 | * `:order_new` 29 | * `:order_close` 30 | * `:position_snapshot` 31 | * `:position_new` 32 | * `:position_update` 33 | * `:position_close` 34 | * `:funding_offer_snapshot` 35 | * `:funding_offer_new` 36 | * `:funding_offer_update` 37 | * `:funding_offer_close` 38 | * `:funding_credit_snapshot` 39 | * `:funding_credit_new` 40 | * `:funding_credit_update` 41 | * `:funding_credit_close` 42 | * `:funding_loan_snapshot` 43 | * `:funding_loan_new` 44 | * `:funding_loan_update` 45 | * `:funding_loan_close` 46 | * `:wallet_snapshot` 47 | * `:wallet_update` 48 | * `:balance_update` 49 | * `:marign_info_update` 50 | * `:funding_info_update` 51 | * `:funding_trade_entry` 52 | * `:funding_trade_update` 53 | -------------------------------------------------------------------------------- /examples/models/funding_offer.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../lib/bitfinex' 2 | require_relative '../../lib/models/funding_offer' 3 | 4 | fo_object = Bitfinex::Models::FundingOffer.unserialize([ 5 | 123, 'tBTCUSD', Time.now.to_i, Time.now.to_i, 1, 1, 'LIMIT', nil, nil, 0, 6 | 'ACTIVE', nil, nil, nil, 0.012, 4, 0, 0, nil, 0, 0.012 7 | ]) 8 | 9 | p fo_object 10 | 11 | fo_model = Bitfinex::Models::FundingOffer.new(fo_object) 12 | 13 | p fo_model 14 | 15 | fo_array = fo_model.serialize() 16 | 17 | p fo_array -------------------------------------------------------------------------------- /examples/models/order_book.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../lib/bitfinex' 2 | require_relative '../../lib/models/order_book' 3 | 4 | ob_object = Bitfinex::Models::OrderBook.unserialize([ 5 | [140, 1, 10], 6 | [145, 1, 10], 7 | [148, 1, 10], 8 | [149, 1, 10], 9 | [151, 1, -10], 10 | [152, 1, -10], 11 | [158, 1, -10], 12 | [160, 1, -10] 13 | ]) 14 | 15 | p ob_object 16 | 17 | ob = Bitfinex::Models::OrderBook.new(ob_object) 18 | 19 | puts ob.checksum 20 | 21 | ob_array = ob.serialize() 22 | 23 | puts ob_array -------------------------------------------------------------------------------- /examples/rest/v1/account_info.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv1.new({ 4 | :url => ENV['REST_URL'], 5 | :proxy => ENV['PROXY'], 6 | :api_key => ENV['API_KEY'], 7 | :api_secret => ENV['API_SECRET'] 8 | }) 9 | 10 | puts client.account_info 11 | puts client.fees 12 | -------------------------------------------------------------------------------- /examples/rest/v1/deposit.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv1.new({ 4 | :url => ENV['REST_URL'], 5 | :proxy => ENV['PROXY'], 6 | :api_key => ENV['API_KEY'], 7 | :api_secret => ENV['API_SECRET'] 8 | }) 9 | 10 | puts client.deposit('bitcoin', 'exchange') 11 | -------------------------------------------------------------------------------- /examples/rest/v1/funding_book.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv1.new({ 4 | :url => ENV['REST_URL'], 5 | :proxy => ENV['PROXY'], 6 | }) 7 | 8 | puts client.funding_book 9 | -------------------------------------------------------------------------------- /examples/rest/v1/new_order.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv1.new({ 4 | :url => ENV['REST_URL'], 5 | :proxy => ENV['PROXY'], 6 | :api_key => ENV['API_KEY'], 7 | :api_secret => ENV['API_SECRET'] 8 | }) 9 | 10 | puts client.new_order('btcusd', 0.1, 'limit', 'sell', 15_000, { 11 | :is_hidden => true 12 | }) 13 | -------------------------------------------------------------------------------- /examples/rest/v1/order_book.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv1.new({ 4 | :url => ENV['REST_URL'], 5 | :proxy => ENV['PROXY'], 6 | }) 7 | 8 | puts client.orderbook 9 | -------------------------------------------------------------------------------- /examples/rest/v1/orders.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv1.new({ 4 | :url => ENV['REST_URL'], 5 | :proxy => ENV['PROXY'], 6 | :api_key => ENV['API_KEY'], 7 | :api_secret => ENV['API_SECRET'] 8 | }) 9 | 10 | puts client.orders 11 | -------------------------------------------------------------------------------- /examples/rest/v1/positions.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv1.new({ 4 | :url => ENV['REST_URL'], 5 | :proxy => ENV['PROXY'], 6 | :api_key => ENV['API_KEY'], 7 | :api_secret => ENV['API_SECRET'] 8 | }) 9 | 10 | puts client.positions 11 | -------------------------------------------------------------------------------- /examples/rest/v1/proxy.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv1.new({ 4 | :url => ENV['REST_URL'], 5 | :proxy => ENV['PROXY'] 6 | }) 7 | 8 | puts client.symbols_details 9 | -------------------------------------------------------------------------------- /examples/rest/v1/stats.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv1.new({ 4 | :url => ENV['REST_URL'], 5 | :proxy => ENV['PROXY'], 6 | :api_key => ENV['API_KEY'], 7 | :api_secret => ENV['API_SECRET'] 8 | }) 9 | 10 | puts client.stats('ethusd') 11 | -------------------------------------------------------------------------------- /examples/rest/v1/symbol_details.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv1.new 4 | puts client.symbols_details 5 | -------------------------------------------------------------------------------- /examples/rest/v1/symbols.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv1.new 4 | puts client.symbols 5 | -------------------------------------------------------------------------------- /examples/rest/v1/ticker.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv1.new({ 4 | :url => ENV['REST_URL'], 5 | :proxy => ENV['PROXY'], 6 | :api_key => ENV['API_KEY'], 7 | :api_secret => ENV['API_SECRET'] 8 | }) 9 | 10 | puts client.ticker('ethusd') 11 | -------------------------------------------------------------------------------- /examples/rest/v1/trades.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv1.new({ 4 | :url => ENV['REST_URL'], 5 | :proxy => ENV['PROXY'], 6 | :api_key => ENV['API_KEY'], 7 | :api_secret => ENV['API_SECRET'] 8 | }) 9 | 10 | puts client.trades('btcusd') 11 | -------------------------------------------------------------------------------- /examples/rest/v2/candles.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv2.new({ 4 | :url => ENV['REST_URL'], 5 | :proxy => ENV['PROXY'], 6 | :api_key => ENV['API_KEY'], 7 | :api_secret => ENV['API_SECRET'] 8 | }) 9 | 10 | puts client.candles('tBTCUSD', '1m', 'hist') 11 | -------------------------------------------------------------------------------- /examples/rest/v2/funding.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv2.new({ 4 | :url => ENV['REST_URL'], 5 | :proxy => ENV['PROXY'], 6 | :api_key => ENV['API_KEY'], 7 | :api_secret => ENV['API_SECRET'] 8 | }) 9 | 10 | o = Bitfinex::Models::FundingOffer.new({ 11 | :type => 'LIMIT', 12 | :symbol => 'fUSD', 13 | :amount => 100, 14 | :rate => 0.002, 15 | :period => 2 16 | }) 17 | 18 | # Submit an offer 19 | print client.submit_funding_offer(o) 20 | # Cancel and offer 21 | print client.cancel_funding_offer(41236686) 22 | 23 | # Request auto funding 24 | print client.submit_funding_auto('USD', 100, 2) 25 | -------------------------------------------------------------------------------- /examples/rest/v2/margin_info.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv2.new({ 4 | :url => ENV['REST_URL'], 5 | :proxy => ENV['PROXY'], 6 | :api_key => ENV['API_KEY'], 7 | :api_secret => ENV['API_SECRET'] 8 | }) 9 | 10 | puts client.margin_info('base') 11 | -------------------------------------------------------------------------------- /examples/rest/v2/orders.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv2.new({ 4 | :url => ENV['REST_URL'], 5 | :proxy => ENV['PROXY'], 6 | :api_key => ENV['API_KEY'], 7 | :api_secret => ENV['API_SECRET'] 8 | }) 9 | 10 | puts client.orders 11 | -------------------------------------------------------------------------------- /examples/rest/v2/positions.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv2.new({ 4 | :url => ENV['REST_URL'], 5 | :proxy => ENV['PROXY'], 6 | :api_key => ENV['API_KEY'], 7 | :api_secret => ENV['API_SECRET'] 8 | }) 9 | 10 | puts client.active_positions 11 | -------------------------------------------------------------------------------- /examples/rest/v2/pulse.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv2.new({ 4 | :url => ENV['REST_URL'], 5 | :api_key => ENV['API_KEY'], 6 | :api_secret => ENV['API_SECRET'] 7 | }) 8 | 9 | # Get pulse profile 10 | p client.get_pulse_profile('Bitfinex') 11 | 12 | # Get public pulse history 13 | p client.get_public_pulse_history({ :limit => 5 }) 14 | 15 | # Submit new Pulse message 16 | pulse = client.submit_pulse({ 17 | :title => '1234 5678 Foo Bar Baz Qux TITLE', 18 | :content => '1234 5678 Foo Bar Baz Qux Content', 19 | :isPublic => 0, 20 | :isPin => 1 21 | }) 22 | p pulse 23 | 24 | # Delete Pulse message 25 | p "About to delete pulse: #{pulse[:id]}" 26 | p client.delete_pulse(pulse[:id]) 27 | 28 | # Get private pulse history 29 | p client.get_private_pulse_history() 30 | 31 | # Submit Pulse message comment 32 | # 1 - create pulse message 33 | pulse2 = client.submit_pulse({ 34 | :title => '2 1234 5678 Foo Bar Baz Qux TITLE', 35 | :content => '2 1234 5678 Foo Bar Baz Qux Content', 36 | :isPublic => 0, 37 | :isPin => 1 38 | }) 39 | 40 | # 2 - submit comment for above pulse message 41 | p client.submit_pulse_comment({ 42 | :parent => pulse2[:id], 43 | :title => 'comment 2 1234 5678 Foo Bar Baz Qux TITLE', 44 | :content => 'comment 2 1234 5678 Foo Bar Baz Qux Content', 45 | :isPublic => 0, 46 | :isPin => 1 47 | }) 48 | -------------------------------------------------------------------------------- /examples/rest/v2/submit_order.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv2.new({ 4 | :url => ENV['REST_URL'], 5 | :proxy => ENV['PROXY'], 6 | :api_key => ENV['API_KEY'], 7 | :api_secret => ENV['API_SECRET'] 8 | }) 9 | 10 | o = Bitfinex::Models::Order.new({ 11 | :type => 'LIMIT', 12 | :price => 8000, 13 | :amount => 0.1, 14 | :symbol => 'tBTCF0:USDF0', 15 | :lev => 4 16 | }) 17 | 18 | # Submit an order 19 | print client.submit_order(o) 20 | # Update an order 21 | print client.update_order({ :id => 1185657359, :price => '14730' }) 22 | # Cancel an order 23 | print client.cancel_order({ :id => 1185657349 }) 24 | -------------------------------------------------------------------------------- /examples/rest/v2/tickers.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv2.new({ 4 | :url => ENV['REST_URL'], 5 | :proxy => ENV['PROXY'], 6 | :api_key => ENV['API_KEY'], 7 | :api_secret => ENV['API_SECRET'] 8 | }) 9 | 10 | puts client.ticker('tBTCUSD', 'fUSD') 11 | -------------------------------------------------------------------------------- /examples/rest/v2/wallets.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::RESTv2.new({ 4 | :url => ENV['REST_URL'], 5 | :proxy => ENV['PROXY'], 6 | :api_key => ENV['API_KEY'], 7 | :api_secret => ENV['API_SECRET'] 8 | }) 9 | 10 | # print all wallets 11 | puts client.wallets 12 | # print bitcoin deposit address 13 | puts client.deposit_address('exchange', 'bitcoin') 14 | # create/print new deposit address 15 | puts client.create_deposit_address('exchange', 'bitcoin') 16 | -------------------------------------------------------------------------------- /examples/ws/v2/auth.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::WSv2.new({ 4 | :url => ENV['WS_URL'], 5 | :api_key => ENV['API_KEY'], 6 | :api_secret => ENV['API_SECRET'], 7 | :transform => true 8 | }) 9 | 10 | client.on(:open) do 11 | client.auth! 12 | end 13 | 14 | client.on(:auth) do 15 | p 'succesfully authenticated' 16 | end 17 | 18 | client.on(:notification) do |n| 19 | p 'received notification: %s' % [n] 20 | end 21 | 22 | client.on(:position_snapshot) do |positions| 23 | p 'recv position snapshot' 24 | positions.each do |pos| 25 | p pos.serialize 26 | end 27 | end 28 | 29 | client.open! -------------------------------------------------------------------------------- /examples/ws/v2/candles.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::WSv2.new({ 4 | :url => ENV['WS_URL'], 5 | :transform => true 6 | }) 7 | 8 | client.on(:candles) do |key, msg| 9 | p "recv candle message for key #{key}" 10 | 11 | if msg.kind_of?(Array) 12 | p msg.map { |c| c.serialize.join('|') } 13 | else 14 | p msg.serialize.join('|') 15 | end 16 | end 17 | 18 | client.on(:open) do 19 | client.subscribe_candles('trade:1m:tBTCUSD') 20 | end 21 | 22 | client.open! -------------------------------------------------------------------------------- /examples/ws/v2/connect.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::WSv2.new({ 4 | :url => ENV['WS_URL'], 5 | }) 6 | -------------------------------------------------------------------------------- /examples/ws/v2/order_book.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::WSv2.new({ 4 | :url => ENV['WS_URL'], 5 | :transform => true 6 | }) 7 | 8 | client.on(:order_book) do |sym, msg| 9 | p "recv order book message for symbol #{sym}" 10 | p msg.serialize 11 | end 12 | 13 | client.on(:open) do 14 | client.subscribe_order_book('tBTCUSD', 'R0', '25') 15 | end 16 | 17 | client.open! -------------------------------------------------------------------------------- /examples/ws/v2/order_book_checksums.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::WSv2.new({ 4 | :url => ENV['WS_URL'], 5 | :manage_order_books => true 6 | }) 7 | 8 | client.on(:order_book) do |sym, msg| 9 | p "recv order book message for symbol #{sym}" 10 | p msg 11 | end 12 | 13 | client.on(:open) do 14 | client.subscribe_order_book('tBTCUSD', 'P0', '25') 15 | client.enable_ob_checksums 16 | end 17 | 18 | client.open! -------------------------------------------------------------------------------- /examples/ws/v2/orders.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::WSv2.new({ 4 | :url => ENV['WS_URL'], 5 | :api_key => ENV['API_KEY'], 6 | :api_secret => ENV['API_SECRET'], 7 | :transform => true 8 | }) 9 | 10 | client.on(:open) do 11 | client.auth! 12 | end 13 | 14 | client.on(:auth) do 15 | p 'succesfully authenticated' 16 | 17 | o = Bitfinex::Models::Order.new({ 18 | :type => 'EXCHANGE LIMIT', 19 | :price => 3.0152235, 20 | :amount => 2.0235235263262, 21 | :symbol => 'tEOSUSD' 22 | }) 23 | 24 | client.submit_order(o) do |order_packet| 25 | p "recv order confirmation packet with ID #{order_packet.id}" 26 | 27 | client.update_order({ 28 | :id => order_packet.id, 29 | :price => '3.0' 30 | }) do |update_packet| 31 | p "updated order #{update_packet.id} with price #{update_packet.price}" 32 | 33 | client.cancel_order(order_packet) do |canceled_order| 34 | p "canceled order with ID #{canceled_order[0]}" 35 | end 36 | end 37 | end 38 | end 39 | 40 | client.on(:notification) do |n| 41 | p 'received notification: %s' % [n.serialize.join('|')] 42 | end 43 | 44 | client.on(:order_new) do |msg| 45 | p 'recv order new: %s' % [msg] 46 | end 47 | 48 | client.open! 49 | -------------------------------------------------------------------------------- /examples/ws/v2/public_trades.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::WSv2.new({ 4 | :url => ENV['WS_URL'], 5 | :transform => true 6 | }) 7 | 8 | client.on(:public_trades) do |sym, msg| 9 | p "recv public trades message for symbol #{sym}" 10 | 11 | if msg.kind_of?(Array) 12 | p msg.map { |t| t.serialize.join('|') } 13 | else 14 | p msg.serialize.join('|') 15 | end 16 | end 17 | 18 | client.on(:public_trade_entry) do |sym, msg| 19 | p "recv public trade entry for symbol #{sym}" 20 | p msg.serialize.join('|') 21 | end 22 | 23 | client.on(:public_trade_update) do |sym, msg| 24 | p "recv public trade update for symbol #{sym}" 25 | p msg.serialize.join('|') 26 | end 27 | 28 | client.on(:open) do 29 | client.subscribe_trades('tBTCUSD') 30 | end 31 | 32 | client.open! -------------------------------------------------------------------------------- /examples/ws/v2/sequence_verification.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::WSv2.new({ 4 | :url => ENV['WS_URL'], 5 | :manage_order_books => true 6 | }) 7 | 8 | client.on(:open) do 9 | client.subscribe_order_book('tBTCUSD', 'P0', '25') 10 | client.enable_sequencing 11 | end 12 | 13 | client.open! -------------------------------------------------------------------------------- /examples/ws/v2/ticker.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/bitfinex.rb' 2 | 3 | client = Bitfinex::WSv2.new({ 4 | :url => ENV['WS_URL'], 5 | :transform => true 6 | }) 7 | 8 | client.on(:ticker) do |symbol, msg| 9 | p "recv ticker message for symbol #{symbol}" 10 | p msg.serialize.join('|') 11 | end 12 | 13 | client.on(:open) do 14 | client.subscribe_ticker('tBTCUSD') 15 | client.subscribe_ticker('fUSD') 16 | end 17 | 18 | client.open! -------------------------------------------------------------------------------- /lib/bitfinex.rb: -------------------------------------------------------------------------------- 1 | require 'uri' 2 | require 'base64' 3 | require 'openssl' 4 | require 'faraday' 5 | require 'socksify' 6 | require 'socksify/http' 7 | require 'json' 8 | require 'faraday_middleware' 9 | require 'dotenv/load' 10 | 11 | require_relative './errors' 12 | require_relative './rest/v1' 13 | require_relative './rest/v2' 14 | require_relative './ws/ws2' 15 | 16 | require_relative './faraday/adapter/net_http_socks' 17 | require_relative './models/alert' 18 | require_relative './models/balance_info' 19 | require_relative './models/candle' 20 | require_relative './models/currency' 21 | require_relative './models/funding_credit' 22 | require_relative './models/funding_info' 23 | require_relative './models/funding_loan' 24 | require_relative './models/funding_offer' 25 | require_relative './models/funding_ticker' 26 | require_relative './models/funding_trade' 27 | require_relative './models/ledger_entry' 28 | require_relative './models/margin_info' 29 | require_relative './models/model' 30 | require_relative './models/movement' 31 | require_relative './models/notification' 32 | require_relative './models/order_book' 33 | require_relative './models/order' 34 | require_relative './models/position' 35 | require_relative './models/public_trade' 36 | require_relative './models/trade' 37 | require_relative './models/trading_ticker' 38 | require_relative './models/user_info' 39 | require_relative './models/wallet' 40 | require_relative './models/pulse_profile' 41 | require_relative './models/pulse' 42 | -------------------------------------------------------------------------------- /lib/errors.rb: -------------------------------------------------------------------------------- 1 | require 'faraday' 2 | 3 | module Bitfinex 4 | class ClientError < Exception; end 5 | class ParamsError < ClientError; end 6 | class InvalidAuthKeyError < ClientError; end 7 | class BlockMissingError < ParamsError; end 8 | class ServerError < Exception; end # Error reported back by Binfinex server 9 | class ConnectionClosed < Exception; end 10 | class BadRequestError < ServerError; end 11 | class NotFoundError < ServerError; end 12 | class ForbiddenError < ServerError; end 13 | class UnauthorizedError < ServerError; end 14 | class InternalServerError < ServerError; end 15 | class WebsocketError < ServerError; end 16 | 17 | class CustomErrors < Faraday::Response::Middleware 18 | def on_complete(env) 19 | case env[:status] 20 | when 400 21 | raise BadRequestError, env.body['message'] 22 | when 401 23 | raise UnauthorizedError, env.body['message'] 24 | when 403 25 | raise ForbiddenError, env.body['message'] 26 | when 404 27 | raise NotFoundError, env.url 28 | when 500 29 | raise InternalServerError, env.body 30 | else 31 | super 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/faraday/adapter/net_http_socks.rb: -------------------------------------------------------------------------------- 1 | class NetHttpSocks < Faraday::Adapter::NetHttp 2 | SOCKS_SCHEMES = ['socks', 'socks4', 'socks5'] 3 | 4 | def net_http_connection(env) 5 | proxy = env[:request][:proxy] 6 | 7 | net_http_class = if proxy 8 | if SOCKS_SCHEMES.include?(proxy[:uri].scheme) 9 | Net::HTTP::SOCKSProxy(proxy[:uri].host, proxy[:uri].port) 10 | else 11 | Net::HTTP::Proxy(proxy[:uri].host, proxy[:uri].port, proxy[:user], proxy[:password]) 12 | end 13 | else 14 | Net::HTTP 15 | end 16 | 17 | net_http_class.new(env[:url].host, env[:url].port) 18 | end 19 | end 20 | 21 | Faraday::Adapter.register_middleware(net_http_socks: NetHttpSocks) 22 | -------------------------------------------------------------------------------- /lib/models/alert.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class Alert < Model 6 | BOOL_FIELDS = [] 7 | FIELDS = { 8 | :key => 0, 9 | :type => 1, 10 | :symbol => 2, 11 | :price => 3 12 | } 13 | 14 | FIELDS.each do |key, index| 15 | attr_accessor key 16 | end 17 | 18 | def initialize (data) 19 | super(data, FIELDS, BOOL_FIELDS) 20 | end 21 | 22 | def self.unserialize (data) 23 | return Model.unserialize(data, FIELDS, BOOL_FIELDS) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/models/balance_info.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class BalanceInfo < Model 6 | BOOL_FIELDS = [] 7 | FIELDS = { 8 | :amount => 0, 9 | :amount_net => 1 10 | } 11 | 12 | FIELDS.each do |key, index| 13 | attr_accessor key 14 | end 15 | 16 | def initialize (data) 17 | super(data, FIELDS, BOOL_FIELDS) 18 | end 19 | 20 | def self.unserialize (data) 21 | return Model.unserialize(data, FIELDS, BOOL_FIELDS) 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/models/candle.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class Candle < Model 6 | BOOL_FIELDS = [] 7 | FIELDS = { 8 | :mts => 0, 9 | :open => 1, 10 | :close => 2, 11 | :high => 3, 12 | :low => 4, 13 | :volume => 5 14 | } 15 | 16 | FIELDS.each do |key, index| 17 | attr_accessor key 18 | end 19 | 20 | def initialize (data) 21 | super(data, FIELDS, BOOL_FIELDS) 22 | end 23 | 24 | def self.unserialize (data) 25 | return Model.unserialize(data, FIELDS, BOOL_FIELDS) 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/models/currency.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class Currency < Model 6 | BOOL_FIELDS = [] 7 | FIELDS = { 8 | :id => 0, 9 | :name => 1, 10 | :pool => 2, 11 | :explorer => 3 12 | } 13 | 14 | FIELDS.each do |key, index| 15 | attr_accessor key 16 | end 17 | 18 | def initialize (data) 19 | super(data, FIELDS, BOOL_FIELDS) 20 | end 21 | 22 | def self.unserialize (data) 23 | return Model.unserialize(data, FIELDS, BOOL_FIELDS) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/models/funding_credit.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class FundingCredit < Model 6 | BOOL_FIELDS = ['notify', 'hidden', 'renew', 'no_close'] 7 | FIELDS = { 8 | :id => 0, 9 | :symbol => 1, 10 | :side => 2, 11 | :mts_create => 3, 12 | :mts_update => 4, 13 | :amount => 5, 14 | :flags => 6, 15 | :status => 7, 16 | :rate => 11, 17 | :period => 12, 18 | :mts_opening => 13, 19 | :mts_last_payout => 14, 20 | :notify => 15, 21 | :hidden => 16, 22 | :renew => 18, 23 | :rate_real => 19, 24 | :no_close => 20, 25 | :position_pair => 21 26 | } 27 | 28 | FIELDS.each do |key, index| 29 | attr_accessor key 30 | end 31 | 32 | def initialize (data) 33 | super(data, FIELDS, BOOL_FIELDS) 34 | end 35 | 36 | def self.unserialize (data) 37 | return Model.unserialize(data, FIELDS, BOOL_FIELDS) 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/models/funding_info.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class FundingInfo < Model 6 | attr_accessor :symbol, :yield_loan, :yield_lend 7 | attr_accessor :duration_loan, :duration_lend 8 | 9 | def initialize (data) 10 | super(data, {}, []) 11 | end 12 | 13 | def serialize () 14 | [ 15 | 'sym', 16 | self.symbol, 17 | [ 18 | self.yield_loan, 19 | self.yield_lend, 20 | self.duration_loan, 21 | self.duration_lend 22 | ] 23 | ] 24 | end 25 | 26 | def self.unserialize (arr) 27 | symbol = arr[1] 28 | data = arr[2] 29 | 30 | { 31 | :symbol => symbol, 32 | :yield_loan => data[0], 33 | :yield_lend => data[1], 34 | :duration_loan => data[2], 35 | :duration_lend => data[3] 36 | } 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/models/funding_loan.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class FundingLoan < Model 6 | BOOL_FIELDS = ['notify', 'hidden', 'renew', 'no_close'] 7 | FIELDS = { 8 | :id => 0, 9 | :symbol => 1, 10 | :side => 2, 11 | :mts_create => 3, 12 | :mts_update => 4, 13 | :amount => 5, 14 | :flags => 6, 15 | :status => 7, 16 | :rate => 11, 17 | :period => 12, 18 | :mts_opening => 13, 19 | :mts_last_payout => 14, 20 | :notify => 15, 21 | :hidden => 16, 22 | :renew => 18, 23 | :rate_real => 19, 24 | :no_close => 20 25 | } 26 | 27 | FIELDS.each do |key, index| 28 | attr_accessor key 29 | end 30 | 31 | def initialize (data) 32 | super(data, FIELDS, BOOL_FIELDS) 33 | end 34 | 35 | def self.unserialize (data) 36 | return Model.unserialize(data, FIELDS, BOOL_FIELDS) 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/models/funding_offer.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class FundingOffer < Model 6 | BOOL_FIELDS = ['notify', 'hidden', 'renew'] 7 | FIELDS = { 8 | :id => 0, 9 | :symbol => 1, 10 | :mts_create => 2, 11 | :mts_update => 3, 12 | :amount => 4, 13 | :amount_orig => 5, 14 | :type => 6, 15 | :flags => 9, 16 | :status => 10, 17 | :rate => 14, 18 | :period => 15, 19 | :notify => 16, 20 | :hidden => 17, 21 | :renew => 19, 22 | :rate_real => 20 23 | } 24 | 25 | FIELDS.each do |key, index| 26 | attr_accessor key 27 | end 28 | 29 | def initialize (data) 30 | super(data, FIELDS, BOOL_FIELDS) 31 | end 32 | 33 | def self.unserialize (data) 34 | return Model.unserialize(data, FIELDS, BOOL_FIELDS) 35 | end 36 | 37 | def to_new_order_packet 38 | data = { 39 | :type => @type, 40 | :symbol => @symbol, 41 | :amount => BigDecimal(@amount, 8).to_s, 42 | :rate => BigDecimal(@rate, 8).to_s, 43 | :period => 2 44 | } 45 | if !@flags.nil? 46 | data[:flags] = @flags 47 | end 48 | data 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/models/funding_ticker.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class FundingTicker < Model 6 | BOOL_FIELDS = [] 7 | FIELDS = { 8 | symbol: 0, 9 | frr: 1, 10 | bid: 2, 11 | bid_size: 3, 12 | bid_period: 4, 13 | ask: 5, 14 | ask_size: 6, 15 | ask_period: 7, 16 | daily_change: 8, 17 | daily_change_perc: 9, 18 | last_price: 10, 19 | volume: 11, 20 | high: 12, 21 | low: 13 22 | } 23 | 24 | FIELDS.each do |key, index| 25 | attr_accessor key 26 | end 27 | 28 | def initialize (data) 29 | super(data, FIELDS, BOOL_FIELDS) 30 | end 31 | 32 | def self.unserialize (data) 33 | return Model.unserialize(data, FIELDS, BOOL_FIELDS) 34 | end 35 | 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/models/funding_trade.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class FundingTrade < Model 6 | BOOL_FIELDS = [] 7 | FIELDS = { 8 | :id => 0, 9 | :symbol => 1, 10 | :mts_create => 2, 11 | :offer_id => 3, 12 | :amount => 4, 13 | :rate => 5, 14 | :period => 6, 15 | :maker => 7 16 | } 17 | 18 | FIELDS.each do |key, index| 19 | attr_accessor key 20 | end 21 | 22 | def initialize (data) 23 | super(data, FIELDS, BOOL_FIELDS) 24 | end 25 | 26 | def self.unserialize (data) 27 | return Model.unserialize(data, FIELDS, BOOL_FIELDS) 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/models/ledger_entry.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class LedgerEntry < Model 6 | BOOL_FIELDS = [] 7 | FIELDS = { 8 | :id => 0, 9 | :currency => 1, 10 | :mts => 3, 11 | :amount => 5, 12 | :balance => 6, 13 | :description => 8, 14 | :wallet => nil 15 | } 16 | 17 | FIELDS.each do |key, index| 18 | attr_accessor key 19 | end 20 | 21 | def initialize (data) 22 | super(data, FIELDS, BOOL_FIELDS) 23 | 24 | spl = self.description.split('wallet') 25 | self.wallet = (spl && spl[1]) ? spl[1].trim() : nil 26 | end 27 | 28 | def self.unserialize (data) 29 | return Model.unserialize(data, FIELDS, BOOL_FIELDS) 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/models/margin_info.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class MarginInfo < Model 6 | attr_accessor :user_pl, :user_swaps, :margin_balance, :margin_net, :type 7 | attr_accessor :symbol, :tradable_balance, :gross_balance, :buy, :sell 8 | 9 | def initialize (data) 10 | super(data, {}, []) 11 | end 12 | 13 | def serialize () 14 | if self.type == 'base' 15 | return [ 16 | self.type, 17 | [ 18 | self.user_pl, 19 | self.user_swaps, 20 | self.margin_balance, 21 | self.margin_net 22 | ] 23 | ] 24 | else 25 | return [ 26 | self.type, 27 | self.symbol, 28 | [ 29 | self.tradable_balance, 30 | self.gross_balance, 31 | self.buy, 32 | self.sell 33 | ] 34 | ] 35 | end 36 | end 37 | 38 | def self.unserialize (arr) 39 | type = arr[0] 40 | 41 | if type == 'base' 42 | payload = arr[1] 43 | 44 | return { 45 | :type => type, 46 | :user_pl => payload[0], 47 | :user_swaps => payload[1], 48 | :margin_balance => payload[2], 49 | :margin_net => payload[3] 50 | } 51 | else 52 | symbol = arr[1] 53 | payload = arr[2] 54 | 55 | return { 56 | :type => type, 57 | :symbol => symbol, 58 | :tradable_balance => payload[0], 59 | :gross_balance => payload[1], 60 | :buy => payload[2], 61 | :sell => payload[3] 62 | } 63 | end 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/models/model.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module Models 3 | class Model 4 | def initialize (data, fields, boolFields) 5 | @fields = fields 6 | @boolFields = boolFields 7 | 8 | if data.kind_of?(Array) 9 | apply(self.class.unserialize(data)) 10 | elsif data.kind_of?(Hash) 11 | apply(data) 12 | end 13 | end 14 | 15 | def serialize 16 | arr = [] 17 | 18 | @fields.each do |key, index| 19 | return if index.nil? 20 | 21 | if @boolFields.include?(key) 22 | arr[index] = instance_variable_get("@#{key}") ? 1 : 0 23 | else 24 | arr[index] = instance_variable_get("@#{key}") 25 | end 26 | end 27 | 28 | arr 29 | end 30 | 31 | def apply (obj) 32 | @fields.each do |key, index| 33 | return if index.nil? 34 | 35 | instance_variable_set("@#{key}", obj[key]) 36 | end 37 | end 38 | 39 | def self.unserialize (data, fields, boolFields) 40 | obj = {} 41 | 42 | fields.each do |key, index| 43 | return if index.nil? 44 | 45 | if boolFields.include?(key) 46 | obj[key] = data[index] == 1 47 | else 48 | obj[key] = data[index] 49 | end 50 | end 51 | 52 | return obj 53 | end 54 | end 55 | end 56 | end -------------------------------------------------------------------------------- /lib/models/movement.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class Movement < Model 6 | BOOL_FIELDS = [] 7 | FIELDS = { 8 | :id => 0, 9 | :currency => 1, 10 | :currency_name => 2, 11 | :mts_started => 5, 12 | :mts_updated => 6, 13 | :status => 9, 14 | :amount => 12, 15 | :fees => 13, 16 | :destination_address => 16, 17 | :transaction_id => 20 18 | } 19 | 20 | FIELDS.each do |key, index| 21 | attr_accessor key 22 | end 23 | 24 | def initialize (data) 25 | super(data, FIELDS, BOOL_FIELDS) 26 | end 27 | 28 | def self.unserialize (data) 29 | return Model.unserialize(data, FIELDS, BOOL_FIELDS) 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/models/notification.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class Notification < Model 6 | BOOL_FIELDS = [] 7 | FIELDS = { 8 | :mts => 0, 9 | :type => 1, 10 | :message_id => 2, 11 | :notify_info => 4, 12 | :code => 5, 13 | :status => 6, 14 | :text => 7 15 | } 16 | 17 | FIELDS.each do |key, index| 18 | attr_accessor key 19 | end 20 | 21 | def initialize (data) 22 | super(data, FIELDS, BOOL_FIELDS) 23 | end 24 | 25 | def self.unserialize (data) 26 | return Model.unserialize(data, FIELDS, BOOL_FIELDS) 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/models/order.rb: -------------------------------------------------------------------------------- 1 | require 'bigdecimal' 2 | require_relative './model' 3 | 4 | module Bitfinex 5 | module Models 6 | class Order < Model 7 | BOOL_FIELDS = [] 8 | FIELDS = { 9 | :id => 0, 10 | :gid => 1, 11 | :cid => 2, 12 | :symbol => 3, 13 | :mts_create => 4, 14 | :mts_update => 5, 15 | :amount => 6, 16 | :amount_orig => 7, 17 | :type => 8, 18 | :type_prev => 9, 19 | :mts_tif => 10, 20 | # placeholder 21 | :flags => 12, 22 | :status => 13, 23 | # placeholder 24 | # placeholder 25 | :price => 16, 26 | :price_avg => 17, 27 | :price_trailing => 18, 28 | :price_aux_limit => 19, 29 | # placeholder 30 | # placeholder 31 | # placeholder 32 | :notify => 23, 33 | :hidden => 24, 34 | :placed_id => 25, 35 | # placeholder 36 | # placeholder 37 | :routing => 28, 38 | # placeholder 39 | # placeholder 40 | :meta => 31 41 | } 42 | 43 | FLAG_OCO = 2 ** 14 # 16384 44 | FLAG_POSTONLY = 2 ** 12 # 4096 45 | FLAG_HIDDEN = 2 ** 6 # 64 46 | FLAG_NO_VR = 2 ** 19 # 524288 47 | FLAG_POS_CLOSE = 2 ** 9 # 512 48 | FLAG_REDUCE_ONLY = 2 ** 10 # 1024 49 | 50 | @@last_cid = Time.now.to_i 51 | 52 | FIELDS.each do |key, index| 53 | attr_accessor key 54 | end 55 | 56 | attr_accessor :last_amount, :lev 57 | 58 | def self.gen_cid 59 | @@last_cid += 1 60 | @@last_cid 61 | end 62 | 63 | def initialize (data) 64 | super(data, FIELDS, BOOL_FIELDS) 65 | 66 | @flags = 0 unless @flags.is_a?(Numeric) 67 | @amount_orig = @amount if @amount_orig.nil? && !@amount.nil? 68 | @last_amount = @amount 69 | 70 | if data.kind_of?(Hash) 71 | set_oco(data[:oco]) if data.has_key?(:oco) 72 | set_hidden(data[:hidden]) if data.has_key?(:hidden) 73 | set_post_only(data[:post_only]) if data.has_key?(:post_only) 74 | if data.has_key?(:lev) 75 | @lev = data[:lev] 76 | end 77 | end 78 | end 79 | 80 | def self.unserialize (data) 81 | return Model.unserialize(data, FIELDS, BOOL_FIELDS) 82 | end 83 | 84 | def modify_flag (flag, active) 85 | return if (@flags & flag != 0) == active 86 | 87 | @flags += active ? flag : -flag 88 | end 89 | 90 | def set_oco (v, stop_price = @price_aux_limit) 91 | @price_aux_limit = stop_price if v 92 | 93 | modify_flag(FLAG_OCO, v) 94 | end 95 | 96 | def set_hidden (v) 97 | modify_flag(FLAG_HIDDEN, v) 98 | end 99 | 100 | def set_post_only (v) 101 | modify_flag(FLAG_POSTONLY, v) 102 | end 103 | 104 | def set_no_variable_rates (v) 105 | modify_flag(FLAG_NO_VR, v) 106 | end 107 | 108 | def set_position_close (v) 109 | modify_flag(FLAG_POS_CLOSE, v) 110 | end 111 | 112 | def set_reduce_only (v) 113 | modify_flag(FLAG_REDUCE_ONLY, v) 114 | end 115 | 116 | def is_oco 117 | return !!(@flags & FLAG_OCO) 118 | end 119 | 120 | def is_hidden 121 | return !!(@flags & FLAG_HIDDEN) 122 | end 123 | 124 | def is_post_only 125 | return !!(@flags & FLAG_POSTONLY) 126 | end 127 | 128 | def includes_variable_rates 129 | return !!(@flags & FLAG_NO_VR) 130 | end 131 | 132 | def is_position_close 133 | return !!(@flags & FLAG_POS_CLOSE) 134 | end 135 | 136 | def is_reduce_only 137 | return !!(@flags & FLAG_REDUCE_ONLY) 138 | end 139 | 140 | def get_last_fill_amount 141 | @last_amount - @amount 142 | end 143 | 144 | def reset_fill_amount 145 | @last_amount = @amount 146 | end 147 | 148 | def get_base_currency 149 | @symbol[1...4] 150 | end 151 | 152 | def get_quote_currency 153 | @symbol[4..-1] 154 | end 155 | 156 | def get_notional_value 157 | (@amount * @price).abs 158 | end 159 | 160 | def is_partially_filled 161 | a = @amount.abs 162 | a > 0 && a < @amount_orig.abs 163 | end 164 | 165 | def to_new_order_packet 166 | if !@cid.nil? 167 | cid = @cid 168 | else 169 | cid = Order.gen_cid 170 | end 171 | 172 | data = { 173 | :cid => cid, 174 | :symbol => @symbol, 175 | :type => @type, 176 | :amount => BigDecimal(@amount, 8).to_s, 177 | :flags => @flags || 0, 178 | :meta => @meta 179 | } 180 | if !@gid.nil? 181 | data[:gid] = @gid 182 | end 183 | if !@lev.nil? 184 | data[:lev] = @lev 185 | end 186 | 187 | data[:price] = BigDecimal(@price, 5).to_s if !@price.nil? 188 | data[:price_trailing] = BigDecimal(@price_trailing, 5).to_s if !@price_trailing.nil? 189 | 190 | if !@price_aux_limit.nil? 191 | if is_oco 192 | data[:price_oco_stop] = BigDecimal(@price_aux_limit, 5).to_s 193 | else 194 | data[:price_aux_limit] = BigDecimal(@price_aux_limit, 5).to_s 195 | end 196 | end 197 | 198 | data 199 | end 200 | 201 | def update (changes = {}) 202 | changes.each do |k, v| 203 | return if k == 'id' 204 | 205 | if FIELDS.has_key?(k) 206 | instance_variable_set(k, v) 207 | elsif k == 'price_trailing' 208 | @price_trailing = v.to_f 209 | elsif k == 'price_oco_stop' || k == 'price_aux_limit' 210 | @price_aux_limit = v.to_f 211 | elsif k == 'delta' && v.is_a?(Numeric) && @amount.is_a?(Numeric) 212 | @amount += v.to_f 213 | @last_amount = @amount 214 | end 215 | end 216 | end 217 | end 218 | end 219 | end 220 | -------------------------------------------------------------------------------- /lib/models/order_book.rb: -------------------------------------------------------------------------------- 1 | require 'zlib' 2 | 3 | module Bitfinex 4 | module Models 5 | class OrderBook 6 | attr_reader :raw, :bids, :asks 7 | 8 | def initialize (snap = [], raw = false) 9 | @raw = raw 10 | 11 | if snap.instance_of?(OrderBook) 12 | @bids = snap.bids.dup 13 | @asks = snap.asks.dup 14 | elsif snap.kind_of?(Array) 15 | update_from_snapshot(snap) 16 | elsif snap.kind_of?(Hash) 17 | @bids = snap[:bids].dup 18 | @asks = snap[:asks].dup 19 | else 20 | @bids = [] 21 | @asks = [] 22 | end 23 | end 24 | 25 | def update_from_snapshot (snap = []) 26 | @bids = [] 27 | @asks = [] 28 | 29 | snap = [snap] if !snap[0].kind_of?(Array) 30 | 31 | snap.each do |entry| 32 | if entry.size == 4 33 | if entry[3] < 0 34 | @bids.push(entry) 35 | else 36 | @asks.push(entry) 37 | end 38 | else 39 | if entry[2] < 0 40 | @asks.push(entry) 41 | else 42 | @bids.push(entry) 43 | end 44 | end 45 | end 46 | 47 | if self.raw 48 | priceI = snap[0].size == 4 ? 2 : 1 49 | else 50 | priceI = 0 51 | end 52 | 53 | @bids.sort! { |a, b| b[priceI] <=> a[priceI]} 54 | @asks.sort! { |a, b| a[priceI] <=> b[priceI]} 55 | end 56 | 57 | def top_bid_level 58 | @bids[0] || nil 59 | end 60 | 61 | def top_bid 62 | if self.raw 63 | priceI = (@bids[0].size == 4 || @asks[0].size == 4) ? 2 : 1 64 | else 65 | priceI = 0 66 | end 67 | 68 | (top_bid_level || [])[priceI] || nil 69 | end 70 | 71 | def top_ask_level 72 | @asks[0] || nil 73 | end 74 | 75 | def top_ask 76 | if self.raw 77 | priceI = (@bids[0].size == 4 || @asks[0].size == 4) ? 2 : 1 78 | else 79 | priceI = 0 80 | end 81 | 82 | (top_ask_level || [])[priceI] || nil 83 | end 84 | 85 | def mid_price 86 | ask = top_ask || 0 87 | bid = top_bid || 0 88 | 89 | return bid if ask == 0 90 | return ask if bid == 0 91 | 92 | return (bid + ask) / 2 93 | end 94 | 95 | def spread 96 | ask = top_ask || 0 97 | bid = top_bid || 0 98 | 99 | return 0 if ask == 0 || bid == 0 100 | 101 | return ask - bid 102 | end 103 | 104 | def bid_amount 105 | amount = 0 106 | 107 | @bids.each do |entry| 108 | amount += entry.size == 4 ? entry[3] : entry[2] 109 | end 110 | 111 | amount.abs 112 | end 113 | 114 | def ask_amount 115 | amount = 0 116 | 117 | @asks.each do |entry| 118 | amount += entry.size == 4 ? entry[3] : entry[2] 119 | end 120 | 121 | amount.abs 122 | end 123 | 124 | def serialize 125 | @asks + @bids 126 | end 127 | 128 | def checksum 129 | data = [] 130 | 131 | for i in 0...25 132 | bid = @bids[i] 133 | ask = @asks[i] 134 | 135 | if !bid.nil? 136 | price = bid[0] 137 | data.push(price) 138 | data.push(bid.size == 4 ? bid[3] : bid[2]) 139 | end 140 | 141 | if !ask.nil? 142 | price = ask[0] 143 | data.push(price) 144 | data.push(ask.size == 4 ? ask[3] : ask[2]) 145 | end 146 | end 147 | 148 | [Zlib::crc32(data.join(':'))].pack("I").unpack("i")[0] 149 | end 150 | 151 | def update_with (entry) 152 | if @raw 153 | priceI = entry.size == 4 ? 2 : 1 154 | count = -1 155 | else 156 | priceI = 0 157 | count = entry.size == 4 ? entry[2] : entry[1] 158 | end 159 | 160 | price = entry[priceI] 161 | oID = entry[0] # only for raw books 162 | amount = entry.size == 4 ? entry[3] : entry[2] 163 | 164 | if entry.size == 4 165 | dir = amount < 0 ? 1 : -1 166 | side = amount < 0 ? @bids : @asks 167 | else 168 | dir = amount < 0 ? -1 : 1 169 | side = amount < 0 ? @asks : @bids 170 | end 171 | 172 | insertIndex = -1 173 | 174 | # apply insert directly if empty 175 | if (side.size == 0 && (@raw || count > 0)) 176 | side.push(entry) 177 | return true 178 | end 179 | 180 | # match by price level, or order ID for raw books 181 | side.each_with_index do |pl, i| 182 | if (!@raw && pl[priceI] == price) || (@raw && pl[0] == oID) 183 | if (!@raw && count == 0) || (@raw && price == 0) 184 | side.slice!(i, 1) 185 | return true 186 | elsif !@raw || (@raw && price > 0) 187 | side.slice!(i, 1) 188 | break 189 | end 190 | end 191 | end 192 | 193 | if (@raw && price == 0) || (!@raw && count == 0) 194 | return false 195 | end 196 | 197 | side.each_with_index do |pl, i| 198 | if (insertIndex == -1 && ( 199 | (dir == -1 && price < pl[priceI]) || # by price 200 | (dir == -1 && price == pl[priceI] && (raw && entry[0] < pl[0])) || # by order ID 201 | (dir == 1 && price > pl[priceI]) || 202 | (dir == 1 && price == pl[priceI] && (raw && entry[0] < pl[0])) 203 | )) 204 | insertIndex = i 205 | break 206 | end 207 | end 208 | 209 | # add 210 | if (insertIndex == -1) 211 | side.push(entry) 212 | else 213 | side.insert(insertIndex, entry) 214 | end 215 | 216 | return true 217 | end 218 | 219 | def self.unserialize (arr, raw = false) 220 | if arr[0].kind_of?(Array) 221 | entries = arr.map { |e| OrderBook.unserialize(e, raw) } 222 | bids = entries.select { |e| (e[:rate] ? -e[:amount] : e[:amount]) > 0 } 223 | asks = entries.select { |e| (e[:rate] ? -e[:amount] : e[:amount]) < 0 } 224 | 225 | return { 226 | :bids => bids, 227 | :asks => asks 228 | } 229 | end 230 | 231 | if arr.size == 4 232 | if raw 233 | return { 234 | :order_id => arr[0], 235 | :period => arr[1], 236 | :rate => arr[2], 237 | :amount => arr[3] 238 | } 239 | else 240 | return { 241 | :rate => arr[0], 242 | :period => arr[1], 243 | :count => arr[2], 244 | :amount => arr[3] 245 | } 246 | end 247 | else 248 | if raw 249 | return { 250 | :order_id => arr[0], 251 | :price => arr[1], 252 | :amount => arr[2] 253 | } 254 | else 255 | return { 256 | :price => arr[0], 257 | :count => arr[1], 258 | :amount => arr[2] 259 | } 260 | end 261 | end 262 | end 263 | end 264 | end 265 | end -------------------------------------------------------------------------------- /lib/models/position.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class Position < Model 6 | BOOL_FIELDS = [] 7 | FIELDS = { 8 | :symbol => 0, 9 | :status => 1, 10 | :amount => 2, 11 | :base_price => 3, 12 | :margin_funding => 4, 13 | :margin_funding_type => 5, 14 | :pl => 6, 15 | :pl_perc => 7, 16 | :liquidation_price => 8, 17 | :leverage => 9, 18 | # placeholder 19 | :id => 11, 20 | :mts_create => 12, 21 | :mts_update => 13, 22 | # placeholder 23 | :type => 15, 24 | # placeholder 25 | :collateral => 17, 26 | :callateral_min => 18, 27 | :meta => 19 28 | } 29 | 30 | FIELDS.each do |key, index| 31 | attr_accessor key 32 | end 33 | 34 | def initialize (data) 35 | super(data, FIELDS, BOOL_FIELDS) 36 | end 37 | 38 | def self.unserialize (data) 39 | return Model.unserialize(data, FIELDS, BOOL_FIELDS) 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/models/public_trade.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class PublicTrade < Model 6 | BOOL_FIELDS = [] 7 | FIELDS = { 8 | :id => 0, 9 | :mts => 1, 10 | :amount => 2, 11 | :price => 3 12 | } 13 | 14 | FIELDS.each do |key, index| 15 | attr_accessor key 16 | end 17 | 18 | def initialize (data) 19 | super(data, FIELDS, BOOL_FIELDS) 20 | end 21 | 22 | def self.unserialize (data) 23 | return Model.unserialize(data, FIELDS, BOOL_FIELDS) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/models/pulse.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class Pulse < Model 6 | BOOL_FIELDS = [] 7 | FIELDS = { 8 | :id => 0, 9 | :mts_create => 1, 10 | :pulse_user_id => 3, 11 | :title => 5, 12 | :content => 6, 13 | :is_pin => 9, 14 | :is_public => 10, 15 | :comments_disabled => 11, 16 | :tags => 12, 17 | :attachments => 13, 18 | :meta => 14, 19 | :likes => 15, 20 | :profile => 18, 21 | :comments => 19 22 | } 23 | 24 | FIELDS.each do |key, index| 25 | attr_accessor key 26 | end 27 | 28 | def initialize (data) 29 | super(data, FIELDS, BOOL_FIELDS) 30 | end 31 | 32 | def self.unserialize (data) 33 | Model.unserialize(data, FIELDS, BOOL_FIELDS) 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/models/pulse_profile.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class PulseProfile < Model 6 | BOOL_FIELDS = [] 7 | FIELDS = { 8 | :id => 0, 9 | :mts_create => 1, 10 | :nickname => 3, 11 | :picture => 5, 12 | :text => 6, 13 | :twitter_handle => 9, 14 | :followers => 11, 15 | :following => 12 16 | } 17 | 18 | FIELDS.each do |key, index| 19 | attr_accessor key 20 | end 21 | 22 | def initialize (data) 23 | super(data, FIELDS, BOOL_FIELDS) 24 | end 25 | 26 | def self.unserialize (data) 27 | return Model.unserialize(data, FIELDS, BOOL_FIELDS) 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/models/trade.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class Trade < Model 6 | BOOL_FIELDS = [] 7 | FIELDS = { 8 | id: 0, 9 | symbol: 1, 10 | mts_create: 2, 11 | order_id: 3, 12 | exec_amount: 4, 13 | exec_price: 5, 14 | order_type: 6, 15 | order_price: 7, 16 | maker: 8, 17 | fee: 9, 18 | fee_currency: 10 19 | } 20 | 21 | FIELDS.each do |key, index| 22 | attr_accessor key 23 | end 24 | 25 | def initialize (data) 26 | super(data, FIELDS, BOOL_FIELDS) 27 | end 28 | 29 | def self.unserialize (data) 30 | return Model.unserialize(data, FIELDS, BOOL_FIELDS) 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/models/trading_ticker.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class TradingTicker < Model 6 | BOOL_FIELDS = [] 7 | FIELDS = { 8 | symbol: 0, 9 | bid: 1, 10 | bid_size: 2, 11 | ask: 3, 12 | ask_size: 4, 13 | daily_change: 5, 14 | daily_change_perc: 6, 15 | last_price: 7, 16 | volume: 8, 17 | high: 9, 18 | low: 10 19 | } 20 | 21 | FIELDS.each do |key, index| 22 | attr_accessor key 23 | end 24 | 25 | def initialize (data) 26 | super(data, FIELDS, BOOL_FIELDS) 27 | end 28 | 29 | def self.unserialize (data) 30 | return Model.unserialize(data, FIELDS, BOOL_FIELDS) 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/models/user_info.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class UserInfo < Model 6 | BOOL_FIELDS = [] 7 | FIELDS = { 8 | :id => 0, 9 | :email => 1, 10 | :username => 2, 11 | :timezone => 7 12 | } 13 | 14 | FIELDS.each do |key, index| 15 | attr_accessor key 16 | end 17 | 18 | def initialize (data) 19 | super(data, FIELDS, BOOL_FIELDS) 20 | end 21 | 22 | def self.unserialize (data) 23 | return Model.unserialize(data, FIELDS, BOOL_FIELDS) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/models/wallet.rb: -------------------------------------------------------------------------------- 1 | require_relative './model' 2 | 3 | module Bitfinex 4 | module Models 5 | class Wallet < Model 6 | BOOL_FIELDS = [] 7 | FIELDS = { 8 | :type => 0, 9 | :currency => 1, 10 | :balance => 2, 11 | :unsettled_interest => 3, 12 | :balance_available => 4 13 | } 14 | 15 | FIELDS.each do |key, index| 16 | attr_accessor key 17 | end 18 | 19 | def initialize (data) 20 | super(data, FIELDS, BOOL_FIELDS) 21 | end 22 | 23 | def self.unserialize (data) 24 | return Model.unserialize(data, FIELDS, BOOL_FIELDS) 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/rest/rest_client.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTClient 3 | def check_params(params, allowed_params) 4 | if (params.keys - allowed_params).empty? 5 | return params 6 | else 7 | raise Bitfinex::ParamsError 8 | end 9 | end 10 | 11 | private 12 | def get(url, params={}) 13 | rest_connection.get do |req| 14 | req.url build_url(url) 15 | req.headers['Content-Type'] = 'application/json' 16 | req.headers['Accept'] = 'application/json' 17 | 18 | params.each do |k,v| 19 | req.params[k] = v 20 | end 21 | 22 | req.options.timeout = config[:rest_timeout] 23 | req.options.open_timeout = config[:rest_open_timeout] 24 | end 25 | end 26 | 27 | def rest_connection 28 | @conn ||= new_rest_connection 29 | end 30 | 31 | def build_url(url) 32 | URI.join(base_api_endpoint, url) 33 | end 34 | 35 | def new_rest_connection 36 | Faraday.new(url: base_api_endpoint, :proxy => config[:proxy]) do |conn| 37 | conn.use Bitfinex::CustomErrors 38 | conn.response :logger, Logger.new(STDOUT), bodies: true if config[:debug_connection] 39 | conn.use FaradayMiddleware::ParseJson, :content_type => /\bjson$/ 40 | conn.adapter :net_http_socks 41 | end 42 | end 43 | 44 | def base_api_endpoint 45 | config[:api_endpoint] 46 | end 47 | 48 | private 49 | def authenticated_post(url, options = {}) 50 | raise Bitfinex::InvalidAuthKeyError unless valid_key? 51 | complete_url = build_url(url) 52 | body = options[:params] || {} 53 | nonce = new_nonce 54 | 55 | payload = if config[:api_version] == 1 56 | build_payload("/v1/#{url}", options[:params], nonce) 57 | else 58 | "/api/v2/#{url}#{nonce}#{body.to_json}" 59 | end 60 | 61 | response = rest_connection.post do |req| 62 | req.url complete_url 63 | req.body = body.to_json 64 | req.options.timeout = config[:rest_timeout] 65 | req.options.open_timeout = config[:rest_open_timeout] 66 | req.headers['Content-Type'] = 'application/json' 67 | req.headers['Accept'] = 'application/json' 68 | 69 | if config[:api_version] == 1 70 | req.headers['X-BFX-PAYLOAD'] = payload 71 | req.headers['X-BFX-SIGNATURE'] = sign(payload) 72 | req.headers['X-BFX-APIKEY'] = config[:api_key] 73 | else 74 | req.headers['bfx-nonce'] = nonce 75 | req.headers['bfx-signature'] = sign(payload) 76 | req.headers['bfx-apikey'] = config[:api_key] 77 | end 78 | end 79 | end 80 | 81 | def build_payload(url, params = {}, nonce) 82 | payload = {} 83 | payload['nonce'] = nonce 84 | payload['request'] = url 85 | payload.merge!(params) if params 86 | Base64.strict_encode64(payload.to_json) 87 | end 88 | 89 | def new_nonce 90 | (Time.now.to_f * 1000000).floor.to_s 91 | end 92 | 93 | def sign(payload) 94 | OpenSSL::HMAC.hexdigest('sha384', config[:api_secret], payload) 95 | end 96 | 97 | def valid_key? 98 | !! (config[:api_key] && config[:api_secret]) 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/rest/v1.rb: -------------------------------------------------------------------------------- 1 | require_relative './rest_client' 2 | require_relative './v1/account_info' 3 | require_relative './v1/deposit' 4 | require_relative './v1/funding_book' 5 | require_relative './v1/historical_data' 6 | require_relative './v1/lends' 7 | require_relative './v1/margin_funding' 8 | require_relative './v1/order_book' 9 | require_relative './v1/orders' 10 | require_relative './v1/positions' 11 | require_relative './v1/stats' 12 | require_relative './v1/symbols' 13 | require_relative './v1/ticker' 14 | require_relative './v1/trades' 15 | require_relative './v1/wallet' 16 | 17 | module Bitfinex 18 | class RESTv1 19 | attr_accessor :api_endpoint, :debug, :debug_connection, :api_version 20 | attr_accessor :rest_timeout, :rest_open_timeout, :proxy 21 | attr_accessor :api_key, :api_secret 22 | 23 | include Bitfinex::RESTClient 24 | include Bitfinex::RESTv1AccountInfo 25 | include Bitfinex::RESTv1Deposit 26 | include Bitfinex::RESTv1FundingBook 27 | include Bitfinex::RESTv1HistoricalData 28 | include Bitfinex::RESTv1Lends 29 | include Bitfinex::RESTv1MarginFunding 30 | include Bitfinex::RESTv1OrderBook 31 | include Bitfinex::RESTv1Orders 32 | include Bitfinex::RESTv1Positions 33 | include Bitfinex::RESTv1Stats 34 | include Bitfinex::RESTv1Symbols 35 | include Bitfinex::RESTv1Ticker 36 | include Bitfinex::RESTv1Trades 37 | include Bitfinex::RESTv1Wallet 38 | 39 | def initialize(args = {}) 40 | self.api_endpoint = args[:url] ? "#{args[:url]}/v1/" : "https://api.bitfinex.com/v1/" 41 | self.proxy = args[:proxy] || nil 42 | self.debug_connection = false 43 | self.api_version = 1 44 | self.rest_timeout = 30 45 | self.rest_open_timeout = 30 46 | self.api_key = args[:api_key] 47 | self.api_secret = args[:api_secret] 48 | end 49 | 50 | def config 51 | { 52 | :api_endpoint => self.api_endpoint, 53 | :debug_connection => self.debug_connection, 54 | :api_version => self.api_version, 55 | :rest_timeout => self.rest_timeout, 56 | :rest_open_timeout => self.rest_open_timeout, 57 | :proxy => self.proxy, 58 | :api_key => self.api_key, 59 | :api_secret => self.api_secret 60 | } 61 | end 62 | end 63 | end -------------------------------------------------------------------------------- /lib/rest/v1/account_info.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv1AccountInfo 3 | # Get account information 4 | # 5 | # @return [Hash] your account information 6 | # @example: 7 | # client.account_info 8 | def account_info 9 | resp = authenticated_post("account_infos") 10 | resp.body 11 | end 12 | 13 | # See the fees applied to your withdrawals 14 | # 15 | # @return [Hash] 16 | # @example: 17 | # client.fees 18 | def fees 19 | resp = authenticated_post("account_fees") 20 | resp.body 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/rest/v1/deposit.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv1Deposit 3 | # Return your deposit address to make a new deposit. 4 | # 5 | # @param method [string] Method of deposit (methods accepted: “bitcoin”, “litecoin”, “darkcoin”, “mastercoin” (tethers)). 6 | # @param wallet_name [string] Wallet to deposit in (accepted: “trading”, “exchange”, “deposit”). Your wallet needs to already exist 7 | # @params renew [integer] (optional) Default is 0. If set to 1, will return a new unused deposit address 8 | # 9 | # @return [Hash] confirmation of your deposit 10 | # @example: 11 | # client.deposit("bitcoin", "exchange") 12 | def deposit (method, wallet_name, renew=0) 13 | params = { 14 | method: method, 15 | wallet_name: wallet_name, 16 | renew: renew 17 | } 18 | 19 | authenticated_post("deposit/new", params: params).body 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/rest/v1/funding_book.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv1FundingBook 3 | # Get the full margin funding book 4 | # 5 | # @param currency [string] (optional) Speficy the currency, default "USD" 6 | # @param params :limit_bids [int] (optional) Limit the number of funding bids returned. May be 0 in which case the array of bids is empty. 7 | # @param params :limit_asks [int] (optional) Limit the number of funding offers returned. May be 0 in which case the array of asks is empty. 8 | # @return [Hash] of :bids and :asks arrays 9 | # @example: 10 | # client.funding_book 11 | def funding_book(currency="usd", params = {}) 12 | check_params(params, %i{limit_bids limit_asks}) 13 | get("lendbook/#{currency}", params: params).body 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/rest/v1/historical_data.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv1HistoricalData 3 | 4 | # View all of your balance ledger entries. 5 | # 6 | # @param currency [string] (optional) Specify the currency, default "USD" 7 | # @param params :since [time] (optional) Return only the history after this timestamp. 8 | # @param params :until [time] (optional) Return only the history before this timestamp. 9 | # @param params :limit [int] (optional) Limit the number of entries to return. Default is 500. 10 | # @param params :wallet [string] (optional) Return only entries that took place in this wallet. Accepted inputs are: “trading”, “exchange”, “deposit” 11 | # @return [Array] 12 | # @example: 13 | # client.history 14 | def history(currency="usd", params = {}) 15 | check_params(params, %i{since until limit wallet}) 16 | params.merge!({currency: currency}) 17 | authenticated_post("history", params: params).body 18 | end 19 | 20 | # View your past deposits/withdrawals. 21 | # 22 | # @param currency [string] (optional) Specify the currency, default "USD" 23 | # @param params :method (optional) The method of the deposit/withdrawal (can be “bitcoin”, “litecoin”, “darkcoin”, “wire”) 24 | # @param params :since (optional) Return only the history after this timestamp 25 | # @param params :until [time] (optional) Return only the history before this timestamp. 26 | # @param params :limit [int] (optional) Limit the number of entries to return. Default is 500. 27 | # @return [Array] 28 | # @example: 29 | # client.movements 30 | def movements(currency="usd", params = {}) 31 | check_params(params, %i{method since until limit since_movement until_movement}) 32 | params.merge!({currency: currency}) 33 | authenticated_post("history/movements", params: params).body 34 | end 35 | 36 | # View your past trades. 37 | # 38 | # @param symbol The pair traded (BTCUSD, LTCUSD, LTCBTC) 39 | # @param params :until [time] (optional) Return only the history before this timestamp. 40 | # @param params :timestamp [time] (optional) Trades made before this timestamp won’t be returned 41 | # @param params :until [time] (optional) Trades made after this timestamp won’t be returned 42 | # @param params :limit_trades [int] Limit the number of trades returned. Default is 50. 43 | # @param params :reverse [int] Return trades in reverse order (the oldest comes first). Default is returning newest trades first. 44 | # @return [Array] 45 | # @example: 46 | # client.mytrades 47 | def mytrades(symbol, params = {}) 48 | check_params(params, %i{until limit_trades reverse timestamp}) 49 | params.merge!({symbol: symbol}) 50 | authenticated_post("mytrades", params: params).body 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/rest/v1/lends.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv1Lends 3 | 4 | # Get a list of the most recent funding data for the given currency: total amount provided and Flash Return Rate (in % by 365 days) over time. 5 | # 6 | # @param currency [string] (optional) Specify the currency, default "USD" 7 | # @param params :timestamp [time] (optional) Only show data at or after this timestamp 8 | # @param params :limit_lends [int] (optional) Limit the amount of funding data returned. Must be > 1, default 50 9 | # @return [Array] 10 | # @example: 11 | # client.lends 12 | def lends(currency = "usd", params = {}) 13 | check_params(params, %i{timestamp limit_lends}) 14 | get("lends/#{currency}", params: params).body 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/rest/v1/margin_funding.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv1MarginFunding 3 | # Submit a new offer 4 | # 5 | # @param currency [string] The name of the currency, es: 'USD' 6 | # @param amount [decimal] Offer size: how much to lend or borrow 7 | # @param rate [decimal] Rate to lend or borrow at. In percentage per 365 days. 8 | # Set to 0 for FRR, ±delta for FRR±delta. 9 | # @param period [integer] Number of days of the funding contract (in days) 10 | # @param direction [string] Either “lend” or “loan” 11 | # @param frrdelta [bool] If true, the rate represents ±delta to FRR. 12 | # @return [Hash] 13 | # @example: 14 | # client.new_offer("btc", 10.0, 20, 365, "lend") 15 | def new_offer(currency, amount, rate, period, direction, frrdelta=false) 16 | params = { 17 | currency: currency, 18 | amount: amount.to_s, 19 | rate: rate.to_s, 20 | period: period.to_i, 21 | direction: direction, 22 | frrdelta: !!frrdelta 23 | } 24 | authenticated_post("offer/new", params: params).body 25 | end 26 | 27 | # Cancel an offer 28 | # 29 | # @param offer_id [int] The offer ID given by `#new_offer` 30 | # @return [Hash] 31 | # @example: 32 | # client.cancel_offer(1000) 33 | def cancel_offer(offer_id) 34 | authenticated_post("offer/cancel", params: {offer_id: offer_id.to_i}).body 35 | end 36 | 37 | # Get the status of an offer. Is it active? Was it cancelled? To what extent has it been executed? etc. 38 | # 39 | # @param offer_id [int] The offer ID give by `#new_offer` 40 | # @return [Hash] 41 | # @example: 42 | # client.offer_status(1000) 43 | def offer_status(offer_id) 44 | authenticated_post("offer/status", params: {offer_id: offer_id.to_i}).body 45 | end 46 | 47 | # View your funds currently taken (active credits) 48 | # 49 | # @return [Array] 50 | # @example: 51 | # client.credits 52 | def credits 53 | authenticated_post("credits").body 54 | end 55 | 56 | # View your active offers 57 | # 58 | # @return [Array] An array of the results of /offer/status for all your live offers (lending or borrowing 59 | # @example: 60 | # client.offers 61 | def offers 62 | authenticated_post("offers").body 63 | end 64 | 65 | # View your funding currently borrowed and used in a margin position 66 | # 67 | # @return [Array] An array of your active margin funds 68 | # @example: 69 | # client.taken_funds 70 | def taken_funds 71 | authenticated_post("taken_funds").body 72 | end 73 | 74 | # View your funding currently borrowed and not used (available for a new margin position). 75 | # 76 | # @return [Array] An array of your active unused margin funds 77 | # @example: 78 | # client.unused_taken_funds 79 | def unused_taken_funds 80 | authenticated_post("unused_taken_funds").body 81 | end 82 | 83 | # View the total of your active funding used in your position(s). 84 | # 85 | # @return [Array] An array of your active funding 86 | # @example: 87 | # client.total_taken_funds 88 | def total_taken_funds 89 | authenticated_post("total_taken_funds").body 90 | end 91 | 92 | # Allow you to close an unused or used taken fund 93 | # 94 | # @param swap_id [int] The ID given by `#taken_funds` or `#unused_taken_funds 95 | # @return [Hash] 96 | # @example: 97 | # client.close_funding(1000) 98 | def close_funding(swap_id) 99 | authenticated_post("funding/close", params: {swap_id: swap_id.to_i}).body 100 | end 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /lib/rest/v1/order_book.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv1OrderBook 3 | # Get the full order book 4 | # 5 | # @param symbol [string] 6 | # @param params :limit_bids [int] (optional) Limit the number of bids returned. May be 0 in which case the array of bids is empty. Default 50. 7 | # @param params :limit_asks [int] (optional) Limit the number of asks returned. May be 0 in which case the array of asks is empty. Default 50. 8 | # @param params :group [0/1] (optional) If 1, orders are grouped by price in the orderbook. If 0, orders are not grouped and sorted individually. Default 1 9 | # @return [Hash] :bids [Array], :asks [Array] 10 | # @example: 11 | # client.orderbook("btcusd") 12 | def orderbook(symbol="btcusd", params = {}) 13 | check_params(params, %i{limit_bids limit_asks group}) 14 | get("book/#{symbol}", params).body 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/rest/v1/orders.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv1Orders 3 | # Submit a new order 4 | # @param symbol [string] The name of the symbol (see `#symbols`) 5 | # @param amount [decimal] Order size: how much to buy or sell 6 | # @param type [string] Either “market” / “limit” / “stop” / “trailing-stop” / “fill-or-kill” / “exchange market” / “exchange limit” / “exchange stop” / “exchange trailing-stop” / “exchange fill-or-kill”. (type starting by “exchange ” are exchange orders, others are margin trading orders) 7 | # @param side [string] Either “buy” or “sell” 8 | # @param price [decimal] Price to buy or sell at. Must be positive. Use random number for market orders. 9 | # @param params :is_hidden [bool] (optional) true if the order should be hidden. Default is false 10 | # @param params :is_postonly [bool] (optional) true if the order should be post only. Default is false. Only relevant for limit orders 11 | # @param params :ocoorder [bool] Set an additional STOP OCO order that will be linked with the current order 12 | # @param params :buy_price_oco [decimal] If ocoorder is true, this field represent the price of the OCO stop order to place 13 | # @param params :sell_price_oco [decimal] If ocoorder is true, this field represent the price of the OCO stop order to place 14 | # @return [Hash] 15 | # @example: 16 | # client.new_order("usdbtc", 100, "market", "sell", 0) 17 | def new_order(symbol, amount, type, side, price = nil, params = {}) 18 | check_params(params, %i{is_hidden is_postonly ocoorder buy_price_oco use_all_available sell_price_oco}) 19 | 20 | # for 'market' order, we need to pass a random positive price, not nil 21 | price ||= 0.001 if type == "market" || type == "exchange market" 22 | 23 | params.merge!({ 24 | symbol: symbol, 25 | amount: amount.to_s, 26 | type: type, 27 | side: side, 28 | exchange: 'bitfinex', 29 | price: "%.10f" % price.to_f.round(10) # Decimalize float price (necessary for small numbers) 30 | }) 31 | authenticated_post("order/new", params: params).body 32 | end 33 | 34 | # Submit several new orders at once 35 | # 36 | # @param orders [Array] Array of Hash with the following elements 37 | # @param orders :symbol [string] The name of the symbol 38 | # @param orders :amount [decimal] Order size: how much to buy or sell 39 | # @param orders :price [decimal] Price to buy or sell at. May omit if a market order 40 | # @param orders :exchange [string] "bitfinex" 41 | # @param orders :side [string] Either “buy” or “sell” 42 | # @param orders :type [string] Either “market” / “limit” / “stop” / “trailing-stop” / “fill-or-kill” 43 | # @return [Hash] with a `object_id` that is an `Array` 44 | # @example: 45 | # client.multiple_orders([{symbol: "usdbtc", amount: 10, price: 0, exchange: "bitfinex", side: "buy", type: "market"}]) 46 | def multiple_orders(orders) 47 | authenticated_post("order/new/multi", params: {orders: orders}).body 48 | end 49 | 50 | # Cancel an order 51 | # 52 | # @param ids [Array] or [integer] or nil 53 | # if it's Array it's supposed to specify a list of IDS 54 | # if it's an integer it's supposed to be a single ID 55 | # if it's not specified it deletes all the orders placed 56 | # @return [Hash] 57 | # @example 58 | # client.cancel_orders([100,231,400]) 59 | def cancel_orders(ids=nil) 60 | case ids 61 | when Array 62 | authenticated_post("order/cancel/multi", params: {order_ids: ids.map(&:to_i)}).body 63 | when Numeric, String 64 | authenticated_post("order/cancel", params: {order_id: ids.to_i}).body 65 | when NilClass 66 | authenticated_post("order/cancel/all").body 67 | else 68 | raise ParamsError 69 | end 70 | end 71 | 72 | # Replace an orders with a new one 73 | # 74 | # @param id [int] the ID of the order to replace 75 | # @param symbol [string] the name of the symbol 76 | # @param amount [decimal] Order size: how much to buy or sell 77 | # @param type [string] Either “market” / “limit” / “stop” / “trailing-stop” / “fill-or-kill” / “exchange market” / “exchange limit” / “exchange stop” / “exchange trailing-stop” / “exchange fill-or-kill”. (type starting by “exchange ” are exchange orders, others are margin trading orders) 78 | # @param side [string] Either “buy” or “sell” 79 | # @param price [decimal] Price to buy or sell at. May omit if a market order 80 | # @param is_hidden [bool] (optional) true if the order should be hidden. Default is false 81 | # @param use_remaining [bool] (optional) will use the amount remaining of the canceled order as the amount of the new order. Default is false 82 | # @return [Hash] the order 83 | # @example: 84 | # client.replace_order(100,"usdbtc", 10, "market", "buy", 0) 85 | def replace_order(id, symbol, amount, type, side, price, is_hidden=false, use_remaining=false) 86 | params = { 87 | order_id: id.to_i, 88 | symbol: symbol, 89 | amount: amount.to_s, 90 | type: type, 91 | side: side, 92 | exchange: 'bitfinex', 93 | is_hidden: is_hidden, 94 | use_remaining: use_remaining, 95 | price: price.to_s 96 | } 97 | authenticated_post("order/cancel/replace", params: params).body 98 | end 99 | 100 | # Get the status of an order. Is it active? Was it cancelled? To what extent has it been executed? etc. 101 | # 102 | # @param id 103 | # @return [Hash] 104 | # @exmaple: 105 | # client.order_status(100) 106 | def order_status(id) 107 | authenticated_post("order/status", params: {order_id: id.to_i}).body 108 | end 109 | 110 | 111 | # View your active orders. 112 | # 113 | # @return [Hash] 114 | # @example: 115 | # client.orders 116 | def orders 117 | authenticated_post("orders").body 118 | end 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /lib/rest/v1/positions.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv1Positions 3 | # View your active positions. 4 | # 5 | # @return [Array] 6 | # @example: 7 | # client.positions 8 | def positions 9 | authenticated_post("positions").body 10 | end 11 | 12 | # A position can be claimed if: 13 | # 14 | # It is a long position: The amount in the last unit of the position pair that you have in your trading wallet AND/OR the realized profit of the position is greater or equal to the purchase amount of the position (base price * position amount) and the funds which need to be returned. For example, for a long BTCUSD position, you can claim the position if the amount of USD you have in the trading wallet is greater than the base price * the position amount and the funds used. 15 | # 16 | # It is a short position: The amount in the first unit of the position pair that you have in your trading wallet is greater or equal to the amount of the position and the margin funding used. 17 | # @param position_id [int] The position ID given by `/positions` 18 | # @param amount [decimal] The partial amount you wish to claim 19 | # @return [Hash] 20 | # @example: 21 | # client.claim_position(100,10) 22 | def claim_position(position_id, amount) 23 | authenticated_post("position/claim", params: {position_id: position_id, amount: amount}).body 24 | end 25 | 26 | # Closes the specified position with a market order 27 | def close_position(position_id) 28 | authenticated_post("position/close", params: {position_id: position_id}).body 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/rest/v1/stats.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv1Stats 3 | # Various statistics about the requested pair. 4 | # 5 | # @param symbol [string] Symbol of the pair you want info about. Default 'btcusd' 6 | # @return [Array] 7 | # @example: 8 | # client.stats('btcusd') 9 | def stats(symbol = "btcusd") 10 | get("stats/#{symbol}").body 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/rest/v1/symbols.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv1Symbols 3 | # Get a list of valid symbol IDs. 4 | # 5 | # @return [Array] 6 | # @example: 7 | # client.symbols 8 | def symbols 9 | get("symbols").body 10 | end 11 | 12 | # Get detailed list of symbols 13 | # 14 | # @return [Array] 15 | # @example: 16 | # client.symbols_details 17 | def symbols_details 18 | get("symbols_details").body 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/rest/v1/ticker.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv1Ticker 3 | # Gives innermost bid and asks and information on the most recent trade, as well as high, low and volume of the last 24 hours. 4 | # 5 | # @param symbol [string] The name of hthe symbol 6 | # @return [Hash] 7 | # @example: 8 | # client.ticker 9 | def ticker(symbol = "btcusd") 10 | get("pubticker/#{symbol}").body 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/rest/v1/trades.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv1Trades 3 | # Get a list of the most recent trades for the given symbol. 4 | # 5 | # @param symbol [string] the name of the symbol 6 | # @param params :timestamp [time] Only show trades at or after this timestamp. 7 | # @param params :limit_trades [int] Limit the number of trades returned. Must be >= 1. 8 | # @return [Array] 9 | # @example: 10 | # client.trades 11 | def trades(symbol="btcusd", params={}) 12 | check_params(params, %i{timestamp limit_trades}) 13 | get("trades/#{symbol}", params).body 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/rest/v1/wallet.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv1Wallet 3 | # See your balances. 4 | # 5 | # @param params :type [string] “trading”, “deposit” or “exchange”. 6 | # @param params :currency [string] currency 7 | # @param params :amount [decimal] How much balance of this currency in this wallet 8 | # @param params :available [decimal] How much X there is in this wallet that is available to trade 9 | # @return [Array] 10 | # @example: 11 | # client.balances 12 | def balances(params = {}) 13 | check_params(params, %i{type currency amount available}) 14 | authenticated_post("balances", params: params).body 15 | end 16 | 17 | # See your trading wallet information for margin trading. 18 | # 19 | # @return [Array] 20 | # @example: 21 | # client.margin_infos 22 | def margin_infos 23 | authenticated_post("margin_infos").body 24 | end 25 | 26 | # See a symmary of your trade volume, funding profits etc. 27 | # 28 | # @return [Hash] 29 | # @example: 30 | # client.summary 31 | def summary 32 | authenticated_post("summary").body 33 | end 34 | 35 | # Allow you to move available balances between your wallets. 36 | # 37 | # @param amount [decimal] Amount to transfer 38 | # @param currency [string] Currency of funds to transfer 39 | # @param wallet_from [string] Wallet to transfer from 40 | # @param wallet_to [string] Wallet to transfer to 41 | # @return [Array] 42 | # @example: 43 | # client.transfer(10, 'btc', "exchange", "deposit") 44 | def transfer(amount, currency, wallet_from, wallet_to) 45 | params = { 46 | amount: amount.to_s, 47 | currency: currency.upcase, 48 | walletfrom: wallet_from.downcase, 49 | walletto: wallet_to.downcase 50 | } 51 | authenticated_post("transfer", params: params).body 52 | end 53 | 54 | # Allow you to request a withdrawal from one of your wallet. 55 | # 56 | # @param withdraw_type [string] can be “bitcoin”, “litecoin” or “darkcoin” or “tether” or “wire” 57 | # @param walletselected [string] The wallet to withdraw from, can be “trading”, “exchange”, or “deposit”. 58 | # @param amount [decimal] Amount to withdraw 59 | # For Cryptocurrencies (including tether): 60 | # @param params :address [string] Destination address for withdrawal 61 | # For wire withdrawals 62 | # @param params :account_name [string] account name 63 | # @param params :account_number [string] account number 64 | # @param params :bank_name [string] bank name 65 | # @param params :bank_address [string] bank address 66 | # @param params :bank_city [string] bank city 67 | # @param params :bank_country [string] bank country 68 | # @param params :detail_payment [string] (optional) message to beneficiary 69 | # @param params :intermediary_bank_name [string] (optional) intermediary bank name 70 | # @param params :intermediary_bank_address [string] (optional) intermediary bank address 71 | # @param params :intermediary_bank_city [string] (optional) intermediary bank city 72 | # @param params :intermediary_bank_country [string] (optional) intermediary bank country 73 | # @param params :intermediary_bank_account [string] (optional) intermediary bank account 74 | # @param params :intermediary_bank_swift [string] (optional) intemediary bank swift 75 | # @param params :expressWire [int] (optional). “1” to submit an express wire withdrawal, “0” or omit for a normal withdrawal 76 | # @return [Array] 77 | # @example: 78 | # client.withdraw("bitcoin","deposit",1000, address: "1DKwqRhDmVyHJDL4FUYpDmQMYA3Rsxtvur") 79 | def withdraw(withdraw_type, walletselected, amount, params={}) 80 | params.merge!({ 81 | withdraw_type: withdraw_type, 82 | walletselected: walletselected.downcase, 83 | amount: amount.to_s}) 84 | 85 | authenticated_post("withdraw", params: params).body 86 | end 87 | 88 | # Check the permissions of the key being used to generate this request 89 | # 90 | # @return [Hash] 91 | # @example: 92 | # client.key_info 93 | def key_info 94 | authenticated_post("key_info").body 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /lib/rest/v2.rb: -------------------------------------------------------------------------------- 1 | require_relative './rest_client' 2 | require_relative './v2/margin' 3 | require_relative './v2/personal' 4 | require_relative './v2/stats' 5 | require_relative './v2/ticker' 6 | require_relative './v2/trading' 7 | require_relative './v2/utils' 8 | require_relative './v2/orders' 9 | require_relative './v2/wallet' 10 | require_relative './v2/funding' 11 | require_relative './v2/positions' 12 | require_relative './v2/pulse' 13 | 14 | module Bitfinex 15 | class RESTv2 16 | attr_accessor :api_endpoint, :debug, :debug_connection, :api_version 17 | attr_accessor :rest_timeout, :rest_open_timeout, :proxy 18 | attr_accessor :api_key, :api_secret, :aff_code 19 | 20 | include Bitfinex::RESTClient 21 | include Bitfinex::RESTv2Margin 22 | include Bitfinex::RESTv2Personal 23 | include Bitfinex::RESTv2Stats 24 | include Bitfinex::RESTv2Ticker 25 | include Bitfinex::RESTv2Trading 26 | include Bitfinex::RESTv2Utils 27 | include Bitfinex::RESTv2Orders 28 | include Bitfinex::RESTv2Wallet 29 | include Bitfinex::RESTv2Funding 30 | include Bitfinex::RESTv2Positions 31 | include Bitfinex::RESTv2Pulse 32 | 33 | def initialize(args = {}) 34 | self.api_endpoint = args[:url] ? "#{args[:url]}/v2/" : "https://api.bitfinex.com/v2/" 35 | self.proxy = args[:proxy] || nil 36 | self.debug_connection = false 37 | self.api_version = 2 38 | self.rest_timeout = 30 39 | self.rest_open_timeout = 30 40 | self.api_key = args[:api_key] 41 | self.api_secret = args[:api_secret] 42 | self.aff_code = args[:aff_code] 43 | end 44 | 45 | def config 46 | { 47 | :api_endpoint => self.api_endpoint, 48 | :debug_connection => self.debug_connection, 49 | :api_version => self.api_version, 50 | :rest_timeout => self.rest_timeout, 51 | :rest_open_timeout => self.rest_open_timeout, 52 | :proxy => self.proxy, 53 | :api_key => self.api_key, 54 | :api_secret => self.api_secret, 55 | :aff_code => self.aff_code 56 | } 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/rest/v2/funding.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv2Funding 3 | ### 4 | # Submit a new funding offer 5 | # 6 | # @param [Hash|FundingOffer] offer 7 | # 8 | # @return [Array] Raw notification 9 | ### 10 | def submit_funding_offer(offer) 11 | if offer.instance_of?(Models::FundingOffer) 12 | packet = offer.to_new_order_packet 13 | elsif offer.kind_of?(Hash) 14 | packet = Models::FundingOffer.new(offer).to_new_order_packet 15 | else 16 | raise Exception, 'tried to submit offer of unkown type' 17 | end 18 | authenticated_post("auth/w/funding/offer/submit", params: packet).body 19 | end 20 | 21 | ### 22 | # Cancel an active funding offer 23 | # 24 | # @param [Hash|Array|FundingOffer|number] offer - must contain or be ID 25 | # 26 | # @return [Array] Raw notification 27 | ### 28 | def cancel_funding_offer(offer) 29 | if offer.is_a?(Numeric) 30 | id = offer 31 | elsif offer.is_a?(Array) 32 | id = offer[0] 33 | elsif offer.instance_of?(Models::FundingOffer) 34 | id = offer.id 35 | elsif offer.kind_of?(Hash) 36 | id = offer[:id] || order['id'] 37 | else 38 | raise Exception, 'tried to cancel offer with invalid ID' 39 | end 40 | authenticated_post("auth/w/funding/offer/cancel", params: { :id => id }).body 41 | end 42 | 43 | ### 44 | # Close a funding loan/credit 45 | # 46 | # @param [Hash|Array|FundingOffer|FundingLoan|FundingCredit|number] funding - must contain or be ID 47 | # 48 | # @return [Array] Raw notification 49 | ### 50 | def close_funding(funding) 51 | if funding.is_a?(Numeric) 52 | id = funding 53 | elsif funding.is_a?(Array) 54 | id = funding[0] 55 | elsif funding.instance_of?(Models::FundingOffer) 56 | id = funding.id 57 | elsif funding.instance_of?(Models::FundingLoan) 58 | id = funding.id 59 | elsif funding.instance_of?(Models::FundingCredit) 60 | id = funding.id 61 | elsif funding.kind_of?(Hash) 62 | id = funding[:id] || order['id'] 63 | else 64 | raise Exception, 'tried to close funding with invalid ID' 65 | end 66 | authenticated_post("auth/w/funding/close", params: { :id => id }).body 67 | end 68 | 69 | ### 70 | # Submit a new auto funding request 71 | # 72 | # @param [string] currency - urrency for which to enable auto-renew 73 | # @param [number] amount - amount to be auto-renewed (Minimum 50 USD equivalent) 74 | # @param [string] rate - percentage rate at which to auto-renew. (rate == 0 to renew at FRR) 75 | # @param [integer] period - period in days 76 | # @param [integer] status - 1 for activate and 0 for deactivate 77 | # 78 | # @return [Array] Raw notification 79 | ### 80 | def submit_funding_auto(currency, amount, period, rate='0', status=1) 81 | dec_amount = BigDecimal(amount, 8).to_s 82 | payload = { :status => status, :currency => currency, :amount => dec_amount, :period => period, :rate => rate } 83 | authenticated_post("auth/w/funding/auto", params: payload).body 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/rest/v2/margin.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv2Margin 3 | 4 | # Get active offers 5 | # 6 | # @example: 7 | # client.offers 8 | def offers 9 | authenticated_post("auth/r/offers").body 10 | end 11 | 12 | # Get account margin info 13 | # - if symbol is not specified return everything 14 | # 15 | # @param symbol [string] (optional) 16 | # 17 | # @example: 18 | # client.margin_info("tBTCUSD") 19 | def margin_info(symbol = "base") 20 | authenticated_post("auth/r/margin/#{symbol}").body 21 | end 22 | 23 | # Get account funding info 24 | # 25 | # @param symbol [string] default fUSD 26 | # 27 | # @example: 28 | # client.funding_info 29 | def funding_info(symbol = "fUSD") 30 | authenticated_post("auth/r/funding/#{symbol}").body 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/rest/v2/orders.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv2Orders 3 | # Get active orders 4 | # 5 | # example: 6 | # client.orders 7 | def orders 8 | authenticated_post("auth/r/orders").body 9 | end 10 | 11 | # Get Trades generated by an Order 12 | # 13 | # @param order_id [int32] Id of the order 14 | # @param symbol [string] symbol used for the order 15 | # 16 | # @return [Array] 17 | # 18 | # @example: 19 | # client.order_trades 10010, "tBTCUSD" 20 | # 21 | def order_trades(order_id, symbol="tBTCUSD") 22 | authenticated_post("auth/r/order/#{symbol}:#{order_id}/trades").body 23 | end 24 | 25 | ### 26 | # Submit a new order 27 | # 28 | # @param [Hash|Order] order 29 | # 30 | # @return [Array] Raw notification 31 | ### 32 | def submit_order(order) 33 | if order.instance_of?(Models::Order) 34 | packet = order.to_new_order_packet 35 | elsif order.kind_of?(Hash) 36 | packet = Models::Order.new(order).to_new_order_packet 37 | else 38 | raise Exception, 'tried to submit order of unkown type' 39 | end 40 | 41 | if !@aff_code.nil? 42 | unless packet[:meta] 43 | packet[:meta] = {} 44 | end 45 | 46 | packet[:meta][:aff_code] = @aff_code 47 | end 48 | 49 | authenticated_post("auth/w/order/submit", params: packet).body 50 | end 51 | 52 | ### 53 | # Update an order with a changeset by ID 54 | # 55 | # @param [Hash] changes - must contain ID 56 | # 57 | # @return [Array] Raw notification 58 | ### 59 | def update_order (changes) 60 | authenticated_post("auth/w/order/update", params: changes).body 61 | end 62 | 63 | ### 64 | # Cancel an order by ID 65 | # 66 | # @param [Hash|Array|Order|number] order - must contain or be ID 67 | # 68 | # @return [Array] Raw notification 69 | ### 70 | def cancel_order (order) 71 | if order.is_a?(Numeric) 72 | id = order 73 | elsif order.is_a?(Array) 74 | id = order[0] 75 | elsif order.instance_of?(Models::Order) 76 | id = order.id 77 | elsif order.kind_of?(Hash) 78 | id = order[:id] || order['id'] 79 | else 80 | raise Exception, 'tried to cancel order with invalid ID' 81 | end 82 | authenticated_post("auth/w/order/cancel", params: { :id => id }).body 83 | end 84 | 85 | # TODO - requires websocket implementation as well 86 | def cancel_multi () 87 | raise Exception, 'not implemented' 88 | end 89 | 90 | # TODO - requires websocket implementation as well 91 | def order_multi () 92 | raise Exception, 'not implemented' 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /lib/rest/v2/personal.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv2Personal 3 | # Get account historical daily performance 4 | # 5 | # @example: 6 | # client.performance 7 | def performance 8 | authenticated_post("auth/r/stats/perf::1D/hist") 9 | end 10 | 11 | # Get the list of alerts 12 | # 13 | # @example: 14 | # client.alerts 15 | def alerts(type = 'price') 16 | authenticated_post("auth/r/alerts", params: {type: type}).body 17 | end 18 | 19 | # Set a new alert 20 | # 21 | # @param price 22 | # @param symbol 23 | # @param type 24 | # 25 | # @example: 26 | # client.alert(3000, "tBTCUSD") 27 | def alert(price, symbol = "tBTCUSD", type = "price") 28 | params = { 29 | type: type, 30 | price: price, 31 | symbol: symbol 32 | } 33 | authenticated_post("auth/w/alert/set", params: params).body 34 | end 35 | 36 | # Delete an existing alert 37 | # 38 | # @param price 39 | # @param symbol 40 | # 41 | # @example: 42 | # client.delete_alert(3000, "tBTCUSD") 43 | def delete_alert(price, symbol = "tBTCUSD") 44 | authenticated_post("auth/w/alert/price:#{symbol}:#{price}/del").body 45 | end 46 | 47 | # Calculate available balance for order/offer 48 | # 49 | # @param rate [int] Rate of the order/offer 50 | # @param dir [int] direction of the order/offer 51 | # (orders: > 0 buy, < 0 sell | offers: 52 | # > 0 sell, < 0 buy) 53 | # @param type [string] Type of the order/offer 54 | # EXCHANGE or MARGIN 55 | # @param symbol [string] 56 | 57 | # @example: 58 | # client.available_balance(800, 1, 'EXCHANGE', 'tBTCUSD') 59 | def available_balance(rate, dir, type, symbol) 60 | params = { 61 | symbol: symbol, 62 | dir: dir, 63 | type: type, 64 | rate: rate 65 | } 66 | authenticated_post("auth/calc/order/avail", params: params).body 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/rest/v2/positions.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv2Positions 3 | ### 4 | # Claim an active position 5 | # 6 | # @param [Hash|Array|Position|number] position - must contain or be ID 7 | # 8 | # @return [Array] Raw notification 9 | ### 10 | def claim_position(position) 11 | if position.is_a?(Numeric) 12 | id = position 13 | elsif position.is_a?(Array) 14 | id = position[0] 15 | elsif position.instance_of?(Models::Position) 16 | id = position.id 17 | elsif position.kind_of?(Hash) 18 | id = position[:id] || position['id'] 19 | else 20 | raise Exception, 'tried to claim position with invalid ID' 21 | end 22 | authenticated_post("auth/w/position/claim", params: { :id => id }).body 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/rest/v2/pulse.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv2Pulse 3 | ### 4 | # Get Pulse Profile 5 | # 6 | # @param [string] nickname Nickname of pulse profile 7 | # 8 | # @return [Hash] the Pulse Profile 9 | # 10 | # @see https://docs.bitfinex.com/reference#rest-public-pulse-profile 11 | ### 12 | def get_pulse_profile(nickname) 13 | resp = get("pulse/profile/#{nickname}").body 14 | Bitfinex::Models::PulseProfile.unserialize(resp) 15 | end 16 | 17 | ### 18 | # Get Public Pulse History 19 | # 20 | # @param [int] end (optional) Return only the entries after this timestamp 21 | # @param [int] limit (optional) Limit the number of entries to return. Default is 25. 22 | # 23 | # @return [Array] public pulse message 24 | # 25 | # @see https://docs.bitfinex.com/reference#rest-public-pulse-hist 26 | ### 27 | def get_public_pulse_history(params = {}) 28 | pulses = get("pulse/hist", params).body 29 | pulses.map { |p| deserialize_pulse_with_profile(p) } 30 | end 31 | 32 | ### 33 | # Get Private Pulse History 34 | # 35 | # @param [int] isPublic allows to receive the public pulse history with the UID_LIKED field 36 | # 37 | # @return [Array] private pulse message 38 | # 39 | # @see https://docs.bitfinex.com/reference#rest-auth-pulse-hist 40 | ### 41 | def get_private_pulse_history(params = {}) 42 | pulses = authenticated_post("auth/r/pulse/hist", params).body 43 | pulses.map { |p| deserialize_pulse_with_profile(p) } 44 | end 45 | 46 | ### 47 | # Submit new Pulse message 48 | # 49 | # @param [Hash] pulse 50 | # 51 | # @return [Hash] pulse 52 | # 53 | # @see https://docs.bitfinex.com/reference#rest-auth-pulse-add 54 | ### 55 | def submit_pulse(pulse) 56 | resp = authenticated_post("auth/w/pulse/add", params: pulse).body 57 | Bitfinex::Models::Pulse.unserialize(resp) 58 | end 59 | 60 | ### 61 | # Submit Pulse message comment, requires :parent (pulse id) to 62 | # be present in request payload 63 | # 64 | # @param [Hash] pulse 65 | # 66 | # @return [Hash] pulse 67 | # 68 | # @see https://docs.bitfinex.com/reference#rest-auth-pulse-add 69 | ### 70 | def submit_pulse_comment(pulse) 71 | if pulse[:parent].to_s.strip.empty? 72 | raise ":parent (pulse id value) is required for comments" 73 | end 74 | resp = authenticated_post("auth/w/pulse/add", params: pulse).body 75 | Bitfinex::Models::Pulse.unserialize(resp) 76 | end 77 | 78 | ### 79 | # Delete Pulse message 80 | # 81 | # @param [string] id pulse id 82 | # 83 | # @return [boolean] true if success, false if error 84 | # 85 | # @see https://docs.bitfinex.com/reference#rest-auth-pulse-del 86 | ### 87 | def delete_pulse(id) 88 | resp = authenticated_post("auth/w/pulse/del", params: { :pid => id }).body 89 | if resp[0] == 1 90 | return true 91 | end 92 | return false 93 | end 94 | 95 | private 96 | 97 | def deserialize_pulse_with_profile(payload) 98 | pulse = Bitfinex::Models::Pulse.unserialize(payload) 99 | if pulse[:profile].any? 100 | pulse[:profile] = Bitfinex::Models::PulseProfile.unserialize(pulse[:profile][0]) 101 | end 102 | pulse 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/rest/v2/stats.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv2Stats 3 | 4 | # Various statistics about the requested pair. 5 | # 6 | # @param symbol [string] The symbol you want information about. 7 | # @param key [string] Allowed values: "funding.size", 8 | # "credits.size", "credits.size.sym", "pos.size" 9 | # @param side [string] Available values: "long", "short" 10 | # @param section [string] Available values: "last", "hist" 11 | # @param size [string] Available values: '1m' 12 | # @param params :sort [int32] if = 1 it sorts results 13 | # returned with old > new 14 | # 15 | # @return [Array] 16 | # 17 | # @example: 18 | # client.stats('fUSD', 'pos.size') 19 | def stats(symbol = 'fUSD', key = 'funding.size', side = "long", section = "last", size = '1m', params = {}) 20 | check_params(params, %i{sort}) 21 | get("stats1/#{key}:#{size}:#{symbol}:#{side}/#{section}").body 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/rest/v2/ticker.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv2Ticker 3 | 4 | # Gives innermost bid and asks and information on 5 | # the most recent trade, as well as high, low and 6 | # volume of the last 24 hours. 7 | # 8 | # @param symbols a list of symbols 9 | # @return [Hash] 10 | # @example: 11 | # client.ticker("tBTCUSD","tLTCUSD","fUSD") 12 | def ticker(*symbols) 13 | if symbols.size == 1 14 | get("ticker/#{symbols.first}").body 15 | else 16 | get("tickers", symbols: "#{symbols.flatten.join(",")}").body 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/rest/v2/trading.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv2Trading 3 | 4 | # Provides a way to access charting candle info 5 | # 6 | # @param symbol [string] The symbol you want information about. 7 | # @param timeframe [string] Available values: '1m', '5m', '15m', 8 | # '30m', '1h', '3h', '6h', '12h', '1D', '7D', '14D', '1M' 9 | # @param section [string] Available values: "last", "hist" 10 | # @param params :limit [int32] Number of candles requested 11 | # @param params :start [int32] Filter start (ms) 12 | # @param params :end [int32] Filter end (ms) 13 | # @param params :sort [int32] if = 1 it sorts 14 | # results returned with old > new 15 | # 16 | # @return [Array] 17 | # 18 | # @example: 19 | # client.candles('tBTCUSD') 20 | def candles(symbol = 'tBTCUSD', timeframe = '1m', section = "hist", params = {}) 21 | check_params(params, %i{limit start end sort}) 22 | get("candles/trade:#{timeframe}:#{symbol}/#{section}", params).body 23 | end 24 | 25 | # The Order Books channel allow you to keep track 26 | # of the state of the Bitfinex order book. 27 | # It is provided on a price aggregated basis, 28 | # with customizable precision. 29 | # 30 | # 31 | # @param symbol [string] The symbol you want 32 | # information about. You can find the list of 33 | # valid symbols by calling the /symbols 34 | # endpoint. 35 | # @param precision [string] Level of price 36 | # aggregation (P0, P1, P2, P3, R0) 37 | # @param params :len [int32] Number of price 38 | # points ("25", "100") 39 | # 40 | # @return [Hash] :bids [Array], :asks [Array] 41 | # 42 | # @example: 43 | # client.orderbook("btcusd") 44 | def books(symbol="btcusd", precision="P0", params = {}) 45 | check_params(params, %i{len}) 46 | get("book/#{symbol}/#{precision}", params: params).body 47 | end 48 | 49 | # Trades endpoint includes all the pertinent details 50 | # of the trade, such as price, size and time. 51 | # 52 | # @param symbol [string] the name of the symbol 53 | # @param params :limit [int32] Number of records 54 | # @param params :start [int32] Millisecond start time 55 | # @param params :end [int32] Millisecond end time 56 | # @param params :sort [int32] if = 1 it sorts 57 | # results returned with old > new 58 | # 59 | # @return [Array] 60 | # 61 | # @example: 62 | # client.trades("tETHUSD") 63 | def trades(symbol="tBTCUSD", params={}) 64 | check_params(params, %i{limit start end sort}) 65 | get("trades/#{symbol}", params).body 66 | end 67 | 68 | # Get active positions 69 | # 70 | # return [Array] 71 | # 72 | # @example: 73 | # client.active_positions 74 | def active_positions 75 | authenticated_post("auth/r/positions").body 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/rest/v2/utils.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv2Utils 3 | 4 | # Calculate the average execution rate for Trading or Margin funding. 5 | # 6 | # @param symbol [string] The symbol you want information about. 7 | # @param amount [string] Amount. Positive for buy, negative for sell (ex. "1.123") 8 | # @param period [string] (optional) Maximum period for Margin Funding 9 | # @param rate_limit [string] Limit rate/price (ex. "1000.5") 10 | # 11 | # @return [Array] 12 | # 13 | # @example: 14 | # client.calc_avg_price('tBTCUSD', 1000, 1000) 15 | def calc_avg_price(symbol = 'tBTCUSD', amount = 0, period = 0, rate_limit = nil) 16 | params = { 17 | symbol: symbol, 18 | amount: amount.to_s, 19 | period: period.to_s 20 | } 21 | params[:rateLimit] = rate_limit.to_s unless rate_limit.nil? 22 | get("calc/trade/avg",params) 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/rest/v2/wallet.rb: -------------------------------------------------------------------------------- 1 | module Bitfinex 2 | module RESTv2Wallet 3 | # Get account wallets 4 | # 5 | # @example: 6 | # client.wallets 7 | def wallets 8 | authenticated_post("auth/r/wallets").body 9 | end 10 | 11 | ### 12 | # Transfer between bitfinex wallets 13 | # 14 | # @param from [string] wallet to transfer funds from (exchange, margin ect...) 15 | # @param to [string] wallet to transfer funds to (exchange, margin ect...) 16 | # @param currency_from [string] original currency of funds 17 | # @param currency_to [string] currency to convert funds to 18 | # @param amount [number] amount of funds to convert 19 | # 20 | # @return [Array] Raw notification 21 | ### 22 | def transfer (from, to, currency_from, currency_to, amount) 23 | payload = { :from => from, :to => to, :currency => currency_from, :currency_to => currency_to, :amount => amount } 24 | authenticated_post("auth/w/transfer", params: payload).body 25 | end 26 | 27 | ### 28 | # Get the deposit address for the given currency 29 | # 30 | # @param wallet [string] wallet to transfer funds from (exchange, margin ect...) 31 | # @param method [string] funds transfer protocol (bitcoin, tetherus ect...) 32 | # 33 | # @return [Array] Raw notification 34 | ### 35 | def deposit_address (wallet, method) 36 | payload = { :wallet => wallet, :method => method, :op_renew => 0 } 37 | authenticated_post("auth/w/deposit/address", params: payload).body 38 | end 39 | 40 | ### 41 | # Regenerate the deposit address for the given currency. All previous addresses 42 | # are still active and can receive funds. 43 | # 44 | # @param wallet [string] wallet to transfer funds from (exchange, margin ect...) 45 | # @param method [string] funds transfer protocol (bitcoin, tetherus ect...) 46 | # 47 | # @return [Array] Raw notification 48 | ### 49 | def create_deposit_address (wallet, method) 50 | payload = { :wallet => wallet, :method => method, :op_renew => 1 } 51 | authenticated_post("auth/w/deposit/address", params: payload).body 52 | end 53 | 54 | ### 55 | # Withdraw from the given bitfinex wallet to the given cryptocurrency address 56 | # 57 | # @param wallet [string] wallet to transfer funds from (exchange, margin ect...) 58 | # @param method [string] funds transfer protocol (bitcoin, tetherus ect...) 59 | # @param amount [number] amount of funds to withdraw 60 | # @param address [string] public key destination address 61 | # 62 | # @return [Array] Raw notification 63 | ### 64 | def withdraw (wallet, method, amount, address) 65 | payload = { :wallet => wallet, :method => method, :amount => amount, :address => address } 66 | authenticated_post("auth/w/withdraw", params: { :id => id }).body 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/ws/ws2.rb: -------------------------------------------------------------------------------- 1 | require 'faye/websocket' 2 | require 'eventmachine' 3 | require 'logger' 4 | require 'emittr' 5 | 6 | require_relative '../models/alert' 7 | require_relative '../models/balance_info' 8 | require_relative '../models/candle' 9 | require_relative '../models/currency' 10 | require_relative '../models/funding_credit' 11 | require_relative '../models/funding_info' 12 | require_relative '../models/funding_loan' 13 | require_relative '../models/funding_offer' 14 | require_relative '../models/funding_ticker' 15 | require_relative '../models/funding_trade' 16 | require_relative '../models/ledger_entry' 17 | require_relative '../models/margin_info' 18 | require_relative '../models/movement' 19 | require_relative '../models/notification' 20 | require_relative '../models/order_book' 21 | require_relative '../models/order' 22 | require_relative '../models/position' 23 | require_relative '../models/public_trade' 24 | require_relative '../models/trade' 25 | require_relative '../models/trading_ticker' 26 | require_relative '../models/user_info' 27 | require_relative '../models/wallet' 28 | 29 | module Bitfinex 30 | ### 31 | # Implements version 2 of the Bitfinex WebSocket API, taking an evented 32 | # approach. Incoming packets trigger event broadcasts with names relevant to 33 | # the individual packets. Provides order manipulation methods that support 34 | # callback blocks, which are called when the relevant confirmation 35 | # notifications are received 36 | ### 37 | class WSv2 38 | include Emittr::Events 39 | 40 | INFO_SERVER_RESTART = 20051 41 | INFO_MAINTENANCE_START = 20060 42 | INFO_MAINTENANCE_END = 20061 43 | 44 | FLAG_DEC_S = 8, # enables all decimals as strings 45 | FLAG_TIME_S = 32, # enables all timestamps as strings 46 | FLAG_TIMESTAMP = 32768, # timestamps in milliseconds 47 | FLAG_SEQ_ALL = 65536, # enable sequencing 48 | FLAG_CHECKSUM = 131072 # enable OB checksums, top 25 levels per side 49 | 50 | ### 51 | # Creates a new instance of the class 52 | # 53 | # @param [Hash] params 54 | # @param [string] params.url - connection URL 55 | # @param [string] params.aff_code - optional affiliate code to be applied to all orders 56 | # @param [string] params.api_key 57 | # @param [string] params.api_secret 58 | # @param [boolean] params.manage_order_books - if true, order books are persisted internally, allowing for automatic checksum verification 59 | # @param [boolean] params.transform - if true, full models are returned in place of array data 60 | # @param [boolean] params.seq_audit - enables automatic seq number verification 61 | # @param [boolean] params.checksum_audit - enables automatic OB checksum verification (requires manage_order_books) 62 | ### 63 | def initialize (params = {}) 64 | @l = Logger.new(STDOUT) 65 | @l.progname = 'ws2' 66 | 67 | @url = params[:url] || 'wss://api.bitfinex.com/ws/2' 68 | @aff_code = params[:aff_code] 69 | @api_key = params[:api_key] 70 | @api_secret = params[:api_secret] 71 | @manage_obs = params[:manage_order_books] 72 | @transform = !!params[:transform] 73 | @seq_audit = !!params[:seq_audit] 74 | @checksum_audit = !!params[:checksum_audit] 75 | 76 | @enabled_flags = 0 77 | @is_open = false 78 | @is_authenticated = false 79 | @channel_map = {} 80 | @order_books = {} 81 | @pending_blocks = {} 82 | @last_pub_seq = nil 83 | @last_auth_seq = nil 84 | end 85 | 86 | def on_open (e) # :nodoc: 87 | @l.info 'client open' 88 | @is_open = true 89 | emit(:open) 90 | 91 | enable_sequencing if @seq_audit 92 | enable_ob_checksums if @checksum_audit 93 | end 94 | 95 | def on_message (e) # :nodoc: 96 | @l.info "recv #{e.data}" 97 | 98 | msg = JSON.parse(e.data) 99 | process_message(msg) 100 | 101 | emit(:message, msg) 102 | end 103 | 104 | def on_close (e) # :nodoc: 105 | @l.info 'client closed' 106 | @is_open = false 107 | emit(:close) 108 | end 109 | 110 | ### 111 | # Opens the websocket client inside an eventmachine run block 112 | ### 113 | def open! 114 | if @is_open 115 | raise Exception, 'already open' 116 | end 117 | 118 | EM.run { 119 | @ws = Faye::WebSocket::Client.new(@url) 120 | 121 | @ws.on(:open) do |e| 122 | on_open(e) 123 | end 124 | 125 | @ws.on(:message) do |e| 126 | on_message(e) 127 | end 128 | 129 | @ws.on(:close) do |e| 130 | on_close(e) 131 | end 132 | } 133 | end 134 | 135 | ### 136 | # Closes the websocket client 137 | ### 138 | def close! 139 | @ws.close 140 | end 141 | 142 | def process_message (msg) # :nodoc: 143 | if @seq_audit 144 | validate_message_seq(msg) 145 | end 146 | 147 | if msg.kind_of?(Array) 148 | process_channel_message(msg) 149 | elsif msg.kind_of?(Hash) 150 | process_event_message(msg) 151 | end 152 | end 153 | 154 | def validate_message_seq (msg) # :nodoc: 155 | return unless @seq_audit 156 | return unless msg.kind_of?(Array) 157 | return unless msg.size > 2 158 | 159 | # The auth sequence # is the last value in channel 0 non-hb packets 160 | if msg[0] == 0 && msg[1] != 'hb' 161 | auth_seq = msg[-1] 162 | else 163 | auth_seq = nil 164 | end 165 | 166 | # all other packets provide a public sequence # as the last value. For 167 | # chan 0 packets, these are included as the 2nd to last value 168 | # 169 | # note that error notifications lack seq 170 | if msg[0] == 0 && msg[1] != 'hb' && !(msg[1] && msg[2][6] == 'ERROR') 171 | pub_seq = msg[-2] 172 | else 173 | pub_seq = msg[-1] 174 | end 175 | 176 | return unless pub_seq.is_a?(Numeric) 177 | 178 | if @last_pub_seq.nil? 179 | @last_pub_seq = pub_seq 180 | return 181 | end 182 | 183 | if pub_seq != (@last_pub_seq + 1) # check pub seq 184 | @l.warn "invalid pub seq #; last #{@last_pub_seq}, got #{pub_seq}" 185 | end 186 | 187 | @last_pub_seq = pub_seq 188 | 189 | return unless auth_seq.is_a?(Numeric) 190 | return if auth_seq == 0 191 | return if msg[1] == 'n' && msg[2][6] == 'ERROR' # error notifications 192 | return if auth_seq == @last_auth_seq # seq didn't advance 193 | 194 | if !@last_auth_seq.nil? && auth_seq != @last_auth_seq + 1 195 | @l.warn "invalid auth seq #; last #{@last_auth_seq}, got #{auth_seq}" 196 | end 197 | 198 | @last_auth_seq = auth_seq 199 | end 200 | 201 | def process_channel_message (msg) # :nodoc: 202 | if !@channel_map.include?(msg[0]) 203 | @l.error "recv message on unknown channel: #{msg[0]}" 204 | return 205 | end 206 | 207 | chan = @channel_map[msg[0]] 208 | type = msg[1] 209 | 210 | if msg.size < 2 || type == 'hb' 211 | return 212 | end 213 | 214 | case chan['channel'] 215 | when 'ticker' 216 | handle_ticker_message(msg, chan) 217 | when 'trades' 218 | handle_trades_message(msg, chan) 219 | when 'candles' 220 | handle_candles_message(msg, chan) 221 | when 'book' 222 | if type == 'cs' 223 | handle_order_book_checksum_message(msg, chan) 224 | else 225 | handle_order_book_message(msg, chan) 226 | end 227 | when 'auth' 228 | handle_auth_message(msg, chan) 229 | end 230 | end 231 | 232 | def handle_ticker_message (msg, chan) # :nodoc: 233 | payload = msg[1] 234 | payload_with_sym = [chan['symbol']].concat(payload) 235 | 236 | if chan['symbol'][0] === 't' 237 | emit(:ticker, chan['symbol'], @transform ? Models::TradingTicker.new(payload_with_sym) : payload) 238 | else 239 | emit(:ticker, chan['symbol'], @transform ? Models::FundingTicker.new(payload_with_sym) : payload) 240 | end 241 | end 242 | 243 | def handle_trades_message (msg, chan) # :nodoc: 244 | if msg[1].kind_of?(Array) 245 | payload = msg[1] 246 | emit(:public_trades, chan['symbol'], @transform ? payload.map { |t| Models::PublicTrade.new(t) } : payload) 247 | else 248 | payload = @transform ? Models::PublicTrade.new(msg[2]) : msg[2] 249 | type = msg[1] 250 | 251 | emit(:public_trades, chan['symbol'], payload) 252 | 253 | if type == 'te' 254 | emit(:public_trade_entry, chan['symbol'], payload) 255 | elsif type == 'tu' 256 | emit(:public_trade_update, chan['symbol'], payload) 257 | end 258 | end 259 | end 260 | 261 | def handle_candles_message (msg, chan) # :nodoc: 262 | payload = msg[1] 263 | 264 | if payload[0].kind_of?(Array) 265 | emit(:candles, chan['key'], @transform ? payload.map { |c| Models::Candle.new(c) } : payload) 266 | else 267 | emit(:candles, chan['key'], @transform ? Models::Candle.new(payload) : payload) 268 | end 269 | end 270 | 271 | def handle_order_book_checksum_message (msg, chan) # :nodoc: 272 | key = "#{chan['symbol']}:#{chan['prec']}:#{chan['len']}" 273 | emit(:checksum, chan['symbol'], msg) 274 | 275 | return unless @manage_obs 276 | return unless @order_books.has_key?(key) 277 | 278 | remote_cs = msg[2] 279 | local_cs = @order_books[key].checksum 280 | 281 | if local_cs != remote_cs 282 | err = "OB checksum mismatch, have #{local_cs} want #{remote_cs} [#{chan['symbol']}" 283 | @l.error err 284 | emit(:error, err) 285 | else 286 | @l.info "OB checksum OK #{local_cs} [#{chan['symbol']}]" 287 | end 288 | end 289 | 290 | def handle_order_book_message (msg, chan) # :nodoc: 291 | ob = msg[1] 292 | 293 | if @manage_obs 294 | key = "#{chan['symbol']}:#{chan['prec']}:#{chan['len']}" 295 | 296 | if !@order_books.has_key?(key) 297 | @order_books[key] = Models::OrderBook.new(ob, chan['prec'][0] == 'R') 298 | else 299 | @order_books[key].update_with(ob) 300 | end 301 | 302 | data = @order_books[key] 303 | elsif @transform 304 | data = Models::OrderBook.new(ob) 305 | else 306 | data = ob 307 | end 308 | 309 | emit(:order_book, chan['symbol'], data) 310 | end 311 | 312 | # Resolves/rejects any pending promise associated with the notification 313 | def handle_notification_promises (n) # :nodoc: 314 | type = n[1] 315 | payload = n[4] 316 | status = n[6] 317 | msg = n[7] 318 | 319 | return unless payload.kind_of?(Array) # expect order payload 320 | 321 | case type 322 | when 'on-req' 323 | cid = payload[2] 324 | k = "order-new-#{cid}" 325 | 326 | return unless @pending_blocks.has_key?(k) 327 | 328 | if status == 'SUCCESS' 329 | @pending_blocks[k].call(@transform ? Models::Order.new(payload) : payload) 330 | else 331 | @pending_blocks[k].call(Exception.new("#{status}: #{msg}")) 332 | end 333 | 334 | @pending_blocks.delete(k) 335 | when 'oc-req' 336 | id = payload[0] 337 | k = "order-cancel-#{id}" 338 | 339 | return unless @pending_blocks.has_key?(k) 340 | 341 | if status == 'SUCCESS' 342 | @pending_blocks[k].call(payload) 343 | else 344 | @pending_blocks[k].call(Exception.new("#{status}: #{msg}")) 345 | end 346 | 347 | @pending_blocks.delete(k) 348 | when 'ou-req' 349 | id = payload[0] 350 | k = "order-update-#{id}" 351 | 352 | return unless @pending_blocks.has_key?(k) 353 | 354 | if status == 'SUCCESS' 355 | @pending_blocks[k].call(@transform ? Models::Order.new(payload) : payload) 356 | else 357 | @pending_blocks[k].call(Exception.new("#{status}: #{msg}")) 358 | end 359 | end 360 | end 361 | 362 | def handle_auth_message (msg, chan) # :nodoc: 363 | type = msg[1] 364 | return if type == 'hb' 365 | payload = msg[2] 366 | 367 | case type 368 | when 'n' 369 | emit(:notification, @transform ? Models::Notification.new(payload) : payload) 370 | handle_notification_promises(payload) 371 | when 'te' 372 | emit(:trade_entry, @transform ? Models::Trade.new(payload) : payload) 373 | when 'tu' 374 | emit(:trade_update, @transform ? Models::Trade.new(payload) : payload) 375 | when 'os' 376 | emit(:order_snapshot, @transform ? payload.map { |o| Models::Order.new(o) } : payload) 377 | when 'ou' 378 | emit(:order_update, @transform ? Models::Order.new(payload) : payload) 379 | when 'on' 380 | emit(:order_new, @transform ? Models::Order.new(payload) : payload) 381 | when 'oc' 382 | emit(:order_close, @transform ? Models::Order.new(payload) : payload) 383 | when 'ps' 384 | emit(:position_snapshot, @transform ? payload.map { |p| Models::Position.new(p) } : payload) 385 | when 'pn' 386 | emit(:position_new, @transform ? Models::Position.new(payload) : payload) 387 | when 'pu' 388 | emit(:position_update, @transform ? Models::Position.new(payload) : payload) 389 | when 'pc' 390 | emit(:position_close, @transform ? Models::Position.new(payload) : payload) 391 | when 'fos' 392 | emit(:funding_offer_snapshot, @transform ? payload.map { |fo| Models::FundingOffer.new(fo) } : payload) 393 | when 'fon' 394 | emit(:funding_offer_new, @transform ? Models::FundingOffer.new(payload) : payload) 395 | when 'fou' 396 | emit(:funding_offer_update, @transform ? Models::FundingOffer.new(payload) : payload) 397 | when 'foc' 398 | emit(:funding_offer_close, @transform ? Models::FundingOffer.new(payload) : payload) 399 | when 'fcs' 400 | emit(:funding_credit_snapshot, @transform ? payload.map { |fc| Models::FundingCredit.new(fc) } : payload) 401 | when 'fcn' 402 | emit(:funding_credit_new, @transform ? Models::FundingCredit.new(payload) : payload) 403 | when 'fcu' 404 | emit(:funding_credit_update, @transform ? Models::FundingCredit.new(payload) : payload) 405 | when 'fcc' 406 | emit(:funding_credit_close, @transform ? Models::FundingCredit.new(payload) : payload) 407 | when 'fls' 408 | emit(:funding_loan_snapshot, @transform ? payload.map { |fl| Models::FundingLoan.new(fl) } : payload) 409 | when 'fln' 410 | emit(:funding_loan_new, @transform ? Models::FundingLoan.new(payload) : payload) 411 | when 'flu' 412 | emit(:funding_loan_update, @transform ? Models::FundingLoan.new(payload) : payload) 413 | when 'flc' 414 | emit(:funding_loan_close, @transform ? Models::FundingLoan.new(payload) : payload) 415 | when 'ws' 416 | emit(:wallet_snapshot, @transform ? payload.map { |w| Models::Wallet.new(payload) } : payload) 417 | when 'wu' 418 | emit(:wallet_update, @transform ? Models::Wallet.new(payload) : payload) 419 | when 'bu' 420 | emit(:balance_update, @transform ? Models::BalanceInfo.new(payload) : payload) 421 | when 'miu' 422 | emit(:margin_info_update, @transform ? Models::MarginInfo.new(payload) : payload) 423 | when 'fiu' 424 | emit(:funding_info_update, @transform ? Models::FundingInfo.new(payload) : payload) 425 | when 'fte' 426 | emit(:funding_trade_entry, @transform ? Models::FundingTrade.new(payload) : payload) 427 | when 'ftu' 428 | emit(:funding_trade_update, @transform ? Models::FundingTrade.new(payload) : payload) 429 | end 430 | end 431 | 432 | ### 433 | # Subscribes to the specified channel; params dictate the channel filter 434 | # 435 | # @param [string] channel - i.e. 'trades', 'candles', etc 436 | # @param [Hash] params 437 | # @param [string?] params.symbol 438 | # @param [string?] params.prec - for order book channels 439 | # @param [string?] params.len - for order book channels 440 | # @param [string?] params.key - for candle channels 441 | ### 442 | def subscribe (channel, params = {}) 443 | @l.info 'subscribing to channel %s [%s]' % [channel, params] 444 | @ws.send(JSON.generate(params.merge({ 445 | :event => 'subscribe', 446 | :channel => channel, 447 | }))) 448 | end 449 | 450 | ### 451 | # Subscribes to a ticker channel by symbol 452 | # 453 | # @param [string] sym - i.e. tBTCUSD 454 | ### 455 | def subscribe_ticker (sym) 456 | subscribe('ticker', { :symbol => sym }) 457 | end 458 | 459 | ### 460 | # Subscribes to a trades channel by symbol 461 | # 462 | # @param [string] sym - i.e. tBTCUSD 463 | ### 464 | def subscribe_trades (sym) 465 | subscribe('trades', { :symbol => sym }) 466 | end 467 | 468 | ### 469 | # Subscribes to a candle channel by key 470 | # 471 | # @param [string] key - i.e. trade:1m:tBTCUSD 472 | ### 473 | def subscribe_candles (key) 474 | subscribe('candles', { :key => key }) 475 | end 476 | 477 | ### 478 | # Subscribes to an order book channel 479 | # 480 | # @param [string] sym - i.e. tBTCUSD 481 | # @param [string] prec - i.e. R0, P0, etc 482 | # @param [string] len - i.e. 25, 100, etc 483 | ### 484 | def subscribe_order_book (sym, prec, len) 485 | subscribe('book', { 486 | :symbol => sym, 487 | :prec => prec, 488 | :len => len 489 | }) 490 | end 491 | 492 | def process_event_message (msg) # :nodoc: 493 | case msg['event'] 494 | when 'auth' 495 | handle_auth_event(msg) 496 | when 'subscribed' 497 | handle_subscribed_event(msg) 498 | when 'unsubscribed' 499 | handle_unsubscribed_event(msg) 500 | when 'info' 501 | handle_info_event(msg) 502 | when 'conf' 503 | handle_config_event(msg) 504 | when 'error' 505 | handle_error_event(msg) 506 | end 507 | end 508 | 509 | def handle_auth_event (msg) # :nodoc: 510 | if msg['status'] != 'OK' 511 | @l.error "auth failed: #{msg['message']}" 512 | return 513 | end 514 | 515 | @channel_map[msg['chanId']] = { 'channel' => 'auth' } 516 | @is_authenticated = true 517 | emit(:auth, msg) 518 | 519 | @l.info 'authenticated' 520 | end 521 | 522 | def handle_info_event (msg) # :nodoc: 523 | if msg.include?('version') 524 | if msg['version'] != 2 525 | close! 526 | raise Exception, "server not running API v2: #{msg['version']}" 527 | end 528 | 529 | platform = msg['platform'] 530 | 531 | @l.info "server running API v2 (platform: %s (%d))" % [ 532 | platform['status'] == 0 ? 'under maintenance' : 'operating normally', 533 | platform['status'] 534 | ] 535 | elsif msg.include?('code') 536 | code = msg['code'] 537 | 538 | if code == INFO_SERVER_RESTART 539 | @l.info 'server restarted, please reconnect' 540 | emit(:server_restart) 541 | elsif code == INFO_MAINTENANCE_START 542 | @l.info 'server maintenance period started!' 543 | emit(:maintenance_start) 544 | elsif code == INFO_MAINTENANCE_END 545 | @l.info 'server maintenance period ended!' 546 | emit(:maintenance_end) 547 | end 548 | end 549 | end 550 | 551 | def handle_error_event (msg) # :nodoc: 552 | @l.error msg 553 | end 554 | 555 | def handle_config_event (msg) # :nodoc: 556 | if msg['status'] != 'OK' 557 | @l.error "config failed: #{msg['message']}" 558 | else 559 | @l.info "flags updated to #{msg['flags']}" 560 | @enabled_flags = msg['flags'] 561 | end 562 | end 563 | 564 | def handle_subscribed_event (msg) # :nodoc: 565 | @l.info "subscribed to #{msg['channel']} [#{msg['chanId']}]" 566 | @channel_map[msg['chanId']] = msg 567 | emit(:subscribed, msg['chanId']) 568 | end 569 | 570 | def handle_unsubscribed_event (msg) # :nodoc: 571 | @l.info "unsubscribed from #{msg['chanId']}" 572 | @channel_map.delete(msg['chanId']) 573 | emit(:unsubscribed, msg['chanId']) 574 | end 575 | 576 | ### 577 | # Enable an individual flag (see FLAG_* constants) 578 | # 579 | # @param [number] flag 580 | ### 581 | def enable_flag (flag) 582 | return unless @is_open 583 | 584 | @ws.send(JSON.generate({ 585 | :event => 'conf', 586 | :flags => @enabled_flags | flag 587 | })) 588 | end 589 | 590 | ### 591 | # Checks if an individual flag is enabled (see FLAG_* constants) 592 | # 593 | # @param [number] flag 594 | # @return [boolean] enabled 595 | ### 596 | def is_flag_enabled (flag) 597 | (@enabled_flags & flag) == flag 598 | end 599 | 600 | ### 601 | # Sets the flag to activate sequence numbers on incoming packets 602 | # 603 | # @param [boolean] audit - if true (default), incoming seq numbers will be checked for consistency 604 | ### 605 | def enable_sequencing (audit = true) 606 | @seq_audit = audit 607 | enable_flag(FLAG_SEQ_ALL) 608 | end 609 | 610 | ### 611 | # Sets the flag to activate order book checksums. Managed order books are 612 | # required for automatic checksum audits. 613 | # 614 | # @param [boolean] audit - if true (default), incoming checksums will be compared to local checksums 615 | ### 616 | def enable_ob_checksums (audit = true) 617 | @checksum_audit = audit 618 | enable_flag(FLAG_CHECKSUM) 619 | end 620 | 621 | ### 622 | # Authenticates the socket connection 623 | # 624 | # @param [number] calc 625 | # @param [number] dms - dead man switch, active 4 626 | ### 627 | def auth! (calc = 0, dms = 0) 628 | if @is_authenticated 629 | raise Exception, 'already authenticated' 630 | end 631 | 632 | auth_nonce = new_nonce 633 | auth_payload = "AUTH#{auth_nonce}#{auth_nonce}" 634 | sig = sign(auth_payload) 635 | 636 | @ws.send(JSON.generate({ 637 | :event => 'auth', 638 | :apiKey => @api_key, 639 | :authSig => sig, 640 | :authPayload => auth_payload, 641 | :authNonce => auth_nonce, 642 | :dms => dms, 643 | :calc => calc 644 | })) 645 | end 646 | 647 | def new_nonce # :nodoc: 648 | (Time.now.to_f * 1000000).floor.to_s 649 | end 650 | 651 | def sign (payload) # :nodoc: 652 | OpenSSL::HMAC.hexdigest('sha384', @api_secret, payload) 653 | end 654 | 655 | ### 656 | # Requests a calculation to be performed 657 | # @see https://docs.bitfinex.com/v2/reference#ws-input-calc 658 | # 659 | # @param [Array] prefixes - i.e. ['margin_base'] 660 | ### 661 | def request_calc (prefixes) 662 | @ws.send(JSON.generate([0, 'calc', nil, prefixes.map { |p| [p] }])) 663 | end 664 | 665 | ### 666 | # Update an order with a changeset by ID 667 | # 668 | # @param [Hash] changes - must contain ID 669 | # @param [Block] cb - triggered upon receipt of confirmation notification 670 | ### 671 | def update_order (changes, &cb) 672 | id = changes[:id] || changes['id'] 673 | @ws.send(JSON.generate([0, 'ou', nil, changes])) 674 | 675 | if !cb.nil? 676 | @pending_blocks["order-update-#{id}"] = cb 677 | end 678 | end 679 | 680 | ### 681 | # Cancel an order by ID 682 | # 683 | # @param [Hash|Array|Order|number] order - must contain or be ID 684 | # @param [Block] cb - triggered upon receipt of confirmation notification 685 | ### 686 | def cancel_order (order, &cb) 687 | return if !@is_authenticated 688 | 689 | if order.is_a?(Numeric) 690 | id = order 691 | elsif order.is_a?(Array) 692 | id = order[0] 693 | elsif order.instance_of?(Models::Order) 694 | id = order.id 695 | elsif order.kind_of?(Hash) 696 | id = order[:id] || order['id'] 697 | else 698 | raise Exception, 'tried to cancel order with invalid ID' 699 | end 700 | 701 | @ws.send(JSON.generate([0, 'oc', nil, { :id => id }])) 702 | 703 | if !cb.nil? 704 | @pending_blocks["order-cancel-#{id}"] = cb 705 | end 706 | end 707 | 708 | ### 709 | # Submit a new order 710 | # 711 | # @param [Hash|Array|Order] order 712 | # @param [Block] cb - triggered upon receipt of confirmation notification 713 | ### 714 | def submit_order (order, &cb) 715 | return if !@is_authenticated 716 | 717 | if order.kind_of?(Array) 718 | packet = order 719 | elsif order.instance_of?(Models::Order) 720 | packet = order.to_new_order_packet 721 | elsif order.kind_of?(Hash) 722 | packet = Models::Order.new(order).to_new_order_packet 723 | else 724 | raise Exception, 'tried to submit order of unkown type' 725 | end 726 | 727 | if !@aff_code.nil? 728 | unless packet[:meta] 729 | packet[:meta] = {} 730 | end 731 | 732 | packet[:meta][:aff_code] = @aff_code 733 | end 734 | 735 | @ws.send(JSON.generate([0, 'on', nil, packet])) 736 | 737 | if packet.has_key?(:cid) && !cb.nil? 738 | @pending_blocks["order-new-#{packet[:cid]}"] = cb 739 | end 740 | end 741 | end 742 | end 743 | -------------------------------------------------------------------------------- /spec/integration/v1_account_info_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'webmock/rspec' 3 | require 'hashdiff' 4 | require_relative '../../lib/bitfinex' 5 | 6 | RSpec.describe 'Bitfinex::RESTv1 Integration' do 7 | before do 8 | # Stub the account info request 9 | stub_request(:post, "https://api.bitfinex.com/v1/account_infos") 10 | .with(body: '{}') 11 | .to_return( 12 | status: 200, 13 | body: '[ 14 | { 15 | "leo_fee_disc_c2c":"0.0", 16 | "leo_fee_disc_c2s":"0.0", 17 | "leo_fee_disc_c2f":"0.0", 18 | "leo_fee_disc_c2d":"0.0", 19 | "leo_fee_disc_abs_c2c":"0.0", 20 | "leo_fee_disc_abs_c2s":"0.0", 21 | "leo_fee_disc_abs_c2f":"0.0", 22 | "leo_fee_disc_abs_c2d":"0.0", 23 | "leo_fee_maker_disc_abs_c2d":"0.0", 24 | "maker_fees":"0.1", 25 | "taker_fees":"0.2", 26 | "fees":[ 27 | {"pairs":"BTC", "maker_fees":"0.1", "taker_fees":"0.2"}, 28 | {"pairs":"ETH", "maker_fees":"0.1", "taker_fees":"0.2"}, 29 | {"pairs":"IOT", "maker_fees":"0.1", "taker_fees":"0.2"}, 30 | {"pairs":"XRP", "maker_fees":"0.1", "taker_fees":"0.2"}, 31 | {"pairs":"REP", "maker_fees":"0.1", "taker_fees":"0.2"}, 32 | {"pairs":"GRG", "maker_fees":"0.1", "taker_fees":"0.2"}, 33 | {"pairs":"ZRX", "maker_fees":"0.1", "taker_fees":"0.2"}, 34 | {"pairs":"MLN", "maker_fees":"0.1", "taker_fees":"0.2"}, 35 | {"pairs":"SAN", "maker_fees":"0.1", "taker_fees":"0.2"}, 36 | {"pairs":"AMP", "maker_fees":"0.1", "taker_fees":"0.2"}, 37 | {"pairs":"DUSK", "maker_fees":"0.1", "taker_fees":"0.2"}, 38 | {"pairs":"LNX", "maker_fees":"0.1", "taker_fees":"0.2"}, 39 | {"pairs":"EOS", "maker_fees":"0.1", "taker_fees":"0.2"}, 40 | {"pairs":"TESTBTC", "maker_fees":"0.1", "taker_fees":"0.2"}, 41 | {"pairs":"TESTUSDT", "maker_fees":"0.1", "taker_fees":"0.2"}, 42 | {"pairs":"TESTUSD", "maker_fees":"0.1", "taker_fees":"0.2"}, 43 | {"pairs":"ETH2P", "maker_fees":"0.1", "taker_fees":"0.2"} 44 | ] 45 | } 46 | ]', 47 | headers: { 'Content-Type' => 'application/json' } 48 | ) 49 | 50 | # Stub the account fees request 51 | stub_request(:post, "https://api.bitfinex.com/v1/account_fees") 52 | .with(body: '{}') 53 | .to_return( 54 | status: 200, 55 | body: '{"withdraw":{"BTC":"0.000001","ETH":"0.0","IOT":"0.0","XRP":"0.0","GRG":"0.025367","ZRX":"0.00000249","MLN":"0.00036065","SAN":"33.819","AMP":"5.3018","DUSK":"0.08371","LNX":"0.000001","EOS":"0.0","TESTBTC":"0.0","TESTUSDT":"0.0","TESTUSD":"0.0","EXO":"0.0","BMN":"0.0"}}', 56 | headers: { 'Content-Type' => 'application/json' } 57 | ) 58 | end 59 | 60 | let(:client) do 61 | Bitfinex::RESTv1.new({ 62 | api_key: 'dummy_api_key', 63 | api_secret: 'dummy_api_secret', 64 | url: 'https://api.bitfinex.com', 65 | }) 66 | end 67 | 68 | it 'fetches account info' do 69 | response = client.account_info 70 | expect(response).not_to be_nil 71 | expect(response).to be_a(Array) 72 | expect(response.first['maker_fees']).to eq('0.1') 73 | expect(response.first['taker_fees']).to eq('0.2') 74 | expect(response.first['fees']).to be_a(Array) 75 | expect(response.first['fees'].first['pairs']).to eq('BTC') 76 | end 77 | 78 | it 'fetches fees' do 79 | response = client.fees 80 | expect(response).not_to be_nil 81 | expect(response).to be_a(Hash) 82 | expect(response['withdraw']['BTC']).to eq('0.000001') 83 | expect(response['withdraw']['ETH']).to eq('0.0') 84 | expect(response['withdraw']['GRG']).to eq('0.025367') 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /spec/integration/v1_proxy_account_info.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'webmock/rspec' 3 | require_relative '../../lib/bitfinex' 4 | 5 | RSpec.describe 'Bitfinex::RESTv1 Proxy Integration' do 6 | before do 7 | stub_request(:post, "https://api.bitfinex.com/v1/account_infos") 8 | .with(body: '{}') 9 | .to_return( 10 | status: 200, 11 | body: '[{"maker_fees":"0.1","taker_fees":"0.2"}]', 12 | headers: { 'Content-Type' => 'application/json' } 13 | ) 14 | end 15 | 16 | let(:client) do 17 | Bitfinex::RESTv1.new({ 18 | api_key: 'dummy_api_key', 19 | api_secret: 'dummy_api_secret', 20 | url: 'https://api.bitfinex.com', 21 | proxy: 'http://proxy.example.com:8080' 22 | }) 23 | end 24 | 25 | it 'fetches account info through proxy' do 26 | response = client.account_info 27 | expect(response).not_to be_nil 28 | expect(response).to be_a(Array) 29 | expect(response.first['maker_fees']).to eq('0.1') 30 | expect(response.first['taker_fees']).to eq('0.2') 31 | 32 | # Verify that the request was made through the proxy 33 | expect(WebMock).to have_requested(:post, "https://api.bitfinex.com/v1/account_infos") 34 | .with(headers: { 'Proxy-Authorization' => 'Basic ' + Base64.encode64('proxy_user:proxy_pass').strip }) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/integration/v2_orders_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'webmock/rspec' 3 | require_relative '../../lib/bitfinex' 4 | 5 | RSpec.describe 'Bitfinex::RESTv2 Integration' do 6 | before do 7 | stub_request(:post, "https://api.bitfinex.com/v2/auth/r/orders") 8 | .with(body: '{}') 9 | .to_return( 10 | status: 200, 11 | body: '[{"id":12345,"symbol":"tBTCUSD","amount":"1.0"}]', 12 | headers: { 'Content-Type' => 'application/json' } 13 | ) 14 | end 15 | 16 | let(:client) do 17 | Bitfinex::RESTv2.new({ 18 | api_key: 'dummy_api_key', 19 | api_secret: 'dummy_api_secret', 20 | url: 'https://api.bitfinex.com', 21 | }) 22 | end 23 | 24 | it 'fetches orders' do 25 | response = client.orders 26 | expect(response).not_to be_nil 27 | expect(response).to be_a(Array) 28 | expect(response.first['id']).to eq(12345) 29 | expect(response.first['symbol']).to eq('tBTCUSD') 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/integration/ws_connect_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'webmock/rspec' 3 | require 'faye/websocket' 4 | require 'eventmachine' 5 | require_relative '../../lib/bitfinex' 6 | 7 | RSpec.describe 'Bitfinex::WSv2 Integration' do 8 | let(:client) do 9 | Bitfinex::WSv2.new({ 10 | url: 'ws://localhost:8080/ws/2', 11 | }) 12 | end 13 | 14 | before do 15 | stub_request(:any, 'ws://localhost:8080/ws/2').to_return(body: '', status: 200) 16 | end 17 | 18 | it 'connects to WebSocket' do 19 | EM.run { 20 | mock_server = EM.start_server('0.0.0.0', 8080) do |conn| 21 | conn.instance_eval do 22 | def post_init 23 | @ws = Faye::WebSocket::Server.new(self) 24 | @ws.on :open do |event| 25 | @ws.send('{"event":"info","version":2,"serverId":"60916660-db0f-4519-b4cd-be8d4c745b24","platform":{"status":1}}') 26 | end 27 | @ws.on :message do |event| 28 | @ws.send('{"event":"subscribed","channel":"ticker","pair":"tBTCUSD"}') 29 | end 30 | @ws.on :close do |event| 31 | close_connection 32 | EM.stop 33 | end 34 | end 35 | 36 | def receive_data(data) 37 | @ws.receive(data) 38 | end 39 | end 40 | end 41 | 42 | expect { client.open! }.not_to raise_error 43 | EM.stop_server(mock_server) 44 | EM.stop 45 | } 46 | end 47 | 48 | it 'subscribes to a channel' do 49 | EM.run { 50 | mock_server = EM.start_server('0.0.0.0', 8080) do |conn| 51 | conn.instance_eval do 52 | def post_init 53 | @ws = Faye::WebSocket::Server.new(self) 54 | @ws.on :open do |event| 55 | @ws.send('{"event":"info","version":2,"serverId":"60916660-db0f-4519-b4cd-be8d4c745b24","platform":{"status":1}}') 56 | end 57 | @ws.on :message do |event| 58 | @ws.send('{"event":"subscribed","channel":"ticker","pair":"tBTCUSD"}') 59 | end 60 | @ws.on :close do |event| 61 | close_connection 62 | EM.stop 63 | end 64 | end 65 | 66 | def receive_data(data) 67 | @ws.receive(data) 68 | end 69 | end 70 | end 71 | 72 | client.on(:open) do 73 | client.subscribe_order_book('tBTCUSD', 'R0', '25') 74 | end 75 | 76 | expect { client.open! }.not_to raise_error 77 | EM.stop_server(mock_server) 78 | EM.stop 79 | } 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rspec --init` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # The generated `.rspec` file contains `--require spec_helper` which will cause 4 | # this file to always be loaded, without a need to explicitly require it in any 5 | # files. 6 | # 7 | # Given that it is always loaded, you are encouraged to keep this file as 8 | # light-weight as possible. Requiring heavyweight dependencies from this file 9 | # will add to the boot time of your test suite on EVERY test run, even for an 10 | # individual file that may not need all of that loaded. Instead, consider making 11 | # a separate helper file that requires the additional dependencies and performs 12 | # the additional setup, and require it from the spec files that actually need 13 | # it. 14 | # 15 | # See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 16 | require 'simplecov' 17 | SimpleCov.start do 18 | add_filter '/spec/' 19 | end 20 | 21 | require 'webmock/rspec' 22 | WebMock.disable_net_connect!(allow_localhost: true) 23 | 24 | RSpec.configure do |config| 25 | # rspec-expectations config goes here. You can use an alternate 26 | # assertion/expectation library such as wrong or the stdlib/minitest 27 | # assertions if you prefer. 28 | config.expect_with :rspec do |expectations| 29 | # This option will default to `true` in RSpec 4. It makes the `description` 30 | # and `failure_message` of custom matchers include text for helper methods 31 | # defined using `chain`, e.g.: 32 | # be_bigger_than(2).and_smaller_than(4).description 33 | # # => "be bigger than 2 and smaller than 4" 34 | # ...rather than: 35 | # # => "be bigger than 2" 36 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 37 | end 38 | 39 | # rspec-mocks config goes here. You can use an alternate test double 40 | # library (such as bogus or mocha) by changing the `mock_with` option here. 41 | config.mock_with :rspec do |mocks| 42 | # Prevents you from mocking or stubbing a method that does not exist on 43 | # a real object. This is generally recommended, and will default to 44 | # `true` in RSpec 4. 45 | mocks.verify_partial_doubles = true 46 | end 47 | 48 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 49 | # have no way to turn it off -- the option exists only for backwards 50 | # compatibility in RSpec 3). It causes shared context metadata to be 51 | # inherited by the metadata hash of host groups and examples, rather than 52 | # triggering implicit auto-inclusion in groups with matching metadata. 53 | config.shared_context_metadata_behavior = :apply_to_host_groups 54 | 55 | # The settings below are suggested to provide a good initial experience 56 | # with RSpec, but feel free to customize to your heart's content. 57 | =begin 58 | # This allows you to limit a spec run to individual examples or groups 59 | # you care about by tagging them with `:focus` metadata. When nothing 60 | # is tagged with `:focus`, all examples get run. RSpec also provides 61 | # aliases for `it`, `describe`, and `context` that include `:focus` 62 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 63 | config.filter_run_when_matching :focus 64 | 65 | # Allows RSpec to persist some state between runs in order to support 66 | # the `--only-failures` and `--next-failure` CLI options. We recommend 67 | # you configure your source control system to ignore this file. 68 | config.example_status_persistence_file_path = "spec/examples.txt" 69 | 70 | # Limits the available syntax to the non-monkey patched syntax that is 71 | # recommended. For more details, see: 72 | # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/ 73 | config.disable_monkey_patching! 74 | 75 | # This setting enables warnings. It's recommended, but in some cases may 76 | # be too noisy due to issues in dependencies. 77 | config.warnings = true 78 | 79 | # Many RSpec users commonly either run the entire suite or an individual 80 | # file, and it's useful to allow more verbose output when running an 81 | # individual spec file. 82 | if config.files_to_run.one? 83 | # Use the documentation formatter for detailed output, 84 | # unless a formatter has already been configured 85 | # (e.g. via a command-line flag). 86 | config.default_formatter = "doc" 87 | end 88 | 89 | # Print the 10 slowest examples and example groups at the 90 | # end of the spec run, to help surface which specs are running 91 | # particularly slow. 92 | config.profile_examples = 10 93 | 94 | # Run specs in random order to surface order dependencies. If you find an 95 | # order dependency and want to debug it, you can fix the order by providing 96 | # the seed, which is printed after each run. 97 | # --seed 1234 98 | config.order = :random 99 | 100 | # Seed global randomization in this process using the `--seed` CLI option. 101 | # Setting this allows you to use `--seed` to deterministically reproduce 102 | # test failures related to randomization by passing the same `--seed` value 103 | # as the one that triggered the failure. 104 | Kernel.srand config.seed 105 | =end 106 | end 107 | --------------------------------------------------------------------------------