app/models/iro/stock.rb



include Math
require 'business_time'

##
## https://www.macrotrends.net/stocks/charts/META/meta-platforms/stock-price-history
##
class Iro::Stock
  include Mongoid::Document
  include Mongoid::Timestamps
  include Mongoid::Paranoia
  store_in collection: 'iro_stocks'

  STATUS_ACTIVE   = 'active'
  STATUS_INACTIVE = 'inactive'
  STATUSES        = [ nil, 'active', 'inactive' ]
  def self.active
    where( status: STATUS_ACTIVE )
  end
  field :status, default: STATUS_ACTIVE

  field :ticker
  validates :ticker, uniqueness: true, presence: true
  index({ ticker: -1 }, { unique: true })
  def symbol;    ticker;     end
  def symbol= a; ticker = a; end

  field :last, type: :float
  field :options_price_increment, type: :float

  field :stdev, type: :float

  has_many :positions,  class_name: 'Iro::Position', inverse_of: :stock
  has_many :strategies, class_name: 'Iro::Strategy', inverse_of: :stock
  # has_many :purses,     class_name: 'Iro::Purse',    inverse_of: :stock
  has_many :options,    class_name: 'Iro::Option',   inverse_of: :stock
  has_many :priceitems, inverse_of: :stock

  belongs_to :user
  LONG_ONLY     = 'long-only'
  LONG_OR_SHORT = 'long-or-short'
  SHORT_ONLY    = 'short-only'
  field :sentiment, default: LONG_OR_SHORT
  field :sentiment_num, default: 0 # 1 is very long, -1 is very short

  default_scope { order_by({ ticker: :asc }) }

  ## my_find
  def self.f ticker
    self.find_by ticker: ticker
  end

  def to_s
    ticker
  end
  def self.list
    [[nil,nil]] + all.map { |sss| [ sss.ticker, sss.id ] }
  end
  def self.tickers_list
    [[nil,nil]] + all.map { |sss| [ sss.ticker, sss.ticker ] }
  end

=begin
  stock = Iro::Stock.find_by( ticker: 'NVDA' )

  duration = 1.month
  stock.volatility_from_mo

  duration = 1.year
  stock.volatility_from_yr

=end
  field :volatility, type: :float
  def volatility duration: 1.year, recompute: false
    if self[:volatility]
      if !recompute
        return self[:volatility]
      end
    end

    stock = self
    begin_on = Time.now - duration - 1.day
    points = Iro::Datapoint.where( kind: 'STOCK', symbol: stock.ticker,
      :date.gte => begin_on,
    ).order_by( date: :asc )

    puts! [points.first.date, points.last.date], "from,to"

    points_p = []
    points.each_with_index do |p, idx|
      next if idx == 0
      prev = points[idx-1]

      out = p.value / prev.value - 1
      points_p.push out
    end
    n = points_p.length

    avg = points_p.reduce(&:+) / n
    _sum_of_sq = []
    points_p.map do |p|
      _sum_of_sq.push( ( p - avg )*( p - avg ) )
    end
    sum_of_sq = _sum_of_sq.reduce( &:+ ) / n

    # n_periods = begin_on.to_date.business_days_until( Date.today )
    out = Math.sqrt( sum_of_sq )*sqrt( n )
    adjustment = 2.0
    out = out * adjustment
    puts! out, 'volatility (adjusted)'
    self.update volatility: out
    return out
  end

  def volatility_from_mo
    volatility( duration: 1.month )
  end
  def volatility_from_yr
    volatility( duration: 1.year )
  end
  def stdev recompute: nil
    if !self[:stdev] || recompute
      out = volatility_from_yr
      self[:stdev] = out
      save( validate: false )
      return out
    else
      self[:stdev]
    end
  end

  ## stdev
  ## From: https://stackoverflow.com/questions/19484891/how-do-i-find-the-standard-deviation-in-ruby
  # contents = [1,2,3,4,5,6,7,8,9]
  # n = contents.size             # => 9
  # contents.map!(&:to_f)         # => [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
  # mean = contents.reduce(&:+)/n # => 5.0
  # sum_sqr = contents.map {|x| x * x}.reduce(&:+) # => 285.0
  # std_dev = Math.sqrt((sum_sqr - n * mean * mean)/(n-1)) # => 2.7386127875258306


end