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 > 0 dates = @rentabilities.keys @start_date, @end_date = [dates[0], 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) _start = months_ago ? (end_date - months_ago.months + 1.month).beginning_of_month : nil (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) vol = MathUtil.volatility(series) date = dates_with_position.fetch(low + days) { dates_with_position.last } [date, vol] if date end end def to_a dates_with_position.map do |date| if @rentabilities[date] [date.to_time.to_i, @rentabilities[date].share] 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_share = rentabilities[last_date].try(:share) || 1 rentability = (this_value / last_value - 1).round(8) new_share = (last_share * (1 + rentability)).round(8) line = RentabilityLine.new(this_date, rentability, nil, new_share, true) after_parse(this_date, last_date, rentabilities, line) end end end