Overall Statistics |
Total Trades 32 Average Win 0% Average Loss -0.04% Compounding Annual Return -9.349% Drawdown 0.600% Expectancy -1 Net Profit -0.640% Sharpe Ratio -29.98 Loss Rate 100% Win Rate 0% Profit-Loss Ratio 0 Alpha -0.09 Beta 0 Annual Standard Deviation 0.003 Annual Variance 0 Information Ratio -0.057 Tracking Error 0.187 Treynor Ratio -210.546 Total Fees $32.00 |
using System; using System.Collections.Generic; using System.Linq; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Orders; public class OrderEvents { public static decimal FillPrice; } namespace QuantConnect.Algorithm.CSharp { public class CoarseFundamentalTop5Algorithm : QCAlgorithm { // initialize our security changes to nothing DateTime lastTradeTime; SecurityChanges _changes = SecurityChanges.None; static readonly decimal EqualWeightPercentage = 1m/3; public override void Initialize() { SetBrokerageModel(BrokerageName.TradierBrokerage, AccountType.Cash); // this sets the resolution for securities added via universe selection UniverseSettings.Resolution = Resolution.Second; SetStartDate(2016, 2, 1); SetEndDate(DateTime.Now.Date.AddDays(-1)); SetCash(5000); foreach (var symbol in _changes.AddedSecurities) { var myString = symbol.ToString(); AddSecurity(SecurityType.Equity, myString, Resolution.Minute, fillDataForward: true, extendedMarketHours: false, leverage: 1 ); } // this add universe method accepts a single parameter that is a function that // accepts an IEnumerable<CoarseFundamental> and returns IEnumerable<Symbol> AddUniverse(CoarseSelectionFunction); } // sort the data by daily dollar volume and take the top 5 symbols public static IEnumerable<Symbol> CoarseSelectionFunction(IEnumerable<CoarseFundamental> coarse) { return (from stock in coarse orderby stock.Price //function that sorts by percent change //where stock.Price > stock.Price - stock.Value / stock.Price ?? where stock.Price < 5 && stock.Price > 2 select stock.Symbol).Take(1); } //Data Event Handler: New data arrives here. "TradeBars" type is a dictionary of strings so you can access it by symbol. public void OnData(TradeBars data) { var closedTrades = TradeBuilder.ClosedTrades; if (Time - lastTradeTime.Date < TimeSpan.FromDays(1)) { // only trade once a day at market open return; } lastTradeTime = Time; // if we have no changes, do nothing if (_changes == SecurityChanges.None) return; // liquidate removed securities foreach (var security in _changes.RemovedSecurities) { Log("Removed " + security); /*if (security.Invested) { Liquidate(security.Symbol); }*/ } foreach (var security in _changes.AddedSecurities) { //var symbolToday = Convert.ToString(_changes.AddedSecurities); //Log("" + symbolToday); var equalWeightedPorfolioSize = Portfolio.TotalPortfolioValue/3; var shareCount = CalculateOrderQuantity(security.Symbol, EqualWeightPercentage); //SetHoldings(security.Symbol, 0.25m); if (Portfolio.Cash > 0) { MarketOrder(security.Symbol, shareCount, tag: "Order Target Value: $" + Math.Round(equalWeightedPorfolioSize, 2)); } var test = Portfolio.TotalProfit; var threePercent = test * .3m; if (test < 30 && Portfolio.HoldStock) { Liquidate(security.Symbol); Log("Total Profit " + test); } if (Portfolio.HoldStock) { MarketOnCloseOrder(security.Symbol, -shareCount); } } var myfill = OrderEvents.FillPrice; Log("Fill Price " + myfill); // you can access the settled only funds using the CashBook var settledCash = Portfolio.CashBook["USD"].Amount; // you can access the unsettled fund using the UnsettledCashBook var unsettledCash = Portfolio.UnsettledCashBook["USD"].Amount; _changes = SecurityChanges.None; } // this event fires whenever we have changes to our universe public override void OnSecuritiesChanged(SecurityChanges changes) { _changes = changes; } /*public override void OnEndOfDay() { // at the end of each day log the state of our settled and unsettled cashbooks Log(string.Empty); Log("-------------------"+Time.Date.ToShortDateString()+"-------------------"); Log("SETTLED::"); var settled = Portfolio.CashBook.ToString(); foreach (var line in settled.Split('\n')) { Log(" " + line); } Log(string.Empty); Log(string.Empty); Log("UNSETTLED::"); var unsettled = Portfolio.UnsettledCashBook.ToString(); foreach (var line in unsettled.Split('\n')) { Log(" " + line); } }*/ } }
using System; using System.Collections.Generic; using System.Linq; using QuantConnect.Orders; using QuantConnect.Util; namespace QuantConnect.Statistics { /// <summary> /// The <see cref="TradeBuilder"/> class generates trades from executions and market price updates /// </summary> public class TradeBuilder { /// <summary> /// Helper class to manage pending trades and market price updates for a symbol /// </summary> private class Position { internal List<Trade> PendingTrades { get; set; } internal List<OrderEvent> PendingFills { get; set; } internal decimal TotalFees { get; set; } internal decimal MaxPrice { get; set; } internal decimal MinPrice { get; set; } public Position() { PendingTrades = new List<Trade>(); PendingFills = new List<OrderEvent>(); } } private const int LiveModeMaxTradeCount = 10000; private const int LiveModeMaxTradeAgeMonths = 12; private const int MaxOrderIdCacheSize = 1000; private readonly List<Trade> _closedTrades = new List<Trade>(); private readonly Dictionary<Symbol, Position> _positions = new Dictionary<Symbol, Position>(); private readonly FixedSizeHashQueue<int> _ordersWithFeesAssigned = new FixedSizeHashQueue<int>(MaxOrderIdCacheSize); private readonly FillGroupingMethod _groupingMethod; private readonly FillMatchingMethod _matchingMethod; private bool _liveMode; /// <summary> /// Initializes a new instance of the <see cref="TradeBuilder"/> class /// </summary> public TradeBuilder(FillGroupingMethod groupingMethod, FillMatchingMethod matchingMethod) { _groupingMethod = groupingMethod; _matchingMethod = matchingMethod; } /// <summary> /// Sets the live mode flag /// </summary> /// <param name="live">The live mode flag</param> public void SetLiveMode(bool live) { _liveMode = live; } /// <summary> /// The list of closed trades /// </summary> public List<Trade> ClosedTrades { get { return _closedTrades; } } /// <summary> /// Returns true if there is an open position for the symbol /// </summary> /// <param name="symbol">The symbol</param> /// <returns>true if there is an open position for the symbol</returns> public bool HasOpenPosition(Symbol symbol) { Position position; if (!_positions.TryGetValue(symbol, out position)) return false; if (_groupingMethod == FillGroupingMethod.FillToFill) return position.PendingTrades.Count > 0; return position.PendingFills.Count > 0; } /// <summary> /// Sets the current market price for the symbol /// </summary> /// <param name="symbol"></param> /// <param name="price"></param> public void SetMarketPrice(Symbol symbol, decimal price) { Position position; if (!_positions.TryGetValue(symbol, out position)) return; if (price > position.MaxPrice) position.MaxPrice = price; else if (price < position.MinPrice) position.MinPrice = price; } /// <summary> /// Processes a new fill, eventually creating new trades /// </summary> /// <param name="fill">The new fill order event</param> /// <param name="conversionRate">The current market conversion rate into the account currency</param> public void ProcessFill(OrderEvent fill, decimal conversionRate) { // If we have multiple fills per order, we assign the order fee only to its first fill // to avoid counting the same order fee multiple times. var orderFee = 0m; if (!_ordersWithFeesAssigned.Contains(fill.OrderId)) { orderFee = fill.OrderFee; _ordersWithFeesAssigned.Add(fill.OrderId); } switch (_groupingMethod) { case FillGroupingMethod.FillToFill: ProcessFillUsingFillToFill(fill.Clone(), orderFee, conversionRate); break; case FillGroupingMethod.FlatToFlat: ProcessFillUsingFlatToFlat(fill.Clone(), orderFee, conversionRate); break; case FillGroupingMethod.FlatToReduced: ProcessFillUsingFlatToReduced(fill.Clone(), orderFee, conversionRate); break; } } private void ProcessFillUsingFillToFill(OrderEvent fill, decimal orderFee, decimal conversionRate) { Position position; if (!_positions.TryGetValue(fill.Symbol, out position) || position.PendingTrades.Count == 0) { // no pending trades for symbol _positions[fill.Symbol] = new Position { PendingTrades = new List<Trade> { new Trade { Symbol = fill.Symbol, EntryTime = fill.UtcTime, EntryPrice = fill.FillPrice, Direction = fill.FillQuantity > 0 ? TradeDirection.Long : TradeDirection.Short, Quantity = fill.AbsoluteFillQuantity, TotalFees = orderFee } }, MinPrice = fill.FillPrice, MaxPrice = fill.FillPrice }; return; } SetMarketPrice(fill.Symbol, fill.FillPrice); var index = _matchingMethod == FillMatchingMethod.FIFO ? 0 : position.PendingTrades.Count - 1; if (Math.Sign(fill.FillQuantity) == (position.PendingTrades[index].Direction == TradeDirection.Long ? +1 : -1)) { // execution has same direction of trade position.PendingTrades.Add(new Trade { Symbol = fill.Symbol, EntryTime = fill.UtcTime, EntryPrice = fill.FillPrice, Direction = fill.FillQuantity > 0 ? TradeDirection.Long : TradeDirection.Short, Quantity = fill.AbsoluteFillQuantity, TotalFees = orderFee }); } else { // execution has opposite direction of trade var totalExecutedQuantity = 0; var orderFeeAssigned = false; while (position.PendingTrades.Count > 0 && Math.Abs(totalExecutedQuantity) < fill.AbsoluteFillQuantity) { var trade = position.PendingTrades[index]; if (fill.AbsoluteFillQuantity >= trade.Quantity) { totalExecutedQuantity -= trade.Quantity * (trade.Direction == TradeDirection.Long ? +1 : -1); position.PendingTrades.RemoveAt(index); if (index > 0 && _matchingMethod == FillMatchingMethod.LIFO) index--; trade.ExitTime = fill.UtcTime; trade.ExitPrice = fill.FillPrice; trade.ProfitLoss = Math.Round((trade.ExitPrice - trade.EntryPrice) * trade.Quantity * (trade.Direction == TradeDirection.Long ? +1 : -1) * conversionRate, 2); // if closing multiple trades with the same order, assign order fee only once trade.TotalFees += orderFeeAssigned ? 0 : orderFee; trade.MAE = Math.Round((trade.Direction == TradeDirection.Long ? position.MinPrice - trade.EntryPrice : trade.EntryPrice - position.MaxPrice) * trade.Quantity * conversionRate, 2); trade.MFE = Math.Round((trade.Direction == TradeDirection.Long ? position.MaxPrice - trade.EntryPrice : trade.EntryPrice - position.MinPrice) * trade.Quantity * conversionRate, 2); AddNewTrade(trade); } else { totalExecutedQuantity += fill.FillQuantity; trade.Quantity -= fill.AbsoluteFillQuantity; AddNewTrade(new Trade { Symbol = trade.Symbol, EntryTime = trade.EntryTime, EntryPrice = trade.EntryPrice, Direction = trade.Direction, Quantity = fill.AbsoluteFillQuantity, ExitTime = fill.UtcTime, ExitPrice = fill.FillPrice, ProfitLoss = Math.Round((fill.FillPrice - trade.EntryPrice) * fill.AbsoluteFillQuantity * (trade.Direction == TradeDirection.Long ? +1 : -1) * conversionRate, 2), TotalFees = trade.TotalFees + (orderFeeAssigned ? 0 : orderFee), MAE = Math.Round((trade.Direction == TradeDirection.Long ? position.MinPrice - trade.EntryPrice : trade.EntryPrice - position.MaxPrice) * fill.AbsoluteFillQuantity * conversionRate, 2), MFE = Math.Round((trade.Direction == TradeDirection.Long ? position.MaxPrice - trade.EntryPrice : trade.EntryPrice - position.MinPrice) * fill.AbsoluteFillQuantity * conversionRate, 2) }); trade.TotalFees = 0; } orderFeeAssigned = true; } if (Math.Abs(totalExecutedQuantity) == fill.AbsoluteFillQuantity && position.PendingTrades.Count == 0) { _positions.Remove(fill.Symbol); } else if (Math.Abs(totalExecutedQuantity) < fill.AbsoluteFillQuantity) { // direction reversal fill.FillQuantity -= totalExecutedQuantity; position.PendingTrades = new List<Trade> { new Trade { Symbol = fill.Symbol, EntryTime = fill.UtcTime, EntryPrice = fill.FillPrice, Direction = fill.FillQuantity > 0 ? TradeDirection.Long : TradeDirection.Short, Quantity = fill.AbsoluteFillQuantity, TotalFees = 0 } }; position.MinPrice = fill.FillPrice; position.MaxPrice = fill.FillPrice; } } } private void ProcessFillUsingFlatToFlat(OrderEvent fill, decimal orderFee, decimal conversionRate) { Position position; if (!_positions.TryGetValue(fill.Symbol, out position) || position.PendingFills.Count == 0) { // no pending executions for symbol _positions[fill.Symbol] = new Position { PendingFills = new List<OrderEvent> { fill }, TotalFees = orderFee, MinPrice = fill.FillPrice, MaxPrice = fill.FillPrice }; return; } SetMarketPrice(fill.Symbol, fill.FillPrice); if (Math.Sign(position.PendingFills[0].FillQuantity) == Math.Sign(fill.FillQuantity)) { // execution has same direction of trade position.PendingFills.Add(fill); position.TotalFees += orderFee; } else { // execution has opposite direction of trade if (position.PendingFills.Sum(x => x.FillQuantity) + fill.FillQuantity == 0 || fill.AbsoluteFillQuantity > Math.Abs(position.PendingFills.Sum(x => x.FillQuantity))) { // trade closed position.PendingFills.Add(fill); position.TotalFees += orderFee; var reverseQuantity = position.PendingFills.Sum(x => x.FillQuantity); var index = _matchingMethod == FillMatchingMethod.FIFO ? 0 : position.PendingFills.Count - 1; var entryTime = position.PendingFills[0].UtcTime; var totalEntryQuantity = 0; var totalExitQuantity = 0; var entryAveragePrice = 0m; var exitAveragePrice = 0m; while (position.PendingFills.Count > 0) { if (Math.Sign(position.PendingFills[index].FillQuantity) != Math.Sign(fill.FillQuantity)) { // entry totalEntryQuantity += position.PendingFills[index].FillQuantity; entryAveragePrice += (position.PendingFills[index].FillPrice - entryAveragePrice) * position.PendingFills[index].FillQuantity / totalEntryQuantity; } else { // exit totalExitQuantity += position.PendingFills[index].FillQuantity; exitAveragePrice += (position.PendingFills[index].FillPrice - exitAveragePrice) * position.PendingFills[index].FillQuantity / totalExitQuantity; } position.PendingFills.RemoveAt(index); if (_matchingMethod == FillMatchingMethod.LIFO && index > 0) index--; } var direction = Math.Sign(fill.FillQuantity) < 0 ? TradeDirection.Long : TradeDirection.Short; AddNewTrade(new Trade { Symbol = fill.Symbol, EntryTime = entryTime, EntryPrice = entryAveragePrice, Direction = direction, Quantity = Math.Abs(totalEntryQuantity), ExitTime = fill.UtcTime, ExitPrice = exitAveragePrice, ProfitLoss = Math.Round((exitAveragePrice - entryAveragePrice) * Math.Abs(totalEntryQuantity) * Math.Sign(totalEntryQuantity) * conversionRate, 2), TotalFees = position.TotalFees, MAE = Math.Round((direction == TradeDirection.Long ? position.MinPrice - entryAveragePrice : entryAveragePrice - position.MaxPrice) * Math.Abs(totalEntryQuantity) * conversionRate, 2), MFE = Math.Round((direction == TradeDirection.Long ? position.MaxPrice - entryAveragePrice : entryAveragePrice - position.MinPrice) * Math.Abs(totalEntryQuantity) * conversionRate, 2) }); _positions.Remove(fill.Symbol); if (reverseQuantity != 0) { // direction reversal fill.FillQuantity = reverseQuantity; _positions[fill.Symbol] = new Position { PendingFills = new List<OrderEvent> { fill }, TotalFees = 0, MinPrice = fill.FillPrice, MaxPrice = fill.FillPrice }; } } else { // trade open position.PendingFills.Add(fill); position.TotalFees += orderFee; } } } private void ProcessFillUsingFlatToReduced(OrderEvent fill, decimal orderFee, decimal conversionRate) { Position position; if (!_positions.TryGetValue(fill.Symbol, out position) || position.PendingFills.Count == 0) { // no pending executions for symbol _positions[fill.Symbol] = new Position { PendingFills = new List<OrderEvent> { fill }, TotalFees = orderFee, MinPrice = fill.FillPrice, MaxPrice = fill.FillPrice }; return; } SetMarketPrice(fill.Symbol, fill.FillPrice); var index = _matchingMethod == FillMatchingMethod.FIFO ? 0 : position.PendingFills.Count - 1; if (Math.Sign(fill.FillQuantity) == Math.Sign(position.PendingFills[index].FillQuantity)) { // execution has same direction of trade position.PendingFills.Add(fill); position.TotalFees += orderFee; } else { // execution has opposite direction of trade var entryTime = position.PendingFills[index].UtcTime; var totalExecutedQuantity = 0; var entryPrice = 0m; position.TotalFees += orderFee; while (position.PendingFills.Count > 0 && Math.Abs(totalExecutedQuantity) < fill.AbsoluteFillQuantity) { if (fill.AbsoluteFillQuantity >= Math.Abs(position.PendingFills[index].FillQuantity)) { if (_matchingMethod == FillMatchingMethod.LIFO) entryTime = position.PendingFills[index].UtcTime; totalExecutedQuantity -= position.PendingFills[index].FillQuantity; entryPrice -= (position.PendingFills[index].FillPrice - entryPrice) * position.PendingFills[index].FillQuantity / totalExecutedQuantity; position.PendingFills.RemoveAt(index); if (_matchingMethod == FillMatchingMethod.LIFO && index > 0) index--; } else { totalExecutedQuantity += fill.FillQuantity; entryPrice += (position.PendingFills[index].FillPrice - entryPrice) * fill.FillQuantity / totalExecutedQuantity; position.PendingFills[index].FillQuantity += fill.FillQuantity; } } var direction = totalExecutedQuantity < 0 ? TradeDirection.Long : TradeDirection.Short; AddNewTrade(new Trade { Symbol = fill.Symbol, EntryTime = entryTime, EntryPrice = entryPrice, Direction = direction, Quantity = Math.Abs(totalExecutedQuantity), ExitTime = fill.UtcTime, ExitPrice = fill.FillPrice, ProfitLoss = Math.Round((fill.FillPrice - entryPrice) * Math.Abs(totalExecutedQuantity) * Math.Sign(-totalExecutedQuantity) * conversionRate, 2), TotalFees = position.TotalFees, MAE = Math.Round((direction == TradeDirection.Long ? position.MinPrice - entryPrice : entryPrice - position.MaxPrice) * Math.Abs(totalExecutedQuantity) * conversionRate, 2), MFE = Math.Round((direction == TradeDirection.Long ? position.MaxPrice - entryPrice : entryPrice - position.MinPrice) * Math.Abs(totalExecutedQuantity) * conversionRate, 2) }); if (Math.Abs(totalExecutedQuantity) < fill.AbsoluteFillQuantity) { // direction reversal fill.FillQuantity -= totalExecutedQuantity; position.PendingFills = new List<OrderEvent> { fill }; position.TotalFees = 0; position.MinPrice = fill.FillPrice; position.MaxPrice = fill.FillPrice; } else if (Math.Abs(totalExecutedQuantity) == fill.AbsoluteFillQuantity) { if (position.PendingFills.Count == 0) _positions.Remove(fill.Symbol); else position.TotalFees = 0; } } } /// <summary> /// Adds a trade to the list of closed trades, capping the total number only in live mode /// </summary> private void AddNewTrade(Trade trade) { _closedTrades.Add(trade); // Due to memory constraints in live mode, we cap the number of trades if (!_liveMode) return; // maximum number of trades if (_closedTrades.Count > LiveModeMaxTradeCount) { _closedTrades.RemoveRange(0, _closedTrades.Count - LiveModeMaxTradeCount); } // maximum age of trades while (_closedTrades.Count > 0 && _closedTrades[0].ExitTime.Date.AddMonths(LiveModeMaxTradeAgeMonths) < DateTime.Today) { _closedTrades.RemoveAt(0); } } } }