mcoin 0.1.0 → 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: 4d8259c823f92b80f9e0124b3443c5e1b74d6120
4
- data.tar.gz: 6de15f6269ef50557c690f414bee21dfef1d63fe
3
+ metadata.gz: 91fde5f4e3e6f808c44d9355719f8c54f1246ef9
4
+ data.tar.gz: d467d3ce1cca1805f402a55c59f16a28ab230a24
5
5
  SHA512:
6
- metadata.gz: 47b20bb1bed806bfcd73fe8a5d42c6709f80872cb088e3a23d126dc2dc339d9198fb1b9ff3990ec1b10ad407fb0c54e65037c68eb4bedb0678c591d22deabafd
7
- data.tar.gz: eb57c25b083e53e67bb9d1a888e53f47ef56f1bc35038b265ae695d890ef1f767760eb9cb7a6abd41f0f154e8a0702ad543a9a08275d45d8067f0b07740c50d3
6
+ metadata.gz: 83c9569ebf33899094243524b65f2ca8f5f7793e40b27d3f1b4b295ee34593485b92af2a49f5f35b2edb328ad1fe5c5416ddebf022de77efc0569af116c7a314
7
+ data.tar.gz: c9f2645d8d1b7126cadcade42809e0a7017334f36a09eea466362acdb72c41ae46581d720e1ed7960c01ef80b7c9b0054bb2693cd404e1cc99dc2b8448779e41
data/.rubocop.yml ADDED
@@ -0,0 +1,2 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.4.0
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mcoin (0.1.0)
4
+ mcoin (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # Mcoin
2
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/mcoin`. 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
3
+ This is a side-project for me to monitor BTC/ETH. The target is create a helpful command line tool can run as cronjob to import data to InfluxDB and make some analytics.
6
4
 
7
5
  ## Installation
8
6
 
@@ -22,7 +20,32 @@ Or install it yourself as:
22
20
 
23
21
  ## Usage
24
22
 
25
- TODO: Write usage instructions here
23
+ ### Ticker
24
+
25
+ Add `-m` to specify market name
26
+
27
+ ```
28
+ mcoin ticker -m Bitfinex
29
+ mcoin ticker -m Bitfinex -m Kraken # Multiple
30
+ ```
31
+
32
+ If you want to save it into InfluxDB, add database information.
33
+
34
+ ```
35
+ mcoin ticker -m Bitfinex -d monitor -e http://localhsot:8086
36
+ ```
37
+
38
+ To get more information please use `-h` option.
39
+
40
+ ## Roadmap
41
+
42
+ * [x] Fetch ticker
43
+ * [x] Fetch in parallels
44
+ * [x] Fetch from multiple market
45
+ * [ ] Fetch multiple coin type via specify `pair`
46
+ * [ ] Support more public API command
47
+ * [ ] Improve printer
48
+ * [ ] Generalize database save interface
26
49
 
27
50
  ## Development
28
51
 
@@ -32,4 +55,4 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
55
 
33
56
  ## Contributing
34
57
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/mcoin.
58
+ Bug reports and pull requests are welcome on GitHub at https://github.com/elct9620/mcoin.
data/Rakefile CHANGED
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  task default: :spec
data/bin/console CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'bundler/setup'
4
5
  require 'mcoin'
data/exe/mcoin CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'bundler/setup'
4
5
  require 'mcoin'
data/lib/mcoin.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'mcoin/version'
2
4
 
3
5
  # :nodoc:
@@ -9,5 +11,6 @@ module Mcoin
9
11
  autoload :Market, 'mcoin/market'
10
12
  autoload :Data, 'mcoin/data'
11
13
  autoload :Printer, 'mcoin/printer'
14
+ autoload :Parallel, 'mcoin/parallel'
12
15
  autoload :InfluxDB, 'mcoin/influx_db'
13
16
  end
data/lib/mcoin/command.rb CHANGED
@@ -1,94 +1,36 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'optparse'
2
4
  require 'optparse/uri'
3
5
  require 'ostruct'
4
6
 
5
7
  module Mcoin
6
8
  # The command line interface
7
- class Command < OptionParser
9
+ module Command
10
+ autoload :Base, 'mcoin/command/base'
11
+ autoload :Common, 'mcoin/command/common'
12
+ autoload :Ticker, 'mcoin/command/ticker'
13
+
14
+ autoload :HasMarket, 'mcoin/command/ext/has_market'
15
+ autoload :HasType, 'mcoin/command/ext/has_type'
16
+ autoload :Saveable, 'mcoin/command/ext/saveable'
17
+
8
18
  class << self
9
19
  def execute
10
- new
20
+ pick.new.parse!.execute
11
21
  end
12
- end
13
-
14
- attr_reader :option
15
-
16
- def initialize(&block)
17
- super
18
- prepare_options
19
-
20
- self.banner = '=== Mcoin : Bitcoin Monitor Tools ==='
21
- options_for_coin
22
- options_for_influxdb
23
- common_options
24
-
25
- parse!
26
- execute
27
- end
28
-
29
- def execute
30
- raise OptionParser::MissingArgument, :market if option.market.nil?
31
- market.print
32
- # TODO: Provide general interface
33
- market.save(option) if influxdb?
34
- end
35
-
36
- def market
37
- @market ||= Market
38
- .pick(option.market)
39
- .new(option.type, option.currency)
40
- end
41
22
 
42
- def influxdb?
43
- option.endpoint && option.database
44
- end
45
-
46
- def on(name, *args)
47
- super(*args, ->(value) { option[name] = value })
48
- end
49
-
50
- def separator(content = nil)
51
- super ''
52
- super content unless content.nil?
53
- end
54
-
55
- private
56
-
57
- def prepare_options
58
- @option = OpenStruct.new
59
- @option.type = :BTC
60
- @option.currency = :USD
61
- end
62
-
63
- def common_options
64
- separator 'Common Options: '
65
- on_tail('-h', '--help', 'Show this message') do
66
- puts self
67
- exit
23
+ def pick
24
+ command = ARGV.first&.capitalize
25
+ return Common unless commands.include?(command&.to_sym)
26
+ const_get(command)
68
27
  end
69
28
 
70
- on_tail('-v', '--version', 'Show version') do
71
- puts "Mcoin #{VERSION}"
72
- exit
29
+ def commands
30
+ constants.select do |klass|
31
+ const_get(klass).is_a?(Class)
32
+ end - %i[Base Common]
73
33
  end
74
34
  end
75
-
76
- def options_for_influxdb
77
- separator 'InfluxDB:'
78
- on(:endpoint, '-e', '--endpoint ENDPOINT', URI, 'Database Endpoint')
79
- on(:database, '-d', '--database NAME', String, 'Database Name')
80
- on(:username, '-u', '--username USERNAME', String, 'Database Username')
81
- on(:password, '-p', '--password PASSWORD', String, 'Database Password')
82
- end
83
-
84
- def options_for_coin
85
- on(:type, '-t', '--type TYPE',
86
- Mcoin::TYPES, "Available: #{Mcoin::TYPES.join(', ')}")
87
- on(:currency, '-c', '--currency CURRENCY',
88
- Mcoin::CURRENCY, "Available: #{Mcoin::CURRENCY.join(', ')}")
89
- on(:market, '-m', '--market MARKET',
90
- Mcoin::Market.available,
91
- "Available: #{Mcoin::Market.available.join(', ')}")
92
- end
93
35
  end
94
36
  end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mcoin
4
+ module Command
5
+ # :nodoc:
6
+ class Base < OptionParser
7
+ class << self
8
+ attr_reader :options, :commands
9
+
10
+ def description(description = nil)
11
+ return @description if description.nil?
12
+ @description = description
13
+ end
14
+
15
+ def option(mode, name, *args)
16
+ @options ||= []
17
+ @options.push(Option.new(mode, name, *args))
18
+ end
19
+ end
20
+
21
+ attr_reader :option
22
+
23
+ def initialize
24
+ super
25
+
26
+ prepare
27
+ self.banner = '=== Mcoin : BTC Monitor Tool ==='
28
+ self.class.options&.each { |option| option.register(self) }
29
+ end
30
+
31
+ def prepare
32
+ @option = OpenStruct.new
33
+ end
34
+
35
+ def parse!
36
+ super
37
+ self
38
+ end
39
+
40
+ def execute
41
+ raise NotImplementedError
42
+ end
43
+
44
+ # Option Register
45
+ class Option
46
+ def initialize(mode, name, *args)
47
+ @mode = mode
48
+ @name = name.to_sym
49
+ @args = args
50
+ end
51
+
52
+ def register(command)
53
+ case @mode
54
+ when :single then register_as_single(command)
55
+ when :multiple then register_as_multiple(command)
56
+ end
57
+ end
58
+
59
+ def register_as_single(command)
60
+ command.on(*@args, ->(value) { command.option[@name] = value })
61
+ end
62
+
63
+ def register_as_multiple(command)
64
+ command.option[@name] ||= []
65
+ command.on(*@args, ->(value) { command.option[@name].push(value) })
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mcoin
4
+ module Command
5
+ # :nodoc:
6
+ class Common < Base
7
+ def initialize
8
+ super
9
+
10
+ separator ''
11
+ separator 'Commands: '
12
+ print_commands
13
+
14
+ separator ''
15
+ separator 'Options: '
16
+ on_tail('-v', '--version', 'Show versions') do
17
+ puts "Mcoin #{Mcoin::VERSION}"
18
+ exit
19
+ end
20
+ end
21
+
22
+ def print_commands
23
+ Command.commands.each do |command|
24
+ separator " #{command.downcase}\t" \
25
+ "#{Command.const_get(command).description}"
26
+ end
27
+ end
28
+
29
+ def parse!
30
+ super
31
+ rescue OptionParser::InvalidOption
32
+ puts self
33
+ exit
34
+ end
35
+
36
+ def execute
37
+ puts self
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mcoin
4
+ module Command
5
+ # :nodoc:
6
+ module HasMarket
7
+ def self.included(base)
8
+ base.option(:multiple, :market, '-m', '--market MARKET',
9
+ Mcoin::Market.available,
10
+ "Available: #{Mcoin::Market.available.join(', ')}")
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mcoin
4
+ module Command
5
+ # :nodoc:
6
+ module HasType
7
+ def self.included(base)
8
+ base.option(:single, :type, '-t', '--type TYPE',
9
+ Mcoin::TYPES, "Available: #{Mcoin::TYPES.join(', ')}")
10
+ base.option(:single, :currency, '-c', '--currency CURRENCY',
11
+ Mcoin::CURRENCY, "Available: #{Mcoin::CURRENCY.join(', ')}")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mcoin
4
+ module Command
5
+ # :nodoc:
6
+ module Saveable
7
+ def self.included(base)
8
+ base.class_eval do
9
+ option(:single, :endpoint, '-e',
10
+ '--endpoint ENDPOINT', URI, 'Database Endpoint')
11
+ option(:single, :database, '-d',
12
+ '--database NAME', String, 'Database Name')
13
+ option(:single, :username, '-u',
14
+ '--username USERNAME', String, 'Database Username')
15
+ option(:single, :password, '-p',
16
+ '--password PASSWORD', String, 'Database Password')
17
+ end
18
+ end
19
+
20
+ def save?
21
+ option.endpoint && option.database
22
+ end
23
+
24
+ def database
25
+ InfluxDB.new(
26
+ option.endpoint,
27
+ option.database,
28
+ option.username,
29
+ option.password
30
+ )
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mcoin
4
+ module Command
5
+ # :nodoc:
6
+ class Ticker < Base
7
+ include HasMarket
8
+ include HasType
9
+ include Saveable
10
+
11
+ attr_reader :option
12
+
13
+ description 'Show market information'
14
+
15
+ def execute
16
+ raise OptionParser::MissingArgument, :market if option.market.nil?
17
+ print
18
+ save if save?
19
+ end
20
+
21
+ def print
22
+ tickers = Parallel.map(markets, :fetch).map(&:to_ticker)
23
+ Printer.new(tickers).print
24
+ end
25
+
26
+ def save
27
+ database.save(markets.map(&:to_ticker).map(&:to_influx))
28
+ end
29
+
30
+ def markets
31
+ @markets ||= option.market.map do |name|
32
+ Market
33
+ .pick(name)
34
+ .new(option.type, option.currency)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def prepare
41
+ super
42
+ option.type = :BTC
43
+ option.currency = :USD
44
+ end
45
+ end
46
+ end
47
+ end
data/lib/mcoin/data.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mcoin
2
4
  # :nodoc:
3
5
  module Data
@@ -1,11 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mcoin
2
4
  module Data
3
5
  # :nodoc:
4
6
  class Ticker
5
- attr_reader :type, :currency
7
+ attr_reader :market, :type, :currency
6
8
  attr_accessor :last, :ask, :bid, :low, :high, :volume
7
9
 
8
- def initialize(type, currency, data = {})
10
+ def initialize(market, type, currency, data = {})
11
+ @market = market
9
12
  @type = type
10
13
  @currency = currency
11
14
  data.each do |key, value|
@@ -14,7 +17,7 @@ module Mcoin
14
17
  end
15
18
 
16
19
  def to_influx(tags = {}, values = {})
17
- tags = { type: @type, currency: @currency }.merge(tags)
20
+ tags = { type: @type, currency: @currency, market: @market }.merge(tags)
18
21
  values = {
19
22
  last: @last,
20
23
  ask: @ask, bid: @bid,
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pp'
1
4
  require 'net/http'
2
5
 
3
6
  module Mcoin
data/lib/mcoin/market.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mcoin
2
4
  # :nodoc:
3
5
  module Market
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pp'
2
4
  require 'json'
3
5
  require 'net/http'
@@ -11,25 +13,6 @@ module Mcoin
11
13
  @currency = currency
12
14
  end
13
15
 
14
- def print
15
- fetch
16
- puts '=== Result ==='
17
- Printer.new(to_ticker).print
18
- self
19
- end
20
-
21
- def save(option)
22
- puts '=== InfluxDB ==='
23
- InfluxDB.new(
24
- option.endpoint,
25
- option.database,
26
- option.username,
27
- option.password
28
- ).save([to_ticker.to_influx(market: name)])
29
- puts 'Saved'
30
- self
31
- end
32
-
33
16
  def name
34
17
  self.class.name.split('::').last
35
18
  end
@@ -1,13 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mcoin
2
4
  module Market
3
5
  # :nodoc:
4
6
  class Bitfinex < Base
5
- # rubocop:disable Metrics/LineLength
6
- ENDPOINT = 'https://api.bitfinex.com/v1/pubticker/%<type>s%<currency>s'.freeze
7
+ ENDPOINT = 'https://api.bitfinex.com/v1/pubticker/%<type>s%<currency>s'
7
8
 
8
9
  def to_ticker
9
10
  fetch
10
11
  Data::Ticker.new(
12
+ :Bitfinex,
11
13
  @type, @currency,
12
14
  last: @data['last_price'],
13
15
  ask: @data['ask'], bid: @data['bid'],
@@ -1,13 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mcoin
2
4
  module Market
3
5
  # :nodoc:
4
6
  class Bitstamp < Base
5
- # rubocop:disable Metrics/LineLength
6
- ENDPOINT = 'https://www.bitstamp.net/api/v2/ticker/%<type>s%<currency>s/'.freeze
7
+ ENDPOINT = 'https://www.bitstamp.net/api/v2/ticker/%<type>s%<currency>s/'
7
8
 
8
9
  def to_ticker
9
10
  fetch
10
11
  Data::Ticker.new(
12
+ :Bitstamp,
11
13
  @type, @currency,
12
14
  last: @data['last'],
13
15
  ask: @data['ask'], bid: @data['bid'],
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mcoin
2
4
  module Market
3
5
  # :nodoc:
4
6
  class Kraken < Base
5
7
  # rubocop:disable Metrics/LineLength
6
- ENDPOINT = 'https://api.kraken.com/0/public/Ticker?pair=%<type>s%<currency>s'.freeze
8
+ ENDPOINT = 'https://api.kraken.com/0/public/Ticker?pair=%<type>s%<currency>s'
7
9
 
8
10
  def initialize(type, currency)
9
11
  type = swap_btc(type)
@@ -13,6 +15,7 @@ module Mcoin
13
15
  def to_ticker
14
16
  fetch
15
17
  Data::Ticker.new(
18
+ :Kraken,
16
19
  swap_btc(@type), @currency,
17
20
  last: @data['c'][0],
18
21
  ask: @data['a'][0], bid: @data['b'][0],
@@ -25,6 +28,7 @@ module Mcoin
25
28
  super
26
29
  return self if @data['result'].nil?
27
30
  @data = @data.dig('result', "X#{@type}Z#{@currency}")
31
+ self
28
32
  end
29
33
 
30
34
  def swap_btc(type)
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mcoin
4
+ # :nodoc:
5
+ class Parallel
6
+ class << self
7
+ def map(array, method)
8
+ array.map do |item|
9
+ Thread.new { item.send(method) }
10
+ end.map(&:join).map(&:value)
11
+ end
12
+ end
13
+ end
14
+ end
data/lib/mcoin/printer.rb CHANGED
@@ -1,15 +1,37 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mcoin
2
4
  # :nodoc:
3
5
  class Printer
4
6
  def initialize(*rows)
5
7
  @rows = rows.flatten
8
+ @outputs = []
9
+
10
+ build
6
11
  end
7
12
 
8
13
  def print
9
- puts row(columns.map(&:capitalize))
10
- puts column_widths.map { |width| '-' * width }.join(' | ')
14
+ if @rows.empty?
15
+ puts 'No result found'
16
+ else
17
+ puts @outputs
18
+ end
19
+ end
20
+
21
+ def build
22
+ return if @rows.empty?
23
+ build_header
24
+ build_rows
25
+ end
26
+
27
+ def build_header
28
+ @outputs << row(columns.map(&:capitalize))
29
+ @outputs << column_widths.map { |width| '-' * width }.join(' | ')
30
+ end
31
+
32
+ def build_rows
11
33
  @rows.each do |row|
12
- puts row(columns.map { |column| row.send(column) })
34
+ @outputs << row(columns.map { |column| row.send(column) })
13
35
  end
14
36
  end
15
37
 
@@ -27,7 +49,8 @@ module Mcoin
27
49
  end
28
50
 
29
51
  def columns
30
- %i[currency type last ask bid]
52
+ # TODO: Load from command
53
+ %i[market currency type last ask bid]
31
54
  end
32
55
  end
33
56
  end
data/lib/mcoin/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mcoin
2
- VERSION = '0.1.0'.freeze
4
+ VERSION = '0.2.0'
3
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mcoin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - 蒼時弦也
@@ -47,6 +47,7 @@ extensions: []
47
47
  extra_rdoc_files: []
48
48
  files:
49
49
  - ".gitignore"
50
+ - ".rubocop.yml"
50
51
  - ".travis.yml"
51
52
  - Gemfile
52
53
  - Gemfile.lock
@@ -57,6 +58,12 @@ files:
57
58
  - exe/mcoin
58
59
  - lib/mcoin.rb
59
60
  - lib/mcoin/command.rb
61
+ - lib/mcoin/command/base.rb
62
+ - lib/mcoin/command/common.rb
63
+ - lib/mcoin/command/ext/has_market.rb
64
+ - lib/mcoin/command/ext/has_type.rb
65
+ - lib/mcoin/command/ext/saveable.rb
66
+ - lib/mcoin/command/ticker.rb
60
67
  - lib/mcoin/data.rb
61
68
  - lib/mcoin/data/ticker.rb
62
69
  - lib/mcoin/influx_db.rb
@@ -65,6 +72,7 @@ files:
65
72
  - lib/mcoin/market/bitfinex.rb
66
73
  - lib/mcoin/market/bitstamp.rb
67
74
  - lib/mcoin/market/kraken.rb
75
+ - lib/mcoin/parallel.rb
68
76
  - lib/mcoin/printer.rb
69
77
  - lib/mcoin/version.rb
70
78
  - mcoin.gemspec