using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Threading.Tasks;
using MarketData.Utils;

namespace MarketData.MarketDataModel
{
  public class EquityPriceShocks : List<EquityPriceShock>
  {
    public static EquityPriceShocks CreateEquityPriceShocks(Price latestPrice, OptionsParams optionsParams)
    {
      EquityPriceShocks equityPriceShocks = new EquityPriceShocks();
      int[] shocks = { 50, 25, 15, 10, 0, -5, -10, -15, -25, -50 };
      for (int index = 0; index < shocks.Length; index++)
      {
        EquityPriceShock equityPriceShock = new EquityPriceShock();
        equityPriceShock.Rank = shocks[index];
        if (equityPriceShock.Rank > 0) equityPriceShock.PriceShock = "+" + Utility.FormatNumber(equityPriceShock.Rank, 0, false).ToString() + "%";
        else if (equityPriceShock.Rank < 0) equityPriceShock.PriceShock = Utility.FormatNumber(equityPriceShock.Rank, 0, false) + "%";
        else equityPriceShock.PriceShock = "+-" + Utility.FormatNumber(equityPriceShock.Rank, 0, false) + "%";
        equityPriceShock.Premium = optionsParams.Premium;
        equityPriceShock.Rank /= 100.00;
        equityPriceShock.MarketPrice = latestPrice.Close + (latestPrice.Close * equityPriceShock.Rank);
        equityPriceShock.StrikePrice = optionsParams.Strike;

        // We expect the option to be excercises because the strike price is more favorable than market price
        // price our gain loss as if we had purchased at prevailing market price and then sold at the strike price
        if (optionsParams.Strike < equityPriceShock.MarketPrice)
        {
          equityPriceShock.PositionGL = (equityPriceShock.StrikePrice * optionsParams.Shares) - (latestPrice.Close * optionsParams.Shares);
          equityPriceShock.TransactionGL = equityPriceShock.Premium + equityPriceShock.PositionGL;
          equityPriceShock.ExpectedOutcome = "Option will be assigned.";
        }
        else
        // We expect the option to go un-excercised because the stock can be purchased in the open market more cheaply than at our strike price
        // price our gain/loss as if we had purchased at prevailing market prie and then sold at the shock price
        {
          equityPriceShock.PositionGL = (equityPriceShock.MarketPrice * optionsParams.Shares) - (latestPrice.Close * optionsParams.Shares);
          equityPriceShock.TransactionGL = equityPriceShock.Premium + equityPriceShock.PositionGL;
          equityPriceShock.ExpectedOutcome = "Option will not be assigned.";
        }
        equityPriceShocks.Add(equityPriceShock);
      }
      return equityPriceShocks;
    }
  }
  public class EquityPriceShock
  {
    public String PriceShock { get; set; }
    public double Rank { get; set; }
    public double MarketPrice { get; set; }
    public double StrikePrice { get; set; }
    public double Premium { get; set; }
    public double PositionGL { get; set; }
    public double TransactionGL { get; set; }
    public String ExpectedOutcome { get; set; }
  }
  [Serializable]
  public class OptionsParams
  {
    private int contracts;
    private double shares;
    public static int CONTRACT_SIZE = 100;
    public String Symbol { get; set; }
    public String CompanyName { get; set; }
    public double Cashdown { get; set; }
    public DateTime ExpirationDate { get; set; }
    public double Bid { get; set; }
    public double Strike { get; set; }
    public double OpenInterest { get; set; }
    public double Volume { get; set; }
    public double Shares 
    {
      get
      {
        return shares;
      }
    }
    public int Contracts 
    {
      get
      {
        return contracts;
      }
      set
      {
        contracts = value;
        shares = contracts * 100.00;
      }
    }
    public double MarketPrice { get; set; }
    public double Premium { get; set; }
    public double Volatility { get; set; }
    public int VolatilityDays { get; set; }
    public static String Header
    {
      get
      {
        StringBuilder sb = new StringBuilder();
        sb.Append("Symbol").Append(",");
        sb.Append("CompanyName").Append(",");
        sb.Append("Cashdown").Append(",");
        sb.Append("ExpirationDate").Append(",");
        sb.Append("Bid").Append(",");
        sb.Append("Strike").Append(",");
        sb.Append("OpenInterest").Append(",");
        sb.Append("Shares").Append(",");
        sb.Append("Contracts").Append(",");
        sb.Append("MarketPrice").Append(",");
        sb.Append("Premium").Append(",");
        sb.Append("Volatility").Append(",");
        sb.Append("VolatilityDays");
        return sb.ToString();
      }
    }
    public override String ToString()
    {
      StringBuilder sb = new StringBuilder();
      sb.Append("\"").Append(Symbol).Append("\"").Append(",");
      sb.Append("\"").Append(CompanyName).Append("\"").Append(",");
      sb.Append("\"").Append(Utility.FormatCurrency(Cashdown)).Append("\"").Append(",");
      sb.Append("\"").Append(Utility.DateTimeToStringMMHDDHYYYY(ExpirationDate)).Append("\"").Append(",");
      sb.Append("\"").Append(Utility.FormatCurrency(Bid)).Append("\"").Append(",");
      sb.Append("\"").Append(Utility.FormatCurrency(Strike)).Append("\"").Append(",");
      sb.Append("\"").Append(Utility.FormatNumber(OpenInterest,0,true)).Append("\"").Append(",");
      sb.Append("\"").Append(Utility.FormatNumber(Shares)).Append("\"").Append(",");
      sb.Append("\"").Append(Utility.FormatNumber(Contracts,0,true)).Append("\"").Append(",");
      sb.Append("\"").Append(Utility.FormatCurrency(MarketPrice)).Append("\"").Append(",");
      sb.Append("\"").Append(Utility.FormatCurrency(Premium)).Append("\"").Append(",");
      sb.Append("\"").Append(Utility.FormatCurrency(Volatility)).Append("\"").Append(",");
      sb.Append("\"").Append(Utility.FormatNumber(VolatilityDays,0,false)).Append("\"");
      return sb.ToString();
    }
  }
  public class MoneyType
  {
    public enum MoneyTypeEnum { AtTheMoney, InTheMoney, OutOfTheMoney, Unset };
    public MoneyTypeEnum moneyType = MoneyTypeEnum.Unset;
    public MoneyType()
    {
      moneyType = MoneyTypeEnum.Unset;
    }
    public MoneyType(double currentPrice, double strikePrice)
    {
      SetMoney(currentPrice, strikePrice);
    }
    public MoneyType.MoneyTypeEnum Money
    {
      get { return moneyType; }
      set { moneyType = value; }
    }
    public void SetMoney(double currentPrice, double strikePrice)
    {
      double upperPrice = strikePrice + .15;
      double lowerPrice = strikePrice - .15;
      if (currentPrice <= upperPrice && currentPrice >= lowerPrice)
      {
        Money = MoneyTypeEnum.AtTheMoney;
      }
      else if (strikePrice < currentPrice) Money = MoneyTypeEnum.InTheMoney;
      else Money = MoneyTypeEnum.OutOfTheMoney;
    }
    public override string ToString()
    {
      switch (Money)
      {
        case MoneyTypeEnum.AtTheMoney:
          return "At The Money";
        case MoneyTypeEnum.InTheMoney:
          return "In The Money";
        case MoneyTypeEnum.OutOfTheMoney:
          return "Out Of The Money";
        default:
          return "Unset";
      }
    }
  }


// *******************************************************************************************************************************************************************
// ************************************************************** P U T   C A L L    R A T I O    C A L C U L A T O R  ***********************************************
// *******************************************************************************************************************************************************************
  public class PutCallRatioHelper
  {
    protected class StrikeExpiryKey 
    {
      public StrikeExpiryKey(double strike,DateTime expiration)
      {
        Expiration=expiration;
        Strike=strike;
      }
      public DateTime Expiration{get;set;}
      public double Strike{get;set;}
      public override int GetHashCode() 
      {
        StringBuilder sb=new StringBuilder();
        sb.Append(Expiration.ToShortDateString()).Append(Utility.FormatCurrency(Strike));
        return sb.ToString().GetHashCode();
      }
      public override bool Equals(object obj)
      {
        return Equals(obj as StrikeExpiryKey);
      }
      public bool Equals(StrikeExpiryKey obj)
      {
        return obj.ToString().Equals(ToString());
      }
      public override String ToString()
      {
        StringBuilder sb=new StringBuilder();
        sb.Append(Expiration.ToShortDateString()).Append(Utility.FormatCurrency(Strike));
        return sb.ToString();
      }
    }
    private PutCallRatioHelper()
    {
    }
    public static void CalculatePutCallRatios(Options options)
    {
      Dictionary<StrikeExpiryKey,Option> putOptions=new Dictionary<StrikeExpiryKey,Option>();
      Dictionary<StrikeExpiryKey,Option> callOptions=new Dictionary<StrikeExpiryKey,Option>();
      foreach(Option option in options)
      {
        StrikeExpiryKey strikeExpiryKey=new StrikeExpiryKey(option.Strike,option.Expiration);
        if(option.Type.Equals(OptionTypeEnum.CallOption)&&!callOptions.ContainsKey(strikeExpiryKey))callOptions.Add(strikeExpiryKey,option);
        else if(!putOptions.ContainsKey(strikeExpiryKey))putOptions.Add(strikeExpiryKey,option);
      }
      foreach(Option option in options)
      {
        if(option.Volume==0)
        {
          option.PutCallRatio=double.NaN;
          option.CallPutRatio=double.NaN;
          continue;
        }
        StrikeExpiryKey strikeExpiryKey=new StrikeExpiryKey(option.Strike,option.Expiration);
        if(option.Type.Equals(OptionTypeEnum.PutOption))     // put options
        {
          Option callOption=null;
          option.CallPutRatio=double.NaN;
          if(!callOptions.ContainsKey(strikeExpiryKey))
          {
            option.PutCallRatio=double.NaN;
            continue;
          }
          callOption=callOptions[strikeExpiryKey];
          if(0==callOption.Volume)
          {
            option.PutCallRatio=double.NaN;
            continue;
          }
          option.PutCallRatio=(double)option.Volume/(double)callOption.Volume;
        }
        else                                                // call options
        {
          Option putOption=null;
          option.PutCallRatio=double.NaN;
          if(!putOptions.ContainsKey(strikeExpiryKey))
          {
            option.CallPutRatio=double.NaN;
            continue;
          }
          putOption=putOptions[strikeExpiryKey];
          if(0==putOption.Volume)
          {
            option.CallPutRatio=double.NaN;
            continue;
          }
          option.CallPutRatio=(double)option.Volume/(double)putOption.Volume;
        }
      }
    }
  }
// *******************************************************************************************************************************************************************

  public enum OptionTypeEnum { CallOption, PutOption };
  public class Options : List<Option>
  {
    public Options()
    {
    }
    public Options(List<Option> options)
    {
      foreach(Option option in options)Add(option);
    }
  }
  [Serializable]
  public class Option
  {
    private MoneyType moneyType = new MoneyType();
    public DateTime Expiration { get; set; }
    public String Symbol { get; set; }
    public OptionTypeEnum Type { get; set; }
    public double Strike { get; set; }
    public double PutCallRatio{get;set;}
    public double CallPutRatio{get;set;}
    public Double Ratio
    {
      get
      {
        if(Type.Equals(OptionTypeEnum.CallOption))return CallPutRatio;
        return PutCallRatio;
      }
    }
    public override int GetHashCode()
    {
      return (Symbol+Expiration.ToShortDateString()+Utility.FormatNumber(Strike,2)+(int)Type).GetHashCode();
    }
    public double Last { get; set; }
    public double Change { get; set; }
    public double Bid { get; set; }
    public double Ask { get; set; }
    public long Volume { get; set; }
    public double OpenInterest { get; set; }
    public double OptionValue { get; set; }
    public DateTime Modified { get; set; }
    public MoneyType MoneyType
    {
      get { return moneyType; }
      set { moneyType = value; }
    }
    public String MoneyTypeAsString { get { return MoneyType.ToString(); } }
    public int DaysToExpiration
    {
      get
      {
        return new DateGenerator().DaysBetween(Expiration, DateTime.Now);
      }
    }
  }

}
