rvgp 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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