crypto-service 0.0.0 → 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: caeb3d4bf826a92cf335e649c50fb7abadd7b6175c021f438641781f070f3e05
4
- data.tar.gz: 9b5a35056f03f7701f8482cd28b2a55edbb0d587548192859bcfd2c0b5cc7c49
3
+ metadata.gz: 42f3bc09e96cae2487fc28602311063ed0d80ec1fbf7a3617b55b1dac90ca755
4
+ data.tar.gz: 54acf3951a8b663e41dfa98965b3c064218d4b070f0b7930cb5a4176200dcf5f
5
5
  SHA512:
6
- metadata.gz: 91558907b161b5cde84533d8df178cf2c20e31d570e029045a7eea9cf85a80868b39d7f0f51f4d9fdb03f712209affe94fc6f296c97b6db609ee18ca0fb7aea4
7
- data.tar.gz: 4024d5c5eaa55c595a46f0404d778eabdcaa20cd27eba183c9b996f40f6053def3d78be7e41cf4bbafb0f3fab4fcb67a45b3100acba536a5ae398dc93cec4c18
6
+ metadata.gz: bee095306e6b1ee2d8baf40ec285295851fb92dd4fc842e65d1c464a23f8c60b6ba54a29ca66331623f6b56d7cd7fe42794954707baf7d91ea3ba1d4b37b8a64
7
+ data.tar.gz: 483f2a06607e4499e7de5659eca9d064bacf6befccf407c18271a1d6e1c12eda21b0ca568fb7fbeec86ece9329906a20f0d52b82a79ba1f82891cae504ee2419
data/lib/crypto/base.rb CHANGED
@@ -4,13 +4,46 @@ 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'
11
+ require 'request'
12
+
7
13
  module Crypto
8
14
  class Base
9
15
  class << self
10
- attr_accessor :app_info, :path_prefix
16
+ attr_accessor :app_info, :path_prefix, :development, :testing
17
+
18
+ def test(fixture)
19
+ load_fixture(fixture)
20
+ end
21
+
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))
25
+ end
26
+
27
+ def correct(result)
28
+ { 'status' => :success, 'code' => 200, 'payload' => result }
29
+ end
30
+
31
+ def error(result)
32
+ { 'status' => :failed, 'code' => result['code'], 'message' => result['message'] }
33
+ end
34
+
35
+ def binance?(exchanges)
36
+ exchanges.include?('Binance')
37
+ end
38
+
39
+ def coinex?(exchanges)
40
+ exchanges.include?('CoinEx')
41
+ end
11
42
  end
12
43
 
13
44
  self.app_info = ::ENV['APP_INFO'] || "#{::Crypto::NAME} V#{::Crypto.version}"
14
45
  self.path_prefix = 'https://www.binance.com/bapi/composite/v1/public/promo/cmc/cryptocurrency'
46
+ self.development = false # For rspec & test
47
+ self.testing = false # For real call, but return development payload
15
48
  end
16
49
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Class that buy crypto, set TP & SL and return their responses
4
+ # Parameter of order:
5
+ # Payload: Hash of Crypto::Listing.info or Crypto::Top.info
6
+ # Settings: contact settings
7
+ # Exchange: exchange where crypto is listed or buy, (Binance → 20%, Other exchange → 10% )
8
+
9
+ module Crypto
10
+ class Buy < Operation
11
+ class << self
12
+ # rubocop: disable Metrics/MethodLength, Lint/MissingCopEnableDirective
13
+ def order(data)
14
+ return test('crypto_buy.json') if Crypto.testing
15
+
16
+ operations(data: data, index: nil)
17
+ end
18
+
19
+ private
20
+
21
+ def divert(data)
22
+ operations = []
23
+ data[:result]['operation'] = 'B'
24
+ operations << data[:result]
25
+
26
+ loss = Crypto::Loss.limit(data: data, index: 0)
27
+ loss['operation'] = 'SL'
28
+ operations << loss
29
+ return operations if data.dig(:payload, :mode) == :top
30
+
31
+ profit = Crypto::Profit.limit(data: data, index: 1)
32
+ profit['operation'] = 'TP'
33
+ operations << profit
34
+
35
+ operations
36
+ end
37
+ end
38
+ end
39
+ end
@@ -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
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Exchange -> Coinex
4
+
5
+ module Crypto
6
+ module Exchanges
7
+ class Cx < Base
8
+ class << self
9
+ def access_id(access_id)
10
+ return unless ping
11
+
12
+ Coinex.access_id = access_id
13
+ end
14
+
15
+ def secret_key(secret_key)
16
+ return unless ping
17
+
18
+ Coinex.secret_key = secret_key
19
+ end
20
+
21
+ def clean_keys
22
+ return unless ping
23
+
24
+ Coinex.access_id = nil
25
+ Coinex.secret_key = nil
26
+ end
27
+
28
+ def ping
29
+ Coinex.ping == { status: 200 }
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
114
+ end
115
+ end
116
+ end
117
+ end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'active_support/all'
4
4
 
5
+ # Class that return crypto listed in a interval of MINUTES
6
+
5
7
  module Crypto
6
8
  class Listing < Base
7
9
  MINUTES = 15
@@ -14,6 +16,8 @@ module Crypto
14
16
 
15
17
  # rubocop: disable Metrics/MethodLength, Lint/MissingCopEnableDirective
16
18
  def info
19
+ return test('crypto_listing.json') if Crypto.testing
20
+
17
21
  payload = detail
18
22
  if status?(payload)
19
23
  news = []
@@ -34,7 +38,7 @@ module Crypto
34
38
 
35
39
  def detail
36
40
  uri = URI("#{Crypto.path}/listings/new?limit=100&start=1")
37
- Crypto::Request.request(uri: uri, method: 'GET')
41
+ Request::Http.request(uri: uri, method: 'GET')
38
42
  end
39
43
 
40
44
  def status?(payload)
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crypto
4
+ class Loss < Stop
5
+ class << self
6
+ private
7
+
8
+ def what_price(prices)
9
+ prices[1]
10
+ end
11
+ end
12
+ end
13
+ end
@@ -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
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crypto
4
+ class Profit < Stop
5
+ class << self
6
+ private
7
+
8
+ def what_price(prices)
9
+ prices.first
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crypto
4
+ class Sell < Operation
5
+ class << self
6
+ def order(data)
7
+ operations(data: data, index: 2)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crypto
4
+ class Stop < Operation
5
+ class << self
6
+ def limit(data:, index:)
7
+ operations(data: data, index: index)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Class that return crypto after a percentage in top gainers
4
+
3
5
  module Crypto
4
6
  class Top < Base
5
7
  PERCENTAGE = 20.0
@@ -32,7 +34,7 @@ module Crypto
32
34
 
33
35
  def detail
34
36
  uri = URI("#{Crypto.path}/trending/gainers-losers?limit=100&sort_dir=desc&start=1")
35
- Crypto::Request.request(uri: uri, method: 'GET')
37
+ Request::Http.request(uri: uri, method: 'GET')
36
38
  end
37
39
 
38
40
  def status?(payload)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Crypto
4
- VERSION = '0.0.0'
4
+ VERSION = '0.0.2'
5
5
  end
data/lib/crypto.rb CHANGED
@@ -27,6 +27,22 @@ module Crypto
27
27
  def version
28
28
  VERSION
29
29
  end
30
+
31
+ def development=(boolean)
32
+ @mutex.synchronize { ::Crypto::Base.development = boolean }
33
+ end
34
+
35
+ def development
36
+ @mutex.synchronize { ::Crypto::Base.development }
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
30
46
  end
31
47
  end
32
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.0
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-02 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
@@ -24,6 +24,76 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 7.0.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: aga-request
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: coinex
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.0.3
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
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
27
97
  - !ruby/object:Gem::Dependency
28
98
  name: zeitwerk
29
99
  requirement: !ruby/object:Gem::Requirement
@@ -162,8 +232,15 @@ files:
162
232
  - lib/crypto.rb
163
233
  - lib/crypto/autoloader.rb
164
234
  - lib/crypto/base.rb
235
+ - lib/crypto/resources/buy.rb
236
+ - lib/crypto/resources/exchange.rb
237
+ - lib/crypto/resources/exchanges/cx.rb
165
238
  - lib/crypto/resources/listing.rb
166
- - lib/crypto/resources/request.rb
239
+ - lib/crypto/resources/loss.rb
240
+ - lib/crypto/resources/operation.rb
241
+ - lib/crypto/resources/profit.rb
242
+ - lib/crypto/resources/sell.rb
243
+ - lib/crypto/resources/stop.rb
167
244
  - lib/crypto/resources/top.rb
168
245
  - lib/crypto/version.rb
169
246
  homepage: https://github.com/Baldaz02/crypto-ruby
@@ -188,5 +265,5 @@ requirements: []
188
265
  rubygems_version: 3.3.24
189
266
  signing_key:
190
267
  specification_version: 4
191
- summary: crypto0.0.0
268
+ summary: crypto0.0.2
192
269
  test_files: []
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Crypto
4
- class Request < Base
5
- class << self
6
- def request(uri:, method:)
7
- req = get_method(name: method, uri: uri)
8
- req['Content-Type'] = 'application/json'
9
-
10
- res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
11
- http.request(req)
12
- end
13
-
14
- JSON.parse(res.body)
15
- end
16
-
17
- def get_method(name:, uri:)
18
- case name
19
- when 'GET'
20
- req = Net::HTTP::Get.new(uri)
21
- # when 'POST'
22
- # req = Net::HTTP::Post.new(uri)
23
- # when 'DELETE'
24
- # req = Net::HTTP::Delete.new(uri)
25
- end
26
-
27
- req
28
- end
29
- end
30
- end
31
- end