capwatch 0.1.13 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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