Module: Timeprice::Compare::Series Private

Defined in:
lib/timeprice/compare/series.rb

Overview

This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.

Annual sample points for the result-card chart. Composes the same FX leg as run and a year-by-year measured-or-forecast CPI ratio for the destination country.

Each point is ‘{ date: “YYYY-01”, amount:, measured: }`. Forecast points additionally carry `:low` and `:high` for the ±1σ band.

Constant Summary collapse

DEFAULT_AMOUNT =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

100.0

Class Method Summary collapse

Class Method Details

.build_context(from:, to:, amount:, forecast:) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/timeprice/compare/series.rb', line 32

def build_context(from:, to:, amount:, forecast:)
  from_point, to_point, to_country = coerce_points(from, to)
  data = DataLoader.load_cpi(to_country)
  last_key, last_cpi = last_known(data)
  last_year = Forecast::Cagr.parse(last_key).year

  {
    source_in_dest: source_amount_in_dest(amount, from_point, to_point),
    source_cpi: CpiLookup.new(data).at(from_point.date.to_s).value.to_f,
    lookup: CpiLookup.new(data),
    last_year: last_year,
    last_cpi: last_cpi,
    from_year: Forecast::Cagr.parse(from_point.date.to_s).year,
    to_year: Forecast::Cagr.parse(to_point.date.to_s).year,
    stats: forecast_stats(data, last_key, forecast, to_point, last_year),
  }
end

.coerce_points(from, to) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



50
51
52
53
54
55
56
57
# File 'lib/timeprice/compare/series.rb', line 50

def coerce_points(from, to)
  from_point = Point.coerce(from)
  to_point   = Point.coerce(to)
  to_country = Supported.country_for_currency(to_point.currency)
  fail UnsupportedCurrency, to_point.currency unless to_country

  [from_point, to_point, to_country]
end

.for(from:, to:, forecast: false, amount: DEFAULT_AMOUNT) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



27
28
29
30
# File 'lib/timeprice/compare/series.rb', line 27

def for(from:, to:, forecast: false, amount: DEFAULT_AMOUNT)
  ctx = build_context(from: from, to: to, amount: amount, forecast: forecast)
  (ctx[:from_year]..ctx[:to_year]).map { |y| point_for(y, ctx) }
end

.forecast_point(y:, last_year:, last_cpi:, source_in_dest:, source_cpi:, stats:) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/timeprice/compare/series.rb', line 100

def forecast_point(y:, last_year:, last_cpi:, source_in_dest:, source_cpi:, stats:)
  yrs = y - last_year
  mid  = last_cpi * ((1.0 + stats[:cagr])**yrs)
  low  = last_cpi * ((1.0 + stats[:cagr] - stats[:sigma_yoy])**yrs)
  high = last_cpi * ((1.0 + stats[:cagr] + stats[:sigma_yoy])**yrs)
  {
    date: "#{y}-01",
    amount: source_in_dest * (mid / source_cpi),
    low: source_in_dest * (low / source_cpi),
    high: source_in_dest * (high / source_cpi),
    measured: false,
  }
end

.forecast_stats(data, last_key, forecast, to_point, last_year) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



72
73
74
75
76
77
78
79
80
# File 'lib/timeprice/compare/series.rb', line 72

def forecast_stats(data, last_key, forecast, to_point, last_year)
  return nil unless forecast && Forecast::Cagr.parse(to_point.date.to_s).year > last_year

  Forecast::Cagr.compute(
    series: Forecast::CpiForecaster.pick_series(data),
    last_date: last_key,
    window_years: Forecast::CpiForecaster::DEFAULT_WINDOW_YEARS
  )
end

.last_known(data) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



66
67
68
69
70
# File 'lib/timeprice/compare/series.rb', line 66

def last_known(data)
  annual_or_monthly = Forecast::CpiForecaster.pick_series(data)
  last_key = annual_or_monthly.keys.max_by { |k| Forecast::Cagr.parse(k) }
  [last_key, annual_or_monthly[last_key].to_f]
end

.measured_point(y:, lookup:, source_in_dest:, source_cpi:) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



93
94
95
96
97
98
# File 'lib/timeprice/compare/series.rb', line 93

def measured_point(y:, lookup:, source_in_dest:, source_cpi:)
  cpi_y = lookup.at(y.to_s).value.to_f
  { date: "#{y}-01", amount: source_in_dest * (cpi_y / source_cpi), measured: true }
rescue DataNotFound
  nil
end

.point_for(year, ctx) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



82
83
84
85
86
87
88
89
90
91
# File 'lib/timeprice/compare/series.rb', line 82

def point_for(year, ctx)
  if year <= ctx[:last_year]
    measured_point(y: year, lookup: ctx[:lookup],
                   source_in_dest: ctx[:source_in_dest], source_cpi: ctx[:source_cpi])
  else
    forecast_point(y: year, last_year: ctx[:last_year], last_cpi: ctx[:last_cpi],
                   source_in_dest: ctx[:source_in_dest], source_cpi: ctx[:source_cpi],
                   stats: ctx[:stats])
  end
end

.source_amount_in_dest(amount, from_point, to_point) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



59
60
61
62
63
64
# File 'lib/timeprice/compare/series.rb', line 59

def source_amount_in_dest(amount, from_point, to_point)
  Exchange.convert(
    amount: amount, from: from_point.currency,
    to: to_point.currency, date: from_point.fx_anchor_date
  ).amount
end