abot-info 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/Gemfile +7 -0
- data/LICENSE +21 -0
- data/README.md +43 -0
- data/Rakefile +6 -0
- data/abot-info.gemspec +41 -0
- data/exe/abot-info +6 -0
- data/lib/abot/info.rb +126 -0
- data/lib/abot/info/binance_account.rb +148 -0
- data/lib/abot/info/coin.rb +134 -0
- data/lib/abot/info/database_table.rb +33 -0
- data/lib/abot/info/decorators/coin_decorator.rb +126 -0
- data/lib/abot/info/helpers.rb +63 -0
- data/lib/abot/info/option_parser.rb +178 -0
- data/lib/abot/info/table.rb +164 -0
- data/lib/abot/info/version.rb +7 -0
- data/spec/abot/statistics_spec.rb +9 -0
- metadata +175 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: bbe9530de84f99fcba646a78e0c4a61bb02ebf2e484159817721724cfe6b7733
|
4
|
+
data.tar.gz: 5f1decd77bea3d406a7abe300c9a887391afe9a7991d27561c9a5a34ea8350e4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4544c13838da0d2ed4af0e559b02d8c3bac17555a2cd7bdad62d0b1fcf496a13ab8e192205c537e2244775b1e4306c001eb60df1f211ae4244719265fb1565ee
|
7
|
+
data.tar.gz: b647a8cd960afb5e5bbaf739949cddfd6ab1fb4e2055ec9ab6e7228681df5e10cb76d8b4a92a528c48dad85eff855cd96275421da508f5bcdd5c8bc81bd6fcc9
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 w_dmitrii
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# Abot::Info
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/abot/info`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'abot-info'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install abot-info
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/abot-info. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
36
|
+
|
37
|
+
## License
|
38
|
+
|
39
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
40
|
+
|
41
|
+
## Code of Conduct
|
42
|
+
|
43
|
+
Everyone interacting in the Abot::Info project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/abot-info/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/abot-info.gemspec
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "abot/info/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'abot-info'
|
8
|
+
spec.version = Abot::Info::VERSION
|
9
|
+
spec.authors = ['w_dmitrii']
|
10
|
+
spec.email = ['wiz.work2021@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = 'Abot Info'
|
13
|
+
spec.license = 'MIT'
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.6.6')
|
15
|
+
|
16
|
+
spec.files = Dir['lib/**/*.rb', 'exe/*'] + %w[
|
17
|
+
abot-info.gemspec
|
18
|
+
Gemfile
|
19
|
+
LICENSE
|
20
|
+
Rakefile
|
21
|
+
README.md
|
22
|
+
]
|
23
|
+
|
24
|
+
spec.test_files = Dir['spec/**/*_spec.rb']
|
25
|
+
|
26
|
+
spec.extra_rdoc_files = %w[LICENSE README.md]
|
27
|
+
|
28
|
+
spec.bindir = 'exe'
|
29
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
30
|
+
spec.require_paths = ['lib']
|
31
|
+
|
32
|
+
spec.add_development_dependency "bundler", "~> 1.17"
|
33
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
34
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
35
|
+
|
36
|
+
spec.add_dependency "activerecord", '~> 6.1.3'
|
37
|
+
spec.add_dependency "paint", '~> 2.2.1'
|
38
|
+
spec.add_dependency "sqlite3", '~> 1.4.2'
|
39
|
+
spec.add_dependency "terminal-table", '~> 3.0.1'
|
40
|
+
spec.add_dependency "binance-ruby", '~> 1.3.1'
|
41
|
+
end
|
data/exe/abot-info
ADDED
data/lib/abot/info.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record'
|
4
|
+
require 'sqlite3'
|
5
|
+
require 'terminal-table'
|
6
|
+
require 'paint'
|
7
|
+
require 'binance-ruby'
|
8
|
+
|
9
|
+
require_relative 'info/option_parser'
|
10
|
+
require_relative 'info/coin'
|
11
|
+
require_relative 'info/binance_account'
|
12
|
+
require_relative 'info/table'
|
13
|
+
require_relative 'info/version'
|
14
|
+
|
15
|
+
module Abot
|
16
|
+
module Info
|
17
|
+
class << self
|
18
|
+
def run
|
19
|
+
Info::OptionParser.instance do |parser|
|
20
|
+
parser.program_name = 'abot-info'
|
21
|
+
parser.version = Abot::Info::VERSION
|
22
|
+
|
23
|
+
parser.separator ''
|
24
|
+
parser.separator 'Application options:'
|
25
|
+
|
26
|
+
parser.add_option(
|
27
|
+
:template, '--template=TEMPLATE',
|
28
|
+
'Шаблон (default || mobile)',
|
29
|
+
)
|
30
|
+
parser.add_option(
|
31
|
+
:add, '--add=COLUMN1,COLUMN2,COLUMN3',
|
32
|
+
"Добавить колонки:\n" \
|
33
|
+
" name: Названия монет\n" \
|
34
|
+
" name_mobile: Название монет(без изменения по дню)\n" \
|
35
|
+
" sell_up: Прибыль со сделки\n" \
|
36
|
+
" sell_price: Цена продажи\n" \
|
37
|
+
" max_price: Максимальная цена за 24ч.\n" \
|
38
|
+
" min_price: Минимальная цена за 24ч.\n" \
|
39
|
+
" current_price: Текущая цена\n" \
|
40
|
+
" next_average_price: Цена следующего усреднения\n" \
|
41
|
+
" current_quote: Текущая стоимость позиции, $\n" \
|
42
|
+
" buy_date: Дата покупки\n" \
|
43
|
+
" timer: Время с покупки\n" \
|
44
|
+
" current_profit: Текущая прибыль\n" \
|
45
|
+
" potential_profit: Потенциальная прибыль\n",
|
46
|
+
)
|
47
|
+
parser.add_option(
|
48
|
+
:del, '--del=COLUMN1,COLUMN2,COLUMN3',
|
49
|
+
"Удалить колонки:\n" \
|
50
|
+
" name: Названия монет\n" \
|
51
|
+
" name_mobile: Название монет(без изменения по дню)\n" \
|
52
|
+
" sell_up: Прибыль со сделки\n" \
|
53
|
+
" sell_price: Цена продажи\n" \
|
54
|
+
" max_price: Максимальная цена за 24ч.\n" \
|
55
|
+
" min_price: Минимальная цена за 24ч.\n" \
|
56
|
+
" current_price: Текущая цена\n" \
|
57
|
+
" next_average_price: Цена следующего усреднения\n" \
|
58
|
+
" current_quote: Текущая стоимость позиции, $\n" \
|
59
|
+
" buy_date: Дата покупки\n" \
|
60
|
+
" timer: Время с покупки\n" \
|
61
|
+
" current_profit: Текущая прибыль\n" \
|
62
|
+
" potential_profit: Потенциальная прибыль\n",
|
63
|
+
)
|
64
|
+
parser.add_option(
|
65
|
+
:db_path, '--db_path=DB_PATH',
|
66
|
+
'Абсолютный путь до базы A-bot',
|
67
|
+
required: true
|
68
|
+
)
|
69
|
+
parser.add_option(
|
70
|
+
:symbols, '--symbols=SYMBOL1,SYMBOL2,SYMBOL3',
|
71
|
+
'Пары для отображения курсов',
|
72
|
+
)
|
73
|
+
end.final!
|
74
|
+
|
75
|
+
require_relative 'info/database_table'
|
76
|
+
|
77
|
+
start_stats({ rate_coins: rate_coins, columns: columns })
|
78
|
+
end
|
79
|
+
|
80
|
+
def start_stats(opts)
|
81
|
+
while true do
|
82
|
+
begin
|
83
|
+
table = Table.new(opts.merge({ account: binance_account })).generate
|
84
|
+
|
85
|
+
system 'clear'
|
86
|
+
puts table
|
87
|
+
rescue StandardError => e
|
88
|
+
puts "ERROR: #{e.message}"
|
89
|
+
end
|
90
|
+
|
91
|
+
sleep 10
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def rate_coins
|
98
|
+
@rate_coins ||= Info::OptionParser.instance.options[:symbols]&.split(',') || []
|
99
|
+
end
|
100
|
+
|
101
|
+
def template
|
102
|
+
@template ||= Info::OptionParser.instance.options[:template]
|
103
|
+
end
|
104
|
+
|
105
|
+
def columns
|
106
|
+
add = Info::OptionParser.instance.options[:add]&.split(',')&.map { |m| m.to_sym } || []
|
107
|
+
del = Info::OptionParser.instance.options[:del]&.split(',')&.map { |m| m.to_sym } || []
|
108
|
+
columns = case template
|
109
|
+
when 'default'
|
110
|
+
Table::HEADINGS_COINS_TABLE_DEFAULT
|
111
|
+
when 'mobile'
|
112
|
+
Table::HEADINGS_COINS_TABLE_MOBILE
|
113
|
+
else
|
114
|
+
[]
|
115
|
+
end
|
116
|
+
columns += add
|
117
|
+
columns -= del
|
118
|
+
Table.last_columns_set(columns)
|
119
|
+
end
|
120
|
+
|
121
|
+
def binance_account
|
122
|
+
BinanceAccount.new(DatabaseTable::API_KEY, DatabaseTable::SECRET_KEY)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abot
|
4
|
+
module Info
|
5
|
+
class BinanceAccount
|
6
|
+
attr_reader :api_key, :secret_key
|
7
|
+
|
8
|
+
def initialize(api_key, secret_key)
|
9
|
+
@api_key = api_key
|
10
|
+
@secret_key = secret_key
|
11
|
+
end
|
12
|
+
|
13
|
+
def free_balance(quote)
|
14
|
+
account_info[:balances].find { |f| f[:asset] == quote }.try(:[], :free).to_f
|
15
|
+
end
|
16
|
+
|
17
|
+
def current_balance_btc
|
18
|
+
@current_balance_btc ||= calculation_current_balance_btc
|
19
|
+
end
|
20
|
+
|
21
|
+
def current_balance(balance_btc, quote)
|
22
|
+
calculation_current_balance(balance_btc, quote)
|
23
|
+
end
|
24
|
+
|
25
|
+
def current_balance_wl(current_coins, quote)
|
26
|
+
calculation_current_balance_wl(current_coins, quote)
|
27
|
+
end
|
28
|
+
|
29
|
+
def percent_free_balance(balance, quote)
|
30
|
+
((free_balance(quote) / balance) * 100).round(2)
|
31
|
+
end
|
32
|
+
|
33
|
+
def account_info
|
34
|
+
@account_info ||= Binance::Api::Account.info!(
|
35
|
+
api_key: api_key,
|
36
|
+
api_secret_key: secret_key,
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
def potential_balance(current_coins, trade_params)
|
41
|
+
return @potential_balance if @potential_balance
|
42
|
+
|
43
|
+
balance = current_balance(current_balance_btc, 'USDT')
|
44
|
+
quote_assets = trade_params['quote_asset'].split(' ')
|
45
|
+
potential = 0.0
|
46
|
+
btcusdt = get_symbols.find { |f| f[:symbol] == 'BTCUSDT' }
|
47
|
+
|
48
|
+
quote_assets.each do |q|
|
49
|
+
begin
|
50
|
+
pp_q = Coin.sum_potential_profit(current_coins, q) - Coin.sum_current_profit(current_coins, q)
|
51
|
+
if q == 'USDT'
|
52
|
+
potential += pp_q
|
53
|
+
elsif q == 'BTC'
|
54
|
+
potential += pp_q * btcusdt[:askPrice].to_f
|
55
|
+
elsif Coin::FIAT.include?(q)
|
56
|
+
btc = pp_q / get_symbols.find { |f| f[:symbol] == "BTC#{quote_asset}" }[:askPrice].to_f
|
57
|
+
potential += btc * btcusdt[:askPrice].to_f
|
58
|
+
else
|
59
|
+
c = pp_q / get_symbols.find { |f| f[:symbol] == "#{quote_asset}BTC" }[:askPrice].to_f
|
60
|
+
potential += btcusdt[:askPrice].to_f / c
|
61
|
+
end
|
62
|
+
rescue StandardError
|
63
|
+
puts "Ошибка подсчета потенц. баланса: #{q}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
@potential_balance ||= potential + balance
|
68
|
+
end
|
69
|
+
|
70
|
+
def symbol_info(symbol)
|
71
|
+
get_symbols.find { |f| f[:symbol] == symbol }
|
72
|
+
end
|
73
|
+
|
74
|
+
def symbol_min_price(symbol)
|
75
|
+
symbol_info(symbol).try(:[], :lowPrice).to_f
|
76
|
+
end
|
77
|
+
|
78
|
+
def symbol_max_price(symbol)
|
79
|
+
symbol_info(symbol).try(:[], :highPrice).to_f
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def calculation_current_balance_btc
|
85
|
+
balance = 0.0
|
86
|
+
account_coins = {}
|
87
|
+
account_info[:balances].each do |coin|
|
88
|
+
if coin[:free].to_f != 0 || coin[:locked].to_f != 0
|
89
|
+
account_coins[coin[:asset].to_s] = coin[:free].to_f + coin[:locked].to_f
|
90
|
+
end
|
91
|
+
end
|
92
|
+
if account_coins.present?
|
93
|
+
symbols = get_symbols
|
94
|
+
btcusdt = symbols.find { |f| f[:symbol] == 'BTCUSDT' }[:askPrice].to_f
|
95
|
+
account_coins.each do |name, value|
|
96
|
+
begin
|
97
|
+
if name == 'BTC'
|
98
|
+
balance += account_coins[name]
|
99
|
+
elsif Coin::FIAT.include?(name)
|
100
|
+
coin = symbols.find { |f| f[:symbol] == "BTC#{name}" }
|
101
|
+
balance += value / coin[:askPrice].to_f
|
102
|
+
elsif symbols.find { |f| f[:symbol] == "#{name}USDT" }.nil?
|
103
|
+
coin = symbols.find { |f| f[:symbol] == "#{name}BTC" }
|
104
|
+
balance += coin[:askPrice].to_f * value
|
105
|
+
else
|
106
|
+
coin = symbols.find { |f| f[:symbol] == "#{name}USDT" }
|
107
|
+
balance += coin[:askPrice].to_f * value / btcusdt
|
108
|
+
end
|
109
|
+
rescue StandardError
|
110
|
+
puts "Ошибка при подсчете: монета #{name}"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
balance
|
115
|
+
end
|
116
|
+
|
117
|
+
def calculation_current_balance(balance_btc, quote)
|
118
|
+
symbols = get_symbols
|
119
|
+
if Coin::FIAT.include?(quote)
|
120
|
+
coin = symbols.find { |f| f[:symbol] == "BTC#{quote}" }
|
121
|
+
balance_btc * coin.try(:[], :askPrice).to_f
|
122
|
+
else
|
123
|
+
coin = symbols.find { |f| f[:symbol] == "#{quote}BTC" }
|
124
|
+
balance_btc / coin.try(:[], :askPrice).to_f
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def calculation_current_balance_wl(current_coins, quote)
|
129
|
+
balance = 0.0
|
130
|
+
current_coins = current_coins.select { |s| s.quote_asset == quote }
|
131
|
+
current_coins.each { |coin| balance += coin.current_quote }
|
132
|
+
quote_info = account_info[:balances].find { |f| f[:asset] == quote }
|
133
|
+
if quote_info.present?
|
134
|
+
balance += (quote_info.try(:[], :free).to_f + quote_info.try(:[], :locked).to_f)
|
135
|
+
end
|
136
|
+
balance
|
137
|
+
end
|
138
|
+
|
139
|
+
def get_symbols
|
140
|
+
@get_symbols ||= Binance::Api.ticker!(
|
141
|
+
type: 'daily',
|
142
|
+
api_key: api_key,
|
143
|
+
api_secret_key: secret_key
|
144
|
+
)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'decorators/coin_decorator'
|
4
|
+
|
5
|
+
module Abot
|
6
|
+
module Info
|
7
|
+
class Coin
|
8
|
+
include Abot::Info::Coin::CoinDecorator
|
9
|
+
|
10
|
+
attr_reader :base_asset, :quote_asset, :average_price, :current_price, :sell_price, :volume, :num_averaging,
|
11
|
+
:total_quote, :buy_price, :step_averaging, :tick_size, :account, :timer, :trade_params
|
12
|
+
|
13
|
+
FIAT = ['USDT', 'BUSD', 'AUD', 'BIDR', 'BRL', 'EUR', 'GBR', 'RUB', 'TRY', 'TUSD', 'USDC', 'DAI', 'IDRT', 'PAX', 'UAH', 'NGN', 'VAI', 'BVND']
|
14
|
+
|
15
|
+
def initialize(options = {})
|
16
|
+
@trade_params = options[:trade_params]
|
17
|
+
@account = options[:account]
|
18
|
+
@base_asset = options[:base_asset]
|
19
|
+
@quote_asset = options[:quote_asset]
|
20
|
+
@average_price = options[:average_price]
|
21
|
+
@current_price = options[:current_price]
|
22
|
+
@sell_price = options[:sell_price]
|
23
|
+
@volume = options[:volume]
|
24
|
+
@num_averaging = options[:num_averaging]
|
25
|
+
@total_quote = options[:total_quote]
|
26
|
+
@buy_price = options[:buy_price]
|
27
|
+
@step_averaging = options[:step_averaging]
|
28
|
+
@tick_size = options[:tick_size]
|
29
|
+
@timer = options[:timer]
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.current_coins(account, trade_params)
|
33
|
+
DatabaseTable.data_coins.map do |coin|
|
34
|
+
Coin.new(
|
35
|
+
trade_params: trade_params,
|
36
|
+
account: account,
|
37
|
+
base_asset: coin['baseAsset'],
|
38
|
+
quote_asset: coin['quoteAsset'],
|
39
|
+
average_price: coin['averagePrice'].to_f,
|
40
|
+
current_price: coin['askPrice'].to_f,
|
41
|
+
sell_price: coin['sellPrice'].to_f,
|
42
|
+
volume: coin['allQuantity'].to_f,
|
43
|
+
num_averaging: (coin['numAveraging'].to_i - 1),
|
44
|
+
total_quote: coin['totalQuote'].to_f.round(2),
|
45
|
+
buy_price: coin['buyPrice'].to_f,
|
46
|
+
step_averaging: coin['stepAveraging'].to_f,
|
47
|
+
tick_size: coin['tickSize'],
|
48
|
+
timer: coin['timer'].to_s[0..9],
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.available_coins_number(current_coins, data_settings, free_balance)
|
54
|
+
aver_arr = []
|
55
|
+
current_coins.each_with_index {|m| aver_arr[m.num_averaging] = ((aver_arr[m.num_averaging].to_i) + 1) }
|
56
|
+
aver_weight = 0
|
57
|
+
aver_arr.each_with_index { |e, idx| aver_weight += (e.to_i * idx) }
|
58
|
+
max_pairs = (free_balance / 100) * data_settings["max_trade_pairs"].to_i
|
59
|
+
progressive_max_pairs = max_pairs - aver_weight
|
60
|
+
progressive_max_pairs.positive? ? progressive_max_pairs.round(0) : 0
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.sum_current_profit(current_coins, quote)
|
64
|
+
current_coins = current_coins.select { |s| s.quote_asset == quote }
|
65
|
+
current_coins.sum(&:current_profit)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.sum_potential_profit(current_coins, quote)
|
69
|
+
current_coins = current_coins.select { |s| s.quote_asset == quote }
|
70
|
+
current_coins.sum(&:potential_profit)
|
71
|
+
end
|
72
|
+
|
73
|
+
def percent_to_order
|
74
|
+
@percent_to_order ||= ((sell_price / current_price - 1) * 100)
|
75
|
+
end
|
76
|
+
|
77
|
+
def percent_from_min_to_average
|
78
|
+
@percent_to_min ||= ((min_price / next_average_price - 1) * 100)
|
79
|
+
end
|
80
|
+
|
81
|
+
def percent_to_max
|
82
|
+
@percent_to_max ||= ((sell_price / max_price - 1) * 100)
|
83
|
+
end
|
84
|
+
|
85
|
+
def min_price
|
86
|
+
@min_price ||= account.symbol_min_price(symbol)
|
87
|
+
end
|
88
|
+
|
89
|
+
def max_price
|
90
|
+
@max_price ||= account.symbol_max_price(symbol)
|
91
|
+
end
|
92
|
+
|
93
|
+
def current_profit
|
94
|
+
@current_profit ||= ((current_price - average_price) * volume)
|
95
|
+
end
|
96
|
+
|
97
|
+
def potential_profit
|
98
|
+
((sell_price - average_price) * volume)
|
99
|
+
end
|
100
|
+
|
101
|
+
def current_quote
|
102
|
+
@current_quote ||= (total_quote + current_profit)
|
103
|
+
end
|
104
|
+
|
105
|
+
def sell_up
|
106
|
+
@sell_up ||= ((potential_profit / total_quote) * 100 - 0.15)
|
107
|
+
end
|
108
|
+
|
109
|
+
def next_average_price
|
110
|
+
@next_average_price ||=
|
111
|
+
(buy_price - (buy_price / 100 * (step_averaging - trade_params['buy_down'].to_f))).round(tick_round_size)
|
112
|
+
end
|
113
|
+
|
114
|
+
def next_average_price_percent
|
115
|
+
@next_average_price_percent ||= ((current_price / next_average_price - 1) * 100)
|
116
|
+
end
|
117
|
+
|
118
|
+
def name
|
119
|
+
quote_asset == 'USDT' ? base_asset : symbol
|
120
|
+
end
|
121
|
+
|
122
|
+
def symbol
|
123
|
+
base_asset + quote_asset
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def tick_round_size
|
129
|
+
@tick_round_size ||= tick_size.length - 2
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abot
|
4
|
+
module Info
|
5
|
+
class DatabaseTable
|
6
|
+
DB_PATH = Info::OptionParser.instance.options[:db_path]
|
7
|
+
|
8
|
+
ActiveRecord::Base.establish_connection(
|
9
|
+
adapter: 'sqlite3',
|
10
|
+
database: DB_PATH,
|
11
|
+
)
|
12
|
+
|
13
|
+
BASE_CONNECTION = ActiveRecord::Base.connection
|
14
|
+
API_KEY_TABLE = BASE_CONNECTION.execute('SELECT * FROM api_key').freeze
|
15
|
+
API_KEY = API_KEY_TABLE.first['api'].freeze
|
16
|
+
SECRET_KEY = API_KEY_TABLE.first['secret'].freeze
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def data_coins
|
20
|
+
BASE_CONNECTION.execute('SELECT * FROM symbols WHERE statusOrder IS NOT "NO_ORDER"')
|
21
|
+
end
|
22
|
+
|
23
|
+
def data_settings
|
24
|
+
BASE_CONNECTION.execute('SELECT * FROM trade_params').first
|
25
|
+
end
|
26
|
+
|
27
|
+
def data_daily_profit
|
28
|
+
BASE_CONNECTION.execute("SELECT quote, sum(profit) FROM daily_profit GROUP BY quote")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abot
|
4
|
+
module Info
|
5
|
+
class Coin
|
6
|
+
module CoinDecorator
|
7
|
+
def decorated_sell_up
|
8
|
+
rounded_sell_up = sell_up.round(2)
|
9
|
+
case num_averaging
|
10
|
+
when 1, 2
|
11
|
+
rounded_sell_up > 2 ? Paint[rounded_sell_up, :red] : rounded_sell_up
|
12
|
+
when 3, 4, 5
|
13
|
+
rounded_sell_up > 2 ? rounded_sell_up : Paint[rounded_sell_up, :red]
|
14
|
+
else
|
15
|
+
rounded_sell_up
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def decorated_current_profit
|
20
|
+
rounded_current_profit = if trade_params['quote_asset'].split(' ').count > 1
|
21
|
+
"#{current_profit.round(2)} #{quote_asset}"
|
22
|
+
else
|
23
|
+
current_profit.round(2)
|
24
|
+
end
|
25
|
+
if current_profit >= 0
|
26
|
+
Paint[rounded_current_profit, :green]
|
27
|
+
else
|
28
|
+
Paint[rounded_current_profit, :red]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def decorated_num_averaging
|
33
|
+
Paint[num_averaging, (Helpers::AVERAGE_COLORS[num_averaging] || :red)]
|
34
|
+
end
|
35
|
+
|
36
|
+
def decorated_next_average_price_percent
|
37
|
+
"#{next_average_price_percent.round(2)}%"
|
38
|
+
end
|
39
|
+
|
40
|
+
def decorated_next_average_price
|
41
|
+
"#{next_average_price} (#{decorated_next_average_price_percent})"
|
42
|
+
end
|
43
|
+
|
44
|
+
def decorated_name
|
45
|
+
arrow = if account.symbol_info(symbol).try(:[], :lastPrice).to_f < account.symbol_info(symbol).try(:[], :askPrice).to_f
|
46
|
+
Paint[' ↑', :green]
|
47
|
+
elsif account.symbol_info(symbol).try(:[], :lastPrice).to_f > account.symbol_info(symbol).try(:[], :askPrice).to_f
|
48
|
+
Paint[' ↓', :red]
|
49
|
+
else
|
50
|
+
''
|
51
|
+
end
|
52
|
+
"#{name}(#{account.symbol_info(symbol).try(:[], :priceChangePercent)}%)#{arrow}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def decorated_name_mobile
|
56
|
+
name
|
57
|
+
end
|
58
|
+
|
59
|
+
def decorated_sell_price
|
60
|
+
sell_price
|
61
|
+
end
|
62
|
+
|
63
|
+
def decorated_current_price
|
64
|
+
"#{current_price} (#{percent_to_order.round(2)}%)"
|
65
|
+
end
|
66
|
+
|
67
|
+
def decorated_min_price
|
68
|
+
"#{min_price} (#{percent_from_min_to_average.round(2)}%)"
|
69
|
+
end
|
70
|
+
|
71
|
+
def decorated_max_price
|
72
|
+
"#{max_price} (#{percent_to_max.round(2)}%)"
|
73
|
+
end
|
74
|
+
|
75
|
+
def decorated_current_quote
|
76
|
+
current_quote.round(2)
|
77
|
+
end
|
78
|
+
|
79
|
+
def decorated_potential_profit
|
80
|
+
if trade_params['quote_asset'].split(' ').count > 1
|
81
|
+
"#{potential_profit.round(2)} #{quote_asset}"
|
82
|
+
else
|
83
|
+
potential_profit.round(2)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def decorated_buy_date
|
88
|
+
Time.at(timer.to_s[0..9].to_f).strftime('%d %h %H:%M')
|
89
|
+
rescue StandardError
|
90
|
+
'-'
|
91
|
+
end
|
92
|
+
|
93
|
+
def decorated_timer
|
94
|
+
medidas = ["г", "мес", "д", "ч", "м", "c"]
|
95
|
+
array = [1970, 1, 1, 0, 0, 0]
|
96
|
+
text = ''
|
97
|
+
Time.at(Time.now.to_i - timer.to_s[0..9].to_f).utc.to_a.take(6).reverse.each_with_index do |k, i|
|
98
|
+
case i
|
99
|
+
when 0, 1, 2
|
100
|
+
next if text.blank? && (k - array[i]).zero?
|
101
|
+
|
102
|
+
text = "#{text} #{k - array[i]}#{medidas[i]}"
|
103
|
+
when 3
|
104
|
+
next_text = (k - array[i]).to_s.rjust(2, '0')
|
105
|
+
text = "#{text} #{next_text}"
|
106
|
+
when 4
|
107
|
+
next_text = (k - array[i]).to_s.rjust(2, '0')
|
108
|
+
text = "#{text}:#{next_text}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
text
|
112
|
+
rescue StandardError
|
113
|
+
'-'
|
114
|
+
end
|
115
|
+
|
116
|
+
def decorated_cell(name)
|
117
|
+
str = public_send("decorated_#{name}")
|
118
|
+
value = Paint[str, (Helpers::AVERAGE_COLORS[num_averaging] || :red)]
|
119
|
+
row = { value: value }
|
120
|
+
row[:alignment] = :right if name == :timer
|
121
|
+
row
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abot
|
4
|
+
module Info
|
5
|
+
# helper
|
6
|
+
module Helpers
|
7
|
+
AVERAGE_COLORS = %i[
|
8
|
+
white
|
9
|
+
green
|
10
|
+
cyan
|
11
|
+
yellow
|
12
|
+
magenta
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
def self.daily_profit_str
|
16
|
+
profit = 'Профит сегодня:'
|
17
|
+
DatabaseTable.data_daily_profit.each do |c|
|
18
|
+
tick_size = (c['quote'] == 'BTC' ? 7 : 2)
|
19
|
+
profit += Paint[" #{c['sum(profit)'].round(tick_size)} #{c['quote']};", :green]
|
20
|
+
end
|
21
|
+
profit
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.balance_str(account, current_coins, trade_params)
|
25
|
+
quote_assets = trade_params['quote_asset'].split(' ')
|
26
|
+
result = ''
|
27
|
+
balance_btc = account.current_balance_btc
|
28
|
+
quote_assets.each_with_index do |quote, idx|
|
29
|
+
result += "\n" unless idx.zero?
|
30
|
+
tick_size = (quote == 'BTC' ? 6 : 2)
|
31
|
+
|
32
|
+
balance = quote == 'BTC' ? balance_btc : account.current_balance(balance_btc, quote)
|
33
|
+
balance_wl = account.current_balance_wl(current_coins, quote)
|
34
|
+
|
35
|
+
cb = "Б: #{Paint["#{balance.round(tick_size)} ", :green]}"
|
36
|
+
cbwl = "Б(WL): #{Paint["#{balance_wl.round(tick_size)} ", :green]}"
|
37
|
+
fb = "С: #{Paint["#{account.free_balance(quote).round(tick_size)} " \
|
38
|
+
"(#{account.percent_free_balance(balance_wl, quote)}%)", :green]} "
|
39
|
+
result += (quote + ' ' + cb + cbwl + fb)
|
40
|
+
end
|
41
|
+
result
|
42
|
+
rescue StandardError
|
43
|
+
''
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.potential_balance_str(account, current_coins, trade_params)
|
47
|
+
"П: #{Paint["#{account.potential_balance(current_coins, trade_params).round(2)} USDT", :green]}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.symbol_price_str(account, symbol_name)
|
51
|
+
symbol = account.symbol_info(symbol_name.to_s)
|
52
|
+
return '' if symbol.nil?
|
53
|
+
|
54
|
+
percent_color = symbol[:priceChangePercent].to_f.positive? ? :green : :red
|
55
|
+
"#{symbol_name}: " + Paint["#{symbol[:askPrice].to_f} (#{symbol[:priceChangePercent]}%)", percent_color]
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.check_abot
|
59
|
+
Paint['WARNING: ПРОВЕРЬТЕ БОТА !!!', :black, :red] + "\n" if (Time.now - File.mtime(DatabaseTable::DB_PATH)) > 10
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
module Abot
|
7
|
+
module Info
|
8
|
+
# Класс раширяет возможности стандартного gem'а
|
9
|
+
#
|
10
|
+
class OptionParser < OptionParser
|
11
|
+
class Error < StandardError; end
|
12
|
+
|
13
|
+
Option = Struct.new(:type, :value, :required)
|
14
|
+
private_class_method :new
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
super
|
20
|
+
@options = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def add_help_about_envs
|
26
|
+
envs = @options.select { |_, option| option[:type] == :environment }
|
27
|
+
|
28
|
+
return if envs.empty?
|
29
|
+
|
30
|
+
separator ''
|
31
|
+
separator 'Accessed environments:'
|
32
|
+
envs.each do |key, v|
|
33
|
+
separator ' ' * 4 + [key.to_s.upcase, v[:value]].join('=')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def show_help!
|
38
|
+
show_help = @options.fetch(
|
39
|
+
:help,
|
40
|
+
Option.new(:option, false, false)
|
41
|
+
)
|
42
|
+
|
43
|
+
return unless show_help[:value]
|
44
|
+
|
45
|
+
add_help_about_envs
|
46
|
+
|
47
|
+
print help
|
48
|
+
# получение справочной информации
|
49
|
+
Process.exit! unless ENV.fetch('RUN_TEST', nil)
|
50
|
+
end
|
51
|
+
|
52
|
+
def show_version!
|
53
|
+
show_version = @options.fetch(
|
54
|
+
:version,
|
55
|
+
Option.new(:option, false, false)
|
56
|
+
)
|
57
|
+
|
58
|
+
return unless show_version[:value]
|
59
|
+
|
60
|
+
puts ver
|
61
|
+
# аналогично методу #show_help!
|
62
|
+
Process.exit! unless ENV.fetch('RUN_TEST', nil)
|
63
|
+
end
|
64
|
+
|
65
|
+
def validate!
|
66
|
+
required_options = @options.select do |_, option|
|
67
|
+
option[:required] && option[:value].nil?
|
68
|
+
end
|
69
|
+
|
70
|
+
return if required_options.empty?
|
71
|
+
|
72
|
+
raise Error, <<~ERROR
|
73
|
+
Having problems:
|
74
|
+
#{
|
75
|
+
required_options.map do |key, option|
|
76
|
+
case option[:type]
|
77
|
+
when :environment
|
78
|
+
"- environment '#{key.to_s.upcase}' is required"
|
79
|
+
when :option
|
80
|
+
"- option '#{key}' is required"
|
81
|
+
end
|
82
|
+
end.join("\n")
|
83
|
+
}
|
84
|
+
ERROR
|
85
|
+
end
|
86
|
+
|
87
|
+
public
|
88
|
+
|
89
|
+
def self.instance
|
90
|
+
@instance ||= new
|
91
|
+
|
92
|
+
yield @instance if block_given?
|
93
|
+
|
94
|
+
@instance
|
95
|
+
end
|
96
|
+
|
97
|
+
# Обрабатывает переменную среды окружения и регистрирует его в опциях
|
98
|
+
#
|
99
|
+
# Указанный ключ в аргументе метода, при поиске переменной среды окружения
|
100
|
+
# будет преобразован сл. образом: `:env_key => "ENV_KEY"`
|
101
|
+
#
|
102
|
+
# @param key [Symbol]
|
103
|
+
#
|
104
|
+
# @param default [Object]
|
105
|
+
#
|
106
|
+
# @param required [FalseClass,TrueClass]
|
107
|
+
#
|
108
|
+
def add_env(key, default: nil, required: false)
|
109
|
+
raise Error, 'call after parse' if frozen?
|
110
|
+
|
111
|
+
@options[key] = Option.new(
|
112
|
+
:environment,
|
113
|
+
environment(key.to_s.upcase) || default,
|
114
|
+
required
|
115
|
+
)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Добавляет обработчик опций командной строки
|
119
|
+
#
|
120
|
+
# @param key [Symbol]
|
121
|
+
#
|
122
|
+
# @param args [Array]
|
123
|
+
#
|
124
|
+
# @param default [Object]
|
125
|
+
#
|
126
|
+
# @param required [FalseClass,TrueClass]
|
127
|
+
#
|
128
|
+
def add_option(key, *args, default: nil, required: false)
|
129
|
+
raise Error, 'call after parse' if frozen?
|
130
|
+
|
131
|
+
@options[key] = Option.new(
|
132
|
+
:option,
|
133
|
+
default,
|
134
|
+
required
|
135
|
+
)
|
136
|
+
|
137
|
+
on(*args) { |value| @options[key][:value] = value }
|
138
|
+
end
|
139
|
+
|
140
|
+
# Выполняет обработку входящих аргументов и читает конфигурационый файл
|
141
|
+
#
|
142
|
+
# @return [void]
|
143
|
+
#
|
144
|
+
def final!(args = ARGV)
|
145
|
+
raise Error, 'duplicate call #final!' if frozen?
|
146
|
+
|
147
|
+
parse args
|
148
|
+
load @options[:config_file] ? @options[:config_file][:value] : nil
|
149
|
+
# заполняет значение до заморозки экземпляра класса
|
150
|
+
@banner = "Usage: #{program_name} [options]" if @banner.nil?
|
151
|
+
|
152
|
+
freeze
|
153
|
+
|
154
|
+
show_help!
|
155
|
+
show_version!
|
156
|
+
validate!
|
157
|
+
end
|
158
|
+
|
159
|
+
# Возвращает полученные в результате работы парсера опции
|
160
|
+
#
|
161
|
+
# @param keys_filter [Array<Symbol>] исключает отсуствующие ключи
|
162
|
+
#
|
163
|
+
# @return [Hash]
|
164
|
+
#
|
165
|
+
def options(keys_filter = [])
|
166
|
+
raise Error, 'call before #final!' unless frozen?
|
167
|
+
|
168
|
+
result = @options.each_with_object({}) do |option, acc|
|
169
|
+
acc[option[0]] = option[1][:value] unless option[1][:value].nil?
|
170
|
+
end
|
171
|
+
|
172
|
+
return result if keys_filter.empty?
|
173
|
+
|
174
|
+
result.select { |k, _| keys_filter.include? k }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'helpers'
|
3
|
+
|
4
|
+
module Abot
|
5
|
+
module Info
|
6
|
+
class Table < Terminal::Table
|
7
|
+
include Abot::Info::Helpers
|
8
|
+
|
9
|
+
attr_reader :current_coins, :account, :rate_coins, :columns, :trade_params
|
10
|
+
|
11
|
+
ALL_HEADINGS = {
|
12
|
+
name: 'Coin',
|
13
|
+
name_mobile: 'Coin',
|
14
|
+
sell_up: "sell\nup",
|
15
|
+
sell_price: 'Ордер',
|
16
|
+
max_price: 'Максимум 24h',
|
17
|
+
min_price: 'Минимум 24h',
|
18
|
+
current_price: 'Тек.цена',
|
19
|
+
next_average_price: "Следующее\nусреднение",
|
20
|
+
current_quote: "Тек.\nстоим.",
|
21
|
+
current_profit: "Тек.\nпрофит",
|
22
|
+
potential_profit: "Пот.\nпрофит",
|
23
|
+
buy_date: "Дата покупки",
|
24
|
+
timer: 'Время',
|
25
|
+
}.freeze
|
26
|
+
HEADINGS_COINS_TABLE_DEFAULT = %i[
|
27
|
+
name
|
28
|
+
sell_up
|
29
|
+
sell_price
|
30
|
+
max_price
|
31
|
+
current_price
|
32
|
+
min_price
|
33
|
+
next_average_price
|
34
|
+
current_quote
|
35
|
+
buy_date
|
36
|
+
timer
|
37
|
+
current_profit
|
38
|
+
potential_profit
|
39
|
+
].freeze
|
40
|
+
HEADINGS_COINS_TABLE_MOBILE = %i[
|
41
|
+
name_mobile
|
42
|
+
sell_up
|
43
|
+
sell_price
|
44
|
+
current_price
|
45
|
+
next_average_price
|
46
|
+
].freeze
|
47
|
+
COLUMN_WITH_TOTAL = %i[
|
48
|
+
current_profit
|
49
|
+
potential_profit
|
50
|
+
].freeze
|
51
|
+
|
52
|
+
def initialize(options)
|
53
|
+
@account = options[:account]
|
54
|
+
@columns = options[:columns] || HEADINGS_COINS_TABLE_DEFAULT
|
55
|
+
@rate_coins = options.fetch(:rate_coins, [])
|
56
|
+
@trade_params ||= DatabaseTable.data_settings
|
57
|
+
@current_coins = Coin.current_coins(account, trade_params)
|
58
|
+
|
59
|
+
|
60
|
+
options = {
|
61
|
+
headings: columns.map { |h| { value: ALL_HEADINGS[h], alignment: :center } },
|
62
|
+
style: { border_x: '=' }
|
63
|
+
}
|
64
|
+
super options
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.last_columns_set(col)
|
68
|
+
col = col.map { |m| ALL_HEADINGS.keys.include?(m) ? m : nil }.compact
|
69
|
+
COLUMN_WITH_TOTAL.each { |e| col << col.delete(e) }
|
70
|
+
col.compact
|
71
|
+
end
|
72
|
+
|
73
|
+
def generate
|
74
|
+
sorted_current_coins.each do |coin|
|
75
|
+
row = columns.map do |m|
|
76
|
+
coin.decorated_cell(m)
|
77
|
+
end
|
78
|
+
add_row row
|
79
|
+
end
|
80
|
+
add_separator
|
81
|
+
add_row row_total
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
def total_averages
|
86
|
+
av_arr = []
|
87
|
+
current_coins.each { |coin| av_arr[coin.num_averaging] = av_arr[coin.num_averaging].to_i + 1 }
|
88
|
+
count = av_arr.compact.sum
|
89
|
+
av_arr = av_arr.map.with_index do |av, idx|
|
90
|
+
Paint["#{idx}: #{av.to_i}", (Helpers::AVERAGE_COLORS[idx] || :red)] if av.to_i != 0
|
91
|
+
end.compact
|
92
|
+
av_arr << "∑: #{count}"
|
93
|
+
# av_arr << "M: #{available_coins_number}" if available_coins_number
|
94
|
+
av_arr.join("\n")
|
95
|
+
end
|
96
|
+
|
97
|
+
def info_str
|
98
|
+
"#{Helpers.check_abot}" \
|
99
|
+
"#{Helpers.daily_profit_str}\n" \
|
100
|
+
"#{Helpers.balance_str(account, current_coins, trade_params)}\n" \
|
101
|
+
"#{Helpers.potential_balance_str(account, current_coins, trade_params)}\n" \
|
102
|
+
"#{rate_coins_str}"
|
103
|
+
end
|
104
|
+
|
105
|
+
def rate_coins_str
|
106
|
+
result = ''
|
107
|
+
rate_coins.each_with_index do |c, idx|
|
108
|
+
number = colspan / 2
|
109
|
+
number += 1 if number.zero?
|
110
|
+
|
111
|
+
result += if idx != 0 && (idx % number).zero?
|
112
|
+
"\n"
|
113
|
+
elsif idx != 0
|
114
|
+
' '
|
115
|
+
else
|
116
|
+
''
|
117
|
+
end
|
118
|
+
str = Helpers.symbol_price_str(account, c.upcase)
|
119
|
+
str.slice!('USDT')
|
120
|
+
result += str
|
121
|
+
end
|
122
|
+
result
|
123
|
+
end
|
124
|
+
|
125
|
+
def available_coins_number
|
126
|
+
@available_coins_number ||= Coin.available_coins_number(
|
127
|
+
current_coins,
|
128
|
+
trade_params,
|
129
|
+
account.percent_free_balance(current_coins)
|
130
|
+
)
|
131
|
+
end
|
132
|
+
|
133
|
+
def colspan
|
134
|
+
(columns - COLUMN_WITH_TOTAL).count - 1
|
135
|
+
end
|
136
|
+
|
137
|
+
def row_total
|
138
|
+
row_total = [
|
139
|
+
total_averages,
|
140
|
+
{ value: info_str, colspan: colspan }
|
141
|
+
]
|
142
|
+
quote_assets = trade_params['quote_asset'].split(' ')
|
143
|
+
cell_current_profit = ''
|
144
|
+
cell_potential_profit = ''
|
145
|
+
quote_assets.each do |qa|
|
146
|
+
if quote_assets.count > 1
|
147
|
+
cell_current_profit += "#{Coin.sum_current_profit(current_coins, qa).round(2)} #{qa}\n"if columns.include?(:current_profit)
|
148
|
+
cell_potential_profit += "#{Coin.sum_potential_profit(current_coins, qa).round(2)} #{qa}\n" if columns.include?(:potential_profit)
|
149
|
+
else
|
150
|
+
cell_current_profit += Coin.sum_current_profit(current_coins, qa).round(2).to_s if columns.include?(:current_profit)
|
151
|
+
cell_potential_profit += Coin.sum_potential_profit(current_coins, qa).round(2).to_s if columns.include?(:potential_profit)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
row_total << cell_current_profit if columns.include?(:current_profit)
|
155
|
+
row_total << cell_potential_profit if columns.include?(:potential_profit)
|
156
|
+
row_total
|
157
|
+
end
|
158
|
+
|
159
|
+
def sorted_current_coins
|
160
|
+
current_coins.sort_by(&:percent_to_order)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
metadata
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: abot-info
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- w_dmitrii
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-07-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.17'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.17'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activerecord
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 6.1.3
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 6.1.3
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: paint
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 2.2.1
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 2.2.1
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: sqlite3
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 1.4.2
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 1.4.2
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: terminal-table
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 3.0.1
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 3.0.1
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: binance-ruby
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 1.3.1
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 1.3.1
|
125
|
+
description:
|
126
|
+
email:
|
127
|
+
- wiz.work2021@gmail.com
|
128
|
+
executables:
|
129
|
+
- abot-info
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files:
|
132
|
+
- LICENSE
|
133
|
+
- README.md
|
134
|
+
files:
|
135
|
+
- Gemfile
|
136
|
+
- LICENSE
|
137
|
+
- README.md
|
138
|
+
- Rakefile
|
139
|
+
- abot-info.gemspec
|
140
|
+
- exe/abot-info
|
141
|
+
- lib/abot/info.rb
|
142
|
+
- lib/abot/info/binance_account.rb
|
143
|
+
- lib/abot/info/coin.rb
|
144
|
+
- lib/abot/info/database_table.rb
|
145
|
+
- lib/abot/info/decorators/coin_decorator.rb
|
146
|
+
- lib/abot/info/helpers.rb
|
147
|
+
- lib/abot/info/option_parser.rb
|
148
|
+
- lib/abot/info/table.rb
|
149
|
+
- lib/abot/info/version.rb
|
150
|
+
- spec/abot/statistics_spec.rb
|
151
|
+
homepage:
|
152
|
+
licenses:
|
153
|
+
- MIT
|
154
|
+
metadata: {}
|
155
|
+
post_install_message:
|
156
|
+
rdoc_options: []
|
157
|
+
require_paths:
|
158
|
+
- lib
|
159
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
160
|
+
requirements:
|
161
|
+
- - ">="
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
version: 2.6.6
|
164
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
requirements: []
|
170
|
+
rubygems_version: 3.0.9
|
171
|
+
signing_key:
|
172
|
+
specification_version: 4
|
173
|
+
summary: Abot Info
|
174
|
+
test_files:
|
175
|
+
- spec/abot/statistics_spec.rb
|