diff --git a/README.md b/README.md index 9384cbe74..c8725a1a6 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ Or install it yourself as: | ----------------- | ------- | ---------- | ------- | ------- | ----------- | | Allcoin | Y | Y | | | User-Defined| | ANX | Y | | | | User-Defined| -| Bit-Z | Y | | | | Y -| Binance | | | | | | +| Bit-Z | Y | | | | Y | +| Binance | Y | Y | | | User-Defined| | Bitbay | Y | | | | User-Defined| | Bitcoin Indonesia | Y | | | | User-Defined| | Bitfinex | Y | | | | Y | diff --git a/lib/cryptoexchange/exchanges/binance/binance.yml b/lib/cryptoexchange/exchanges/binance/binance.yml new file mode 100644 index 000000000..56f8a6236 --- /dev/null +++ b/lib/cryptoexchange/exchanges/binance/binance.yml @@ -0,0 +1,147 @@ +:pairs: + - :base: ETH + :target: BTC + - :base: LTC + :target: BTC + - :base: BNB + :target: BTC + - :base: NEO + :target: BTC + - :base: QTUM + :target: ETH + - :base: EOS + :target: ETH + - :base: SNT + :target: ETH + - :base: BNT + :target: ETH + - :base: BCC + :target: BTC + - :base: GAS + :target: BTC + - :base: BNB + :target: ETH + - :base: BTM + :target: ETH + - :base: HCC + :target: BTC + - :base: BTC + :target: USDT + - :base: ETH + :target: USDT + - :base: HSR + :target: BTC + - :base: OAX + :target: ETH + - :base: DNT + :target: ETH + - :base: MCO + :target: ETH + - :base: ICN + :target: ETH + - :base: ELC + :target: BTC + - :base: MCO + :target: BTC + - :base: WTC + :target: BTC + - :base: WTC + :target: ETH + - :base: LLT + :target: BTC + - :base: LRC + :target: BTC + - :base: LRC + :target: ETH + - :base: QTUM + :target: BTC + - :base: YOYO + :target: BTC + - :base: OMG + :target: BTC + - :base: OMG + :target: ETH + - :base: ZRX + :target: BTC + - :base: ZRX + :target: ETH + - :base: STRAT + :target: BTC + - :base: STRAT + :target: ETH + - :base: SNGLS + :target: BTC + - :base: SNGLS + :target: ETH + - :base: BQX + :target: BTC + - :base: BQX + :target: ETH + - :base: KNC + :target: BTC + - :base: KNC + :target: ETH + - :base: FUN + :target: BTC + - :base: FUN + :target: ETH + - :base: SNM + :target: BTC + - :base: SNM + :target: ETH + - :base: NEO + :target: ETH + - :base: IOTA + :target: BTC + - :base: IOTA + :target: ETH + - :base: LINK + :target: BTC + - :base: LINK + :target: ETH + - :base: XVG + :target: BTC + - :base: XVG + :target: ETH + - :base: CTR + :target: BTC + - :base: CTR + :target: ETH + - :base: SALT + :target: BTC + - :base: SALT + :target: ETH + - :base: MDA + :target: BTC + - :base: MDA + :target: ETH + - :base: MTL + :target: BTC + - :base: MTL + :target: ETH + - :base: SUB + :target: BTC + - :base: SUB + :target: ETH + - :base: EOS + :target: BTC + - :base: SNT + :target: BTC + - :base: ETC + :target: ETH + - :base: ETC + :target: BTC + - :base: MTH + :target: BTC + - :base: MTH + :target: ETH + - :base: ENG + :target: BTC + - :base: ENG + :target: ETH + - :base: DNT + :target: BTC + - :base: ZEC + :target: BTC + - :base: ZEC + :target: ETH diff --git a/lib/cryptoexchange/exchanges/binance/market.rb b/lib/cryptoexchange/exchanges/binance/market.rb new file mode 100644 index 000000000..dd9c426f2 --- /dev/null +++ b/lib/cryptoexchange/exchanges/binance/market.rb @@ -0,0 +1,8 @@ +module Cryptoexchange::Exchanges + module Binance + class Market + NAME = 'binance' + API_URL = 'https://www.binance.com/api/v1' + end + end +end diff --git a/lib/cryptoexchange/exchanges/binance/services/market.rb b/lib/cryptoexchange/exchanges/binance/services/market.rb new file mode 100644 index 000000000..c16e3a41f --- /dev/null +++ b/lib/cryptoexchange/exchanges/binance/services/market.rb @@ -0,0 +1,40 @@ +module Cryptoexchange::Exchanges + module Binance + module Services + class Market < Cryptoexchange::Services::Market + class << self + def supports_individual_ticker_query? + true + end + end + + def fetch(market_pair) + output = super(ticker_url(market_pair)) + adapt(output, market_pair) + end + + def ticker_url(market_pair) + base = market_pair.base + target = market_pair.target + "#{Cryptoexchange::Exchanges::Binance::Market::API_URL}/ticker/24hr?symbol=#{base}#{target}" + end + + def adapt(output, market_pair) + ticker = Cryptoexchange::Models::Ticker.new + ticker.base = market_pair.base + ticker.target = market_pair.target + ticker.market = Binance::Market::NAME + ticker.bid = NumericHelper.to_d(output['bidPrice']) + ticker.ask = NumericHelper.to_d(output['askPrice']) + ticker.last = NumericHelper.to_d(output['lastPrice']) + ticker.high = NumericHelper.to_d(output['highPrice']) + ticker.low = NumericHelper.to_d(output['lowPrice']) + ticker.volume = NumericHelper.to_d(output['volume']) + ticker.timestamp = output['closeTime'].to_i / 1000 + ticker.payload = output + ticker + end + end + end + end +end diff --git a/lib/cryptoexchange/exchanges/binance/services/order_book.rb b/lib/cryptoexchange/exchanges/binance/services/order_book.rb new file mode 100644 index 000000000..d4b266c1b --- /dev/null +++ b/lib/cryptoexchange/exchanges/binance/services/order_book.rb @@ -0,0 +1,46 @@ +module Cryptoexchange::Exchanges + module Binance + module Services + class OrderBook < Cryptoexchange::Services::Market + class << self + def supports_individual_ticker_query? + true + end + end + + def fetch(market_pair) + output = super(order_book_url(market_pair)) + adapt(output, market_pair) + end + + def order_book_url(market_pair) + base = market_pair.base + target = market_pair.target + "#{Cryptoexchange::Exchanges::Binance::Market::API_URL}/depth?symbol=#{base}#{target}" + end + + def adapt(output, market_pair) + order_book = Cryptoexchange::Models::OrderBook.new + timestamp = Time.now.to_i + + order_book.base = market_pair.base + order_book.target = market_pair.target + order_book.market = Binance::Market::NAME + order_book.asks = adapt_orders(output['asks'], timestamp) + order_book.bids = adapt_orders(output['bids'], timestamp) + order_book.timestamp = timestamp + order_book.payload = output + order_book + end + + def adapt_orders(orders, timestamp) + orders.collect do |order_entry| + Cryptoexchange::Models::Order.new(price: NumericHelper.to_d(order_entry[0]), + amount: NumericHelper.to_d(order_entry[1]), + timestamp: timestamp) + end + end + end + end + end +end diff --git a/lib/cryptoexchange/exchanges/binance/services/pairs.rb b/lib/cryptoexchange/exchanges/binance/services/pairs.rb new file mode 100644 index 000000000..1da4a4736 --- /dev/null +++ b/lib/cryptoexchange/exchanges/binance/services/pairs.rb @@ -0,0 +1,25 @@ +module Cryptoexchange::Exchanges + module Binance + module Services + class Pairs < Cryptoexchange::Services::Pairs + + def fetch + output = super + adapt(output) + end + + def adapt(output) + market_pairs = [] + output.each do |pair| + market_pairs << Cryptoexchange::Models::MarketPair.new( + base: pair[:base], + target: pair[:target], + market: Binance::Market::NAME + ) + end + market_pairs + end + end + end + end +end diff --git a/spec/cassettes/vcr_cassettes/Binance/integration_specs_fetch_order_book.yml b/spec/cassettes/vcr_cassettes/Binance/integration_specs_fetch_order_book.yml new file mode 100644 index 000000000..ed35f0d5b --- /dev/null +++ b/spec/cassettes/vcr_cassettes/Binance/integration_specs_fetch_order_book.yml @@ -0,0 +1,36 @@ +--- +http_interactions: +- request: + method: get + uri: https://www.binance.com/api/v1/depth?symbol=ETHBTC + body: + encoding: US-ASCII + string: '' + headers: + Connection: + - close + Host: + - www.binance.com + User-Agent: + - http.rb/1.0.4 + response: + status: + code: 200 + message: OK + headers: + Date: + - Mon, 16 Oct 2017 23:06:50 GMT + Content-Type: + - text/html;charset=utf-8 + Transfer-Encoding: + - chunked + Connection: + - close + Server: + - nginx + body: + encoding: UTF-8 + string: '{"lastUpdateId":16678475,"bids":[["0.05860000","8.55100000",[]],["0.05846700","9.36400000",[]],["0.05840800","3.40000000",[]],["0.05840700","1.80200000",[]],["0.05831000","0.00100000",[]],["0.05828600","0.51500000",[]],["0.05822200","5.70100000",[]],["0.05820000","1.00000000",[]],["0.05817800","0.34900000",[]],["0.05816500","0.25000000",[]],["0.05811000","9.40800000",[]],["0.05810000","1.00000000",[]],["0.05809000","2.00000000",[]],["0.05802800","0.25400000",[]],["0.05802700","0.50000000",[]],["0.05802600","0.50000000",[]],["0.05802500","0.50000000",[]],["0.05802400","0.50000000",[]],["0.05802300","0.50000000",[]],["0.05802200","0.50000000",[]],["0.05802100","0.50000000",[]],["0.05802000","0.50000000",[]],["0.05801900","0.50000000",[]],["0.05801800","0.50000000",[]],["0.05801700","0.50000000",[]],["0.05801600","0.50000000",[]],["0.05801500","0.50000000",[]],["0.05801400","0.70000000",[]],["0.05801300","0.50000000",[]],["0.05801200","0.50000000",[]],["0.05801100","0.50000000",[]],["0.05801000","0.50000000",[]],["0.05800900","0.50000000",[]],["0.05800800","0.50000000",[]],["0.05800700","0.50000000",[]],["0.05800600","0.50000000",[]],["0.05800500","0.50000000",[]],["0.05800400","0.50000000",[]],["0.05800300","0.50000000",[]],["0.05800200","0.50000000",[]],["0.05800100","0.54000000",[]],["0.05800000","17.54600000",[]],["0.05778300","2.39000000",[]],["0.05778100","0.16800000",[]],["0.05772100","1.03900000",[]],["0.05762900","0.20000000",[]],["0.05762800","51.55600000",[]],["0.05761300","0.71800000",[]],["0.05758700","5.92700000",[]],["0.05750000","35.75500000",[]],["0.05741300","4.86500000",[]],["0.05735000","0.80200000",[]],["0.05725600","2.25000000",[]],["0.05716500","2.70100000",[]],["0.05710300","8.50000000",[]],["0.05700000","20.02000000",[]],["0.05689900","5.40100000",[]],["0.05687000","1.58300000",[]],["0.05670700","6.90100000",[]],["0.05663300","0.12000000",[]],["0.05661300","7.15100000",[]],["0.05655100","12.32000000",[]],["0.05650000","3.59700000",[]],["0.05648800","1.42300000",[]],["0.05636200","5.75000000",[]],["0.05626700","15.00000000",[]],["0.05619800","7.70100000",[]],["0.05609800","0.12000000",[]],["0.05609100","5.05000000",[]],["0.05600000","7.48200000",[]],["0.05586400","8.70000000",[]],["0.05565800","5.75000000",[]],["0.05551700","5.75000000",[]],["0.05550000","20.00000000",[]],["0.05533800","8.40100000",[]],["0.05531600","0.12000000",[]],["0.05524900","3.15000000",[]],["0.05500800","1.65000000",[]],["0.05500000","1.81800000",[]],["0.05482800","1.30100000",[]],["0.05473500","5.24900000",[]],["0.05462400","4.90100000",[]],["0.05459500","4.98900000",[]],["0.05444400","5.25000000",[]],["0.05430500","0.12000000",[]],["0.05426700","20.00000000",[]],["0.05422100","5.05000000",[]],["0.05403600","1.45000000",[]],["0.05400000","2.05100000",[]],["0.05383600","4.65100000",[]],["0.05362400","2.80000000",[]],["0.05349900","3.30000000",[]],["0.05336300","4.80000000",[]],["0.05319000","8.80100000",[]],["0.05310000","3.00000000",[]],["0.05308900","3.25000000",[]],["0.05306400","0.08600000",[]],["0.05300000","49.23600000",[]],["0.05251000","3.00000000",[]],["0.05234200","20.00000000",[]]],"asks":[["0.05868000","0.02000000",[]],["0.05868900","24.45200000",[]],["0.05870100","9.40900000",[]],["0.05873600","2.50000000",[]],["0.05881700","0.17000000",[]],["0.05889700","19.95000000",[]],["0.05889800","12.87900000",[]],["0.05891300","14.55800000",[]],["0.05892000","0.02500000",[]],["0.05892100","7.54800000",[]],["0.05892900","5.37400000",[]],["0.05911600","9.00000000",[]],["0.05929700","3.84700000",[]],["0.05948600","2.35100000",[]],["0.05957300","1.00700000",[]],["0.05959000","0.22100000",[]],["0.05961700","1.35100000",[]],["0.05971500","0.86300000",[]],["0.05972200","1.00000000",[]],["0.05978300","18.63600000",[]],["0.05980000","5.85900000",[]],["0.05980600","70.00000000",[]],["0.05992800","2.49600000",[]],["0.06000000","41.39400000",[]],["0.06009700","5.15100000",[]],["0.06012100","1.50000000",[]],["0.06012200","1.00000000",[]],["0.06013300","0.25200000",[]],["0.06016100","1.00000000",[]],["0.06030000","2.45100000",[]],["0.06030700","0.94600000",[]],["0.06035000","76.00000000",[]],["0.06035900","0.25000000",[]],["0.06041700","2.00000000",[]],["0.06050000","8.50000000",[]],["0.06050300","6.30000000",[]],["0.06055000","1.47600000",[]],["0.06060000","1.65700000",[]],["0.06064500","0.49800000",[]],["0.06064900","0.17700000",[]],["0.06069800","7.35000000",[]],["0.06078000","6.00000000",[]],["0.06083100","1.09800000",[]],["0.06088800","4.83700000",[]],["0.06091800","6.95100000",[]],["0.06100000","20.76300000",[]],["0.06110700","4.64100000",[]],["0.06120000","0.38900000",[]],["0.06120700","1.00000000",[]],["0.06120800","10.49000000",[]],["0.06121100","4.50000000",[]],["0.06123000","6.19300000",[]],["0.06131600","2.74900000",[]],["0.06145000","1.00000000",[]],["0.06150000","32.77700000",[]],["0.06152400","7.45100000",[]],["0.06152800","3.35400000",[]],["0.06155000","7.28100000",[]],["0.06160000","12.34000000",[]],["0.06167000","0.09900000",[]],["0.06170000","0.75100000",[]],["0.06172000","6.00000000",[]],["0.06176800","0.32500000",[]],["0.06180000","0.16000000",[]],["0.06181800","8.80100000",[]],["0.06187700","0.92600000",[]],["0.06188000","1.00000000",[]],["0.06189000","30.70000000",[]],["0.06190000","1.00000000",[]],["0.06190700","9.49800000",[]],["0.06192300","2.96900000",[]],["0.06193000","24.95800000",[]],["0.06193600","0.38500000",[]],["0.06197000","0.10000000",[]],["0.06198500","0.65100000",[]],["0.06199900","1.00000000",[]],["0.06200000","35.97700000",[]],["0.06210000","2.64000000",[]],["0.06218000","1.00000000",[]],["0.06219000","1.14600000",[]],["0.06219100","1.00000000",[]],["0.06225900","5.20100000",[]],["0.06228100","1.00000000",[]],["0.06240000","2.64000000",[]],["0.06246000","9.71700000",[]],["0.06248900","2.00000000",[]],["0.06250000","6.02000000",[]],["0.06257800","0.11600000",[]],["0.06260000","1.70300000",[]],["0.06269100","10.00000000",[]],["0.06270000","1.16000000",[]],["0.06300000","2.23000000",[]],["0.06307000","1.00000000",[]],["0.06310000","0.02000000",[]],["0.06316600","14.12000000",[]],["0.06320000","0.02000000",[]],["0.06330000","2.32500000",[]],["0.06339100","2.97700000",[]],["0.06340000","0.02000000",[]],["0.06341900","1.50100000",[]]]}' + http_version: + recorded_at: Mon, 16 Oct 2017 23:03:28 GMT +recorded_with: VCR 3.0.3 diff --git a/spec/cassettes/vcr_cassettes/Binance/integration_specs_fetch_ticker.yml b/spec/cassettes/vcr_cassettes/Binance/integration_specs_fetch_ticker.yml new file mode 100644 index 000000000..dabfa0842 --- /dev/null +++ b/spec/cassettes/vcr_cassettes/Binance/integration_specs_fetch_ticker.yml @@ -0,0 +1,36 @@ +--- +http_interactions: +- request: + method: get + uri: https://www.binance.com/api/v1/ticker/24hr?symbol=ETHBTC + body: + encoding: US-ASCII + string: '' + headers: + Connection: + - close + Host: + - www.binance.com + User-Agent: + - http.rb/1.0.4 + response: + status: + code: 200 + message: OK + headers: + Date: + - Mon, 16 Oct 2017 23:06:49 GMT + Content-Type: + - text/html;charset=utf-8 + Transfer-Encoding: + - chunked + Connection: + - close + Server: + - nginx + body: + encoding: UTF-8 + string: '{"priceChange":"-0.00032200","priceChangePercent":"-0.546","weightedAvgPrice":"0.06013048","prevClosePrice":"0.05876800","lastPrice":"0.05861800","lastQty":"7.32800000","bidPrice":"0.05860000","bidQty":"8.55100000","askPrice":"0.05868000","askQty":"0.02000000","openPrice":"0.05894000","highPrice":"0.06216600","lowPrice":"0.05820900","volume":"32499.85700000","quoteVolume":"1954.23205043","openTime":1508108809488,"closeTime":1508195209488,"firstId":2109065,"lastId":2137995,"count":28928}' + http_version: + recorded_at: Mon, 16 Oct 2017 23:03:27 GMT +recorded_with: VCR 3.0.3 diff --git a/spec/exchanges/binance/integration/market_spec.rb b/spec/exchanges/binance/integration/market_spec.rb new file mode 100644 index 000000000..95d2f70bc --- /dev/null +++ b/spec/exchanges/binance/integration/market_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +RSpec.describe 'Binance integration specs' do + let(:client) { Cryptoexchange::Client.new } + let(:market) { 'binance' } + let(:btc_eur_pair) do + Cryptoexchange::Models::MarketPair.new(base: 'ETH', target: 'BTC', market: market) + end + + it 'fetch pairs' do + pairs = client.pairs(market) + expect(pairs).not_to be_empty + + pair = pairs.first + expect(pair.base).to eq 'ETH' + expect(pair.target).to eq 'BTC' + expect(pair.market).to eq market + end + + it 'fetch ticker' do + ticker = client.ticker(btc_eur_pair) + + expect(ticker.base).to eq 'ETH' + expect(ticker.target).to eq 'BTC' + expect(ticker.market).to eq market + + expect(ticker.bid).to be_a Numeric + expect(ticker.ask).to be_a Numeric + expect(ticker.volume).to be_a Numeric + expect(ticker.last).to be_a Numeric + expect(ticker.timestamp).to be_a Numeric + expect(DateTime.strptime(ticker.timestamp.to_s, '%s').year).to eq Date.today.year + expect(ticker.payload).to_not be nil + end + + it 'fetch order book' do + order_book = client.order_book(btc_eur_pair) + + expect(order_book.base).to eq 'ETH' + expect(order_book.target).to eq 'BTC' + expect(order_book.market).to eq market + + expect(order_book.asks).to_not be_empty + expect(order_book.bids).to_not be_empty + expect(order_book.asks.first.price).to_not be_nil + expect(order_book.bids.first.amount).to_not be_nil + expect(order_book.bids.first.timestamp).to_not be_nil + expect(order_book.asks.count).to be > 10 + expect(order_book.bids.count).to be > 10 + expect(order_book.timestamp).to be_a Numeric + expect(order_book.payload).to_not be nil + end +end diff --git a/spec/exchanges/binance/market_spec.rb b/spec/exchanges/binance/market_spec.rb new file mode 100644 index 000000000..8fe0b24c7 --- /dev/null +++ b/spec/exchanges/binance/market_spec.rb @@ -0,0 +1,6 @@ +require 'spec_helper' + +RSpec.describe Cryptoexchange::Exchanges::Binance::Market do + it { expect(described_class::NAME).to eq 'binance' } + it { expect(described_class::API_URL).to eq 'https://www.binance.com/api/v1' } +end diff --git a/spec/exchanges/binance/services/pairs_spec.rb b/spec/exchanges/binance/services/pairs_spec.rb new file mode 100644 index 000000000..3c7381db8 --- /dev/null +++ b/spec/exchanges/binance/services/pairs_spec.rb @@ -0,0 +1,4 @@ +require 'spec_helper' + +RSpec.describe Cryptoexchange::Exchanges::Binance::Services::Pairs do +end