Class: Edgar::Financials

Inherits:
Object
  • Object
show all
Defined in:
lib/edgar/financials.rb

Overview

Wraps EntityFacts/FilingFacts with statement rendering and convenience accessors matching Python edgartools' Financials API.

Constant Summary collapse

CURRENCY_SYMBOLS =

Map ISO 4217 currency codes to symbols (matches Python edgartools)

{
  "USD" => "$",
  "EUR" => "\u20AC",
  "GBP" => "\u00A3",
  "JPY" => "\u00A5",
  "CAD" => "C$",
  "CHF" => "Fr",
  "AUD" => "A$",
  "CNY" => "\u00A5",
  "HKD" => "HK$",
  "NZD" => "NZ$",
  "SEK" => "kr",
  "NOK" => "kr",
  "DKK" => "kr",
  "INR" => "\u20B9",
  "BRL" => "R$",
  "ZAR" => "R",
  "MXN" => "Mex$",
  "SGD" => "S$",
  "TWD" => "NT$",
  "KRW" => "\u20A9",
  "RUB" => "\u20BD",
  "PLN" => "z\u0142",
  "TRY" => "\u20BA",
  "ILS" => "\u20AA"
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(cik: nil, facts: nil) ⇒ Financials

Returns a new instance of Financials.



37
38
39
# File 'lib/edgar/financials.rb', line 37

def initialize(cik: nil, facts: nil)
  @facts = facts || EntityFacts.new(cik: cik)
end

Instance Attribute Details

#factsObject (readonly)

Returns the value of attribute facts.



35
36
37
# File 'lib/edgar/financials.rb', line 35

def facts
  @facts
end

Class Method Details

.extract(filing) ⇒ Object



319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/edgar/financials.rb', line 319

def extract(filing)
  xbrl = filing.xbrl
  facts = if xbrl
            FilingFacts.new(xbrl, form_type: filing.form)
          else
            EntityFacts.new(cik: filing.cik)
          end
  new(facts: facts)
rescue StandardError => e
  warn "Failed to extract financials from filing: #{e.message}"
  nil
end

Instance Method Details

#available?Boolean

Returns:

  • (Boolean)


41
42
43
# File 'lib/edgar/financials.rb', line 41

def available?
  @facts.available?
end

#balance_sheet(periods: 4) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/edgar/financials.rb', line 63

def balance_sheet(periods: 4)
  concepts = %w[Assets CurrentAssets
                CashAndCashEquivalentsAtCarryingValue
                AccountsReceivableNetCurrent
                InventoryNet
                PropertyPlantAndEquipmentNet
                AssetsCurrent LiabilitiesCurrent
                LongTermDebtCurrent LongTermDebtNoncurrent
                Liabilities StockholdersEquity
                StockholdersEquityIncludingPortionAttributableToNoncontrollingInterest
                RetainedEarningsAccumulatedDeficit
                CommonStocksIncludingAdditionalPaidInCapital
                TreasuryStockValue]
  extract_concepts(concepts, periods)
end

#cashflow_statement(periods: 4) ⇒ Object



79
80
81
82
83
84
85
86
# File 'lib/edgar/financials.rb', line 79

def cashflow_statement(periods: 4)
  concepts = %w[NetCashProvidedByUsedInOperatingActivities
                NetCashProvidedByUsedInInvestingActivities
                NetCashProvidedByUsedInFinancingActivities
                CashAndCashEquivalentsPeriodIncreaseDecrease
                CashAndCashEquivalentsAtCarryingValue]
  extract_concepts(concepts, periods)
end

#comprehensive_income(periods: 4) ⇒ Object



100
101
102
103
104
105
106
# File 'lib/edgar/financials.rb', line 100

def comprehensive_income(periods: 4)
  concepts = %w[ComprehensiveIncomeNetOfTax
                OtherComprehensiveIncomeLossNetOfTax
                ComprehensiveIncomeNetOfTaxAttributableToParent
                ComprehensiveIncomeNetOfTaxAttributableToNoncontrollingInterest]
  extract_concepts(concepts, periods)
end

#currency_symbolObject



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/edgar/financials.rb', line 234

def currency_symbol
  # Try to find the currency from us-gaap units.
  # EntityFacts returns Hash-format concepts with a "units" sub-hash.
  # FilingFacts returns Array-format concepts with a "unit" string key.
  @facts.us_gaap.each_value do |concept|
    if concept.is_a?(Array)
      # FilingFacts format: each element is a fact hash with "unit" key
      concept.each do |v|
        result = currency_from_unit(v["unit"])
        return result if result
      end
    else
      # EntityFacts format: concept hash with "units" sub-hash
      units = concept["units"]
      next unless units

      units.each_key do |unit|
        result = currency_from_unit(unit)
        return result if result
      end
    end
  end

  # Fallback: try facts_map when us_gaap is empty (e.g. custom facts objects)
  if @facts.respond_to?(:facts_map)
    @facts.facts_map.each_value do |values|
      next unless values.any?

      values.each do |v|
        result = currency_from_unit(v["unit"])
        return result if result
      end
    end
  end

  # Default fallback (Python edgartools defaults to "$")
  "$"
end

#financial_metricsObject



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/edgar/financials.rb', line 273

def financial_metrics
  revenue = get_revenue
  net_income = get_net_income
  operating_income = get_operating_income
  total_assets = get_total_assets
  total_liabilities = get_total_liabilities
  equity = get_stockholders_equity
  ocf = get_operating_cash_flow
  fcf = get_free_cash_flow
  gross_profit = get_gross_profit
  eps = get_earnings_per_share
  current_assets = get_current_assets
  current_liabilities = get_current_liabilities
  capex = get_capital_expenditures
  shares_basic = get_shares_outstanding_basic
  shares_diluted = get_shares_outstanding_diluted

  metrics = {
    revenue: revenue,
    operating_income: operating_income,
    net_income: net_income,
    total_assets: total_assets,
    total_liabilities: total_liabilities,
    stockholders_equity: equity,
    current_assets: current_assets,
    current_liabilities: current_liabilities,
    operating_cash_flow: ocf,
    capital_expenditures: capex,
    free_cash_flow: fcf,
    shares_outstanding_basic: shares_basic,
    shares_outstanding_diluted: shares_diluted,
    gross_profit: gross_profit,
    eps: eps
  }

  # Calculate ratios (matching Python edgartools)
  if current_assets && current_liabilities&.positive?
    metrics[:current_ratio] = current_assets / current_liabilities.to_f
  end

  metrics[:debt_to_assets] = total_liabilities / total_assets.to_f if total_liabilities && total_assets&.positive?

  metrics
end

#get_capital_expenditures(limit: nil, annual: true, period: nil) ⇒ Object



196
197
198
199
200
201
202
203
# File 'lib/edgar/financials.rb', line 196

def get_capital_expenditures(limit: nil, annual: true, period: nil)
  if limit
    v = @facts.concept_values("PaymentsToAcquirePropertyPlantAndEquipment", limit: limit, annual: annual)
    v.empty? ? nil : v
  else
    @facts._select_value("PaymentsToAcquirePropertyPlantAndEquipment", annual: annual, period: period)
  end
end

#get_current_assets(limit: nil, annual: true, period: nil) ⇒ Object



178
179
180
181
182
183
184
185
# File 'lib/edgar/financials.rb', line 178

def get_current_assets(limit: nil, annual: true, period: nil)
  if limit
    v = @facts.concept_values("AssetsCurrent", limit: limit, annual: annual)
    v.empty? ? nil : v
  else
    @facts._select_value("AssetsCurrent", annual: annual, period: period)
  end
end

#get_current_liabilities(limit: nil, annual: true, period: nil) ⇒ Object



187
188
189
190
191
192
193
194
# File 'lib/edgar/financials.rb', line 187

def get_current_liabilities(limit: nil, annual: true, period: nil)
  if limit
    v = @facts.concept_values("LiabilitiesCurrent", limit: limit, annual: annual)
    v.empty? ? nil : v
  else
    @facts._select_value("LiabilitiesCurrent", annual: annual, period: period)
  end
end

#get_earnings_per_share(limit: nil, annual: true, period: nil) ⇒ Object



167
168
169
170
171
172
173
174
175
176
# File 'lib/edgar/financials.rb', line 167

def get_earnings_per_share(limit: nil, annual: true, period: nil)
  if limit
    v = @facts.concept_values("EarningsPerShareDiluted", limit: limit, annual: annual)
    v = @facts.concept_values("EarningsPerShareBasic", limit: limit, annual: annual) if v.empty?
    v.empty? ? nil : v
  else
    val = @facts._select_value("EarningsPerShareDiluted", annual: annual, period: period)
    val || @facts._select_value("EarningsPerShareBasic", annual: annual, period: period)
  end
end

#get_free_cash_flow(limit: nil, annual: true) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/edgar/financials.rb', line 152

def get_free_cash_flow(limit: nil, annual: true)
  op = @facts._select_value("NetCashProvidedByUsedInOperatingActivities", annual: annual)
  capex = @facts._select_value("PaymentsToAcquirePropertyPlantAndEquipment", annual: annual)
  return nil unless op && capex

  if limit
    end_date = @facts.concept_values(
      "NetCashProvidedByUsedInOperatingActivities", limit: 1, annual: annual
    )&.first&.dig("end")
    [{ "end" => end_date, "val" => op - capex.abs }]
  else
    op - capex.abs
  end
end

#get_gross_profit(limit: nil, annual: true, period: nil) ⇒ Object



134
135
136
# File 'lib/edgar/financials.rb', line 134

def get_gross_profit(limit: nil, annual: true, period: nil)
  @facts.get_gross_profit(limit: limit, annual: annual, period: period)
end

#get_net_income(limit: nil, annual: true, period: nil) ⇒ Object



113
114
115
# File 'lib/edgar/financials.rb', line 113

def get_net_income(limit: nil, annual: true, period: nil)
  @facts.get_net_income(limit: limit, annual: annual, period: period)
end

#get_operating_cash_flow(limit: nil, annual: true, period: nil) ⇒ Object

Financials-specific convenience accessors matching Python edgartools Financials API. These use @facts._select_value and @facts.concept_values since Python Financials implements them via XBRL statement rendering (not available in ruby-edgar yet).



143
144
145
146
147
148
149
150
# File 'lib/edgar/financials.rb', line 143

def get_operating_cash_flow(limit: nil, annual: true, period: nil)
  if limit
    v = @facts.concept_values("NetCashProvidedByUsedInOperatingActivities", limit: limit, annual: annual)
    v.empty? ? nil : v
  else
    @facts._select_value("NetCashProvidedByUsedInOperatingActivities", annual: annual, period: period)
  end
end

#get_operating_income(limit: nil, annual: true, period: nil) ⇒ Object



117
118
119
# File 'lib/edgar/financials.rb', line 117

def get_operating_income(limit: nil, annual: true, period: nil)
  @facts.get_operating_income(limit: limit, annual: annual, period: period)
end

#get_revenue(limit: nil, annual: true, period: nil) ⇒ Object

Delegates to EntityFacts/FilingFacts for the 7 core EntityFacts accessors



109
110
111
# File 'lib/edgar/financials.rb', line 109

def get_revenue(limit: nil, annual: true, period: nil)
  @facts.get_revenue(limit: limit, annual: annual, period: period)
end

#get_shareholders_equity(limit: nil, annual: true, period: nil) ⇒ Object Also known as: get_stockholders_equity



129
130
131
# File 'lib/edgar/financials.rb', line 129

def get_shareholders_equity(limit: nil, annual: true, period: nil)
  @facts.get_shareholders_equity(limit: limit, annual: annual, period: period)
end

#get_shares_outstanding_basic(limit: nil, annual: true, period: nil) ⇒ Object



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/edgar/financials.rb', line 205

def get_shares_outstanding_basic(limit: nil, annual: true, period: nil)
  concepts = %w[WeightedAverageNumberOfSharesOutstandingBasic
                EntityCommonStockSharesOutstanding
                CommonStockSharesOutstanding]
  if limit
    concepts.each do |name|
      v = @facts.concept_values(name, limit: limit, annual: annual)
      return v unless v.empty?
    end
  else
    concepts.each do |name|
      val = @facts._select_value(name, annual: annual, period: period)
      return val if val
    end
  end
  nil
end

#get_shares_outstanding_diluted(limit: nil, annual: true, period: nil) ⇒ Object



223
224
225
226
227
228
229
230
231
232
# File 'lib/edgar/financials.rb', line 223

def get_shares_outstanding_diluted(limit: nil, annual: true, period: nil)
  if limit
    v = @facts.concept_values("WeightedAverageNumberOfDilutedSharesOutstanding", limit: limit, annual: annual)
    v = @facts.concept_values("DilutedAverageShares", limit: limit, annual: annual) if v.empty?
    v.empty? ? nil : v
  else
    val = @facts._select_value("WeightedAverageNumberOfDilutedSharesOutstanding", annual: annual, period: period)
    val || @facts._select_value("DilutedAverageShares", annual: annual, period: period)
  end
end

#get_total_assets(limit: nil, annual: true, period: nil) ⇒ Object



121
122
123
# File 'lib/edgar/financials.rb', line 121

def get_total_assets(limit: nil, annual: true, period: nil)
  @facts.get_total_assets(limit: limit, annual: annual, period: period)
end

#get_total_liabilities(limit: nil, annual: true, period: nil) ⇒ Object



125
126
127
# File 'lib/edgar/financials.rb', line 125

def get_total_liabilities(limit: nil, annual: true, period: nil)
  @facts.get_total_liabilities(limit: limit, annual: annual, period: period)
end

#income_statement(periods: 4) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/edgar/financials.rb', line 45

def income_statement(periods: 4)
  concepts = %w[Revenues Revenue
                RevenueFromContractWithCustomerExcludingAssessedTax
                RevenueFromContractWithCustomer
                CostOfGoodsAndServicesSold
                GrossProfit OperatingExpenses
                OperatingIncomeLoss
                NonoperatingIncomeExpensePlusInterestAndDividendIncome
                IncomeLossFromContinuingOperationsBeforeIncomeTaxExpenseBenefit
                IncomeTaxExpenseBenefit
                NetIncomeLoss
                EarningsPerShareBasic
                EarningsPerShareDiluted
                WeightedAverageNumberOfSharesOutstandingBasic
                WeightedAverageNumberOfDilutedSharesOutstanding]
  extract_concepts(concepts, periods)
end

#statement_of_equity(periods: 4) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
# File 'lib/edgar/financials.rb', line 88

def statement_of_equity(periods: 4)
  concepts = %w[StockholdersEquity
                StockholdersEquityIncludingPortionAttributableToNoncontrollingInterest
                RetainedEarningsAccumulatedDeficit
                CommonStocksIncludingAdditionalPaidInCapital
                TreasuryStockValue
                AccumulatedOtherComprehensiveIncomeLossNetOfTax
                ComprehensiveIncomeNetOfTax
                Dividends]
  extract_concepts(concepts, periods)
end