rvgp 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +23 -0
  4. data/LICENSE +504 -0
  5. data/README.md +223 -0
  6. data/Rakefile +32 -0
  7. data/bin/rvgp +8 -0
  8. data/lib/rvgp/application/config.rb +159 -0
  9. data/lib/rvgp/application/descendant_registry.rb +122 -0
  10. data/lib/rvgp/application/status_output.rb +139 -0
  11. data/lib/rvgp/application.rb +170 -0
  12. data/lib/rvgp/base/command.rb +457 -0
  13. data/lib/rvgp/base/grid.rb +531 -0
  14. data/lib/rvgp/base/reader.rb +29 -0
  15. data/lib/rvgp/base/reconciler.rb +434 -0
  16. data/lib/rvgp/base/validation.rb +261 -0
  17. data/lib/rvgp/commands/cashflow.rb +160 -0
  18. data/lib/rvgp/commands/grid.rb +70 -0
  19. data/lib/rvgp/commands/ireconcile.rb +95 -0
  20. data/lib/rvgp/commands/new_project.rb +296 -0
  21. data/lib/rvgp/commands/plot.rb +41 -0
  22. data/lib/rvgp/commands/publish_gsheets.rb +83 -0
  23. data/lib/rvgp/commands/reconcile.rb +58 -0
  24. data/lib/rvgp/commands/rotate_year.rb +202 -0
  25. data/lib/rvgp/commands/validate_journal.rb +59 -0
  26. data/lib/rvgp/commands/validate_system.rb +44 -0
  27. data/lib/rvgp/commands.rb +160 -0
  28. data/lib/rvgp/dashboard.rb +252 -0
  29. data/lib/rvgp/fakers/fake_feed.rb +245 -0
  30. data/lib/rvgp/fakers/fake_journal.rb +57 -0
  31. data/lib/rvgp/fakers/fake_reconciler.rb +88 -0
  32. data/lib/rvgp/fakers/faker_helpers.rb +25 -0
  33. data/lib/rvgp/gem.rb +80 -0
  34. data/lib/rvgp/journal/commodity.rb +453 -0
  35. data/lib/rvgp/journal/complex_commodity.rb +214 -0
  36. data/lib/rvgp/journal/currency.rb +101 -0
  37. data/lib/rvgp/journal/journal.rb +141 -0
  38. data/lib/rvgp/journal/posting.rb +156 -0
  39. data/lib/rvgp/journal/pricer.rb +267 -0
  40. data/lib/rvgp/journal.rb +24 -0
  41. data/lib/rvgp/plot/gnuplot.rb +478 -0
  42. data/lib/rvgp/plot/google-drive/output_csv.rb +44 -0
  43. data/lib/rvgp/plot/google-drive/output_google_sheets.rb +434 -0
  44. data/lib/rvgp/plot/google-drive/sheet.rb +67 -0
  45. data/lib/rvgp/plot.rb +293 -0
  46. data/lib/rvgp/pta/hledger.rb +237 -0
  47. data/lib/rvgp/pta/ledger.rb +308 -0
  48. data/lib/rvgp/pta.rb +311 -0
  49. data/lib/rvgp/reconcilers/csv_reconciler.rb +424 -0
  50. data/lib/rvgp/reconcilers/journal_reconciler.rb +41 -0
  51. data/lib/rvgp/reconcilers/shorthand/finance_gem_hacks.rb +48 -0
  52. data/lib/rvgp/reconcilers/shorthand/international_atm.rb +152 -0
  53. data/lib/rvgp/reconcilers/shorthand/investment.rb +144 -0
  54. data/lib/rvgp/reconcilers/shorthand/mortgage.rb +195 -0
  55. data/lib/rvgp/utilities/grid_query.rb +190 -0
  56. data/lib/rvgp/utilities/yaml.rb +131 -0
  57. data/lib/rvgp/utilities.rb +44 -0
  58. data/lib/rvgp/validations/balance_validation.rb +68 -0
  59. data/lib/rvgp/validations/duplicate_tags_validation.rb +48 -0
  60. data/lib/rvgp/validations/uncategorized_validation.rb +15 -0
  61. data/lib/rvgp.rb +66 -0
  62. data/resources/README.MD/2022-cashflow-google.png +0 -0
  63. data/resources/README.MD/2022-cashflow.png +0 -0
  64. data/resources/README.MD/all-wealth-growth-google.png +0 -0
  65. data/resources/README.MD/all-wealth-growth.png +0 -0
  66. data/resources/gnuplot/default.yml +80 -0
  67. data/resources/i18n/en.yml +192 -0
  68. data/resources/iso-4217-currencies.json +171 -0
  69. data/resources/skel/Rakefile +5 -0
  70. data/resources/skel/app/grids/cashflow_grid.rb +27 -0
  71. data/resources/skel/app/grids/monthly_income_and_expenses_grid.rb +25 -0
  72. data/resources/skel/app/grids/wealth_growth_grid.rb +35 -0
  73. data/resources/skel/app/plots/cashflow.yml +33 -0
  74. data/resources/skel/app/plots/monthly-income-and-expenses.yml +17 -0
  75. data/resources/skel/app/plots/wealth-growth.yml +20 -0
  76. data/resources/skel/config/csv-format-acme-checking.yml +9 -0
  77. data/resources/skel/config/google-secrets.yml +5 -0
  78. data/resources/skel/config/rvgp.yml +0 -0
  79. data/resources/skel/journals/prices.db +0 -0
  80. data/rvgp.gemspec +6 -0
  81. data/test/assets/ledger_total_monthly_liabilities_with_empty.xml +383 -0
  82. data/test/assets/ledger_total_monthly_liabilities_with_empty2.xml +428 -0
  83. data/test/test_command_base.rb +61 -0
  84. data/test/test_commodity.rb +270 -0
  85. data/test/test_csv_reconciler.rb +60 -0
  86. data/test/test_currency.rb +24 -0
  87. data/test/test_fake_feed.rb +228 -0
  88. data/test/test_fake_journal.rb +98 -0
  89. data/test/test_fake_reconciler.rb +60 -0
  90. data/test/test_journal_parse.rb +545 -0
  91. data/test/test_ledger.rb +102 -0
  92. data/test/test_plot.rb +133 -0
  93. data/test/test_posting.rb +50 -0
  94. data/test/test_pricer.rb +139 -0
  95. data/test/test_pta_adapter.rb +575 -0
  96. data/test/test_utilities.rb +45 -0
  97. metadata +268 -0
data/test/test_plot.rb ADDED
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'csv'
5
+ require 'minitest/autorun'
6
+
7
+ require_relative '../lib/rvgp'
8
+ require_relative '../lib/rvgp/plot'
9
+
10
+ # Tests for RVGP::Plot
11
+ class TestPlot < Minitest::Test
12
+ YEAR_ONLY_CORPUS = [
13
+ '/path/to/ledger/2018-wealth-growth.csv',
14
+ '/path/to/ledger/2019-wealth-growth.csv',
15
+ '/path/to/ledger/2020-wealth-growth.csv',
16
+ '/path/to/ledger/2021-wealth-growth.csv',
17
+ '/path/to/ledger/2022-wealth-growth.csv',
18
+ # These are meant to be ignored:
19
+ '/path/to/ledger/2018-profit-and-loss.csv',
20
+ '/path/to/ledger/2019-profit-and-loss.csv',
21
+ '/path/to/ledger/2020-profit-and-loss.csv',
22
+ '/path/to/ledger/2021-profit-and-loss.csv'
23
+ ].freeze
24
+
25
+ YEAR_AND_INTENTION_CORPUS = [
26
+ 'build/grids/2018-cashflow-burgershop.csv',
27
+ 'build/grids/2018-cashflow-multiplex.csv',
28
+ 'build/grids/2018-cashflow-ignored.csv',
29
+ 'build/grids/2018-cashflow-personal.csv',
30
+ # NOTE: We sold the frozenbananastand in 2018, so, there's no 2019 grid
31
+ 'build/grids/2018-cashflow-frozenbananastand.csv',
32
+ 'build/grids/2019-cashflow-burgershop.csv',
33
+ 'build/grids/2019-cashflow-multiplex.csv',
34
+ 'build/grids/2019-cashflow-ignored.csv',
35
+ 'build/grids/2019-cashflow-personal.csv',
36
+ # These are meant to be ignored:
37
+ '/path/to/ledger/2018-profit-and-loss.csv',
38
+ '/path/to/ledger/2019-profit-and-loss.csv',
39
+ '/path/to/ledger/2020-profit-and-loss.csv',
40
+ '/path/to/ledger/2021-profit-and-loss.csv'
41
+ ].freeze
42
+
43
+ def test_globber
44
+ matches = RVGP::Plot.glob_variants '%{year}-wealth-growth.csv', YEAR_ONLY_CORPUS # rubocop:disable Style/FormatStringToken
45
+
46
+ assert_equal 5, matches.length
47
+
48
+ 0.upto(4).map do |i|
49
+ year = 2018 + i
50
+ assert_equal [format('/path/to/ledger/%d-wealth-growth.csv', year)], matches[i][:files]
51
+ assert_equal format('%d-wealth-growth', year), matches[i][:name]
52
+ assert_equal({ year: year.to_s }, matches[i][:pairs])
53
+ end
54
+
55
+ matches = RVGP::Plot.glob_variants '%{year}-cashflow-%{intention}.csv', YEAR_AND_INTENTION_CORPUS # rubocop:disable Style/FormatStringToken
56
+
57
+ assert_equal 9, matches.length
58
+
59
+ assert_equal ['build/grids/2018-cashflow-burgershop.csv'], matches[0][:files]
60
+ assert_equal '2018-cashflow-burgershop', matches[0][:name]
61
+ assert_equal({ year: '2018', intention: 'burgershop' }, matches[0][:pairs])
62
+
63
+ assert_equal ['build/grids/2018-cashflow-multiplex.csv'], matches[1][:files]
64
+ assert_equal '2018-cashflow-multiplex', matches[1][:name]
65
+ assert_equal({ year: '2018', intention: 'multiplex' }, matches[1][:pairs])
66
+
67
+ assert_equal ['build/grids/2018-cashflow-ignored.csv'], matches[2][:files]
68
+ assert_equal '2018-cashflow-ignored', matches[2][:name]
69
+ assert_equal({ year: '2018', intention: 'ignored' }, matches[2][:pairs])
70
+
71
+ assert_equal ['build/grids/2018-cashflow-personal.csv'], matches[3][:files]
72
+ assert_equal '2018-cashflow-personal', matches[3][:name]
73
+ assert_equal({ year: '2018', intention: 'personal' }, matches[3][:pairs])
74
+
75
+ assert_equal ['build/grids/2018-cashflow-frozenbananastand.csv'], matches[4][:files]
76
+ assert_equal '2018-cashflow-frozenbananastand', matches[4][:name]
77
+ assert_equal({ year: '2018', intention: 'frozenbananastand' }, matches[4][:pairs])
78
+
79
+ assert_equal ['build/grids/2019-cashflow-burgershop.csv'], matches[5][:files]
80
+ assert_equal '2019-cashflow-burgershop', matches[5][:name]
81
+ assert_equal({ year: '2019', intention: 'burgershop' }, matches[5][:pairs])
82
+
83
+ assert_equal ['build/grids/2019-cashflow-multiplex.csv'], matches[6][:files]
84
+ assert_equal '2019-cashflow-multiplex', matches[6][:name]
85
+ assert_equal({ year: '2019', intention: 'multiplex' }, matches[6][:pairs])
86
+
87
+ assert_equal ['build/grids/2019-cashflow-ignored.csv'], matches[7][:files]
88
+ assert_equal '2019-cashflow-ignored', matches[7][:name]
89
+ assert_equal({ year: '2019', intention: 'ignored' }, matches[7][:pairs])
90
+
91
+ assert_equal ['build/grids/2019-cashflow-personal.csv'], matches[8][:files]
92
+ assert_equal '2019-cashflow-personal', matches[8][:name]
93
+ assert_equal({ year: '2019', intention: 'personal' }, matches[8][:pairs])
94
+ end
95
+
96
+ def test_all_year_globber
97
+ matches = RVGP::Plot.glob_variants '%{year}-wealth-growth.csv', YEAR_ONLY_CORPUS, year: 'all' # rubocop:disable Style/FormatStringToken
98
+
99
+ assert_equal 1, matches.length
100
+
101
+ assert_equal ['/path/to/ledger/2018-wealth-growth.csv',
102
+ '/path/to/ledger/2019-wealth-growth.csv',
103
+ '/path/to/ledger/2020-wealth-growth.csv',
104
+ '/path/to/ledger/2021-wealth-growth.csv',
105
+ '/path/to/ledger/2022-wealth-growth.csv'], matches[0][:files]
106
+ assert_equal 'all-wealth-growth', matches[0][:name]
107
+ assert_equal({ year: 'all' }, matches[0][:pairs])
108
+
109
+ matches = RVGP::Plot.glob_variants '%{year}-cashflow-%{intention}.csv', YEAR_AND_INTENTION_CORPUS, year: 'all' # rubocop:disable Style/FormatStringToken
110
+
111
+ assert_equal 5, matches.length
112
+
113
+ assert_equal(%w[2018 2019].map { |y| format('build/grids/%s-cashflow-burgershop.csv', y) }, matches[0][:files])
114
+ assert_equal 'all-cashflow-burgershop', matches[0][:name]
115
+ assert_equal({ year: 'all', intention: 'burgershop' }, matches[0][:pairs])
116
+
117
+ assert_equal(%w[2018 2019].map { |y| format('build/grids/%s-cashflow-multiplex.csv', y) }, matches[1][:files])
118
+ assert_equal 'all-cashflow-multiplex', matches[1][:name]
119
+ assert_equal({ year: 'all', intention: 'multiplex' }, matches[1][:pairs])
120
+
121
+ assert_equal(%w[2018 2019].map { |y| format('build/grids/%s-cashflow-ignored.csv', y) }, matches[2][:files])
122
+ assert_equal 'all-cashflow-ignored', matches[2][:name]
123
+ assert_equal({ year: 'all', intention: 'ignored' }, matches[2][:pairs])
124
+
125
+ assert_equal(%w[2018 2019].map { |y| format('build/grids/%s-cashflow-personal.csv', y) }, matches[3][:files])
126
+ assert_equal 'all-cashflow-personal', matches[3][:name]
127
+ assert_equal({ year: 'all', intention: 'personal' }, matches[3][:pairs])
128
+
129
+ assert_equal ['build/grids/2018-cashflow-frozenbananastand.csv'], matches[4][:files]
130
+ assert_equal 'all-cashflow-frozenbananastand', matches[4][:name]
131
+ assert_equal({ year: 'all', intention: 'frozenbananastand' }, matches[4][:pairs])
132
+ end
133
+ end
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'date'
5
+ require 'minitest/autorun'
6
+
7
+ require_relative '../lib/rvgp'
8
+ require_relative '../lib/rvgp/journal'
9
+
10
+ # Tests for RVGP::Journal::Posting
11
+ class TestPosting < Minitest::Test
12
+ def test_posting_to_ledger
13
+ posting = RVGP::Journal::Posting.new(
14
+ Date.new(2021, 10, 2),
15
+ 'Heroes R Us',
16
+ tags: ['Vacation: Seattle'.to_tag],
17
+ transfers: [
18
+ ['Expenses:Comics', { commodity: '$ 5.00'.to_commodity, tags: ['Publisher: Marvel'.to_tag] }],
19
+ ['Expenses:Cards', { commodity: '$ 9.00'.to_commodity, tags: ['Collection: Baseball'.to_tag] }],
20
+ ['Cash']
21
+ ].collect { |args| RVGP::Journal::Posting::Transfer.new(*args) }
22
+ )
23
+
24
+ assert_equal [
25
+ '2021-10-02 Heroes R Us',
26
+ ' ; Vacation: Seattle',
27
+ ' Expenses:Comics $ 5.00',
28
+ ' ; Publisher: Marvel',
29
+ ' Expenses:Cards $ 9.00',
30
+ ' ; Collection: Baseball',
31
+ ' Cash'
32
+ ].join("\n"), posting.to_ledger
33
+ end
34
+
35
+ def test_posting_with_complex_commodity_to_ledger
36
+ complex_commodity = RVGP::Journal::ComplexCommodity.from_s(['-1000.0001 VBTLX', '@@', '$ 100000.00'].join(' '))
37
+
38
+ posting = RVGP::Journal::Posting.new(
39
+ '2022-03-28',
40
+ 'VANGUARD TOTAL BOND MARKET INDEX Sell',
41
+ transfers: [RVGP::Journal::Posting::Transfer.new('Personal:Assets:Vanguard:VBTLX', commodity: complex_commodity),
42
+ RVGP::Journal::Posting::Transfer.new('Personal:Assets:Vanguard:MoneyMarket')]
43
+ )
44
+
45
+ assert_equal ['2022-03-28 VANGUARD TOTAL BOND MARKET INDEX Sell',
46
+ ' Personal:Assets:Vanguard:VBTLX -1000.0001 VBTLX @@ $ 100000.00',
47
+ ' Personal:Assets:Vanguard:MoneyMarket'].join("\n"),
48
+ posting.to_ledger
49
+ end
50
+ end
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'time'
5
+ require 'minitest/autorun'
6
+
7
+ require_relative '../lib/rvgp'
8
+
9
+ # Tests for RVGP::Journal::Pricer
10
+ class TestPricer < Minitest::Test
11
+ TEST_PRICES_DB_FORMAT1 = <<~DB_FORMAT1
12
+ P 2004/06/25 00:00:00 FEQTX $24.00
13
+ P 2004/06/21 02:18:01 FEQTX $22.49
14
+ P 2004/06/21 02:18:01 BORL $6.20
15
+ P 2004/06/21 02:18:02 AAPL $32.91
16
+ P 2004/06/21 02:18:02 AU $400.00
17
+ P 2004/06/21 14:15:00 FEQTX $ 22.75
18
+ P 2004/06/25 01:00:00 BORL $6.20
19
+ P 2004/06/25 02:00:00 AAPL $32.91
20
+ P 2004/06/25 03:18:02 AU $400.00
21
+ DB_FORMAT1
22
+
23
+ TEST_PRICES_DB_FORMAT2 = <<~DB_FORMAT2
24
+ P 2020-01-01 USD 0.893179 EUR
25
+ P 2020-02-01 EUR 1.109275 USD
26
+ P 2020-03-01 USD 0.907082 EUR
27
+ DB_FORMAT2
28
+
29
+ TEST_PRICES_LARGE_ASSETS = <<~LARGE_ASSETS
30
+ P 2018/01/01 FLORIDAHOME $500,000.00
31
+ P 2018/01/01 CORVETTE $50,000.00
32
+ LARGE_ASSETS
33
+
34
+ def test_price_format1
35
+ p = RVGP::Journal::Pricer.new TEST_PRICES_DB_FORMAT1
36
+
37
+ assert_raises(RVGP::Journal::Pricer::NoPriceError) { price_on p, '1900-01-01', 'FEQTX', 'USD' }
38
+ assert_raises(RVGP::Journal::Pricer::NoPriceError) { price_on p, '2004-06-20', 'FEQTX', '$' }
39
+ assert_raises(RVGP::Journal::Pricer::NoPriceError) { price_on p, '2004-06-21', 'FEQTX', 'USD' }
40
+
41
+ assert_equal '$ 22.49', price_on(p, '2004-06-21 12:00:00', 'FEQTX', '$')
42
+ assert_equal '$ 22.49', price_on(p, '2004-06-21 14:14:59', 'FEQTX', 'USD')
43
+ assert_equal '$ 22.75', price_on(p, '2004-06-21 14:15:00', 'FEQTX', '$')
44
+ assert_equal '$ 22.75', price_on(p, '2004-06-21 14:15:01', 'FEQTX', 'USD')
45
+ assert_equal '$ 22.75', price_on(p, '2004-06-22', 'FEQTX', '$')
46
+ assert_equal '$ 24.00', price_on(p, '2004-06-25', 'FEQTX', 'USD')
47
+ assert_equal '$ 24.00', price_on(p, '2300-01-01', 'FEQTX', '$')
48
+
49
+ # This is the same set of tests as above, but ,inverted:
50
+ # Probably this doesn't make much sense in production, but, we use it with
51
+ # fiat currencies later:
52
+ assert_raises(RVGP::Journal::Pricer::NoPriceError) { price_on p, '1900-01-01', 'USD', 'FEQTX' }
53
+ assert_raises(RVGP::Journal::Pricer::NoPriceError) { price_on p, '2004-06-20', '$', 'FEQTX' }
54
+ assert_raises(RVGP::Journal::Pricer::NoPriceError) { price_on p, '2004-06-21', 'USD', 'FEQTX' }
55
+
56
+ assert_equal '0.0444642063139173 FEQTX', price_on(p, '2004-06-21 12:00:00', 'USD', 'FEQTX')
57
+ assert_equal '0.0444642063139173 FEQTX', price_on(p, '2004-06-21 14:14:59', '$', 'FEQTX')
58
+ assert_equal '0.04395604395604396 FEQTX', price_on(p, '2004-06-21 14:15:00', 'USD', 'FEQTX')
59
+ assert_equal '0.04395604395604396 FEQTX', price_on(p, '2004-06-21 14:15:01', '$', 'FEQTX')
60
+ assert_equal '0.04395604395604396 FEQTX', price_on(p, '2004-06-22', 'USD', 'FEQTX')
61
+ assert_equal '0.04166666666666667 FEQTX', price_on(p, '2004-06-25', '$', 'FEQTX')
62
+ assert_equal '0.04166666666666667 FEQTX', price_on(p, '2300-01-01', 'USD', 'FEQTX')
63
+ end
64
+
65
+ def test_price_format2
66
+ p = RVGP::Journal::Pricer.new TEST_PRICES_DB_FORMAT2
67
+
68
+ assert_raises(RVGP::Journal::Pricer::NoPriceError) { price_on p, '2019-12-31', 'EUR', 'USD' }
69
+ assert_raises(RVGP::Journal::Pricer::NoPriceError) { price_on p, '2019-12-31', 'USD', 'EUR' }
70
+
71
+ assert_equal '1.11959640788688494 USD', price_on(p, '2020-01-01', 'EUR', 'USD')
72
+ assert_equal '0.893179 EUR', price_on(p, '2020-01-01', 'USD', 'EUR')
73
+
74
+ assert_equal '1.11959640788688494 USD', price_on(p, '2020-01-15', 'EUR', 'USD')
75
+ assert_equal '0.893179 EUR', price_on(p, '2020-01-15', 'USD', 'EUR')
76
+
77
+ assert_equal '1.109275 USD', price_on(p, '2020-02-01', 'EUR', 'USD')
78
+ assert_equal '0.90148971174866467 EUR', price_on(p, '2020-02-01', 'USD', 'EUR')
79
+
80
+ assert_equal '1.10243616343395636 USD', price_on(p, '2020-03-01', 'EUR', 'USD')
81
+ assert_equal '0.907082 EUR', price_on(p, '2020-03-01', 'USD', 'EUR')
82
+
83
+ assert_equal '1.10243616343395636 USD', price_on(p, '2020-03-01', 'EUR', 'USD')
84
+ assert_equal '0.907082 EUR', price_on(p, '2020-03-15', 'USD', 'EUR')
85
+ end
86
+
87
+ def test_price_format3
88
+ p = RVGP::Journal::Pricer.new TEST_PRICES_LARGE_ASSETS
89
+
90
+ # Make sure the database is parsed ok:
91
+ assert_equal '$ 500000.00', price_on(p, '2020-01-01 12:00:00', 'FLORIDAHOME', '$')
92
+ assert_equal '$ 50000.00', price_on(p, '2020-01-01 12:00:00', 'CORVETTE', '$')
93
+
94
+ # I don't think anyone will ever do this, but, why not:
95
+ assert_equal '0.000002 FLORIDAHOME', price_on(p, '2020-01-01 12:00:00', '$', 'FLORIDAHOME')
96
+ assert_equal '0.00002 CORVETTE', price_on(p, '2020-01-01 12:00:00', '$', 'CORVETTE')
97
+
98
+ # And now get fancy with the conversions:
99
+ at = Date.new(2020, 1, 1).to_time
100
+
101
+ dollar = '$ 1.00'.to_commodity
102
+ home = '1 FLORIDAHOME'.to_commodity
103
+ minus_home = '-1 FLORIDAHOME'.to_commodity
104
+ minus_dollar = '$ -1.00'.to_commodity
105
+
106
+ assert_equal '$ 500000.00', p.convert(at, home, '$').to_s
107
+ assert_equal '$ -500000.00', p.convert(at, minus_home, '$').to_s
108
+
109
+ assert_equal '0.000002 FLORIDAHOME', p.convert(at, dollar, 'FLORIDAHOME').to_s
110
+ assert_equal '-0.000002 FLORIDAHOME', p.convert(at, minus_dollar, 'FLORIDAHOME').to_s
111
+ end
112
+
113
+ def test_price_insert
114
+ pricer = RVGP::Journal::Pricer.new
115
+ pricer.add(Date.new(2020, 8, 1), 'ABC', '$ 0.80'.to_commodity)
116
+ pricer.add(Date.new(2020, 1, 1), 'ABC', '0.10 USD'.to_commodity)
117
+ pricer.add(Date.new(2020, 2, 1), 'ABC', '$0.20'.to_commodity)
118
+ pricer.add(Date.new(2020, 3, 1), 'ABC', '0.30 USD'.to_commodity)
119
+ pricer.add(Date.new(2020, 4, 1), 'ABC', '$0.40'.to_commodity)
120
+ pricer.add(Date.new(2020, 6, 1), 'ABC', '0.60 USD'.to_commodity)
121
+ pricer.add(Date.new(2020, 7, 1), 'ABC', '$0.70'.to_commodity)
122
+ pricer.add(Date.new(2020, 5, 1), 'ABC', '0.50 USD'.to_commodity)
123
+
124
+ assert_equal '0.10 USD', price_on(pricer, '2020-01-15', 'ABC', '$')
125
+ assert_equal '$ 0.20', price_on(pricer, '2020-02-15', 'ABC', '$')
126
+ assert_equal '0.30 USD', price_on(pricer, '2020-03-15', 'ABC', '$')
127
+ assert_equal '$ 0.40', price_on(pricer, '2020-04-15', 'ABC', '$')
128
+ assert_equal '0.50 USD', price_on(pricer, '2020-05-15', 'ABC', '$')
129
+ assert_equal '0.60 USD', price_on(pricer, '2020-06-15', 'ABC', '$')
130
+ assert_equal '$ 0.70', price_on(pricer, '2020-07-15', 'ABC', '$')
131
+ assert_equal '$ 0.80', price_on(pricer, '2020-08-15', 'ABC', '$')
132
+ end
133
+
134
+ private
135
+
136
+ def price_on(pricer, date_str, code_from, code_to)
137
+ pricer.price(Time.parse(date_str), code_from, code_to).to_s
138
+ end
139
+ end