Ejemplo n.º 1
0
        public void AddRate(Rate newRate)
        {
            if(newRate == null)
            {
                throw new ArgumentNullException(nameof(newRate));
            }

            var roundedTime = newRate.Time.RoundDown(period);
            if(roundedTime < this.timestamp)
            {
                throw new Exception("Data stream is not advancing in time");
            }

            if(timestamp == null)
            {
                this.timestamp = roundedTime;
                this.open = newRate.MidPoint;
            }

            if(roundedTime > this.timestamp)
            {
                OnNewCandleCreated(new CandleBuilderEventArgs(CurrentCandle));
                this.timestamp = roundedTime;
                this.open = newRate.MidPoint;
            }

            this.high = newRate.MidPoint >= this.high.GetValueOrDefault(newRate.MidPoint) ? newRate.MidPoint : high;
            this.low = newRate.MidPoint <= this.low.GetValueOrDefault(newRate.MidPoint) ? newRate.MidPoint :  low;
            this.close = newRate.MidPoint;
        }
        public void SetRate(Rate newRate, Rate accountCurrencyRate)
        {
            //TODO: Check margin call
            //TODO: Handle error on leverage and insuficient funds
            //TODO: Handle target profit
            this.currentRate = newRate;
            this.currentAccountCurrencyRate = accountCurrencyRate;

            var currentTrade = this.trades.FirstOrDefault();
            if (currentTrade == null)
            {
                return;
            }

            decimal amountToCompare;
            if (currentTrade.Side == OrderSideBuy) //In case of a long trade
            {
                if (currentTrade.TrailingStop > 0)
                {
                    var newTrailingAmount = newRate.Bid - currentTrade.TrailingStop * newRate.QuoteCurrency.GetPipFraction();

                    currentTrade.TrailingAmount = newTrailingAmount >= currentTrade.TrailingAmount
                        ? newTrailingAmount
                        : currentTrade.TrailingAmount;
                    amountToCompare = currentTrade.TrailingAmount;
                }
                else if (currentTrade.StopLoss > 0)
                {
                    currentTrade.TrailingAmount = 0;
                    amountToCompare = currentTrade.StopLoss;
                }
                else
                {
                    amountToCompare = 0; //TODO: To code a strategy without stops
                }
            }
            else  //In case of short trade
            {
                if (currentTrade.TrailingStop > 0)
                {
                    var newTrailingAmount = newRate.Ask + currentTrade.TrailingStop * newRate.QuoteCurrency.GetPipFraction();
                    currentTrade.TrailingAmount = newTrailingAmount < currentTrade.TrailingAmount
                        ? newTrailingAmount
                        : currentTrade.TrailingAmount;
                    amountToCompare = currentTrade.TrailingAmount;
                }
                else if (currentTrade.StopLoss > 0)
                {
                    currentTrade.TrailingAmount = 0;
                    amountToCompare = currentTrade.StopLoss;
                }
                else
                {
                    amountToCompare = 0;
                }
            }

            this.LiquidateTrade(newRate, accountCurrencyRate, currentTrade, amountToCompare);
        }
        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;
        }
        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);
        }
Ejemplo n.º 5
0
 private bool ValidateIndicatorsState(Rate currentRate)
 {
     return
         this.candles.Count >= MinNbOfCandles
         && (this.candles.Last().Timestamp - currentRate.Time).Minutes <= this.PeriodInMinutes;
 }
        private decimal GetAccountCurrencyRate(Rate newRate, Rate accountCurrencyRate)
        {
            var quoteInstrument = newRate.QuoteCurrency.Safe().Trim().ToUpper();
            var baseInstrument = this.accountInformation.AccountCurrency.Safe().Trim().ToUpper();

            if (quoteInstrument == baseInstrument) return 1.00m;

            var price = (accountCurrencyRate.Ask + accountCurrencyRate.Bid)/2.00m;
            if (accountCurrencyRate.BaseCurrency == this.accountInformation.AccountCurrency)
            {
                return 1.00m / price;
            }

            return price;
        }
Ejemplo n.º 7
0
        private void PlaceOrder(string side, Rate rate)
        {
            var stopLossDistance = this.CalculateStopLossDistanceInPips(side, rate.QuoteCurrency, rate);
            var positionSizeInUnits = this.CalculatePositionSize(stopLossDistance, side, rate);
            //TODO: Decide if to user lower-upper bounds or just market order and assume the slippage

            var newOrder = new Order
            {
                Instrument = this.Instrument,
                Units = positionSizeInUnits,
                Side = side,
                OrderType = OrderTypeMarket,
                TrailingStop = stopLossDistance,
                AcountId = this.AccountId,
                Timestamp = rate.Time
            };
            this.tradingAdapter.PlaceOrder(newOrder);

            Trace.TraceInformation("Order placed :{0}", JsonConvert.SerializeObject(newOrder));
        }
Ejemplo n.º 8
0
        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;
            }
        }
Ejemplo n.º 9
0
        private bool CanGoShort(Rate rate)
        {
            var slowSmaLowValue = this.slowSmaLow.Values.LastOrDefault();
            var fastEmaLowValue = this.fastEmaLow.Values.LastOrDefault();

            if (slowSmaLowValue < rate.Bid)
            {
                return false;
            }

            if (fastEmaLowValue < rate.Bid)
            {
                return false;
            }

            var currentCandle = this.candles.LastOrDefault();
            if (currentCandle == null)
            {
                return false;
            }

            if (!currentCandle.IsDown)
            {
                return false;
            }

            if (fastEmaLowValue < currentCandle.Open)
            {
                return false;
            }

            if (currentCandle.IsReversal(GetThreshold()))
            {
                return false;
            }

            var previousCandle = this.candles.TakeLast(2).Skip(1).FirstOrDefault();

            if (previousCandle == null)
            {
                return false;
            }

            if (previousCandle.IsReversal(GetThreshold()))
            {
                return false;
            }

            if (ConfirmPreviousCandleForBid(previousCandle, currentCandle))
            {
                return true;
            }

            previousCandle = this.candles.TakeLast(3).Skip(2).FirstOrDefault();

            if (previousCandle == null)
            {
                return false;
            }

            return ConfirmPreviousCandleForBid(previousCandle, currentCandle);
        }
Ejemplo n.º 10
0
        private decimal CalculateStopLossDistanceInPips(string side, string quoteCurrency, Rate currentRate)
        {
            var pipFraction = quoteCurrency.GetPipFraction();
            if (side == OrderSideBuy)
            {
                var lowLimit = this.fastEmaLow.Values.LastOrDefault();
                return Math.Ceiling((currentRate.Ask - lowLimit) / pipFraction);
            }

            var highLimit = this.fastEmaHigh.Values.LastOrDefault();
            return Math.Abs((highLimit - currentRate.Bid) / pipFraction);
        }
Ejemplo n.º 11
0
        private int CalculatePositionSize(decimal stopLoss, string side, Rate currentRate)
        {
            var accountInformation = this.tradingAdapter.GetAccountInformation(this.AccountId);
            //Account currency should be USD
            var balance = accountInformation.Balance.SafeParseDecimal().GetValueOrDefault();
            var maxRiskAmount = balance * BaseRiskPercentage;

            var pipFraction = currentRate.QuoteCurrency.GetPipFraction();

            var useRate = side == OrderSideBuy ? currentRate.Ask : currentRate.Bid;
            //Instead of hardcoding the account currency, better to read it from the api.
            var maxRiskAmountInQuote = currentRate.QuoteCurrency == "USD" ? maxRiskAmount : maxRiskAmount * useRate;
            var positionSize = (maxRiskAmountInQuote / stopLoss) / pipFraction;

            var accountMarginRate = accountInformation.MarginRate.SafeParseDecimal().GetValueOrDefault();
            var accountLeverage = 1m;
            if (accountMarginRate > 0)
            {
                accountLeverage = 1m / accountMarginRate;
            }

            var availablePositionSize = Math.Min(positionSize, balance * accountLeverage * useRate);

            //TODO: Use Kelly Criterior to calculate position size
            //TODO: Implement criterias for minimum stop loss condition

            return (int)availablePositionSize;
        }
Ejemplo n.º 12
0
        /// <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);
            }
        }
Ejemplo n.º 13
0
        //Return structure containig reason why cannot go long to improve testability
        public bool CanGoLong(Rate rate)
        {
            var slowSmaHighValue = this.slowSmaHigh.Values.LastOrDefault();
            var fastEmaHighValue = this.fastEmaHigh.Values.LastOrDefault();

            if (slowSmaHighValue > rate.Ask)
            {
                return false;
            }

            if (fastEmaHighValue > rate.Ask)
            {
                return false;
            }

            var currentCandle = this.candles.LastOrDefault();
            if (currentCandle == null)
            {
                return false;
            }

            if (!currentCandle.IsUp)
            {
                return false;
            }

            if (fastEmaHighValue > currentCandle.Open)
            {
                return false;
            }

            if (currentCandle.IsReversal(GetThreshold()))
            {
                return false;
            }

            var previousCandle = this.candles.TakeLast(2).Skip(1).FirstOrDefault();

            if (previousCandle == null || previousCandle.IsReversal(GetThreshold()))
            {
                return false;
            }

            if (!this.ConfirmPreviousCandleForAsk(previousCandle, currentCandle))
            {
                previousCandle = this.candles.TakeLast(3).Skip(2)
                    .FirstOrDefault();

                if (previousCandle == null || previousCandle.IsReversal(GetThreshold()))
                {
                    return false;
                }

                if (!this.ConfirmPreviousCandleForAsk(previousCandle, currentCandle))
                {
                    return false;
                }
            }

            return true;
        }