My cBot randomly crashes when placing market order

Created at 05 Mar 2019, 15:46
How’s your experience with the cTrader Platform?
Your feedback is crucial to cTrader's development. Please take a few seconds to share your opinion and help us improve your trading experience. Thanks!
ER

erik.lindblad

Joined 05.03.2019

My cBot randomly crashes when placing market order
05 Mar 2019, 15:46


Hi!

This is my first post in this forum. My problem is that my bot crashes with NullReferenceException every other time a place a market order. The order is successfully placed, but the bot crashes immediately afterwards in Positions.Opened and shuts down the bot. I am not at all using this event and I have not subscribed to it, so I guess it crashes inside the api code.

I cannot figure out what object is actually null here. As you can see from the journal log, the only input argument that is null is the expiration date which is nullable so it should be fine. Label and Comment are just strings. Been struggling with this for days now and I cannot see any pattern among the orders that fails. Thankful for any help!
 


TradeResult result = ExecuteMarketOrder(cAlgo.API.TradeType.Buy, symbol, order.Volume, order.Label, stopLossPips, takeProfitPips, null, order.MetaData);

 

Best regards
Erik


@erik.lindblad
Replies

PanagiotisCharalampous
05 Mar 2019, 15:48

Hi Erik,

Thanks for posting in our forum. Can you share the full cBot code so that we can have a look? 

Best Regards,

Panagiotis


@PanagiotisCharalampous

erik.lindblad
05 Mar 2019, 16:02

Hi Panagiotis! Thanks for your reply. Sure, I can post the code, although it is almost 200 lines. I am using the cBot as a trade executor only. Receving signals from another application I have built. So the cBot basically just takes a trade signal, validates it and places an order of the correct type on the market. So the bot handles all symbols and do not care abut time frames etc, it just executes trades. Only the market order fails, and not all of them, but maybe 30%. Here is the code.
 


namespace MasterlessTrading
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;
    using cAlgo.API;
    using MasterlessTrading.Ipc;
    using MasterlessTrading.Ipc.Contracts;
    using Newtonsoft.Json;

    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)]
    public class RoninBot : Robot
    {
        [Parameter(DefaultValue = 9712)]
        public int ServerPort { get; set; }

        [Parameter(DefaultValue = 2.0)]
        public double PercentageRiskPerTrade { get; set; }

        [Parameter(DefaultValue = 1.0)]
        public double MaximumPipOffset { get; set; }

        private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();

        private TradeSettings _tradeSettings;
        private OrderFactory _orderFactory;
        private OrderValidator _orderValidator;
        private RoninSignalTransformer _roninSignalValidator;
        private IpcServer _server;
        public const string ProviderGreen = "Green";
        public const string ProviderRonin = "Ronin";

        protected override void OnStart()
        {
            _orderFactory = new OrderFactory();
            _orderValidator = new OrderValidator();
            _roninSignalValidator = new RoninSignalTransformer();
            _tradeSettings = new TradeSettings(PercentageRiskPerTrade, MaximumPipOffset);

            var tgClient = Guid.Parse("5C0A5345-2798-47A8-BC41-6598C41E2051");

            _server = IpcServer.Listen(Guid.Empty, this.ServerPort, new[]
            {
                typeof(RoninSignal).Assembly
            }, Print);

            _server.Consume<RoninSignal>(signal =>
            {
                DebugLog(tgClient, signal);

                var marketData = GetMarketData(signal.Currency.ToString());
                _roninSignalValidator.TransformSignal(signal, marketData);
                var orders = _orderFactory.CreateRoninOrders(signal, marketData, _tradeSettings, Account.Balance, ProviderRonin);
                var validOrders = GetValidOrders(orders, marketData);
                ExecuteOrders(validOrders);
            });

            _server.Consume<GreenSignal>(signal =>
            {
                DebugLog(tgClient, signal);

                var marketData = GetMarketData(signal.Currency.ToString());
                var orders = _orderFactory.CreateGreenOrders(signal, marketData, _tradeSettings, Account.Balance, ProviderGreen);
                var validOrders = GetValidOrders(orders, marketData);
                ExecuteOrders(validOrders);
            });

            _server.Consume<GreenInstruction>(signal =>
            {
                DebugLog(tgClient, signal);

                switch (signal.InstructionType)
                {
                    case InstructionType.Hold:
                        // keep going
                        break;
                    case InstructionType.Close:
                        ClosePosition(signal.SubjectMessageId);
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            });

            // Subscribe to events
            Positions.Closed += PositionsOnClosed;

        }

        private void DebugLog(Guid tgClient, object signal)
        {
            _server.Send(tgClient, new Message
            {
                Headers = new MessageHeaders
                {
                    MessageId = Guid.NewGuid(),
                    CorrelationId = Guid.NewGuid()
                },
                Payload = new LogMessage
                {
                    Text = "Received: " + JsonConvert.SerializeObject(signal)
                }
            });
        }

        protected override void OnTick()
        {
        }

        protected override void OnStop()
        {
            _server.Dispose();
            _cancellationTokenSource.Cancel();
        }

        private MarketData GetMarketData(string symbol)
        {
            var symbolData = MarketData.GetSymbol(symbol);
            return new MarketData
            {
                AskPrice = symbolData.Ask,
                BidPrice = symbolData.Bid,
                PipSize = symbolData.PipSize,
                PipValue = symbolData.PipValue
            };
        }

        private List<Order> GetValidOrders(List<Order> orders, MarketData marketData)
        {
            var validOrders = new List<Order>();
            foreach (var order in orders)
            {
                var validationResult = _orderValidator.ValidateOrder(order, marketData, _tradeSettings);
                if (validationResult.IsValid)
                    validOrders.Add(order);
                else
                    Print(validationResult.ErrorMessage);
            }
            return validOrders;
        }

        private void ExecuteOrders(List<Order> orders)
        {
            foreach (var order in orders)
            {
                var symbol = MarketData.GetSymbol(order.Symbol.ToString());
                var takeProfitPips = Math.Abs(Math.Round((order.EntryPrice.Value - order.TakeProfitPrice) / symbol.PipSize));
                var stopLossPips = Math.Abs(Math.Round((order.EntryPrice.Value - order.StopLossPrice) / symbol.PipSize));

                order.Volume = symbol.NormalizeVolumeInUnits(order.Volume);

                TradeResult result = null;

                switch (order.Type)
                {
                    case OrderType.BUYLIMIT:
                        result = PlaceLimitOrder(cAlgo.API.TradeType.Buy, symbol, order.Volume, order.EntryPrice.Value, order.Label, stopLossPips, takeProfitPips, null, order.MetaData);
                        break;
                    case OrderType.SELLLIMIT:
                        result = PlaceLimitOrder(cAlgo.API.TradeType.Sell, symbol, order.Volume, order.EntryPrice.Value, order.Label, stopLossPips, takeProfitPips, null, order.MetaData);
                        break;
                    case OrderType.BUYSTOP:
                        result = PlaceStopOrder(cAlgo.API.TradeType.Buy, symbol, order.Volume, order.EntryPrice.Value, order.Label, stopLossPips, takeProfitPips, null, order.MetaData);
                        break;
                    case OrderType.SELLSTOP:
                        result = PlaceStopOrder(cAlgo.API.TradeType.Sell, symbol, order.Volume, order.EntryPrice.Value, order.Label, stopLossPips, takeProfitPips, null, order.MetaData);
                        break;
                    case OrderType.BUYMARKET:
                        result = ExecuteMarketOrder(cAlgo.API.TradeType.Buy, symbol, order.Volume, order.Label, stopLossPips, takeProfitPips, null, order.MetaData);
                        break;
                    case OrderType.SELLMARKET:
                        result = ExecuteMarketOrder(cAlgo.API.TradeType.Sell, symbol, order.Volume, order.Label, stopLossPips, takeProfitPips, null, order.MetaData);
                        break;
                    default:
                        LogOrder(order, "Order type not recognized.", LogMessageType.Warning);
                        break;
                }
            }
        }

        private void PositionsOnClosed(PositionClosedEventArgs args)
        {
            if (args.Reason == PositionCloseReason.TakeProfit)
            {
                var metaData = JsonConvert.DeserializeObject<OrderMetaData>(args.Position.Comment);
                switch (metaData.Provider)
                {
                    case ProviderRonin:
                        MoveConnectedStopLossesToBreakEven(metaData.ProviderMessageId);
                        break;
                    case ProviderGreen:
                        return;
                }
            }
        }

        private void MoveConnectedStopLossesToBreakEven(int providerMessageId)
        {
            var connectedPositions = Positions.Where(p => JsonConvert.DeserializeObject<OrderMetaData>(p.Comment).ProviderMessageId == providerMessageId);
            if (connectedPositions != null && connectedPositions.Any())
            {
                foreach (var connectedPosition in connectedPositions)
                {
                    connectedPosition.ModifyStopLossPrice(connectedPosition.EntryPrice);
                    Print("Stop loss moved to break even. Label:" + connectedPosition.Label);
                }
            }
        }

        private void ClosePosition(int providerMessageId)
        {
            var position = Positions.FirstOrDefault(p => JsonConvert.DeserializeObject<OrderMetaData>(p.Comment).ProviderMessageId == providerMessageId);
            if (position != null)
            {
                position.Close();
                Print("Position closed: " + position.Label);
            }
            else
            {
                Print(string.Format("Failed to close position. Could not find position with ProviderMessageId: {0}", providerMessageId));
            }
        }

        private void LogOrder(Order order, string message, LogMessageType messageType)
        {
            Print(string.Format("{0}: {1} Label: {2} SL: {3} TP: {4} metadata: {5}", messageType.ToString(), message, order.Label, order.StopLossPrice, order.TakeProfitPrice, order.MetaData));
        }

        private void LogExecutionResult(TradeResult result, string message, LogMessageType messageType)
        {
            if (!result.IsSuccessful)
            {
                Print(string.Format("{0}: {1} {2}", messageType.ToString(), message, result.Error));
            }

            if (result.Position != null)
            {
                Print(string.Format("{0}: {1} {2} {3} {4} Entry price: {5} SL: {6} TP: {7}", messageType.ToString(), message, result.Position.EntryTime.ToString("yyyy-MM-dd HH:mm:ss"), result.Position.TradeType, result.Position.SymbolCode, result.Position.EntryPrice, result.Position.StopLoss, result.Position.TakeProfit));
            }
            else if (result.PendingOrder != null)
            {
                Print(string.Format("{0}: {1} {2} {3} {4} Entry price: {5} SL: {6} TP: {7}", messageType.ToString(), message, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), result.PendingOrder.TradeType, result.PendingOrder.SymbolCode, result.PendingOrder.TargetPrice, result.PendingOrder.StopLoss, result.PendingOrder.TakeProfit));
            }
        }
    }
}

 


@erik.lindblad

erisoftdevelop
05 Mar 2019, 16:02

The problem is in the Position.Opened event so you have some code affecting inside the event and take a look there.


@erisoftdevelop

erik.lindblad
05 Mar 2019, 16:05

RE:

erisoftdevelop said:

The problem is in the Position.Opened event so you have some code affecting inside the event and take a look there.

Hi and thanks for your reply! What you are saying makes sense but I do not use that event, I have not subscribed to it at all. That's what's confusing me.

Or am I missing something?

/Erik


@erik.lindblad

PanagiotisCharalampous
05 Mar 2019, 16:11

Hi Erik,

Thanks. Is there a chance we can get access to the referenced libraries as well? We need to reproduce this behavior to investigate further.

Best Regards,

Panagiotis


@PanagiotisCharalampous

erik.lindblad
05 Mar 2019, 16:27

Sure, this is all code used by the bot excluding the code communicating with the signal provider application, which should not affect the order execution I guess. The bot recevies signals from different signal providers, validates the signals, makes a sanity check on the provided take profits and stop losses, and then places the order. I guess the order execution part is the key part here. After the trade is placed I do not act on any specific events except Positions.Closed, so I cannot really see what is happening inside Postions.Opened which I do not use.
 

namespace MasterlessTrading
{
    using System;

    public class Order
    {
        public string Label { get; set; }
        public string Symbol { get; set; }
        public double Volume { get; set; }
        public double StopLossPrice { get; set; }
        public double TakeProfitPrice { get; set; }
        public OrderType Type { get; set; }
        public double? EntryPrice { get; set; }
        public DateTime TimeStamp { get; set; }
        public string MetaData { get; set; }
    }
}
using MasterlessTrading.Telegram;
using MasterlessTrading.Telegram.Core;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MasterlessTrading
{
    using MasterlessTrading.Ipc.Contracts;

    public class OrderFactory
    {
        private const string ProviderGreen = "Green";
        private const string ProviderRonin = "Ronin";

        public List<Order> CreateRoninOrders(RoninSignal signal, MarketData marketData, TradeSettings tradeSettings, double accountBalance, string provider)
        {
            var orders = signal
                .TakeProfits
                .Select(tp => new Order
                {
                    TimeStamp = signal.Date,
                    Symbol = signal.Currency.ToString(),
                    Type = GetOrderType(signal.TradeType),
                    EntryPrice = signal.EntryPrice,
                    TakeProfitPrice = tp.Price,
                    StopLossPrice = signal.StopLossPrice,
                    Label = string.Format("{0:yyyy-MM-dd HH:mm:ss} {1} {2} {3} Provider: {4} MessageId: {5}", signal.Date, signal.Currency.ToString(), signal.TradeType, signal.EntryPrice, ProviderRonin, signal.ProviderMessageId),
                    MetaData = JsonConvert.SerializeObject(new OrderMetaData()
                    {
                        Provider = ProviderRonin,
                        ProviderMessageId = signal.ProviderMessageId
                    })
                }).ToList();

            SetPositionSizes(orders, marketData, tradeSettings, accountBalance, provider);

            return orders;
        }
        public List<Order> CreateGreenOrders(GreenSignal signal, MarketData marketData, TradeSettings tradeSettings, double accountBalance, string provider)
        {
            var orders = new List<Order>();
            var order = new Order
            {
                TimeStamp = signal.Date,
                Symbol = signal.Currency.ToString(),
                Type = GetOrderType(signal.TradeType),
                EntryPrice = signal.EntryPrice,
                TakeProfitPrice = signal.TakeProfit,
                StopLossPrice = signal.StopLoss,
                Label = string.Format("{0:yyyy-MM-dd HH:mm:ss} {1} {2} {3} Provider: {4} MessageId: {5}", signal.Date, signal.Currency, signal.TradeType, signal.EntryPrice, ProviderGreen, signal.ProviderMessageId),
                MetaData = JsonConvert.SerializeObject(new OrderMetaData()
                {
                    Provider = ProviderGreen,
                    ProviderMessageId = signal.ProviderMessageId
                })
            };
            orders.Add(order);
            SetPositionSizes(orders, marketData, tradeSettings, accountBalance, provider);
            return orders;
        }

        private void SetPositionSizes(List<Order> orders, MarketData marketData, TradeSettings tradeSettings, double accountBalance, string provider)
        {
            var dollarRiskPerTrade = accountBalance * tradeSettings.GetPercentageRiskPerTrade(provider) / 100;

            foreach (var order in orders)
            {
                var stopLossPips = Math.Abs(Math.Round((order.EntryPrice.Value - order.StopLossPrice) / marketData.PipSize));
                var volume = dollarRiskPerTrade / (stopLossPips * marketData.PipValue);
                order.Volume = volume;
            }
        }

        private OrderType GetOrderType(TradeType type)
        {
            switch (type)
            {
                case TradeType.Buy:
                    return OrderType.BUY;
                case TradeType.BuyStop:
                    return OrderType.BUYSTOP;
                case TradeType.Sell:
                    return OrderType.SELL;
                case TradeType.SellStop:
                    return OrderType.SELLSTOP;
                case TradeType.BuyLimit:
                    return OrderType.BUYLIMIT;
                case TradeType.SellLimit:
                    return OrderType.SELLLIMIT;
                default:
                    throw new ArgumentOutOfRangeException("type", type, null);
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MasterlessTrading
{
    public class OrderMetaData
    {
        public string Provider { get; set; }
        public int ProviderMessageId { get; set; }
    }
}
namespace MasterlessTrading
{
    public enum OrderType
    {
        BUY,
        SELL,
        BUYLIMIT,
        SELLLIMIT,
        BUYSTOP,
        SELLSTOP,
        BUYMARKET,
        SELLMARKET,
        UNKNOWN
    }
}

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MasterlessTrading
{
    public class OrderValidator
    {
        public ValidationResult ValidateOrder(Order order, MarketData marketData, TradeSettings tradeSettings)
        {
            if (order.EntryPrice == 0 || order.StopLossPrice == 0 || order.TakeProfitPrice == 0)
            {
                return new ValidationResult()
                {
                    IsValid = false,
                    ErrorMessage = "Invalid order. Entry, SL and TP must be provided"
                };
            }

            AdjustOrderType(order, marketData, tradeSettings);


            switch (order.Type)
            {
                case OrderType.BUYLIMIT:
                    return ValidateBuyLimitOrder(order, marketData.AskPrice);
                case OrderType.BUYSTOP:
                    return ValidateBuyStopOrder(order, marketData.AskPrice);
                case OrderType.BUYMARKET:
                    return ValidateBuyMarketOrder(order, marketData.AskPrice);
                case OrderType.SELLLIMIT:
                    return ValidateSellLimitOrder(order, marketData.BidPrice);
                case OrderType.SELLSTOP:
                    return ValidateSellStopOrder(order, marketData.BidPrice);
                case OrderType.SELLMARKET:
                    return ValidateSellMarketOrder(order, marketData.BidPrice);
                default:
                    return new ValidationResult()
                    {
                        IsValid = false,
                        ErrorMessage = "Invalid order. Unknown order type."
                    };
            }
        }

        private void AdjustOrderType(Order order, MarketData marketData, TradeSettings tradeSettings)
        {
            if (order.Type == OrderType.SELL)
            {
                // Change to stop or limit order depedning on market price
                if (order.EntryPrice.Value > marketData.BidPrice)
                    order.Type = OrderType.SELLLIMIT;
                else
                    order.Type = OrderType.SELLSTOP;
            }
            if (order.Type == OrderType.BUY)
            {
                // Change to stop or limit order depedning on market price
                if (order.EntryPrice.Value < marketData.AskPrice)
                    order.Type = OrderType.BUYLIMIT;
                else
                    order.Type = OrderType.BUYSTOP;
            }

            if (order.Type == OrderType.SELLSTOP || order.Type == OrderType.SELLLIMIT)
            {
                // Change to market order if entry price is not below market price
                var priceOffset = Math.Abs((marketData.BidPrice - order.EntryPrice.Value) / marketData.PipSize);
                if (priceOffset <= tradeSettings.MaximumPipOffset)
                    order.Type = OrderType.SELLMARKET;
            }
            if (order.Type == OrderType.BUYSTOP || order.Type == OrderType.BUYLIMIT)
            {
                // Change to market order if entry price is not below market price
                var priceOffset = Math.Abs((marketData.AskPrice - order.EntryPrice.Value) / marketData.PipSize);
                if (priceOffset <= tradeSettings.MaximumPipOffset)
                    order.Type = OrderType.BUYMARKET;
            }
        }

        private ValidationResult ValidateSellMarketOrder(Order order, double currentBidPrice)
        {
            if (order.StopLossPrice <= currentBidPrice)
            {
                return new ValidationResult()
                {
                    IsValid = false,
                    ErrorMessage = "Invalid sell market order. SL must be above market price."
                };
            }

            if (order.TakeProfitPrice >= currentBidPrice)
            {
                return new ValidationResult()
                {
                    IsValid = false,
                    ErrorMessage = "Invalid sell market order. TP must be below market price."
                };
            }

            return new ValidationResult() { IsValid = true };
        }
        private ValidationResult ValidateBuyMarketOrder(Order order, double currentAskPrice)
        {
            if (order.StopLossPrice >= currentAskPrice)
            {
                return new ValidationResult()
                {
                    IsValid = false,
                    ErrorMessage = "Invalid buy market order. SL must be below entry."
                };
            }

            if (order.TakeProfitPrice <= currentAskPrice)
            {
                return new ValidationResult()
                {
                    IsValid = false,
                    ErrorMessage = "Invalid buy market order. TP must be above entry."
                };
            }

            return new ValidationResult() { IsValid = true };
        }

        private ValidationResult ValidateSellStopOrder(Order order, double currentBidPrice)
        {
            if (order.StopLossPrice <= order.EntryPrice)
            {
                return new ValidationResult()
                {
                    IsValid = false,
                    ErrorMessage = "Invalid sell stop order. SL must be above entry."
                };
            }

            if (order.TakeProfitPrice >= order.EntryPrice)
            {
                return new ValidationResult()
                {
                    IsValid = false,
                    ErrorMessage = "Invalid sell stop order. TP must be below entry."
                };
            }

            if (order.EntryPrice >= currentBidPrice)
                return new ValidationResult()
                {
                    IsValid = false,
                    ErrorMessage = "Invalid sell stop order. Entry must be below market price."
                };

            return new ValidationResult() { IsValid = true };
        }

        private ValidationResult ValidateBuyStopOrder(Order order, double currentAskPrice)
        {
            if (order.StopLossPrice >= order.EntryPrice)
            {
                return new ValidationResult()
                {
                    IsValid = false,
                    ErrorMessage = "Invalid buy stop order. SL must be below entry."
                };
            }

            if (order.TakeProfitPrice <= order.EntryPrice)
            {
                return new ValidationResult()
                {
                    IsValid = false,
                    ErrorMessage = "Invalid buy stop order. TP must be above entry."
                };
            }

            if (order.EntryPrice <= currentAskPrice)
                return new ValidationResult()
                {
                    IsValid = false,
                    ErrorMessage = "Invalid buy stop order. Entry must be above market price."
                };

            return new ValidationResult() { IsValid = true };
        }

        private ValidationResult ValidateSellLimitOrder(Order order, double currentBidPrice)
        {
            if (order.StopLossPrice <= order.EntryPrice)
            {
                return new ValidationResult()
                {
                    IsValid = false,
                    ErrorMessage = "Invalid sell limit order. SL must be above entry."
                };
            }

            if (order.TakeProfitPrice >= order.EntryPrice)
            {
                return new ValidationResult()
                {
                    IsValid = false,
                    ErrorMessage = "Invalid sell limit order. TP must be below entry."
                };
            }

            if (order.EntryPrice <= currentBidPrice)
                return new ValidationResult()
                {
                    IsValid = false,
                    ErrorMessage = "Invalid sell limit order. Entry must be above market price."
                };

            return new ValidationResult() { IsValid = true };
        }

        private ValidationResult ValidateBuyLimitOrder(Order order, double currentAskPrice)
        {
            if (order.StopLossPrice >= order.EntryPrice)
            {
                return new ValidationResult()
                {
                    IsValid = false,
                    ErrorMessage = "Invalid buy limit order. SL must be below entry."
                };
            }

            if (order.TakeProfitPrice <= order.EntryPrice)
            {
                return new ValidationResult()
                {
                    IsValid = false,
                    ErrorMessage = "Invalid buy limit order. TP must be above entry."
                };
            }

            if (order.EntryPrice >= currentAskPrice)
                return new ValidationResult()
                {
                    IsValid = false,
                    ErrorMessage = "Invalid buy limit order. Entry must be below market price."
                };

            return new ValidationResult() { IsValid = true };
        }

        public class ValidationResult
        {
            public bool IsValid { get; set; }
            public string ErrorMessage { get; set; }
        }
    }
}

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MasterlessTrading.Telegram;
using MasterlessTrading.Telegram.Core;

namespace MasterlessTrading
{
    using cAlgo.API;
    using MasterlessTrading.Ipc;
    using MasterlessTrading.Ipc.Contracts;
    using TradeType = MasterlessTrading.Ipc.Contracts.TradeType;

    public class RoninSignalTransformer
    {
        public void TransformSignal(RoninSignal signal, MarketData marketData)
        {
            AdjustStopLoss(signal, marketData);
            AdjustTakeProfits(signal, marketData);
            RoundPrices(signal);
        }

        private void AdjustStopLoss(RoninSignal signal, MarketData marketData, double minimumStopLoss = 30, double maximumStopLoss = 50, double defaultStopLoss = 40)
        {
            var stopLossPips = Math.Round((signal.EntryPrice - signal.StopLossPrice) / marketData.PipSize);
            if (Math.Abs(stopLossPips) < minimumStopLoss || Math.Abs(stopLossPips) > maximumStopLoss)
            {
                if (signal.TradeType == TradeType.Buy || signal.TradeType == TradeType.BuyStop || signal.TradeType == TradeType.BuyLimit)
                    signal.StopLossPrice = signal.EntryPrice - (defaultStopLoss * marketData.PipSize);
                else if (signal.TradeType == TradeType.Sell || signal.TradeType == TradeType.SellStop || signal.TradeType == TradeType.SellLimit)
                    signal.StopLossPrice = signal.EntryPrice + (defaultStopLoss * marketData.PipSize);
            }
        }

        private void AdjustTakeProfits(RoninSignal signal, MarketData marketData)
        {
            var takeProfitPips = new int[3] { 20, 40, 100 };
            var index = 0;
            foreach (var tp in signal.TakeProfits)
            {
                var tpPips = takeProfitPips[index++];
                tp.Pips = tpPips;
                if (signal.TradeType == TradeType.Buy || signal.TradeType == TradeType.BuyStop || signal.TradeType == TradeType.BuyLimit)
                    tp.Price = signal.EntryPrice + (tpPips * marketData.PipSize);
                else if (signal.TradeType == TradeType.Sell || signal.TradeType == TradeType.SellStop || signal.TradeType == TradeType.SellLimit)
                    tp.Price = signal.EntryPrice - (tpPips * marketData.PipSize);
            }
        }

        private void RoundPrices(RoninSignal signal)
        {
            var noOfdecimals = signal.Currency.ToString().Contains("JPY") ? 3 : 4;
            signal.EntryPrice = Math.Round(signal.EntryPrice, noOfdecimals);
            signal.StopLossPrice = Math.Round(signal.StopLossPrice, noOfdecimals);
            foreach (var tp in signal.TakeProfits)
            {
                tp.Price = Math.Round(tp.Price, noOfdecimals);
            }
        }
    }
}
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MasterlessTrading
{
    public class TradeSettings
    {
        private double _percentageRiskPerTrade;
        public const string ProviderGreen = "Green";
        public const string ProviderRonin = "Ronin";

        public TradeSettings(double percentageRiskPerTrade, double maximumPipOffset)
        {
            MaximumPipOffset = maximumPipOffset;
            _percentageRiskPerTrade = percentageRiskPerTrade;
        }

        public double GetPercentageRiskPerTrade(string provider)
        {
            return provider == ProviderRonin ? _percentageRiskPerTrade / 3 : _percentageRiskPerTrade;
        }

        public double MaximumPipOffset { get; private set; }
    }
}

 


@erik.lindblad

erik.lindblad
05 Mar 2019, 18:42

@Panagiotis Guess it could be a bit ovewhelming to look at all that code. But if we focus on the execution part. The market order is successfully placed on the market so what can be wrong with the arguments I pass to the ExecuteMarketOrder method? The error occurrs after the order is placed, leading me to my next question - what could possibly cause a NullReferenceException in Postions.Opened, when I am not even using that event? 


@erik.lindblad

PanagiotisCharalampous
06 Mar 2019, 09:53

Hi Erik,

Indeed it is a lot of code. Did you check yourself if all the parameters passed to the ExecuteMarketOrder are not null?

Best Regards,

Panagiotis


@PanagiotisCharalampous

erik.lindblad
06 Mar 2019, 14:27

No unfortunately not. Since I have had a hard time debugging the bot I have so far relied on logging, both the Print method and the built-in logging. If you look at the log lines in my first post it looks like everything I have passed as an argument had a value. But I am doing some refactoring now and trying to do the bot more light weight by moving business logic outside the bot. Then troubleshooting shuld be easier. I will get back here and report when I am done.

/Erik


@erik.lindblad

erik.lindblad
18 Mar 2019, 01:42 ( Updated at: 21 Dec 2023, 09:21 )

RE:

Panagiotis Charalampous said:

Hi Erik,

Indeed it is a lot of code. Did you check yourself if all the parameters passed to the ExecuteMarketOrder are not null?

Best Regards,

Panagiotis

Hello again Panagiotis! I have now spent a week on refactoring my code making my bot class minimal. So forget about all previous code posted. But the problem remains. Around one in three market orders still crashes in OnPositionOpened. I do not pass anything that is null. I'm now even logging my paramters just before calling ExecuteMarketOrder. Could you please have a look at my bot class.

It's just trade type, symbol, volume, stop loss pips, takeprofit pips and comment which is just a small json string. How could this cause a nullreference? Please help me, it is absolutely key for the bot to place a market order and I have put so much time and effort into this. 

Really appreciate your help.

Best regards

Erik

 


namespace MasterlessTrading
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;
    using cAlgo.API;
    using MasterlessTrading.Core;
    using MasterlessTrading.Ipc;
    using MasterlessTrading.Ipc.Contracts;
    using Newtonsoft.Json;

    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)]
    public class RoninBot : Robot
    {
        [Parameter(DefaultValue = 9712)]
        public int ServerPort { get; set; }

        private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();

        private IpcServer _server;
        public const string ProviderGreen = "Green";
        public const string ProviderRonin = "Ronin";

        protected override void OnStart()
        {
            Loggers.Register(Print, new[] 
            {
                LoggingLevel.Debug,
                LoggingLevel.Info,
                LoggingLevel.Warn,
                LoggingLevel.Error
            });

            _server = IpcServer.Start(Guid.Empty, this.ServerPort, new[] 
            {
                typeof(RoninSignal).Assembly
            });

            _server.Consume<AccountDataRequest, AccountDataResponse>((request, context) => { return new AccountDataResponse {Balance = Account.Balance}; });

            _server.Consume<MarketDataRequest, MarketDataResponse>((request, context) =>
            {
                var symbol = MarketData.GetSymbol(request.Symbol);

                return new MarketDataResponse 
                {
                    AskPrice = symbol.Ask,
                    BidPrice = symbol.Bid,
                    PipSize = symbol.PipSize,
                    PipValue = symbol.PipValue
                };
            });


            _server.Consume<OrderExecutionRequest, OrderExecutionReponse>((request, context) =>
            {
                ExecuteOrders(request.Orders);
                return new OrderExecutionReponse();
            });

            _server.Consume<PositionCloseRequest, PositionCloseResponse>((request, context) =>
            {
                ClosePosition(request.ProviderMessageId);
                return new PositionCloseResponse();
            });

            _server.Consume<TestRequest, TestResponse>((request, context) => { return new TestResponse {Count = DateTime.Now.Millisecond}; });

            // Subscribe to events
            Positions.Closed += PositionsOnClosed;
        }
        protected override void OnStop()
        {
            _server.Dispose();
            _cancellationTokenSource.Cancel();
        }

        private void ExecuteOrders(List<Order> orders)
        {
            if (orders == null)
            {
                return;
            }

            foreach (var order in orders)
            {
                var symbol = MarketData.GetSymbol(order.Symbol);
                var volume = symbol.NormalizeVolumeInUnits(order.Volume);
                var takeProfitPips = Math.Abs(Math.Round((order.EntryPrice.Value - order.TakeProfitPrice) / symbol.PipSize));
                var stopLossPips = Math.Abs(Math.Round((order.EntryPrice.Value - order.StopLossPrice) / symbol.PipSize));

                TradeResult result = null;

                var metadata = JsonConvert.SerializeObject(new OrderMetaData 
                {
                    Provider = order.Provider,
                    ProviderMessageId = order.ProviderMessageId
                });

                switch (order.Type)
                {
                    case OrderType.BuyLimit:
                        result = PlaceLimitOrder(TradeType.Buy, symbol, volume, order.EntryPrice.Value, "", stopLossPips, takeProfitPips, null, metadata);
                        break;
                    case OrderType.SellLimit:
                        result = PlaceLimitOrder(TradeType.Sell, symbol, volume, order.EntryPrice.Value, "", stopLossPips, takeProfitPips, null, metadata);
                        break;
                    case OrderType.BuyStop:
                        result = PlaceStopOrder(TradeType.Buy, symbol, volume, order.EntryPrice.Value, "", stopLossPips, takeProfitPips, null, metadata);
                        break;
                    case OrderType.SellStop:
                        result = PlaceStopOrder(TradeType.Sell, symbol, volume, order.EntryPrice.Value, "", stopLossPips, takeProfitPips, null, metadata);
                        break;
                    case OrderType.BuyMarket:
                        Print("Printing order parameters: Type: " + order.Type + " Symbol: " + symbol + " Volume: " + volume + " StopLossPips: " + stopLossPips + " TakeProfitPips: " + takeProfitPips);
                        result = ExecuteMarketOrder(TradeType.Buy, symbol, volume, "", stopLossPips, takeProfitPips);
                        break;
                    case OrderType.SellMarket:
                        Print("Printing order parameters: Type: " + order.Type + " Symbol: " + symbol + " Volume: " + volume + " StopLossPips: " + stopLossPips + " TakeProfitPips: " + takeProfitPips);
                        result = ExecuteMarketOrder(TradeType.Sell, symbol, volume, "", stopLossPips, takeProfitPips);
                        break;
                    default:
                        break;
                }

                if (result.IsSuccessful)
                {
                    if (result.Position != null)
                    {
                        try
                        {
                            result.Position.ModifyStopLossPrice(order.StopLossPrice);
                            result.Position.ModifyTakeProfitPrice(order.TakeProfitPrice);
                            Print("Stop loss and take profit modified on market order.");
                        }
                        catch (Exception e)
                        {
                            Print("Failed to modify SL or TP: " + e.Message + " " + e.StackTrace);
                        }
                    }
                }
            }
        }

        private void PositionsOnClosed(PositionClosedEventArgs args)
        {
            if (args.Reason == PositionCloseReason.TakeProfit)
            {
                var metaData = JsonConvert.DeserializeObject<OrderMetaData>(args.Position.Comment);
                switch (metaData.Provider)
                {
                    case ProviderRonin:
                        MoveConnectedStopLossesToBreakEven(metaData.ProviderMessageId);
                        break;
                    case ProviderGreen:
                        return;
                }
            }
        }

        private void MoveConnectedStopLossesToBreakEven(int providerMessageId)
        {
            var connectedPositions = Positions.Where(p => JsonConvert.DeserializeObject<OrderMetaData>(p.Comment).ProviderMessageId == providerMessageId);
            if (connectedPositions != null && connectedPositions.Any())
            {
                foreach (var connectedPosition in connectedPositions)
                {
                    var result = connectedPosition.ModifyStopLossPrice(connectedPosition.EntryPrice);
                    if (result.IsSuccessful)
                        Print("Stop loss moved to break even. Label:" + connectedPosition.Label);
                    else
                        Print("Failed to move stop loss moved to break even. Label:" + connectedPosition.Label);
                }
            }
        }

        private void ClosePosition(int providerMessageId)
        {
            var position = Positions.FirstOrDefault(p => JsonConvert.DeserializeObject<OrderMetaData>(p.Comment).ProviderMessageId == providerMessageId);
            if (position != null)
            {
                var result = position.Close();
                if (result.IsSuccessful)
                    Print("Position closed: " + position.Label);
                else
                    Print("Failed to close position: " + position.Label);
            }
            else
            {
                Print(string.Format("Failed to close position. Could not find position with ProviderMessageId: {0}", providerMessageId));
            }
        }
    }
}

 


@erik.lindblad

... Deleted by UFO ...

PanagiotisCharalampous
18 Mar 2019, 11:20

Hi Erik,

Since there are may components and references in this cBot, please send me the compressed solution folder at community@spotware.com.

Best Regards,

Panagiotis 


@PanagiotisCharalampous

erik.lindblad
18 Mar 2019, 16:09

RE:

Panagiotis Charalampous said:

Hi Erik,

Since there are may components and references in this cBot, please send me the compressed solution folder at community@spotware.com.

Best Regards,

Panagiotis 

Thanks for your reply. I don't have any problem with sending you the code or the assemblies. But the code is a seperate client application which is integrated with Telegram. The application listens to a Telegram channel and collects signals containing trade setups that are parsed and sent to the cBot for execution. You need a Telegram account and you will need to post trade signals on a specific format in a specific channel to create any interaction with the bot. You need credentials to connect to Telegram and they will be sent to my phone number when you start the application etc. I think you will get a hard time running it.

Correct me if I am wrong, but I can't see how the client calling the cBots methods are relevant here? The bot is very simple, it has only three methods. One to get market data for a symbol, one to give me the account balance and one to execute trades.

Still want me to send you code and/or assemblies? You do not have access to any error logs do you? Where you can track down the errors based on my account or the positionIds or something?

Cheers Erik


@erik.lindblad

PanagiotisCharalampous
18 Mar 2019, 16:21

Hi Erik,

If you can create a cBot that reproduces the exception without using all these components then it will be easier for us to assist you. In order for us to help you, we need to be able to reproduce the problem.

Best Regards,

Panagiotis


@PanagiotisCharalampous

erik.lindblad
18 Mar 2019, 17:09

RE:

Panagiotis Charalampous said:

Hi Erik,

If you can create a cBot that reproduces the exception without using all these components then it will be easier for us to assist you. In order for us to help you, we need to be able to reproduce the problem.

Best Regards,

Panagiotis

I understand, I will try to make a small stand alone bot and see if I can reproduce the error with that. I'll report back here in a few days.

Cheers
Erik


@erik.lindblad

Shares4UsDevelopment
19 Mar 2019, 12:40

try catch block in Onstart and print the stack-trace that will give a bit more info about where it happens


@Shares4UsDevelopment

erik.lindblad
08 Apr 2019, 19:00

This is issue is now resolved. It had to do with multiple threads interfering with each other and I have found a way to solve that.


@erik.lindblad