capwatch 0.1.13 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 37394be004f4cec6e948ddb4b59cf484ec764773
4
- data.tar.gz: 305494f7086c7cbc00253428075f5cdf1830170d
3
+ metadata.gz: 5db899bf97a10c058d9b81f91795a1f2500cdd63
4
+ data.tar.gz: 7d5848e31de157b5d24b49ea9d51131e71167e18
5
5
  SHA512:
6
- metadata.gz: a05c7e5f0a8204a3dea47f39d3f937726b0eb11054d40005d202723d4d264057b9db9b1fae63b476516dfff944187d674505e8009a4cc4036b8a18a24e9212ff
7
- data.tar.gz: 33429b9c8095c53cb77d00b14eb3bd747d4c08be8b099a661f4c2ee6a507d6ce5ce0c8e0a9fb8b682df30b3343a13c270f1c68ea97b7f1f4bf615a12593f20ca
6
+ metadata.gz: e83306472382128dd16317851affd3aa981f9e62d29b8992aa0cd36d378a4a6b2801d9c0346f8199fd5b37dd50c0b1f7018d1f218e076220ab9cea328bd20e88
7
+ data.tar.gz: f10ea26c4fbd3e7ef0703c5ed23fbae7c887004f8a256370d8702d0dc845ca2916e9046cf08e60d3a692fbabee0b4552f3e0ee970eb73395eaf7c311a9e186a4
data/Gemfile CHANGED
@@ -1,8 +1,11 @@
1
- source 'https://rubygems.org'
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
2
4
 
3
5
  # Specify your gem's dependencies in capwatch.gemspec
4
6
  gemspec
5
7
 
6
8
  group :test do
7
- gem 'coveralls', require: false
9
+ gem "coveralls", require: false
10
+ gem "webmock"
8
11
  end
data/README.md CHANGED
@@ -11,39 +11,18 @@ Watch your cryptoportfolio in a console
11
11
  ## Installation
12
12
 
13
13
  $ gem install capwatch
14
-
15
- ```bash
16
- cat <<EOT > ~/.capwatch
17
- {
18
- "name": "Basic Fund",
19
- "symbols": {
20
- "MAID": 25452.47,
21
- "GAME": 22253.51,
22
- "NEO": 3826.53,
23
- "FCT": 525.67875,
24
- "SC": 4152770,
25
- "DCR": 453.22,
26
- "BTC": 8.219,
27
- "ETH": 166.198,
28
- "KMD": 19056.20,
29
- "LSK": 5071.42
30
- }
31
- }
32
- EOT
33
- ```
34
-
35
14
  $ capwatch
36
15
 
16
+ Don't forget to edit `~/.capwatch` with the amount of cryptocurrencies that you hold.
37
17
 
38
18
  ## Telegram
39
19
 
40
- If you want to get portfolio notifications on demand to your telegram, you'll need:
20
+ If you want to get portfolio notifications on demand into your telegram, you'll need:
41
21
 
42
22
  1. Create a telegram bot via [BotFather](https://core.telegram.org/bots)
43
23
  2. Get the bot `token`
44
24
  3. Start capwatch with the bot `token` in hand
45
25
 
46
-
47
26
  $capwatch -e <bot_token>
48
27
 
49
28
  Currently Capwatch supports only two commands
@@ -53,14 +32,10 @@ Currently Capwatch supports only two commands
53
32
 
54
33
  Remember to start it on a server in a tmux window or as a daemon.
55
34
 
56
- ## Fund Examples
57
-
58
- Fund examples can be found [here](funds/demo)
35
+ ## Data Providers
59
36
 
60
- Demo funds taken from www.bluemagic.info
37
+ - http://coinmarketcap.com
61
38
 
62
- Data is being processed from from http://coinmarketcap.com
39
+ ## Demo Funds
63
40
 
64
- ## Todo
65
- - [ ] Write Tests
66
- - [ ] Re-factor the table class
41
+ Fund examples can be found [here](spec/fixtures/funds) which were taken from [here](www.bluemagic.info)
data/Rakefile CHANGED
@@ -1,5 +1,7 @@
1
- require 'bundler/gem_tasks'
2
- require 'rspec/core/rake_task'
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require 'bundler/setup'
4
- require 'capwatch'
4
+ require "bundler/setup"
5
+ require "capwatch"
5
6
 
6
7
  # You can add fixtures and/or initialization code here to make experimenting
7
8
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +11,5 @@ require 'capwatch'
10
11
  # require "pry"
11
12
  # Pry.start
12
13
 
13
- require 'irb'
14
+ require "irb"
14
15
  IRB.start(__FILE__)
@@ -1,33 +1,35 @@
1
1
  # coding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
- lib = File.expand_path('../lib', __FILE__)
4
+ lib = File.expand_path("../lib", __FILE__)
4
5
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- require 'capwatch/version'
6
+ require "capwatch/version"
6
7
 
7
8
  Gem::Specification.new do |spec|
8
- spec.name = 'capwatch'
9
+ spec.name = "capwatch"
9
10
  spec.version = Capwatch::VERSION
10
- spec.authors = ['Nick Bugaiov']
11
- spec.email = ['nick@bugaiov.com']
11
+ spec.authors = ["Nick Bugaiov"]
12
+ spec.email = ["nick@bugaiov.com"]
12
13
 
13
- spec.summary = 'Cryptoportfolio watch'
14
- spec.description = 'Watches your cryptoportfolio'
15
- spec.homepage = 'https://cryptowatch.one'
16
- spec.license = 'MIT'
14
+ spec.summary = "Cryptoportfolio watch"
15
+ spec.description = "Watches your cryptoportfolio"
16
+ spec.homepage = "https://cryptowatch.one"
17
+ spec.license = "MIT"
17
18
 
18
19
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
20
  f.match(%r{^(test|spec|features)/})
20
21
  end
21
22
 
22
- spec.bindir = 'exe'
23
+ spec.bindir = "exe"
23
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
- spec.require_paths = ['lib']
25
+ spec.require_paths = ["lib"]
25
26
 
26
- spec.add_dependency 'terminal-table', '~> 1.8'
27
- spec.add_dependency 'colorize', '~> 0.8'
28
- spec.add_dependency 'telegram_bot', '~> 0.0.7'
27
+ spec.add_dependency "terminal-table", "~> 1.8"
28
+ spec.add_dependency "colorize", "~> 0.8"
29
+ spec.add_dependency "telegram_bot", "~> 0.0.7"
29
30
 
30
- spec.add_development_dependency 'bundler', '~> 1.15'
31
- spec.add_development_dependency 'rake', '~> 10.0'
32
- spec.add_development_dependency 'rspec', '~> 3.0'
31
+ spec.add_development_dependency "bundler", "~> 1.15"
32
+ spec.add_development_dependency "pry", "~> 0.10"
33
+ spec.add_development_dependency "rake", "~> 10.0"
34
+ spec.add_development_dependency "rspec", "~> 3.0"
33
35
  end
@@ -1,17 +1,29 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require 'capwatch'
4
+ require "bundler/setup"
5
+ require "capwatch"
4
6
  include Capwatch
5
7
 
6
8
  options = CLI.parse(ARGV)
7
9
 
10
+ trap("SIGINT") {
11
+ system("clear")
12
+ exit 130
13
+ }
14
+
8
15
  if options.telegram
9
- Telegram.new(token: options.telegram).start
16
+ Telegram.new(options.telegram).start
10
17
  else
11
18
  loop do
12
- table = Calculator.fund_hash(FundParser.new.fund, CoinMarketCap.fetch)
13
- system('clear')
14
- puts Console.draw_table(table)
19
+ config = FundConfig.new
20
+ provider = Providers::CoinMarketCap.new
21
+ fund = Fund.new(provider: provider, config: config)
22
+ system("clear")
23
+ puts fund.console_table
24
+ puts "\nHey there! This is a Demo Fund. Please set up your fund by editing the \"#{FundConfig::DEMO_CONFIG_FILE}\" in your home directory".green if config.demo?
15
25
  sleep options.tick
16
26
  end
17
27
  end
28
+
29
+
@@ -1,11 +1,16 @@
1
- require 'colorize'
2
- require 'terminal-table'
3
- require 'telegram_bot'
1
+ # frozen_string_literal: true
4
2
 
5
- require 'capwatch/version'
6
- require 'capwatch/fundparser'
7
- require 'capwatch/coinmarketcap'
8
- require 'capwatch/calculator'
9
- require 'capwatch/cli'
10
- require 'capwatch/console'
11
- require 'capwatch/telegram'
3
+ require "colorize"
4
+ require "terminal-table"
5
+ require "telegram_bot"
6
+
7
+ require "capwatch/version"
8
+ require "capwatch/fund"
9
+ require "capwatch/fund_config"
10
+ require "capwatch/fund_calculator"
11
+ require "capwatch/providers/coin_market_cap"
12
+ require "capwatch/exchange"
13
+ require "capwatch/cli"
14
+ require "capwatch/coin"
15
+ require "capwatch/console"
16
+ require "capwatch/telegram"
@@ -1,21 +1,25 @@
1
- require 'optparse'
2
- require 'ostruct'
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+ require "ostruct"
3
5
 
4
6
  module Capwatch
5
7
  class CLI
8
+
6
9
  def self.parse(args)
7
10
  options = OpenStruct.new
8
11
  options.tick = 60 * 5
9
12
  opt_parser = OptionParser.new do |opts|
10
- opts.on('-t', '--tick [Integer]', Integer, 'Tick Interval') do |t|
13
+ opts.on("-t", "--tick [Integer]", Integer, "Tick Interval") do |t|
11
14
  options.tick = t
12
15
  end
13
- opts.on('-e', '--telegram-token=', String) do |val|
16
+ opts.on("-e", "--telegram-token=", String) do |val|
14
17
  options.telegram = val
15
18
  end
16
19
  end
17
20
  opt_parser.parse!(args)
18
21
  options
19
22
  end
23
+
20
24
  end
21
25
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capwatch
4
+ class Coin
5
+ attr_accessor :name, :symbol, :quantity,
6
+ :price_usd, :price_btc,
7
+ :distribution,
8
+ :percent_change_1h,
9
+ :percent_change_24h,
10
+ :percent_change_7d
11
+
12
+ def initialize
13
+ yield self if block_given?
14
+ end
15
+
16
+ def value_btc
17
+ price_btc * quantity
18
+ end
19
+
20
+ def value_usd
21
+ price_usd * quantity
22
+ end
23
+
24
+ def value_eth
25
+ price_eth * quantity
26
+ end
27
+
28
+ def price_eth
29
+ price_btc / Exchange.rate_for("ETH")
30
+ end
31
+
32
+ def serialize
33
+ {
34
+ symbol: symbol,
35
+ name: name,
36
+ quantity: quantity,
37
+ price_usd: price_usd,
38
+ price_btc: price_btc,
39
+ distribution: distribution,
40
+ percent_change_1h: percent_change_1h,
41
+ percent_change_24h: percent_change_24h,
42
+ percent_change_7d: percent_change_7d,
43
+ value_btc: value_btc,
44
+ value_usd: value_usd,
45
+ value_eth: value_eth,
46
+ price_eth: price_eth,
47
+ }
48
+ end
49
+
50
+ end
51
+ end
@@ -1,91 +1,113 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Capwatch
2
4
  class Console
3
- def self.format_table(hash)
4
- hash[:table].each do |x|
5
- x[1] = format_usd(x[1])
6
- x[3] = format_usd(x[3])
7
- x[4] = format_btc(x[4])
8
- x[5] = format_eth(x[5])
9
- x[6] = format_percent(x[6])
10
- x[7] = format_percent(x[7])
11
- x[8] = format_percent(x[8])
12
- end
13
- hash[:footer][3] = format_usd(hash[:footer][3])
14
- hash[:footer][4] = format_btc(hash[:footer][4])
15
- hash[:footer][5] = format_eth(hash[:footer][5])
16
- hash[:footer][7] = format_percent(hash[:footer][7])
17
- hash[:footer][8] = format_percent(hash[:footer][8])
18
- hash
5
+
6
+ attr_accessor :name, :body, :totals
7
+
8
+ def initialize(name, body, totals)
9
+ @name = name
10
+ @body = format_body(body)
11
+ @totals = format_totals(totals)
19
12
  end
20
13
 
21
- def self.colorize_table(hash)
22
- hash[:table].each do |x|
23
- x[7] = condition_color(x[7])
24
- x[8] = condition_color(x[8])
14
+ def format_body(body)
15
+ JSON.parse(body).sort_by! { |e| -e["value_btc"].to_f }.map do |coin|
16
+ [
17
+ coin["name"],
18
+ Formatter.format_usd(coin["price_usd"]),
19
+ coin["quantity"],
20
+ Formatter.format_percent(coin["distribution"].to_f * 100),
21
+ Formatter.format_btc(coin["value_btc"]),
22
+ Formatter.format_eth(coin["value_eth"]),
23
+ Formatter.condition_color(Formatter.format_percent(coin["percent_change_24h"])),
24
+ Formatter.condition_color(Formatter.format_percent(coin["percent_change_7d"])),
25
+ Formatter.format_usd(coin["value_usd"])
26
+ ]
25
27
  end
26
- hash[:footer][7] = condition_color(hash[:footer][7])
27
- hash[:footer][8] = condition_color(hash[:footer][8])
28
- hash
29
28
  end
30
29
 
31
- def self.draw_table(hash)
32
- hash = colorize_table(format_table(hash))
33
- table = Terminal::Table.new do |t|
34
- t.title = hash[:title].upcase
30
+ def format_totals(totals)
31
+ [
32
+ "",
33
+ "",
34
+ "",
35
+ "",
36
+ Formatter.format_btc(totals[:value_btc]),
37
+ Formatter.format_eth(totals[:value_eth]),
38
+ Formatter.condition_color(Formatter.format_percent(totals[:percent_change_24h])),
39
+ Formatter.condition_color(Formatter.format_percent(totals[:percent_change_7d])),
40
+ Formatter.format_usd(totals[:value_usd]).bold
41
+ ]
42
+ end
43
+
44
+ def draw_table
45
+ table = Terminal::Table.new do |t|
46
+ t.title = name.upcase
35
47
  t.style = {
36
48
  border_top: false,
37
49
  border_bottom: false,
38
- border_y: '',
39
- border_i: '',
50
+ border_y: "",
51
+ border_i: "",
40
52
  padding_left: 1,
41
53
  padding_right: 1
42
54
  }
43
55
  t.headings = [
44
- 'ASSET',
45
- 'PRICE',
46
- 'QUANTITY',
47
- 'VALUE (USD)',
48
- 'VALUE (BTC)',
49
- 'VALUE (ETH)',
50
- 'DIST %',
51
- '24H %',
52
- '7D %'
56
+ "ASSET",
57
+ "PRICE",
58
+ "QUANTITY",
59
+ "DIST %",
60
+ "BTC",
61
+ "ETH",
62
+ "24H %",
63
+ "7D %",
64
+ "USD"
53
65
  ]
54
- hash[:table].each do |x|
66
+ body.each do |x|
55
67
  t << x
56
68
  end
57
69
  t.add_separator
58
- t.add_row hash[:footer]
70
+ t.add_row totals
59
71
  end
60
72
 
61
73
  table
62
74
  end
63
75
 
64
- def self.format_usd(n)
65
- '$' + n.round(2).to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
66
- end
67
76
 
68
- def self.format_btc(value)
69
- format('฿%.2f', value)
70
- end
77
+ class Formatter
71
78
 
72
- def self.format_eth(value)
73
- format('Ξ%.2f', value)
74
- end
79
+ class << self
75
80
 
76
- def self.format_percent(value)
77
- format('%.2f%', value)
78
- end
81
+ def format_usd(n)
82
+ "$" + n.to_f.round(2).to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
83
+ end
84
+
85
+ def format_btc(value)
86
+ format("฿%.2f", value)
87
+ end
88
+
89
+ def format_eth(value)
90
+ format("Ξ%.2f", value)
91
+ end
92
+
93
+ def format_percent(value)
94
+ format("%.2f%", value.to_f)
95
+ end
96
+
97
+ def condition_color(value)
98
+ percent_value = value.to_f
99
+ if percent_value > 1
100
+ value.green
101
+ elsif percent_value < 0
102
+ value.red
103
+ else
104
+ value.green
105
+ end
106
+ end
79
107
 
80
- def self.condition_color(value)
81
- percent_value = value.to_f
82
- if percent_value > 1
83
- value.green
84
- elsif percent_value < 0
85
- value.red
86
- else
87
- value.green
88
108
  end
89
- end
109
+
110
+ end # class Formatter
111
+
90
112
  end
91
113
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capwatch
4
+ class Exchange
5
+
6
+ @@rates = {}
7
+
8
+ def self.rate_for(symbol)
9
+ raise "No Exchange Rate for #{symbol}" if @@rates[symbol].nil?
10
+ @@rates[symbol]
11
+ end
12
+
13
+ def self.rate(symbol, value)
14
+ @@rates[symbol] = value
15
+ end
16
+
17
+ def self.rates
18
+ @@rates
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capwatch
4
+ class Fund
5
+ attr_accessor :provider, :config, :coins, :positions
6
+
7
+ def initialize(provider:, config:)
8
+ @provider = provider
9
+ @config = config
10
+ @positions = config.positions
11
+ @coins = config.coins
12
+ build
13
+ end
14
+
15
+ def [](symbol)
16
+ coins.find { |coin| coin.symbol == symbol }
17
+ end
18
+
19
+ def value_btc
20
+ coins.map(&:value_btc).sum
21
+ end
22
+
23
+ def value_usd
24
+ coins.map(&:value_usd).sum
25
+ end
26
+
27
+ def value_eth
28
+ coins.map(&:value_eth).sum
29
+ end
30
+
31
+ def percent_change_1h
32
+ coins.map { |coin| coin.percent_change_1h * coin.distribution }.sum
33
+ end
34
+
35
+ def percent_change_24h
36
+ coins.map { |coin| coin.percent_change_24h * coin.distribution }.sum
37
+ end
38
+
39
+ def percent_change_7d
40
+ coins.map { |coin| coin.percent_change_7d * coin.distribution }.sum
41
+ end
42
+
43
+ def build
44
+ calculator.assign_quantity
45
+ calculator.assign_prices
46
+ calculator.distribution
47
+ end
48
+
49
+ def calculator
50
+ @calculator ||= FundCalculator.new(self)
51
+ end
52
+
53
+ def serialize
54
+ coins.map { |coin| coin.serialize }.to_json
55
+ end
56
+
57
+ def fund_totals
58
+ {
59
+ value_usd: value_usd,
60
+ value_btc: value_btc,
61
+ value_eth: value_eth,
62
+ percent_change_24h: percent_change_24h,
63
+ percent_change_7d: percent_change_7d
64
+ }
65
+ end
66
+
67
+ def console_table
68
+ Console.new(name = config.name, body = serialize, totals = fund_totals).draw_table
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capwatch
4
+ class FundCalculator
5
+
6
+ attr_accessor :fund
7
+
8
+ def initialize(fund)
9
+ @fund = fund
10
+ end
11
+
12
+ def assign_quantity
13
+ fund.coins.each do |coin|
14
+ coin.quantity = fund.positions[coin.symbol]
15
+ end
16
+ end
17
+
18
+ def assign_prices
19
+ fund.coins.each do |coin|
20
+ fund.provider.update_coin(coin)
21
+ end
22
+ end
23
+
24
+ def distribution
25
+ fund.coins.each do |coin|
26
+ coin.distribution = coin.value_btc / fund.value_btc
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capwatch
4
+ class FundConfig
5
+
6
+ DEMO_CONFIG_NAME = "Your Demo Fund"
7
+ DEMO_CONFIG_FILE = "~/.capwatch"
8
+
9
+ attr_accessor :name, :positions, :config_path
10
+
11
+ def initialize(config_path = nil)
12
+ @config_path = config_path || File.expand_path(DEMO_CONFIG_FILE)
13
+ demo_config! unless config_exists?
14
+ end
15
+
16
+ def positions
17
+ @positions ||= parsed_config["symbols"]
18
+ end
19
+
20
+ def name
21
+ @name ||= parsed_config["name"]
22
+ end
23
+
24
+ def parsed_config
25
+ parse @config_path
26
+ end
27
+
28
+ def coins
29
+ positions.map do |symbol, quantity|
30
+ Coin.new do |coin|
31
+ coin.symbol = symbol
32
+ coin.quantity = quantity
33
+ end
34
+ end
35
+ end
36
+
37
+ def demo?
38
+ name == DEMO_CONFIG_NAME
39
+ end
40
+
41
+ private
42
+
43
+ def open_config(path)
44
+ File.open(path).read
45
+ end
46
+
47
+ def parse(path)
48
+ JSON.parse open_config(path)
49
+ end
50
+
51
+ def config_exists?
52
+ File.exist? @config_path
53
+ end
54
+
55
+ def demo_fund
56
+ file_path = File.join(__dir__, "..", "..", "spec", "fixtures", "funds", "basic.json")
57
+ demo_fund = File.expand_path(file_path)
58
+ File.open(demo_fund).read
59
+ end
60
+
61
+ def demo_config!
62
+ @config_path = File.expand_path(@config_path)
63
+ File.open(@config_path, "w") do |file|
64
+ file.write(demo_fund.gsub!("Basic Fund", DEMO_CONFIG_NAME))
65
+ end
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "open-uri"
5
+
6
+ module Capwatch
7
+ module Providers
8
+ class CoinMarketCap
9
+
10
+ attr_accessor :body
11
+
12
+ NoCoinInProvider = Class.new(RuntimeError)
13
+
14
+ TICKER_URL = "https://api.coinmarketcap.com/v1/ticker/"
15
+
16
+ def fetched_json
17
+ response = parse(fetch)
18
+ update_rates(response)
19
+ response
20
+ end
21
+
22
+ def fetch
23
+ @body ||= open(TICKER_URL).read
24
+ end
25
+
26
+ def parse(response)
27
+ JSON.parse(response)
28
+ end
29
+
30
+ def update_rates(response)
31
+ response.each do |coin_json|
32
+ Capwatch::Exchange.rate(
33
+ coin_json['symbol'],
34
+ coin_json["price_btc"].to_f
35
+ )
36
+ end
37
+ end
38
+
39
+ def update_coin(coin)
40
+ provider_coin = fetched_json.find { |c| c["symbol"] == coin.symbol }
41
+ fail NoCoinInProvider, "No #{coin.symbol} in provider response" if provider_coin.nil?
42
+ coin.name = provider_coin["name"]
43
+ coin.price_usd = provider_coin["price_usd"].to_f
44
+ coin.price_btc = provider_coin["price_btc"].to_f
45
+ coin.percent_change_1h = provider_coin["percent_change_1h"].to_f
46
+ coin.percent_change_24h = provider_coin["percent_change_24h"].to_f
47
+ coin.percent_change_7d = provider_coin["percent_change_7d"].to_f
48
+ end
49
+
50
+ end # class CoinMarketCap
51
+
52
+ end # module Providers
53
+
54
+ end
@@ -1,15 +1,57 @@
1
- require 'logger'
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+ require "logger"
2
5
 
3
6
  module Capwatch
7
+
4
8
  class Telegram
5
9
 
6
- attr_reader :logger, :bot, :fund
10
+ attr_reader :logger, :bot, :config
7
11
 
8
- def initialize(fund: FundParser.new.fund, token:)
9
- @fund = fund
12
+ def initialize(token)
13
+ @config = config
10
14
  @logger = Logger.new(STDOUT, Logger::DEBUG)
11
- @logger.debug 'Starting telegram bot...'
15
+ @logger.debug "Starting telegram bot..."
12
16
  @bot = TelegramBot.new(token: token)
17
+ @console_formatter = Capwatch::Console::Formatter
18
+ end
19
+
20
+ def new_fund
21
+ config = FundConfig.new
22
+ provider = Providers::CoinMarketCap.new
23
+ Fund.new(provider: provider, config: config)
24
+ end
25
+
26
+ def template(name)
27
+ File.open(File.expand_path("#{__dir__}/../templates/#{name}")).read
28
+ end
29
+
30
+ def reply_cap
31
+ fund = new_fund
32
+ ERB.new(template("cap.erb")).result(binding)
33
+ end
34
+
35
+ def reply_watch
36
+ fund = new_fund
37
+ body = format_body(fund.serialize)
38
+ ERB.new(template("watch.erb")).result(binding)
39
+ end
40
+
41
+ def format_body(body)
42
+ JSON.parse(body).sort_by! { |e| -e["value_btc"].to_f }.map do |coin|
43
+ [
44
+ coin["name"],
45
+ Console::Formatter.format_usd(coin["price_usd"]),
46
+ coin["quantity"],
47
+ Console::Formatter.format_percent(coin["distribution"].to_f * 100),
48
+ Console::Formatter.format_btc(coin["value_btc"]),
49
+ Console::Formatter.format_eth(coin["value_eth"]),
50
+ Console::Formatter.format_percent(coin["percent_change_24h"]),
51
+ Console::Formatter.format_percent(coin["percent_change_7d"]),
52
+ Console::Formatter.format_usd(coin["value_usd"])
53
+ ]
54
+ end
13
55
  end
14
56
 
15
57
  def start
@@ -19,26 +61,21 @@ module Capwatch
19
61
 
20
62
  message.reply do |reply|
21
63
  begin
64
+
22
65
  case command
66
+
23
67
  when /\/cap/i
24
- table = Console.format_table(Calculator.fund_hash(fund, CoinMarketCap.fetch))
25
- reply.text = table[:footer].reject(&:empty?).join("\n")
68
+ reply.text = reply_cap
26
69
  when /\/watch/i
27
- table = Console.format_table(Calculator.fund_hash(fund, CoinMarketCap.fetch))
28
- text = [
29
- "*#{table[:title]}*",
30
- "\n",
31
- table[:table].map{|x| x.join(" | ") }.join("\n"),
32
- "\n",
33
- table[:footer].reject(&:empty?).join(" | ")
34
- ].join("\n")
35
- reply.text = text
70
+ reply.text = reply_watch
36
71
  else
37
72
  reply.text = "#{message.from.first_name}, have no idea what _#{command}_ means."
38
73
  end
74
+
39
75
  logger.info "sending #{reply.text.inspect} to @#{message.from.username}"
40
- reply.parse_mode = 'Markdown'
76
+ reply.parse_mode = "Markdown"
41
77
  reply.send_with(bot)
78
+
42
79
  rescue => e
43
80
  logger.error e
44
81
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Capwatch
2
- VERSION = '0.1.13'.freeze
4
+ VERSION = "0.2.0"
3
5
  end
@@ -0,0 +1,3 @@
1
+ <%= @console_formatter.format_btc(fund.fund_totals[:value_btc]) %>
2
+ <%= @console_formatter.format_eth(fund.fund_totals[:value_eth]) %>
3
+ <%= @console_formatter.format_usd(fund.fund_totals[:value_usd]) %>
@@ -0,0 +1,4 @@
1
+ <% body.each do |array| %>
2
+ <% array.each do |value| %><%= value %> <% end %>
3
+ <% end %>
4
+ <%= @console_formatter.format_btc(fund.fund_totals[:value_btc]) %> <%= @console_formatter.format_eth(fund.fund_totals[:value_eth]) %> <%= @console_formatter.format_percent(fund.fund_totals[:percent_change_24h]) %> <%= @console_formatter.format_percent(fund.fund_totals[:percent_change_7d]) %> <%= @console_formatter.format_usd(fund.fund_totals[:value_usd]) %>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capwatch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.13
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Bugaiov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-08-08 00:00:00.000000000 Z
11
+ date: 2017-08-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: terminal-table
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1.15'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.10'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.10'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: rake
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -114,17 +128,19 @@ files:
114
128
  - bin/setup
115
129
  - capwatch.gemspec
116
130
  - exe/capwatch
117
- - funds/demo/basic.json
118
- - funds/demo/dynamic.json
119
- - funds/demo/extreme.json
120
131
  - lib/capwatch.rb
121
- - lib/capwatch/calculator.rb
122
132
  - lib/capwatch/cli.rb
123
- - lib/capwatch/coinmarketcap.rb
133
+ - lib/capwatch/coin.rb
124
134
  - lib/capwatch/console.rb
125
- - lib/capwatch/fundparser.rb
135
+ - lib/capwatch/exchange.rb
136
+ - lib/capwatch/fund.rb
137
+ - lib/capwatch/fund_calculator.rb
138
+ - lib/capwatch/fund_config.rb
139
+ - lib/capwatch/providers/coin_market_cap.rb
126
140
  - lib/capwatch/telegram.rb
127
141
  - lib/capwatch/version.rb
142
+ - lib/templates/cap.erb
143
+ - lib/templates/watch.erb
128
144
  homepage: https://cryptowatch.one
129
145
  licenses:
130
146
  - MIT
@@ -1,15 +0,0 @@
1
- {
2
- "name": "Basic Fund",
3
- "symbols": {
4
- "MAID": 25452.47,
5
- "GAME": 22253.51,
6
- "NEO": 3826.53,
7
- "FCT": 525.67875,
8
- "SC": 4152770,
9
- "DCR": 453.22,
10
- "BTC": 8.219,
11
- "ETH": 166.198,
12
- "KMD": 19056.20,
13
- "LSK": 5071.42
14
- }
15
- }
@@ -1,11 +0,0 @@
1
- {
2
- "name": "Dynamic Fund",
3
- "symbols": {
4
- "NEO": 5102.04,
5
- "GAME": 41952.55,
6
- "SC": 10341309,
7
- "DCR": 791.489,
8
- "ETH": 128.57,
9
- "BTC": 22.876
10
- }
11
- }
@@ -1,12 +0,0 @@
1
- {
2
- "name": "Extreme Fund",
3
- "symbols": {
4
- "FLO": 2140.96,
5
- "DAR": 141.93,
6
- "UBQ": 141.54,
7
- "BTC": 0.09,
8
- "WAVES": 218.18,
9
- "GUP": 487.56,
10
- "WINGS": 508.67
11
- }
12
- }
@@ -1,88 +0,0 @@
1
- module Capwatch
2
- class Calculator
3
- def self.fund_hash(fund, coinmarketcap_json)
4
- table = []
5
-
6
- title = fund['name']
7
- symbols = fund['symbols']
8
- fund_keys = symbols.keys
9
-
10
- price_eth_btc = coinmarketcap_json.find do |x|
11
- x['symbol'] == 'ETH'
12
- end['price_usd'].to_f
13
-
14
- filtered_response_json = coinmarketcap_json.select do |x|
15
- fund_keys.include?(x['symbol'])
16
- end
17
-
18
- total_value_usd = filtered_response_json.inject(0) do |sum, n|
19
- sum + symbols[n['symbol']] * n['price_usd'].to_f
20
- end
21
-
22
- total_value_btc = filtered_response_json.inject(0) do |sum, n|
23
- sum + symbols[n['symbol']] * n['price_btc'].to_f
24
- end
25
-
26
- total_value_eth = filtered_response_json.inject(0) do |sum, n|
27
- sum + symbols[n['symbol']] * n['price_usd'].to_f / price_eth_btc
28
- end
29
-
30
- distribution_hash = {}
31
-
32
- fund_keys.each do |x|
33
- x = filtered_response_json.find { |e| e['symbol'] == x }
34
- symbol = x['symbol']
35
- asset_name = "#{x['name']} (#{symbol})"
36
- quant_value = symbols[symbol]
37
- price = x['price_usd'].to_f
38
- value_btc = quant_value * x['price_btc'].to_f
39
- value_eth = quant_value * x['price_usd'].to_f / price_eth_btc
40
- value_usd = quant_value * x['price_usd'].to_f
41
- distribution_float = value_usd / total_value_usd
42
- distribution_hash[symbol] = distribution_float
43
- distribution = distribution_float * 100
44
- percent_change_24h = x['percent_change_24h'].to_f || 0
45
- percent_change_7d = x['percent_change_7d'].to_f || 0
46
- table << [
47
- asset_name,
48
- price,
49
- quant_value,
50
- value_usd,
51
- value_btc,
52
- value_eth,
53
- distribution,
54
- percent_change_24h,
55
- percent_change_7d
56
- ]
57
- end
58
-
59
- a_24h = filtered_response_json.inject(0) do |sum, n|
60
- sum + n['percent_change_24h'].to_f * distribution_hash[n['symbol']].to_f
61
- end
62
-
63
- a_7d = filtered_response_json.inject(0) do |sum, n|
64
- sum + n['percent_change_7d'].to_f * distribution_hash[n['symbol']].to_f
65
- end
66
-
67
- footer = [
68
- '',
69
- '',
70
- '',
71
- total_value_usd,
72
- total_value_btc,
73
- total_value_eth,
74
- '',
75
- a_24h,
76
- a_7d
77
- ]
78
-
79
- table.sort_by! { |e| -e[3].to_f }
80
- table.each.with_index(1) { |e, i| e[0] = "#{i}. #{e[0]}" }
81
-
82
- {}
83
- .merge(title: title)
84
- .merge(table: table)
85
- .merge(footer: footer)
86
- end
87
- end
88
- end
@@ -1,12 +0,0 @@
1
- require 'json'
2
- require 'open-uri'
3
-
4
- module Capwatch
5
- class CoinMarketCap
6
- URL = 'https://api.coinmarketcap.com/v1/ticker/'.freeze
7
- def self.fetch
8
- response = open(URL).read
9
- JSON.parse response
10
- end
11
- end
12
- end
@@ -1,10 +0,0 @@
1
- module Capwatch
2
- class FundParser
3
- def initialize(filename = '~/.capwatch')
4
- @capwatch_file = File.expand_path(filename)
5
- end
6
- def fund
7
- JSON.parse(File.open(@capwatch_file).read)
8
- end
9
- end
10
- end