crypto-service 0.0.1 → 0.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed5769c8ed79cd6a12e10aabd5035776ea6ef5cdcc2f32812e65e86a1464ab1d
4
- data.tar.gz: 9e547251610c7f066564c4af4d7ff87cb2b5c1a0e0a3ec90ee9bdad3aee464ea
3
+ metadata.gz: 42f3bc09e96cae2487fc28602311063ed0d80ec1fbf7a3617b55b1dac90ca755
4
+ data.tar.gz: 54acf3951a8b663e41dfa98965b3c064218d4b070f0b7930cb5a4176200dcf5f
5
5
  SHA512:
6
- metadata.gz: 1a06fc3a72160c2ce570e91725fcf8b3d749088a13b322b55e630a70a6ba40c2cc075f003f9dbe6e1ca29445782227b89f0a3b7902a55abdc296c915a9eae0f7
7
- data.tar.gz: c3c7dc4526dc42383a968db113aad034aaf7da46e310e0436e6c4ed00a893ef7e897ac87a49b6999f5ffa130295f6ac8abe6986821f9d87e8f742421f93f9beb
6
+ metadata.gz: bee095306e6b1ee2d8baf40ec285295851fb92dd4fc842e65d1c464a23f8c60b6ba54a29ca66331623f6b56d7cd7fe42794954707baf7d91ea3ba1d4b37b8a64
7
+ data.tar.gz: 483f2a06607e4499e7de5659eca9d064bacf6befccf407c18271a1d6e1c12eda21b0ca568fb7fbeec86ece9329906a20f0d52b82a79ba1f82891cae504ee2419
data/lib/crypto/base.rb CHANGED
@@ -3,39 +3,25 @@
3
3
  require 'uri'
4
4
  require 'net/http'
5
5
  require 'json'
6
+
6
7
  require 'coinex'
8
+ require 'coinpaprika'
9
+ require 'gecko'
10
+ require 'string/similarity'
7
11
  require 'request'
8
12
 
9
13
  module Crypto
10
14
  class Base
11
15
  class << self
12
- attr_accessor :app_info, :path_prefix, :development
13
-
14
- def calculate_win_or_lost(avg_price:, exchange:, settings:)
15
- percentages = percentage(exchange: exchange, settings: settings)
16
- profit = percentages.first
17
- lost = percentages[1]
18
-
19
- win_price = avg_price.to_f + ((avg_price.to_f * profit.to_f) / 100)
20
- lost_price = avg_price.to_f - ((avg_price.to_f * lost.to_f) / 100)
21
-
22
- [win_price, lost_price]
23
- end
16
+ attr_accessor :app_info, :path_prefix, :development, :testing
24
17
 
25
- def binance?(exchange)
26
- exchange == 'Binance'
18
+ def test(fixture)
19
+ load_fixture(fixture)
27
20
  end
28
21
 
29
- def percentage(exchange:, settings:)
30
- if binance?(exchange)
31
- profit = settings[:perc_profit_exchange]
32
- loss = settings[:perc_lost_exchange]
33
- else
34
- profit = settings[:perc_profit_all]
35
- loss = settings[:perc_lost_all]
36
- end
37
-
38
- [profit, loss]
22
+ def load_fixture(fixture)
23
+ fixture_path = File.expand_path(File.join(__dir__, '../../spec', 'fixtures/test', fixture))
24
+ JSON.parse(File.read(fixture_path))
39
25
  end
40
26
 
41
27
  def correct(result)
@@ -46,27 +32,18 @@ module Crypto
46
32
  { 'status' => :failed, 'code' => result['code'], 'message' => result['message'] }
47
33
  end
48
34
 
49
- def coinex_keys(settings)
50
- Crypto::Exchanges::Cx.access_id(settings[:access_id])
51
- Crypto::Exchanges::Cx.secret_key(settings[:secret_key])
52
- end
53
-
54
- def response_correct?(result)
55
- (result['code']).zero?
35
+ def binance?(exchanges)
36
+ exchanges.include?('Binance')
56
37
  end
57
38
 
58
- def calculate_signature(params)
59
- data_signature = Coinex::Signature.calculate(params: params)
60
-
61
- params[:tonce] = Crypto.development ? '1672669323219' : data_signature[:tonce]
62
- params[:signature] = data_signature[:signature]
63
-
64
- params
39
+ def coinex?(exchanges)
40
+ exchanges.include?('CoinEx')
65
41
  end
66
42
  end
67
43
 
68
44
  self.app_info = ::ENV['APP_INFO'] || "#{::Crypto::NAME} V#{::Crypto.version}"
69
45
  self.path_prefix = 'https://www.binance.com/bapi/composite/v1/public/promo/cmc/cryptocurrency'
70
- self.development = false
46
+ self.development = false # For rspec & test
47
+ self.testing = false # For real call, but return development payload
71
48
  end
72
49
  end
@@ -7,48 +7,29 @@
7
7
  # Exchange: exchange where crypto is listed or buy, (Binance → 20%, Other exchange → 10% )
8
8
 
9
9
  module Crypto
10
- class Buy < Base
10
+ class Buy < Operation
11
11
  class << self
12
12
  # rubocop: disable Metrics/MethodLength, Lint/MissingCopEnableDirective
13
- def order(payload:, settings:, exchange:, count:)
14
- coinex_keys(settings)
15
- result = Coinex::Order.market(params: params(
16
- symbol: payload['symbol'],
17
- count: count
18
- ))
19
-
20
- Crypto::Exchanges::Cx.clean_keys
21
-
22
- if response_correct?(result)
23
- prices = calculate_win_or_lost(avg_price: result['data']['avg_price'], exchange: exchange, settings: settings)
24
- divert(payload: payload, result: result, prices: prices, settings: settings)
25
- else
26
- error(result)
27
- end
28
- end
29
-
30
- def balance(count)
31
- params = Coinex::Signature.calculate
32
- params[:tonce] = '1672669323219' if Crypto.development
13
+ def order(data)
14
+ return test('crypto_buy.json') if Crypto.testing
33
15
 
34
- balance = Coinex::Balance.info(params: params)
35
- balance['data']['USDT']['available'].to_i / count.to_i
16
+ operations(data: data, index: nil)
36
17
  end
37
18
 
38
- def params(symbol:, count:)
39
- params = { market: "#{symbol}USDT", type: 'buy', amount: balance(count) }
40
- calculate_signature(params)
41
- end
19
+ private
42
20
 
43
- def divert(payload:, result:, prices:, settings:)
21
+ def divert(data)
44
22
  operations = []
45
- operations << result
23
+ data[:result]['operation'] = 'B'
24
+ operations << data[:result]
46
25
 
47
- loss = Crypto::Loss.limit(payload: payload, result: result, prices: prices, settings: settings)
26
+ loss = Crypto::Loss.limit(data: data, index: 0)
27
+ loss['operation'] = 'SL'
48
28
  operations << loss
49
- return operations if payload['mode'] == :top
29
+ return operations if data.dig(:payload, :mode) == :top
50
30
 
51
- profit = Crypto::Profit.limit(payload: payload, result: result, prices: prices, settings: settings)
31
+ profit = Crypto::Profit.limit(data: data, index: 1)
32
+ profit['operation'] = 'TP'
52
33
  operations << profit
53
34
 
54
35
  operations
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Class that return exchanges of a crypto
4
+
5
+ module Crypto
6
+ class Exchange < Base
7
+ PERCENTAGE = 70
8
+
9
+ class << self
10
+ def info(symbol:, name:)
11
+ exchanges = gecko(symbol: symbol, name: name)
12
+ return exchanges unless exchanges.nil?
13
+
14
+ coinpaprika(symbol: symbol, name: name)
15
+ end
16
+
17
+ private
18
+
19
+ def exchange(symbol:, service:, name:)
20
+ payload = Object.const_get(service).info(symbol)
21
+ return nil if payload.empty?
22
+
23
+ if hash?(payload) || (array?(payload) && payload.size <= 1)
24
+ return simple_exchange(service: service,
25
+ payload: payload)
26
+ end
27
+
28
+ # If array & size > 1, calculate max similar string
29
+ max_similarity(name: name, service: service, payload: payload)
30
+ end
31
+
32
+ def gecko(symbol:, name:)
33
+ exchange(symbol: symbol, service: 'Gecko::Coin', name: name)
34
+ end
35
+
36
+ def coinpaprika(symbol:, name:)
37
+ exchange(symbol: symbol, service: 'Coinpaprika::Coin', name: name)
38
+ end
39
+
40
+ def hash?(payload)
41
+ payload.instance_of?(::Hash)
42
+ end
43
+
44
+ def array?(payload)
45
+ payload.instance_of?(::Array)
46
+ end
47
+
48
+ def simple_exchange(service:, payload:)
49
+ if array?(payload)
50
+ Object.const_get(service).exchanges(payload.first['id'])
51
+ else
52
+ Object.const_get(service).exchanges(payload[:id])
53
+ end
54
+ end
55
+
56
+ def max_similarity(name:, service:, payload:)
57
+ similarity = calculate_similarity(name: name, payload: payload)
58
+ similarity = similarity.sort_by { |h| h[:percentage] }.reverse
59
+
60
+ crypto = similarity.select { |x| (x[:percentage] * 100).to_i > PERCENTAGE }.first
61
+ return nil if similarity.nil? || similarity.empty?
62
+
63
+ simple_exchange(service: service, payload: crypto)
64
+ end
65
+
66
+ def calculate_similarity(name:, payload:)
67
+ similarity = []
68
+ payload.each do |p|
69
+ similarity.push(
70
+ 'id': p['id'],
71
+ 'percentage': String::Similarity.cosine(name, p['name'])
72
+ )
73
+ end
74
+
75
+ similarity
76
+ end
77
+ end
78
+ end
79
+ end
@@ -28,6 +28,89 @@ module Crypto
28
28
  def ping
29
29
  Coinex.ping == { status: 200 }
30
30
  end
31
+
32
+ private
33
+
34
+ # rubocop: disable Lint/MissingCopEnableDirective
35
+ def order(data:, index: nil)
36
+ if data[:result].nil? && data[:prices].nil? # Buy
37
+ result = Coinex::Order.market(params: params(data: data, index: index))
38
+ return correct(result) if cex_response_correct?(result) && index == 2
39
+
40
+ order_market(data: data, result: result)
41
+ else # Limit (SL & TP)
42
+ result = Coinex::Order.limit(params: params(data: data, index: index))
43
+ order_limit(result: result)
44
+ end
45
+ end
46
+
47
+ # rubocop: disable Metrics/MethodLength
48
+ def order_market(data:, result:)
49
+ if cex_response_correct?(result) # If correct, call SL & TP
50
+ prices = Crypto::Operation.send :calculate_win_or_lost,
51
+ avg_price: result['data']['avg_price'],
52
+ exchanges: data[:exchanges],
53
+ settings: data[:settings]
54
+
55
+ data[:prices] = prices
56
+ data[:result] = result
57
+ Crypto::Buy.send :divert, data
58
+ else
59
+ error(result)
60
+ end
61
+ end
62
+
63
+ def order_limit(result:)
64
+ if cex_response_correct?(result)
65
+ correct(result)
66
+ else
67
+ error(result)
68
+ end
69
+ end
70
+
71
+ def balance(count)
72
+ params = Coinex::Signature.calculate
73
+ params[:tonce] = '1672669323219' if Crypto.development
74
+
75
+ balance = Coinex::Balance.info(params: params)
76
+ balance['data']['USDT']['available'].to_i / count.to_i
77
+ end
78
+
79
+ def params(data:, index: nil)
80
+ params = if index.nil?
81
+ { market: "#{data.dig(:payload, :symbol)}USDT", type: 'buy', amount: balance(data[:count]) }
82
+ else
83
+ limit_params(data: data, index: index)
84
+ end
85
+
86
+ calculate_signature(params)
87
+ end
88
+
89
+ def limit_params(data:, index:)
90
+ case index
91
+ when 0 # SL
92
+ { market: "#{data.dig(:payload, :symbol)}USDT", type: 'sell', amount: data.dig(:result, 'data', 'amount'),
93
+ stop_price: Crypto::Loss.send(:what_price, data[:prices]) }
94
+ when 1 # TP
95
+ { market: "#{data.dig(:payload, :symbol)}USDT", type: 'sell', amount: data.dig(:result, 'data', 'amount'),
96
+ stop_price: Crypto::Profit.send(:what_price, data[:prices]) }
97
+ when 2 # SELL
98
+ { market: "#{data.dig(:payload, :symbol)}USDT", type: 'sell', amount: data[:balance] }
99
+ end
100
+ end
101
+
102
+ def calculate_signature(params)
103
+ data_signature = Coinex::Signature.calculate(params: params)
104
+
105
+ params[:tonce] = Crypto.development ? '1672669323219' : data_signature[:tonce]
106
+ params[:signature] = data_signature[:signature]
107
+
108
+ params
109
+ end
110
+
111
+ def cex_response_correct?(result)
112
+ (result['code']).zero?
113
+ end
31
114
  end
32
115
  end
33
116
  end
@@ -16,6 +16,8 @@ module Crypto
16
16
 
17
17
  # rubocop: disable Metrics/MethodLength, Lint/MissingCopEnableDirective
18
18
  def info
19
+ return test('crypto_listing.json') if Crypto.testing
20
+
19
21
  payload = detail
20
22
  if status?(payload)
21
23
  news = []
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crypto
4
+ class Operation < Base
5
+ class << self
6
+ private
7
+
8
+ def setup_keys(exchanges:, settings:)
9
+ return unless coinex?(exchanges)
10
+
11
+ coinex_keys(settings)
12
+ 'CoinEx'
13
+ end
14
+
15
+ def coinex_keys(settings)
16
+ Crypto::Exchanges::Cx.access_id(settings[:access_id])
17
+ Crypto::Exchanges::Cx.secret_key(settings[:secret_key])
18
+ end
19
+
20
+ def clean_keys(exchange)
21
+ first = exchange.chr.upcase
22
+ last = exchange[-1].downcase
23
+
24
+ class_send = "Crypto::Exchanges::#{first}#{last}"
25
+ Object.const_get(class_send).clean_keys
26
+ end
27
+
28
+ def operations(data:, index: nil)
29
+ exchange = setup_keys(exchanges: data[:exchanges], settings: data[:settings])
30
+ return nil if exchange.nil?
31
+
32
+ # Buy or TP or SL
33
+ result = buy_or_limit(data: data, exchange: exchange, index: index)
34
+ clean_keys(exchange)
35
+
36
+ result
37
+ end
38
+
39
+ def buy_or_limit(data:, exchange:, index:)
40
+ case exchange
41
+ when 'CoinEx'
42
+ Crypto::Exchanges::Cx.send(:order, data: data, index: index)
43
+ end
44
+ end
45
+
46
+ def calculate_win_or_lost(avg_price:, exchanges:, settings:)
47
+ percentages = percentage(exchanges: exchanges, settings: settings)
48
+ profit = percentages.first
49
+ lost = percentages[1]
50
+
51
+ win_price = avg_price.to_f + ((avg_price.to_f * profit.to_f) / 100)
52
+ lost_price = avg_price.to_f - ((avg_price.to_f * lost.to_f) / 100)
53
+
54
+ [win_price, lost_price]
55
+ end
56
+
57
+ def percentage(exchanges:, settings:)
58
+ if binance?(exchanges)
59
+ profit = settings[:perc_profit_exchange]
60
+ loss = settings[:perc_lost_exchange]
61
+ else
62
+ profit = settings[:perc_profit_all]
63
+ loss = settings[:perc_lost_all]
64
+ end
65
+
66
+ [profit, loss]
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,26 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Crypto
4
- class Sell < Base
4
+ class Sell < Operation
5
5
  class << self
6
- def order(payload:, settings:)
7
- coinex_keys(settings)
8
- result = Coinex::Order.market(params: params(payload))
9
-
10
- Crypto::Exchanges::Cx.clean_keys
11
-
12
- if response_correct?(result)
13
- correct(result)
14
- else
15
- error(result)
16
- end
17
- end
18
-
19
- private
20
-
21
- def params(payload)
22
- params = { market: "#{payload['symbol']}USDT", type: 'sell', amount: payload['balance'] }
23
- calculate_signature(params)
6
+ def order(data)
7
+ operations(data: data, index: 2)
24
8
  end
25
9
  end
26
10
  end
@@ -1,30 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Crypto
4
- class Stop < Base
4
+ class Stop < Operation
5
5
  class << self
6
- def limit(payload:, result:, prices:, settings:)
7
- coinex_keys(settings)
8
-
9
- result = Coinex::Order.limit(params: params(
10
- payload: payload, result: result, prices: prices
11
- ))
12
-
13
- Crypto::Exchanges::Cx.clean_keys
14
- if response_correct?(result)
15
- correct(result)
16
- else
17
- error(result)
18
- end
19
- end
20
-
21
- private
22
-
23
- def params(payload:, result:, prices:)
24
- params = { market: "#{payload['symbol']}USDT", type: 'sell', amount: result['data']['amount'],
25
- stop_price: what_price(prices) }
26
-
27
- calculate_signature(params)
6
+ def limit(data:, index:)
7
+ operations(data: data, index: index)
28
8
  end
29
9
  end
30
10
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Crypto
4
- VERSION = '0.0.1'
4
+ VERSION = '0.0.2'
5
5
  end
data/lib/crypto.rb CHANGED
@@ -35,6 +35,14 @@ module Crypto
35
35
  def development
36
36
  @mutex.synchronize { ::Crypto::Base.development }
37
37
  end
38
+
39
+ def testing=(boolean)
40
+ @mutex.synchronize { ::Crypto::Base.testing = boolean }
41
+ end
42
+
43
+ def testing
44
+ @mutex.synchronize { ::Crypto::Base.testing }
45
+ end
38
46
  end
39
47
  end
40
48
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: crypto-service
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefano Baldazzi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-09 00:00:00.000000000 Z
11
+ date: 2023-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -52,6 +52,48 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: 0.0.3
55
+ - !ruby/object:Gem::Dependency
56
+ name: coinpaprika
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.0.2
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.0.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: gecko-api
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.0.3
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.0.3
83
+ - !ruby/object:Gem::Dependency
84
+ name: string-similarity
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 2.1.0
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 2.1.0
55
97
  - !ruby/object:Gem::Dependency
56
98
  name: zeitwerk
57
99
  requirement: !ruby/object:Gem::Requirement
@@ -191,9 +233,11 @@ files:
191
233
  - lib/crypto/autoloader.rb
192
234
  - lib/crypto/base.rb
193
235
  - lib/crypto/resources/buy.rb
236
+ - lib/crypto/resources/exchange.rb
194
237
  - lib/crypto/resources/exchanges/cx.rb
195
238
  - lib/crypto/resources/listing.rb
196
239
  - lib/crypto/resources/loss.rb
240
+ - lib/crypto/resources/operation.rb
197
241
  - lib/crypto/resources/profit.rb
198
242
  - lib/crypto/resources/sell.rb
199
243
  - lib/crypto/resources/stop.rb
@@ -221,5 +265,5 @@ requirements: []
221
265
  rubygems_version: 3.3.24
222
266
  signing_key:
223
267
  specification_version: 4
224
- summary: crypto0.0.1
268
+ summary: crypto0.0.2
225
269
  test_files: []