coinsync 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE.txt +21 -0
- data/README.md +255 -0
- data/bin/coinsync +78 -0
- data/doc/importers.md +201 -0
- data/lib/coinsync.rb +18 -0
- data/lib/coinsync/balance.rb +19 -0
- data/lib/coinsync/balance_task.rb +65 -0
- data/lib/coinsync/build_task.rb +43 -0
- data/lib/coinsync/builder.rb +32 -0
- data/lib/coinsync/config.rb +94 -0
- data/lib/coinsync/crypto_classifier.rb +27 -0
- data/lib/coinsync/currencies.rb +35 -0
- data/lib/coinsync/currency_converter.rb +65 -0
- data/lib/coinsync/currency_converters/all.rb +1 -0
- data/lib/coinsync/currency_converters/base.rb +46 -0
- data/lib/coinsync/currency_converters/cache.rb +34 -0
- data/lib/coinsync/currency_converters/fixer.rb +36 -0
- data/lib/coinsync/currency_converters/nbp.rb +38 -0
- data/lib/coinsync/formatter.rb +44 -0
- data/lib/coinsync/import_task.rb +37 -0
- data/lib/coinsync/importers/all.rb +1 -0
- data/lib/coinsync/importers/ark_voting.rb +121 -0
- data/lib/coinsync/importers/base.rb +35 -0
- data/lib/coinsync/importers/binance_api.rb +210 -0
- data/lib/coinsync/importers/bitbay20.rb +144 -0
- data/lib/coinsync/importers/bitbay_api.rb +224 -0
- data/lib/coinsync/importers/bitcurex.rb +71 -0
- data/lib/coinsync/importers/bittrex_api.rb +81 -0
- data/lib/coinsync/importers/bittrex_csv.rb +75 -0
- data/lib/coinsync/importers/changelly.rb +57 -0
- data/lib/coinsync/importers/circle.rb +58 -0
- data/lib/coinsync/importers/default.rb +90 -0
- data/lib/coinsync/importers/etherdelta.rb +93 -0
- data/lib/coinsync/importers/kraken_api.rb +134 -0
- data/lib/coinsync/importers/kraken_common.rb +137 -0
- data/lib/coinsync/importers/kraken_csv.rb +28 -0
- data/lib/coinsync/importers/kucoin_api.rb +172 -0
- data/lib/coinsync/importers/lisk_voting.rb +110 -0
- data/lib/coinsync/outputs/all.rb +1 -0
- data/lib/coinsync/outputs/base.rb +32 -0
- data/lib/coinsync/outputs/list.rb +123 -0
- data/lib/coinsync/outputs/raw.rb +45 -0
- data/lib/coinsync/outputs/summary.rb +48 -0
- data/lib/coinsync/request.rb +31 -0
- data/lib/coinsync/run_command_task.rb +20 -0
- data/lib/coinsync/source.rb +43 -0
- data/lib/coinsync/source_filter.rb +10 -0
- data/lib/coinsync/table_printer.rb +29 -0
- data/lib/coinsync/transaction.rb +125 -0
- data/lib/coinsync/version.rb +3 -0
- metadata +95 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module CoinSync
|
5
|
+
module CurrencyConverters
|
6
|
+
class Cache
|
7
|
+
def initialize(name)
|
8
|
+
@name = name
|
9
|
+
@filename = "caches/#{name}.json"
|
10
|
+
|
11
|
+
if File.exist?(@filename)
|
12
|
+
@rates = JSON.parse(File.read(@filename))
|
13
|
+
else
|
14
|
+
@rates = {}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](from, to, date)
|
19
|
+
@rates["#{from.code}:#{to.code}"] ||= {}
|
20
|
+
@rates["#{from.code}:#{to.code}"][date.to_s]
|
21
|
+
end
|
22
|
+
|
23
|
+
def []=(from, to, date, amount)
|
24
|
+
@rates["#{from.code}:#{to.code}"] ||= {}
|
25
|
+
@rates["#{from.code}:#{to.code}"][date.to_s] = amount
|
26
|
+
end
|
27
|
+
|
28
|
+
def save
|
29
|
+
FileUtils.mkdir_p(File.dirname(@filename))
|
30
|
+
File.write(@filename, JSON.generate(@rates))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
require_relative 'base'
|
5
|
+
require_relative '../request'
|
6
|
+
|
7
|
+
module CoinSync
|
8
|
+
module CurrencyConverters
|
9
|
+
class Fixer < Base
|
10
|
+
register_converter :fixer
|
11
|
+
|
12
|
+
BASE_URL = "https://api.fixer.io"
|
13
|
+
|
14
|
+
class Exception < StandardError; end
|
15
|
+
class NoDataException < Exception; end
|
16
|
+
class BadRequestException < Exception; end
|
17
|
+
|
18
|
+
def fetch_conversion_rate(from:, to:, date:)
|
19
|
+
response = Request.get("#{BASE_URL}/#{date}?base=#{from.code}")
|
20
|
+
|
21
|
+
case response
|
22
|
+
when Net::HTTPSuccess
|
23
|
+
json = JSON.parse(response.body)
|
24
|
+
rate = json['rates'][to.code.upcase]
|
25
|
+
raise NoDataException.new("No exchange rate found for #{to.code.upcase}") if rate.nil?
|
26
|
+
|
27
|
+
return rate
|
28
|
+
when Net::HTTPBadRequest
|
29
|
+
raise BadRequestException.new(response)
|
30
|
+
else
|
31
|
+
raise Exception.new(response)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
require_relative 'base'
|
5
|
+
require_relative '../request'
|
6
|
+
|
7
|
+
module CoinSync
|
8
|
+
module CurrencyConverters
|
9
|
+
class NBP < Base
|
10
|
+
register_converter :nbp
|
11
|
+
|
12
|
+
BASE_URL = "https://api.nbp.pl/api"
|
13
|
+
|
14
|
+
class Exception < StandardError; end
|
15
|
+
class NoDataException < Exception; end
|
16
|
+
class BadRequestException < Exception; end
|
17
|
+
|
18
|
+
def fetch_conversion_rate(from:, to:, date:)
|
19
|
+
raise "Only conversions to PLN are supported" if to.code != 'PLN'
|
20
|
+
|
21
|
+
response = Request.get("#{BASE_URL}/exchangerates/rates/a/#{from.code}/#{date - 8}/#{date - 1}/?format=json")
|
22
|
+
|
23
|
+
case response
|
24
|
+
when Net::HTTPSuccess
|
25
|
+
json = JSON.parse(response.body)
|
26
|
+
rate = json['rates'] && json['rates'].last && json['rates'].last['mid']
|
27
|
+
raise NoDataException.new("No exchange rate found for #{from.code.upcase}") if rate.nil?
|
28
|
+
|
29
|
+
return rate
|
30
|
+
when Net::HTTPBadRequest
|
31
|
+
raise BadRequestException.new(response)
|
32
|
+
else
|
33
|
+
raise Exception.new(response)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
|
3
|
+
module CoinSync
|
4
|
+
class Formatter
|
5
|
+
def initialize(config)
|
6
|
+
@config = config
|
7
|
+
@decimal_separator = config.custom_decimal_separator
|
8
|
+
end
|
9
|
+
|
10
|
+
def format_decimal(value, precision: nil)
|
11
|
+
v = precision ? value.round(precision) : value
|
12
|
+
s = v.to_s('F').gsub(/\.0$/, '')
|
13
|
+
s = s.gsub(/\./, @decimal_separator) if @decimal_separator
|
14
|
+
s
|
15
|
+
end
|
16
|
+
|
17
|
+
def format_float(value, precision:)
|
18
|
+
s = sprintf("%.#{precision}f", value)
|
19
|
+
s = s.gsub(/\./, @decimal_separator) if @decimal_separator
|
20
|
+
s
|
21
|
+
end
|
22
|
+
|
23
|
+
def format_fiat(amount)
|
24
|
+
format_float(amount, precision: 2)
|
25
|
+
end
|
26
|
+
|
27
|
+
def format_fiat_price(amount)
|
28
|
+
format_float(amount, precision: (amount < 10 ? 4 : 2))
|
29
|
+
end
|
30
|
+
|
31
|
+
def format_crypto(amount)
|
32
|
+
format_decimal(amount, precision: 8)
|
33
|
+
end
|
34
|
+
|
35
|
+
def format_time(time)
|
36
|
+
time.strftime(@config.time_format || '%Y-%m-%d %H:%M:%S')
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse_decimal(string)
|
40
|
+
string = string.gsub(@decimal_separator, '.') if @decimal_separator
|
41
|
+
BigDecimal.new(string)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
require_relative 'importers/all'
|
4
|
+
|
5
|
+
module CoinSync
|
6
|
+
class ImportTask
|
7
|
+
def initialize(config)
|
8
|
+
@config = config
|
9
|
+
end
|
10
|
+
|
11
|
+
def run(selected = nil, except = nil)
|
12
|
+
@config.filtered_sources(selected, except).each do |key, source|
|
13
|
+
importer = source.importer
|
14
|
+
filename = source.filename
|
15
|
+
|
16
|
+
if importer.respond_to?(:can_import?)
|
17
|
+
if importer.can_import?(:transactions)
|
18
|
+
if filename.nil?
|
19
|
+
raise "No filename specified for '#{key}', please add a 'file' parameter."
|
20
|
+
end
|
21
|
+
|
22
|
+
print "[#{key}] Importing transactions... "
|
23
|
+
|
24
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
25
|
+
importer.import_transactions(filename)
|
26
|
+
|
27
|
+
puts "√"
|
28
|
+
else
|
29
|
+
puts "[#{key}] Skipping import"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
puts "Done."
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.join(File.dirname(__FILE__), '*.rb')].each { |f| require(f) }
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'json'
|
3
|
+
require 'net/http'
|
4
|
+
require 'time'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
require_relative 'base'
|
8
|
+
require_relative '../balance'
|
9
|
+
require_relative '../currencies'
|
10
|
+
require_relative '../request'
|
11
|
+
require_relative '../transaction'
|
12
|
+
|
13
|
+
module CoinSync
|
14
|
+
module Importers
|
15
|
+
class ArkVoting < Base
|
16
|
+
register_importer :ark_voting
|
17
|
+
|
18
|
+
BASE_URL = "https://explorer.dafty.net/api"
|
19
|
+
EPOCH_TIME = Time.parse('2017-03-21 13:00 UTC')
|
20
|
+
ARK = CryptoCurrency.new('ARK')
|
21
|
+
|
22
|
+
class HistoryEntry
|
23
|
+
attr_accessor :timestamp, :amount
|
24
|
+
|
25
|
+
def initialize(hash)
|
26
|
+
@timestamp = EPOCH_TIME + hash['timestamp']
|
27
|
+
@amount = BigDecimal.new(hash['amount']) / 100_000_000
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(config, params = {})
|
32
|
+
super
|
33
|
+
@address = params['address']
|
34
|
+
end
|
35
|
+
|
36
|
+
def can_import?(type)
|
37
|
+
@address && [:balances, :transactions].include?(type)
|
38
|
+
end
|
39
|
+
|
40
|
+
def import_transactions(filename)
|
41
|
+
offset = 0
|
42
|
+
limit = 50
|
43
|
+
transactions = []
|
44
|
+
|
45
|
+
loop do
|
46
|
+
response = make_request('/getTransactionsByAddress', address: @address, limit: limit, offset: offset)
|
47
|
+
|
48
|
+
case response
|
49
|
+
when Net::HTTPSuccess
|
50
|
+
json = JSON.parse(response.body)
|
51
|
+
|
52
|
+
if json['success'] != true || !json['transactions']
|
53
|
+
raise "Ark importer: Invalid response: #{response.body}"
|
54
|
+
end
|
55
|
+
|
56
|
+
break if json['transactions'].empty?
|
57
|
+
|
58
|
+
rewards = json['transactions'].select { |tx| tx['senderDelegate'] }
|
59
|
+
transactions.concat(rewards)
|
60
|
+
|
61
|
+
offset += limit
|
62
|
+
when Net::HTTPBadRequest
|
63
|
+
raise "Ark importer: Bad request: #{response}"
|
64
|
+
else
|
65
|
+
raise "Ark importer: Bad response: #{response}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
File.write(filename, JSON.pretty_generate(transactions) + "\n")
|
70
|
+
end
|
71
|
+
|
72
|
+
def import_balances
|
73
|
+
response = make_request('/getAccount', address: @address)
|
74
|
+
|
75
|
+
case response
|
76
|
+
when Net::HTTPSuccess
|
77
|
+
json = JSON.parse(response.body)
|
78
|
+
|
79
|
+
if json['success'] != true || !json['balance']
|
80
|
+
raise "Ark importer: Invalid response: #{response.body}"
|
81
|
+
end
|
82
|
+
|
83
|
+
[Balance.new(ARK, available: BigDecimal.new(json['balance']) / 100_000_000)]
|
84
|
+
when Net::HTTPBadRequest
|
85
|
+
raise "Ark importer: Bad request: #{response}"
|
86
|
+
else
|
87
|
+
raise "Ark importer: Bad response: #{response}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def read_transaction_list(source)
|
92
|
+
json = JSON.parse(source.read)
|
93
|
+
transactions = []
|
94
|
+
|
95
|
+
json.each do |hash|
|
96
|
+
entry = HistoryEntry.new(hash)
|
97
|
+
|
98
|
+
transactions << Transaction.new(
|
99
|
+
exchange: 'Ark voting',
|
100
|
+
time: entry.timestamp,
|
101
|
+
bought_amount: entry.amount,
|
102
|
+
bought_currency: ARK,
|
103
|
+
sold_amount: BigDecimal.new(0),
|
104
|
+
sold_currency: FiatCurrency.new(nil)
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
transactions.reverse
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def make_request(path, params = {})
|
114
|
+
url = URI(BASE_URL + path)
|
115
|
+
url.query = URI.encode_www_form(params)
|
116
|
+
|
117
|
+
Request.get(url)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module CoinSync
|
2
|
+
module Importers
|
3
|
+
def self.registered
|
4
|
+
@importers ||= {}
|
5
|
+
end
|
6
|
+
|
7
|
+
class Base
|
8
|
+
def self.register_importer(key)
|
9
|
+
if Importers.registered[key]
|
10
|
+
raise "Importer has already been registered at '#{key}'"
|
11
|
+
else
|
12
|
+
Importers.registered[key] = self
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.register_commands(*commands)
|
17
|
+
@commands ||= []
|
18
|
+
@commands += commands.map(&:to_sym)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.registered_commands
|
22
|
+
@commands || []
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(config, params = {})
|
26
|
+
@config = config
|
27
|
+
@params = params
|
28
|
+
end
|
29
|
+
|
30
|
+
def can_build?
|
31
|
+
true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'json'
|
3
|
+
require 'net/http'
|
4
|
+
require 'openssl'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
require_relative 'base'
|
8
|
+
require_relative '../balance'
|
9
|
+
require_relative '../currencies'
|
10
|
+
require_relative '../request'
|
11
|
+
require_relative '../transaction'
|
12
|
+
|
13
|
+
module CoinSync
|
14
|
+
module Importers
|
15
|
+
class BinanceAPI < Base
|
16
|
+
register_importer :binance_api
|
17
|
+
register_commands :find_all_pairs
|
18
|
+
|
19
|
+
BASE_URL = "https://api.binance.com/api"
|
20
|
+
BASE_COINS = ['BTC', 'ETH', 'BNB', 'USDT']
|
21
|
+
|
22
|
+
class HistoryEntry
|
23
|
+
attr_accessor :quantity, :commission, :commission_asset, :price, :time, :buyer, :asset, :currency
|
24
|
+
|
25
|
+
def initialize(hash)
|
26
|
+
@quantity = BigDecimal.new(hash['qty'])
|
27
|
+
@commission = BigDecimal.new(hash['commission'])
|
28
|
+
@commission_asset = CryptoCurrency.new(hash['commissionAsset'])
|
29
|
+
@price = BigDecimal.new(hash['price'])
|
30
|
+
@time = Time.at(hash['time'] / 1000)
|
31
|
+
@buyer = hash['isBuyer']
|
32
|
+
|
33
|
+
@asset, @currency = parse_coins(hash['symbol'])
|
34
|
+
|
35
|
+
if (@buyer && @commission_asset != @asset) || (!@buyer && @commission_asset != @currency)
|
36
|
+
raise "Binance API: Unexpected fee: #{hash}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse_coins(symbol)
|
41
|
+
BASE_COINS.each do |coin|
|
42
|
+
if symbol.end_with?(coin)
|
43
|
+
asset = symbol.gsub(/#{coin}$/, '')
|
44
|
+
return [CryptoCurrency.new(asset), CryptoCurrency.new(coin)]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
raise "Binance API: Unexpected trade symbol: #{symbol}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize(config, params = {})
|
53
|
+
super
|
54
|
+
|
55
|
+
# only "Read Info" permission is required for the key
|
56
|
+
@api_key = params['api_key']
|
57
|
+
@secret_key = params['secret_key']
|
58
|
+
@traded_pairs = params['traded_pairs']
|
59
|
+
end
|
60
|
+
|
61
|
+
def can_import?(type)
|
62
|
+
@api_key && @secret_key && [:balances, :transactions].include?(type)
|
63
|
+
end
|
64
|
+
|
65
|
+
def import_transactions(filename)
|
66
|
+
@traded_pairs or raise "Please add a traded_pairs parameter"
|
67
|
+
|
68
|
+
transactions = []
|
69
|
+
|
70
|
+
@traded_pairs.uniq.each do |pair|
|
71
|
+
response = make_request('/v3/myTrades', limit: 500, symbol: pair) # TODO: paging
|
72
|
+
|
73
|
+
case response
|
74
|
+
when Net::HTTPSuccess
|
75
|
+
json = JSON.parse(response.body)
|
76
|
+
|
77
|
+
if json.is_a?(Hash)
|
78
|
+
raise "Binance importer: Invalid response: #{response.body}"
|
79
|
+
end
|
80
|
+
|
81
|
+
json.each { |tx| tx['symbol'] = pair }
|
82
|
+
|
83
|
+
transactions.concat(json)
|
84
|
+
when Net::HTTPBadRequest
|
85
|
+
raise "Binance importer: Bad request: #{response} (#{response.body})"
|
86
|
+
else
|
87
|
+
raise "Binance importer: Bad response: #{response}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
File.write(filename, JSON.pretty_generate(transactions.sort_by { |tx| [tx['time'], tx['id']] }))
|
92
|
+
end
|
93
|
+
|
94
|
+
def import_balances
|
95
|
+
response = make_request('/v3/account')
|
96
|
+
|
97
|
+
case response
|
98
|
+
when Net::HTTPSuccess
|
99
|
+
json = JSON.parse(response.body)
|
100
|
+
|
101
|
+
if json['code'] || !json['balances']
|
102
|
+
raise "Binance importer: Invalid response: #{response.body}"
|
103
|
+
end
|
104
|
+
|
105
|
+
return json['balances'].select { |b|
|
106
|
+
b['free'].to_f > 0 || b['locked'].to_f > 0
|
107
|
+
}.map { |b|
|
108
|
+
Balance.new(
|
109
|
+
CryptoCurrency.new(b['asset']),
|
110
|
+
available: BigDecimal.new(b['free']),
|
111
|
+
locked: BigDecimal.new(b['locked'])
|
112
|
+
)
|
113
|
+
}
|
114
|
+
when Net::HTTPBadRequest
|
115
|
+
raise "Binance importer: Bad request: #{response}"
|
116
|
+
else
|
117
|
+
raise "Binance importer: Bad response: #{response}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def find_all_pairs
|
122
|
+
info_response = make_request('/v1/exchangeInfo', {}, false)
|
123
|
+
|
124
|
+
if !info_response.is_a?(Net::HTTPSuccess)
|
125
|
+
raise "Binance importer: Bad response: #{info_response.body}"
|
126
|
+
end
|
127
|
+
|
128
|
+
info_json = JSON.parse(info_response.body)
|
129
|
+
|
130
|
+
found = []
|
131
|
+
|
132
|
+
info_json['symbols'].each do |data|
|
133
|
+
symbol = data['symbol']
|
134
|
+
trades_response = make_request('/v3/myTrades', limit: 1, symbol: symbol)
|
135
|
+
|
136
|
+
case trades_response
|
137
|
+
when Net::HTTPSuccess
|
138
|
+
trades_json = JSON.parse(trades_response.body)
|
139
|
+
|
140
|
+
if trades_json.length > 0
|
141
|
+
print '*'
|
142
|
+
found << symbol
|
143
|
+
else
|
144
|
+
print '.'
|
145
|
+
end
|
146
|
+
else
|
147
|
+
raise "Binance importer: Bad response: #{trades_response.body}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
puts
|
152
|
+
puts "Trading pairs found:"
|
153
|
+
puts found.sort
|
154
|
+
end
|
155
|
+
|
156
|
+
def read_transaction_list(source)
|
157
|
+
json = JSON.parse(source.read)
|
158
|
+
transactions = []
|
159
|
+
|
160
|
+
json.each do |hash|
|
161
|
+
entry = HistoryEntry.new(hash)
|
162
|
+
|
163
|
+
if entry.buyer
|
164
|
+
transactions << Transaction.new(
|
165
|
+
exchange: 'Binance',
|
166
|
+
time: entry.time,
|
167
|
+
bought_amount: entry.quantity - entry.commission,
|
168
|
+
bought_currency: entry.asset,
|
169
|
+
sold_amount: entry.price * entry.quantity,
|
170
|
+
sold_currency: entry.currency
|
171
|
+
)
|
172
|
+
else
|
173
|
+
transactions << Transaction.new(
|
174
|
+
exchange: 'Binance',
|
175
|
+
time: entry.time,
|
176
|
+
bought_amount: entry.price * entry.quantity - entry.commission,
|
177
|
+
bought_currency: entry.currency,
|
178
|
+
sold_amount: entry.quantity,
|
179
|
+
sold_currency: entry.asset
|
180
|
+
)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
transactions
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
def make_request(path, params = {}, signed = true)
|
190
|
+
if signed
|
191
|
+
(@api_key && @secret_key) or raise "Public and secret API keys must be provided"
|
192
|
+
|
193
|
+
params['timestamp'] = (Time.now.to_f * 1000).to_i
|
194
|
+
end
|
195
|
+
|
196
|
+
url = URI(BASE_URL + path)
|
197
|
+
url.query = URI.encode_www_form(params)
|
198
|
+
|
199
|
+
if signed
|
200
|
+
hmac = OpenSSL::HMAC.hexdigest('sha256', @secret_key, url.query)
|
201
|
+
url.query += "&signature=#{hmac}"
|
202
|
+
end
|
203
|
+
|
204
|
+
Request.get(url) do |request|
|
205
|
+
request['X-MBX-APIKEY'] = @api_key
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|