Overall Statistics
Total Trades
65
Average Win
0.01%
Average Loss
0%
Compounding Annual Return
-0.992%
Drawdown
1.900%
Expectancy
0
Net Profit
-0.582%
Sharpe Ratio
-0.566
Probabilistic Sharpe Ratio
9.123%
Loss Rate
0%
Win Rate
100%
Profit-Loss Ratio
0
Alpha
-0.01
Beta
0.005
Annual Standard Deviation
0.014
Annual Variance
0
Information Ratio
-2.165
Tracking Error
0.169
Treynor Ratio
-1.605
Total Fees
$0.00
namespace QuantConnect.Algorithm.CSharp
{
	public class TachyonMultidimensionalSplitter : QCAlgorithm
	{
		public bool enableLonging;
		public bool enableShorting;
		public Symbol symbol;
		public string symbolString;
		public StandardDeviation stdev;
		public SimpleMovingAverage mean;
		public RollingWindow<decimal> priceSeries;
		private OrderTicket loLower;
		private OrderTicket loUpper;
		private OrderTicket loClose;
		private decimal percentageOfEquity;
		private decimal lvl1;
		private int window;
		public decimal qty {
			get {
				return Portfolio[symbol].Quantity;
			}
		}
		public bool isFlat {
			get {
				return !Portfolio[symbol].IsLong && !Portfolio[symbol].IsShort;
			}
		}
		public bool isLong {
			get {
				return Portfolio[symbol].IsLong;
			}
		}
		public bool isShort {
			get {
				return Portfolio[symbol].IsShort;
			}
		}
		
        
        public override void Initialize()
        {
            SetStartDate(2020, 6, 1);
            SetEndDate(2021, 1, 1);
            SetCash(100000);
            
            symbolString = GetParameter("asset_name");
            symbol = AddForex(symbolString, Resolution.Minute).Symbol;
            Securities[symbolString].SetSlippageModel(new CustomSlippageModel(this));
            
            window = int.Parse(GetParameter("window"));
            percentageOfEquity = decimal.Parse(GetParameter("percentage_of_equity"));
            lvl1 = decimal.Parse(GetParameter("lvl1"));
            
            enableLonging = int.Parse(GetParameter("enable_longing")) == 1;
            enableShorting = int.Parse(GetParameter("enable_shorting")) == 1;
            
            mean = new SimpleMovingAverage(window);
            stdev = new StandardDeviation(window);
            priceSeries = new RollingWindow<decimal>(window + 1);
            
            // var zChart = new Chart("Z-Score");
            // var pChart = new Chart("Market Close");
            // zChart.AddSeries(new Series("Line Plot", SeriesType.Line, 0));
            // pChart.AddSeries(new Series("Line Plot", SeriesType.Line, 0));
            // AddChart(zChart);
            // AddChart(pChart);
            
            foreach (var bar in History<QuoteBar>(symbolString, window * 2 + 1, Resolution.Minute).ToList()) {
                priceSeries.Add(bar.Close);
                
                if (priceSeries.IsReady) {
                    var diff = priceSeries[0] - priceSeries[window];
                    stdev.Update(bar.Time, diff);
                    mean.Update(bar.Time, diff);
                }
            }
        }
        
        
        public override void OnData(Slice data)
        {
            if (!data.ContainsKey(symbol)) {
                return;
            }
            
            var priceNow = data[symbol].Close;
            priceSeries.Add(priceNow);
            var latestReturn = priceNow - priceSeries[window];
            mean.Update(Time, latestReturn);
            stdev.Update(Time, latestReturn);
            
            if (!stdev.IsReady || !mean.IsReady || !priceSeries.IsReady) {
            	throw new Exception("Indicator not ready: Should never happen!");
            }
            
            var zscore = GetZScore();
            
            // var startTime = new DateTime(2021, 1, 5, 11, 40, 0);
            // var endTime = new DateTime(2021, 1, 5, 11, 58, 0);
            // if (Time < startTime || Time > endTime) { return; }
            
            // Plot("Market Close", "Line Plot", priceNow);
            // Plot("Z-Score", "Line Plot", zscore);
            
            if (isFlat) {
                UpdateEntryOrder();
                return;
            }
            if (Portfolio[symbol].UnrealizedProfitPercent < -0.1m) {
            	Debug("Liquidate");
                Liquidate(symbol);
            }
        }
        
        
        public void UpdateEntryOrder()
        {
            if (enableLonging) {
                var targetPriceL = GetPriceAtZScore(-lvl1);
                
                if (loLower == null) {
                    int qty = (int)Math.Floor(Portfolio.Cash * percentageOfEquity / priceSeries[0]);
                    Debug(String.Format("Set new long entry level at {0}", targetPriceL));
                    loLower = LimitOrder(symbol, qty, targetPriceL);
                } else {
                    loLower.Update(new UpdateOrderFields() { LimitPrice = targetPriceL });
                }
            }
            if (enableShorting) {
                var targetPriceS = GetPriceAtZScore(+lvl1);
                
                if (loUpper == null) {
                    int qty = (int)Math.Floor(Portfolio.Cash * percentageOfEquity / priceSeries[0]);
                    Debug(String.Format("Set new short entry level at {0}", targetPriceS));
                    loUpper = LimitOrder(symbol, -qty, targetPriceS);
                } else {
                    loUpper.Update(new UpdateOrderFields() { LimitPrice = targetPriceS });
                }
            }
        }
        
        
        public void UpdateExitOrder()
        {
            var targetPrice = GetPriceAtZScore(0m);
            
            if (loClose == null) {
                int qty = (int)Math.Floor(Portfolio[symbol].Quantity);
                Debug(String.Format("Set new exit level at {0}", targetPrice));
                if (isLong) {
                    loClose = LimitOrder(symbol, -qty, targetPrice, "exit long");
                } else {
                    loClose = LimitOrder(symbol, qty, targetPrice, "exit short");
                }
            } else {
                loClose.Update(new UpdateOrderFields() { LimitPrice = targetPrice });
            }
        }
        
        
        public void OnExitFilled()
        {
            if (qty > 0m || qty < 0m) {
                Liquidate(symbol);
            }
            if (loLower != null && loLower.Status == OrderStatus.PartiallyFilled) {
            	loLower.Cancel();
            }
            if (loUpper != null && loUpper.Status == OrderStatus.PartiallyFilled) {
            	loUpper.Cancel();
            }
            loLower = null;
            loUpper = null;
            loClose = null;
        }
        
        
        public void OnEntryFilled(decimal fillPrice)
        {
            var direction = isLong ? "long" : "short";
            Debug(String.Format("Entry {0} filled at {1}", direction, fillPrice));
            UpdateExitOrder();
        }
        
        
        public override void OnOrderEvent(OrderEvent evt)
        {
        	if (enableLonging) {
                if (loLower != null && evt.OrderId == loLower.OrderId) {
                    if (evt.Status == OrderStatus.PartiallyFilled || evt.Status == OrderStatus.Filled) {
                        // cancel opposite side entry order
                        if (loUpper != null) {
                            loUpper.Cancel();
                            loUpper = null;
                        }
                        OnEntryFilled(evt.FillPrice);
                    }
                    return;
                }
            }
            if (enableShorting) {
                if (loUpper != null && evt.OrderId == loUpper.OrderId) {
                    if (evt.Status == OrderStatus.PartiallyFilled || evt.Status == OrderStatus.Filled) {
                        // cancel opposite side entry order
                        if (loLower != null) {
                            loLower.Cancel();
                            loLower = null;
                        }
                        OnEntryFilled(evt.FillPrice);
                    }
                    return;
                }
            }
            if (loClose != null && evt.OrderId == loClose.OrderId) {
                if (evt.Status == OrderStatus.PartiallyFilled) {
                    Liquidate(symbol);
                }
                if (evt.Status == OrderStatus.Filled) {
                    Debug(String.Format("Exit filled at {0}", evt.FillPrice));
                    OnExitFilled();
                    UpdateEntryOrder();
                    return;
                }
                return;
            }
            if (evt.Status == OrderStatus.Submitted) {
            	return;
            }
            if (isFlat) {
                var orderName = "";
                if (loUpper != null && evt.OrderId == loUpper.OrderId) {
                    orderName = "enter short";
                }
                if (loLower != null && evt.OrderId == loLower.OrderId) {
                    orderName = "enter long";
                }
                if (loClose != null && evt.OrderId == loClose.OrderId) {
                    orderName = "exit";
                }
                Debug(String.Format("Order event while flat: {0} {1}", evt.Status, orderName));
            }
        }
        
        
        public decimal GetPriceAtZScore(decimal zscore)
        {
            return Math.Round(zscore * 1.5m * stdev.Current.Value + mean.Current.Value + priceSeries[window], 5);
        }
        
        
        public decimal GetZScore()
        {
            decimal zscore = 0m;
            if (stdev.Current.Value > 0m || stdev.Current.Value < 0m)
            {
                zscore = (priceSeries[0] - priceSeries[window] - mean.Current.Value) / stdev.Current.Value;
            }
            return zscore;
        }
    }

    public class CustomSlippageModel : ISlippageModel {
        private readonly QCAlgorithm _algorithm;
        
        public CustomSlippageModel(QCAlgorithm algorithm) {
            _algorithm = algorithm;
        }
        
        public decimal GetSlippageApproximation(Security asset, Order order) {
            return 0m;
        }
    }
}