finmodeling 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/Gemfile +10 -0
- data/README.md +292 -0
- data/Rakefile +6 -0
- data/TODO.txt +36 -0
- data/examples/dump_report.rb +33 -0
- data/examples/lists/nasdaq-mid-to-mega-tech-symbols.txt +226 -0
- data/examples/show_report.rb +218 -0
- data/examples/show_reports.rb +77 -0
- data/finmodeling.gemspec +31 -0
- data/lib/finmodeling/annual_report_filing.rb +104 -0
- data/lib/finmodeling/array_with_stats.rb +22 -0
- data/lib/finmodeling/assets_calculation.rb +36 -0
- data/lib/finmodeling/assets_item.rb +14 -0
- data/lib/finmodeling/assets_item_vectors.rb +638 -0
- data/lib/finmodeling/balance_sheet_analyses.rb +33 -0
- data/lib/finmodeling/balance_sheet_calculation.rb +68 -0
- data/lib/finmodeling/calculation_summary.rb +148 -0
- data/lib/finmodeling/can_cache_classifications.rb +36 -0
- data/lib/finmodeling/can_cache_summaries.rb +16 -0
- data/lib/finmodeling/can_classify_rows.rb +54 -0
- data/lib/finmodeling/cash_change_calculation.rb +67 -0
- data/lib/finmodeling/cash_change_item.rb +14 -0
- data/lib/finmodeling/cash_change_item_vectors.rb +241 -0
- data/lib/finmodeling/cash_flow_statement_calculation.rb +85 -0
- data/lib/finmodeling/classifiers.rb +11 -0
- data/lib/finmodeling/company.rb +102 -0
- data/lib/finmodeling/company_filing.rb +64 -0
- data/lib/finmodeling/company_filing_calculation.rb +75 -0
- data/lib/finmodeling/company_filings.rb +100 -0
- data/lib/finmodeling/config.rb +37 -0
- data/lib/finmodeling/constant_forecasting_policy.rb +23 -0
- data/lib/finmodeling/factory.rb +27 -0
- data/lib/finmodeling/float_helpers.rb +17 -0
- data/lib/finmodeling/forecasts.rb +48 -0
- data/lib/finmodeling/generic_forecasting_policy.rb +19 -0
- data/lib/finmodeling/has_string_classifer.rb +96 -0
- data/lib/finmodeling/income_statement_analyses.rb +74 -0
- data/lib/finmodeling/income_statement_calculation.rb +71 -0
- data/lib/finmodeling/income_statement_item.rb +14 -0
- data/lib/finmodeling/income_statement_item_vectors.rb +654 -0
- data/lib/finmodeling/liabs_and_equity_calculation.rb +36 -0
- data/lib/finmodeling/liabs_and_equity_item.rb +14 -0
- data/lib/finmodeling/liabs_and_equity_item_vectors.rb +1936 -0
- data/lib/finmodeling/net_income_calculation.rb +41 -0
- data/lib/finmodeling/paths.rb +5 -0
- data/lib/finmodeling/period_array.rb +24 -0
- data/lib/finmodeling/quarterly_report_filing.rb +23 -0
- data/lib/finmodeling/rate.rb +20 -0
- data/lib/finmodeling/ratio.rb +20 -0
- data/lib/finmodeling/reformulated_balance_sheet.rb +176 -0
- data/lib/finmodeling/reformulated_cash_flow_statement.rb +140 -0
- data/lib/finmodeling/reformulated_income_statement.rb +436 -0
- data/lib/finmodeling/string_helpers.rb +26 -0
- data/lib/finmodeling/version.rb +3 -0
- data/lib/finmodeling.rb +70 -0
- data/spec/annual_report_filing_spec.rb +68 -0
- data/spec/assets_calculation_spec.rb +21 -0
- data/spec/assets_item_spec.rb +66 -0
- data/spec/balance_sheet_analyses_spec.rb +43 -0
- data/spec/balance_sheet_calculation_spec.rb +91 -0
- data/spec/calculation_summary_spec.rb +63 -0
- data/spec/can_classify_rows_spec.rb +86 -0
- data/spec/cash_change_calculation_spec.rb +56 -0
- data/spec/cash_change_item_spec.rb +66 -0
- data/spec/cash_flow_statement_calculation_spec.rb +108 -0
- data/spec/company_filing_calculation_spec.rb +74 -0
- data/spec/company_filing_spec.rb +30 -0
- data/spec/company_filings_spec.rb +55 -0
- data/spec/company_spec.rb +73 -0
- data/spec/constant_forecasting_policy_spec.rb +37 -0
- data/spec/factory_spec.rb +18 -0
- data/spec/forecasts_spec.rb +21 -0
- data/spec/generic_forecasting_policy_spec.rb +33 -0
- data/spec/income_statement_analyses_spec.rb +63 -0
- data/spec/income_statement_calculation_spec.rb +88 -0
- data/spec/income_statement_item_spec.rb +86 -0
- data/spec/liabs_and_equity_calculation_spec.rb +20 -0
- data/spec/liabs_and_equity_item_spec.rb +66 -0
- data/spec/mocks/calculation.rb +10 -0
- data/spec/mocks/income_statement_analyses.rb +93 -0
- data/spec/mocks/sec_query.rb +31 -0
- data/spec/net_income_calculation_spec.rb +23 -0
- data/spec/period_array.rb +52 -0
- data/spec/quarterly_report_filing_spec.rb +69 -0
- data/spec/rate_spec.rb +33 -0
- data/spec/ratio_spec.rb +33 -0
- data/spec/reformulated_balance_sheet_spec.rb +146 -0
- data/spec/reformulated_cash_flow_statement_spec.rb +174 -0
- data/spec/reformulated_income_statement_spec.rb +293 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/string_helpers_spec.rb +23 -0
- data/tools/create_balance_sheet_training_vectors.rb +65 -0
- data/tools/create_cash_change_training_vectors.rb +48 -0
- data/tools/create_credit_debit_training_vectors.rb +51 -0
- data/tools/create_income_statement_training_vectors.rb +48 -0
- metadata +289 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
@item = FinModeling::MultiColumnCalculationSummary.new
|
2
|
+
@item.title = ""
|
3
|
+
@item.num_value_columns = 9
|
4
|
+
@item.key_width = 18
|
5
|
+
@item.val_width = 12
|
6
|
+
@item.max_decimals = 4
|
7
|
+
@item.totals_row_enabled = false
|
8
|
+
args = { }
|
9
|
+
args[:key] = ""
|
10
|
+
args[:vals] = ["Unknown...", "2010-03-31", "2010-06-30", "2010-09-30", "2010-12-31", "2011-03-31", "2011-06-30", "2011-09-30", "2011-12-31"]
|
11
|
+
@item_header_row = FinModeling::MultiColumnCalculationSummaryHeaderRow.new(args)
|
12
|
+
@item.header_row = @item_header_row
|
13
|
+
args = { }
|
14
|
+
args[:key] = "Revenue (000's)"
|
15
|
+
args[:type] = ""
|
16
|
+
args[:vals] = [0, 1596960.0, 1601379.0, 1601203.0, 1525109.0, 1214357.0, 1229024.0, 1216665.0, 1324153.0]
|
17
|
+
@item_row0 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
|
18
|
+
args = { }
|
19
|
+
args[:key] = "Core OI (000's)"
|
20
|
+
args[:type] = ""
|
21
|
+
args[:vals] = [0, 171660.0, 117788.0, 173458.0, 230022.0, 146258.0, 133437.0, 126070.0, 178193.0]
|
22
|
+
@item_row1 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
|
23
|
+
args = { }
|
24
|
+
args[:key] = "OI (000's)"
|
25
|
+
args[:type] = ""
|
26
|
+
args[:vals] = [0, 168792.0, 111254.0, 169715.0, 205495.0, 139384.0, 133283.0, 127839.0, 167579.0]
|
27
|
+
@item_row2 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
|
28
|
+
args = { }
|
29
|
+
args[:key] = "FI (000's)"
|
30
|
+
args[:type] = ""
|
31
|
+
args[:vals] = [0, 141399.0, 102067.0, 226416.0, 106525.0, 83608.0, 103689.0, 165452.0, 127993.0]
|
32
|
+
@item_row3 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
|
33
|
+
args = { }
|
34
|
+
args[:key] = "NI (000's)"
|
35
|
+
args[:type] = ""
|
36
|
+
args[:vals] = [0, 310191.0, 213321.0, 396131.0, 312020.0, 222992.0, 236972.0, 293291.0, 295572.0]
|
37
|
+
@item_row4 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
|
38
|
+
args = { }
|
39
|
+
args[:key] = "Gross Margin"
|
40
|
+
args[:type] = ""
|
41
|
+
args[:vals] = [0, 0.5576708245666767, 0.5736661964469373, 0.5748484108510913, 0.6343297429888618, 0.689167188890911, 0.6980083383237431, 0.7047042530195247, 0.7018796166304045]
|
42
|
+
@item_row5 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
|
43
|
+
args = { }
|
44
|
+
args[:key] = "Sales PM"
|
45
|
+
args[:type] = ""
|
46
|
+
args[:vals] = [0, 0.10749148381925659, 0.07355385577055774, 0.10832951849328286, 0.15082361326305202, 0.12044085882487604, 0.10857147622829172, 0.10361968988998616, 0.13457104277224763]
|
47
|
+
@item_row6 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
|
48
|
+
args = { }
|
49
|
+
args[:key] = "Operating PM"
|
50
|
+
args[:type] = ""
|
51
|
+
args[:vals] = [0, 0.10569569682396554, 0.06947374731403372, 0.10599208844849779, 0.13474099228317452, 0.1147804558297107, 0.10844613286640456, 0.10507337681284495, 0.12655546602243095]
|
52
|
+
@item_row7 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
|
53
|
+
args = { }
|
54
|
+
args[:key] = "FI / Sales"
|
55
|
+
args[:type] = ""
|
56
|
+
args[:vals] = [0, 0.0885427311892596, 0.06373706661571059, 0.14140377578608085, 0.06984766334734108, 0.06884923461552081, 0.08436702619314188, 0.13598804929869768, 0.09666043123415496]
|
57
|
+
@item_row8 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
|
58
|
+
args = { }
|
59
|
+
args[:key] = "NI / Sales"
|
60
|
+
args[:type] = ""
|
61
|
+
args[:vals] = [0, 0.19423842801322513, 0.1332108139297443, 0.24739586423457863, 0.2045886556305156, 0.1836296904452315, 0.19281315905954644, 0.24106142611154263, 0.22321589725658592]
|
62
|
+
@item_row9 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
|
63
|
+
args = { }
|
64
|
+
args[:key] = "Sales / NOA"
|
65
|
+
args[:type] = ""
|
66
|
+
args[:vals] = [0, 0, 0.31589471931674, 0.3027171516963245, 0.2812087090658527, 0.22230803971150923, 0.21556306903197936, 0.2058721704664517, 0.22777909563225565]
|
67
|
+
@item_row10 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
|
68
|
+
args = { }
|
69
|
+
args[:key] = "FI / NFA"
|
70
|
+
args[:type] = ""
|
71
|
+
args[:vals] = [0, 0, 0.01388443269851155, 0.03307811869055056, 0.01601019419285102, 0.011782960277534123, 0.014556844204896556, 0.0241571697495845, 0.019256398729732317]
|
72
|
+
@item_row11 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
|
73
|
+
args = { }
|
74
|
+
args[:key] = "Revenue Growth"
|
75
|
+
args[:type] = ""
|
76
|
+
args[:vals] = [0, 0, 0.01114526118072745, -0.00043596613617347124, -0.17565789513442365, -0.6030968787992619, 0.04933275376563051, -0.03930454685490847, 0.39916742999669985]
|
77
|
+
@item_row12 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
|
78
|
+
args = { }
|
79
|
+
args[:key] = "Core OI Growth"
|
80
|
+
args[:type] = ""
|
81
|
+
args[:vals] = [0, 0, -0.7792359004477285, 3.644010853978843, 2.0641568409990847, -0.8406048886193428, -0.30787317437309647, -0.20172411964684556, 2.9464409948934227]
|
82
|
+
@item_row13 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
|
83
|
+
args = { }
|
84
|
+
args[:key] = "OI Growth"
|
85
|
+
args[:type] = ""
|
86
|
+
args[:vals] = [0, 0, -0.8121268899268889, 4.341206465523186, 1.136062554145436, -0.7928481118131626, -0.16434540325239466, -0.1524844979682214, 1.926767066125802]
|
87
|
+
@item_row14 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
|
88
|
+
args = { }
|
89
|
+
args[:key] = "ReOI (000's)"
|
90
|
+
args[:type] = ""
|
91
|
+
args[:vals] = [0, 0, -10648.0, 41106.0, 73629.0, 9489.0, -3819.0, -15854.0, 26232.0]
|
92
|
+
@item_row15 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
|
93
|
+
@item.rows = [@item_row0,@item_row1,@item_row2,@item_row3,@item_row4,@item_row5,@item_row6,@item_row7,@item_row8,@item_row9,@item_row10,@item_row11,@item_row12,@item_row13,@item_row14,@item_row15]
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module FinModeling
|
2
|
+
module Mocks
|
3
|
+
class Filing_10K
|
4
|
+
attr_accessor :term, :date, :link
|
5
|
+
def initialize
|
6
|
+
@term="10-K"
|
7
|
+
@date="1994-01-26T00:00:00-05:00"
|
8
|
+
@link="http://www.sec.gov/Archives/edgar/data/320193/000119312512023398/0001193125-12-023398-index.htm"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Filing_10Q
|
13
|
+
attr_accessor :term, :date, :link
|
14
|
+
def initialize
|
15
|
+
@term="10-Q"
|
16
|
+
@date="1995-01-26T00:00:00-05:00"
|
17
|
+
@link="http://www.sec.gov/Archives/edgar/data/1288776/000119312511282235/0001193125-11-282235-index.htm"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Entity
|
22
|
+
attr_accessor :name, :filings
|
23
|
+
def initialize
|
24
|
+
@name = "Apple Inc"
|
25
|
+
@filings = []
|
26
|
+
@filings.push Mocks::Filing_10K.new
|
27
|
+
@filings.push Mocks::Filing_10Q.new
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# income_statement_calculation_spec.rb
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe FinModeling::NetIncomeCalculation do
|
6
|
+
before(:all) do
|
7
|
+
google_2011_annual_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312512025336/0001193125-12-025336-index.htm"
|
8
|
+
filing = FinModeling::AnnualReportFiling.download google_2011_annual_rpt
|
9
|
+
@inc_stmt = filing.income_statement
|
10
|
+
@period = @inc_stmt.periods.last
|
11
|
+
@ni = @inc_stmt.net_income_calculation
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "summary" do
|
15
|
+
it "only requires a period (knows how debts/credits work and whether to flip the total)" do
|
16
|
+
@ni.summary(:period=>@period).should be_an_instance_of FinModeling::CalculationSummary
|
17
|
+
end
|
18
|
+
it "tags each row with an Income Statement Type" do
|
19
|
+
FinModeling::IncomeStatementItem::TYPES.include?(@ni.summary(:period=>@period).rows.first.type).should be_true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# period_array_spec.rb
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe FinModeling::PeriodArray do
|
6
|
+
before(:all) do
|
7
|
+
@t_now = Date.parse('2012-01-01')
|
8
|
+
@t_3mo_ago = Date.parse('2011-09-01')
|
9
|
+
@t_6mo_ago = Date.parse('2011-06-01')
|
10
|
+
@t_0mo_ago = Date.parse('2011-03-01')
|
11
|
+
@t_1yr_ago = Date.parse('2011-01-01')
|
12
|
+
|
13
|
+
@arr = FinModeling::PeriodArray.new
|
14
|
+
@arr.push Xbrlware::Context::Period.new({"start_date"=>@t_1yr_ago, "end_date"=>@t_now}) # 1 yr
|
15
|
+
@arr.push Xbrlware::Context::Period.new({"start_date"=>@t_1yr_ago, "end_date"=>@t_3mo_ago}) # 9 mo
|
16
|
+
@arr.push Xbrlware::Context::Period.new({"start_date"=>@t_1yr_ago, "end_date"=>@t_6mo_ago}) # 6 mo
|
17
|
+
@arr.push Xbrlware::Context::Period.new({"start_date"=>@t_3mo_ago, "end_date"=>@t_now}) # 3 mo
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "yearly" do
|
21
|
+
subject { @arr.yearly }
|
22
|
+
it { should be_an_instance_of FinModeling::PeriodArray }
|
23
|
+
it "returns only annual periods" do
|
24
|
+
subject.first.to_pretty_s.should == @arr[0].to_pretty_s
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "threequarterly" do
|
29
|
+
subject { @arr.threequarterly }
|
30
|
+
it { should be_an_instance_of FinModeling::PeriodArray }
|
31
|
+
it "returns only three-quarter periods" do
|
32
|
+
subject.first.to_pretty_s.should == @arr[1].to_pretty_s
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "halfyearly" do
|
37
|
+
subject { @arr.halfyearly }
|
38
|
+
it { should be_an_instance_of FinModeling::PeriodArray }
|
39
|
+
it "returns only two-quarter periods" do
|
40
|
+
subject.first.to_pretty_s.should == @arr[2].to_pretty_s
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "quarterly" do
|
45
|
+
subject { @arr.quarterly }
|
46
|
+
it { should be_an_instance_of FinModeling::PeriodArray }
|
47
|
+
it "returns only quarterly periods" do
|
48
|
+
subject.first.to_pretty_s.should == @arr[3].to_pretty_s
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# quarterly_report_filing_spec.rb
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe FinModeling::QuarterlyReportFiling do
|
6
|
+
before(:all) do
|
7
|
+
company = FinModeling::Company.new(FinModeling::Mocks::Entity.new)
|
8
|
+
filing_url = company.quarterly_reports.last.link
|
9
|
+
FinModeling::Config::disable_caching
|
10
|
+
@filing = FinModeling::QuarterlyReportFiling.download(filing_url)
|
11
|
+
end
|
12
|
+
|
13
|
+
after(:all) do
|
14
|
+
FinModeling::Config::enable_caching
|
15
|
+
end
|
16
|
+
|
17
|
+
subject { @filing }
|
18
|
+
its(:balance_sheet) { should be_a FinModeling::BalanceSheetCalculation }
|
19
|
+
its(:income_statement) { should be_a FinModeling::IncomeStatementCalculation }
|
20
|
+
its(:cash_flow_statement) { should be_a FinModeling::CashFlowStatementCalculation }
|
21
|
+
|
22
|
+
its(:is_valid?) { should == (@filing.income_statement.is_valid? && @filing.balance_sheet.is_valid? && @filing.cash_flow_statement.is_valid?) }
|
23
|
+
|
24
|
+
describe "write_constructor" do
|
25
|
+
before(:all) do
|
26
|
+
file_name = "/tmp/finmodeling-quarterly-rpt.rb"
|
27
|
+
schema_version_item_name = "@schema_version"
|
28
|
+
item_name = "@quarterly_rpt"
|
29
|
+
file = File.open(file_name, "w")
|
30
|
+
@filing.write_constructor(file, item_name)
|
31
|
+
file.close
|
32
|
+
|
33
|
+
eval(File.read(file_name))
|
34
|
+
|
35
|
+
@schema_version = eval(schema_version_item_name)
|
36
|
+
@loaded_filing = eval(item_name)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "writes itself to a file, and saves a schema version of 1.1" do
|
40
|
+
@schema_version.should be == 1.1
|
41
|
+
end
|
42
|
+
|
43
|
+
it "writes itself to a file, and when reloaded, has the same periods" do
|
44
|
+
expected_periods = @filing.balance_sheet.periods.map{|x| x.to_pretty_s}.join(',')
|
45
|
+
@loaded_filing.balance_sheet.periods.map{|x| x.to_pretty_s}.join(',').should == expected_periods
|
46
|
+
end
|
47
|
+
it "writes itself to a file, and when reloaded, has the same net operating assets" do
|
48
|
+
period = @filing.balance_sheet.periods.last
|
49
|
+
expected_noa = @filing.balance_sheet.reformulated(period).net_operating_assets.total
|
50
|
+
@loaded_filing.balance_sheet.reformulated(period).net_operating_assets.total.should be_within(1.0).of(expected_noa)
|
51
|
+
end
|
52
|
+
it "writes itself to a file, and when reloaded, has the same net financing income" do
|
53
|
+
period = @filing.income_statement.periods.last
|
54
|
+
expected_nfi = @filing.income_statement.reformulated(period).net_financing_income.total
|
55
|
+
@loaded_filing.income_statement.reformulated(period).net_financing_income.total.should be_within(1.0).of(expected_nfi)
|
56
|
+
end
|
57
|
+
it "writes itself to a file, and when reloaded, has the same net change in cash" do
|
58
|
+
period = @filing.cash_flow_statement.periods.last
|
59
|
+
expected_cash_change = @filing.cash_flow_statement.cash_change_calculation.summary(:period=>period).total
|
60
|
+
@loaded_filing.cash_flow_statement.cash_change_calculation.summary(:period=>period).total.should be_within(1.0).of(expected_cash_change)
|
61
|
+
end
|
62
|
+
it "writes itself to a file, and when reloaded, has the same disclosures" do
|
63
|
+
period = @filing.disclosures.first.periods.last
|
64
|
+
expected_total = @filing.disclosures.first.summary(:period=>period).total
|
65
|
+
@loaded_filing.disclosures.first.summary(:period=>period).total.should == expected_total
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
data/spec/rate_spec.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# rate_spec.rb
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe FinModeling::Rate do
|
6
|
+
describe ".annualize" do
|
7
|
+
let(:val) { 0.2 }
|
8
|
+
context "when annualizing from a quarter to a year" do
|
9
|
+
subject { FinModeling::Rate.new(val).annualize(from_days=365.0/4.0, to_days=365.0) }
|
10
|
+
it { should be_a_kind_of Float }
|
11
|
+
it { should be_within(0.0001).of( (val+1.0)**(4.00) - 1.0 ) }
|
12
|
+
end
|
13
|
+
context "when annualizing from a year to a quarter" do
|
14
|
+
subject { FinModeling::Rate.new(val).annualize(from_days=365.0, to_days=365.0/4.0) }
|
15
|
+
it { should be_a_kind_of Float }
|
16
|
+
it { should be_within(0.0001).of( (val+1.0)**(0.25) - 1.0 ) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe ".yearly_to_quarterly" do
|
21
|
+
let(:val) { 0.2 }
|
22
|
+
subject { FinModeling::Rate.new(val).yearly_to_quarterly }
|
23
|
+
it { should be_a_kind_of Float }
|
24
|
+
it { should be_within(0.0001).of( FinModeling::Rate.new(val).annualize(from_days=365.0, to_days=365.0/4.0) ) }
|
25
|
+
end
|
26
|
+
|
27
|
+
describe ".quarterly_to_yearly" do
|
28
|
+
let(:val) { 0.2 }
|
29
|
+
subject { FinModeling::Rate.new(val).quarterly_to_yearly }
|
30
|
+
it { should be_a_kind_of Float }
|
31
|
+
it { should be_within(0.0001).of( FinModeling::Rate.new(val).annualize(from_days=365.0/4.0, to_days=365.0) ) }
|
32
|
+
end
|
33
|
+
end
|
data/spec/ratio_spec.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# rate_spec.rb
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe FinModeling::Ratio do
|
6
|
+
describe ".annualize" do
|
7
|
+
let(:val) { 0.2 }
|
8
|
+
context "when annualizing from a quarter to a year" do
|
9
|
+
subject { FinModeling::Ratio.new(val).annualize(from_days=365.0/4.0, to_days=365.0) }
|
10
|
+
it { should be_a_kind_of Float }
|
11
|
+
it { should be_within(0.0001).of(val * 4.0) }
|
12
|
+
end
|
13
|
+
context "when annualizing from a year to a quarter" do
|
14
|
+
subject { FinModeling::Ratio.new(val).annualize(from_days=365.0, to_days=365.0/4.0) }
|
15
|
+
it { should be_a_kind_of Float }
|
16
|
+
it { should be_within(0.0001).of(val / 4.0) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe ".yearly_to_quarterly" do
|
21
|
+
let(:val) { 0.2 }
|
22
|
+
subject { FinModeling::Ratio.new(val).yearly_to_quarterly }
|
23
|
+
it { should be_a_kind_of Float }
|
24
|
+
it { should be_within(0.0001).of( FinModeling::Ratio.new(val).annualize(from_days=365.0, to_days=365.0/4.0) ) }
|
25
|
+
end
|
26
|
+
|
27
|
+
describe ".quarterly_to_yearly" do
|
28
|
+
let(:val) { 0.2 }
|
29
|
+
subject { FinModeling::Ratio.new(val).quarterly_to_yearly }
|
30
|
+
it { should be_a_kind_of Float }
|
31
|
+
it { should be_within(0.0001).of( FinModeling::Ratio.new(val).annualize(from_days=365.0/4.0, to_days=365.0) ) }
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# reformulated_income_statement_spec.rb
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe FinModeling::ReformulatedBalanceSheet do
|
6
|
+
before(:all) do
|
7
|
+
google_2010_annual_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312510030774/0001193125-10-030774-index.htm"
|
8
|
+
filing = FinModeling::AnnualReportFiling.download google_2010_annual_rpt
|
9
|
+
@bal_sheet= filing.balance_sheet
|
10
|
+
|
11
|
+
@period = @bal_sheet.periods.last
|
12
|
+
@prev_reformed_bal_sheet = @bal_sheet.reformulated(@period)
|
13
|
+
|
14
|
+
google_2011_annual_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312512025336/0001193125-12-025336-index.htm"
|
15
|
+
filing = FinModeling::AnnualReportFiling.download google_2011_annual_rpt
|
16
|
+
@bal_sheet = filing.balance_sheet
|
17
|
+
@period = @bal_sheet.periods.last
|
18
|
+
@reformed_bal_sheet = @bal_sheet.reformulated(@period)
|
19
|
+
|
20
|
+
@years_between_sheets = 2.0
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "new" do
|
24
|
+
it "takes an assets calculation and a liabs_and_equity calculation and a period and returns a CalculationSummary" do
|
25
|
+
rbs = FinModeling::ReformulatedBalanceSheet.new(@period, @bal_sheet.assets_calculation.summary(:period=>@period), @bal_sheet.liabs_and_equity_calculation.summary(:period=>@period))
|
26
|
+
rbs.should be_an_instance_of FinModeling::ReformulatedBalanceSheet
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "operating_assets" do
|
31
|
+
subject { @reformed_bal_sheet.operating_assets }
|
32
|
+
it { should be_an_instance_of FinModeling::CalculationSummary }
|
33
|
+
its(:total) { should be_within(0.1).of(26943000000.0) }
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "financial_assets" do
|
37
|
+
subject { @reformed_bal_sheet.financial_assets }
|
38
|
+
it { should be_an_instance_of FinModeling::CalculationSummary }
|
39
|
+
its(:total) { should be_within(0.1).of(45631000000.0) }
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "operating_liabilities" do
|
43
|
+
subject { @reformed_bal_sheet.operating_liabilities }
|
44
|
+
it { should be_an_instance_of FinModeling::CalculationSummary }
|
45
|
+
its(:total) { should be_within(0.1).of(6041000000.0) }
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "financial_liabilities" do
|
49
|
+
subject { @reformed_bal_sheet.financial_liabilities }
|
50
|
+
it { should be_an_instance_of FinModeling::CalculationSummary }
|
51
|
+
its(:total) { should be_within(0.1).of(8388000000.0) }
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "net_operating_assets" do
|
55
|
+
subject { @reformed_bal_sheet.net_operating_assets }
|
56
|
+
it { should be_an_instance_of FinModeling::CalculationSummary }
|
57
|
+
its(:total) { should be_within(0.1).of(20902000000.0) }
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "net_financial_assets" do
|
61
|
+
subject { @reformed_bal_sheet.net_financial_assets }
|
62
|
+
it { should be_an_instance_of FinModeling::CalculationSummary }
|
63
|
+
its(:total) { should be_within(0.1).of(37243000000.0) }
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "common_shareholders_equity" do
|
67
|
+
subject { @reformed_bal_sheet.common_shareholders_equity }
|
68
|
+
it { should be_an_instance_of FinModeling::CalculationSummary }
|
69
|
+
its(:total) { should be_within(0.1).of(58145000000.0) }
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "composition_ratio" do
|
73
|
+
subject { @reformed_bal_sheet.composition_ratio }
|
74
|
+
it { should be_within(0.1).of(@reformed_bal_sheet.net_operating_assets.total / @reformed_bal_sheet.net_financial_assets.total) }
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "noa_growth" do
|
78
|
+
subject { @reformed_bal_sheet.noa_growth(@prev_reformed_bal_sheet) }
|
79
|
+
let(:noa0) { @prev_reformed_bal_sheet.net_operating_assets.total }
|
80
|
+
let(:noa1) { @reformed_bal_sheet.net_operating_assets.total }
|
81
|
+
let(:expected_growth) { FinModeling::Rate.new((noa1-noa0) / noa0).annualize(from=365.0*@years_between_sheets, to=365.0) }
|
82
|
+
it { should be_within(0.001).of(expected_growth) }
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "cse_growth" do
|
86
|
+
subject { @reformed_bal_sheet.cse_growth(@prev_reformed_bal_sheet) }
|
87
|
+
let(:cse0) { @prev_reformed_bal_sheet.common_shareholders_equity.total }
|
88
|
+
let(:cse1) { @reformed_bal_sheet.common_shareholders_equity.total }
|
89
|
+
let(:expected_growth) { FinModeling::Rate.new((cse1-cse0) / cse0).annualize(from=365.0*@years_between_sheets, to=365.0) }
|
90
|
+
it { should be_within(0.001).of(expected_growth) }
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "analysis" do
|
94
|
+
subject {@reformed_bal_sheet.analysis(@prev_reformed_bal_sheet) }
|
95
|
+
it { should be_an_instance_of FinModeling::CalculationSummary }
|
96
|
+
it "contains the expected rows" do
|
97
|
+
expected_keys = ["NOA ($MM)", "NFA ($MM)", "CSE ($MM)",
|
98
|
+
"Composition Ratio", "NOA Growth", "CSE Growth" ]
|
99
|
+
|
100
|
+
subject.rows.map{ |row| row.key }.should == expected_keys
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "#forecast_next" do
|
105
|
+
before (:all) do
|
106
|
+
@company = FinModeling::Company.find("aapl")
|
107
|
+
@filings = FinModeling::CompanyFilings.new(@company.filings_since_date(Time.parse("2010-10-01")))
|
108
|
+
@policy = FinModeling::GenericForecastingPolicy.new
|
109
|
+
|
110
|
+
prev_bs_period = @filings.last.balance_sheet.periods.last
|
111
|
+
next_bs_period_value = prev_bs_period.value.next_month.next_month.next_month
|
112
|
+
@next_bs_period = Xbrlware::Context::Period.new(next_bs_period_value)
|
113
|
+
|
114
|
+
next_is_period_value = {"start_date" => prev_bs_period.value,
|
115
|
+
"end_date" => prev_bs_period.value.next_month.next_month.next_month }
|
116
|
+
@next_is_period = Xbrlware::Context::Period.new(next_is_period_value)
|
117
|
+
end
|
118
|
+
|
119
|
+
let(:last_re_is) { @filings.last.income_statement.latest_quarterly_reformulated(nil) }
|
120
|
+
let(:last_re_bs) { @filings.last.balance_sheet.reformulated(@filings.last.balance_sheet.periods.last) }
|
121
|
+
let(:next_re_is) { FinModeling::ReformulatedIncomeStatement.forecast_next(@next_is_period, @policy, last_re_bs, last_re_is) }
|
122
|
+
|
123
|
+
subject { FinModeling::ReformulatedBalanceSheet.forecast_next(@next_bs_period, @policy, last_re_bs, next_re_is) }
|
124
|
+
|
125
|
+
it { should be_a_kind_of FinModeling::ReformulatedBalanceSheet }
|
126
|
+
it "should have the given period" do
|
127
|
+
subject.period.to_pretty_s == @next_bs_period.to_pretty_s
|
128
|
+
end
|
129
|
+
it "should set NOA to the same period's operating revenue over the policy's asset turnover" do
|
130
|
+
expected_val = next_re_is.operating_revenues.total / FinModeling::Ratio.new(@policy.sales_over_noa).yearly_to_quarterly
|
131
|
+
subject.net_operating_assets.total.should == expected_val
|
132
|
+
end
|
133
|
+
it "should set CSE to last year's CSE plus this year's net income" do
|
134
|
+
expected_val = last_re_bs.common_shareholders_equity.total + next_re_is.comprehensive_income.total
|
135
|
+
subject.common_shareholders_equity.total.should == expected_val
|
136
|
+
end
|
137
|
+
it "should set NFA to the gap between CSE and NOA" do
|
138
|
+
expected_val = subject.common_shareholders_equity.total - subject.net_operating_assets.total
|
139
|
+
subject.net_financial_assets.total.should == expected_val
|
140
|
+
end
|
141
|
+
it "should have an analysis (with the same rows)" do
|
142
|
+
subject.analysis(last_re_bs)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# reformulated_income_statement_spec.rb
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe FinModeling::ReformulatedCashFlowStatement do
|
6
|
+
before(:all) do
|
7
|
+
google_2009_annual_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312510030774/0001193125-10-030774-index.htm"
|
8
|
+
filing_2009 = FinModeling::AnnualReportFiling.download google_2009_annual_rpt
|
9
|
+
cash_flow_stmt_2009 = filing_2009.cash_flow_statement
|
10
|
+
period_2009 = cash_flow_stmt_2009.periods.last
|
11
|
+
@reformed_cash_flow_stmt_2009 = cash_flow_stmt_2009.reformulated(period_2009)
|
12
|
+
|
13
|
+
google_2011_annual_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312512025336/0001193125-12-025336-index.htm"
|
14
|
+
filing = FinModeling::AnnualReportFiling.download google_2011_annual_rpt
|
15
|
+
|
16
|
+
@inc_stmt = filing.income_statement
|
17
|
+
@is_period = @inc_stmt.periods.last
|
18
|
+
@reformed_inc_stmt = @inc_stmt.reformulated(@is_period)
|
19
|
+
|
20
|
+
@cash_flow_stmt = filing.cash_flow_statement
|
21
|
+
@period = @cash_flow_stmt.periods.last
|
22
|
+
@reformed_cash_flow_stmt = @cash_flow_stmt.reformulated(@period)
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "new" do
|
26
|
+
it "takes a cash change calculation and a period and returns a CalculationSummary" do
|
27
|
+
rcfs = FinModeling::ReformulatedCashFlowStatement.new(@period, @cash_flow_stmt.cash_change_calculation.summary(:period=>@period))
|
28
|
+
rcfs.should be_an_instance_of FinModeling::ReformulatedCashFlowStatement
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
subject { @reformed_cash_flow_stmt }
|
33
|
+
|
34
|
+
describe "cash_from_operations" do
|
35
|
+
subject { @reformed_cash_flow_stmt.cash_from_operations }
|
36
|
+
it { should be_an_instance_of FinModeling::CalculationSummary }
|
37
|
+
it "totals up the values of rows with type :c" do
|
38
|
+
sum = @cash_flow_stmt.cash_change_calculation
|
39
|
+
.summary(:period=>@period)
|
40
|
+
.rows.select{ |row| row.type == :c }
|
41
|
+
.map{ |row| row.vals.first }
|
42
|
+
.inject(:+)
|
43
|
+
subject.total.should == sum
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "cash_investments_in_operations" do
|
48
|
+
subject { @reformed_cash_flow_stmt.cash_investments_in_operations }
|
49
|
+
it { should be_an_instance_of FinModeling::CalculationSummary }
|
50
|
+
it "totals up the values of rows with type :i" do
|
51
|
+
sum = @cash_flow_stmt.cash_change_calculation
|
52
|
+
.summary(:period=>@period)
|
53
|
+
.rows.select{ |row| row.type == :i }
|
54
|
+
.map{ |row| row.vals.first }
|
55
|
+
.inject(:+)
|
56
|
+
subject.total.should == sum
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "payments_to_debtholders" do
|
61
|
+
subject { @reformed_cash_flow_stmt.payments_to_debtholders }
|
62
|
+
it { should be_an_instance_of FinModeling::CalculationSummary }
|
63
|
+
it "totals up the values of rows with type :d, minus the total change in cash" do
|
64
|
+
sum = @cash_flow_stmt.cash_change_calculation
|
65
|
+
.summary(:period=>@period)
|
66
|
+
.rows.select{ |row| row.type == :d }
|
67
|
+
.map{ |row| row.vals.first }
|
68
|
+
.inject(:+)
|
69
|
+
sum = sum - @cash_flow_stmt.cash_change_calculation
|
70
|
+
.summary(:period=>@period)
|
71
|
+
.total
|
72
|
+
subject.total.should == sum
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "payments_to_stockholders" do
|
77
|
+
subject { @reformed_cash_flow_stmt.payments_to_stockholders }
|
78
|
+
it { should be_an_instance_of FinModeling::CalculationSummary }
|
79
|
+
it "totals up the values of rows with type :f" do
|
80
|
+
sum = @cash_flow_stmt.cash_change_calculation
|
81
|
+
.summary(:period=>@period)
|
82
|
+
.rows.select{ |row| row.type == :f }
|
83
|
+
.map{ |row| row.vals.first }
|
84
|
+
.inject(:+)
|
85
|
+
subject.total.should == sum
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "free_cash_flow" do
|
90
|
+
subject { @reformed_cash_flow_stmt.free_cash_flow }
|
91
|
+
it { should be_an_instance_of FinModeling::CalculationSummary }
|
92
|
+
it "totals up cash from operations and cash investments in operations" do
|
93
|
+
sum = @reformed_cash_flow_stmt.cash_from_operations.total
|
94
|
+
sum = sum + @reformed_cash_flow_stmt.cash_investments_in_operations.total
|
95
|
+
subject.total.should == sum
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "ni_over_c" do
|
100
|
+
subject { @reformed_cash_flow_stmt.ni_over_c(@reformed_inc_stmt) }
|
101
|
+
it { should be_an_instance_of Float }
|
102
|
+
it { should be_within(0.1).of(@reformed_inc_stmt.comprehensive_income.total / @reformed_cash_flow_stmt.cash_from_operations.total) }
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "financing_flows" do
|
106
|
+
subject { @reformed_cash_flow_stmt.financing_flows }
|
107
|
+
it { should be_an_instance_of FinModeling::CalculationSummary }
|
108
|
+
it "totals up payments to both debtholders and stockholders" do
|
109
|
+
sum = @reformed_cash_flow_stmt.payments_to_debtholders.total
|
110
|
+
sum = sum + @reformed_cash_flow_stmt.payments_to_stockholders.total
|
111
|
+
subject.total.should == sum
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "analysis" do
|
116
|
+
subject { @reformed_cash_flow_stmt.analysis(@reformed_inc_stmt) }
|
117
|
+
|
118
|
+
it { should be_an_instance_of FinModeling::CalculationSummary }
|
119
|
+
it "contains the expected rows" do
|
120
|
+
expected_keys = [ "C ($MM)", "I ($MM)", "d ($MM)", "F ($MM)", "FCF ($MM)", "NI / C" ]
|
121
|
+
subject.rows.map{ |row| row.key }.should == expected_keys
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "-" do
|
126
|
+
before(:all) do
|
127
|
+
google_2011_q3_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312511282235/0001193125-11-282235-index.htm"
|
128
|
+
@filing_2011_q3 = FinModeling::AnnualReportFiling.download google_2011_q3_rpt
|
129
|
+
|
130
|
+
@cash_flow_stmt_2011_q3 = @filing_2011_q3.cash_flow_statement
|
131
|
+
cfs_period_2011_q3 = @cash_flow_stmt_2011_q3.periods.threequarterly.last
|
132
|
+
@reformed_cash_flow_stmt_2011_q3 = @cash_flow_stmt_2011_q3.reformulated(cfs_period_2011_q3)
|
133
|
+
|
134
|
+
@diff = @reformed_cash_flow_stmt - @reformed_cash_flow_stmt_2011_q3
|
135
|
+
end
|
136
|
+
subject { @diff }
|
137
|
+
|
138
|
+
it { should be_an_instance_of FinModeling::ReformulatedCashFlowStatement }
|
139
|
+
its(:period) { should_not be_nil } # FIXME
|
140
|
+
|
141
|
+
it "returns the difference between the two re_cfs's for each calculation" do
|
142
|
+
methods = [ :cash_from_operations, :cash_investments_in_operations,
|
143
|
+
:payments_to_debtholders, :payments_to_stockholders,
|
144
|
+
:free_cash_flow, :financing_flows ]
|
145
|
+
methods.each do |method|
|
146
|
+
expected_val = @reformed_cash_flow_stmt.send(method).total - @reformed_cash_flow_stmt_2011_q3.send(method).total
|
147
|
+
@diff.send(method).total.should be_within(1.0).of(expected_val)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
it "returns values that are close to 1/4th of the annual value" do
|
152
|
+
methods = [ :cash_from_operations, :cash_investments_in_operations,
|
153
|
+
#:payments_to_debtholders, :payments_to_stockholders,
|
154
|
+
:free_cash_flow, :financing_flows ]
|
155
|
+
|
156
|
+
methods.each do |method|
|
157
|
+
orig = @reformed_cash_flow_stmt.send(method).total
|
158
|
+
max = (orig > 0) ? (0.35 * orig) : (0.15 * orig)
|
159
|
+
min = (orig > 0) ? (0.15 * orig) : (0.35 * orig)
|
160
|
+
actual = @diff.send(method).total
|
161
|
+
if (actual < min) || (actual > max)
|
162
|
+
err = "#{method} returns #{actual.to_nearest_thousand.to_s.with_thousands_separators}, "
|
163
|
+
err += "which is outside bounds: [#{min.to_nearest_thousand.to_s.with_thousands_separators}, "
|
164
|
+
err += "#{max.to_nearest_thousand.to_s.with_thousands_separators}]"
|
165
|
+
puts err
|
166
|
+
end
|
167
|
+
@diff.send(method).total.should be > min
|
168
|
+
@diff.send(method).total.should be < max
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
|