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,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
|