app/models/iro/strategy.rb



class Iro::Strategy
  include Mongoid::Document
  include Mongoid::Timestamps
  include Mongoid::Paranoia
  store_in collection: 'iro_strategies'

  # field :slug
  # validates :slug, presence: true, uniqueness: true

  field :description

  LONG  = 'long'
  SHORT = 'short'
  field     :long_or_short, type: :string
  validates :long_or_short, presence: true

  CREDIT = 'credit'
  DEBIT  = 'debit'
  field     :credit_or_debit, type: :string
  validates :credit_or_debit, presence: true


  has_many :positions,             class_name: 'Iro::Position', inverse_of: :strategy
  has_one  :next_position,         class_name: 'Iro::Position', inverse_of: :next_strategy
  belongs_to :stock,               class_name: 'Iro::Stock',    inverse_of: :strategies
  # has_and_belongs_to_many :purses, class_name: 'Iro::Purse',    inverse_of: :strategies

  # KIND_COVERED_CALL             = 'covered_call'
  # KIND_IRON_CONDOR              = 'iron_condor'
  # KIND_LONG_CREDIT_PUT_SPREAD   = 'long_credit_put_spread'
  # KIND_LONG_DEBIT_CALL_SPREAD   = 'long_debit_call_spread'
  # KIND_SHORT_CREDIT_CALL_SPREAD = 'short_credit_call_spread'
  # KIND_SHORT_DEBIT_PUT_SPREAD   = 'short_debit_put_spread'
  # KINDS = [ nil,
  #   KIND_COVERED_CALL,
  #   KIND_IRON_CONDOR,
  #   KIND_LONG_CREDIT_PUT_SPREAD,
  #   KIND_LONG_DEBIT_CALL_SPREAD,
  #   KIND_SHORT_CREDIT_CALL_SPREAD,
  #   KIND_SHORT_DEBIT_PUT_SPREAD,
  # ];
  KIND_SPREAD = 'spread'
  KIND_WHEEL  = 'wheel'
  field :kind

  def put_call
    case kind
    # when Iro::Strategy::KIND_LONG_CREDIT_PUT_SPREAD
    #   put_call = 'PUT'
    # when Iro::Strategy::KIND_LONG_DEBIT_CALL_SPREAD
    #   put_call = 'CALL'
    # when Iro::Strategy::KIND_SHORT_CREDIT_CALL_SPREAD
    #   put_call = 'CALL'
    # when Iro::Strategy::KIND_SHORT_DEBIT_PUT_SPREAD
    #   put_call = 'PUT'
    # when Iro::Strategy::KIND_COVERED_CALL
    #   put_call = 'CALL'
    when Iro::Strategy::KIND_SPREAD
      if credit_or_debit == CREDIT
        if long_or_short == LONG
          'PUT'
        elsif long_or_short == SHORT
          'CALL'
        else
          throw 'zz5 - should never happen'
        end
      else
        throw 'zz6 - debit spreads are not implemented'
      end
    when Iro::Strategy::KIND_WHEEL
      'CALL'
    else
      throw 'zz9 - this should never happen'
    end
  end

  field :threshold_buffer_above_water, type: :float
  field :threshold_delta,              type: :float
  field :threshold_netp,               type: :float
  field :threshold_dte,                type: :integer, default: 1

  field :next_inner_delta,        type: :float
  field :next_inner_strike,       type: :float
  field :next_outer_delta,        type: :float
  field :next_outer_strike,       type: :float
  field :next_spread_amount,      type: :float # e.g. $20 for a $2000 NVDA spread
  field :next_buffer_above_water, type: :float






  def begin_delta_wheel p
    p.inner.begin_delta
  end
  def begin_delta_spread p
    p.inner.begin_delta - p.outer.begin_delta
  end


  def breakeven_covered_call p
    p.inner.strike + p.inner.begin_price
  end
  def breakeven_long_debit_call_spread p
    p.inner.strike - p.max_gain
  end
  alias_method :breakeven_short_debit_put_spread, :breakeven_long_debit_call_spread


  def end_delta_wheel p
    p.inner.end_delta
  end
  def end_delta_spread p
    p.inner.end_delta - p.outer.end_delta
  end


  # def max_gain_covered_call p
  #   p.inner.begin_price * 100 - 0.66 # @TODO: is this *100 really?
  # end
  # # def max_gain_long_credit_put_spread p
  #   ## 100 * disallowed for gameui
  #   p.inner.begin_price - p.outer.begin_price
  # end
  # def max_gain_long_debit_call_spread p
  #   ## 100 * disallowed for gameui
  #   ( p.inner.strike - p.outer.strike - p.outer.begin_price + p.inner.begin_price ) # - 2*0.66
  # end
  # def max_gain_short_credit_call_spread p
  #   p.inner.begin_price - p.outer.begin_price
  # end
  # def max_gain_short_debit_put_spread p
  #   ## 100 * disallowed for gameui
  #   ( p.outer.strike - p.inner.strike - p.outer.begin_price + p.inner.begin_price ) # - 2*0.66
  # end
  def max_gain_spread p
    ## 100 * disallowed for gameui
    ( p.outer.strike - p.inner.strike ).abs - p.outer.begin_price + p.inner.begin_price # - 2*0.66
  end
  def max_gain_wheel p
    p.inner.begin_price * 100 - 0.66 # @TODO: is this *100 really?
  end


  # def max_loss_covered_call p
  #   p.inner.begin_price*10 # just suppose 10,000%
  # end
  # def max_loss_long_credit_put_spread p
  #   out = p.inner.strike - p.outer.strike
  # end
  # def max_loss_long_debit_call_spread p
  #   out = p.outer.strike - p.inner.strike
  # end
  # def max_loss_short_debit_put_spread p # different
  #   out = p.inner.strike - p.outer.strike
  # end
  # def max_loss_short_credit_call_spread p
  #   out = p.outer.strike - p.inner.strike
  # end
  def max_loss_spread p
    ( p.outer.strike - p.inner.strike ).abs
  end
  def max_loss_wheel p
    p.inner.begin_price*10 # just suppose 10,000%
  end



  def net_amount_spread p
    p.inner.begin_price - p.inner.end_price
  end


  ## 2024-05-09 @TODO
  def next_inner_strike_on expires_on
    outs = Tda::Option.get_quotes({
      contractType: put_call,
      expirationDate: expires_on,
      ticker: stock.ticker,
    })
  end



  ##
  ## decisions
  ##

  def calc_rollp_covered_call p

    if ( p.expires_on.to_date - Time.now.to_date ).to_i < 1
      return [ 0.99, '0 DTE, must exit' ]
    end

    if ( stock.last - buffer_above_water ) < p.inner.strike
      return [ 0.98, "Last #{'%.2f' % stock.last} is " +
          "#{'%.2f' % [p.inner.strike + buffer_above_water - stock.last]} " +
          "below #{'%.2f' % [p.inner.strike + buffer_above_water]} water" ]
    end

    if p.inner.end_delta < threshold_delta
      return [ 0.61, "Delta #{p.inner.end_delta} is lower than #{threshold_delta} threshold." ]
    end

    if 1 - p.inner.end_price/p.inner.begin_price > threshold_netp
      return [ 0.51, "made enough #{'%.02f' % [(1.0 - p.inner.end_price/p.inner.begin_price )*100]}% profit." ]
    end

    return [ 0.33, '-' ]
  end

  ## @TODO
  def calc_rollp_long_debit_call_spread p

    if ( p.expires_on.to_date - Time.now.to_date ).to_i < 1
      return [ 0.99, '0 DTE, must exit' ]
    end
    if ( p.expires_on.to_date - Time.now.to_date ).to_i < 2
      return [ 0.99, '1 DTE, must exit' ]
    end

    if ( stock.last - buffer_above_water ) < p.inner.strike
      return [ 0.95, "Last #{'%.2f' % stock.last} is " +
          "#{'%.2f' % [stock.last - p.inner.strike - buffer_above_water]} " +
          "below #{'%.2f' % [p.inner.strike + buffer_above_water]} water" ]
    end

    if p.inner.end_delta < threshold_delta
      return [ 0.79, "Delta #{p.inner.end_delta} is lower than #{threshold_delta} threshold." ]
    end

    if 1 - p.inner.end_price/p.inner.begin_price > threshold_netp
      return [ 0.51, "made enough #{'%.02f' % [(1.0 - p.inner.end_price/p.inner.begin_price )*100]}% profit^" ]
    end

    return [ 0.33, '-' ]
  end

  ## @TODO
  def calc_rollp_short_debit_put_spread p

    if ( p.expires_on.to_date - Time.now.to_date ).to_i <= min_dte
      return [ 0.99, "< #{min_dte}DTE, must exit" ]
    end

    if stock.last + buffer_above_water > p.inner.strike
      return [ 0.98, "Last #{'%.2f' % stock.last} is " +
          "#{'%.2f' % [stock.last + buffer_above_water - p.inner.strike]} " +
          "above #{'%.2f' % [p.inner.strike - buffer_above_water]} water" ]
    end

    if p.inner.end_delta.abs < threshold_delta.abs
      return [ 0.79, "Delta #{p.inner.end_delta} is lower than #{threshold_delta} threshold." ]
    end

    if p.net_percent > threshold_netp
      return [ 0.51, "made enough #{'%.0f' % [p.net_percent*100]}% > #{"%.2f" % [threshold_netp*100]}% profit," ]
    end

    return [ 0.33, '-' ]
  end


  ## scopes

  def self.for_ticker ticker
    where( ticker: ticker )
  end


  def slug
    "#{long_or_short} #{credit_or_debit} #{kind} #{stock}"
  end
  def to_s
    slug
  end
  def self.list long_or_short = nil
    these = long_or_short ? where( long_or_short: long_or_short ) : all
    [[nil,nil]] + these.map { |ttt| [ ttt, ttt.id ] }
  end
end