app/models/iro/datapoint.rb



##
## Datapoints are at most daily!
## See Priceitem for intra-day data
##
class Iro::Datapoint
  include Mongoid::Document
  include Mongoid::Timestamps
  store_in collection: 'iro_datapoints'

  field :kind
  validates :kind, presence: true
  index({ kind: -1 })
  KIND_CRYPTO   = 'CRYPTO'
  KIND_STOCK    = 'STOCK'
  KIND_OPTION   = 'OPTION' ## but not PUT or CALL
  KIND_CURRENCY = 'CURRENCY'
  KIND_TREASURY = 'TREASURY'

  field :symbol ## ticker, but use 'symbol' here
  ## crypto
  SYMBOL_BTC = 'BTC'
  SYMBOL_ETH = 'ETH'
  ## currencies
  SYMBOL_JPY = 'JPY'
  SYMBOL_COP = 'COP'
  SUMBOL_EUR = 'EUR'
  ## treasuries
  SYMBOL_T1MO  = 'T1MO'
  SYMBOL_T2MO  = 'T2MO'
  SYMBOL_T3MO  = 'T3MO'
  SYMBOL_T4MO  = 'T4MO'
  SYMBOL_T6MO  = 'T6MO'
  SYMBOL_T1YR  = 'T1YR'
  SYMBOL_T2YR  = 'T2YR'
  SYMBOL_T3YR  = 'T3YR'
  SYMBOL_T5YR  = 'T5YR'
  SYMBOL_T7YR  = 'T7YR'
  SYMBOL_T10YR = 'T10YR'
  SYMBOL_T20YR = 'T20YR'
  SYMBOL_T30YR = 'T30YR'

  field :date, type: Date
  index({ kind: -1, date: -1 })
  validates :date, uniqueness: { scope: [ :symbol ] }

  field :quote_at, type: DateTime
  index({ kind: -1, quote_at: -1 })
  validates :quote_at, uniqueness: { scope: [ :kind, :symbol ] } ## scope-by-kind is unnecessary here? _vp_ 2024-08-08

  field :open, type: Float
  field :high, type: Float
  field :low, type: Float
  def close;    value;    end
  def close= a; value= a; end

  field :value, type: Float
  validates :value, presence: true


  field :volume, type: Integer


  def self.test_0trash
    add_fields = { '$addFields':  {
      'date_string': {
        '$dateToString': { 'format': "%Y-%m-%d", 'date': "$created_at" }
      }
    } }
    # group = { '$group': {
    #   '_id': "$date_string",
    #   'my_doc': { '$first': "$$ROOT" }
    # } }
    group = { '$group': {
      '_id': "$date",
      'my_doc': { '$first': "$$ROOT" }
    } }
    lookup = { '$lookup': {
      'from':         'iro_dates',
      'localField':   'date_string',
      'foreignField': 'date',
      'as':           'dates',
    } }
    lookup_merge = { '$replaceRoot': {
      'newRoot': { '$mergeObjects': [
        { '$arrayElemAt': [ "$dates", 0 ] }, "$$ROOT"
      ] }
    } }
    match = { '$match': {
      'kind': 'some-type',
      'created_at': {
        '$gte': '2023-12-01'.to_datetime,
        '$lte': '2023-12-31'.to_datetime,
      }
    } }

    outs = Iro::Datapoint.collection.aggregate([
      add_fields,
      lookup, lookup_merge,
      match,

      { '$sort': { 'date_string': 1 } },
      group,
      # { '$replaceRoot': { 'newRoot': "$my_doc" } },
      # { '$project': { '_id': 0, 'date_string': 1, 'value': 1 } },
    ])

    puts! 'result'
    pp outs.to_a
    # puts! outs.to_a, 'result'
  end

  def self.test
    lookup = { '$lookup': {
      'from':         'iro_datapoints',
      'localField':   'date',
      'foreignField': 'date',
      'pipeline': [
        { '$sort': { 'value': -1 } },
      ],
      'as':           'datapoints',
    } }
    lookup_merge = { '$replaceRoot': {
      'newRoot': { '$mergeObjects': [
        { '$arrayElemAt': [ "$datapoints", 0 ] }, "$$ROOT"
      ] }
    } }


    match = { '$match': {
      'date': {
        '$gte': '2023-12-25',
        '$lte': '2023-12-31',
      }
    } }

    group = { '$group': {
      '_id': "$date",
      'my_doc': { '$first': "$$ROOT" }
    } }

    outs = Iro::Date.collection.aggregate([
      match,

      lookup,
      lookup_merge,

      group,
      { '$replaceRoot': { 'newRoot': "$my_doc" } },
      # { '$replaceRoot': { 'newRoot': "$my_doc" } },


      { '$project': { '_id': 0, 'date': 1, 'value': 1 } },
      { '$sort': { 'date': 1 } },
    ])

    puts! 'result'
    pp outs.to_a
    # puts! outs.to_a, 'result'
  end

  def self.import_stock symbol:, path:
    csv = CSV.read(path, headers: true)
    csv.each do |row|
      flag = create({
        kind:     KIND_STOCK,
        symbol:   symbol,
        date:     row['Date'],
        quote_at: row['Date'],

        volume: row['Volume'],

        open:  row['Open'],
        high:  row['High'],
        low:   row['Low'],
        value: row['Close'],
      })
      if flag.persisted?
        print '^'
      else
        puts flag.errors.messages
      end
    end
    puts 'ok'
  end

end