public override void PlaceOrder(Order order) { var price = order.Side == OrderSideBuy ? this.CurrentRate.Ask : this.CurrentRate.Bid; var trailingSize = order.TrailingStop * 0.0001m; var trailingAmount = order.Side == OrderSideBuy ? price - trailingSize : price + trailingSize; this.currentTrade = new Trade { Side = order.Side, TrailingAmount = trailingAmount, Price = price, TrailingStop = order.TrailingStop, StopLoss = order.StopLoss, Time = order.Timestamp, Units = order.Units }; Console.WriteLine("Order placed ..."); }
public override void UpdateTrade(Trade updatedTrade) { this.currentTrade.StopLoss = updatedTrade.StopLoss; this.currentTrade.TrailingAmount = updatedTrade.TrailingAmount; this.currentTrade.TrailingStop = updatedTrade.TrailingStop; }
private void LiquidateTrade(Rate newRate, Rate accountCurrencyRate, Trade currentTrade, decimal referencePrice) { var gainLoss = 0m; if (currentTrade.Side == OrderSideBuy) { if ((newRate.Bid - referencePrice) > DebouncingLimit) return; gainLoss = referencePrice - currentTrade.Price; } else { if (referencePrice - newRate.Ask > DebouncingLimit) return; gainLoss = currentTrade.Price - referencePrice; } Console.WriteLine("Stop loss triggered=>Gain/Loss={0} pips", gainLoss / newRate.QuoteCurrency.GetPipFraction()); var gainLossInQuoteCurrency = gainLoss * currentTrade.Units; var gainLossConversionRate = this.GetAccountCurrencyRate(newRate, accountCurrencyRate); this.accountInformation.Balance += gainLossInQuoteCurrency * gainLossConversionRate; Console.WriteLine("{1} - Balance = {0}", this.accountInformation.Balance, currentTrade.Time); this.trades.Remove(currentTrade); }
public override Rate GetRate(string instrument) { var currentCandle = this.GetCurrentCandle(this.nbOfCalls); var rateValue = currentCandle.FullRange * (decimal)this.rateGenerator.NextDouble() + currentCandle.Low; var minuteFraction = (double)(this.nbOfCalls % this.ticksInPeriod) / this.ticksInPeriod; this.nbOfCalls++; this.CurrentRate = new Rate { Ask = rateValue, Bid = rateValue, Instrument = instrument, Time = currentCandle.Timestamp.AddMinutes(this.periodInMinutes * minuteFraction) }; if (this.currentTrade == null) { return this.CurrentRate; } var amountToCompare = 0m; if (this.currentTrade.Side == OrderSideBuy) { if (this.currentTrade.TrailingStop > 0) { var newTrailingAmount = this.CurrentRate.Bid - this.currentTrade.TrailingStop * 0.0001m; this.currentTrade.TrailingAmount = newTrailingAmount >= this.currentTrade.TrailingAmount ? newTrailingAmount : this.currentTrade.TrailingAmount; amountToCompare = this.currentTrade.TrailingAmount; } else { this.currentTrade.TrailingAmount = 0; amountToCompare = this.currentTrade.StopLoss; } } else { if (this.currentTrade.TrailingStop > 0) { var newTrailingAmount = this.CurrentRate.Ask + this.currentTrade.TrailingStop * 0.0001m; this.currentTrade.TrailingAmount = newTrailingAmount < this.currentTrade.TrailingAmount ? newTrailingAmount : this.currentTrade.TrailingAmount; amountToCompare = this.currentTrade.TrailingAmount; } else { this.currentTrade.TrailingAmount = 0; amountToCompare = this.currentTrade.StopLoss; } } var gainLoss = 0m; if (this.currentTrade.Side == OrderSideBuy) { if (this.CurrentRate.Bid < amountToCompare) { gainLoss = (this.currentTrade.Price - amountToCompare) / DolarsByPip; Console.WriteLine("Stop loss triggered=>Gain/Loss={0}", gainLoss); this.balancePips += gainLoss * this.currentTrade.Units * DolarsByPip; Console.WriteLine("{1} - Balance = {0}", this.balancePips, this.currentTrade.Time); this.currentTrade = null; } } else { if (this.CurrentRate.Ask > amountToCompare) { gainLoss = (amountToCompare - this.currentTrade.Price) / DolarsByPip; Console.WriteLine("Stop loss triggered=>Gain/Loss={0}", gainLoss); this.balancePips += gainLoss * this.currentTrade.Units * DolarsByPip; Console.WriteLine("{1} - Balance = {0}", this.balancePips, this.currentTrade.Time); this.currentTrade = null; } } return this.CurrentRate; }
public void UpdateTrade(Trade updatedTrade) { var trade = this.trades.FirstOrDefault(x => x.Id == updatedTrade.Id); if (trade == null) { throw new Exception($"No such trade with Id = {updatedTrade.Id}"); } trade.StopLoss = updatedTrade.StopLoss; trade.TakeProfit = updatedTrade.TakeProfit; trade.TrailingStop = updatedTrade.TrailingStop; }
private bool ShouldSetStopLoss(Trade currentTrade, Rate currentRate) { if (currentTrade.StopLoss > 0) { return false; } var pipFraction = currentTrade.QuoteCurrency.GetPipFraction(); decimal? spreadPips = (currentRate.Ask - currentRate.Bid) / (2 * pipFraction); var cushionDeltaPrice = (spreadPips + SlippagePips + MarginalGainPips) * pipFraction; switch (currentTrade.Side) { case OrderSideBuy: return currentTrade.TrailingAmount >= currentTrade.Price + cushionDeltaPrice; default: return currentTrade.TrailingAmount <= currentTrade.Price - cushionDeltaPrice; } }
private bool ShouldCloseTrade(Trade currentTrade) { if (currentTrade.TrailingStop > 0) { return false; } var currentCandle = this.candles.LastOrDefault(); switch (currentTrade.Side) { case OrderSideBuy: { var fastEmaLowValue = this.fastEmaLow.Values.FirstOrDefault(); return currentCandle.Close < fastEmaLowValue; } case OrderSideSell: { var fastEmaHighValue = this.fastEmaHigh.Values.FirstOrDefault(); return currentCandle.Close > fastEmaHighValue; } } return true; //This should not happen, but in case it happens, it could be wise to close the trade or maybe send a notification }
/// <summary> /// Should happen every minute because the check needs to be frequent, /// but the candles are queried in the predefined timeframe /// </summary> public void CheckRate(Rate newRate) { Trace.CorrelationManager.ActivityId = Guid.NewGuid(); validations.Clear(); if (this.CurrentRate != null && newRate.Time < this.CurrentRate.Time) { //This is likely to happen in a backtest or practice scenario validations.AddErrorMessage("Rate timer is going backwards"); } else { this.CurrentRate = newRate; } candleBuilder.AddRate(newRate); Trace.TraceInformation("New rate : {0}", JsonConvert.SerializeObject(newRate)); if (!this.isbacktesting) { var systemTimeDiff = this.dateProvider.GetCurrentUtcDate() - newRate.Time.ToUniversalTime(); if (systemTimeDiff.TotalMinutes >= MaxRateStaleTime) { validations.AddErrorMessage("Price timer lagging behind current time"); } } if (!this.ValidateIndicatorsState(newRate)) { validations.AddErrorMessage("Incomplete indicator values"); } if (this.tradingAdapter.HasOpenTrade(this.AccountId)) { var currentTrade = this.tradingAdapter.GetOpenTrade(this.AccountId); //Not sure if doing this or just keep the trailing stop if (!validations.IsValid && currentTrade.StopLoss > 0) { Trace.TraceInformation("Closing trade because of validation errors and open trade with stop loss"); //If the trade is open with stop loss, it is likely that we can close the trade with a profit this.tradingAdapter.CloseTrade(this.AccountId, currentTrade.Id); Trace.TraceError(validations.ToString()); return; } if (!validations.IsValid) { Trace.TraceInformation("Exit early because of validation errors and open trade"); Trace.TraceError(validations.ToString()); return; } if (this.ShouldCloseTrade(currentTrade)) { Trace.TraceInformation("Closing trade"); this.tradingAdapter.CloseTrade(this.AccountId, currentTrade.Id); return; } if (this.ShouldSetStopLoss(currentTrade, newRate)) { Trace.TraceInformation("Break even"); var updatedTrade = new Trade { Id = currentTrade.Id, StopLoss = currentTrade.TrailingAmount, TrailingStop = 0, TakeProfit = 0, AccountId = this.AccountId }; this.tradingAdapter.UpdateTrade(updatedTrade); return; } Trace.TraceInformation("Exit early because of open trade, trailing amount {0}", currentTrade.TrailingAmount); return; } if (!validations.IsValid) { Trace.TraceInformation("Exit early because of validation errors"); Trace.TraceError(validations.ToString()); return; } if (this.tradingAdapter.HasOpenOrder(this.AccountId)) { Trace.TraceInformation("Open order"); return; } if (!this.IsTradingDay()) { Trace.TraceInformation("Non trading day"); return; } if (!this.IsTradingTime()) { Trace.TraceInformation("Non trading time"); return; } var currentSpread = Math.Abs(newRate.Ask - newRate.Bid) * (1.00m / newRate.QuoteCurrency.GetPipFraction()); if (currentSpread > MaxSpread) { Trace.TraceInformation($"Not enough liquidity, spread = {currentSpread}"); return; } var currentAdxValue = this.adx.Values.LastOrDefault() * 100; if (currentAdxValue < AdxTrendLevel) { Trace.TraceInformation("ADX ({0}) below threshold ({1})", currentAdxValue, AdxTrendLevel); return; } //TODO: Set history to calculate adx direction in app config //Calculating adx trend with more than one candle back will cause to lost many chances to enter and will enter late var previousAdxValue = this.adx.Values.TakeLast(2).Skip(1).FirstOrDefault() * 100m; if (currentAdxValue <= previousAdxValue) { Trace.TraceInformation("ADX not increasing {0} => {1}", previousAdxValue, currentAdxValue); return; } if (this.CanGoLong(newRate)) { this.PlaceOrder(OrderSideBuy, newRate); return; } if (this.CanGoShort(newRate)) { this.PlaceOrder(OrderSideSell, newRate); } }
public virtual void UpdateTrade(Trade updatedTrade) { var result = this.proxy.PatchTradeAsync(updatedTrade.AccountId, updatedTrade.Id, new Dictionary<string, string> { { "takeProfit", updatedTrade.TakeProfit.ToString(CultureInfo.InvariantCulture) }, { "trailingStop", updatedTrade.TrailingStop.ToString(CultureInfo.InvariantCulture) }, { "stopLoss", updatedTrade.StopLoss.ToString(CultureInfo.InvariantCulture) } }).Result; }