/// <summary> /// maximum drawdown as a percentage /// </summary> /// <param name="fills"></param> /// <returns></returns> public static decimal MaxDDPct(List <Trade> fills) { Portfolio portfolio = new Portfolio(); List <decimal> ret = new List <decimal>(); decimal mmiu = 0; for (int i = 0; i < fills.Count; i++) { portfolio.Adjust(fills[i]); decimal miu = Calc.Sum(Calc.MoneyInUse(portfolio.Positions)); if (miu > mmiu) { mmiu = miu; } ret.Add(Calc.Sum(Calc.AbsoluteReturn(portfolio.Positions, new Dictionary <string, decimal>(), true))); } decimal maxddval = MaxDDVal(ret.ToArray()); decimal pct = mmiu == 0 ? 0 : maxddval / mmiu; return(pct); }
/// <summary> /// Generate reports /// call after InitializePosition /// </summary> public void GenerateReports(List <Trade> trades) { try { _fills.Clear(); _fills.AddRange(trades); List <decimal> moneyinuse = new List <decimal>(); // money in use List <decimal> tradepnl = new List <decimal>(); List <int> days = new List <int>(); // hostorical trading days when the trades happened Dictionary <string, int> tradecount = new Dictionary <string, int>(); // symbol --> trade count List <decimal> negret = new List <decimal>(_fills.Count); int consecWinners = 0; int consecLosers = 0; List <long> exitscounted = new List <long>(); decimal winpl = 0; decimal losepl = 0; foreach (Trade trade in _fills) { if (tradecount.ContainsKey(trade.FullSymbol)) { tradecount[trade.FullSymbol]++; } else { tradecount.Add(trade.FullSymbol, 1); } if (!days.Contains(trade.TradeDate)) { days.Add(trade.TradeDate); } int usizebefore = 0; decimal closedpnlfromthistrade = 0; if (_positions.ContainsKey(trade.FullSymbol)) { usizebefore = _positions[trade.FullSymbol].UnsignedSize; closedpnlfromthistrade = _positions[trade.FullSymbol].Adjust(trade); // closed pnl } else { // add the trade to position _positions.Add(trade.FullSymbol, new Position(trade)); usizebefore = 0; closedpnlfromthistrade = 0; } bool isroundturn = (usizebefore != 0) && (_positions[trade.FullSymbol].UnsignedSize == 0); // end at exact 0 bool isclosing = _positions[trade.FullSymbol].UnsignedSize < usizebefore; // calculate MIU and store on array decimal miu = Calc.Sum(Calc.MoneyInUse(_positions)); if (miu != 0) { moneyinuse.Add(miu); } // if we closed something, update return if (isclosing) { // get p&l for portfolio decimal pl = Calc.Sum(Calc.AbsoluteReturn(_positions)); // with one param, AbsoluteReturn returns closed pnl // count return tradepnl.Add(pl); // get pct return for portfolio decimal pctret = moneyinuse[moneyinuse.Count - 1] == 0 ? 0 : pl / moneyinuse[moneyinuse.Count - 1]; // if it is below our zero, count it as negative return if (pctret < 0) { negret.Add(pl); } } if (isroundturn) // # of RoundTurns = RoundWinners + RoundLosers { RoundTurns++; if (closedpnlfromthistrade >= 0) { RoundWinners++; } else if (closedpnlfromthistrade < 0) { RoundLosers++; } } Trades++; SharesTraded += Math.Abs(trade.TradeSize); Commissions += CalculateIBCommissions(trade); GrossPL += closedpnlfromthistrade; if ((closedpnlfromthistrade > 0) && !exitscounted.Contains(trade.Id)) { if (trade.Side) { SellWins++; SellPL += closedpnlfromthistrade; } else { BuyWins++; BuyPL += closedpnlfromthistrade; } if (trade.Id != 0) { exitscounted.Add(trade.Id); } Winners++; consecWinners++; consecLosers = 0; } else if ((closedpnlfromthistrade < 0) && !exitscounted.Contains(trade.Id)) { if (trade.Side) { SellLosers++; SellPL += closedpnlfromthistrade; } else { BuyLosers++; BuyPL += closedpnlfromthistrade; } if (trade.Id != 0) { exitscounted.Add(trade.Id); } Losers++; consecLosers++; consecWinners = 0; } if (closedpnlfromthistrade > 0) { winpl += closedpnlfromthistrade; } else if (closedpnlfromthistrade < 0) { losepl += closedpnlfromthistrade; } if (consecWinners > ConsecWin) { ConsecWin = consecWinners; } if (consecLosers > ConsecLose) { ConsecLose = consecLosers; } if ((_positions[trade.FullSymbol].Size == 0) && (closedpnlfromthistrade == 0)) { Flats++; } if (closedpnlfromthistrade > MaxWin) { MaxWin = closedpnlfromthistrade; } if (closedpnlfromthistrade < MaxLoss) { MaxLoss = closedpnlfromthistrade; } if (_positions[trade.FullSymbol].OpenPL > MaxOpenWin) { MaxOpenWin = _positions[trade.FullSymbol].OpenPL; } if (_positions[trade.FullSymbol].OpenPL < MaxOpenLoss) { MaxOpenLoss = _positions[trade.FullSymbol].OpenPL; } } // end of loop over trades if (Trades != 0) { AvgPerTrade = Math.Round((losepl + winpl) / Trades, 2); AvgLoser = Losers == 0 ? 0 : Math.Round(losepl / Losers, 2); AvgWin = Winners == 0 ? 0 : Math.Round(winpl / Winners, 2); MoneyInUse = Math.Round(Calc.Max(moneyinuse.ToArray()), 2); MaxPL = Math.Round(Calc.Max(tradepnl.ToArray()), 2); MinPL = Math.Round(Calc.Min(tradepnl.ToArray()), 2); MaxDD = Calc.MaxDDPct(_fills); SymbolCount = _positions.Count; DaysTraded = days.Count; GrossPerDay = Math.Round(GrossPL / days.Count, 2); GrossPerSymbol = Math.Round(GrossPL / _positions.Count, 2); if (PerSymbol) { foreach (KeyValuePair <string, Position> item in _positions) { PerSymbolStats.Add(item.Value.FullSymbol, tradecount[item.Value.FullSymbol] + " for " + item.Value.ClosedPL.ToString("C2")); } } } else { MoneyInUse = 0; MaxPL = 0; MinPL = 0; MaxDD = 0; GrossPerDay = 0; GrossPerSymbol = 0; } // ratio measures try { SharpeRatio = tradepnl.Count < 2 ? 0 : Math.Round(Calc.SharpeRatio(tradepnl[tradepnl.Count - 1], Calc.StdDev(tradepnl.ToArray()), (RiskFreeRate * MoneyInUse * DaysTraded / 252)), 3); } catch (Exception ex) { Debug("sharpe error: " + ex.Message); } try { if (tradepnl.Count == 0) { SortinoRatio = 0; } else if (negret.Count == 1) { SortinoRatio = 0; } else if ((negret.Count == 0) && (tradepnl[tradepnl.Count - 1] == 0)) { SortinoRatio = 0; } else if ((negret.Count == 0) && (tradepnl[tradepnl.Count - 1] > 0)) { SortinoRatio = 0; } //SortinoRatio = decimal.MaxValue; else if ((negret.Count == 0) && (tradepnl[tradepnl.Count - 1] < 0)) { SortinoRatio = 0; } //SortinoRatio = decimal.MinValue; else { SortinoRatio = Math.Round(Calc.SortinoRatio(tradepnl[tradepnl.Count - 1], Calc.StdDev(negret.ToArray()), (RiskFreeRate * MoneyInUse)), 3); } } catch (Exception ex) { Debug("sortino error: " + ex.Message); } } catch (Exception ex) { Debug("error in generating performance report" + ex.Message); } }