finmodeling 0.1
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 +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,30 @@
|
|
|
1
|
+
# company_filing_spec.rb
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe FinModeling::CompanyFiling do
|
|
6
|
+
let(:company) { FinModeling::Company.new(FinModeling::Mocks::Entity.new) }
|
|
7
|
+
let(:filing_url) { company.annual_reports.last.link }
|
|
8
|
+
|
|
9
|
+
subject { FinModeling::CompanyFiling.download filing_url }
|
|
10
|
+
|
|
11
|
+
describe "#download" do
|
|
12
|
+
it { should be_a FinModeling::CompanyFiling }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe ".print_presentations" do
|
|
16
|
+
it { should respond_to(:print_presentations) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe ".print_calculations" do
|
|
20
|
+
it { should respond_to(:print_calculations) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe ".disclosures" do
|
|
24
|
+
subject { (FinModeling::CompanyFiling.download filing_url).disclosures }
|
|
25
|
+
|
|
26
|
+
it { should be_an_instance_of Array }
|
|
27
|
+
it { should_not be_empty }
|
|
28
|
+
its(:first) { should be_a FinModeling::CompanyFilingCalculation }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# company_filings_spec.rb
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe FinModeling::CompanyFiling do
|
|
6
|
+
before (:all) do
|
|
7
|
+
@company = FinModeling::Company.find("aapl")
|
|
8
|
+
@filings = FinModeling::CompanyFilings.new(@company.filings_since_date(Time.parse("2010-10-01")))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
describe "balance_sheet_analyses" do
|
|
12
|
+
subject { @filings.balance_sheet_analyses }
|
|
13
|
+
it { should be_an_instance_of FinModeling::BalanceSheetAnalyses }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe "cash_flow_statement_analyses" do
|
|
17
|
+
subject { @filings.cash_flow_statement_analyses }
|
|
18
|
+
it { should be_an_instance_of FinModeling::CalculationSummary } # FIXME: model this guy the same way...
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe "income_statement_analyses" do
|
|
22
|
+
subject { @filings.income_statement_analyses }
|
|
23
|
+
it { should be_an_instance_of FinModeling::IncomeStatementAnalyses }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe "choose_forecasting_policy" do
|
|
27
|
+
context "when one or two filings" do
|
|
28
|
+
let(:filings) { FinModeling::CompanyFilings.new(@filings[-2..-1]) }
|
|
29
|
+
subject { filings.choose_forecasting_policy }
|
|
30
|
+
|
|
31
|
+
it { should be_an_instance_of FinModeling::GenericForecastingPolicy }
|
|
32
|
+
end
|
|
33
|
+
context "when two or more filings" do
|
|
34
|
+
let(:filings) { FinModeling::CompanyFilings.new(@filings[-3..-1]) }
|
|
35
|
+
subject { filings.choose_forecasting_policy }
|
|
36
|
+
it { should be_an_instance_of FinModeling::ConstantForecastingPolicy }
|
|
37
|
+
|
|
38
|
+
let(:isa) { filings.income_statement_analyses }
|
|
39
|
+
|
|
40
|
+
its(:revenue_growth) { should be_within(0.01).of(isa.revenue_growth_row.valid_vals.mean) }
|
|
41
|
+
its(:sales_pm) { should be_within(0.01).of(isa.operating_pm_row.valid_vals.mean) } # FIXME: name mismatch
|
|
42
|
+
its(:sales_over_noa) { should be_within(0.01).of(isa.sales_over_noa_row.valid_vals.mean) }
|
|
43
|
+
its(:fi_over_nfa) { should be_within(0.01).of(isa.fi_over_nfa_row.valid_vals.mean) }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe "forecasts" do
|
|
48
|
+
let(:policy) { @filings.choose_forecasting_policy }
|
|
49
|
+
let(:num_quarters) { 3 }
|
|
50
|
+
subject { @filings.forecasts(policy, num_quarters) }
|
|
51
|
+
it { should be_an_instance_of FinModeling::Forecasts }
|
|
52
|
+
its(:reformulated_income_statements) { should have(num_quarters).items }
|
|
53
|
+
its(:reformulated_balance_sheets) { should have(num_quarters).items }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# company_spec.rb
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe FinModeling::Company do
|
|
6
|
+
before(:each) do
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
describe "initialize" do
|
|
10
|
+
it "takes a SecQuery::Entity and creates a new company" do
|
|
11
|
+
entity = SecQuery::Entity.find("aapl", {:relationships=>false, :transactions=>false, :filings=>true})
|
|
12
|
+
FinModeling::Company.new(entity).should be_an_instance_of FinModeling::Company
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe "find" do
|
|
17
|
+
it "looks up a company by its stock ticker" do
|
|
18
|
+
FinModeling::Company.find("aapl").should be_an_instance_of FinModeling::Company
|
|
19
|
+
end
|
|
20
|
+
it "returns nil if the stock symbol is invalid" do
|
|
21
|
+
FinModeling::Company.find("bogus symbol").should be_nil
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe "name" do
|
|
26
|
+
it "returns the name of the company" do
|
|
27
|
+
c = FinModeling::Company.find("aapl")
|
|
28
|
+
c.name.should == "APPLE INC"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe "annual_reports" do
|
|
33
|
+
before(:all) do
|
|
34
|
+
@company = FinModeling::Company.find "aapl"
|
|
35
|
+
end
|
|
36
|
+
it "returns a CompanyFilings object " do
|
|
37
|
+
@company.annual_reports.should be_an_instance_of FinModeling::CompanyFilings
|
|
38
|
+
end
|
|
39
|
+
it "returns an array of 10-K filings" do
|
|
40
|
+
@company.annual_reports.last.term.should == "10-K"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe "quarterly_reports" do
|
|
45
|
+
before(:all) do
|
|
46
|
+
@company = FinModeling::Company.find "aapl"
|
|
47
|
+
end
|
|
48
|
+
it "returns a CompanyFilings object " do
|
|
49
|
+
@company.quarterly_reports.should be_an_instance_of FinModeling::CompanyFilings
|
|
50
|
+
end
|
|
51
|
+
it "returns an array of 10-Q filings" do
|
|
52
|
+
@company.quarterly_reports.last.term.should == "10-Q"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe "filings_since_date" do
|
|
57
|
+
before(:all) do
|
|
58
|
+
@company = FinModeling::Company.find "aapl"
|
|
59
|
+
end
|
|
60
|
+
it "returns a CompanyFilings object " do
|
|
61
|
+
@company.filings_since_date(Time.parse("2010-01-01")).should be_an_instance_of FinModeling::CompanyFilings
|
|
62
|
+
end
|
|
63
|
+
it "returns an array of 10-Q and/or 10-K filings filed after the given date" do
|
|
64
|
+
@company.filings_since_date(Time.parse("1994-01-01")).length.should == 11
|
|
65
|
+
end
|
|
66
|
+
it "returns an array of 10-Q and/or 10-K filings filed after the given date" do
|
|
67
|
+
@company.filings_since_date(Time.parse("2010-01-01")).length.should == 9
|
|
68
|
+
end
|
|
69
|
+
it "returns an array of 10-Q and/or 10-K filings filed after the given date" do
|
|
70
|
+
@company.filings_since_date(Time.parse("2011-01-01")).length.should == 5
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# constant_forecasting_policy_spec.rb
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe FinModeling::ConstantForecastingPolicy do
|
|
6
|
+
before (:all) do
|
|
7
|
+
@vals = { :revenue_growth => 0.04,
|
|
8
|
+
:sales_pm => 0.20,
|
|
9
|
+
:fi_over_nfa => 0.01,
|
|
10
|
+
:sales_over_noa => 2.00 }
|
|
11
|
+
@policy = FinModeling::ConstantForecastingPolicy.new(@vals)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe ".revenue_growth" do
|
|
15
|
+
subject { @policy.revenue_growth }
|
|
16
|
+
it { should be_an_instance_of Float }
|
|
17
|
+
it { should be_within(0.01).of(@vals[:revenue_growth]) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe ".sales_pm" do
|
|
21
|
+
subject { @policy.sales_pm }
|
|
22
|
+
it { should be_an_instance_of Float }
|
|
23
|
+
it { should be_within(0.01).of(@vals[:sales_pm]) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe ".fi_over_nfa" do
|
|
27
|
+
subject { @policy.fi_over_nfa }
|
|
28
|
+
it { should be_an_instance_of Float }
|
|
29
|
+
it { should be_within(0.01).of(@vals[:fi_over_nfa]) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe ".sales_over_noa" do
|
|
33
|
+
subject { @policy.sales_over_noa }
|
|
34
|
+
it { should be_an_instance_of Float }
|
|
35
|
+
it { should be_within(0.01).of(@vals[:sales_over_noa]) }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# factory_spec.rb
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe FinModeling::Factory do
|
|
6
|
+
describe "BalanceSheetCalculation" do
|
|
7
|
+
subject { FinModeling::Factory.BalanceSheetCalculation }
|
|
8
|
+
|
|
9
|
+
it { should be_an_instance_of FinModeling::BalanceSheetCalculation }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe "incomeStatementCalculation" do
|
|
13
|
+
subject { FinModeling::Factory.IncomeStatementCalculation }
|
|
14
|
+
|
|
15
|
+
it { should be_an_instance_of FinModeling::IncomeStatementCalculation }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# company_filings_spec.rb
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe FinModeling::Forecasts do
|
|
6
|
+
before (:all) do
|
|
7
|
+
@company = FinModeling::Company.find("aapl")
|
|
8
|
+
@filings = FinModeling::CompanyFilings.new(@company.filings_since_date(Time.parse("2010-10-01")))
|
|
9
|
+
@forecasts = @filings.forecasts(@filings.choose_forecasting_policy, num_quarters=3)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe "balance_sheet_analyses" do
|
|
13
|
+
subject { @forecasts.balance_sheet_analyses(@filings) }
|
|
14
|
+
it { should be_a FinModeling::CalculationSummary }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe "income_statement_analyses" do
|
|
18
|
+
subject { @forecasts.income_statement_analyses(@filings) }
|
|
19
|
+
it { should be_a FinModeling::CalculationSummary }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# generic_forecasting_policy_spec.rb
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe FinModeling::GenericForecastingPolicy do
|
|
6
|
+
before (:all) do
|
|
7
|
+
@policy = FinModeling::GenericForecastingPolicy.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
describe ".revenue_growth" do
|
|
11
|
+
subject { @policy.revenue_growth }
|
|
12
|
+
it { should be_an_instance_of Float }
|
|
13
|
+
it { should be_within(0.01).of(0.04) } # FIXME: Make this not hard-coded
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe ".sales_pm" do
|
|
17
|
+
subject { @policy.sales_pm }
|
|
18
|
+
it { should be_an_instance_of Float }
|
|
19
|
+
it { should be_within(0.01).of(0.20) } # FIXME: Make this not hard-coded
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe ".fi_over_nfa" do
|
|
23
|
+
subject { @policy.fi_over_nfa }
|
|
24
|
+
it { should be_an_instance_of Float }
|
|
25
|
+
it { should be_within(0.01).of(0.01) } # FIXME: Make this not hard-coded
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe ".sales_over_noa" do
|
|
29
|
+
subject { @policy.sales_over_noa }
|
|
30
|
+
it { should be_an_instance_of Float }
|
|
31
|
+
it { should be_within(0.01).of(2.00) } # FIXME: Make this not hard-coded
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# income_statement_analyses_spec.rb
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe FinModeling::IncomeStatementAnalyses do
|
|
6
|
+
before(:all) do
|
|
7
|
+
@summary = FinModeling::CalculationSummary.new
|
|
8
|
+
@summary.title = "Title 123"
|
|
9
|
+
@summary.rows = [ ]
|
|
10
|
+
@summary.rows << FinModeling::CalculationRow.new(:key => "Revenue Growth", :type => :oa, :vals => [ 4])
|
|
11
|
+
@summary.rows << FinModeling::CalculationRow.new(:key => "Sales / NOA", :type => :oa, :vals => [ 4])
|
|
12
|
+
@summary.rows << FinModeling::CalculationRow.new(:key => "Operating PM", :type => :oa, :vals => [ 4])
|
|
13
|
+
@summary.rows << FinModeling::CalculationRow.new(:key => "FI / NFA", :type => :oa, :vals => [ 4])
|
|
14
|
+
@summary.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :fa, :vals => [109])
|
|
15
|
+
@summary.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :oa, :vals => [ 93])
|
|
16
|
+
@summary.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :fa, :vals => [ 1])
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe ".new" do
|
|
20
|
+
subject { FinModeling::IncomeStatementAnalyses.new(@summary) }
|
|
21
|
+
|
|
22
|
+
it { should be_a_kind_of FinModeling::CalculationSummary }
|
|
23
|
+
its(:title) { should == @summary.title }
|
|
24
|
+
its(:rows) { should == @summary.rows }
|
|
25
|
+
its(:header_row) { should == @summary.header_row }
|
|
26
|
+
its(:rows) { should == @summary.rows }
|
|
27
|
+
its(:num_value_columns) { should == @summary.num_value_columns }
|
|
28
|
+
its(:key_width) { should == @summary.key_width }
|
|
29
|
+
its(:val_width) { should == @summary.val_width }
|
|
30
|
+
its(:max_decimals) { should == @summary.max_decimals }
|
|
31
|
+
its(:totals_row_enabled) { should be_false }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe ".print_extras" do
|
|
35
|
+
subject { FinModeling::IncomeStatementAnalyses.new(@summary) }
|
|
36
|
+
|
|
37
|
+
it { should respond_to(:print_extras) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe ".revenue_growth_row" do
|
|
41
|
+
subject { FinModeling::IncomeStatementAnalyses.new(@summary).revenue_growth_row }
|
|
42
|
+
it { should be_a FinModeling::CalculationRow }
|
|
43
|
+
its(:key) { should == "Revenue Growth" }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe ".operating_pm_row" do
|
|
47
|
+
subject { FinModeling::IncomeStatementAnalyses.new(@summary).operating_pm_row }
|
|
48
|
+
it { should be_a FinModeling::CalculationRow }
|
|
49
|
+
its(:key) { should == "Operating PM" }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe ".sales_over_noa_row" do
|
|
53
|
+
subject { FinModeling::IncomeStatementAnalyses.new(@summary).sales_over_noa_row }
|
|
54
|
+
it { should be_a FinModeling::CalculationRow }
|
|
55
|
+
its(:key) { should == "Sales / NOA" }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe ".fi_over_nfa_row" do
|
|
59
|
+
subject { FinModeling::IncomeStatementAnalyses.new(@summary).fi_over_nfa_row }
|
|
60
|
+
it { should be_a FinModeling::CalculationRow }
|
|
61
|
+
its(:key) { should == "FI / NFA" }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# income_statement_calculation_spec.rb
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe FinModeling::IncomeStatementCalculation do
|
|
6
|
+
before(:all) do
|
|
7
|
+
google_2010_q3_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312511282235/0001193125-11-282235-index.htm"
|
|
8
|
+
filing_q3 = FinModeling::AnnualReportFiling.download google_2010_q3_rpt
|
|
9
|
+
@prev_inc_stmt = filing_q3.income_statement
|
|
10
|
+
|
|
11
|
+
google_2011_annual_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312512025336/0001193125-12-025336-index.htm"
|
|
12
|
+
filing = FinModeling::AnnualReportFiling.download google_2011_annual_rpt
|
|
13
|
+
@inc_stmt = filing.income_statement
|
|
14
|
+
@period = @inc_stmt.periods.last
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe "net_income_calculation" do
|
|
18
|
+
it "returns a NetIncomeCalculation" do
|
|
19
|
+
@inc_stmt.net_income_calculation.should be_an_instance_of FinModeling::NetIncomeCalculation
|
|
20
|
+
end
|
|
21
|
+
it "returns the root node of the net income calculation" do
|
|
22
|
+
@inc_stmt.net_income_calculation.label.downcase.should match /net.*income/
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe "is_valid?" do
|
|
27
|
+
context "when no node contains the term 'tax'" do
|
|
28
|
+
#before(:all) do
|
|
29
|
+
# @inc_stmt_no_taxes = FinModeling::Factory.IncomeStatementCalculation(:sheet => 'google 10-k 2011-12-31 income statement',
|
|
30
|
+
# :delete_tax_item => true)
|
|
31
|
+
#end
|
|
32
|
+
it "returns false if none of the net income leaf nodes contains the term 'tax'" do
|
|
33
|
+
#@inc_stmt_no_taxes.is_valid?.should be_false
|
|
34
|
+
pending "no good way of setting up this test..."
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
context "when no node contains the terms 'sales' or 'revenue'" do
|
|
38
|
+
#before(:all) do
|
|
39
|
+
# @inc_stmt_no_sales = FinModeling::Factory.IncomeStatementCalculation(:sheet => 'google 10-k 2011-12-31 income statement',
|
|
40
|
+
# :delete_sales_item => true)
|
|
41
|
+
#end
|
|
42
|
+
it "returns false if none of the net income leaf nodes contains the term 'tax'" do
|
|
43
|
+
#@inc_stmt_no_sales.is_valid?.should be_false
|
|
44
|
+
pending "no good way of setting up this test..."
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
context "otherwise" do
|
|
48
|
+
subject { @inc_stmt.is_valid? }
|
|
49
|
+
it { should be_true }
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
describe "reformulated" do
|
|
54
|
+
subject { @inc_stmt.reformulated(@period) }
|
|
55
|
+
it { should be_an_instance_of FinModeling::ReformulatedIncomeStatement }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe "latest_quarterly_reformulated" do
|
|
59
|
+
subject{ @inc_stmt.latest_quarterly_reformulated(@prev_inc_stmt) }
|
|
60
|
+
it { should be_an_instance_of FinModeling::ReformulatedIncomeStatement }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
describe "write_constructor" do
|
|
64
|
+
before(:all) do
|
|
65
|
+
file_name = "/tmp/finmodeling-inc-stmt.rb"
|
|
66
|
+
item_name = "@inc_stmt"
|
|
67
|
+
file = File.open(file_name, "w")
|
|
68
|
+
@inc_stmt.write_constructor(file, item_name)
|
|
69
|
+
file.close
|
|
70
|
+
|
|
71
|
+
eval(File.read(file_name))
|
|
72
|
+
|
|
73
|
+
@loaded_is = eval(item_name)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "writes itself to a file, and when reloaded, has the same periods" do
|
|
77
|
+
expected_periods = @inc_stmt.periods.map{|x| x.to_pretty_s}.join(',')
|
|
78
|
+
@loaded_is.periods.map{|x| x.to_pretty_s}.join(',').should == expected_periods
|
|
79
|
+
end
|
|
80
|
+
it "writes itself to a file, and when reloaded, has the same net financing income" do
|
|
81
|
+
period = @inc_stmt.periods.yearly.last
|
|
82
|
+
expected_nfi = @inc_stmt.reformulated(period).net_financing_income.total
|
|
83
|
+
@loaded_is.reformulated(period).net_financing_income.total.should be_within(1.0).of(expected_nfi)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
end
|
|
88
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# period_array_spec.rb
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe FinModeling::IncomeStatementItem do
|
|
6
|
+
|
|
7
|
+
before(:all) do
|
|
8
|
+
#FinModeling::IncomeStatementItem.load_vectors_and_train(FinModeling::IncomeStatementItem::TRAINING_VECTORS)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
describe "new" do
|
|
12
|
+
it "takes a string and returns a new IncomeStatementItem" do
|
|
13
|
+
isi = FinModeling::IncomeStatementItem.new("Cost of Goods Sold")
|
|
14
|
+
isi.should be_an_instance_of FinModeling::IncomeStatementItem
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe "train" do
|
|
19
|
+
it "trains the classifier that this ISI is of the given type" do
|
|
20
|
+
FinModeling::IncomeStatementItem.new("provision for income tax").train(:tax)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe "classification_estimates" do
|
|
25
|
+
it "returns a hash with the confidence in each ISI type" do
|
|
26
|
+
isi = FinModeling::IncomeStatementItem.new("Cost of Services")
|
|
27
|
+
|
|
28
|
+
FinModeling::IncomeStatementItem::TYPES.each do |klass|
|
|
29
|
+
isi.classification_estimates.keys.include?(klass).should be_true
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe "classify" do
|
|
35
|
+
it "returns the ISI type with the highest probability estimate" do
|
|
36
|
+
isi = FinModeling::IncomeStatementItem.new("provision for income tax")
|
|
37
|
+
estimates = isi.classification_estimates
|
|
38
|
+
estimates[isi.classify].should be_within(0.1).of(estimates.values.max)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "load_vectors_and_train" do
|
|
43
|
+
# the before(:all) clause calls load_vectors_and_train already
|
|
44
|
+
# we can just focus, here, on its effects
|
|
45
|
+
|
|
46
|
+
it "loads vectors from a given file, trains on each example, and correctly classifies tax" do
|
|
47
|
+
isi = FinModeling::IncomeStatementItem.new("provision for income tax")
|
|
48
|
+
isi.classify.should == :tax
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "loads vectors from a given file, trains on each example, and correctly classifies revenue" do
|
|
52
|
+
isi = FinModeling::IncomeStatementItem.new("software licensing revenues net")
|
|
53
|
+
isi.classify.should == :or
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "classifies >95% correctly" do
|
|
57
|
+
num_items = 0
|
|
58
|
+
errors = []
|
|
59
|
+
FinModeling::IncomeStatementItem::TRAINING_VECTORS.each do |vector|
|
|
60
|
+
num_items += 1
|
|
61
|
+
isi = FinModeling::IncomeStatementItem.new(vector[:item_string])
|
|
62
|
+
if isi.classify != vector[:klass]
|
|
63
|
+
errors.push({ :isi=>isi.to_s, :expected=>vector[:klass], :got=>isi.classify })
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
pct_errors = errors.length.to_f / num_items
|
|
68
|
+
if pct_errors > 0.05
|
|
69
|
+
puts "errors: " + errors.inspect
|
|
70
|
+
end
|
|
71
|
+
pct_errors.should be < 0.05
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
describe "tokenize" do
|
|
77
|
+
it "returns an array of downcased 1-word, 2-word, and 3-word tokens" do
|
|
78
|
+
isi = FinModeling::IncomeStatementItem.new("Cost of Goods Sold")
|
|
79
|
+
expected_tokens = ["^", "cost", "of", "goods", "sold", "$"]
|
|
80
|
+
expected_tokens += ["^ cost", "cost of", "of goods", "goods sold", "sold $"]
|
|
81
|
+
expected_tokens += ["^ cost of", "cost of goods", "of goods sold", "goods sold $"]
|
|
82
|
+
isi.tokenize.sort.should == expected_tokens.sort
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# liabs_and_equity_calculation_spec.rb
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe FinModeling::LiabsAndEquityCalculation 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
|
+
@bal_sheet = filing.balance_sheet
|
|
10
|
+
@period = @bal_sheet.periods.last
|
|
11
|
+
@lse = @bal_sheet.liabs_and_equity_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
|
+
@lse.summary(:period=>@period).should be_an_instance_of FinModeling::CalculationSummary
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# period_array_spec.rb
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe FinModeling::LiabsAndEquityItem do
|
|
6
|
+
|
|
7
|
+
before(:all) do
|
|
8
|
+
#FinModeling::LiabsAndEquityItem.load_vectors_and_train(FinModeling::LiabsAndEquityItem::TRAINING_VECTORS)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
describe "new" do
|
|
12
|
+
it "takes a string and returns a new LiabsAndEquityItem" do
|
|
13
|
+
laei = FinModeling::LiabsAndEquityItem.new("Accounts Payable Current")
|
|
14
|
+
laei.should be_an_instance_of FinModeling::LiabsAndEquityItem
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe "train" do
|
|
19
|
+
it "trains the classifier that this LiabsAndEquityItem is of the given type" do
|
|
20
|
+
FinModeling::LiabsAndEquityItem.new("Accounts Payable Current").train(:ol)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe "classification_estimates" do
|
|
25
|
+
it "returns a hash with the confidence in each LiabsAndEquityItem type" do
|
|
26
|
+
laei = FinModeling::LiabsAndEquityItem.new("Accounts Payable Current")
|
|
27
|
+
|
|
28
|
+
FinModeling::LiabsAndEquityItem::TYPES.each do |klass|
|
|
29
|
+
laei.classification_estimates.keys.include?(klass).should be_true
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe "classify" do
|
|
35
|
+
it "returns the LiabsAndEquityItem type with the highest probability estimate" do
|
|
36
|
+
laei = FinModeling::LiabsAndEquityItem.new("Accounts Payable Current")
|
|
37
|
+
estimates = laei.classification_estimates
|
|
38
|
+
estimates[laei.classify].should be_within(0.1).of(estimates.values.max)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "load_vectors_and_train" do
|
|
43
|
+
# the before(:all) clause calls load_vectors_and_train already
|
|
44
|
+
# we can just focus, here, on its effects
|
|
45
|
+
|
|
46
|
+
it "classifies >95% correctly" do
|
|
47
|
+
num_items = 0
|
|
48
|
+
errors = []
|
|
49
|
+
FinModeling::LiabsAndEquityItem::TRAINING_VECTORS.each do |vector|
|
|
50
|
+
num_items += 1
|
|
51
|
+
laei = FinModeling::LiabsAndEquityItem.new(vector[:item_string])
|
|
52
|
+
if laei.classify != vector[:klass]
|
|
53
|
+
errors.push({ :laei=>laei.to_s, :expected=>vector[:klass], :got=>laei.classify })
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
pct_errors = errors.length.to_f / num_items
|
|
58
|
+
if pct_errors > 0.05
|
|
59
|
+
puts "errors: " + errors.inspect
|
|
60
|
+
end
|
|
61
|
+
pct_errors.should be < 0.05
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|