yf_as_dataframe 0.2.15
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 +7 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG.rst +0 -0
- data/CODE_OF_CONDUCT.md +15 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +299 -0
- data/Rakefile +8 -0
- data/chart.png +0 -0
- data/lib/yf_as_dataframe/analysis.rb +68 -0
- data/lib/yf_as_dataframe/financials.rb +304 -0
- data/lib/yf_as_dataframe/fundamentals.rb +53 -0
- data/lib/yf_as_dataframe/holders.rb +253 -0
- data/lib/yf_as_dataframe/multi.rb +238 -0
- data/lib/yf_as_dataframe/price_history.rb +2045 -0
- data/lib/yf_as_dataframe/price_technical.rb +579 -0
- data/lib/yf_as_dataframe/quote.rb +343 -0
- data/lib/yf_as_dataframe/ticker.rb +380 -0
- data/lib/yf_as_dataframe/tickers.rb +50 -0
- data/lib/yf_as_dataframe/utils.rb +354 -0
- data/lib/yf_as_dataframe/version.rb +3 -0
- data/lib/yf_as_dataframe/yf_connection.rb +304 -0
- data/lib/yf_as_dataframe/yfinance_exception.rb +15 -0
- data/lib/yf_as_dataframe.rb +24 -0
- metadata +139 -0
@@ -0,0 +1,304 @@
|
|
1
|
+
require 'polars-df'
|
2
|
+
|
3
|
+
class YfAsDataframe
|
4
|
+
module Financials
|
5
|
+
include ActiveSupport::Inflector
|
6
|
+
|
7
|
+
def self.included(base) # built-in Ruby hook for modules
|
8
|
+
base.class_eval do
|
9
|
+
original_method = instance_method(:initialize)
|
10
|
+
define_method(:initialize) do |*args, &block|
|
11
|
+
original_method.bind(self).call(*args, &block)
|
12
|
+
initialize_financials # (your module code here)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize_financials
|
18
|
+
@income_time_series = {}
|
19
|
+
@balance_sheet_time_series = {}
|
20
|
+
@cash_flow_time_series = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def income_stmt; _get_income_stmt(pretty: true); end
|
24
|
+
def quarterly_income_stmt; _get_income_stmt(pretty: true, freq: 'quarterly'); end
|
25
|
+
alias_method :quarterly_incomestmt, :quarterly_income_stmt
|
26
|
+
alias_method :quarterly_financials, :quarterly_income_stmt
|
27
|
+
alias_method :annual_incomestmt, :income_stmt
|
28
|
+
alias_method :annual_income_stmt, :income_stmt
|
29
|
+
alias_method :annual_financials, :income_stmt
|
30
|
+
|
31
|
+
def balance_sheet; _get_balance_sheet(pretty: true); end
|
32
|
+
def quarterly_balance_sheet; _get_balance_sheet(pretty: true, freq: 'quarterly'); end
|
33
|
+
alias_method :quarterly_balancesheet, :quarterly_balance_sheet
|
34
|
+
alias_method :annual_balance_sheet, :balance_sheet
|
35
|
+
alias_method :annual_balancesheet, :balance_sheet
|
36
|
+
|
37
|
+
def cash_flow; _get_cash_flow(pretty: true, freq: 'yearly'); end
|
38
|
+
alias_method :cashflow, :cash_flow
|
39
|
+
def quarterly_cash_flow; _get_cash_flow(pretty: true, freq: 'quarterly'); end
|
40
|
+
alias_method :quarterly_cashflow, :quarterly_cash_flow
|
41
|
+
alias_method :annual_cashflow, :cash_flow
|
42
|
+
alias_method :annual_cash_flow, :cash_flow
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def _get_cash_flow(as_dict: false, pretty: false, freq: "yearly")
|
55
|
+
data = _get_cash_flow_time_series(freq)
|
56
|
+
|
57
|
+
if pretty
|
58
|
+
# data = data.dup
|
59
|
+
# data.index = YfAsDataframe::Utils.camel2title(data.index, sep: ' ', acronyms: ["PPE"])
|
60
|
+
end
|
61
|
+
|
62
|
+
as_dict ? data.to_h : data
|
63
|
+
end
|
64
|
+
|
65
|
+
def _get_income_stmt(as_dict: false, pretty: false, freq: "yearly")
|
66
|
+
data = _get_income_time_series(freq)
|
67
|
+
|
68
|
+
if pretty
|
69
|
+
# data = data.dup
|
70
|
+
# data.index = YfAsDataframe::Utils.camel2title(data.index, sep: ' ', acronyms: ["EBIT", "EBITDA", "EPS", "NI"])
|
71
|
+
end
|
72
|
+
|
73
|
+
as_dict ? data.to_h : data
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def _get_balance_sheet(as_dict: false, pretty: false, freq: "yearly")
|
78
|
+
data = _get_balance_sheet_time_series(freq)
|
79
|
+
|
80
|
+
if pretty
|
81
|
+
# data = data.dup
|
82
|
+
# data.index = YfAsDataframe::Utils.camel2title(data.index, sep: ' ', acronyms: ["PPE"])
|
83
|
+
end
|
84
|
+
|
85
|
+
as_dict ? data.to_h : data
|
86
|
+
end
|
87
|
+
|
88
|
+
def _get_income_time_series(freq = "yearly")
|
89
|
+
res = @income_time_series
|
90
|
+
res[freq] ||= _fetch_time_series("income", freq)
|
91
|
+
res[freq]
|
92
|
+
end
|
93
|
+
|
94
|
+
def _get_balance_sheet_time_series(freq = "yearly")
|
95
|
+
res = @balance_sheet_time_series
|
96
|
+
res[freq] ||= _fetch_time_series("balancesheet", freq)
|
97
|
+
res[freq]
|
98
|
+
end
|
99
|
+
|
100
|
+
def _get_cash_flow_time_series(freq = "yearly")
|
101
|
+
res = @cash_flow_time_series
|
102
|
+
res[freq] ||= _fetch_time_series("cashflow", freq)
|
103
|
+
res[freq]
|
104
|
+
end
|
105
|
+
|
106
|
+
def _get_financials_time_series(timescale, ts_keys)
|
107
|
+
Polars::Config.set_tbl_rows(-1)
|
108
|
+
timescale_translation = { "yearly" => "annual", "quarterly" => "quarterly" }
|
109
|
+
timescale = timescale_translation[timescale]
|
110
|
+
|
111
|
+
ts_url_base = "https://query2.finance.yahoo.com/ws/fundamentals-timeseries/v1/finance/timeseries/#{symbol}?symbol=#{symbol}"
|
112
|
+
url = ts_url_base + "&type=" + ts_keys.map { |k| "#{timescale}#{k}" }.join(",")
|
113
|
+
start_dt = DateTime.new(2016, 12, 31)
|
114
|
+
end_dt = DateTime.now.tomorrow.midnight
|
115
|
+
url += "&period1=#{start_dt.to_i}&period2=#{end_dt.to_i}"
|
116
|
+
|
117
|
+
json_str = get(url).parsed_response
|
118
|
+
data_raw = json_str["timeseries"]["result"]
|
119
|
+
data_raw.each { |d| d.delete("meta") }
|
120
|
+
|
121
|
+
timestamps = data_raw.map{|d| d['timestamp']}.flatten.uniq.compact.uniq.sort.reverse
|
122
|
+
|
123
|
+
cols = [ :metric ] + timestamps.map{|ts| Time.at(ts).utc.to_date.to_s }
|
124
|
+
df = {}; cols.each {|c| df[c] = [] }
|
125
|
+
ts_keys.each { |k| df[:metric] << k.gsub(/([A-Z]+)/,' \1').strip }
|
126
|
+
|
127
|
+
timestamps.each do |ts|
|
128
|
+
ts_date = Time.at(ts).utc.to_date.to_s
|
129
|
+
|
130
|
+
ts_keys.each_with_index do |k, ndex|
|
131
|
+
l = "#{timescale}#{k}"
|
132
|
+
d = data_raw.detect{|dd| dd.key?(l) }
|
133
|
+
if d.nil?
|
134
|
+
df[ts_date] << ''
|
135
|
+
next
|
136
|
+
end
|
137
|
+
tv = d[l].detect{|dd| dd['asOfDate'] == ts_date }
|
138
|
+
df[ts_date] << (tv.nil? ? '' : tv['reportedValue']['raw'].to_s)
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
df = Polars::DataFrame.new(df)
|
144
|
+
timestamps.map{|ts| Time.at(ts).utc.to_date.to_s }.each do |t|
|
145
|
+
puts t
|
146
|
+
df.replace(t, Polars::Series.new(df[t].cast(Polars::String)))
|
147
|
+
end
|
148
|
+
df
|
149
|
+
end
|
150
|
+
|
151
|
+
def _fetch_time_series(nam, timescale)
|
152
|
+
# Rails.logger.info { "#{__FILE__}:#{__LINE__}"}
|
153
|
+
allowed_names = FUNDAMENTALS_KEYS.keys + [:income]
|
154
|
+
allowed_timescales = ["yearly", "quarterly"]
|
155
|
+
|
156
|
+
raise ArgumentError, "Illegal argument: name (#{nam}) must be one of: #{allowed_names}" unless allowed_names.include?(nam.to_sym)
|
157
|
+
raise ArgumentError, "Illegal argument: timescale (#{timescale}) must be one of: #{allowed_timescales}" unless allowed_timescales.include?(timescale)
|
158
|
+
|
159
|
+
begin
|
160
|
+
statement = _create_financials_table(nam, timescale)
|
161
|
+
return statement unless statement.nil?
|
162
|
+
rescue Yfin::YfinDataException => e
|
163
|
+
Rails.logger.error {"#{@symbol}: Failed to create #{nam} financials table for reason: #{e}"}
|
164
|
+
end
|
165
|
+
Polars::DataFrame.new()
|
166
|
+
end
|
167
|
+
|
168
|
+
def _create_financials_table(nam, timescale)
|
169
|
+
nam = "financials" if nam == "income"
|
170
|
+
|
171
|
+
keys = FUNDAMENTALS_KEYS[nam.to_sym]
|
172
|
+
begin
|
173
|
+
_get_financials_time_series(timescale, keys)
|
174
|
+
rescue StandardError
|
175
|
+
nil
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
|
181
|
+
FUNDAMENTALS_KEYS = {
|
182
|
+
financials: [
|
183
|
+
"TaxEffectOfUnusualItems", "TaxRateForCalcs", "NormalizedEBITDA", "NormalizedDilutedEPS",
|
184
|
+
"NormalizedBasicEPS", "TotalUnusualItems", "TotalUnusualItemsExcludingGoodwill",
|
185
|
+
"NetIncomeFromContinuingOperationNetMinorityInterest", "ReconciledDepreciation",
|
186
|
+
"ReconciledCostOfRevenue", "EBITDA", "EBIT", "NetInterestIncome", "InterestExpense",
|
187
|
+
"InterestIncome", "ContinuingAndDiscontinuedDilutedEPS", "ContinuingAndDiscontinuedBasicEPS",
|
188
|
+
"NormalizedIncome", "NetIncomeFromContinuingAndDiscontinuedOperation", "TotalExpenses",
|
189
|
+
"RentExpenseSupplemental", "ReportedNormalizedDilutedEPS", "ReportedNormalizedBasicEPS",
|
190
|
+
"TotalOperatingIncomeAsReported", "DividendPerShare", "DilutedAverageShares", "BasicAverageShares",
|
191
|
+
"DilutedEPS", "DilutedEPSOtherGainsLosses", "TaxLossCarryforwardDilutedEPS",
|
192
|
+
"DilutedAccountingChange", "DilutedExtraordinary", "DilutedDiscontinuousOperations",
|
193
|
+
"DilutedContinuousOperations", "BasicEPS", "BasicEPSOtherGainsLosses", "TaxLossCarryforwardBasicEPS",
|
194
|
+
"BasicAccountingChange", "BasicExtraordinary", "BasicDiscontinuousOperations",
|
195
|
+
"BasicContinuousOperations", "DilutedNIAvailtoComStockholders", "AverageDilutionEarnings",
|
196
|
+
"NetIncomeCommonStockholders", "OtherunderPreferredStockDividend", "PreferredStockDividends",
|
197
|
+
"NetIncome", "MinorityInterests", "NetIncomeIncludingNoncontrollingInterests",
|
198
|
+
"NetIncomeFromTaxLossCarryforward", "NetIncomeExtraordinary", "NetIncomeDiscontinuousOperations",
|
199
|
+
"NetIncomeContinuousOperations", "EarningsFromEquityInterestNetOfTax", "TaxProvision",
|
200
|
+
"PretaxIncome", "OtherIncomeExpense", "OtherNonOperatingIncomeExpenses", "SpecialIncomeCharges",
|
201
|
+
"GainOnSaleOfPPE", "GainOnSaleOfBusiness", "OtherSpecialCharges", "WriteOff",
|
202
|
+
"ImpairmentOfCapitalAssets", "RestructuringAndMergernAcquisition", "SecuritiesAmortization",
|
203
|
+
"EarningsFromEquityInterest", "GainOnSaleOfSecurity", "NetNonOperatingInterestIncomeExpense",
|
204
|
+
"TotalOtherFinanceCost", "InterestExpenseNonOperating", "InterestIncomeNonOperating",
|
205
|
+
"OperatingIncome", "OperatingExpense", "OtherOperatingExpenses", "OtherTaxes",
|
206
|
+
"ProvisionForDoubtfulAccounts", "DepreciationAmortizationDepletionIncomeStatement",
|
207
|
+
"DepletionIncomeStatement", "DepreciationAndAmortizationInIncomeStatement", "Amortization",
|
208
|
+
"AmortizationOfIntangiblesIncomeStatement", "DepreciationIncomeStatement", "ResearchAndDevelopment",
|
209
|
+
"SellingGeneralAndAdministration", "SellingAndMarketingExpense", "GeneralAndAdministrativeExpense",
|
210
|
+
"OtherGandA", "InsuranceAndClaims", "RentAndLandingFees", "SalariesAndWages", "GrossProfit",
|
211
|
+
"CostOfRevenue", "TotalRevenue", "ExciseTaxes", "OperatingRevenue"
|
212
|
+
],
|
213
|
+
balancesheet: [
|
214
|
+
"TreasurySharesNumber", "PreferredSharesNumber", "OrdinarySharesNumber", "ShareIssued", "NetDebt",
|
215
|
+
"TotalDebt", "TangibleBookValue", "InvestedCapital", "WorkingCapital", "NetTangibleAssets",
|
216
|
+
"CapitalLeaseObligations", "CommonStockEquity", "PreferredStockEquity", "TotalCapitalization",
|
217
|
+
"TotalEquityGrossMinorityInterest", "MinorityInterest", "StockholdersEquity",
|
218
|
+
"OtherEquityInterest", "GainsLossesNotAffectingRetainedEarnings", "OtherEquityAdjustments",
|
219
|
+
"FixedAssetsRevaluationReserve", "ForeignCurrencyTranslationAdjustments",
|
220
|
+
"MinimumPensionLiabilities", "UnrealizedGainLoss", "TreasuryStock", "RetainedEarnings",
|
221
|
+
"AdditionalPaidInCapital", "CapitalStock", "OtherCapitalStock", "CommonStock", "PreferredStock",
|
222
|
+
"TotalPartnershipCapital", "GeneralPartnershipCapital", "LimitedPartnershipCapital",
|
223
|
+
"TotalLiabilitiesNetMinorityInterest", "TotalNonCurrentLiabilitiesNetMinorityInterest",
|
224
|
+
"OtherNonCurrentLiabilities", "LiabilitiesHeldforSaleNonCurrent", "RestrictedCommonStock",
|
225
|
+
"PreferredSecuritiesOutsideStockEquity", "DerivativeProductLiabilities", "EmployeeBenefits",
|
226
|
+
"NonCurrentPensionAndOtherPostretirementBenefitPlans", "NonCurrentAccruedExpenses",
|
227
|
+
"DuetoRelatedPartiesNonCurrent", "TradeandOtherPayablesNonCurrent",
|
228
|
+
"NonCurrentDeferredLiabilities", "NonCurrentDeferredRevenue",
|
229
|
+
"NonCurrentDeferredTaxesLiabilities", "LongTermDebtAndCapitalLeaseObligation",
|
230
|
+
"LongTermCapitalLeaseObligation", "LongTermDebt", "LongTermProvisions", "CurrentLiabilities",
|
231
|
+
"OtherCurrentLiabilities", "CurrentDeferredLiabilities", "CurrentDeferredRevenue",
|
232
|
+
"CurrentDeferredTaxesLiabilities", "CurrentDebtAndCapitalLeaseObligation",
|
233
|
+
"CurrentCapitalLeaseObligation", "CurrentDebt", "OtherCurrentBorrowings", "LineOfCredit",
|
234
|
+
"CommercialPaper", "CurrentNotesPayable", "PensionandOtherPostRetirementBenefitPlansCurrent",
|
235
|
+
"CurrentProvisions", "PayablesAndAccruedExpenses", "CurrentAccruedExpenses", "InterestPayable",
|
236
|
+
"Payables", "OtherPayable", "DuetoRelatedPartiesCurrent", "DividendsPayable", "TotalTaxPayable",
|
237
|
+
"IncomeTaxPayable", "AccountsPayable", "TotalAssets", "TotalNonCurrentAssets",
|
238
|
+
"OtherNonCurrentAssets", "DefinedPensionBenefit", "NonCurrentPrepaidAssets",
|
239
|
+
"NonCurrentDeferredAssets", "NonCurrentDeferredTaxesAssets", "DuefromRelatedPartiesNonCurrent",
|
240
|
+
"NonCurrentNoteReceivables", "NonCurrentAccountsReceivable", "FinancialAssets",
|
241
|
+
"InvestmentsAndAdvances", "OtherInvestments", "InvestmentinFinancialAssets",
|
242
|
+
"HeldToMaturitySecurities", "AvailableForSaleSecurities",
|
243
|
+
"FinancialAssetsDesignatedasFairValueThroughProfitorLossTotal", "TradingSecurities",
|
244
|
+
"LongTermEquityInvestment", "InvestmentsinJointVenturesatCost",
|
245
|
+
"InvestmentsInOtherVenturesUnderEquityMethod", "InvestmentsinAssociatesatCost",
|
246
|
+
"InvestmentsinSubsidiariesatCost", "InvestmentProperties", "GoodwillAndOtherIntangibleAssets",
|
247
|
+
"OtherIntangibleAssets", "Goodwill", "NetPPE", "AccumulatedDepreciation", "GrossPPE", "Leases",
|
248
|
+
"ConstructionInProgress", "OtherProperties", "MachineryFurnitureEquipment",
|
249
|
+
"BuildingsAndImprovements", "LandAndImprovements", "Properties", "CurrentAssets",
|
250
|
+
"OtherCurrentAssets", "HedgingAssetsCurrent", "AssetsHeldForSaleCurrent", "CurrentDeferredAssets",
|
251
|
+
"CurrentDeferredTaxesAssets", "RestrictedCash", "PrepaidAssets", "Inventory",
|
252
|
+
"InventoriesAdjustmentsAllowances", "OtherInventories", "FinishedGoods", "WorkInProcess",
|
253
|
+
"RawMaterials", "Receivables", "ReceivablesAdjustmentsAllowances", "OtherReceivables",
|
254
|
+
"DuefromRelatedPartiesCurrent", "TaxesReceivable", "AccruedInterestReceivable", "NotesReceivable",
|
255
|
+
"LoansReceivable", "AccountsReceivable", "AllowanceForDoubtfulAccountsReceivable",
|
256
|
+
"GrossAccountsReceivable", "CashCashEquivalentsAndShortTermInvestments",
|
257
|
+
"OtherShortTermInvestments", "CashAndCashEquivalents", "CashEquivalents", "CashFinancial"
|
258
|
+
],
|
259
|
+
cashflow: [
|
260
|
+
"ForeignSales", "DomesticSales", "AdjustedGeographySegmentData", "FreeCashFlow",
|
261
|
+
"RepurchaseOfCapitalStock", "RepaymentOfDebt", "IssuanceOfDebt", "IssuanceOfCapitalStock",
|
262
|
+
"CapitalExpenditure", "InterestPaidSupplementalData", "IncomeTaxPaidSupplementalData",
|
263
|
+
"EndCashPosition", "OtherCashAdjustmentOutsideChangeinCash", "BeginningCashPosition",
|
264
|
+
"EffectOfExchangeRateChanges", "ChangesInCash", "OtherCashAdjustmentInsideChangeinCash",
|
265
|
+
"CashFlowFromDiscontinuedOperation", "FinancingCashFlow", "CashFromDiscontinuedFinancingActivities",
|
266
|
+
"CashFlowFromContinuingFinancingActivities", "NetOtherFinancingCharges", "InterestPaidCFF",
|
267
|
+
"ProceedsFromStockOptionExercised", "CashDividendsPaid", "PreferredStockDividendPaid",
|
268
|
+
"CommonStockDividendPaid", "NetPreferredStockIssuance", "PreferredStockPayments",
|
269
|
+
"PreferredStockIssuance", "NetCommonStockIssuance", "CommonStockPayments", "CommonStockIssuance",
|
270
|
+
"NetIssuancePaymentsOfDebt", "NetShortTermDebtIssuance", "ShortTermDebtPayments",
|
271
|
+
"ShortTermDebtIssuance", "NetLongTermDebtIssuance", "LongTermDebtPayments", "LongTermDebtIssuance",
|
272
|
+
"InvestingCashFlow", "CashFromDiscontinuedInvestingActivities",
|
273
|
+
"CashFlowFromContinuingInvestingActivities", "NetOtherInvestingChanges", "InterestReceivedCFI",
|
274
|
+
"DividendsReceivedCFI", "NetInvestmentPurchaseAndSale", "SaleOfInvestment", "PurchaseOfInvestment",
|
275
|
+
"NetInvestmentPropertiesPurchaseAndSale", "SaleOfInvestmentProperties",
|
276
|
+
"PurchaseOfInvestmentProperties", "NetBusinessPurchaseAndSale", "SaleOfBusiness",
|
277
|
+
"PurchaseOfBusiness", "NetIntangiblesPurchaseAndSale", "SaleOfIntangibles", "PurchaseOfIntangibles",
|
278
|
+
"NetPPEPurchaseAndSale", "SaleOfPPE", "PurchaseOfPPE", "CapitalExpenditureReported",
|
279
|
+
"OperatingCashFlow", "CashFromDiscontinuedOperatingActivities",
|
280
|
+
"CashFlowFromContinuingOperatingActivities", "TaxesRefundPaid", "InterestReceivedCFO",
|
281
|
+
"InterestPaidCFO", "DividendReceivedCFO", "DividendPaidCFO", "ChangeInWorkingCapital",
|
282
|
+
"ChangeInOtherWorkingCapital", "ChangeInOtherCurrentLiabilities", "ChangeInOtherCurrentAssets",
|
283
|
+
"ChangeInPayablesAndAccruedExpense", "ChangeInAccruedExpense", "ChangeInInterestPayable",
|
284
|
+
"ChangeInPayable", "ChangeInDividendPayable", "ChangeInAccountPayable", "ChangeInTaxPayable",
|
285
|
+
"ChangeInIncomeTaxPayable", "ChangeInPrepaidAssets", "ChangeInInventory", "ChangeInReceivables",
|
286
|
+
"ChangesInAccountReceivables", "OtherNonCashItems", "ExcessTaxBenefitFromStockBasedCompensation",
|
287
|
+
"StockBasedCompensation", "UnrealizedGainLossOnInvestmentSecurities", "ProvisionandWriteOffofAssets",
|
288
|
+
"AssetImpairmentCharge", "AmortizationOfSecurities", "DeferredTax", "DeferredIncomeTax",
|
289
|
+
"DepreciationAmortizationDepletion", "Depletion", "DepreciationAndAmortization",
|
290
|
+
"AmortizationCashFlow", "AmortizationOfIntangibles", "Depreciation", "OperatingGainsLosses",
|
291
|
+
"PensionAndEmployeeBenefitExpense", "EarningsLossesFromEquityInvestments",
|
292
|
+
"GainLossOnInvestmentSecurities", "NetForeignCurrencyExchangeGainLoss", "GainLossOnSaleOfPPE",
|
293
|
+
"GainLossOnSaleOfBusiness", "NetIncomeFromContinuingOperations",
|
294
|
+
"CashFlowsfromusedinOperatingActivitiesDirect", "TaxesRefundPaidDirect", "InterestReceivedDirect",
|
295
|
+
"InterestPaidDirect", "DividendsReceivedDirect", "DividendsPaidDirect", "ClassesofCashPayments",
|
296
|
+
"OtherCashPaymentsfromOperatingActivities", "PaymentsonBehalfofEmployees",
|
297
|
+
"PaymentstoSuppliersforGoodsandServices", "ClassesofCashReceiptsfromOperatingActivities",
|
298
|
+
"OtherCashReceiptsfromOperatingActivities", "ReceiptsfromGovernmentGrants", "ReceiptsfromCustomers"
|
299
|
+
]
|
300
|
+
}
|
301
|
+
|
302
|
+
|
303
|
+
end
|
304
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class YfAsDataframe
|
2
|
+
module Fundamentals
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def self.included(base) # built-in Ruby hook for modules
|
6
|
+
base.class_eval do
|
7
|
+
attr_reader :financials, :earnings, :shares, :ticker
|
8
|
+
|
9
|
+
original_method = instance_method(:initialize)
|
10
|
+
define_method(:initialize) do |*args, &block|
|
11
|
+
original_method.bind(self).call(*args, &block)
|
12
|
+
initialize_fundamentals # (your module code here)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize_fundamentals
|
18
|
+
@earnings = nil
|
19
|
+
@financials = nil
|
20
|
+
@shares = nil
|
21
|
+
|
22
|
+
@financials_data = nil
|
23
|
+
@fin_data_quote = nil
|
24
|
+
@basics_already_scraped = false
|
25
|
+
end
|
26
|
+
|
27
|
+
# delegate :proxy, :tz, to: :ticker
|
28
|
+
|
29
|
+
def earnings
|
30
|
+
raise YFNotImplementedError.new('earnings') if @earnings.nil?
|
31
|
+
@earnings
|
32
|
+
end
|
33
|
+
|
34
|
+
def shares
|
35
|
+
raise YFNotImplementedError.new('shares') if @shares.nil?
|
36
|
+
@shares
|
37
|
+
end
|
38
|
+
|
39
|
+
# financials_methods = [:income_stmt, :incomestmt, :financials, :balance_sheet, :balancesheet, :cash_flow, :cashflow]
|
40
|
+
# financials_methods.each do |meth|
|
41
|
+
# delegate "get_#{meth}".to_sym, meth, to: :financials
|
42
|
+
# end
|
43
|
+
|
44
|
+
# fundamentals_methods = [:earnings, :shares]
|
45
|
+
# fundamentals_methods.each do |meth|
|
46
|
+
# alias_method "get_#{meth}".to_sym, meth
|
47
|
+
# end
|
48
|
+
|
49
|
+
# def quarterly_earnings
|
50
|
+
# earnings(freq: 'quarterly')
|
51
|
+
# end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,253 @@
|
|
1
|
+
class YfAsDataframe
|
2
|
+
module Holders
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
# include YfConnection
|
5
|
+
|
6
|
+
BASE_URL = 'https://query2.finance.yahoo.com'.freeze
|
7
|
+
QUOTE_SUMMARY_URL = "#{BASE_URL}/v10/finance/quoteSummary/".freeze
|
8
|
+
|
9
|
+
# attr_accessor :ticker
|
10
|
+
|
11
|
+
def self.included(base) # built-in Ruby hook for modules
|
12
|
+
base.class_eval do
|
13
|
+
original_method = instance_method(:initialize)
|
14
|
+
define_method(:initialize) do |*args, &block|
|
15
|
+
original_method.bind(self).call(*args, &block)
|
16
|
+
initialize_holders # (your module code here)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize_holders
|
22
|
+
@major = nil
|
23
|
+
@major_direct_holders = nil
|
24
|
+
@institutional = nil
|
25
|
+
@mutualfund = nil
|
26
|
+
|
27
|
+
@insider_transactions = nil
|
28
|
+
@insider_purchases = nil
|
29
|
+
@insider_roster = nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def major
|
33
|
+
_fetch_and_parse if @major.nil?
|
34
|
+
return @major
|
35
|
+
end
|
36
|
+
|
37
|
+
alias_method :major_holders, :major
|
38
|
+
|
39
|
+
def institutional
|
40
|
+
_fetch_and_parse if @institutional.nil?
|
41
|
+
return @institutional
|
42
|
+
end
|
43
|
+
|
44
|
+
alias_method :institutional_holders, :institutional
|
45
|
+
|
46
|
+
def mutualfund
|
47
|
+
_fetch_and_parse if @mutualfund.nil?
|
48
|
+
return @mutualfund
|
49
|
+
end
|
50
|
+
|
51
|
+
alias_method :mutualfund_holders, :mutualfund
|
52
|
+
|
53
|
+
def insider_transactions
|
54
|
+
_fetch_and_parse if @insider_transactions.nil?
|
55
|
+
return @insider_transactions
|
56
|
+
end
|
57
|
+
|
58
|
+
def insider_purchases
|
59
|
+
_fetch_and_parse if @insider_purchases.nil?
|
60
|
+
return @insider_purchases
|
61
|
+
end
|
62
|
+
|
63
|
+
def insider_roster
|
64
|
+
return @insider_roster unless @insider_roster.nil?
|
65
|
+
|
66
|
+
_fetch_and_parse
|
67
|
+
return @insider_roster
|
68
|
+
end
|
69
|
+
|
70
|
+
alias_method :insider_roster_holders, :insider_roster
|
71
|
+
|
72
|
+
# holders_methods = [:major, :major_holders, :institutional, :institutional_holders, :mutualfund, \
|
73
|
+
# :mutualfund_holders, :insider_transactions, :insider_purchases, :insider_roster, \
|
74
|
+
# :insider_roster_holders]
|
75
|
+
# holders_methods.each do |meth|
|
76
|
+
# alias_method "get_#{meth}".to_sym, meth
|
77
|
+
# end
|
78
|
+
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def _fetch_for_parse(params) #(self, proxy, modules: list)
|
86
|
+
# raise YahooFinanceException("Should provide a list of modules, see available modules using `valid_modules`") if !modules.is_a?(Array)
|
87
|
+
|
88
|
+
# modules = modules.intersection(QUOTE_SUMMARY_VALID_MODULES) #[m for m in modules if m in quote_summary_valid_modules])
|
89
|
+
|
90
|
+
modules = params[:modules]
|
91
|
+
|
92
|
+
raise YahooFinanceException("No valid modules provided.") if modules.empty?
|
93
|
+
|
94
|
+
params_dict = {"modules": modules, "corsDomain": "finance.yahoo.com", "formatted": "false", "symbol": symbol}
|
95
|
+
|
96
|
+
begin
|
97
|
+
result = get_raw_json(QUOTE_SUMMARY_URL + "/#{symbol}", user_agent_headers=user_agent_headers, params=params_dict)
|
98
|
+
# Rails.logger.info { "#{__FILE__}:#{__LINE__} result = #{result.inspect}" }
|
99
|
+
rescue Exception => e
|
100
|
+
Rails.logger.error("ERROR: #{e.message}")
|
101
|
+
return nil
|
102
|
+
end
|
103
|
+
return result
|
104
|
+
end
|
105
|
+
|
106
|
+
# def _fetch_for_parse(params)
|
107
|
+
# url = "#{QUOTE_SUMMARY_URL}#{symbol}"
|
108
|
+
# Rails.logger.info { "#{__FILE__}:#{__LINE__} url: #{url}, params = #{params.inspect}" }
|
109
|
+
# get(url).parsed_response
|
110
|
+
|
111
|
+
# # JSON.parse(URI.open(url, proxy: proxy, 'User-Agent' => 'Mozilla/5.0 (compatible; yahoo-finance2/0.0.1)').read(query: params))
|
112
|
+
# end
|
113
|
+
|
114
|
+
def _fetch_and_parse
|
115
|
+
modules = ['institutionOwnership', 'fundOwnership', 'majorDirectHolders', 'majorHoldersBreakdown',
|
116
|
+
'insiderTransactions', 'insiderHolders', 'netSharePurchaseActivity'].join(',')
|
117
|
+
# Rails.logger.info { "#{__FILE__}:#{__LINE__} modules = #{modules.inspect}"}
|
118
|
+
params = { modules: modules, corsDomain: 'finance.yahoo.com', formatted: 'false' }
|
119
|
+
result = _fetch_for_parse(params)
|
120
|
+
|
121
|
+
_parse_result(result)
|
122
|
+
rescue OpenURI::HTTPError => e
|
123
|
+
# Rails.logger.error { "#{__FILE__}:#{__LINE__} Error: #{e.message}" }
|
124
|
+
|
125
|
+
@major = []
|
126
|
+
@major_direct_holders = []
|
127
|
+
@institutional = []
|
128
|
+
@mutualfund = []
|
129
|
+
@insider_transactions = []
|
130
|
+
@insider_purchases = []
|
131
|
+
@insider_roster = []
|
132
|
+
end
|
133
|
+
|
134
|
+
def _parse_result(result)
|
135
|
+
data = result.parsed_response['quoteSummary']['result'].first #.dig('quoteSummary', 'result', 0)
|
136
|
+
Rails.logger.info { "#{__FILE__}:#{__LINE__} data = #{data.inspect}" }
|
137
|
+
_parse_institution_ownership(data['institutionOwnership'])
|
138
|
+
_parse_fund_ownership(data['fundOwnership'])
|
139
|
+
_parse_major_holders_breakdown(data['majorHoldersBreakdown'])
|
140
|
+
_parse_insider_transactions(data['insiderTransactions'])
|
141
|
+
_parse_insider_holders(data['insiderHolders'])
|
142
|
+
_parse_net_share_purchase_activity(data['netSharePurchaseActivity'])
|
143
|
+
rescue NoMethodError
|
144
|
+
raise "Failed to parse holders json data."
|
145
|
+
end
|
146
|
+
|
147
|
+
def _parse_raw_values(data)
|
148
|
+
data.is_a?(Hash) && data.key?('raw') ? data['raw'] : data
|
149
|
+
end
|
150
|
+
|
151
|
+
def _parse_institution_ownership(data)
|
152
|
+
holders = data['ownershipList'].map { |owner| owner.transform_values { |v| _parse_raw_values(v) }.except('maxAge') }
|
153
|
+
|
154
|
+
@institutional = holders.map do |holder|
|
155
|
+
{
|
156
|
+
'Date Reported' => DateTime.strptime(holder['reportDate'].to_s, '%s'),
|
157
|
+
'Holder' => holder['organization'],
|
158
|
+
'Shares' => holder['position'],
|
159
|
+
'Value' => holder['value']
|
160
|
+
}
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def _parse_fund_ownership(data)
|
165
|
+
holders = data['ownershipList'].map { |owner| owner.transform_values { |v| _parse_raw_values(v) }.except('maxAge') }
|
166
|
+
|
167
|
+
@mutualfund = holders.map do |holder|
|
168
|
+
{
|
169
|
+
'Date Reported' => DateTime.strptime(holder['reportDate'].to_s, '%s'),
|
170
|
+
'Holder' => holder['organization'],
|
171
|
+
'Shares' => holder['position'],
|
172
|
+
'Value' => holder['value']
|
173
|
+
}
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def _parse_major_holders_breakdown(data)
|
178
|
+
data.except!('maxAge') if data.key?('maxAge')
|
179
|
+
@major = data.map { |k, v| [k, _parse_raw_values(v)] }.to_h
|
180
|
+
end
|
181
|
+
|
182
|
+
def _parse_insider_transactions(data)
|
183
|
+
holders = data['transactions'].map { |owner| owner.transform_values { |v| _parse_raw_values(v) }.except('maxAge') }
|
184
|
+
|
185
|
+
@insider_transactions = holders.map do |holder|
|
186
|
+
{
|
187
|
+
'Start Date' => DateTime.strptime(holder['startDate'].to_s, '%s'),
|
188
|
+
'Insider' => holder['filerName'],
|
189
|
+
'Position' => holder['filerRelation'],
|
190
|
+
'URL' => holder['filerUrl'],
|
191
|
+
'Transaction' => holder['moneyText'],
|
192
|
+
'Text' => holder['transactionText'],
|
193
|
+
'Shares' => holder['shares'],
|
194
|
+
'Value' => holder['value'],
|
195
|
+
'Ownership' => holder['ownership']
|
196
|
+
}
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def _parse_insider_holders(data)
|
201
|
+
holders = data['holders'].map { |owner| owner.transform_values { |v| _parse_raw_values(v) }.except('maxAge') }
|
202
|
+
|
203
|
+
@insider_roster = holders.map do |holder|
|
204
|
+
{
|
205
|
+
'Name' => holder['name'].to_s,
|
206
|
+
'Position' => holder['relation'].to_s,
|
207
|
+
'URL' => holder['url'].to_s,
|
208
|
+
'Most Recent Transaction' => holder['transactionDescription'].to_s,
|
209
|
+
'Latest Transaction Date' => holder['latestTransDate'] ? DateTime.strptime(holder['latestTransDate'].to_s, '%s') : nil,
|
210
|
+
'Position Direct Date' => DateTime.strptime(holder['positionDirectDate'].to_s, '%s'),
|
211
|
+
'Shares Owned Directly' => holder['positionDirect'],
|
212
|
+
'Position Indirect Date' => holder['positionIndirectDate'] ? DateTime.strptime(holder['positionIndirectDate'].to_s, '%s') : nil,
|
213
|
+
'Shares Owned Indirectly' => holder['positionIndirect']
|
214
|
+
}
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def _parse_net_share_purchase_activity(data)
|
219
|
+
period = data['period'] || ''
|
220
|
+
@insider_purchases = {
|
221
|
+
"Insider Purchases Last #{period}" => [
|
222
|
+
'Purchases',
|
223
|
+
'Sales',
|
224
|
+
'Net Shares Purchased (Sold)',
|
225
|
+
'Total Insider Shares Held',
|
226
|
+
'% Net Shares Purchased (Sold)',
|
227
|
+
'% Buy Shares',
|
228
|
+
'% Sell Shares'
|
229
|
+
],
|
230
|
+
'Shares' => [
|
231
|
+
data['buyInfoShares'],
|
232
|
+
data['sellInfoShares'],
|
233
|
+
data['netInfoShares'],
|
234
|
+
data['totalInsiderShares'],
|
235
|
+
data['netPercentInsiderShares'],
|
236
|
+
data['buyPercentInsiderShares'],
|
237
|
+
data['sellPercentInsiderShares']
|
238
|
+
],
|
239
|
+
'Trans' => [
|
240
|
+
data['buyInfoCount'],
|
241
|
+
data['sellInfoCount'],
|
242
|
+
data['netInfoCount'],
|
243
|
+
nil,
|
244
|
+
nil,
|
245
|
+
nil,
|
246
|
+
nil
|
247
|
+
]
|
248
|
+
}
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
|
253
|
+
end
|