# frozen_string_literal: true require 'memoist' require 'roi/financial_result' require 'roi/rentability_periods' require 'math_util' require 'active_support' require 'active_support/core_ext' module ROI class Rentability extend Memoist include RentabilityPeriods attr_accessor :rentabilities attr_reader :id, :start_date, :end_date, :skip_on_gap def initialize(options) @id = options[:id].try(:to_i) @start_date = options[:start_date] @end_date = options[:end_date] || Date.today - 1 @skip_on_gap = false post_initialize(options) end def post_initialize(options); end def calculate parse_rentabilities if @rentabilities.count.positive? dates = @rentabilities.keys @start_date = dates[0] @end_date = dates[-1] end self end def financial FinancialResult.new(rentabilities, start_date, end_date) end def dates_with_position rentabilities.select { |_, rentability| rentability.have_position }.keys end def rentabilities_array(start_date = nil, end_date = nil) start_date ||= @start_date end_date ||= @end_date filtered = @rentabilities.values.select do |val| val.date.between?(start_date, end_date) end filtered.map(&:rentability).compact end def volatility(months_ago = nil) if months_ago start = (end_date - months_ago.months + 1.month).beginning_of_month end (volatility_in_window(nil, start).first || [])[1] end def volatility_in_window(days = nil, start_date = nil) filtered = rentabilities_array(start_date) days ||= filtered.size 0.upto(filtered.size - days).map do |low| series = filtered.slice(low, days + 1) date = dates_with_position.fetch(low + days) { dates_with_position.last } [date, MathUtil.volatility(series)] if date end end def to_a dates_with_position.map do |date| if @rentabilities[date] [date.strftime('%Y-%m-%d'), @rentabilities[date].quota - 1] end end.compact end protected def before_parse_rentabilities; end def work_days start_date && end_date ? WorkDay.between(start_date, end_date) : [] end memoize :work_days private def parse_rentabilities @rentabilities = {} before_parse_rentabilities work_days.each_cons(2) do |last_date, date| parse(date, last_date, @rentabilities) end end def parse(this_date, last_date, rentabilities) this_value = rentabilities_source(this_date) last_value = rentabilities_source(last_date) return rentabilities unless this_value && last_value last_quota = rentabilities[last_date].try(:quota) || 1 rentability = (this_value / last_value - 1).round(8) new_quota = (last_quota * (1 + rentability)).round(8) line = RentabilityLine.new(this_date, rentability, new_quota, true) after_parse(this_date, last_date, rentabilities, line) end end end