using System;
using System.Collections.Generic;
using MarketData.MarketDataModel;
using MarketData.DataAccess;
using MarketData.Utils;
using System.Linq;
using MarketData.Cache;
using MarketData.Generator.Model;

namespace MarketData.Generator.Momentum
{
  public class MomentumBacktest
  {
    private double NonTradeableCash{get;set;}
    private double CashBalance{get;set;}
    private MGConfiguration Configuration{get;set;}
    private int HoldingPeriod{get{return Configuration.HoldingPeriod;}}
    private int MaxPositions{get{return Configuration.MaxPositions;}}
    private ActivePositions ActivePositions{get;set;}
    private Positions AllPositions{get;set;}
    private int Cycle{get;set;}
    private DateTime TradeDate{get;set;}
    private DateTime StartDate{get;set;}
    private DateTime AnalysisDate{get;set;}
    private String PathSessionFileName{get;set;}
// ******************************************************************************************************************************************************
//************************************************************** D I S P L A Y  G A I N  L O S S    *****************************************************
// ******************************************************************************************************************************************************
    public static void DisplayGainLoss(String paramPathSessionFileName)
    {
      Profiler profiler=new Profiler();
      ModelPerformanceSeries performanceSeries=GetModelPerformance(paramPathSessionFileName);
      if(null==performanceSeries)return;
      MDTrace.WriteLine("Date,Exposure,MarketValue,GainLossDOD,GainLoss,CumulativeGainLoss,R,(1+R),CumProd,CumProd-1,ClosedPositions");
      foreach(ModelPerformanceItem modelPerformanceItem in performanceSeries)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\",\"{8}\",\"{9}\",\"{10}\"",
          modelPerformanceItem.Date.ToShortDateString(),
          Utility.FormatCurrency(modelPerformanceItem.Exposure),                                                                         
          Utility.FormatCurrency(modelPerformanceItem.MarketValue),                                                                     
          Utility.FormatCurrency(modelPerformanceItem.GainLossDOD),
          Utility.FormatCurrency(modelPerformanceItem.GainLoss), 
          Utility.FormatCurrency(modelPerformanceItem.CumulativeGainLoss),
          Utility.FormatNumber(modelPerformanceItem.R,4),
          Utility.FormatNumber(modelPerformanceItem.OnePlusR,4),
          Utility.FormatNumber(modelPerformanceItem.CumProd,4),
          Utility.FormatNumber(modelPerformanceItem.CumProdMinusOne,4), 
          modelPerformanceItem.ClosedPositions));
      }
      MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Done, took {0}(ms)",profiler.End()));
    }

    public static ModelPerformanceSeries GetModelPerformance(String paramPathSessionFileName)
    {
      try
      {
        MGSessionParams sessionParams=MGSessionManager.RestoreSession(paramPathSessionFileName);
        return GetModelPerformance(sessionParams);
      }
      catch(Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
        return null;
      }
    }
// Calculates the expectation for the model ( Percent of Winning Trades * Average Gain)/(Percent Losing Trades * Average Loss)
// The expectation should be above zero
    public static ModelStatistics GetModelStatistics(MGSessionParams sessionParams)
    {
      ModelStatistics modelStatistics=new ModelStatistics();
      try
      {
        if(null==sessionParams||null==sessionParams.AllPositions||0==sessionParams.AllPositions.Count) return modelStatistics;
        double totalTrades=sessionParams.AllPositions.Count;
        double winningTrades=sessionParams.AllPositions.Where(x => x.GainLoss>=0.00).Count();
        double losingTrades=sessionParams.AllPositions.Where(x => x.GainLoss<0.00).Count();
        double averageWinningTrade=sessionParams.AllPositions.Where(x => x.GainLoss>=0.00).Average(x => x.GainLossPcnt)*100.00;
        double averageLosingTrade=sessionParams.AllPositions.Where(x => x.GainLoss<0.00).Average(x => x.GainLossPcnt)*100.00;
        double percentWinningTrades=(winningTrades/(double)sessionParams.AllPositions.Count)*100.00;
        double percentLosingTrades=(losingTrades/(double)sessionParams.AllPositions.Count)*100.00;
        double expectation=(percentWinningTrades*averageWinningTrade)/(percentLosingTrades*Math.Abs(averageLosingTrade));

        modelStatistics.TotalTrades=(long)totalTrades;
        modelStatistics.WinningTrades=(long)winningTrades;
        modelStatistics.LosingTrades=(long)losingTrades;
        modelStatistics.AverageWinningTradePercentGain=averageWinningTrade;
        modelStatistics.AverageLosingTradePercentLoss=averageLosingTrade;
        modelStatistics.WinningTradesPercent=percentWinningTrades;
        modelStatistics.LosingTradesPercent=percentLosingTrades;
        modelStatistics.Expectancy=expectation;
        return modelStatistics;
      }
      catch(Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
        return modelStatistics;
      }
    }

    public static ModelPerformanceSeries GetModelPerformance(MGSessionParams sessionParams)
    {
      Profiler profiler=new Profiler();
      ModelPerformanceSeries performanceSeries=new ModelPerformanceSeries();
      DateGenerator dateGenerator=new DateGenerator();
      try
      {
        if(null==sessionParams)return null;
        MarketData.Generator.Momentum.Positions combinedPositions=sessionParams.GetCombinedPositions();

        DateTime minDate=combinedPositions.Min(x => x.PurchaseDate);
        DateTime maxDate=PricingDA.GetLatestDate();
        double prevGainLoss=double.NaN;
        LocalPriceCache.GetInstance().RemoveDate(maxDate);
        List<DateTime> historicalDates=dateGenerator.GenerateHistoricalDates(minDate,maxDate);
        foreach(DateTime currentDate in historicalDates)
        {
          MarketData.Generator.Momentum.Positions openPositions=new MarketData.Generator.Momentum.Positions(combinedPositions.Where(x => (x.PurchaseDate<=currentDate&&(!Utility.IsEpoch(x.SellDate)&&x.SellDate>currentDate))||(x.PurchaseDate<=currentDate&&Utility.IsEpoch(x.SellDate))).ToList());
          MarketData.Generator.Momentum.Positions closedPositions=new MarketData.Generator.Momentum.Positions(combinedPositions.Where(x => (!Utility.IsEpoch(x.SellDate)&&x.SellDate.Equals(currentDate))).ToList());
          if(0==openPositions.Count&&0==closedPositions.Count) continue;
          double gainLoss=0.00;
          double gainLossClosedPositions=0.00;
          double exposure=0.00;
          double marketValue=0.00;
          ModelPerformanceItem performanceItem=new ModelPerformanceItem();
          foreach(MarketData.Generator.Momentum.Position openPosition in openPositions)
          {
            exposure+=openPosition.Shares*openPosition.PurchasePrice;
            if(!LocalPriceCache.GetInstance().ContainsPrice(openPosition.Symbol,currentDate))
            {
              Prices prices=PricingDA.GetPricesForward(openPosition.Symbol,currentDate,PricingDA.ForwardLookingDays);
              LocalPriceCache.GetInstance().Add(prices);
            }
            Price price=LocalPriceCache.GetInstance().GetPrice(openPosition.Symbol,currentDate);
            if(null==price)
            {
              MDTrace.WriteLine(LogLevel.DEBUG,String.Format("No price for {0} on {1}",openPosition.Symbol,currentDate.ToShortDateString()));
            }
            else
            {
              gainLoss+=((price.Close*openPosition.Shares)-(openPosition.PurchasePrice*openPosition.Shares));
              marketValue+=(price.Close*openPosition.Shares);
            }
          }
          foreach(MarketData.Generator.Momentum.Position closedPosition in closedPositions)
          {
            double gainLossPosition=(closedPosition.CurrentPrice*closedPosition.Shares)-(closedPosition.PurchasePrice*closedPosition.Shares);
            gainLossClosedPositions+=gainLossPosition;
          }
          performanceItem.Date=currentDate;
          performanceItem.Exposure=exposure;
          performanceItem.MarketValue=marketValue;
          performanceItem.GainLossDOD=double.IsNaN(prevGainLoss)?gainLoss:(gainLoss-prevGainLoss)+gainLossClosedPositions;
          performanceItem.GainLoss=gainLoss+gainLossClosedPositions;
          performanceItem.ClosedPositions=closedPositions.Count>0?true:false;
          performanceSeries.Add(performanceItem);
          prevGainLoss=gainLoss;
        }
        performanceSeries.CalculatePerformance();
        return performanceSeries;
      }
      catch(Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
        return null;
      }
      finally
      {
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Done, total took {0}(ms)",profiler.End()));
      }
    }
// ******************************************************************************************************************************************************
//************************************************************** D I S P L A Y  S E S S I O N   *****************************************************
// ******************************************************************************************************************************************************
    public void DisplaySession(String paramPathSessionFileName)
    {
      if(null==paramPathSessionFileName)return;
      PathSessionFileName=paramPathSessionFileName;
      MGSessionParams sessionParams=null;
      if(null==(sessionParams=RestoreSession()))
      {
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Error loading session file {0}",paramPathSessionFileName));
        return;
      }
      Console.WriteLine(String.Format("SessionFile:{0} Last Updated:{1}",paramPathSessionFileName,sessionParams.LastUpdated));
      Configuration.DisplayConfiguration();
      MDTrace.WriteLine(LogLevel.DEBUG,"************** A L L   P O S I T I O N S  *************");
      AllPositions=new Positions((from Position position in AllPositions orderby position.PurchaseDate ascending select position).ToList());
      AllPositions.Display();
      MDTrace.WriteLine(LogLevel.DEBUG,"************** A C T I V E   P O S I T I O N S  *************");
      ActivePositions.Display();
      MDTrace.WriteLine(LogLevel.DEBUG, "************** T O P  G A I N E R S  *************");
      AllPositions.DisplayTopFive();
      MDTrace.WriteLine(LogLevel.DEBUG, "************** T O P  L O S S E R S   *************");
      AllPositions.DisplayBottomFive();
//      DisplayBalanceFromPositions();
      DisplayLatestModelPerformance(paramPathSessionFileName);
      MDTrace.WriteLine(LogLevel.DEBUG,String.Format("StartDate:{0}",Utility.DateTimeToStringMMHDDHYYYY(StartDate)));
      MDTrace.WriteLine(LogLevel.DEBUG,String.Format("TradeDate:{0}",Utility.DateTimeToStringMMHDDHYYYY(TradeDate)));
      MDTrace.WriteLine(LogLevel.DEBUG,String.Format("AnalysisDate:{0}",Utility.DateTimeToStringMMHDDHYYYY(AnalysisDate)));
      MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Next Slot:{0}",Cycle));
    }
// ******************************************************************************************************************************************************
//******************************************************* L I Q U I D A T E   A L L  P O S I T I O N S    ***********************************************
// ******************************************************************************************************************************************************
    public void MGLiquididate(String pathSessionFile,DateTime? tradeDate)
    {
      if (null == pathSessionFile) return;
      MGSessionParams sessionParams=null;

      PathSessionFileName = pathSessionFile;
      if (null == (sessionParams = RestoreSession()))
      {
        MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Error loading session file {0}", pathSessionFile));
        return;
      }
      MDTrace.WriteLine(LogLevel.DEBUG, "************** L I Q U I D A T E   P O S I T I O N S  *************");
      if (null == ActivePositions || 0 == ActivePositions.Count)
      {
        MDTrace.WriteLine(LogLevel.DEBUG, String.Format("No active positions in file {0}", pathSessionFile));
        return;
      }
      if(null==tradeDate)tradeDate = PricingDA.GetLatestDate(ActivePositions.GetSymbols());
      MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Trade date:{0}", Utility.DateTimeToStringMMHDDHYYYY(tradeDate.Value)));
      for (int slotIndex = 0; slotIndex < ActivePositions.Count; slotIndex++)
      {
        Positions slotPositions = ActivePositions[slotIndex];
        SellPositions(slotPositions, tradeDate.Value);
        MDTrace.WriteLine(LogLevel.DEBUG, "********************* S E L L  *********************");
        slotPositions.Display();
        AllPositions.Add(slotPositions);
        DisplaySales(slotPositions, TradeDate);
        CashBalance += slotPositions.MarketValue;
        ActivePositions[slotIndex].Clear();
      }
      GBPriceCache.GetInstance().Dispose();
      SaveSession();
    }
// ******************************************************************************************************************************************************
// ****************************************************************** C L O S E    **********************************************************************
// ******************************************************************************************************************************************************
    public bool ClosePosition(String symbol,DateTime purchaseDate,DateTime sellDate,double sellPrice,String pathSessionFile)
    {
      if(null==pathSessionFile) return false;
      MGSessionParams sessionParams=null;

      PathSessionFileName=pathSessionFile;
      if(null==(sessionParams=RestoreSession()))
      {
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Error loading session file {0}",pathSessionFile));
        return false;
      }
      if(!BackupSession()) return false;
      Positions activePositions = ActivePositions.GetPositions();
      Position position=activePositions.Where(x => x.Symbol.Equals(symbol) && x.PurchaseDate.Equals(purchaseDate)).FirstOrDefault();
      if(null==position)  // if it is not in the active positions then the position is already closed and we are modifying either the sell date or the sell price
      {
        position=AllPositions.Where(x => x.Symbol.Equals(symbol) && x.PurchaseDate.Equals(purchaseDate)).FirstOrDefault();
        if(null==position)
        {
          MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot locate position for symbol '{0}' purchased on {1}.",symbol,purchaseDate.ToShortDateString()));
          return false;
        }
        position.SellDate = sellDate;
        CashBalance -= position.MarketValue;
        position.CurrentPrice = sellPrice;
        CashBalance += position.MarketValue;
        SaveSession();
        return true;
      }
      position.SellDate = sellDate;
      position.CurrentPrice = sellPrice;
      CashBalance += position.MarketValue;
      ActivePositions.Remove(position);
      AllPositions.Add(position);
      MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Position for symbol '{0}' purchased on {1} is now closed.",symbol,purchaseDate.ToShortDateString()));
      SaveSession();
      return true;
    }
// ******************************************************************************************************************************************************
// *************************************************************************** E D I T ******************************************************************
// ******************************************************************************************************************************************************
    public bool EditPosition(String symbol,DateTime purchaseDate,double purchasePrice,String pathSessionFile)
    {
      if(null==pathSessionFile) return false;
      PathSessionFileName=pathSessionFile;
      MGSessionParams sessionParams=null;
      if(null==(sessionParams=RestoreSession()))
      {
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Error loading session file {0}",pathSessionFile));
        return false;
      }
      if(!BackupSession()) return false;

      Positions activePositions = ActivePositions.GetPositions();

      Position position=activePositions.Where(x => x.Symbol.Equals(symbol) && x.PurchaseDate.Equals(purchaseDate)).FirstOrDefault();
      if(null==position)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot locate position for symbol '{0}' purchased on {1}.",symbol,purchaseDate.ToShortDateString()));
        return false;
      }
      if(!position.PurchaseDate.Equals(purchaseDate)) position.PurchaseDate=purchaseDate;
      if(!position.PurchasePrice.Equals(purchasePrice))
      {
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Adjusting Cash for Position for symbol '{0}' purchased on {1}.  Original Price: {2}  New Price: {3} Change in Cash: {4}",
          symbol,purchaseDate.ToShortDateString(),
          Utility.FormatCurrency(position.PurchasePrice),
          Utility.FormatCurrency(purchasePrice),
          Utility.FormatCurrency((position.PurchasePrice-purchasePrice)*position.Shares)));
        CashBalance+=(position.PurchasePrice-purchasePrice)*position.Shares;
        position.PurchasePrice=purchasePrice;
      }
      SaveSession();
      MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Position for symbol '{0}' purchased on {1} has been modified and saved.",symbol,purchaseDate.ToShortDateString()));
      return true;
    }

// ******************************************************************************************************************************************************
// ****************************************************************** B A C K T E S T   *****************************************************************
// ******************************************************************************************************************************************************
//  Ideally, startDate should be November,February,May,August 
// paramStartDate is startDate  {ORIGINAL START DATE}
// paramAnalysisDate is endDate {TODAY}
    public BacktestResult PerformBacktest(DateTime paramStartDate,DateTime paramAnalysisDate,String paramPathSessionFileName,MGConfiguration configuration)
    {
      BacktestResult backTestResult=new BacktestResult();
      DateGenerator dateGenerator=new DateGenerator();
      Configuration=configuration;
      CashBalance=Configuration.InitialCash;
      ActivePositions=new ActivePositions();
      AllPositions=new Positions();
      StartDate=paramStartDate;
      TradeDate=paramStartDate;
      AnalysisDate=paramAnalysisDate;
      PathSessionFileName=paramPathSessionFileName;
      MGSessionParams sessionParams=null;

      Cycle=0;
      if(AnalysisDate.Date>Today().Date)return backTestResult;
      if(Utility.IsEpoch(AnalysisDate))AnalysisDate=dateGenerator.GetPrevBusinessDay(Today());  // Ensure AnalysisDate is not a weekend or holiday
      TradeDate =dateGenerator.GetCurrentMonthEnd(StartDate);
      if(TradeDate>AnalysisDate)
      {
        int startMonth=StartDate.Month;
        TimeSpan timeSpan=new TimeSpan();
        if((new int[]{12,3,6,9}).Any(x=>x.Equals(startMonth)))timeSpan=new TimeSpan(30,0,0,0);     
        else if((new int[]{1,4,7,10}).Any(x=>x.Equals(startMonth)))timeSpan=new TimeSpan(60,0,0,0);
        else if((new int[]{2,5,8,11}).Any(x=>x.Equals(startMonth)))timeSpan=new TimeSpan(90,0,0,0);
        StartDate=StartDate-timeSpan;
        TradeDate=dateGenerator.GetCurrentMonthEnd(StartDate);
      }
      if(null!=PathSessionFileName)sessionParams=RestoreSession();
      if(null!=sessionParams)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Using session file {0}, Last updated {1}",paramPathSessionFileName,sessionParams.LastUpdated));
      }
      Configuration.DisplayConfiguration();
      DisplayBalance();
      while(true)
      {
        if(TradeDate>AnalysisDate)break;
        int slotIndex=(int)(((double)Cycle)%((double)(HoldingPeriod)));
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("TRADE DATE {0} , ANALYSIS DATE {1}",Utility.DateTimeToStringMMHDDHYYYY(TradeDate),Utility.DateTimeToStringMMHDDHYYYY(AnalysisDate)));
        if(!ActivePositions.ContainsKey(slotIndex))
        {
          Positions positions = null;
          positions=BuyPositions(TradeDate,AnalysisDate,CashBalance/((double)HoldingPeriod-(double)ActivePositions.Count),SymbolsHeld(TradeDate));
          MDTrace.WriteLine(LogLevel.DEBUG, "******************** B U Y ********************");
          if(CashBalance-positions.Exposure<0.00)
          {
            positions.Clear();
            MDTrace.WriteLine(LogLevel.DEBUG,String.Format("********** Insufficient funds to make additional purchases.**************"));
          }
          positions.Display();
          ActivePositions.Add(slotIndex,positions);
          DisplayPurchases(positions,TradeDate);
          CashBalance-=positions.Exposure;
          DisplayBalance();
        }
        else
        {
          Positions slotPositions=ActivePositions[slotIndex];
          List<String> closedSymbols = slotPositions.ConvertAll(x => x.Symbol); // capture the closed symbols so we don't re-enter the position (avoid wash trades)
          SellPositions(slotPositions,TradeDate);
          MDTrace.WriteLine(LogLevel.DEBUG,"********************* S E L L  *********************");
          slotPositions.Display();
          AllPositions.Add(slotPositions);
          DisplaySales(slotPositions, TradeDate);
          CashBalance+=slotPositions.MarketValue;
          ActivePositions[slotIndex].Clear();
          DisplayBalance();
          double cashAllocation=CashBalance;
          cashAllocation = Math.Min(CashBalance, (ActivePositions.GetExposure() + CashBalance) / (double)HoldingPeriod);
          MDTrace.WriteLine(LogLevel.DEBUG,String.Format("CASH ALLOCATION:{0}",Utility.FormatCurrency(cashAllocation)));
          Positions positions = null;
          positions=BuyPositions(TradeDate,AnalysisDate,cashAllocation,new List<String>(SymbolsHeld(TradeDate).Concat(closedSymbols)));
          MDTrace.WriteLine(LogLevel.DEBUG,"********************** B U Y ********************");
          positions.Display();
          if(CashBalance-positions.Exposure<=0.00)
          {
            MDTrace.WriteLine(LogLevel.DEBUG,String.Format("********** Insufficient funds to make additional purchases.**************"));
            break;
          }
          ActivePositions[slotIndex]=positions;
          DisplayPurchases(positions, TradeDate);
          CashBalance-=positions.Exposure;
          DisplayBalance();
        }
        Cycle++;
        TradeDate=dateGenerator.GetNextMonthEnd(TradeDate);
        if(TradeDate>AnalysisDate)break;
      }   // WHILE TRUE
      MDTrace.WriteLine(LogLevel.DEBUG,"RUN COMPLETE.");
      if(null!=PathSessionFileName)SaveSession();
      for(int slotIndex=0;slotIndex<HoldingPeriod;slotIndex++)
      {
        if(!ActivePositions.ContainsKey(slotIndex)||0==ActivePositions[slotIndex].Count())continue;
        Positions slotPositions=ActivePositions[slotIndex];
        SellPositions(slotPositions,AnalysisDate);
        MDTrace.WriteLine(LogLevel.DEBUG,"********************* S E L L  ********************");
        slotPositions.Display();
        AllPositions.Add(slotPositions);
        DisplaySales(slotPositions, TradeDate);
        CashBalance+=slotPositions.MarketValue;
        ActivePositions[slotIndex].Clear();
      }
      MDTrace.WriteLine(LogLevel.DEBUG,"************** A L L   P O S I T I O N S  *************");
      AllPositions=new Positions((from Position position in AllPositions orderby position.PurchaseDate ascending select position).ToList());
      AllPositions.Display();
      MDTrace.WriteLine(LogLevel.DEBUG, "************** T O P  G A I N E R S  *************");
      AllPositions.DisplayTopFive();
      MDTrace.WriteLine(LogLevel.DEBUG, "************** T O P  L O S E R S   *************");
      AllPositions.DisplayBottomFive();
      DisplayBalance();
      backTestResult.Success=true;
      backTestResult.CashBalance=CashBalance;
      GBPriceCache.GetInstance().Dispose();
      return backTestResult;
    }
    
    public List<String> SymbolsHeld(DateTime tradeDate)
    {
      List<String> symbolsHeld=ActivePositions.SymbolsHeld();
      if(!Configuration.IncludeTradeMasterForSymbolsHeld)return symbolsHeld;
      if(null == symbolsHeld)symbolsHeld=new List<String>();
      PortfolioTrades portfolioTrades=PortfolioDA.GetOpenTradesAsOf(tradeDate);
      if(null == portfolioTrades || 0==portfolioTrades.Count)return symbolsHeld;
      symbolsHeld.AddRange(portfolioTrades.Symbols.Distinct());
      return symbolsHeld;
    }  
// **********************************************************************************************************************************************************
// **************************************************************** G E T  E X P O S U R E  / M A R K E T  V A L U E*****************************************
// **********************************************************************************************************************************************************
    public RealtimeGainLoss GetRealtimeGainLoss(DateTime tradeDate)
    {
      int count=ActivePositions.Count;
      double marketValue=0.00;
      double exposure=0.00;
      RealtimeGainLoss gainLoss=new RealtimeGainLoss();

      for(int slotIndex=0;slotIndex<count;slotIndex++)
      {
        List<Position> positions=ActivePositions[slotIndex];
        if(null==positions||0==positions.Count)continue;
        foreach(Position position in positions)
        {
          Price price=GBPriceCache.GetInstance().GetRealtimePrice(position.Symbol);
          if(null==price){MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Cannot price {0} on {1}",position.Symbol,Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));continue;}
          position.CurrentPrice=price.Close;
        }
      }
      for(int slotIndex=0;slotIndex<count;slotIndex++)
      {
        List<Position> positions=ActivePositions[slotIndex];
        if(null==positions||0==positions.Count)continue;
        exposure+=(from Position position in positions select position.Exposure).Sum();
        marketValue+=(from Position position in positions select position.MarketValue).Sum();
      }
      gainLoss.Exposure=exposure;
      gainLoss.MarketValue=marketValue;
      return gainLoss;
    }
// **************************************************************************************************************************************************
// **************************************************************** S E L L   P O S I T I O N S  *****************************************************
// ***************************************************************************************************************************************************
    private void SellPositions(Positions positions,DateTime sellDate)
    {
      foreach(Position position in positions)
      {
        Price price=GBPriceCache.GetInstance().GetPrice(position.Symbol,sellDate);
        position.SellDate=sellDate;
        if(null==price)
        {
          MDTrace.WriteLine(LogLevel.DEBUG,String.Format("**********Cannot locate a price for {0} on {1}**********",position.Symbol,Utility.DateTimeToStringMMHDDHYYYY(sellDate)));
          position.CurrentPrice=position.PurchasePrice;
        }
        else position.CurrentPrice=price.Close;
      }
    }

    private void SellPosition(Position position,DateTime sellDate)
    {
      Price price=GBPriceCache.GetInstance().GetPrice(position.Symbol,sellDate);
      position.SellDate=sellDate;
      if(null==price)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("**********Cannot locate a price for {0} on {1}**********",position.Symbol,Utility.DateTimeToStringMMHDDHYYYY(sellDate)));
        position.CurrentPrice=position.PurchasePrice;
      }
      else position.CurrentPrice=price.Close;
    }
// ***************************************************************************************************************************************************
// ******************************************************** B U Y    P O S I T I O N S   *************************************************************
// ***************************************************************************************************************************************************
    private Positions BuyPositions(DateTime tradeDate, DateTime analysisDate, double cash, List<String> symbolsHeld)
    {
      MDTrace.WriteLine(LogLevel.DEBUG,"**BUYPOSITIONS**");
      Positions positions = new Positions();
      int positionCount = 0;
      if (Configuration.BenchmarkMode) return BuyBenchmarkPositions(tradeDate, cash);
      MomentumCandidates momentumCandidates = MomentumGenerator.GenerateMomentum(tradeDate, symbolsHeld, Configuration);
      for (int index = 0; index < momentumCandidates.Count; index++)
      {
        MomentumCandidate momentumCandidate = momentumCandidates[index];
        Price price = GBPriceCache.GetInstance().GetPrice(momentumCandidate.Symbol, tradeDate);
        if (null == price)
        {
          MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Cannot locate a price for {0} on {1}", momentumCandidate.Symbol, Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));
          continue;
        }
        Position position = new Position();
        position.Symbol = momentumCandidate.Symbol;
        position.CumReturn252 = momentumCandidate.CumReturn252;
        position.IDIndicator = momentumCandidate.IDIndicator;
        position.Score=momentumCandidate.Score;
        position.MaxDrawdown = momentumCandidate.MaxDrawdown;
        position.MaxUpside = momentumCandidate.MaxUpside;
        position.PE = momentumCandidate.PE;
        position.Beta = momentumCandidate.Beta;
        position.ZacksRank = momentumCandidate.ZacksRank;
        position.Velocity = momentumCandidate.Velocity;
        position.Volume = momentumCandidate.Volume;
        position.Return1D = position.Return1D;
        position.PurchaseDate = tradeDate;
        position.PurchasePrice = price.Close;
        position.CurrentPrice = price.Close;
        position.Shares = (int)Math.Floor((cash / (double)MaxPositions) / position.PurchasePrice);
        if (0 == position.Shares) continue;    // if not able to purchase any shares, for example, if the share price exceeds our purchasing power then move on to the next candidate
        positions.Add(position);
        positionCount++;
        if (positionCount >= MaxPositions) break;
      }
      if (0 == positions.Count && Configuration.UseFallbackCandidate)    // if we don't get any signals then consider the fallback candidate options as per the configuration file
      {
        String fallbackCandidate = Configuration.FallbackCandidate;
        if (null != Configuration.FallbackCandidateBestOf && !"".Equals(Configuration.FallbackCandidateBestOf))
        {
          QualityIndicator qualityIndicator=new QualityIndicator(Configuration.QualityIndicatorType);
          fallbackCandidate=CandidateSelector.SelectBestCandidateSymbol(qualityIndicator,Utility.ToList(Configuration.FallbackCandidateBestOf),Configuration.FallbackCandidate,tradeDate);
        }
        Price price = GBPriceCache.GetInstance().GetPrice(fallbackCandidate, tradeDate);
        if (null == price)
        {
          MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Cannot locate a price for {0} on {1}", fallbackCandidate, Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));
          return positions;
        }
        Position position = new Position();
        position.Symbol = fallbackCandidate;
        position.PurchaseDate = tradeDate;
        position.PurchasePrice = price.Close;
        position.CurrentPrice = price.Close;
        position.Shares = (int)Math.Floor(cash / position.PurchasePrice);
        if(0 == position.Shares)return positions;
        positions.Add(position);
      }
      return positions;
    }

    private Positions BuyBenchmarkPositions(DateTime tradeDate, double cash)
    {
      Positions positions = new Positions();
      Price benchmarkPrice = GBPriceCache.GetInstance().GetPrice(Configuration.BenchmarkModeSymbol, tradeDate);
      if (null == benchmarkPrice)
      {
        MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Cannot locate a price for {0} on {1}", Configuration.BenchmarkModeSymbol, Utility.DateTimeToStringMMHDDHYYYY(tradeDate)));
        return positions;
      }
      Position position = new Position();
      position.Symbol = Configuration.BenchmarkModeSymbol;
      position.PurchaseDate = tradeDate;
      position.PurchasePrice = benchmarkPrice.Close;
      position.Shares = (int)Math.Floor(cash / position.PurchasePrice);
      if (0 == position.Shares) return positions;
      positions.Add(position);
      return positions;
    }
// *********************************************************************************************************************************************************************
// ************************************************************ E N D   B U Y  P O S I T I O N S ***********************************************
// *********************************************************************************************************************************************************************
    private void DisplayLatestModelPerformance(String pathSessionFileName)
    {
      ModelPerformanceSeries performanceSeries=GetModelPerformance(pathSessionFileName);
      if(null==performanceSeries || 0==performanceSeries.Count)return;
      MDTrace.WriteLine("Date,Exposure,MarketValue,GainLossDoD,GainLoss,CumulativeGainLoss,R,(1+R),CumProd,CumProd-1,ClosedPositions");
      ModelPerformanceItem modelPerformanceItem = performanceSeries[performanceSeries.Count-1];   // get the last record
      MDTrace.WriteLine(LogLevel.DEBUG,String.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\",\"{8}\",\"{9}\",\"{10}\"",
        modelPerformanceItem.Date.ToShortDateString(),
        Utility.FormatCurrency(modelPerformanceItem.Exposure),                                                                         
        Utility.FormatCurrency(modelPerformanceItem.MarketValue),                                                                     
        Utility.FormatCurrency(modelPerformanceItem.GainLossDOD),
        Utility.FormatCurrency(modelPerformanceItem.GainLoss), 
        Utility.FormatCurrency(modelPerformanceItem.CumulativeGainLoss),
        Utility.FormatNumber(modelPerformanceItem.R,4),
        Utility.FormatNumber(modelPerformanceItem.OnePlusR,4),
        Utility.FormatNumber(modelPerformanceItem.CumProd,4),
        Utility.FormatNumber(modelPerformanceItem.CumProdMinusOne,4), 
        modelPerformanceItem.ClosedPositions));        
    }

    private void DisplayBalance()
    {
      MDTrace.WriteLine(LogLevel.DEBUG,"*******************************************");
      MDTrace.WriteLine(LogLevel.DEBUG,"EXPOSURE,AVAILABLE CASH,TOTAL ACCOUNT");
      MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0},{1},{2}",Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetExposure())),Utility.AddQuotes(Utility.FormatCurrency(CashBalance)),Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetMarketValue()+CashBalance))));
      for (int slotIndex = 0; slotIndex < ActivePositions.Count; slotIndex++)
      {
        Positions slotPositions = ActivePositions[slotIndex];
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("SLOT:{0} EXPOSURE:{1}",slotIndex,Utility.FormatCurrency(slotPositions.Exposure)));
      }
      MDTrace.WriteLine(LogLevel.DEBUG,String.Format("TOTAL EXPOSURE: {0}",Utility.FormatCurrency(ActivePositions.GetExposure())));
      MDTrace.WriteLine(LogLevel.DEBUG,"*******************************************");
    }

    private void DisplayBalanceFromPositions()
    {
      MDTrace.WriteLine(LogLevel.DEBUG,"EXPOSURE,GAIN/LOSS,GAIN/LOSS(%),AVAILABLE CASH,TOTAL ACCOUNT");
      MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0},{1},{2},{3},{4}",
        Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetExposure())),
        Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetGainLoss())),
        Utility.AddQuotes(Utility.FormatPercent(ActivePositions.GetGainLossPercent())),
        Utility.AddQuotes(Utility.FormatCurrency(CashBalance)),
        Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetMarketValue()+CashBalance))));
    }

    private void DisplayBalance(RealtimeGainLoss gainLoss)
    {
      MDTrace.WriteLine(LogLevel.DEBUG,"EXPOSURE,GAIN/LOSS,GAIN/LOSS(%),AVAILABLE CASH,TOTAL ACCOUNT");
      MDTrace.WriteLine(LogLevel.DEBUG,String.Format("{0},{1},{2},{3},{4}",
        Utility.AddQuotes(Utility.FormatCurrency(ActivePositions.GetExposure())),
        Utility.AddQuotes(Utility.FormatCurrency(gainLoss.GainLoss)),
        Utility.AddQuotes(Utility.FormatPercent(gainLoss.GainLossPercent)),
        Utility.AddQuotes(Utility.FormatCurrency(CashBalance)),
        Utility.AddQuotes(Utility.FormatCurrency(gainLoss.MarketValue+CashBalance))));
    }
// ****************************************************************************************************************************************
// ************************************************************* C O N T R O L  T O D A Y ***********************************************
// ****************************************************************************************************************************************
    public DateTime Today()
    {
      return DateTime.Now;
    }
// ****************************************************************************************************************************************
// **************************************************************** S E S S I O N  M A N A G E M E N T  ***********************************
// ****************************************************************************************************************************************
    public MGSessionParams RestoreSession()
    {
      try
      {
        MGSessionManager sessionManager=new MGSessionManager();
        if(!MGSessionManager.SessionAvailable(PathSessionFileName))return null;
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Restoring session from '{0}'",PathSessionFileName));
        MGSessionParams sessionParams=MGSessionManager.RestoreSession(PathSessionFileName);
        TradeDate=sessionParams.TradeDate;
        if(TradeDate.Date<AnalysisDate.Date)TradeDate=AnalysisDate;   // AnalysisDate will not fall on a weekend or holiday
        StartDate=sessionParams.StartDate;
        Configuration=sessionParams.Configuration;
        ActivePositions=sessionParams.ActivePositions;
        AllPositions=sessionParams.AllPositions;
        Cycle=sessionParams.Cycle;
        CashBalance=sessionParams.CashBalance;
        NonTradeableCash=sessionParams.NonTradeableCash;
        return sessionParams;
      }
      catch(Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
        return null;
      }
    }
    public void SaveSession()
    {
      DateGenerator dateGenerator = new DateGenerator();
      MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Saving session to '{0}'", PathSessionFileName));
      MGSessionParams sessionParams = new MGSessionParams();
      if (Utility.IsEpoch(AnalysisDate)) AnalysisDate = dateGenerator.GetPrevBusinessDay(Today());
      sessionParams.LastUpdated = Today();
      sessionParams.TradeDate = TradeDate;
      sessionParams.StartDate = StartDate;
      sessionParams.AnalysisDate = AnalysisDate;
      sessionParams.Configuration = Configuration;
      sessionParams.ActivePositions = ActivePositions;
      sessionParams.AllPositions = AllPositions;
      sessionParams.Cycle = Cycle;
      sessionParams.CashBalance = CashBalance;
      sessionParams.NonTradeableCash = NonTradeableCash;
      MGSessionManager.SaveSession(sessionParams,PathSessionFileName);
    }

    public bool BackupSession()
    {
      DateGenerator dateGenerator = new DateGenerator();
      String[] parts = PathSessionFileName.Split('.');
      String backupFileName = parts[0] + "_" + Utility.DateTimeToStringYYYYMMDDMMSSTT(DateTime.Now) + ".bak";
      MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Saving session to '{0}'", backupFileName));
      MGSessionParams sessionParams = new MGSessionParams();
      if (Utility.IsEpoch(AnalysisDate)) AnalysisDate = dateGenerator.GetPrevBusinessDay(Today());
      sessionParams.LastUpdated = Today();
      sessionParams.TradeDate = TradeDate;
      sessionParams.StartDate = StartDate;
      sessionParams.AnalysisDate = AnalysisDate;
      sessionParams.Configuration = Configuration;
      sessionParams.ActivePositions = ActivePositions;
      sessionParams.AllPositions = AllPositions;
      sessionParams.Cycle = Cycle;
      sessionParams.CashBalance = CashBalance;
      sessionParams.NonTradeableCash = NonTradeableCash;
      return MGSessionManager.SaveSession(sessionParams,backupFileName);
    }

    /// <summary>
    /// This makes for easier reading of the sales
    /// </summary>
    /// <param name="positions"></param>
    /// <param name="tradeDate"></param>
    private static void DisplaySales(Positions positions,DateTime tradeDate)
    {
      MDTrace.WriteLine(LogLevel.DEBUG,"********* S E L L S *********");
      foreach (Position position in positions)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Sell {0} {1} @ {2} on {3}",position.Symbol,Utility.FormatNumber(position.Shares,3),Utility.FormatCurrency(position.CurrentPrice,2),tradeDate.ToShortDateString()));
      }
    }

    /// <summary>
    /// This makes for easier reading of the purchases
    /// </summary>
    /// <param name="positions"></param>
    /// <param name="tradeDate"></param>
     private static void DisplayPurchases(Positions positions, DateTime tradeDate)
     {
       MDTrace.WriteLine(LogLevel.DEBUG,"********* B U Y S  *********");
       foreach (Position position in positions)
       {
         MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Buy {0} {1} @ {2} on {3}",position.Symbol,Utility.FormatNumber(position.Shares,3),Utility.FormatCurrency(position.PurchasePrice,2),tradeDate.ToShortDateString()));
       }
     }   
  }
}
