finmodeling 0.1 → 0.2
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.
- data/.gitignore +0 -0
- data/Gemfile +2 -0
- data/README.md +289 -269
- data/Rakefile +12 -0
- data/TODO.txt +113 -20
- data/examples/{dump_report.rb → dump_latest_10k.rb} +1 -1
- data/examples/list_disclosures.rb +50 -0
- data/examples/lists/nasdaq-mid-to-mega-tech-symbols.txt +0 -0
- data/examples/show_report.rb +112 -32
- data/examples/show_reports.rb +162 -33
- data/finmodeling.gemspec +4 -1
- data/lib/finmodeling/annual_report_filing.rb +97 -18
- data/lib/finmodeling/array_with_stats.rb +0 -0
- data/lib/finmodeling/assets_calculation.rb +12 -3
- data/lib/finmodeling/assets_item.rb +0 -0
- data/lib/finmodeling/assets_item_vectors.rb +0 -0
- data/lib/finmodeling/balance_sheet_analyses.rb +19 -4
- data/lib/finmodeling/balance_sheet_calculation.rb +52 -37
- data/lib/finmodeling/calculation_summary.rb +119 -14
- data/lib/finmodeling/can_cache_classifications.rb +0 -0
- data/lib/finmodeling/can_cache_summaries.rb +0 -0
- data/lib/finmodeling/can_choose_successive_periods.rb +15 -0
- data/lib/finmodeling/can_classify_rows.rb +0 -0
- data/lib/finmodeling/capm.rb +80 -0
- data/lib/finmodeling/cash_change_calculation.rb +3 -3
- data/lib/finmodeling/cash_change_item.rb +0 -0
- data/lib/finmodeling/cash_change_item_vectors.rb +0 -0
- data/lib/finmodeling/cash_change_summary_from_differences.rb +36 -0
- data/lib/finmodeling/cash_flow_statement_analyses.rb +36 -0
- data/lib/finmodeling/cash_flow_statement_calculation.rb +28 -52
- data/lib/finmodeling/classifiers.rb +2 -0
- data/lib/finmodeling/company.rb +0 -0
- data/lib/finmodeling/company_filing.rb +30 -7
- data/lib/finmodeling/company_filing_calculation.rb +16 -6
- data/lib/finmodeling/company_filings.rb +112 -46
- data/lib/finmodeling/comprehensive_income_calculation.rb +60 -0
- data/lib/finmodeling/comprehensive_income_statement_calculation.rb +74 -0
- data/lib/finmodeling/comprehensive_income_statement_item.rb +20 -0
- data/lib/finmodeling/comprehensive_income_statement_item_vectors.rb +235 -0
- data/lib/finmodeling/config.rb +0 -0
- data/lib/finmodeling/debt_cost_of_capital.rb +14 -0
- data/lib/finmodeling/equity_change_calculation.rb +43 -0
- data/lib/finmodeling/equity_change_item.rb +25 -0
- data/lib/finmodeling/equity_change_item_vectors.rb +156 -0
- data/lib/finmodeling/factory.rb +0 -0
- data/lib/finmodeling/fama_french_cost_of_equity.rb +119 -0
- data/lib/finmodeling/float_helpers.rb +14 -8
- data/lib/finmodeling/forecasted_reformulated_balance_sheet.rb +55 -0
- data/lib/finmodeling/forecasted_reformulated_income_statement.rb +110 -0
- data/lib/finmodeling/forecasts.rb +4 -4
- data/lib/finmodeling/has_string_classifer.rb +0 -0
- data/lib/finmodeling/income_statement_analyses.rb +23 -17
- data/lib/finmodeling/income_statement_calculation.rb +46 -43
- data/lib/finmodeling/income_statement_item.rb +1 -1
- data/lib/finmodeling/income_statement_item_vectors.rb +24 -13
- data/lib/finmodeling/invalid_filing_error.rb +4 -0
- data/lib/finmodeling/liabs_and_equity_calculation.rb +18 -8
- data/lib/finmodeling/liabs_and_equity_item.rb +1 -1
- data/lib/finmodeling/liabs_and_equity_item_vectors.rb +24 -24
- data/lib/finmodeling/linear_trend_forecasting_policy.rb +23 -0
- data/lib/finmodeling/net_income_calculation.rb +23 -10
- data/lib/finmodeling/net_income_summary_from_differences.rb +51 -0
- data/lib/finmodeling/paths.rb +0 -0
- data/lib/finmodeling/period_array.rb +8 -4
- data/lib/finmodeling/quarterly_report_filing.rb +9 -4
- data/lib/finmodeling/rate.rb +8 -0
- data/lib/finmodeling/ratio.rb +0 -0
- data/lib/finmodeling/reformulated_balance_sheet.rb +47 -88
- data/lib/finmodeling/reformulated_cash_flow_statement.rb +18 -41
- data/lib/finmodeling/reformulated_income_statement.rb +44 -206
- data/lib/finmodeling/reformulated_shareholder_equity_statement.rb +50 -0
- data/lib/finmodeling/reoi_valuation.rb +104 -0
- data/lib/finmodeling/shareholder_equity_statement_calculation.rb +34 -0
- data/lib/finmodeling/string_helpers.rb +18 -1
- data/lib/finmodeling/time_series_estimator.rb +25 -0
- data/lib/finmodeling/trailing_avg_forecasting_policy.rb +23 -0
- data/lib/finmodeling/version.rb +1 -1
- data/lib/finmodeling/weighted_avg_cost_of_capital.rb +35 -0
- data/lib/finmodeling/yahoo_finance_helpers.rb +20 -0
- data/lib/finmodeling.rb +33 -2
- data/spec/annual_report_filing_spec.rb +81 -45
- data/spec/assets_calculation_spec.rb +7 -4
- data/spec/assets_item_spec.rb +9 -14
- data/spec/balance_sheet_analyses_spec.rb +13 -13
- data/spec/balance_sheet_calculation_spec.rb +45 -51
- data/spec/calculation_summary_spec.rb +113 -21
- data/spec/can_classify_rows_spec.rb +0 -0
- data/spec/cash_change_calculation_spec.rb +1 -10
- data/spec/cash_change_item_spec.rb +10 -18
- data/spec/cash_flow_statement_calculation_spec.rb +10 -24
- data/spec/company_beta_spec.rb +53 -0
- data/spec/company_filing_calculation_spec.rb +39 -49
- data/spec/company_filing_spec.rb +0 -0
- data/spec/company_filings_spec.rb +75 -25
- data/spec/company_spec.rb +37 -47
- data/spec/comprehensive_income_statement_calculation_spec.rb +54 -0
- data/spec/comprehensive_income_statement_item_spec.rb +56 -0
- data/spec/debt_cost_of_capital_spec.rb +19 -0
- data/spec/equity_change_calculation_spec.rb +33 -0
- data/spec/equity_change_item_spec.rb +58 -0
- data/spec/factory_spec.rb +2 -2
- data/spec/forecasts_spec.rb +2 -2
- data/spec/income_statement_analyses_spec.rb +23 -21
- data/spec/income_statement_calculation_spec.rb +17 -49
- data/spec/income_statement_item_spec.rb +17 -29
- data/spec/liabs_and_equity_calculation_spec.rb +6 -3
- data/spec/liabs_and_equity_item_spec.rb +14 -22
- data/spec/linear_trend_forecasting_policy_spec.rb +37 -0
- data/spec/matchers/custom_matchers.rb +79 -0
- data/spec/mocks/calculation.rb +0 -0
- data/spec/mocks/income_statement_analyses.rb +0 -0
- data/spec/mocks/sec_query.rb +0 -0
- data/spec/net_income_calculation_spec.rb +16 -10
- data/spec/period_array.rb +0 -0
- data/spec/quarterly_report_filing_spec.rb +21 -38
- data/spec/rate_spec.rb +0 -0
- data/spec/ratio_spec.rb +0 -0
- data/spec/reformulated_balance_sheet_spec.rb +56 -33
- data/spec/reformulated_cash_flow_statement_spec.rb +18 -10
- data/spec/reformulated_income_statement_spec.rb +16 -15
- data/spec/reformulated_shareholder_equity_statement_spec.rb +43 -0
- data/spec/reoi_valuation_spec.rb +146 -0
- data/spec/shareholder_equity_statement_calculation_spec.rb +59 -0
- data/spec/spec_helper.rb +4 -1
- data/spec/string_helpers_spec.rb +15 -13
- data/spec/time_series_estimator_spec.rb +61 -0
- data/spec/trailing_avg_forecasting_policy_spec.rb +37 -0
- data/spec/weighted_avg_cost_of_capital_spec.rb +32 -0
- data/tools/create_equity_change_training_vectors.rb +49 -0
- data/tools/time_specs.sh +7 -0
- metadata +182 -36
- data/lib/finmodeling/constant_forecasting_policy.rb +0 -23
- data/lib/finmodeling/generic_forecasting_policy.rb +0 -19
- data/spec/constant_forecasting_policy_spec.rb +0 -37
- data/spec/generic_forecasting_policy_spec.rb +0 -33
|
@@ -2,41 +2,6 @@ module FinModeling
|
|
|
2
2
|
class ReformulatedCashFlowStatement
|
|
3
3
|
attr_accessor :period
|
|
4
4
|
|
|
5
|
-
class FakeCashChangeSummary
|
|
6
|
-
def initialize(re_cfs1, re_cfs2)
|
|
7
|
-
@re_cfs1 = re_cfs1
|
|
8
|
-
@re_cfs2 = re_cfs2
|
|
9
|
-
end
|
|
10
|
-
def filter_by_type(key)
|
|
11
|
-
case key
|
|
12
|
-
when :c
|
|
13
|
-
@cs = FinModeling::CalculationSummary.new
|
|
14
|
-
@cs.title = "Cash from Operations"
|
|
15
|
-
@cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @re_cfs1.cash_from_operations.total] ),
|
|
16
|
-
CalculationRow.new(:key => "Second Row", :vals => [-@re_cfs2.cash_from_operations.total] ) ]
|
|
17
|
-
return @cs
|
|
18
|
-
when :i
|
|
19
|
-
@cs = FinModeling::CalculationSummary.new
|
|
20
|
-
@cs.title = "Cash Investments in Operations"
|
|
21
|
-
@cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @re_cfs1.cash_investments_in_operations.total] ),
|
|
22
|
-
CalculationRow.new(:key => "Second Row", :vals => [-@re_cfs2.cash_investments_in_operations.total] ) ]
|
|
23
|
-
return @cs
|
|
24
|
-
when :d
|
|
25
|
-
@cs = FinModeling::CalculationSummary.new
|
|
26
|
-
@cs.title = "Payments to Debtholders"
|
|
27
|
-
@cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @re_cfs1.payments_to_debtholders.total] ),
|
|
28
|
-
CalculationRow.new(:key => "Second Row", :vals => [-@re_cfs2.payments_to_debtholders.total] ) ]
|
|
29
|
-
return @cs
|
|
30
|
-
when :f
|
|
31
|
-
@cs = FinModeling::CalculationSummary.new
|
|
32
|
-
@cs.title = "Payments to Stockholders"
|
|
33
|
-
@cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @re_cfs1.payments_to_stockholders.total] ),
|
|
34
|
-
CalculationRow.new(:key => "Second Row", :vals => [-@re_cfs2.payments_to_stockholders.total] ) ]
|
|
35
|
-
return @cs
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
5
|
def initialize(period, cash_change_summary)
|
|
41
6
|
@period = period
|
|
42
7
|
|
|
@@ -50,7 +15,7 @@ module FinModeling
|
|
|
50
15
|
@d.title = "Payments to debtholders"
|
|
51
16
|
@f.title = "Payments to stockholders"
|
|
52
17
|
|
|
53
|
-
if cash_change_summary.
|
|
18
|
+
if !(cash_change_summary.is_a? CashChangeSummaryFromDifferences)
|
|
54
19
|
@d.rows << CalculationRow.new(:key => "Investment in Cash and Equivalents",
|
|
55
20
|
:type => :d,
|
|
56
21
|
:vals => [-cash_change_summary.total])
|
|
@@ -58,7 +23,7 @@ module FinModeling
|
|
|
58
23
|
end
|
|
59
24
|
|
|
60
25
|
def -(re_cfs2)
|
|
61
|
-
summary =
|
|
26
|
+
summary = CashChangeSummaryFromDifferences.new(self, re_cfs2)
|
|
62
27
|
return ReformulatedCashFlowStatement.new(@period, summary)
|
|
63
28
|
end
|
|
64
29
|
|
|
@@ -105,10 +70,10 @@ module FinModeling
|
|
|
105
70
|
analysis.header_row = CalculationHeader.new(:key => "", :vals => ["Unknown..."])
|
|
106
71
|
|
|
107
72
|
analysis.rows = []
|
|
108
|
-
analysis.rows << CalculationRow.new(:key => "C
|
|
109
|
-
analysis.rows << CalculationRow.new(:key => "I
|
|
110
|
-
analysis.rows << CalculationRow.new(:key => "d
|
|
111
|
-
analysis.rows << CalculationRow.new(:key => "F
|
|
73
|
+
analysis.rows << CalculationRow.new(:key => "C ($MM)", :vals => [nil])
|
|
74
|
+
analysis.rows << CalculationRow.new(:key => "I ($MM)", :vals => [nil])
|
|
75
|
+
analysis.rows << CalculationRow.new(:key => "d ($MM)", :vals => [nil])
|
|
76
|
+
analysis.rows << CalculationRow.new(:key => "F ($MM)", :vals => [nil])
|
|
112
77
|
analysis.rows << CalculationRow.new(:key => "FCF ($MM)", :vals => [nil])
|
|
113
78
|
analysis.rows << CalculationRow.new(:key => "NI / C", :vals => [nil])
|
|
114
79
|
|
|
@@ -136,5 +101,17 @@ module FinModeling
|
|
|
136
101
|
return analysis
|
|
137
102
|
end
|
|
138
103
|
|
|
104
|
+
ALLOWED_IMBALANCE = 1.0
|
|
105
|
+
def flows_are_balanced?
|
|
106
|
+
(free_cash_flow.total - financing_flows.total) < ALLOWED_IMBALANCE
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def flows_are_plausible?
|
|
110
|
+
return [ payments_to_debtholders,
|
|
111
|
+
payments_to_stockholders,
|
|
112
|
+
cash_from_operations,
|
|
113
|
+
cash_investments_in_operations ].all?{ |x| x.total.abs > 1.0 }
|
|
114
|
+
end
|
|
115
|
+
|
|
139
116
|
end
|
|
140
117
|
end
|
|
@@ -1,86 +1,45 @@
|
|
|
1
1
|
module FinModeling
|
|
2
2
|
class ReformulatedIncomeStatement
|
|
3
3
|
attr_accessor :period
|
|
4
|
+
attr_accessor :operating_revenues, :cost_of_revenues, :operating_expenses
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
def initialize(ris1, ris2)
|
|
7
|
-
@ris1 = ris1
|
|
8
|
-
@ris2 = ris2
|
|
9
|
-
end
|
|
10
|
-
def filter_by_type(key)
|
|
11
|
-
case key
|
|
12
|
-
when :or
|
|
13
|
-
@cs = FinModeling::CalculationSummary.new
|
|
14
|
-
@cs.title = "Operating Revenues"
|
|
15
|
-
@cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.operating_revenues.total] ),
|
|
16
|
-
CalculationRow.new(:key => "Second Row", :vals => [-@ris2.operating_revenues.total] ) ]
|
|
17
|
-
return @cs
|
|
18
|
-
when :cogs
|
|
19
|
-
@cs = FinModeling::CalculationSummary.new
|
|
20
|
-
@cs.title = "Cost of Revenues"
|
|
21
|
-
@cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.cost_of_revenues.total] ),
|
|
22
|
-
CalculationRow.new(:key => "Second Row", :vals => [-@ris2.cost_of_revenues.total] ) ]
|
|
23
|
-
return @cs
|
|
24
|
-
when :oe
|
|
25
|
-
@cs = FinModeling::CalculationSummary.new
|
|
26
|
-
@cs.title = "Operating Expenses"
|
|
27
|
-
@cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.operating_expenses.total] ),
|
|
28
|
-
CalculationRow.new(:key => "Second Row", :vals => [-@ris2.operating_expenses.total] ) ]
|
|
29
|
-
return @cs
|
|
30
|
-
when :oibt
|
|
31
|
-
@cs = FinModeling::CalculationSummary.new
|
|
32
|
-
@cs.title = "Operating Income from Sales, Before taxes"
|
|
33
|
-
@cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.operating_income_after_tax.rows[1].vals.first] ),
|
|
34
|
-
CalculationRow.new(:key => "Second Row", :vals => [-@ris2.operating_income_after_tax.rows[1].vals.first] ) ]
|
|
35
|
-
return @cs
|
|
36
|
-
when :fibt
|
|
37
|
-
@cs = FinModeling::CalculationSummary.new
|
|
38
|
-
@cs.title = "Financing Income, Before Taxes"
|
|
39
|
-
@cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.net_financing_income.rows[0].vals.first] ),
|
|
40
|
-
CalculationRow.new(:key => "Second Row", :vals => [-@ris2.net_financing_income.rows[0].vals.first] ) ]
|
|
41
|
-
return @cs
|
|
42
|
-
when :tax
|
|
43
|
-
@cs = FinModeling::CalculationSummary.new
|
|
44
|
-
@cs.title = "Taxes"
|
|
45
|
-
@cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.income_from_sales_after_tax.rows[1].vals.first] ),
|
|
46
|
-
CalculationRow.new(:key => "Second Row", :vals => [-@ris2.income_from_sales_after_tax.rows[1].vals.first] ) ]
|
|
47
|
-
return @cs
|
|
48
|
-
when :ooiat
|
|
49
|
-
@cs = FinModeling::CalculationSummary.new
|
|
50
|
-
@cs.title = "Other Operating Income, After Taxes"
|
|
51
|
-
@cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.operating_income_after_tax.rows[3].vals.first] ),
|
|
52
|
-
CalculationRow.new(:key => "Second Row", :vals => [-@ris2.operating_income_after_tax.rows[3].vals.first] ) ]
|
|
53
|
-
return @cs
|
|
54
|
-
when :fiat
|
|
55
|
-
@cs = FinModeling::CalculationSummary.new
|
|
56
|
-
@cs.title = "Financing Income, After Taxes"
|
|
57
|
-
@cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.net_financing_income.rows[2].vals.first] ),
|
|
58
|
-
CalculationRow.new(:key => "Second Row", :vals => [-@ris2.net_financing_income.rows[2].vals.first] ) ]
|
|
59
|
-
return @cs
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def initialize(period, net_income_summary, tax_rate=0.35)
|
|
6
|
+
def initialize(period, net_income_summary, comprehensive_income_summary, tax_rate=0.35) # FIXME: clarify naming. This is effective tax rate? marginal? statutory?
|
|
65
7
|
@period = period
|
|
66
8
|
@tax_rate = tax_rate
|
|
67
|
-
|
|
68
|
-
@
|
|
69
|
-
@
|
|
70
|
-
@
|
|
71
|
-
@oibt
|
|
72
|
-
@fibt
|
|
73
|
-
@tax
|
|
74
|
-
@ooiat
|
|
75
|
-
@
|
|
9
|
+
|
|
10
|
+
@operating_revenues = net_income_summary.filter_by_type(:or )
|
|
11
|
+
@cost_of_revenues = net_income_summary.filter_by_type(:cogs)
|
|
12
|
+
@operating_expenses = net_income_summary.filter_by_type(:oe )
|
|
13
|
+
@oibt = net_income_summary.filter_by_type(:oibt )
|
|
14
|
+
@fibt = net_income_summary.filter_by_type(:fibt )
|
|
15
|
+
@tax = net_income_summary.filter_by_type(:tax )
|
|
16
|
+
@ooiat = net_income_summary.filter_by_type(:ooiat )
|
|
17
|
+
@ooiat_nci = net_income_summary.filter_by_type(:ooiat_nci)
|
|
18
|
+
@fiat = net_income_summary.filter_by_type(:fiat )
|
|
19
|
+
|
|
20
|
+
# If there is a CI statement:
|
|
21
|
+
# Strip O-NCI-OCI out of the NI calculation (***)
|
|
22
|
+
# Double-check that the NI portion of the CI calculation matches (NI minus O-NCI-OCI)
|
|
23
|
+
# Add OOCI + 1/2 of the UNKOCI in as an OOIAT
|
|
24
|
+
# Add FOCI + 1/2 of the UNKOCI in as a FIAT
|
|
25
|
+
if comprehensive_income_summary
|
|
26
|
+
@ooiat.rows += comprehensive_income_summary.filter_by_type(:ooci).rows
|
|
27
|
+
@ooiat.rows += comprehensive_income_summary.filter_by_type(:ooci_nci).rows
|
|
28
|
+
@fiat.rows += comprehensive_income_summary.filter_by_type(:foci).rows
|
|
29
|
+
comprehensive_income_summary.filter_by_type(:unkoci).rows.each do |row|
|
|
30
|
+
row.vals = row.vals.map{ |val| val / 2.0 }
|
|
31
|
+
@ooiat.rows << row
|
|
32
|
+
@fiat.rows << row
|
|
33
|
+
end
|
|
34
|
+
end
|
|
76
35
|
|
|
77
36
|
@fibt_tax_effect = (@fibt.total * @tax_rate).round.to_f
|
|
78
37
|
@nfi = @fibt.total + -@fibt_tax_effect + @fiat.total
|
|
79
38
|
|
|
80
39
|
@oibt_tax_effect = (@oibt.total * @tax_rate).round.to_f
|
|
81
40
|
|
|
82
|
-
@gm = @
|
|
83
|
-
@oisbt = @gm + @
|
|
41
|
+
@gm = @operating_revenues.total + @cost_of_revenues.total
|
|
42
|
+
@oisbt = @gm + @operating_expenses.total
|
|
84
43
|
|
|
85
44
|
@oisat = @oisbt + @tax.total + @fibt_tax_effect + @oibt_tax_effect
|
|
86
45
|
|
|
@@ -90,35 +49,23 @@ module FinModeling
|
|
|
90
49
|
end
|
|
91
50
|
|
|
92
51
|
def -(ris2)
|
|
93
|
-
net_income_summary =
|
|
94
|
-
return ReformulatedIncomeStatement.new(@period, net_income_summary, @tax_rate)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def operating_revenues
|
|
98
|
-
@orev
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def cost_of_revenues
|
|
102
|
-
@cogs
|
|
52
|
+
net_income_summary = NetIncomeSummaryFromDifferences.new(self, ris2)
|
|
53
|
+
return ReformulatedIncomeStatement.new(@period, net_income_summary, nil, @tax_rate)
|
|
103
54
|
end
|
|
104
55
|
|
|
105
56
|
def gross_revenue
|
|
106
57
|
cs = FinModeling::CalculationSummary.new
|
|
107
58
|
cs.title = "Gross Revenue"
|
|
108
|
-
cs.rows = [ CalculationRow.new(:key => "Operating Revenues (OR)", :vals => [@
|
|
109
|
-
CalculationRow.new(:key => "Cost of Goods Sold (COGS)", :vals => [@
|
|
59
|
+
cs.rows = [ CalculationRow.new(:key => "Operating Revenues (OR)", :vals => [@operating_revenues.total] ),
|
|
60
|
+
CalculationRow.new(:key => "Cost of Goods Sold (COGS)", :vals => [@cost_of_revenues.total] ) ]
|
|
110
61
|
return cs
|
|
111
62
|
end
|
|
112
63
|
|
|
113
|
-
def operating_expenses
|
|
114
|
-
@oe
|
|
115
|
-
end
|
|
116
|
-
|
|
117
64
|
def income_from_sales_before_tax
|
|
118
65
|
cs = FinModeling::CalculationSummary.new
|
|
119
66
|
cs.title = "Operating Income from sales, before tax (OISBT)"
|
|
120
67
|
cs.rows = [ CalculationRow.new(:key => "Gross Margin (GM)", :vals => [@gm] ),
|
|
121
|
-
CalculationRow.new(:key => "Operating Expense (OE)", :vals => [@
|
|
68
|
+
CalculationRow.new(:key => "Operating Expense (OE)", :vals => [@operating_expenses.total] ) ]
|
|
122
69
|
return cs
|
|
123
70
|
end
|
|
124
71
|
|
|
@@ -204,7 +151,7 @@ module FinModeling
|
|
|
204
151
|
return annualize_rate(prev, rate)
|
|
205
152
|
end
|
|
206
153
|
|
|
207
|
-
def re_oi(prev_bal_sheet, expected_rate_of_return
|
|
154
|
+
def re_oi(prev_bal_sheet, expected_rate_of_return)
|
|
208
155
|
e_ror = deannualize_rate(prev_bal_sheet, expected_rate_of_return)
|
|
209
156
|
return (operating_income_after_tax.total - (e_ror * prev_bal_sheet.net_operating_assets.total))
|
|
210
157
|
end
|
|
@@ -242,7 +189,7 @@ module FinModeling
|
|
|
242
189
|
return analysis
|
|
243
190
|
end
|
|
244
191
|
|
|
245
|
-
def analysis(re_bs, prev_re_is, prev_re_bs)
|
|
192
|
+
def analysis(re_bs, prev_re_is, prev_re_bs, expected_rate_of_return)
|
|
246
193
|
analysis = CalculationSummary.new
|
|
247
194
|
analysis.title = ""
|
|
248
195
|
analysis.rows = []
|
|
@@ -253,11 +200,11 @@ module FinModeling
|
|
|
253
200
|
analysis.header_row = CalculationHeader.new(:key => "", :vals => [re_bs.period.to_pretty_s])
|
|
254
201
|
end
|
|
255
202
|
|
|
256
|
-
analysis.rows << CalculationRow.new(:key => "Revenue ($MM)", :vals => [operating_revenues.total.to_nearest_million])
|
|
203
|
+
analysis.rows << CalculationRow.new(:key => "Revenue ($MM)", :vals => [@operating_revenues.total.to_nearest_million])
|
|
257
204
|
if Config.income_detail_enabled?
|
|
258
|
-
analysis.rows << CalculationRow.new(:key => "COGS ($MM)", :vals => [@
|
|
205
|
+
analysis.rows << CalculationRow.new(:key => "COGS ($MM)", :vals => [@cost_of_revenues.total.to_nearest_million])
|
|
259
206
|
analysis.rows << CalculationRow.new(:key => "GM ($MM)", :vals => [@gm.to_nearest_million])
|
|
260
|
-
analysis.rows << CalculationRow.new(:key => "OE ($MM)", :vals => [@
|
|
207
|
+
analysis.rows << CalculationRow.new(:key => "OE ($MM)", :vals => [@operating_expenses.total.to_nearest_million])
|
|
261
208
|
analysis.rows << CalculationRow.new(:key => "OISBT ($MM)", :vals => [income_from_sales_before_tax.total.to_nearest_million])
|
|
262
209
|
end
|
|
263
210
|
analysis.rows << CalculationRow.new(:key => "Core OI ($MM)", :vals => [income_from_sales_after_tax.total.to_nearest_million])
|
|
@@ -276,7 +223,7 @@ module FinModeling
|
|
|
276
223
|
analysis.rows << CalculationRow.new(:key => "Revenue Growth",:vals => [revenue_growth(prev_re_is)])
|
|
277
224
|
analysis.rows << CalculationRow.new(:key => "Core OI Growth",:vals => [core_oi_growth(prev_re_is)])
|
|
278
225
|
analysis.rows << CalculationRow.new(:key => "OI Growth", :vals => [oi_growth( prev_re_is)])
|
|
279
|
-
analysis.rows << CalculationRow.new(:key => "ReOI ($MM)", :vals => [re_oi(
|
|
226
|
+
analysis.rows << CalculationRow.new(:key => "ReOI ($MM)", :vals => [re_oi(prev_re_bs, expected_rate_of_return).to_nearest_million])
|
|
280
227
|
else
|
|
281
228
|
analysis.rows << CalculationRow.new(:key => "Sales / NOA", :vals => [nil])
|
|
282
229
|
analysis.rows << CalculationRow.new(:key => "FI / NFA", :vals => [nil])
|
|
@@ -290,10 +237,9 @@ module FinModeling
|
|
|
290
237
|
end
|
|
291
238
|
|
|
292
239
|
def self.forecast_next(period, policy, last_re_bs, last_re_is)
|
|
293
|
-
operating_revenues =
|
|
294
|
-
income_from_sales_after_tax = operating_revenues * policy.
|
|
295
|
-
net_financing_income = last_re_bs.net_financial_assets.total * Ratio.new(policy.
|
|
296
|
-
|
|
240
|
+
operating_revenues = policy.revenue_on(period.value["end_date"])
|
|
241
|
+
income_from_sales_after_tax = operating_revenues * policy.sales_pm_on(period.value["end_date"])
|
|
242
|
+
net_financing_income = last_re_bs.net_financial_assets.total * Ratio.new(policy.fi_over_nfa_on(period.value["end_date"])).yearly_to_quarterly
|
|
297
243
|
comprehensive_income = income_from_sales_after_tax + net_financing_income
|
|
298
244
|
|
|
299
245
|
ForecastedReformulatedIncomeStatement.new(period, operating_revenues,
|
|
@@ -325,112 +271,4 @@ module FinModeling
|
|
|
325
271
|
|
|
326
272
|
end
|
|
327
273
|
|
|
328
|
-
class ForecastedReformulatedIncomeStatement < ReformulatedIncomeStatement
|
|
329
|
-
def initialize(period, operating_revenues, income_from_sales_after_tax, net_financing_income, comprehensive_income)
|
|
330
|
-
@period = period
|
|
331
|
-
@orev = operating_revenues
|
|
332
|
-
@income_from_sales_after_tax = income_from_sales_after_tax
|
|
333
|
-
@net_financing_income = net_financing_income
|
|
334
|
-
@comprehensive_income = comprehensive_income
|
|
335
|
-
end
|
|
336
|
-
|
|
337
|
-
def -(ris2)
|
|
338
|
-
raise RuntimeError.new("not implmeneted")
|
|
339
|
-
end
|
|
340
|
-
|
|
341
|
-
def operating_revenues
|
|
342
|
-
cs = FinModeling::CalculationSummary.new
|
|
343
|
-
cs.title = "Operating Revenues"
|
|
344
|
-
cs.rows = [ CalculationRow.new(:key => "Operating Revenues (OR)", :vals => [@orev] ) ]
|
|
345
|
-
return cs
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
def cost_of_revenues
|
|
349
|
-
nil
|
|
350
|
-
end
|
|
351
|
-
|
|
352
|
-
def gross_revenue
|
|
353
|
-
nil
|
|
354
|
-
end
|
|
355
|
-
|
|
356
|
-
def operating_expenses
|
|
357
|
-
nil
|
|
358
|
-
end
|
|
359
|
-
|
|
360
|
-
def income_from_sales_before_tax
|
|
361
|
-
nil
|
|
362
|
-
end
|
|
363
|
-
|
|
364
|
-
def income_from_sales_after_tax
|
|
365
|
-
cs = FinModeling::CalculationSummary.new
|
|
366
|
-
cs.title = "Operating Income from sales, after tax (OISAT)"
|
|
367
|
-
cs.rows = [ CalculationRow.new(:key => "Operating income from sales (after tax)", :vals => [@income_from_sales_after_tax] ) ]
|
|
368
|
-
return cs
|
|
369
|
-
end
|
|
370
|
-
|
|
371
|
-
def operating_income_after_tax
|
|
372
|
-
income_from_sales_after_tax # this simplified version assumes no non-sales operating income
|
|
373
|
-
end
|
|
374
|
-
|
|
375
|
-
def net_financing_income
|
|
376
|
-
cs = FinModeling::CalculationSummary.new
|
|
377
|
-
cs.title = "Net financing income, after tax (NFI)"
|
|
378
|
-
cs.rows = [ CalculationRow.new(:key => "Net financing income", :vals => [@net_financing_income] ) ]
|
|
379
|
-
return cs
|
|
380
|
-
end
|
|
381
|
-
|
|
382
|
-
def comprehensive_income
|
|
383
|
-
cs = FinModeling::CalculationSummary.new
|
|
384
|
-
cs.title = "Comprehensive Income (CI)"
|
|
385
|
-
cs.rows = [ CalculationRow.new(:key => "Comprehensive income", :vals => [@comprehensive_income] ) ]
|
|
386
|
-
return cs
|
|
387
|
-
end
|
|
388
|
-
|
|
389
|
-
def analysis(re_bs, prev_re_is, prev_re_bs)
|
|
390
|
-
analysis = CalculationSummary.new
|
|
391
|
-
analysis.title = ""
|
|
392
|
-
analysis.rows = []
|
|
393
|
-
|
|
394
|
-
if re_bs.nil?
|
|
395
|
-
analysis.header_row = CalculationHeader.new(:key => "", :vals => ["Unknown..."])
|
|
396
|
-
else
|
|
397
|
-
analysis.header_row = CalculationHeader.new(:key => "", :vals => [re_bs.period.to_pretty_s + "E"])
|
|
398
|
-
end
|
|
399
|
-
|
|
400
|
-
analysis.rows << CalculationRow.new(:key => "Revenue ($MM)", :vals => [operating_revenues.total.to_nearest_million])
|
|
401
|
-
if Config.income_detail_enabled?
|
|
402
|
-
analysis.rows << CalculationRow.new(:key => "COGS ($MM)", :vals => [nil])
|
|
403
|
-
analysis.rows << CalculationRow.new(:key => "GM ($MM)", :vals => [nil])
|
|
404
|
-
analysis.rows << CalculationRow.new(:key => "OE ($MM)", :vals => [nil])
|
|
405
|
-
analysis.rows << CalculationRow.new(:key => "OISBT ($MM)", :vals => [nil])
|
|
406
|
-
end
|
|
407
|
-
analysis.rows << CalculationRow.new(:key => "Core OI ($MM)", :vals => [income_from_sales_after_tax.total.to_nearest_million])
|
|
408
|
-
analysis.rows << CalculationRow.new(:key => "OI ($MM)", :vals => [nil])
|
|
409
|
-
analysis.rows << CalculationRow.new(:key => "FI ($MM)", :vals => [net_financing_income.total.to_nearest_million])
|
|
410
|
-
analysis.rows << CalculationRow.new(:key => "NI ($MM)", :vals => [comprehensive_income.total.to_nearest_million])
|
|
411
|
-
analysis.rows << CalculationRow.new(:key => "Gross Margin", :vals => [nil])
|
|
412
|
-
analysis.rows << CalculationRow.new(:key => "Sales PM", :vals => [sales_profit_margin])
|
|
413
|
-
analysis.rows << CalculationRow.new(:key => "Operating PM", :vals => [nil])
|
|
414
|
-
analysis.rows << CalculationRow.new(:key => "FI / Sales", :vals => [fi_over_sales])
|
|
415
|
-
analysis.rows << CalculationRow.new(:key => "NI / Sales", :vals => [ni_over_sales])
|
|
416
|
-
|
|
417
|
-
if !prev_re_bs.nil? && !prev_re_is.nil?
|
|
418
|
-
analysis.rows << CalculationRow.new(:key => "Sales / NOA", :vals => [sales_over_noa(prev_re_bs)])
|
|
419
|
-
analysis.rows << CalculationRow.new(:key => "FI / NFA", :vals => [fi_over_nfa( prev_re_bs)])
|
|
420
|
-
analysis.rows << CalculationRow.new(:key => "Revenue Growth",:vals => [revenue_growth(prev_re_is)])
|
|
421
|
-
analysis.rows << CalculationRow.new(:key => "Core OI Growth",:vals => [core_oi_growth(prev_re_is)])
|
|
422
|
-
analysis.rows << CalculationRow.new(:key => "OI Growth", :vals => [nil])
|
|
423
|
-
analysis.rows << CalculationRow.new(:key => "ReOI ($MM)", :vals => [re_oi( prev_re_bs).to_nearest_million])
|
|
424
|
-
else
|
|
425
|
-
analysis.rows << CalculationRow.new(:key => "Sales / NOA", :vals => [nil])
|
|
426
|
-
analysis.rows << CalculationRow.new(:key => "FI / NFA", :vals => [nil])
|
|
427
|
-
analysis.rows << CalculationRow.new(:key => "Revenue Growth",:vals => [nil])
|
|
428
|
-
analysis.rows << CalculationRow.new(:key => "Core OI Growth",:vals => [nil])
|
|
429
|
-
analysis.rows << CalculationRow.new(:key => "OI Growth", :vals => [nil])
|
|
430
|
-
analysis.rows << CalculationRow.new(:key => "ReOI ($MM)", :vals => [nil])
|
|
431
|
-
end
|
|
432
|
-
|
|
433
|
-
return analysis
|
|
434
|
-
end
|
|
435
|
-
end
|
|
436
274
|
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module FinModeling
|
|
2
|
+
class ReformulatedShareholderEquityStatement
|
|
3
|
+
attr_accessor :period
|
|
4
|
+
|
|
5
|
+
def initialize(period, equity_change_summary)
|
|
6
|
+
@period = period
|
|
7
|
+
|
|
8
|
+
@share_issue = equity_change_summary.filter_by_type(:share_issue )
|
|
9
|
+
@minority_int = equity_change_summary.filter_by_type(:minority_int )
|
|
10
|
+
@share_repurch = equity_change_summary.filter_by_type(:share_repurch)
|
|
11
|
+
@common_div = equity_change_summary.filter_by_type(:common_div )
|
|
12
|
+
@net_income = equity_change_summary.filter_by_type(:net_income )
|
|
13
|
+
@oci = equity_change_summary.filter_by_type(:oci )
|
|
14
|
+
@preferred_div = equity_change_summary.filter_by_type(:preferred_div)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def transactions_with_shareholders
|
|
18
|
+
cs = FinModeling::CalculationSummary.new
|
|
19
|
+
cs.title = "Transactions with Shareholders"
|
|
20
|
+
cs.rows = [ CalculationRow.new(:key => "Share Issues", :vals => [@share_issue .total] ),
|
|
21
|
+
CalculationRow.new(:key => "Minority Interest", :vals => [@minority_int .total] ),
|
|
22
|
+
CalculationRow.new(:key => "Share Repurchases", :vals => [@share_repurch.total] ),
|
|
23
|
+
CalculationRow.new(:key => "Common Dividends", :vals => [@common_div .total] ) ]
|
|
24
|
+
return cs
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def comprehensive_income
|
|
28
|
+
cs = FinModeling::CalculationSummary.new
|
|
29
|
+
cs.title = "Comprehensive Income"
|
|
30
|
+
cs.rows = [ CalculationRow.new(:key => "Net Income", :vals => [@net_income .total] ),
|
|
31
|
+
CalculationRow.new(:key => "Other Comprehensive Income", :vals => [@oci .total] ),
|
|
32
|
+
CalculationRow.new(:key => "Preferred Dividends", :vals => [@preferred_div.total] ) ]
|
|
33
|
+
return cs
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def analysis
|
|
37
|
+
analysis = CalculationSummary.new
|
|
38
|
+
|
|
39
|
+
analysis.title = ""
|
|
40
|
+
analysis.header_row = CalculationHeader.new(:key => "", :vals => [@period.value["end_date"].to_s])
|
|
41
|
+
|
|
42
|
+
analysis.rows = []
|
|
43
|
+
analysis.rows << CalculationRow.new(:key => "Tx w Shareholders ($MM)", :vals => [transactions_with_shareholders.total.to_nearest_million])
|
|
44
|
+
analysis.rows << CalculationRow.new(:key => "CI ($MM)", :vals => [comprehensive_income.total.to_nearest_million])
|
|
45
|
+
|
|
46
|
+
return analysis
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
module FinModeling
|
|
2
|
+
class ReOIValuation
|
|
3
|
+
def initialize(filings, forecasts, cost_of_capital, num_shares)
|
|
4
|
+
@filings, @forecasts, @cost_of_capital, @num_shares = [filings, forecasts, cost_of_capital, num_shares]
|
|
5
|
+
@discount_rate = FinModeling::DiscountRate.new(@cost_of_capital.value + 1.0)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def summary
|
|
9
|
+
s = CalculationSummary.new
|
|
10
|
+
s.title = "ReOI Valuation"
|
|
11
|
+
s.totals_row_enabled = false
|
|
12
|
+
|
|
13
|
+
s.header_row = CalculationHeader.new(:key => "", :vals => periods.map{ |x| x.to_pretty_s + ((x.value > Date.today) ? "E" : "") })
|
|
14
|
+
|
|
15
|
+
s.rows = [ ]
|
|
16
|
+
|
|
17
|
+
s.rows << CalculationRow.new(:key => "ReOI ($MM)", :vals => reoi_vals.map{ |x| x ? x.to_nearest_million : nil })
|
|
18
|
+
s.rows << CalculationRow.new(:key => "PV(ReOI) ($MM)", :vals => pv_reoi_vals.map{ |x| x ? x.to_nearest_million : nil })
|
|
19
|
+
s.rows << CalculationRow.new(:key => "CV ($MM)", :vals => cv_vals.map{ |x| x ? x.to_nearest_million : nil })
|
|
20
|
+
s.rows << CalculationRow.new(:key => "PV(CV) ($MM)", :vals => pv_cv_vals.map{ |x| x ? x.to_nearest_million : nil })
|
|
21
|
+
s.rows << CalculationRow.new(:key => "Book Value of Common Equity ($MM)", :vals => bv_cse_vals.map{ |x| x ? x.to_nearest_million : nil })
|
|
22
|
+
s.rows << CalculationRow.new(:key => "Enterprise Value ($MM)", :vals => ev_vals.map{ |x| x ? x.to_nearest_million : nil })
|
|
23
|
+
s.rows << CalculationRow.new(:key => "NFA ($MM)", :vals => bv_nfa_vals.map{ |x| x ? x.to_nearest_million : nil })
|
|
24
|
+
s.rows << CalculationRow.new(:key => "Value of Common Equity ($MM)", :vals => cse_vals.map{ |x| x ? x.to_nearest_million : nil })
|
|
25
|
+
s.rows << CalculationRow.new(:key => "# Shares (MM)", :vals => num_shares_vals.map{ |x| x ? x.to_nearest_million : nil })
|
|
26
|
+
s.rows << CalculationRow.new(:key => "Value / Share ($)", :vals => val_per_shares_vals.map{ |x| x ? x.to_nearest_dollar(num_decimals=2) : nil })
|
|
27
|
+
|
|
28
|
+
return s
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def periods
|
|
32
|
+
@periods ||= [ @filings.re_bs_arr.last.period ] +
|
|
33
|
+
@forecasts.reformulated_balance_sheets.map{ |x| x.period }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def reoi_vals
|
|
39
|
+
prev_re_bses = [@filings.re_bs_arr.last] + @forecasts.reformulated_balance_sheets[0..-2]
|
|
40
|
+
re_ises = @forecasts.reformulated_income_statements
|
|
41
|
+
re_ois = [nil] + re_ises.zip(prev_re_bses).map{ |pair| pair[0].re_oi(pair[1], @cost_of_capital.value) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def pv_reoi_vals
|
|
45
|
+
reoi_vals[0..-2].each_with_index.map do |reoi, idx|
|
|
46
|
+
days_from_now = periods[idx].value - Date.today
|
|
47
|
+
d = @discount_rate.annualize(from_days=365, to_days=days_from_now)
|
|
48
|
+
reoi ? (reoi / d) : nil
|
|
49
|
+
end + [nil]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def cv_vals
|
|
53
|
+
vals = [nil]*periods.length
|
|
54
|
+
d = @cost_of_capital.annualize(from_days=365, @forecasts.reformulated_income_statements.last.period.days)
|
|
55
|
+
vals[-2] = reoi_vals.last / d
|
|
56
|
+
vals
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def pv_cv_vals
|
|
60
|
+
cv_vals[0..-2].each_with_index.map do |cv, idx|
|
|
61
|
+
days_from_now = periods[idx].value - Date.today
|
|
62
|
+
d = @discount_rate.annualize(from_days=365, to_days=days_from_now)
|
|
63
|
+
cv ? (cv / d) : nil
|
|
64
|
+
end + [nil]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def bv_cse_vals
|
|
68
|
+
vals = [nil]*periods.length
|
|
69
|
+
vals[0] = @filings.re_bs_arr.last.common_shareholders_equity.total
|
|
70
|
+
vals
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def ev_vals
|
|
74
|
+
vals = [nil]*periods.length
|
|
75
|
+
vals[0] = (pv_reoi_vals[1..-2] + pv_cv_vals[-2..-2] + bv_cse_vals[0..0]).inject(:+)
|
|
76
|
+
vals
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def bv_nfa_vals
|
|
80
|
+
vals = [nil]*periods.length
|
|
81
|
+
vals[0] = @filings.re_bs_arr.last.net_financial_assets.total
|
|
82
|
+
vals
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def cse_vals
|
|
86
|
+
vals = [nil]*periods.length
|
|
87
|
+
vals[0] = ev_vals[0] + bv_nfa_vals[0]
|
|
88
|
+
vals
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def num_shares_vals
|
|
92
|
+
vals = [nil]*periods.length
|
|
93
|
+
vals[0] = @num_shares
|
|
94
|
+
vals
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def val_per_shares_vals
|
|
98
|
+
vals = [nil]*periods.length
|
|
99
|
+
vals[0] = cse_vals[0] / @num_shares
|
|
100
|
+
vals
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module FinModeling
|
|
2
|
+
class ShareholderEquityStatementCalculation < CompanyFilingCalculation
|
|
3
|
+
#include CanChooseSuccessivePeriods
|
|
4
|
+
|
|
5
|
+
EC_GOAL = "change in shareholder equity"
|
|
6
|
+
EC_LABELS = [ /^stockholders equity period increase decrease$/ ]
|
|
7
|
+
EC_ANTI_LABELS = [ ]
|
|
8
|
+
EC_IDS = [ /^(|Locator_|loc_)(|us-gaap_)StockholdersEquityPeriodIncreaseDecrease[_a-z0-9]+/ ]
|
|
9
|
+
def equity_change_calculation
|
|
10
|
+
begin
|
|
11
|
+
@ec ||= EquityChangeCalculation.new(find_calculation_arc(EC_GOAL, EC_LABELS, EC_ANTI_LABELS, EC_IDS))
|
|
12
|
+
rescue FinModeling::InvalidFilingError => e
|
|
13
|
+
pre_msg = "calculation tree:\n" + self.calculation.sprint_tree
|
|
14
|
+
raise e, pre_msg+e.message, e.backtrace
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def is_valid?
|
|
19
|
+
return true
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def reformulated(period)
|
|
23
|
+
return ReformulatedShareholderEquityStatement.new(period,
|
|
24
|
+
equity_change_calculation.summary(:period=>period))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def write_constructor(file, item_name)
|
|
28
|
+
item_calc_name = item_name + "_calc"
|
|
29
|
+
@calculation.write_constructor(file, item_calc_name)
|
|
30
|
+
file.puts "#{item_name} = FinModeling::ShareholderEquityStatementCalculation.new(#{item_calc_name})"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -20,7 +20,24 @@ class String
|
|
|
20
20
|
self.gsub(r, '\1')
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
def
|
|
23
|
+
def matches_any_regex?(regexes)
|
|
24
24
|
return regexes.inject(false){ |matches, regex| matches or regex =~ self }
|
|
25
25
|
end
|
|
26
|
+
|
|
27
|
+
def split_into_lines_shorter_than(max_line_width)
|
|
28
|
+
lines = []
|
|
29
|
+
cur_line = []
|
|
30
|
+
|
|
31
|
+
split(' ').each do |word|
|
|
32
|
+
if (cur_line + [word]).join(' ').length > max_line_width
|
|
33
|
+
lines << cur_line.join(' ')
|
|
34
|
+
cur_line = []
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
cur_line << word
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
lines << cur_line.join(' ') if !cur_line.empty?
|
|
41
|
+
lines
|
|
42
|
+
end
|
|
26
43
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module FinModeling
|
|
2
|
+
class TimeSeriesEstimator
|
|
3
|
+
attr_reader :a, :b
|
|
4
|
+
|
|
5
|
+
def initialize(a, b)
|
|
6
|
+
@a, @b = a, b
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def estimate_on(date)
|
|
10
|
+
x = (date - Date.today)
|
|
11
|
+
a + (b*x)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.from_time_series(dates, ys)
|
|
15
|
+
xs = dates.map{ |date| date - Date.today }
|
|
16
|
+
|
|
17
|
+
simple_regression = Statsample::Regression.simple(xs.to_scale, ys.to_scale)
|
|
18
|
+
TimeSeriesEstimator.new(simple_regression.a, simple_regression.b)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.from_const(y)
|
|
22
|
+
TimeSeriesEstimator.new(a=y, b=0.0)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|