/// <summary> /// must send new positions here (eg from GotPosition on Response) /// </summary> /// <param name="p"></param> public void Adjust(Position p) { // update position _pt.Adjust(p); // do we have events? if (!HasEvents()) { return; } // do update doupdate(p.Symbol, true); }
/// <summary> /// must send new positions here (eg from GotPosition on Response) /// </summary> /// <param name="p"></param> public void Adjust(Position p) { // update position _pt.Adjust(p); // if we're flat, nothing to do if (_pt[p.Symbol].isFlat) { return; } // do we have events? if (!HasEvents()) { return; } // do update doupdate(p.Symbol); }
public void InitAndAdjust() { const string sym = "IBM"; // startup position tracker PositionTracker pt = new PositionTracker(); PositionTracker pt2 = new PositionTracker(); // give pt our initial position Position init = new PositionImpl(sym, 0, 0); pt.Adjust(init); pt2.Adjust(init); // fill a trade in both places Trade fill = new TradeImpl(sym,100, 100); pt.Adjust(fill); pt2.Adjust(fill); // make sure it's only 100 in both places Assert.AreEqual(100, pt[sym].Size); Assert.AreEqual(100, pt2[sym].Size); }
public void Adjust() { const string s = "IBM"; TradeImpl t1 = new TradeImpl(s, 100, 100); PositionTracker pt = new PositionTracker(); // make we have no position yet Assert.IsTrue(pt[t1.symbol].isFlat); // send some adjustments decimal cpl = 0; cpl += pt.Adjust(t1); cpl += pt.Adjust(t1); // verify that adjustments took hold Assert.AreEqual(0, cpl); Assert.AreEqual(200, pt[t1.symbol].Size); }
/// <summary> /// must send new positions here (eg from GotPosition on Response) /// </summary> /// <param name="p"></param> public void Adjust(Position p) { // did position exist? bool exists = !_pt[p.symbol].isFlat; if (exists) { debug(p.symbol + " re-initialization of existing position"); } if (exists && ShutdownOnReinit) { // get offset OffsetInfo oi = GetOffset(p.symbol); // disable it oi.ProfitPercent = 0; oi.StopPercent = 0; // save it SetOffset(p.symbol, oi); // cancel existing orders CancelAll(p.symbol); // stop processing return; } // update position _pt.Adjust(p); // if we're flat, nothing to do if (_pt[p.symbol].isFlat) { debug(p.symbol + " initialized to flat."); // cancel pending offsets CancelAll(p.symbol); // reset offset state but not configuration SetOffset(p.symbol, new OffsetInfo(this[p.symbol])); return; } // do we have events? if (!HasEvents()) { return; } // do update doupdate(p.symbol); }
public void MaxDD() { PositionTracker pt = new PositionTracker(); const string sym = "TST"; System.Collections.Generic.List<TradeLink.API.Trade> fills = new System.Collections.Generic.List<TradeLink.API.Trade>(); TradeImpl t = new TradeImpl(sym,10,100); System.Collections.Generic.List<decimal> ret = new System.Collections.Generic.List<decimal>(); fills.Add(t); pt.Adjust(t); t = new TradeImpl(sym, 11, -100); fills.Add(t); ret.Add(pt.Adjust(t)); t = new TradeImpl(sym, 11, -100); pt.Adjust(t); fills.Add(t); t = new TradeImpl(sym, 13, 100); fills.Add(t); ret.Add(pt.Adjust(t)); decimal maxdd = Calc.MaxDDVal(ret.ToArray()); decimal maxddp = Calc.MaxDDPct(fills); Assert.AreEqual(-300,maxdd); Assert.AreEqual(-.18m,Math.Round(maxddp,2)); }
public static Results FetchResults(List<TradeResult> results, decimal RiskFreeRate, decimal CommissionPerContractShare, bool persymbol, DebugDelegate d) { try { List<Trade> fills = new List<Trade>(); foreach (TradeResult tr in results) fills.Add(tr.Source); List<decimal> _MIU = new List<decimal>(); List<decimal> _grossreturn = new List<decimal>(); List<decimal> _netreturns = new List<decimal>(); List<decimal> pctrets = new List<decimal>(); List<int> days = new List<int>(); //clear position tracker PositionTracker pt = new PositionTracker(results.Count); // setup new results Results r = new Results(); r.ResultsDateTime = Util.ToTLDateTime(); r.ComPerShare = CommissionPerContractShare; int consecWinners = 0; int consecLosers = 0; List<long> exitscounted = new List<long>(); decimal winpl = 0; decimal losepl = 0; Dictionary<string, int> tradecount = new Dictionary<string, int>(); List<decimal> negret = new List<decimal>(results.Count); decimal curcommiss = 0; for (int i = 0; i<results.Count; i++) { var tr = results[i]; if (tradecount.ContainsKey(tr.Source.symbol)) tradecount[tr.Source.symbol]++; else tradecount.Add(tr.Source.symbol, 1); if (!days.Contains(tr.Source.xdate)) days.Add(tr.Source.xdate); var pos = pt[tr.Source.symbol]; int usizebefore = pos.UnsignedSize; decimal pricebefore = pos.AvgPrice; var islongbefore = pos.isLong; var miubefore = pos.isFlat ? 0 : pos.UnsignedSize * pos.AvgPrice; decimal pospl = pt.Adjust(tr.Source); bool islong = pt[tr.Source.symbol].isLong; bool isroundturn = (usizebefore != 0) && (pt[tr.Source.symbol].UnsignedSize == 0); // get comissions curcommiss += Math.Abs(tr.Source.xsize) * CommissionPerContractShare; bool isclosing = pt[tr.Source.symbol].UnsignedSize<usizebefore; // calculate MIU and store on array var miu = pt[tr.Source.symbol].isFlat ? 0 : pt[tr.Source.symbol].AvgPrice * pt[tr.Source.symbol].UnsignedSize; if (miu!=0) _MIU.Add(miu); // get miu up to this point var maxmiu = _MIU.Count == 0 ? 0 : Math.Abs(Calc.Max(_MIU.ToArray())); // if we closed something, update return decimal grosspl = 0; if (isclosing) if (islongbefore) grosspl = (tr.Source.xprice - pricebefore) * Math.Abs(tr.Source.xsize); else grosspl = (pricebefore - tr.Source.xprice) * Math.Abs(tr.Source.xsize); decimal netpl = grosspl - (Math.Abs(tr.Source.xsize) * CommissionPerContractShare); // get p&l for portfolio curcommiss = 0; // count return _grossreturn.Add(grosspl); _netreturns.Add(netpl); // get pct return for portfolio decimal pctret = 0; if (miubefore == 0) pctret = netpl / miu; else pctret = netpl / miubefore; pctrets.Add(pctret); // if it is below our zero, count it as negative return if (pctret < 0) negret.Add(pctret); if (isroundturn) { r.RoundTurns++; if (pospl >= 0) r.RoundWinners++; else if (pospl < 0) r.RoundLosers++; } if (!r.Symbols.Contains(tr.Source.symbol)) r.Symbols += tr.Source.symbol + ","; r.Trades++; r.SharesTraded += Math.Abs(tr.Source.xsize); r.GrossPL += tr.ClosedPL; if ((tr.ClosedPL > 0) && !exitscounted.Contains(tr.Source.id)) { if (tr.Source.side) { r.SellWins++; r.SellPL += tr.ClosedPL; } else { r.BuyWins++; r.BuyPL += tr.ClosedPL; } if (tr.Source.id != 0) exitscounted.Add(tr.id); r.Winners++; consecWinners++; consecLosers = 0; } else if ((tr.ClosedPL < 0) && !exitscounted.Contains(tr.Source.id)) { if (tr.Source.side) { r.SellLosers++; r.SellPL += tr.ClosedPL; } else { r.BuyLosers++; r.BuyPL += tr.ClosedPL; } if (tr.Source.id != 0) exitscounted.Add(tr.id); r.Losers++; consecLosers++; consecWinners = 0; } if (tr.ClosedPL > 0) winpl += tr.ClosedPL; else if (tr.ClosedPL < 0) losepl += tr.ClosedPL; if (consecWinners > r.ConsecWin) r.ConsecWin = consecWinners; if (consecLosers > r.ConsecLose) r.ConsecLose = consecLosers; if ((tr.OpenSize == 0) && (tr.ClosedPL == 0)) r.Flats++; if (tr.ClosedPL > r.MaxWin) r.MaxWin = tr.ClosedPL; if (tr.ClosedPL < r.MaxLoss) r.MaxLoss = tr.ClosedPL; if (tr.OpenPL > r.MaxOpenWin) r.MaxOpenWin = tr.OpenPL; if (tr.OpenPL < r.MaxOpenLoss) r.MaxOpenLoss = tr.OpenPL; } if (r.Trades != 0) { r.AvgPerTrade = Math.Round((losepl + winpl) / r.Trades, 2); r.AvgLoser = r.Losers == 0 ? 0 : Math.Round(losepl / r.Losers, 2); r.AvgWin = r.Winners == 0 ? 0 : Math.Round(winpl / r.Winners, 2); r.MoneyInUse = Math.Round(Calc.Max(_MIU.ToArray()), 2); r.MaxPL = Math.Round(Calc.Max(_grossreturn.ToArray()), 2); r.MinPL = Math.Round(Calc.Min(_grossreturn.ToArray()), 2); r.MaxDD = Calc.MaxDDPct(fills); r.SymbolCount = pt.Count; r.DaysTraded = days.Count; r.GrossPerDay = Math.Round(r.GrossPL / days.Count, 2); r.GrossPerSymbol = Math.Round(r.GrossPL / pt.Count, 2); if (persymbol) { for (int i = 0; i < pt.Count; i++) { r.PerSymbolStats.Add(pt[i].symbol + ": " + tradecount[pt[i].symbol] + " for " + pt[i].ClosedPL.ToString("C2")); } } } else { r.MoneyInUse = 0; r.MaxPL = 0; r.MinPL = 0; r.MaxDD = 0; r.GrossPerDay = 0; r.GrossPerSymbol = 0; } r.PctReturns = pctrets.ToArray(); r.DollarReturns = _netreturns.ToArray(); r.NegPctReturns = negret.ToArray(); try { var fret = Calc.Avg(pctrets.ToArray()); if (pctrets.Count == 0) r.SharpeRatio = 0; else if (pctrets.Count == 1) r.SharpeRatio = Math.Round((fret- RiskFreeRate), 3); else r.SharpeRatio = Math.Round(Calc.SharpeRatio(fret, Calc.StdDev(pctrets.ToArray()), RiskFreeRate), 3); } catch (Exception ex) { if (d != null) d("sharpe error: " + ex.Message); } try { var fret = Calc.Avg(pctrets.ToArray()); if (pctrets.Count == 0) r.SortinoRatio = 0; else if (negret.Count == 1) r.SortinoRatio = (fret - RiskFreeRate) / negret[0]; else if ((negret.Count == 0)) r.SortinoRatio = fret - RiskFreeRate; else r.SortinoRatio = Math.Round(Calc.SortinoRatio(fret, Calc.StdDev(negret.ToArray()), RiskFreeRate ), 3); } catch (Exception ex) { if (d != null) d("sortino error: " + ex.Message); } return r; } catch (Exception ex) { if (d != null) d("error generting report: " + ex.Message + ex.StackTrace); return new Results(); } }
/// <summary> /// maximum drawdown as a percentage /// </summary> /// <param name="fills"></param> /// <returns></returns> public static decimal MaxDDPct(List<Trade> fills) { PositionTracker pt = new PositionTracker(); List<decimal> ret = new List<decimal>(); decimal mmiu = 0; for (int i = 0; i < fills.Count; i++) { pt.Adjust(fills[i]); decimal miu = Calc.Sum(Calc.MoneyInUse(pt)); if (miu > mmiu) mmiu = miu; ret.Add(Calc.Sum(Calc.AbsoluteReturn(pt, new decimal[0], true, false))); } decimal maxddval = MaxDDVal(ret.ToArray()); decimal pct = mmiu== 0 ? 0 : maxddval / mmiu; return pct; }
/// <summary> /// this must be called once per position tracker, for each position update. /// if you are using your own position tracker with this trailing stop(eg from offset tracker, or somewhere else) /// you only need to adjust it once, so if you adjust it directly you don't need to call again here. /// </summary> /// <param name="p"></param> public void Adjust(Position p) { _pt.Adjust(p); }
public virtual void GotPosition(Position p) { pt.Adjust(p); v(p.symbol + " position: " + p); }
public static Results FetchResults(List<TradeResult> results, decimal RiskFreeRate, decimal CommissionPerContractShare, bool persymbol, DebugDelegate d) { try { List<Trade> fills = new List<Trade>(); foreach (TradeResult tr in results) fills.Add(tr.Source); List<decimal> _MIU = new List<decimal>(); List<decimal> _return = new List<decimal>(); List<int> days = new List<int>(); //clear position tracker PositionTracker pt = new PositionTracker(results.Count); // setup new results Results r = new Results(); r.ResultsDateTime = Util.ToTLDate() * 1000000 + Util.ToTLTime(); r.ComPerShare = CommissionPerContractShare; r.RiskFreeRet = string.Format("{0:P2}", RiskFreeRate); int consecWinners = 0; int consecLosers = 0; List<long> exitscounted = new List<long>(); decimal winpl = 0; decimal losepl = 0; Dictionary<string, int> tradecount = new Dictionary<string, int>(); List<decimal> negret = new List<decimal>(results.Count); foreach (TradeResult tr in results) { if (tradecount.ContainsKey(tr.Source.symbol)) tradecount[tr.Source.symbol]++; else tradecount.Add(tr.Source.symbol, 1); if (!days.Contains(tr.Source.xdate)) days.Add(tr.Source.xdate); int usizebefore = pt[tr.Source.symbol].UnsignedSize; pt.Adjust(tr.Source); bool isclosing = pt[tr.Source.symbol].UnsignedSize<usizebefore; // calculate MIU and store on array decimal miu = Calc.Sum(Calc.MoneyInUse(pt)); if (miu!=0) _MIU.Add(miu); // if we closed something, update return if (isclosing) { // get p&l for portfolio decimal pl = Calc.Sum(Calc.AbsoluteReturn(pt)); // count return _return.Add(pl); // get pct return for portfolio decimal pctret = _MIU[_MIU.Count - 1] == 0 ? 0 : pl / _MIU[_MIU.Count - 1]; // if it is below our zero, count it as negative return if (pctret < 0) negret.Add(pl); } if (!r.Symbols.Contains(tr.Source.symbol)) r.Symbols += tr.Source.symbol + ","; r.Trades++; r.SharesTraded += Math.Abs(tr.Source.xsize); r.GrossPL += tr.ClosedPL; if ((tr.ClosedPL > 0) && !exitscounted.Contains(tr.Source.id)) { if (tr.Source.side) { r.SellWins++; r.SellPL += tr.ClosedPL; } else { r.BuyWins++; r.BuyPL += tr.ClosedPL; } if (tr.Source.id != 0) exitscounted.Add(tr.id); r.Winners++; consecWinners++; consecLosers = 0; } else if ((tr.ClosedPL < 0) && !exitscounted.Contains(tr.Source.id)) { if (tr.Source.side) { r.SellLosers++; r.SellPL += tr.ClosedPL; } else { r.BuyLosers++; r.BuyPL += tr.ClosedPL; } if (tr.Source.id != 0) exitscounted.Add(tr.id); r.Losers++; consecLosers++; consecWinners = 0; } if (tr.ClosedPL > 0) winpl += tr.ClosedPL; else if (tr.ClosedPL < 0) losepl += tr.ClosedPL; if (consecWinners > r.ConsecWin) r.ConsecWin = consecWinners; if (consecLosers > r.ConsecLose) r.ConsecLose = consecLosers; if ((tr.OpenSize == 0) && (tr.ClosedPL == 0)) r.Flats++; if (tr.ClosedPL > r.MaxWin) r.MaxWin = tr.ClosedPL; if (tr.ClosedPL < r.MaxLoss) r.MaxLoss = tr.ClosedPL; if (tr.OpenPL > r.MaxOpenWin) r.MaxOpenWin = tr.OpenPL; if (tr.OpenPL < r.MaxOpenLoss) r.MaxOpenLoss = tr.OpenPL; } if (r.Trades != 0) { r.AvgPerTrade = Math.Round((losepl + winpl) / r.Trades, 2); r.AvgLoser = r.Losers == 0 ? 0 : Math.Round(losepl / r.Losers, 2); r.AvgWin = r.Winners == 0 ? 0 : Math.Round(winpl / r.Winners, 2); r.MoneyInUse = Math.Round(Calc.Max(_MIU.ToArray()), 2); r.MaxPL = Math.Round(Calc.Max(_return.ToArray()), 2); r.MinPL = Math.Round(Calc.Min(_return.ToArray()), 2); r.MaxDD = string.Format("{0:P1}", Calc.MaxDDPct(fills)); r.SymbolCount = pt.Count; r.DaysTraded = days.Count; r.GrossPerDay = Math.Round(r.GrossPL / days.Count, 2); r.GrossPerSymbol = Math.Round(r.GrossPL / pt.Count, 2); if (persymbol) { for (int i = 0; i < pt.Count; i++) { r.PerSymbolStats.Add(pt[i].Symbol + ": " + tradecount[pt[i].Symbol] + " for " + pt[i].ClosedPL.ToString("C2")); } } } else { r.MoneyInUse = 0; r.MaxPL = 0; r.MinPL = 0; r.MaxDD = "0"; r.GrossPerDay = 0; r.GrossPerSymbol = 0; } try { r.SharpeRatio = _return.Count < 2 ? 0 : Math.Round(Calc.SharpeRatio(_return[_return.Count - 1], Calc.StdDev(_return.ToArray()), (RiskFreeRate*r.MoneyInUse)), 3); } catch (Exception ex) { if (d != null) d("sharp error: " + ex.Message); } try { if (_return.Count == 0) r.SortinoRatio = 0; else if (negret.Count == 1) r.SortinoRatio = 0; else if ((negret.Count == 0) && (_return[_return.Count - 1] == 0)) r.SortinoRatio = 0; else if ((negret.Count == 0) && (_return[_return.Count - 1] > 0)) r.SortinoRatio = decimal.MaxValue; else if ((negret.Count == 0) && (_return[_return.Count - 1] < 0)) r.SortinoRatio = decimal.MinValue; else r.SortinoRatio = Math.Round(Calc.SortinoRatio(_return[_return.Count - 1], Calc.StdDev(negret.ToArray()), (RiskFreeRate * r.MoneyInUse)), 3); } catch (Exception ex) { if (d != null) d("sortino error: " + ex.Message); } return r; } catch (Exception ex) { if (d != null) d("error generting report: " + ex.Message + ex.StackTrace); return new Results(); } }
public void ClosedPL() { const string sym = "RYN"; PositionTracker pt = new PositionTracker(); Position p = new PositionImpl(sym, 44.39m, 800, 0); pt.Adjust(p); System.IO.StreamReader sr = new System.IO.StreamReader("TestPositionClosedPL.txt"); string[] file = sr.ReadToEnd().Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries); foreach (string line in file) { Trade t = TradeImpl.FromString(line); pt.Adjust(t); } Assert.AreEqual(-66, pt[sym].ClosedPL); }
public void MultipleAccount() { // setup defaults for 1st and 2nd accounts and positions string sym = "TST"; string a1 = "account1"; string a2 = "account2"; int s1 = 300; int s2 = 500; decimal p = 100m; // create position tracker PositionTracker pt = new PositionTracker(); // set initial position in 1st account pt.Adjust(new PositionImpl(sym, p, s1, 0, a1)); // set initial position in 2nd account pt.Adjust(new PositionImpl(sym, p, s2, 0, a2)); // verify I can query default account and it's correct Assert.AreEqual(s1, pt[sym].Size); // change default to 2nd account pt.DefaultAccount = a2; // verify I can query default and it's correct Assert.AreEqual(s2, pt[sym].Size); // verify I can query 1st account and correct Assert.AreEqual(s1, pt[sym,a1].Size); // verify I can query 2nd account and correct Assert.AreEqual(s2, pt[sym,a2].Size); // get fill in sym for 1st account TradeImpl f = new TradeImpl(sym, p, s1); f.Account = a1; pt.Adjust(f); // get fill in sym for 2nd account TradeImpl f2 = new TradeImpl(sym, p, s2); f2.Account = a2; pt.Adjust(f2); // verify that I can querry 1st account and correct Assert.AreEqual(s1*2, pt[sym, a1].Size); // verify I can query 2nd account and correct Assert.AreEqual(s2*2, pt[sym, a2].Size); // reset pt.Clear(); // ensure I can query first and second account and get flat symbols Assert.AreEqual(0, pt[sym].Size); Assert.AreEqual(0, pt[sym, a1].Size); Assert.AreEqual(0, pt[sym, a2].Size); Assert.IsTrue(pt[sym, a1].isFlat); Assert.IsTrue(pt[sym, a2].isFlat); Assert.IsTrue(pt[sym].isFlat); Assert.AreEqual(string.Empty, pt.DefaultAccount); }
public static decimal GetPortfolioPlot(string title,decimal start, int startdate, int starttime, int enddate, int endtime, List<Trade> trades, ref ChartControl c, decimal compershare) { var cureq = start; if (trades.Count == 0) return GetPortfolioPlot(title,start, startdate, starttime, enddate, endtime, ref c); c.NewBarList(new BarListImpl(title)); var tradessorted = SortTrades(trades); c.newPoint(title, cureq, 0, tradessorted[0].xdate, 100); // plot money made PositionTracker pt = new PositionTracker(); foreach (var t in tradessorted) { var grosspl = pt.Adjust(t); var netpl = grosspl - (compershare * Math.Abs(t.xsize)); cureq += netpl; c.newPoint(title, cureq, t.xtime, t.xdate, 100); } c.redraw(); // set final equity return cureq; }
public void GotFill(Trade t) { _pt.Adjust(t); }