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
|
@@ -17,7 +17,7 @@ module FinModeling
|
|
|
17
17
|
def summary(args)
|
|
18
18
|
summary_cache_key = args[:period].to_pretty_s
|
|
19
19
|
summary = lookup_cached_summary(summary_cache_key)
|
|
20
|
-
return summary if !summary.nil?
|
|
20
|
+
return summary if !summary.nil? && false # FIXME: get rid of "and false"
|
|
21
21
|
|
|
22
22
|
mapping = Xbrlware::ValueMapping.new
|
|
23
23
|
mapping.policy[:unknown] = :flip
|
|
@@ -30,7 +30,7 @@ module FinModeling
|
|
|
30
30
|
find_and_tag_special_items(args)
|
|
31
31
|
|
|
32
32
|
summary = super(:period => args[:period], :mapping => mapping)
|
|
33
|
-
if !lookup_cached_classifications(BASE_FILENAME, summary.rows)
|
|
33
|
+
if !lookup_cached_classifications(BASE_FILENAME, summary.rows) || true # FIXME: get rid of "or true"
|
|
34
34
|
lookahead = [4, summary.rows.length-1].min
|
|
35
35
|
classify_rows(ALL_STATES, NEXT_STATES, summary.rows, FinModeling::CashChangeItem, lookahead)
|
|
36
36
|
save_cached_classifications(BASE_FILENAME, summary.rows)
|
|
@@ -45,7 +45,7 @@ module FinModeling
|
|
|
45
45
|
|
|
46
46
|
def find_and_tag_special_items(args)
|
|
47
47
|
leaf_items(:period => args[:period]).each do |item|
|
|
48
|
-
if item.name.
|
|
48
|
+
if item.name.matches_any_regex?([ /NetIncomeLoss/,
|
|
49
49
|
/ProfitLoss/ ])
|
|
50
50
|
item.def = {} if !item.def
|
|
51
51
|
item.def["xbrli:balance"] = "netincome"
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module FinModeling
|
|
2
|
+
class CashChangeSummaryFromDifferences
|
|
3
|
+
def initialize(re_cfs1, re_cfs2)
|
|
4
|
+
@re_cfs1 = re_cfs1
|
|
5
|
+
@re_cfs2 = re_cfs2
|
|
6
|
+
end
|
|
7
|
+
def filter_by_type(key)
|
|
8
|
+
case key
|
|
9
|
+
when :c
|
|
10
|
+
@cs = FinModeling::CalculationSummary.new
|
|
11
|
+
@cs.title = "Cash from Operations"
|
|
12
|
+
@cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @re_cfs1.cash_from_operations.total] ),
|
|
13
|
+
CalculationRow.new(:key => "Second Row", :vals => [-@re_cfs2.cash_from_operations.total] ) ]
|
|
14
|
+
return @cs
|
|
15
|
+
when :i
|
|
16
|
+
@cs = FinModeling::CalculationSummary.new
|
|
17
|
+
@cs.title = "Cash Investments in Operations"
|
|
18
|
+
@cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @re_cfs1.cash_investments_in_operations.total] ),
|
|
19
|
+
CalculationRow.new(:key => "Second Row", :vals => [-@re_cfs2.cash_investments_in_operations.total] ) ]
|
|
20
|
+
return @cs
|
|
21
|
+
when :d
|
|
22
|
+
@cs = FinModeling::CalculationSummary.new
|
|
23
|
+
@cs.title = "Payments to Debtholders"
|
|
24
|
+
@cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @re_cfs1.payments_to_debtholders.total] ),
|
|
25
|
+
CalculationRow.new(:key => "Second Row", :vals => [-@re_cfs2.payments_to_debtholders.total] ) ]
|
|
26
|
+
return @cs
|
|
27
|
+
when :f
|
|
28
|
+
@cs = FinModeling::CalculationSummary.new
|
|
29
|
+
@cs.title = "Payments to Stockholders"
|
|
30
|
+
@cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @re_cfs1.payments_to_stockholders.total] ),
|
|
31
|
+
CalculationRow.new(:key => "Second Row", :vals => [-@re_cfs2.payments_to_stockholders.total] ) ]
|
|
32
|
+
return @cs
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module FinModeling
|
|
4
|
+
|
|
5
|
+
class CashFlowStatementAnalyses < CalculationSummary
|
|
6
|
+
def initialize(calc_summary)
|
|
7
|
+
@title = calc_summary.title
|
|
8
|
+
@rows = calc_summary.rows
|
|
9
|
+
@header_row = calc_summary.header_row
|
|
10
|
+
@key_width = calc_summary.key_width
|
|
11
|
+
@val_width = calc_summary.val_width
|
|
12
|
+
@max_decimals = calc_summary.max_decimals
|
|
13
|
+
@totals_row_enabled = false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def print_regressions # FIXME: rename
|
|
17
|
+
lr = ni_over_c_row.valid_vals.linear_regression
|
|
18
|
+
puts "\t\tNI / C: "+
|
|
19
|
+
"a:#{lr.a.to_s.cap_decimals(4)}, "+
|
|
20
|
+
"b:#{lr.b.to_s.cap_decimals(4)}, "+
|
|
21
|
+
"r²:#{lr.r2.to_s.cap_decimals(4)}, "+
|
|
22
|
+
"σ²:#{ni_over_c_row.valid_vals.variance.to_s.cap_decimals(4)}, " +
|
|
23
|
+
( (lr.r2 > 0.6) ? "strong fit" : ( (lr.r2 < 0.2) ? "weak fit [**]" : "avg fit") )
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def ni_over_c_row
|
|
27
|
+
find_row_by_key('NI / C')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def find_row_by_key(key) # FIXME: move this to CalculationSummary
|
|
31
|
+
self.rows.find{ |x| x.key == key }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
|
|
@@ -1,28 +1,32 @@
|
|
|
1
1
|
module FinModeling
|
|
2
2
|
class CashFlowStatementCalculation < CompanyFilingCalculation
|
|
3
|
+
include CanChooseSuccessivePeriods
|
|
4
|
+
|
|
5
|
+
CASH_GOAL = "cash change"
|
|
6
|
+
CASH_LABELS = [ /^cash and cash equivalents period increase decrease/,
|
|
7
|
+
/^(|net )(change|increase|decrease|decrease *increase|increase *decrease) in cash and(| cash) equivalents/,
|
|
8
|
+
/^net cash provided by used in (|operating activities )continuing operations/,
|
|
9
|
+
/^net cash provided by used in operating activities/]
|
|
10
|
+
CASH_ANTI_LABELS = [ ]
|
|
11
|
+
CASH_IDS = [ /^(|Locator_|loc_)(|us-gaap_)CashAndCashEquivalentsPeriodIncreaseDecrease[_a-z0-9]+/,
|
|
12
|
+
/^(|Locator_|loc_)(|us-gaap_)NetCashProvidedByUsedIn(|OperatingActivities)ContinuingOperations[_a-z0-9]+/ ]
|
|
3
13
|
|
|
4
14
|
def cash_change_calculation
|
|
5
|
-
|
|
6
|
-
friendly_goal = "cash change"
|
|
7
|
-
label_regexes = [ /^cash and cash equivalents period increase decrease/,
|
|
8
|
-
/^(|net )(change|increase|decrease|decrease *increase|increase *decrease) in cash and cash equivalents/,
|
|
9
|
-
/^net cash provided by used in continuing operations/]
|
|
10
|
-
id_regexes = [ /^(|Locator_|loc_)(|us-gaap_)CashAndCashEquivalentsPeriodIncreaseDecrease[_a-z0-9]+/,
|
|
11
|
-
/^(|Locator_|loc_)(|us-gaap_)NetCashProvidedByUsedInContinuingOperations[_a-z0-9]+/ ]
|
|
12
|
-
|
|
13
|
-
calc = find_and_verify_calculation_arc(friendly_goal, label_regexes, id_regexes)
|
|
14
|
-
@cash_change = CashChangeCalculation.new(calc)
|
|
15
|
-
end
|
|
16
|
-
return @cash_change
|
|
15
|
+
@cash_change ||= CashChangeCalculation.new(find_calculation_arc(CASH_GOAL, CASH_LABELS, CASH_ANTI_LABELS, CASH_IDS))
|
|
17
16
|
end
|
|
18
17
|
|
|
19
18
|
def is_valid?
|
|
20
19
|
re_cfs = reformulated(periods.last)
|
|
21
20
|
flows_are_balanced = (re_cfs.free_cash_flow.total == (-1*re_cfs.financing_flows.total))
|
|
21
|
+
puts "flows are not balanced" if !flows_are_balanced
|
|
22
22
|
none_are_zero = (re_cfs.cash_from_operations.total != 0) &&
|
|
23
23
|
(re_cfs.cash_investments_in_operations.total != 0) &&
|
|
24
|
-
(re_cfs.payments_to_debtholders.total != 0)
|
|
25
|
-
(re_cfs.payments_to_stockholders.total != 0)
|
|
24
|
+
(re_cfs.payments_to_debtholders.total != 0) #&&
|
|
25
|
+
#(re_cfs.payments_to_stockholders.total != 0) # I relaxed this constraint. Seems it is often legitimately zero
|
|
26
|
+
puts "(re_cfs.cash_from_operations.total == 0)" if (re_cfs.cash_from_operations.total == 0)
|
|
27
|
+
puts "(re_cfs.cash_investments_in_operations.total == 0)" if (re_cfs.cash_investments_in_operations.total == 0)
|
|
28
|
+
puts "(re_cfs.payments_to_debtholders.total == 0)" if (re_cfs.payments_to_debtholders.total == 0)
|
|
29
|
+
#puts "(re_cfs.payments_to_stockholders.total == 0)" if (re_cfs.payments_to_stockholders.total == 0)
|
|
26
30
|
return (flows_are_balanced && none_are_zero)
|
|
27
31
|
end
|
|
28
32
|
|
|
@@ -30,46 +34,18 @@ module FinModeling
|
|
|
30
34
|
return ReformulatedCashFlowStatement.new(period, cash_change_calculation.summary(:period => period))
|
|
31
35
|
end
|
|
32
36
|
|
|
33
|
-
def latest_quarterly_reformulated(
|
|
34
|
-
if cash_change_calculation.periods.quarterly.any?
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return nil
|
|
40
|
-
|
|
41
|
-
elsif cash_change_calculation.periods.halfyearly.any? &&
|
|
42
|
-
prev_cash_flow_statement.cash_change_calculation.periods.quarterly.any?
|
|
43
|
-
cfs_period = cash_change_calculation.periods.halfyearly.last
|
|
44
|
-
re_cfs = reformulated(cfs_period)
|
|
45
|
-
|
|
46
|
-
period_1q_thru_1q = prev_cash_flow_statement.cash_change_calculation.periods.quarterly.last
|
|
47
|
-
prev1q = prev_cash_flow_statement.reformulated(period_1q_thru_1q)
|
|
48
|
-
re_cfs = re_cfs - prev1q
|
|
49
|
-
|
|
50
|
-
return re_cfs
|
|
51
|
-
|
|
52
|
-
elsif cash_change_calculation.periods.threequarterly.any? &&
|
|
53
|
-
prev_cash_flow_statement.cash_change_calculation.periods.halfyearly.any?
|
|
54
|
-
cfs_period = cash_change_calculation.periods.threequarterly.last
|
|
55
|
-
re_cfs = reformulated(cfs_period)
|
|
56
|
-
|
|
57
|
-
period_1q_thru_2q = prev_cash_flow_statement.cash_change_calculation.periods.halfyearly.last
|
|
58
|
-
prev2q = prev_cash_flow_statement.reformulated(period_1q_thru_2q)
|
|
59
|
-
re_cfs = re_cfs - prev2q
|
|
60
|
-
|
|
61
|
-
return re_cfs
|
|
37
|
+
def latest_quarterly_reformulated(prev_cfs)
|
|
38
|
+
if cash_change_calculation.periods.quarterly.any?
|
|
39
|
+
period = cash_change_calculation.periods.quarterly.last
|
|
40
|
+
lqr = reformulated(period)
|
|
41
|
+
return lqr if lqr.flows_are_plausible?
|
|
42
|
+
end
|
|
62
43
|
|
|
63
|
-
|
|
64
|
-
prev_cash_flow_statement.cash_change_calculation.periods.threequarterly.any?
|
|
65
|
-
cfs_period = cash_change_calculation.periods.yearly.last
|
|
66
|
-
re_cfs = reformulated(cfs_period)
|
|
67
|
-
|
|
68
|
-
period_1q_thru_3q = prev_cash_flow_statement.cash_change_calculation.periods.threequarterly.last
|
|
69
|
-
prev3q = prev_cash_flow_statement.reformulated(period_1q_thru_3q)
|
|
70
|
-
re_cfs = re_cfs - prev3q
|
|
44
|
+
return nil if !prev_cfs
|
|
71
45
|
|
|
72
|
-
|
|
46
|
+
cur_period, prev_period = choose_successive_periods(cash_change_calculation, prev_cfs.cash_change_calculation)
|
|
47
|
+
if cur_period && prev_period
|
|
48
|
+
return reformulated(cur_period) - prev_cfs.reformulated(prev_period)
|
|
73
49
|
end
|
|
74
50
|
|
|
75
51
|
return nil
|
|
@@ -5,7 +5,9 @@ module FinModeling
|
|
|
5
5
|
FinModeling::AssetsItem.load_vectors_and_train
|
|
6
6
|
FinModeling::LiabsAndEquityItem.load_vectors_and_train
|
|
7
7
|
FinModeling::IncomeStatementItem.load_vectors_and_train
|
|
8
|
+
FinModeling::ComprehensiveIncomeStatementItem.load_vectors_and_train
|
|
8
9
|
FinModeling::CashChangeItem.load_vectors_and_train
|
|
10
|
+
FinModeling::EquityChangeItem.load_vectors_and_train
|
|
9
11
|
end
|
|
10
12
|
end
|
|
11
13
|
end
|
data/lib/finmodeling/company.rb
CHANGED
|
File without changes
|
|
@@ -1,16 +1,39 @@
|
|
|
1
1
|
module FinModeling
|
|
2
2
|
|
|
3
3
|
class CachedAnnualFiling
|
|
4
|
-
attr_accessor :balance_sheet, :income_statement, :cash_flow_statement, :disclosures
|
|
5
|
-
def initialize(bs, is, cfs, disclosures)
|
|
6
|
-
@balance_sheet
|
|
7
|
-
@income_statement
|
|
8
|
-
@
|
|
9
|
-
@
|
|
4
|
+
attr_accessor :balance_sheet, :income_statement, :comprehensive_income_statement, :cash_flow_statement, :shareholder_equity_statement, :disclosures
|
|
5
|
+
def initialize(bs, is, cis, cfs, ses, disclosures)
|
|
6
|
+
@balance_sheet = bs
|
|
7
|
+
@income_statement = is
|
|
8
|
+
@comprehensive_income_statement = cis
|
|
9
|
+
@cash_flow_statement = cfs
|
|
10
|
+
@shareholder_equity_statement = ses
|
|
11
|
+
@disclosures = disclosures
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def has_an_income_statement?
|
|
15
|
+
!@income_statement.nil?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def has_a_comprehensive_income_statement?
|
|
19
|
+
!@comprehensive_income_statement.nil?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def has_a_shareholder_equity_statement?
|
|
23
|
+
!@shareholder_equity_statement.nil?
|
|
10
24
|
end
|
|
11
25
|
|
|
12
26
|
def is_valid?
|
|
13
|
-
|
|
27
|
+
puts "balance sheet is not valid" if !@balance_sheet.is_valid?
|
|
28
|
+
puts "income statment is not valid" if has_an_income_statement? && !@income_statement.is_valid?
|
|
29
|
+
puts "comprehensive income statment is not valid" if has_a_comprehensive_income_statement? && !@comprehensive_income_statement.is_valid?
|
|
30
|
+
#puts "cash flow statement is not valid" if !cash_flow_statement.is_valid?
|
|
31
|
+
|
|
32
|
+
return false if !@balance_sheet.is_valid?
|
|
33
|
+
return false if has_an_income_statement? && !@income_statement.is_valid?
|
|
34
|
+
return false if has_a_comprehensive_income_statement? && !@comprehensive_income_statement.is_valid?
|
|
35
|
+
#return false if !@cash_flow_statement.is_valid? # FIXME: why can't we enable this?
|
|
36
|
+
return true
|
|
14
37
|
end
|
|
15
38
|
end
|
|
16
39
|
|
|
@@ -56,15 +56,25 @@ module FinModeling
|
|
|
56
56
|
|
|
57
57
|
protected
|
|
58
58
|
|
|
59
|
-
def
|
|
60
|
-
|
|
59
|
+
def find_calculation_arc(friendly_goal, label_regexes, anti_label_regexes, id_regexes, criterion=:first)
|
|
60
|
+
calcs = @calculation.arcs.select{ |x| x.label.downcase.gsub(/[^a-z ]/, '').matches_any_regex?(label_regexes) &&
|
|
61
|
+
!x.label.downcase.gsub(/[^a-z ]/, '').matches_any_regex?(anti_label_regexes) }
|
|
61
62
|
|
|
62
|
-
if
|
|
63
|
-
summary_of_arcs = @calculation.arcs.map{ |x| "\"#{x.label}\"" }.join("
|
|
64
|
-
raise
|
|
63
|
+
if calcs.empty?
|
|
64
|
+
summary_of_arcs = @calculation.arcs.map{ |x| "\t\"#{x.label}\"" }.join("\n")
|
|
65
|
+
raise InvalidFilingError.new("Couldn't find #{friendly_goal} in:\n" + summary_of_arcs + "\nTried: #{label_regexes.inspect}. (Ignoring: #{anti_label_regexes.inspect}.).")
|
|
65
66
|
end
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
calc = case
|
|
69
|
+
when criterion == :first
|
|
70
|
+
calcs.first
|
|
71
|
+
when criterion == :longest
|
|
72
|
+
calcs.sort{ |x,y| x.leaf_items(periods.last).length <=> y.leaf_items(periods.last).length }.last
|
|
73
|
+
else
|
|
74
|
+
raise ArgumentError.new("\"#{criterion}\" is not a valid criterion")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if !calc.item_id.matches_any_regex?(id_regexes)
|
|
68
78
|
puts "Warning: #{friendly_goal} id is not recognized: #{calc.item_id}"
|
|
69
79
|
end
|
|
70
80
|
|
|
@@ -1,72 +1,138 @@
|
|
|
1
1
|
module FinModeling
|
|
2
2
|
class CompanyFilings < Array
|
|
3
|
+
def re_bs_arr
|
|
4
|
+
@re_bs_arr ||= self.map{ |filing| filing.balance_sheet.reformulated(filing.balance_sheet.periods.last) }
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def re_is_arr
|
|
8
|
+
prev_stmt = nil
|
|
9
|
+
@re_is_arr ||= ([nil] + self).each_cons(2).map do |prev_filing, filing|
|
|
10
|
+
cur_re_is = nil
|
|
11
|
+
cur_ci_calc = filing.has_a_comprehensive_income_statement? ? filing.comprehensive_income_statement.comprehensive_income_calculation : nil
|
|
12
|
+
prev_ci_calc = (prev_filing && prev_filing.has_a_comprehensive_income_statement?) ?
|
|
13
|
+
prev_filing.comprehensive_income_statement.comprehensive_income_calculation : nil
|
|
14
|
+
if filing.has_an_income_statement?
|
|
15
|
+
cur_re_is = filing.income_statement.latest_quarterly_reformulated(cur_ci_calc, prev_stmt, prev_ci_calc)
|
|
16
|
+
prev_stmt = filing.income_statement
|
|
17
|
+
elsif filing.has_a_comprehensive_income_statement? &&
|
|
18
|
+
filing.comprehensive_income_statement
|
|
19
|
+
.comprehensive_income_calculation
|
|
20
|
+
.has_revenue_item?
|
|
21
|
+
cur_re_is = filing.comprehensive_income_statement.latest_quarterly_reformulated(prev_stmt, prev_ci_calc)
|
|
22
|
+
prev_stmt = filing.comprehensive_income_statement
|
|
23
|
+
else
|
|
24
|
+
raise RuntimeError.new("Can't create reformulated income statement")
|
|
25
|
+
end
|
|
26
|
+
cur_re_is
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def re_cfs_arr
|
|
31
|
+
@re_cfs_arr ||= ([nil] + self).each_cons(2).map do |prev_filing, filing|
|
|
32
|
+
filing.cash_flow_statement.latest_quarterly_reformulated(prev_filing ? prev_filing.cash_flow_statement : nil)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
3
36
|
def balance_sheet_analyses
|
|
4
37
|
if !@balance_sheet_analyses
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
re_bs = filing.balance_sheet.reformulated(filing.balance_sheet.periods.last)
|
|
9
|
-
next_analysis = re_bs.analysis(prev_re_bs)
|
|
10
|
-
|
|
11
|
-
@balance_sheet_analyses = @balance_sheet_analyses + next_analysis if @balance_sheet_analyses
|
|
12
|
-
@balance_sheet_analyses = next_analysis if !@balance_sheet_analyses
|
|
13
|
-
end
|
|
14
|
-
@balance_sheet_analyses = BalanceSheetAnalyses.new(@balance_sheet_analyses)
|
|
38
|
+
analyses = ([nil] + re_bs_arr).each_cons(2).map { |prev, cur| cur.analysis(prev) }
|
|
39
|
+
analyses.delete_if{ |x| x.nil? }
|
|
40
|
+
@balance_sheet_analyses = BalanceSheetAnalyses.new( analyses.inject(:+) )
|
|
15
41
|
end
|
|
16
42
|
return @balance_sheet_analyses
|
|
17
43
|
end
|
|
18
44
|
|
|
19
|
-
def income_statement_analyses
|
|
45
|
+
def income_statement_analyses(expected_rate_of_return)
|
|
20
46
|
if !@income_statement_analyses
|
|
21
|
-
|
|
22
|
-
self.
|
|
23
|
-
|
|
24
|
-
|
|
47
|
+
analyses = []
|
|
48
|
+
self.each_with_index do |filing, idx|
|
|
49
|
+
prev_re_bs = (idx > 0) ? re_bs_arr[idx-1] : nil
|
|
50
|
+
prev_re_is = (idx > 0) ? re_is_arr[idx-1] : nil
|
|
51
|
+
re_bs = re_bs_arr[idx]
|
|
52
|
+
re_is = re_is_arr[idx]
|
|
25
53
|
|
|
26
|
-
|
|
27
|
-
next_analysis = re_is.analysis(re_bs, prev_re_is, prev_re_bs) if re_is
|
|
28
|
-
|
|
29
|
-
@income_statement_analyses = @income_statement_analyses + next_analysis if @income_statement_analyses
|
|
30
|
-
@income_statement_analyses = next_analysis if !@income_statement_analyses
|
|
31
|
-
|
|
32
|
-
prev_re_bs, prev_re_is, prev_filing = [re_bs, re_is, filing]
|
|
54
|
+
analyses << (re_is ? re_is.analysis(re_bs, prev_re_is, prev_re_bs, expected_rate_of_return) : FinModeling::ReformulatedIncomeStatement.empty_analysis )
|
|
33
55
|
end
|
|
34
|
-
|
|
56
|
+
|
|
57
|
+
analyses.delete_if{ |x| x.nil? }
|
|
58
|
+
@income_statement_analyses = IncomeStatementAnalyses.new( analyses.inject(:+) )
|
|
35
59
|
end
|
|
36
60
|
return @income_statement_analyses
|
|
37
61
|
end
|
|
38
|
-
|
|
62
|
+
|
|
39
63
|
def cash_flow_statement_analyses
|
|
40
64
|
if !@cash_flow_statement_analyses
|
|
41
|
-
|
|
42
|
-
self.
|
|
43
|
-
re_is
|
|
44
|
-
re_cfs =
|
|
65
|
+
analyses = []
|
|
66
|
+
self.each_with_index do |filing, idx|
|
|
67
|
+
re_is = re_is_arr[idx]
|
|
68
|
+
re_cfs = re_cfs_arr[idx]
|
|
45
69
|
|
|
46
|
-
|
|
47
|
-
next_analysis = re_cfs.analysis(re_is) if re_cfs
|
|
48
|
-
|
|
49
|
-
@cash_flow_statement_analyses = @cash_flow_statement_analyses + next_analysis if @cash_flow_statement_analyses
|
|
50
|
-
@cash_flow_statement_analyses = next_analysis if !@cash_flow_statement_analyses
|
|
51
|
-
|
|
52
|
-
prev_filing, prev_re_cfs = [filing, re_cfs]
|
|
70
|
+
analyses << (re_cfs ? re_cfs.analysis(re_is) : FinModeling::ReformulatedCashFlowStatement.empty_analysis)
|
|
53
71
|
end
|
|
72
|
+
|
|
73
|
+
analyses.delete_if{ |x| x.nil? }
|
|
74
|
+
@cash_flow_statement_analyses = CashFlowStatementAnalyses.new( analyses.inject(:+) )
|
|
54
75
|
@cash_flow_statement_analyses.totals_row_enabled = false
|
|
55
76
|
end
|
|
56
77
|
return @cash_flow_statement_analyses
|
|
57
78
|
end
|
|
79
|
+
|
|
80
|
+
def disclosures(title_regex, period_type=nil)
|
|
81
|
+
ds = nil
|
|
82
|
+
self.each do |filing|
|
|
83
|
+
cur_disclosures = filing.disclosures
|
|
84
|
+
if ( disclosure = filing.disclosures.find{ |disc| disc.summary(:period => disc.periods.last)
|
|
85
|
+
.title
|
|
86
|
+
.gsub(/ \(.*/,'') =~ title_regex } )
|
|
87
|
+
|
|
88
|
+
period = case period_type
|
|
89
|
+
when nil then disclosure.periods.last
|
|
90
|
+
when :yearly then disclosure.periods.yearly.last
|
|
91
|
+
when :quarterly then disclosure.periods.quarterly.last
|
|
92
|
+
else raise RuntimeError.new("bogus period type")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
if period
|
|
96
|
+
next_d = disclosure.summary(:period => period )
|
|
97
|
+
next_d.header_row = CalculationHeader.new(:key => "", :vals => [period.to_pretty_s.gsub(/.* to /,'')])
|
|
98
|
+
|
|
99
|
+
ds = ds + next_d if ds
|
|
100
|
+
ds = next_d if !ds
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
return ds
|
|
105
|
+
end
|
|
58
106
|
|
|
59
|
-
def choose_forecasting_policy
|
|
60
|
-
if length < 3
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
isa = income_statement_analyses
|
|
107
|
+
def choose_forecasting_policy(expected_rate_of_return, policy_type=:linear_trend)
|
|
108
|
+
raise RuntimeError.new("Cannot properly forecast with fewer than 3 filings") if length < 3
|
|
109
|
+
case policy_type
|
|
110
|
+
when :trailing_avg
|
|
111
|
+
isa = income_statement_analyses(expected_rate_of_return)
|
|
112
|
+
args = { }
|
|
113
|
+
|
|
114
|
+
args[:revenue_estimator] = TimeSeriesEstimator.from_const(ArrayWithStats.new(re_is_arr[1..-1].map{ |re_is| re_is.operating_revenues.total }).mean)
|
|
115
|
+
args[:sales_pm_estimator] = TimeSeriesEstimator.from_const(ArrayWithStats.new(isa.operating_pm_row.valid_vals[1..-1]).mean)
|
|
116
|
+
args[:sales_over_noa_estimator] = TimeSeriesEstimator.from_const(ArrayWithStats.new(isa.sales_over_noa_row.valid_vals[1..-1]).mean)
|
|
117
|
+
args[:fi_over_nfa_estimator] = TimeSeriesEstimator.from_const(ArrayWithStats.new(isa.fi_over_nfa_row.valid_vals[1..-1]).mean)
|
|
118
|
+
return FinModeling::LinearTrendForecastingPolicy.new(args)
|
|
119
|
+
|
|
120
|
+
when :linear_trend
|
|
121
|
+
isa = income_statement_analyses(expected_rate_of_return)
|
|
64
122
|
args = { }
|
|
65
|
-
|
|
66
|
-
args[:
|
|
67
|
-
|
|
68
|
-
args[:
|
|
69
|
-
|
|
123
|
+
|
|
124
|
+
args[:revenue_estimator] = TimeSeriesEstimator.from_time_series(re_is_arr[1..-1].map{ |re_is| re_is.period.value["end_date"] },
|
|
125
|
+
re_is_arr[1..-1].map{ |re_is| re_is.operating_revenues.total })
|
|
126
|
+
args[:sales_pm_estimator] = TimeSeriesEstimator.from_time_series(re_is_arr[1..-1].map{ |re_is| re_is.period.value["end_date"] },
|
|
127
|
+
isa.operating_pm_row.vals[1..-1])
|
|
128
|
+
args[:sales_over_noa_estimator] = TimeSeriesEstimator.from_time_series(re_is_arr[1..-1].map{ |re_is| re_is.period.value["end_date"] },
|
|
129
|
+
isa.sales_over_noa_row.vals[1..-1])
|
|
130
|
+
args[:fi_over_nfa_estimator] = TimeSeriesEstimator.from_time_series(re_is_arr[1..-1].map{ |re_is| re_is.period.value["end_date"] },
|
|
131
|
+
isa.fi_over_nfa_row.vals[1..-1])
|
|
132
|
+
return FinModeling::LinearTrendForecastingPolicy.new(args)
|
|
133
|
+
|
|
134
|
+
else
|
|
135
|
+
raise ArgumentError.new("\"#{policy_type}\" is not a valid forecasting policy type")
|
|
70
136
|
end
|
|
71
137
|
end
|
|
72
138
|
|
|
@@ -77,7 +143,7 @@ module FinModeling
|
|
|
77
143
|
|
|
78
144
|
last_last_is = (self.length >= 2) ? self[-2].income_statement : nil
|
|
79
145
|
puts "warning: last_last_is is nil..." if !last_last_is
|
|
80
|
-
last_re_is = self.last.income_statement.latest_quarterly_reformulated(last_last_is)
|
|
146
|
+
last_re_is = self.last.income_statement.latest_quarterly_reformulated(last_cis=nil, last_last_is, last_last_cis=nil)
|
|
81
147
|
raise RuntimeError.new("last_re_is is nil!") if !last_re_is
|
|
82
148
|
|
|
83
149
|
num_quarters.times do |i|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module FinModeling
|
|
2
|
+
class ComprehensiveIncomeCalculation < CompanyFilingCalculation
|
|
3
|
+
include CanCacheClassifications
|
|
4
|
+
include CanCacheSummaries
|
|
5
|
+
include CanClassifyRows
|
|
6
|
+
|
|
7
|
+
BASE_FILENAME = File.join(FinModeling::BASE_PATH, "summaries/comprehensive_income_")
|
|
8
|
+
|
|
9
|
+
ALL_STATES = [ :or, :cogs, :oe, :oibt, :fibt, :tax, :ooiat, :fiat, :ni, :ooci, :ooci_nci, :foci, :unkoci ]
|
|
10
|
+
NEXT_STATES = { nil => [ :or, :ni ],
|
|
11
|
+
:or => [ :or, :cogs, :oe, :oibt, :fibt ],
|
|
12
|
+
:cogs => [ :cogs, :oe, :oibt, :fibt, :tax ],
|
|
13
|
+
:oe => [ :oe, :oibt, :fibt, :tax ],
|
|
14
|
+
:oibt => [ :oibt, :fibt, :tax ], # obit/fibt can cycle back/forth
|
|
15
|
+
:fibt => [ :obit, :fibt, :tax ], # obit/fibt can cycle back/forth
|
|
16
|
+
:tax => [ :ooiat, :fiat, :ooci, :ooci_nci, :foci, :unkoci ], # 1 tax item. then moves forward.
|
|
17
|
+
:ooiat => [ :ooiat, :fiat, :ooci, :ooci_nci, :foci, :unkoci ], # ooiat/fiat can cycle back/forth
|
|
18
|
+
:fiat => [ :ooiat, :fiat, :ooci, :ooci_nci, :foci, :unkoci ], # ooiat/fiat can cycle back/forth
|
|
19
|
+
|
|
20
|
+
:ni => [ :ooci, :ooci_nci, :foci, :unkoci ], # after ni, no ordering
|
|
21
|
+
|
|
22
|
+
:ooci => [ :ooci, :ooci_nci, :foci, :unkoci ], # after ni, no ordering
|
|
23
|
+
:ooci_nci => [ :ooci, :ooci_nci, :foci, :unkoci ], # after ni, no ordering
|
|
24
|
+
:foci => [ :ooci, :ooci_nci, :foci, :unkoci ], # after ni, no ordering
|
|
25
|
+
:unkoci => [ :ooci, :ooci_nci, :foci, :unkoci ] }# after ni, no ordering
|
|
26
|
+
|
|
27
|
+
def summary(args)
|
|
28
|
+
summary_cache_key = args[:period].to_pretty_s
|
|
29
|
+
thesummary = lookup_cached_summary(summary_cache_key)
|
|
30
|
+
return thesummary if !thesummary.nil?
|
|
31
|
+
|
|
32
|
+
mapping = Xbrlware::ValueMapping.new
|
|
33
|
+
mapping.policy[:debit] = :flip
|
|
34
|
+
|
|
35
|
+
thesummary = super(:period => args[:period], :mapping => mapping)
|
|
36
|
+
if !lookup_cached_classifications(BASE_FILENAME, thesummary.rows)
|
|
37
|
+
lookahead = [4, thesummary.rows.length-1].min
|
|
38
|
+
classify_rows(ALL_STATES, NEXT_STATES, thesummary.rows, FinModeling::ComprehensiveIncomeStatementItem, lookahead)
|
|
39
|
+
save_cached_classifications(BASE_FILENAME, thesummary.rows)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
save_cached_summary(summary_cache_key, thesummary)
|
|
43
|
+
|
|
44
|
+
return thesummary
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def has_revenue_item?
|
|
48
|
+
@has_revenue_item ||= summary(:period => periods.last).rows.any? do |row|
|
|
49
|
+
row.type == :or
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def has_net_income_item?
|
|
54
|
+
@has_net_income_item ||= summary(:period => periods.last).rows.any? do |row|
|
|
55
|
+
row.type == :ni
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module FinModeling
|
|
2
|
+
class ComprehensiveIncomeStatementCalculation < CompanyFilingCalculation
|
|
3
|
+
include CanChooseSuccessivePeriods
|
|
4
|
+
|
|
5
|
+
CI_GOAL = "comprehensive income"
|
|
6
|
+
CI_LABELS = [ /^comprehensive (income|loss|loss income|income loss)(| net of tax)(| attributable to .*)$/ ]
|
|
7
|
+
CI_ANTI_LABELS = [ /noncontrolling interest/,
|
|
8
|
+
/minority interest/ ]
|
|
9
|
+
CI_IDS = [ /^(|Locator_|loc_)(|us-gaap_)ComprehensiveIncomeNetOfTax[_0-9a-z]+/ ]
|
|
10
|
+
def comprehensive_income_calculation
|
|
11
|
+
begin
|
|
12
|
+
@ci ||= ComprehensiveIncomeCalculation.new(find_calculation_arc(CI_GOAL, CI_LABELS, CI_ANTI_LABELS, CI_IDS))
|
|
13
|
+
rescue FinModeling::InvalidFilingError => e
|
|
14
|
+
pre_msg = "calculation tree:\n" + self.calculation.sprint_tree
|
|
15
|
+
raise e, pre_msg+e.message, e.backtrace
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def is_valid?
|
|
20
|
+
if !comprehensive_income_calculation.has_net_income_item? && !comprehensive_income_calculation.has_revenue_item?
|
|
21
|
+
puts "comprehensive income statement's comprehensive income calculation lacks net income item"
|
|
22
|
+
puts "comprehensive income statement's comprehensive income calculation lacks sales/revenue item"
|
|
23
|
+
if comprehensive_income_calculation
|
|
24
|
+
puts "summary:"
|
|
25
|
+
comprehensive_income_calculation.summary(:period => periods.last).print
|
|
26
|
+
end
|
|
27
|
+
puts "calculation tree:\n" + self.calculation.sprint_tree(indent_count=0, simplified=true)
|
|
28
|
+
end
|
|
29
|
+
return (comprehensive_income_calculation.has_revenue_item? || comprehensive_income_calculation.has_net_income_item?)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def reformulated(period, dummy_comprehensive_income_calculation) # 2nd param is just to keep signature consistent w/ IncomeStatement::reformulated
|
|
33
|
+
# The way ReformulatedIncomeStatement.new() is implemented, it'll just ignore rows with types it
|
|
34
|
+
# doesn't know about (like OCI). So this should extract just the NI-related rows.
|
|
35
|
+
return ReformulatedIncomeStatement.new(period,
|
|
36
|
+
comprehensive_income_calculation.summary(:period=>period), # NI
|
|
37
|
+
comprehensive_income_calculation.summary(:period=>period)) # CI
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def latest_quarterly_reformulated(dummy_cur_ci_calc, prev_stmt, prev_ci_calc)
|
|
41
|
+
if comprehensive_income_calculation.periods.quarterly.any?
|
|
42
|
+
period = comprehensive_income_calculation.periods.quarterly.last
|
|
43
|
+
lqr = reformulated(period, comprehensive_income_calculation)
|
|
44
|
+
|
|
45
|
+
if (lqr.operating_revenues.total.abs > 1.0) && # FIXME: make an is_valid here?
|
|
46
|
+
(lqr.cost_of_revenues .total.abs > 1.0) # FIXME: make an is_valid here?
|
|
47
|
+
return lqr
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
return nil if !prev_stmt
|
|
52
|
+
|
|
53
|
+
prev_calc = prev_stmt.respond_to?(:net_income_calculation) ? prev_stmt.net_income_calculation : prev_stmt.comprehensive_income_calculation
|
|
54
|
+
|
|
55
|
+
cur_period, prev_period = choose_successive_periods(comprehensive_income_calculation, prev_calc)
|
|
56
|
+
if cur_period && prev_period
|
|
57
|
+
new_re_is = reformulated(cur_period, comprehensive_income_calculation) - prev_stmt.reformulated(prev_period, prev_ci_calc)
|
|
58
|
+
# the above subtraction doesn't know what period you want. So let's patch the result to have
|
|
59
|
+
# a quarterly period with the right end-points
|
|
60
|
+
new_re_is.period = Xbrlware::Context::Period.new({"start_date"=>prev_period.value["end_date"],
|
|
61
|
+
"end_date" =>cur_period.value["end_date"]})
|
|
62
|
+
return new_re_is
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
return nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def write_constructor(file, item_name)
|
|
69
|
+
item_calc_name = item_name + "_calc"
|
|
70
|
+
@calculation.write_constructor(file, item_calc_name)
|
|
71
|
+
file.puts "#{item_name} = FinModeling::ComprehensiveIncomeStatementCalculation.new(#{item_calc_name})"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module FinModeling
|
|
2
|
+
class ComprehensiveIncomeStatementItem < String
|
|
3
|
+
include HasStringClassifier
|
|
4
|
+
|
|
5
|
+
BASE_FILENAME = File.join(FinModeling::BASE_PATH, "classifiers/cisi_")
|
|
6
|
+
TYPES = [ :or, :cogs, :oe, :oibt, :fibt, :tax, :ooiat, :fiat, :ni, :ooci, :ooci_nci, :foci, :unkoci ]
|
|
7
|
+
# same as in IncomeStatementItem, plus four new types:
|
|
8
|
+
# ni (net income -- optional, for when it is rolled up, versus (more typically) presented in the same detail as in the income statement)
|
|
9
|
+
# ooci (operating other comperhensive income)
|
|
10
|
+
# ooci_nci (operating other comperhensive income - non-controling interest)
|
|
11
|
+
# foci (financial other comperhensive income)
|
|
12
|
+
# unkoci (unknown (either operating or financial) other comperhensive income)
|
|
13
|
+
|
|
14
|
+
has_string_classifier(TYPES, ComprehensiveIncomeStatementItem)
|
|
15
|
+
|
|
16
|
+
def self.load_vectors_and_train
|
|
17
|
+
self._load_vectors_and_train(BASE_FILENAME, FinModeling::ComprehensiveIncomeStatementItem::TRAINING_VECTORS)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|