mcoin 0.1.0 → 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: 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