stocksy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f0658fd1d695020d19b5c09160524f0d76678600d96723086abf4c574580af25
4
+ data.tar.gz: 45fc1f6edf80ce5e4671b0903c307cdd4fe912dbf3a1fd1516cef771a0315a0c
5
+ SHA512:
6
+ metadata.gz: f3bf475c3567ec32cafb9cefcaba51739069705c0f2373cfa68a5415021737542c1c3c1547c003dc975e37f6d07a46edd210e7b99cb185a8cbe71c29cde11082
7
+ data.tar.gz: e72670c3dbe37c1171c03b26930b3a782036f6501bbfd063460de68a231ba5b57eb1634406d8d5d669460912f0260997a539f5afe12bb4c7947bb37070725d06
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Bruno Prieto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # Stocksy
2
+
3
+ Stocksy is a command line tool that displays stock market information obtained from the [Alpha Vantage API](https://www.alphavantage.co/).
4
+
5
+ ## Installation
6
+
7
+ Install the gem by executing:
8
+
9
+ $ gem install stocksy
10
+
11
+ ## Usage
12
+
13
+ Get a free API key from [Alpha Vantage](https://www.alphavantage.co/) and set it up with `stocksy login [API key]` or with the "ALPHA_VANTAGE_API_KEY" environment variable so Stocksy can get data.
14
+
15
+ You can review the full list of commands by running `stocksy help`.
16
+
17
+ ## Development
18
+
19
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
20
+
21
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
22
+
23
+ ## Contributing
24
+
25
+ Bug reports and pull requests are welcome on GitHub at https://github.com/brunoprietog/stocksy. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/brunoprietog/stocksy/blob/master/CODE_OF_CONDUCT.md).
26
+
27
+ ## License
28
+
29
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
30
+
31
+ ## Code of Conduct
32
+
33
+ Everyone interacting in the Stocksy project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/brunoprietog/stocksy/blob/master/CODE_OF_CONDUCT.md).
data/bin/stocksy ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "stocksy"
5
+
6
+ begin
7
+ Stocksy::Cli.start(ARGV)
8
+ rescue StandardError => e
9
+ puts " \e[31mERROR (#{e.class}): #{e.message}\e[0m"
10
+ puts e.backtrace if ENV["VERBOSE"]
11
+ exit 1
12
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stocksy::AlphaVantage::ApiKey
4
+ extend self
5
+ FILE_PATH = File.join(Dir.home, ".stocksy")
6
+
7
+ def fetch
8
+ api_key = ENV.fetch("ALPHA_VANTAGE_API_KEY") do
9
+ ENV["ALPHA_VANTAGE_API_KEY"] = retrieve_api_key_from_file
10
+ end
11
+ api_key || raise(Stocksy::Error, "No API key found")
12
+ end
13
+
14
+ def save(api_key)
15
+ File.write(FILE_PATH, api_key)
16
+ end
17
+
18
+ def delete
19
+ return unless File.exist?(FILE_PATH)
20
+
21
+ File.delete(FILE_PATH)
22
+ end
23
+
24
+ private
25
+
26
+ def retrieve_api_key_from_file
27
+ return unless File.exist? FILE_PATH
28
+
29
+ api_key = File.read(FILE_PATH).strip
30
+ return api_key unless api_key.empty?
31
+ end
32
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "active_support/inflector"
6
+ require "active_support/ordered_options"
7
+
8
+ module Stocksy::AlphaVantage
9
+ extend self
10
+ URL = "https://www.alphavantage.co/query"
11
+
12
+ def overview(symbol)
13
+ response = get(function: "OVERVIEW", symbol:)
14
+ optionize(response)
15
+ end
16
+
17
+ def get_time_series_dayly(symbol)
18
+ response = get(function: "TIME_SERIES_DAILY", symbol:, outputsize: "full")
19
+ normalize_response(response["Time Series (Daily)"])
20
+ end
21
+
22
+ def search(keywords)
23
+ response = get(function: "SYMBOL_SEARCH", keywords:)
24
+ normalize_response(response["bestMatches"])
25
+ end
26
+
27
+ private
28
+
29
+ def get(params)
30
+ uri = URI.parse(URL)
31
+ uri.query = URI.encode_www_form(params.merge(apikey: api_key))
32
+ response = Net::HTTP.get(uri)
33
+ JSON.parse(response)
34
+ end
35
+
36
+ def api_key
37
+ ApiKey.fetch
38
+ end
39
+
40
+ def normalize_response(response)
41
+ if response.is_a?(Array)
42
+ response.map! { |hash| optionize(hash) }
43
+ elsif response.is_a?(Hash)
44
+ response.transform_values! { |hash| optionize(hash) }
45
+ end
46
+ end
47
+
48
+ def optionize(hash)
49
+ hash.transform_keys! { |key| key.split.last.underscore.to_sym }
50
+ ActiveSupport::InheritableOptions.new(hash)
51
+ end
52
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stocksy::Asset::Overview
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ delegate :name, :asset_type, :description, :exchange, :currency, :country, :sector, :industry,
8
+ :dividend_per_share, :dividend_yield, :dividend_date, :ex_dividend_date, to: :overview
9
+ end
10
+
11
+ def overview
12
+ @overview ||= Stocksy::AlphaVantage.overview(symbol)
13
+ end
14
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stocksy::Asset::Profitability
4
+ def profitability_summary
5
+ {
6
+ "1D" => profitability_ago(1.day),
7
+ "5D" => profitability_ago(5.days),
8
+ "1M" => profitability_ago(1.month),
9
+ "6M" => profitability_ago(6.months),
10
+ "YTD" => profitability_from(Date.today.beginning_of_year),
11
+ "1Y" => profitability_ago(1.year),
12
+ "5Y" => profitability_ago(5.years)
13
+ }
14
+ end
15
+
16
+ private
17
+
18
+ def profitability_ago(time_ago)
19
+ initial_date = time_ago == 1.day ? last_traded_date - 1.day : Date.today - time_ago
20
+ profitability_from(initial_date)
21
+ end
22
+
23
+ def profitability_from(date)
24
+ profitability_between(date, last_traded_date)
25
+ end
26
+
27
+ def profitability_between(initial_date, final_date)
28
+ initial_date = closest_traded_date_from(initial_date)
29
+ final_date = closest_traded_date_from(final_date)
30
+ initial_price = time_series_dayly[initial_date.to_s].close.to_f
31
+ final_price = time_series_dayly[final_date.to_s].close.to_f
32
+ profitability = ((final_price - initial_price) / initial_price) * 100
33
+ profitability.round(2)
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+ require "active_support/core_ext/module/delegation"
5
+ require "active_support/core_ext/numeric/time"
6
+ require "active_support/core_ext/integer/time"
7
+
8
+ class Stocksy::Asset
9
+ include Overview
10
+ include Profitability
11
+
12
+ attr_reader :symbol
13
+
14
+ def initialize(symbol)
15
+ @symbol = symbol
16
+ end
17
+
18
+ def last_traded_date
19
+ @last_traded_date ||= closest_traded_date_from(Date.today)
20
+ end
21
+
22
+ private
23
+
24
+ def closest_traded_date_from(date)
25
+ 10.times do |days|
26
+ next_date = date - days.days
27
+ return next_date if time_series_dayly[next_date.to_s]
28
+ end
29
+ raise Stocksy::Error, "No trading date found in the last 10 days from #{date}"
30
+ end
31
+
32
+ def time_series_dayly
33
+ @time_series_dayly ||= Stocksy::AlphaVantage.get_time_series_dayly(symbol)
34
+ end
35
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ # rubocop:disable Metrics/AbcSize
6
+ # rubocop:disable Metrics/MethodLength
7
+ class Stocksy::Cli < Thor
8
+ package_name "Stocksy"
9
+
10
+ desc "show [SYMBOL]", "Overview of an asset by symbol"
11
+ def show(symbol)
12
+ asset = Stocksy::Asset.new(symbol)
13
+
14
+ if asset.name
15
+ say "#{asset.name} (#{asset.symbol})"
16
+ say "#{asset.asset_type} - #{asset.exchange} - #{asset.currency} - #{asset.country}"
17
+ say "#{asset.sector}, #{asset.industry}"
18
+ blank_line
19
+ print_wrapped asset.description
20
+ blank_line
21
+ say "Dividend per share: #{asset.dividend_per_share} #{asset.currency}"
22
+ say "Dividend yield: #{asset.dividend_yield.to_f * 100}%"
23
+ say "Next dividend date: #{asset.dividend_date}"
24
+ say "Previous dividend date: #{asset.ex_dividend_date}"
25
+ else
26
+ say asset.symbol
27
+ end
28
+ blank_line
29
+ say "Profitabilities until #{asset.last_traded_date}:"
30
+ asset.profitability_summary.each do |interval, profitability|
31
+ say "#{interval}: #{profitability}%"
32
+ end
33
+ end
34
+
35
+ desc "search [KEYWORDS]", "Search for assets by keywords"
36
+ def search(keywords)
37
+ search = Stocksy::Search.new(keywords)
38
+ print_ordered_options_as_table(search.results, %i[name symbol type region currency])
39
+ end
40
+
41
+ desc "login [API KEY]", "Set AlphaVantage API key"
42
+ def login(api_key)
43
+ Stocksy::AlphaVantage::ApiKey.save(api_key)
44
+ say "API key saved into #{Stocksy::AlphaVantage::ApiKey::FILE_PATH}"
45
+ end
46
+
47
+ desc "logout", "Delete AlphaVantage API key"
48
+ def logout
49
+ Stocksy::AlphaVantage::ApiKey.delete
50
+ say "API key deleted from #{Stocksy::AlphaVantage::ApiKey::FILE_PATH}"
51
+ end
52
+
53
+ desc "version", "Show Stocksy version"
54
+ def version
55
+ say Stocksy::VERSION
56
+ end
57
+
58
+ private
59
+
60
+ def blank_line
61
+ say "\n"
62
+ end
63
+
64
+ def print_ordered_options_as_table(ordered_options, columns)
65
+ table_data = [columns.map { |column| column.to_s.gsub("_", " ").capitalize }]
66
+ ordered_options.each do |option|
67
+ row = columns.map { |column| option[column] }
68
+ table_data << row
69
+ end
70
+ print_table(table_data)
71
+ end
72
+ end
73
+ # rubocop:enable Metrics/AbcSize
74
+ # rubocop:enable Metrics/MethodLength
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Stocksy::Search
4
+ attr_reader :results
5
+
6
+ def initialize(keywords)
7
+ @results = Stocksy::AlphaVantage.search(keywords)
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stocksy
4
+ VERSION = "0.1.0"
5
+ end
data/lib/stocksy.rb ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stocksy
4
+ class Error < StandardError; end
5
+ end
6
+
7
+ require "active_support"
8
+ require "zeitwerk"
9
+
10
+ loader = Zeitwerk::Loader.for_gem
11
+ loader.setup
12
+ loader.eager_load
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stocksy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bruno Prieto
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-09-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: zeitwerk
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description:
56
+ email:
57
+ - brunoprietog@hey.com
58
+ executables:
59
+ - stocksy
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - LICENSE.txt
64
+ - README.md
65
+ - bin/stocksy
66
+ - lib/stocksy.rb
67
+ - lib/stocksy/alpha_vantage.rb
68
+ - lib/stocksy/alpha_vantage/api_key.rb
69
+ - lib/stocksy/asset.rb
70
+ - lib/stocksy/asset/overview.rb
71
+ - lib/stocksy/asset/profitability.rb
72
+ - lib/stocksy/cli.rb
73
+ - lib/stocksy/search.rb
74
+ - lib/stocksy/version.rb
75
+ homepage: https://github.com/brunoprietog/stocksy
76
+ licenses:
77
+ - MIT
78
+ metadata:
79
+ rubygems_mfa_required: 'true'
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 3.2.0
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubygems_version: 3.4.15
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: Command line tool to get stock market data
99
+ test_files: []