eodhd 0.19.5

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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +195 -0
  3. data/Gemfile +3 -0
  4. data/README.md +160 -0
  5. data/Rakefile +28 -0
  6. data/eodhd.gemspec +52 -0
  7. data/lib/Eodhd/Client.rb +143 -0
  8. data/lib/Eodhd/DefaultLogger.rb +30 -0
  9. data/lib/Eodhd/EodBulkLastDay.rb +56 -0
  10. data/lib/Eodhd/EodData.rb +56 -0
  11. data/lib/Eodhd/Error.rb +16 -0
  12. data/lib/Eodhd/Exchange.rb +50 -0
  13. data/lib/Eodhd/ExchangeSymbol.rb +50 -0
  14. data/lib/Eodhd/Fundamentals/AnalystRatings.rb +29 -0
  15. data/lib/Eodhd/Fundamentals/ESGScores.rb +37 -0
  16. data/lib/Eodhd/Fundamentals/Earnings/AnnualEntry.rb +21 -0
  17. data/lib/Eodhd/Fundamentals/Earnings/HistoryEntry.rb +33 -0
  18. data/lib/Eodhd/Fundamentals/Earnings/TrendEntry.rb +65 -0
  19. data/lib/Eodhd/Fundamentals/Earnings.rb +30 -0
  20. data/lib/Eodhd/Fundamentals/Financials/BalanceSheet/Entry.rb +147 -0
  21. data/lib/Eodhd/Fundamentals/Financials/BalanceSheet.rb +28 -0
  22. data/lib/Eodhd/Fundamentals/Financials/CashFlow/Entry.rb +83 -0
  23. data/lib/Eodhd/Fundamentals/Financials/CashFlow.rb +28 -0
  24. data/lib/Eodhd/Fundamentals/Financials/IncomeStatement/Entry.rb +87 -0
  25. data/lib/Eodhd/Fundamentals/Financials/IncomeStatement.rb +28 -0
  26. data/lib/Eodhd/Fundamentals/Financials.rb +25 -0
  27. data/lib/Eodhd/Fundamentals/General.rb +85 -0
  28. data/lib/Eodhd/Fundamentals/Highlights.rb +63 -0
  29. data/lib/Eodhd/Fundamentals/Holders/Fund.rb +31 -0
  30. data/lib/Eodhd/Fundamentals/Holders/Institution.rb +31 -0
  31. data/lib/Eodhd/Fundamentals/Holders.rb +27 -0
  32. data/lib/Eodhd/Fundamentals/InsiderTransaction.rb +35 -0
  33. data/lib/Eodhd/Fundamentals/OutstandingShares/Entry.rb +25 -0
  34. data/lib/Eodhd/Fundamentals/OutstandingShares.rb +26 -0
  35. data/lib/Eodhd/Fundamentals/SharesStats.rb +33 -0
  36. data/lib/Eodhd/Fundamentals/SplitsDividends.rb +31 -0
  37. data/lib/Eodhd/Fundamentals/Technicals.rb +33 -0
  38. data/lib/Eodhd/Fundamentals/Valuation.rb +29 -0
  39. data/lib/Eodhd/Fundamentals.rb +92 -0
  40. data/lib/Eodhd/Intraday.rb +76 -0
  41. data/lib/Eodhd/VERSION.rb +3 -0
  42. data/lib/Eodhd/Validations.rb +78 -0
  43. data/lib/Eodhd/WebSocketClient.rb +130 -0
  44. data/lib/Hash/x_www_form_urlencode.rb +7 -0
  45. data/lib/Thoran/Hash/XWwwFormUrlencode/x_www_form_urlencode.rb +27 -0
  46. data/lib/Thoran/String/Urlencode/urlencode.rb +29 -0
  47. data/lib/eodhd.rb +78 -0
  48. data/test/Eodhd/Client_test.rb +111 -0
  49. data/test/Eodhd/Fundamentals_test.rb +163 -0
  50. data/test/Eodhd/VERSION_test.rb +20 -0
  51. data/test/Eodhd/Validations_test.rb +135 -0
  52. data/test/Eodhd/WebSocketClient_test.rb +99 -0
  53. data/test/Eodhd_test.rb +73 -0
  54. data/test/helper.rb +17 -0
  55. metadata +218 -0
@@ -0,0 +1,92 @@
1
+ # Eodhd/Fundamentals.rb
2
+ # Eodhd::Fundamentals
3
+
4
+ require_relative './Fundamentals/AnalystRatings'
5
+ require_relative './Fundamentals/Earnings'
6
+ require_relative './Fundamentals/ESGScores'
7
+ require_relative './Fundamentals/Financials'
8
+ require_relative './Fundamentals/General'
9
+ require_relative './Fundamentals/Highlights'
10
+ require_relative './Fundamentals/Holders'
11
+ require_relative './Fundamentals/InsiderTransaction'
12
+ require_relative './Fundamentals/OutstandingShares'
13
+ require_relative './Fundamentals/SharesStats'
14
+ require_relative './Fundamentals/SplitsDividends'
15
+ require_relative './Fundamentals/Technicals'
16
+ require_relative './Fundamentals/Valuation'
17
+
18
+ class Eodhd
19
+ class Fundamentals
20
+ class << self
21
+ def all(client: nil, api_token: nil, exchange_code:, symbol:, filter: nil)
22
+ load(client: client, api_token: api_token, exchange_code: exchange_code, symbol: symbol, filter: filter)
23
+ end
24
+
25
+ private
26
+
27
+ def load(client: nil, api_token: nil, exchange_code:, symbol:, filter:)
28
+ client ||= Client.new(api_token: api_token)
29
+ response = client.fundamentals(exchange_code: exchange_code, symbol: symbol, filter: filter)
30
+ self.new(
31
+ exchange_code: exchange_code,
32
+ symbol: symbol,
33
+ general: response['General'],
34
+ highlights: response['Highlights'],
35
+ valuation: response['Valuation'],
36
+ shares_stats: response['SharesStats'],
37
+ technicals: response['Technicals'],
38
+ splits_dividends: response['SplitsDividends'],
39
+ analyst_ratings: response['AnalystRatings'],
40
+ holders: response['Holders'],
41
+ insider_transactions: response['InsiderTransactions'],
42
+ esg_scores: response['ESGScores'],
43
+ outstanding_shares: response['outstandingShares'],
44
+ earnings: response['Earnings'],
45
+ financials: response['Financials']
46
+ )
47
+ end
48
+ end # class << self
49
+
50
+ attr_reader\
51
+ :exchange_code,
52
+ :symbol,
53
+ :general,
54
+ :highlights,
55
+ :valuation,
56
+ :shares_stats,
57
+ :technicals,
58
+ :splits_dividends,
59
+ :analyst_ratings,
60
+ :holders,
61
+ :insider_transactions,
62
+ :esg_scores,
63
+ :outstanding_shares,
64
+ :earnings,
65
+ :financials
66
+
67
+ private
68
+
69
+ def initialize(exchange_code:, symbol:, general:, highlights:, valuation:, shares_stats:, technicals:, splits_dividends:, analyst_ratings:, holders:, insider_transactions:, esg_scores:, outstanding_shares:, earnings:, financials:)
70
+ @exchange_code = exchange_code
71
+ @symbol = symbol
72
+ @general = General.new(general) if general
73
+ @highlights = Highlights.new(highlights) if highlights
74
+ @valuation = Valuation.new(valuation) if valuation
75
+ @shares_stats = SharesStats.new(shares_stats) if shares_stats
76
+ @technicals = Technicals.new(technicals) if technicals
77
+ @splits_dividends = SplitsDividends.new(splits_dividends) if splits_dividends
78
+ @analyst_ratings = AnalystRatings.new(analyst_ratings) if analyst_ratings
79
+ @holders = Holders.new(holders) if holders
80
+ @insider_transactions = wrap_insider_transactions(insider_transactions)
81
+ @esg_scores = ESGScores.new(esg_scores) if esg_scores
82
+ @outstanding_shares = OutstandingShares.new(outstanding_shares) if outstanding_shares
83
+ @earnings = Earnings.new(earnings) if earnings
84
+ @financials = Financials.new(financials) if financials
85
+ end
86
+
87
+ def wrap_insider_transactions(entries)
88
+ return [] unless entries
89
+ entries.values.map{|entry| InsiderTransaction.new(entry)}.sort_by(&:date)
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,76 @@
1
+ # Eodhd/Intraday.rb
2
+ # Eodhd::Intraday
3
+
4
+ class Eodhd
5
+ class Intraday
6
+ class << self
7
+ def all(client: nil, api_token: nil, exchange_code: 'US', symbol:, interval:, from: nil, to: nil)
8
+ load(
9
+ client: client,
10
+ api_token: api_token,
11
+ exchange_code: exchange_code,
12
+ symbol: symbol,
13
+ interval: interval,
14
+ from: from,
15
+ to: to
16
+ )
17
+ end
18
+
19
+ private
20
+
21
+ def load(client: nil, api_token: nil, exchange_code: 'US', symbol:, interval:, from:, to:)
22
+ client ||= Client.new(api_token: api_token)
23
+ client.intraday(
24
+ exchange_code: exchange_code,
25
+ symbol: symbol,
26
+ interval: interval,
27
+ from: from,
28
+ to: to
29
+ ).collect do |intraday_data|
30
+ self.new(
31
+ exchange_code: exchange_code,
32
+ symbol: symbol,
33
+ interval: interval,
34
+ timestamp: intraday_data['timestamp'],
35
+ gmtoffset: intraday_data['gmtoffset'],
36
+ datetime: intraday_data['datetime'],
37
+ open: intraday_data['open'],
38
+ high: intraday_data['high'],
39
+ low: intraday_data['low'],
40
+ close: intraday_data['close'],
41
+ volume: intraday_data['volume']
42
+ )
43
+ end
44
+ end
45
+ end # class << self
46
+
47
+ attr_reader\
48
+ :exchange_code,
49
+ :symbol,
50
+ :interval,
51
+ :timestamp,
52
+ :gmtoffset,
53
+ :datetime,
54
+ :open,
55
+ :high,
56
+ :low,
57
+ :close,
58
+ :volume
59
+
60
+ private
61
+
62
+ def initialize(exchange_code:, symbol:, interval:, timestamp:, gmtoffset:, datetime:, open:, high:, low:, close:, volume:)
63
+ @exchange_code = exchange_code
64
+ @symbol = symbol
65
+ @interval = interval
66
+ @timestamp = timestamp
67
+ @gmtoffset = gmtoffset
68
+ @datetime = datetime
69
+ @open = open
70
+ @high = high
71
+ @low = low
72
+ @close = close
73
+ @volume = volume
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,3 @@
1
+ class Eodhd
2
+ VERSION = '0.19.5'
3
+ end
@@ -0,0 +1,78 @@
1
+ # Eodhd/Validations.rb
2
+ # Eodhd::Validations
3
+
4
+ class Eodhd
5
+ module Validations
6
+ def validate_arguments(exchange_code: nil, exchange_id: nil, symbol: nil, period: nil, interval: nil, from: nil, to: nil, date: nil)
7
+ exchange_code ||= exchange_id
8
+ validate_exchange_code(exchange_code)
9
+ validate_symbol(symbol)
10
+ validate_period(period)
11
+ validate_interval(interval)
12
+ validate_date(from, 'from')
13
+ validate_date(to, 'to')
14
+ validate_date_range(from, to)
15
+ end
16
+
17
+ def validate_exchange_code(exchange_code)
18
+ return unless exchange_code
19
+ unless exchange_code.match?(/\A[A-Z]{2,6}\z/)
20
+ raise ArgumentError, "Invalid exchange_code '#{exchange_code}'. Must be 2-6 uppercase letters"
21
+ end
22
+ end
23
+
24
+ def validate_symbol(symbol)
25
+ return unless symbol
26
+ if symbol.strip.empty?
27
+ raise ArgumentError, "Symbol cannot be empty"
28
+ end
29
+ unless symbol.match?(/\A[A-Za-z0-9.-]{1,12}\z/)
30
+ raise ArgumentError, "Invalid symbol '#{symbol}'. Must be 1-12 characters, letters/numbers/dots/hyphens only"
31
+ end
32
+ end
33
+
34
+ def validate_period(period)
35
+ return unless period
36
+ valid_periods = %w[d w m]
37
+ unless valid_periods.include?(period)
38
+ raise ArgumentError, "Invalid period '#{period}'. Must be one of: #{valid_periods.join(', ')}"
39
+ end
40
+ end
41
+
42
+ def validate_interval(interval)
43
+ return unless interval
44
+ valid_intervals = %w[1m 5m 15m 30m 1h 4h 1d 1w 1mo]
45
+ unless valid_intervals.include?(interval)
46
+ raise ArgumentError, "Invalid interval: #{interval}. Must be one of: #{valid_intervals.join(', ')}"
47
+ end
48
+ end
49
+
50
+ def validate_date(date, param_name = 'date')
51
+ return unless date
52
+ case date
53
+ when Date
54
+ return
55
+ when String
56
+ unless date.match?(/\A\d{4}-\d{2}-\d{2}\z/)
57
+ raise ArgumentError, "Invalid #{param_name} '#{date}'. Must be in format 'yyyy-mm-dd'"
58
+ end
59
+ begin
60
+ Date.parse(date)
61
+ rescue Date::Error
62
+ raise ArgumentError, "Invalid #{param_name} '#{date}'. Not a valid date"
63
+ end
64
+ else
65
+ raise ArgumentError, "Invalid #{param_name}. Must be String in 'yyyy-mm-dd' format or Date object"
66
+ end
67
+ end
68
+
69
+ def validate_date_range(from, to)
70
+ return unless from && to
71
+ from_date = from.is_a?(Date) ? from : Date.parse(from.to_s)
72
+ to_date = to.is_a?(Date) ? to : Date.parse(to.to_s)
73
+ if from_date > to_date
74
+ raise ArgumentError, "from date (#{from}) cannot be after to date (#{to})"
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,130 @@
1
+ # Eodhd/WebSocketClient.rb
2
+ # Eodhd::WebSocketClient
3
+
4
+ # Notes:
5
+ # 1. All 4 of 4 WebSocket endpoints which Eodhd provides (See https://eodhd.com/financial-apis/new-real-time-data-api-websockets.) are as folllows...
6
+ # i. US market trade data: wss://ws.eodhistoricaldata.com/ws/us?api_token=XXX
7
+ # ii. US market quote data: wss://ws.eodhistoricaldata.com/ws/us-quote?api_token=XXX
8
+ # iii. Forex: wss://ws.eodhistoricaldata.com/ws/forex?api_token=XXX
9
+ # iv. Crypto: wss://ws.eodhistoricaldata.com/ws/crypto?api_token=XXX
10
+
11
+ require 'iodine'
12
+ require 'json'
13
+
14
+ require_relative './DefaultLogger'
15
+ require_relative './Error'
16
+
17
+ class Eodhd
18
+ class WebSocketClient
19
+ API_HOST = 'ws.eodhistoricaldata.com'
20
+
21
+ class Handler
22
+ def on_open(connection)
23
+ connection.write({action: 'subscribe', symbols: @symbols}.to_json)
24
+ end
25
+
26
+ def on_message(connection, message)
27
+ dataframe = handle_response(message)
28
+ @consumer.call(dataframe) if @consumer
29
+ end
30
+
31
+ def on_close(connection)
32
+ Iodine.stop
33
+ end
34
+
35
+ private
36
+
37
+ def initialize(symbols:, consumer:, logger: nil)
38
+ @symbols = symbols
39
+ @consumer = consumer
40
+ @logger = logger
41
+ end
42
+
43
+ def log_error(code:, message:, body:)
44
+ log_string = "WebSocketError #{code}\n#{message}\n#{body}"
45
+ @logger.error(log_string) if use_logging?
46
+ end
47
+
48
+ def handle_response(message)
49
+ JSON.parse(message)
50
+ rescue StandardError => e
51
+ log_error(code: 'ws_parse_error', message: e.message, body: message)
52
+ raise Eodhd::Error.new(code: 'ws_parse_error', message: e.message, body: message)
53
+ end
54
+
55
+ def use_logging?
56
+ !@logger.nil?
57
+ end
58
+ end
59
+
60
+ class << self
61
+ def path_prefix
62
+ '/ws'
63
+ end
64
+
65
+ def us_trade(api_token:, symbols:, consumer:)
66
+ self.class.new(api_token: api_token, asset_class: 'us', symbols: symbols, consumer: consumer)
67
+ end
68
+ alias_method :us, :us_trade
69
+
70
+ def us_quote(api_token:, symbols:, consumer:)
71
+ self.class.new(api_token: api_token, asset_class: 'us-quote', symbols: symbols, consumer: consumer)
72
+ end
73
+
74
+ def forex(api_token:, symbols:, consumer:)
75
+ self.class.new(api_token: api_token, asset_class: 'forex', symbols: symbols, consumer: consumer)
76
+ end
77
+
78
+ def crypto(api_token:, symbols:, consumer:)
79
+ self.class.new(api_token: api_token, asset_class: 'crypto', symbols: symbols, consumer: consumer)
80
+ end
81
+ end # class << self
82
+
83
+ attr_accessor\
84
+ :api_token,
85
+ :asset_class,
86
+ :consumer,
87
+ :logger
88
+
89
+ attr_reader\
90
+ :symbols
91
+
92
+ def symbols=(symbols)
93
+ @symbols = format_symbols(symbols)
94
+ end
95
+
96
+ def run
97
+ Iodine.threads = 1
98
+ Iodine.connect(url: url, handler: Handler.new(symbols: @symbols, consumer: @consumer, logger: @logger))
99
+ Iodine.start
100
+ rescue SystemExit, Interrupt
101
+ return
102
+ end
103
+
104
+ private
105
+
106
+ def initialize(api_token:, asset_class:, symbols:, consumer:, logger: nil, use_default_logger: false)
107
+ @api_token = api_token
108
+ @asset_class = asset_class # crypto, forex, us-quote, us
109
+ @symbols = format_symbols(symbols)
110
+ @consumer = consumer
111
+ @logger = use_default_logger ? DefaultLogger.logger : logger
112
+ end
113
+
114
+ def use_logging?
115
+ !@logger.nil?
116
+ end
117
+
118
+ def format_symbols(symbols)
119
+ if symbols.is_a?(Array)
120
+ symbols.join(',')
121
+ elsif symbols.is_a?(String)
122
+ symbols
123
+ end
124
+ end
125
+
126
+ def url
127
+ "wss://#{API_HOST}#{self.class.path_prefix}/#{@asset_class}?api_token=#{@api_token}"
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,7 @@
1
+ # Hash/x_www_form_urlencode.rb
2
+ # Hash#x_www_form_urlencode
3
+
4
+ # 20241009
5
+ # 0.1.0
6
+
7
+ require 'Thoran/Hash/XWwwFormUrlencode/x_www_form_urlencode'
@@ -0,0 +1,27 @@
1
+ # Thoran/Hash/XWwwFormUrlencode/x_www_form_urlencode.rb
2
+ # Thoran::Hash::XWwwFormUrlencode#x_www_form_urlencode
3
+
4
+ # 20260713
5
+ # 0.3.0
6
+
7
+ # Changes since 0.2:
8
+ # -/0: (The class name and the snake case name really are consistent now.)
9
+ # 1. /XWwwFormUrlEncode/XWwwFormUrlencode/
10
+ # 2. /url_encode/urlencode/
11
+
12
+ require 'Thoran/String/Urlencode/urlencode'
13
+
14
+ module Thoran
15
+ module Hash
16
+ module XWwwFormUrlencode
17
+
18
+ def x_www_form_urlencode(joiner = '&')
19
+ inject([]){|a,e| a << "#{e.first.to_s.urlencode}=#{e.last.to_s.urlencode}" unless e.last.nil?; a}.join(joiner)
20
+ end
21
+ alias_method :x_www_form_url_encode, :x_www_form_urlencode
22
+
23
+ end
24
+ end
25
+ end
26
+
27
+ Hash.send(:include, Thoran::Hash::XWwwFormUrlencode)
@@ -0,0 +1,29 @@
1
+ # Thoran/String/Urlencode/urlencode.rb
2
+ # Thoran::String::Urlencode#urlencode
3
+
4
+ # 20260713
5
+ # 0.4.0
6
+
7
+ # Acknowledgements: I've simply ripped off and refashioned the code from the CGI module!...
8
+
9
+ # Changes since 0.3:
10
+ # -/0: More consistent naming.
11
+ # 1. /UrlEncode/Urlencode/
12
+ # 2. /url_encode/urlencode/
13
+
14
+ module Thoran
15
+ module String
16
+ module Urlencode
17
+
18
+ def urlencode
19
+ self.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
20
+ '%' + $1.unpack('H2' * $1.size).join('%').upcase
21
+ end.tr(' ', '+')
22
+ end
23
+ alias_method :url_encode, :urlencode
24
+
25
+ end
26
+ end
27
+ end
28
+
29
+ String.send(:include, Thoran::String::Urlencode)
data/lib/eodhd.rb ADDED
@@ -0,0 +1,78 @@
1
+ # Eodhd.rb
2
+ # Eodhd
3
+
4
+ require_relative './Eodhd/Client'
5
+ require_relative './Eodhd/EodBulkLastDay'
6
+ require_relative './Eodhd/EodData'
7
+ require_relative './Eodhd/Exchange'
8
+ require_relative './Eodhd/ExchangeSymbol'
9
+ require_relative './Eodhd/Intraday'
10
+ require_relative './Eodhd/Fundamentals'
11
+ require_relative './Eodhd/WebSocketClient'
12
+ require_relative './Eodhd/VERSION'
13
+
14
+ class Eodhd
15
+ def exchanges
16
+ Eodhd::Exchange.all(api_token: @api_token)
17
+ end
18
+
19
+ def exchange_symbols(exchange: nil, exchange_code: nil)
20
+ exchange_code ||= exchange&.code
21
+ Eodhd::ExchangeSymbol.all(api_token: @api_token, exchange_code: exchange_code)
22
+ end
23
+
24
+ def eod_data(exchange: nil, exchange_code: nil, exchange_symbol: nil, symbol: nil, period: nil, from: nil, to: nil)
25
+ exchange_code ||= exchange&.code
26
+ symbol ||= exchange_symbol&.code
27
+ Eodhd::EodData.all(api_token: @api_token, exchange_code: exchange_code, symbol: symbol, period: period, from: from, to: to)
28
+ end
29
+
30
+ def eod_bulk_last_day(exchange: nil, exchange_code: nil, date:)
31
+ exchange_code ||= exchange&.code
32
+ Eodhd::EodBulkLastDay.all(api_token: @api_token, exchange_code: exchange_code, date: date)
33
+ end
34
+
35
+ def intraday(exchange: nil, exchange_code: nil, exchange_symbol: nil, symbol: nil, interval:, from: nil, to: nil)
36
+ exchange_code ||= exchange&.code
37
+ symbol ||= exchange_symbol&.code
38
+ Eodhd::Intraday.all(api_token: @api_token, exchange_code: exchange_code, symbol: symbol, interval: interval, from: from, to: to)
39
+ end
40
+
41
+ def fundamentals(exchange: nil, exchange_code: nil, exchange_symbol: nil, symbol: nil, filter: nil)
42
+ exchange_code ||= exchange&.code
43
+ symbol ||= exchange_symbol&.code
44
+ Eodhd::Fundamentals.all(api_token: @api_token, exchange_code: exchange_code, symbol: symbol, filter: filter)
45
+ end
46
+
47
+ def us_trade_stream(symbols)
48
+ stream(asset_class: 'us', symbols: symbols)
49
+ end
50
+ alias_method :us_stream, :us_trade_stream
51
+
52
+ def us_quote_stream(symbols)
53
+ stream(asset_class: 'us-quote', symbols: symbols)
54
+ end
55
+
56
+ def forex_stream(symbols)
57
+ stream(asset_class: 'forex', symbols: symbols)
58
+ end
59
+
60
+ def crypto_stream(symbols)
61
+ stream(asset_class: 'crypto', symbols: symbols)
62
+ end
63
+
64
+ private
65
+
66
+ def initialize(api_token:, consumer: nil)
67
+ @api_token = api_token
68
+ @consumer = consumer
69
+ end
70
+
71
+ def web_socket(asset_class:, symbols:)
72
+ Eodhd::WebSocketClient.new(api_token: @api_token, asset_class: asset_class, symbols: symbols, consumer: @consumer)
73
+ end
74
+
75
+ def stream(asset_class:, symbols:)
76
+ web_socket(asset_class: asset_class, symbols: symbols).run
77
+ end
78
+ end
@@ -0,0 +1,111 @@
1
+ # Eodhd/Client_test.rb
2
+
3
+ require_relative '../helper'
4
+
5
+ describe Eodhd::Client do
6
+ let(:api_token){ENV.fetch('EODHD_API_TOKEN', '<API_TOKEN>')}
7
+ let(:client){Eodhd::Client.new(api_token: api_token)}
8
+
9
+ describe "#exchanges_list" do
10
+ it "returns parsed JSON array" do
11
+ VCR.use_cassette("client_exchanges_list") do
12
+ result = client.exchanges_list
13
+ _(result).must_be_kind_of(Array)
14
+ _(result.first["Code"]).wont_be_nil
15
+ end
16
+ end
17
+
18
+ it "raises Eodhd::Error on failure" do
19
+ VCR.use_cassette("client_exchanges_list_401_error") do
20
+ _{client.exchanges_list}.must_raise(Eodhd::Error)
21
+ end
22
+ end
23
+ end
24
+
25
+ describe "#exchange_symbol_list" do
26
+ it "returns parsed JSON array" do
27
+ VCR.use_cassette("client_exchange_symbol_list") do
28
+ result = client.exchange_symbol_list(exchange_code: 'AU')
29
+ _(result).must_be_kind_of(Array)
30
+ _(result.first['Code']).wont_be_nil
31
+ end
32
+ end
33
+
34
+ it "raises Eodhd::Error on failure" do
35
+ VCR.use_cassette("client_exchange_symbol_list_401_error") do
36
+ _{client.exchange_symbol_list(exchange_code: 'AU')}.must_raise(Eodhd::Error)
37
+ end
38
+ end
39
+ end
40
+
41
+ describe "#eod_data" do
42
+ it "returns parsed JSON array" do
43
+ VCR.use_cassette('client_eod_data') do
44
+ result = client.eod_data(exchange_id: 'AU', symbol: 'BHP', period: 'd')
45
+ _(result).must_be_kind_of(Array)
46
+ _(result.first.keys).must_equal(%w{date open high low close adjusted_close volume})
47
+ end
48
+ end
49
+
50
+ it "raises Eodhd::Error on failure" do
51
+ VCR.use_cassette('client_eod_data_401_error') do
52
+ _{client.eod_data(exchange_id: 'AU', symbol: 'BHP', period: 'd')}.must_raise(Eodhd::Error)
53
+ end
54
+ end
55
+ end
56
+
57
+ describe "#eod_bulk_last_day" do
58
+ it "returns parsed JSON array" do
59
+ VCR.use_cassette("client_eod_bulk_last_day") do
60
+ result = client.eod_bulk_last_day(exchange_id: 'AU', date: "2024-09-30")
61
+ _(result).must_be_kind_of(Array)
62
+ _(result.first.keys).must_equal(%w{code exchange_short_name date open high low close adjusted_close volume})
63
+ end
64
+ end
65
+
66
+ it "raises Eodhd::Error on failure" do
67
+ VCR.use_cassette('client_eod_bulk_last_day_401_error') do
68
+ _{client.eod_bulk_last_day(exchange_id: 'AU', date: "2024-09-30")}.must_raise(Eodhd::Error)
69
+ end
70
+ end
71
+ end
72
+
73
+ describe "#intraday" do
74
+ it "returns parsed JSON array" do
75
+ VCR.use_cassette('client_intraday') do
76
+ result = client.intraday(exchange_code: 'US', symbol: 'AAPL', interval: '5m')
77
+ _(result).must_be_kind_of(Array)
78
+ _(result.first.keys).must_equal(%w{timestamp gmtoffset datetime open high low close volume})
79
+ end
80
+ end
81
+
82
+ it "raises Eodhd::Error on failure" do
83
+ VCR.use_cassette('client_intraday_401_error') do
84
+ _{client.intraday(exchange_code: 'US', symbol: 'AAPL', interval: '5m')}.must_raise(Eodhd::Error)
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "#fundamentals" do
90
+ it "returns parsed JSON hash" do
91
+ VCR.use_cassette('client_fundamentals') do
92
+ result = client.fundamentals(exchange_code: 'US', symbol: 'AAPL')
93
+ _(result).must_be_kind_of(Hash)
94
+ _(result['General']).wont_be_nil
95
+ end
96
+ end
97
+
98
+ it "supports filter parameter" do
99
+ VCR.use_cassette('client_fundamentals_filtered') do
100
+ result = client.fundamentals(exchange_code: 'US', symbol: 'AAPL', filter: 'General')
101
+ _(result).must_be_kind_of(Hash)
102
+ end
103
+ end
104
+
105
+ it "raises Eodhd::Error on failure" do
106
+ VCR.use_cassette('client_fundamentals_401_error') do
107
+ _{client.fundamentals(exchange_code: 'US', symbol: 'AAPL')}.must_raise(Eodhd::Error)
108
+ end
109
+ end
110
+ end
111
+ end