QuantConnect开源框架Lean后端与Panoptes前端交互的一个测试案例

目的:QuantConnect的Lean缺少GUI前端回测显示,可考虑使用第三方Panoptes.exe作为GUI前端,从而实现Lean后端和Panoptes前端交互,即策略跑起后Lean将通过TCP将结果发到Panoptes进行动态显示。

  • DebugStreamingMessageHandler.cs
using NetMQ.Sockets;
using NetMQ;
using Newtonsoft.Json;
using QuantConnect;
using QuantConnect.Notifications;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders;
using QuantConnect.Packets;
using QuantConnect.Securities;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using QuantConnect.Orders.Serialization;
using QuantConnect.Interfaces;

namespace QuantConnect.Messaging
{
    /// <summary>
    /// 提交订单请求的模拟类,用于测试或模拟场景。
    /// </summary>
    public class MockSubmitOrderRequest : SubmitOrderRequest
    {
        /// <summary>
        /// 构造函数,创建一个模拟提交订单请求。
        /// </summary>
        /// <param name="id">订单ID。</param>
        /// <param name="orderType">订单类型。</param>
        /// <param name="securityType">证券类型。</param>
        /// <param name="symbol">证券符号。</param>
        /// <param name="quantity">数量。</param>
        /// <param name="stopPrice">止损价格。</param>
        /// <param name="limitPrice">限价。</param>
        /// <param name="time">时间。</param>
        /// <param name="tag">标签。</param>
        /// <param name="properties">订单附加属性。</param>
        public MockSubmitOrderRequest(int id, OrderType orderType, SecurityType securityType, Symbol symbol, decimal quantity, decimal stopPrice, decimal limitPrice, DateTime time, string tag, IOrderProperties properties = null)
            : base(orderType, securityType, symbol, quantity, stopPrice, limitPrice, time, tag, properties)
        {
            this.OrderId = id;
        }

        /// <summary>
        /// 构造函数,创建一个带触发价格的模拟提交订单请求。
        /// </summary>
        /// <param name="id">订单ID。</param>
        /// <param name="orderType">订单类型。</param>
        /// <param name="securityType">证券类型。</param>
        /// <param name="symbol">证券符号。</param>
        /// <param name="quantity">数量。</param>
        /// <param name="stopPrice">止损价格。</param>
        /// <param name="limitPrice">限价。</param>
        /// <param name="triggerPrice">触发价格。</param>
        /// <param name="time">时间。</param>
        /// <param name="tag">标签。</param>
        /// <param name="properties">订单附加属性。</param>
        public MockSubmitOrderRequest(int id, OrderType orderType, SecurityType securityType, Symbol symbol, decimal quantity, decimal stopPrice, decimal limitPrice, decimal triggerPrice, DateTime time, string tag, IOrderProperties properties = null)
            : base(orderType, securityType, symbol, quantity, stopPrice, limitPrice, triggerPrice, time, tag, properties)
        {
            this.OrderId = id;
        }
    }

    /// <summary>
    /// 会话参数接口,定义了一个会话的基本参数集合。
    /// </summary>
    public interface ISessionParameters
    {
        /// <summary>
        /// 获取是否从命令行启动的标志。
        /// </summary>
        bool IsFromCmdLine { get; }
    }

    /// <summary>
    /// 流式会话参数类,实现了ISessionParameters接口,提供了会话的流式参数配置。
    /// </summary>
    public class StreamSessionParameters : ISessionParameters
    {
        /// <summary>
        /// 获取或设置主机地址。
        /// </summary>
        public string Host { get; set; }

        /// <summary>
        /// 获取或设置端口号。
        /// </summary>
        public string Port { get; set; }

        /// <summary>
        /// 获取或设置一个值,指示会话是否在接收到最后一个数据包后关闭。
        /// 当禁用时,新的数据包将重置会话状态。
        /// </summary>
        public bool CloseAfterCompleted { get; set; } = true;

        /// <summary>
        /// 获取是否从命令行启动的标志。
        /// </summary>
        public bool IsFromCmdLine { get; init; }
    }

    public class DebugStreamingMessageHandler : IMessagingHandler
    {
        // 私有成员变量定义部分
        private int _port; // 用于指定服务器端口的整型变量
        private PushSocket _server; // 代表服务器的PushSocket对象
        private AlgorithmNodePacket _job; // 代表算法节点任务的AlgorithmNodePacket对象
        private OrderEventJsonConverter _orderEventJsonConverter; // 用于订单事件JSON转换的OrderEventJsonConverter对象

        // 背景工作者对象,用于执行永恒队列监听任务,支持取消操作
        protected readonly BackgroundWorker _eternalQueueListener = new BackgroundWorker() { WorkerSupportsCancellation = true };
        // 背景工作者对象,用于执行队列读取任务,支持取消操作
        protected readonly BackgroundWorker _queueReader = new BackgroundWorker() { WorkerSupportsCancellation = true };
        // 取消令牌源,用于控制任务的取消
        protected CancellationTokenSource _cts;

        // 使用BlockingCollection创建的队列,用于线程安全地存储数据包,支持阻塞读写操作
        protected readonly BlockingCollection<Packet> _packetQueue = new BlockingCollection<Packet>();

        /// <summary>
        /// 用于指示当前对象是否具有订阅者的属性。
        /// </summary>
        /// <value>
        /// 如果当前对象有至少一个订阅者,则为 <see langword="true"/>;否则为 <see langword="false"/>.
        /// </value>
        public bool HasSubscribers { get; set; }
        
        public DebugStreamingMessageHandler()
        {
            _port = 33333;

            _startTime = DateTime.UtcNow;
            _currentTime = _startTime;
            _orderEventJsonConverter = new OrderEventJsonConverter(AlgorithmId);
        }

        /// <summary>
        /// Initialize the messaging system
        /// </summary>
        public void Initialize(MessagingHandlerInitializeParameters initializeParameters)
        {
            JsonConvert.DefaultSettings = () => new JsonSerializerSettings
            {
                Converters = { new OrderJsonConverter() }
            };

            CheckPort();
            _server = new PushSocket($"@tcp://*:{_port}");

            _cts = new CancellationTokenSource();

            // Configure the worker threads
            //_eternalQueueListener.WorkerSupportsCancellation = true;
            _eternalQueueListener.DoWork += EventsListener;
            _eternalQueueListener.RunWorkerAsync();

            //_queueReader.WorkerSupportsCancellation = true;
            _queueReader.DoWork += QueueReader;
            _queueReader.RunWorkerAsync();
        }

        /// <summary>
        /// Set the user communication channel
        /// </summary>
        /// <param name="job"></param>
        public void SetAuthentication(AlgorithmNodePacket job)
        {
            _job = job;
            _orderEventJsonConverter = new OrderEventJsonConverter(job.AlgorithmId);
            Transmit(_job);
        }

        /// <summary>
        /// Send any notification with a base type of Notification.
        /// </summary>
        /// <param name="notification">The notification to be sent.</param>
        public void SendNotification(Notification notification)
        {
            var type = notification.GetType();
            if (type == typeof(NotificationEmail) || type == typeof(NotificationWeb) || type == typeof(NotificationSms) || type == typeof(NotificationTelegram))
            {
                Trace.TraceError("Messaging.SendNotification(): Send not implemented for notification of type: " + type.Name);
                return;
            }
            notification.Send();
        }

        /// <summary>
        /// Send all types of packets
        /// </summary>
        public void Send(Packet packet)
        {
            Transmit(packet);
        }

        /// <summary>
        /// Send a message to the _server using ZeroMQ
        /// </summary>
        /// <param name="packet">Packet to transmit</param>
        public void Transmit(Packet packet)
        {
            var payload = JsonConvert.SerializeObject(packet, Formatting.None, _orderEventJsonConverter);

            var message = new NetMQMessage();

            message.Append(payload, Encoding.UTF8);

            _server.SendMultipartMessage(message);
        }

        /// <summary>
        /// Check if port to be used by the desktop application is available.
        /// </summary>
        private void CheckPort()
        {
            try
            {
                TcpListener tcpListener = new TcpListener(IPAddress.Any, _port);
                tcpListener.Start();
                tcpListener.Stop();
            }
            catch
            {
                throw new Exception("The port configured in config.json is either being used or blocked by a firewall." +
                    "Please choose a new port or open the port in the firewall.");
            }
        }

        protected virtual void QueueReader(object sender, DoWorkEventArgs e)
        {
            try
            {
                while (!_queueReader.CancellationPending && !_cts.Token.IsCancellationRequested)
                {
                    var p = _packetQueue.Take(_cts.Token);
                    Transmit(p);
                }
            }
            catch (OperationCanceledException)
            { }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                //_resetEvent.Set();
            }
        }

        protected void EventsListener(object sender, DoWorkEventArgs e)
        {
            int deltaMinutes = _random.Next(1, 60);
            _currentTime = _currentTime.AddMinutes(deltaMinutes);

            var values = Enum.GetValues(typeof(PacketType));

            _packetQueue.Add(GetAlgorithmStatusPacket(AlgorithmStatus.RuntimeError));
            Thread.Sleep(200);
            _packetQueue.Add(GetAlgorithmStatusPacket(AlgorithmStatus.InQueue));
            Thread.Sleep(200);

            _packetQueue.Add(GetAlgorithmStatusPacket(AlgorithmStatus.History));
            for (int s = 0; s < (deltaMinutes * 60 / _stepSecond); s++)
            {
                NextChartStep();
                NextPriceStep();
            }

            _packetQueue.Add(GetLiveNodePacket());
            Thread.Sleep(200);

            _packetQueue.Add(GetAlgorithmStatusPacket(AlgorithmStatus.LoggingIn));
            Thread.Sleep(200);
            _packetQueue.Add(GetAlgorithmStatusPacket(AlgorithmStatus.Initializing));
            Thread.Sleep(200);

            // Live results here
            _packetQueue.Add(GetLiveResultPacket());
            Thread.Sleep(200);

            _packetQueue.Add(GetSecurityTypesPacket());
            Thread.Sleep(200);

            _packetQueue.Add(GetAlgorithmStatusPacket(AlgorithmStatus.Running));
            Thread.Sleep(200);

            _packetQueue.Add(GetDebugPacket($"Launching analysis for {AlgorithmId} with LEAN Engine v2.5.0.0"));
            _packetQueue.Add(GetDebugPacket("Mock Brokerage account base currency: USD"));

            while (_cts?.IsCancellationRequested == false)
            {
                NextPriceStep();
                // Always send a LiveResult packet
                _packetQueue.Add(GetLiveResultPacket());

                switch ((PacketType)values.GetValue(_random.Next(values.Length)))
                {
                    case PacketType.AlgorithmStatus:
                        //_packetQueue.Add(GetAlgorithmStatusPacket());
                        break;

                    case PacketType.LiveNode:
                        //_packetQueue.Add(GetLiveNodePacket());
                        break;

                    //case PacketType.AlgorithmNode:
                    //    _packetQueue.Add(JsonConvert.DeserializeObject<AlgorithmNodePacket>(payload));
                    //    break;

                    case PacketType.LiveResult:
                        _packetQueue.Add(GetLiveResultPacketOrders());
                        break;

                    //case PacketType.BacktestResult:
                    //    _packetQueue.Add(JsonConvert.DeserializeObject<BacktestResultPacket>(payload, orderConverterId));
                    //    break;

                    case PacketType.OrderEvent:
                        _packetQueue.Add(GetOrderEventPacket());
                        _packetQueue.Add(GetOrderEventPacket());
                        _packetQueue.Add(GetOrderEventPacket());
                        break;

                    case PacketType.Log:
                        _packetQueue.Add(GetLogPacket());
                        break;

                    case PacketType.Debug:
                        _packetQueue.Add(GetDebugPacket());
                        break;

                    case PacketType.HandledError:
                        _packetQueue.Add(GetHandledErrorPacket());
                        break;
                }

                _currentTime = _currentTime.AddSeconds(_stepSecond + _random.Next(0, 3));
                Thread.Sleep(_sleep);
            }
        }

        #region Mock data
        private static readonly Random _random = new Random();
        private readonly DateTime _startTime;
        private DateTime _currentTime;
        private readonly int _sleep = 50; // ms
        private readonly int _stepSecond = 1;

        private readonly string[] _symbols = new string[] { "ALGOEUR XJ", "ALGOGBP XJ", "ALGOUSD XJ", "ATOMBTC XJ", "ATOMUSD XJ", "BALBTC XJ", "BALUSD XJ", "BANDBTC XJ", "BANDEUR XJ", "BANDGBP XJ", "BANDUSD XJ", "BATETH XJ", "BATUSDC XJ", "BCHBTC XJ", "BCHEUR XJ", "BCHGBP XJ", "BCHUSD XJ", "BTCEUR XJ", "BTCGBP XJ", "BTCUSD XJ", "BTCUSDC XJ", "CGLDBTC XJ", "CGLDEUR XJ", "CGLDGBP XJ", "CGLDUSD XJ", "COMPBTC XJ", "COMPUSD XJ", "CVCUSDC XJ", "DAIUSD XJ", "DAIUSDC XJ", "DASHBTC XJ", "DASHUSD XJ", "DNTUSDC XJ", "EOSBTC XJ", "EOSEUR XJ", "EOSUSD XJ", "ETCBTC XJ", "ETCEUR XJ", "ETCGBP XJ", "ETCUSD XJ", "ETHBTC XJ", "ETHDAI XJ", "ETHEUR XJ", "ETHGBP XJ", "ETHUSD XJ", "ETHUSDC XJ", "GNTUSDC XJ", "KNCBTC XJ", "KNCUSD XJ", "LINKETH XJ", "LINKEUR XJ", "LINKGBP XJ", "LINKUSD XJ", "LOOMUSDC XJ", "LRCUSD XJ", "LTCBTC XJ", "LTCEUR XJ", "LTCGBP XJ", "LTCUSD XJ", "MANAUSDC XJ", "MKRBTC XJ", "MKRUSD XJ", "NMRBTC XJ", "NMREUR XJ", "NMRGBP XJ", "NMRUSD XJ", "OMGBTC XJ", "OMGEUR XJ", "OMGGBP XJ", "OMGUSD XJ", "OXTUSD XJ", "RENBTC XJ", "RENUSD XJ", "REPBTC XJ", "REPUSD XJ", "UMABTC XJ", "UMAEUR XJ", "UMAGBP XJ", "UMAUSD XJ", "UNIUSD XJ", "USDCEUR XJ", "USDCGBP XJ", "WBTCBTC XJ", "WBTCUSD XJ", "XLMBTC XJ", "XLMEUR XJ", "XLMUSD XJ", "XRPBTC XJ", "XRPEUR XJ", "XRPGBP XJ", "XRPUSD XJ", "XTZBTC XJ", "XTZEUR XJ", "XTZGBP XJ", "XTZUSD XJ", "YFIUSD XJ", "ZECBTC XJ", "ZECUSDC XJ", "ZRXBTC XJ", "ZRXEUR XJ", "ZRXUSD XJ" };

        private int _orderId;

        private readonly ConcurrentDictionary<int, Order> _orders = new ConcurrentDictionary<int, Order>();

        private readonly Dictionary<string, decimal> _lastSeriesPoint = new Dictionary<string, decimal>();
        private readonly Dictionary<string, decimal> _lastPrice = new Dictionary<string, decimal>();
        private const decimal maximumPercentDeviation = 0.1m;

        public const string HostName = "MOCK-HOST-NAME";
        public const string AlgorithmId = "mock-algo-id";
        public const int ProjectId = 42;
        public const int UserId = 99;
        public const string Channel = "Channel-algo-status";
        public const string CompileId = "CompileId-algo-status";
        public const string SessionId = "SessionId-algo-status";
        public const string DeployId = "DeployId-algo-status";
        private readonly Dictionary<string, (string, SeriesType, ScatterMarkerSymbol?)[]> Charts = new Dictionary<string, (string, SeriesType, ScatterMarkerSymbol?)[]>()
        {
            { "Strategy Equity", new (string, SeriesType, ScatterMarkerSymbol?)[] { ("Equity", (SeriesType)2, null) } },
            { "MACD",            new (string, SeriesType, ScatterMarkerSymbol?)[] { ("Price", (SeriesType)2, null), ("MACD-10d", SeriesType.Line, null), ("MACD-100d", SeriesType.Line, null) } },
            { "Markers",         new (string, SeriesType, ScatterMarkerSymbol?)[] { ("Line-Diamond", (SeriesType)2, ScatterMarkerSymbol.Diamond), ("Scatter-Square", SeriesType.Scatter, ScatterMarkerSymbol.Square), ("Line-Null", SeriesType.Line, null) } }
        };

        private static readonly Array algoStatus = Enum.GetValues(typeof(AlgorithmStatus));
        private static readonly Array orderTypes = Enum.GetValues(typeof(OrderType));
        private static readonly Array orderStatus = Enum.GetValues(typeof(OrderStatus));

        private static SecurityTypesPacket GetSecurityTypesPacket()
        {
            return JsonConvert.DeserializeObject<SecurityTypesPacket>("{'aMarkets':[7],'TypesCSV':'crypto','eType':'SecurityTypes','sChannel':''}");
        }

        private static AlgorithmStatusPacket GetAlgorithmStatusPacket(AlgorithmStatus? status = null)
        {
            if (!status.HasValue)
            {
                status = (AlgorithmStatus)algoStatus.GetValue(_random.Next(algoStatus.Length));
            }

            return new AlgorithmStatusPacket(AlgorithmId, ProjectId, status.Value, $"Message for algo status {status}")
            {
                Channel = Channel
            };
        }

        private LiveResultPacket GetLiveResultPacket()
        {
            return new LiveResultPacket()
            {
                CompileId = CompileId,
                Channel = Channel,
                SessionId = SessionId,
                DeployId = DeployId,
                UserId = UserId,
                ProjectId = ProjectId,
                ProcessingTime = _random.NextDouble() * 100,
                Results = new LiveResult(GetLiveResultParameters()),
            };
        }

        private LiveResultParameters GetLiveResultParameters()
        {
            return new LiveResultParameters(
                Charts.Select(c => GetChart(c.Key, c.Value)).ToDictionary(k => k.Name, k => k),
                null,
                GetProfitLoss(),
                GetHoldings(),
                GetCashBook(),
                null,
                GetRuntimeStatistics(),
                null,
                GetServerStatistics(),
                null);

            // What about orderevent in here??
        }

        public static CashBook GetCashBook()
        {
            return new CashBook
            {
                ["EUR"] = new Cash("EUR", _random.Next(5_000_000, 10_000_000), (decimal)(_random.NextDouble() * 10)),
                ["USD"] = new Cash("USD", _random.Next(5_000, 1_000_000), (decimal)(_random.NextDouble() * 10)),
                ["BTC"] = new Cash("BTC", _random.Next(10, 10_000), (decimal)(_random.NextDouble() * 10)),
                ["GBP"] = new Cash("GBP", _random.Next(5_000_000, 10_000_000), (decimal)(_random.NextDouble() * 10)),
                ["ETH"] = new Cash("ETH", _random.Next(10, 10_000), (decimal)(_random.NextDouble() * 10)),
            };
        }

        public Dictionary<string, Holding> GetHoldings()
        {
            var hlds = new Dictionary<string, Holding>();
            foreach (var symbol in _symbols)
            {
                var sid = SecurityIdentifier.Parse(symbol);
                decimal qty = (decimal)_random.Next(0, 1_000);
                decimal price = _lastPrice[symbol];
                hlds[symbol] = new Holding()
                {
                    Symbol = new Symbol(sid, sid.Symbol),
                    AveragePrice = _lastPrice[symbol] * (decimal)(1 + _random.Next() - 0.5),
                    MarketPrice = price,
                    CurrencySymbol = symbol.Substring(0, 3),
                    ConversionRate = (decimal)(1 + _random.Next() - 0.5),
                    Quantity = qty,
                    MarketValue = qty * price,
                    UnrealizedPnL = _random.Next() * (decimal)_random.NextDouble()
                };
            }
            return hlds;
        }

        /// <summary>
        /// Gets the statistics of the machine, including CPU% and RAM
        /// </summary>
        public Dictionary<string, string> GetServerStatistics()
        {
            var upTime = _currentTime - _startTime;
            return new Dictionary<string, string>
            {
                { "CPU Usage", $"{_random.NextDouble() * 100:0.0}%" },
                { "Used RAM (MB)", $"{_random.Next(100, 2000)}" },
                { "Total RAM (MB)", "2000" },
                { "Hostname", HostName },
                { "LEAN Version", $"v{Globals.Version}"},
                { "Up Time", $"{upTime.Days}d {upTime:hh\\:mm\\:ss}" },
            };
        }

        private Dictionary<DateTime, decimal> GetProfitLoss()
        {
            if (_currentTime.Second > 9) return null;

            return new Dictionary<DateTime, decimal>()
            {
                { _currentTime.AddSeconds(-_stepSecond / 2.0), (decimal)(_random.NextDouble() - 0.5) * 2m },
                { _currentTime, (decimal)(_random.NextDouble() - 0.5) * 2m }
            };
        }

        private LiveResultPacket GetLiveResultPacketOrders()
        {
            return new LiveResultPacket()
            {
                CompileId = CompileId,
                Channel = Channel,
                SessionId = SessionId,
                DeployId = DeployId,
                UserId = UserId,
                ProjectId = ProjectId,
                ProcessingTime = _random.NextDouble() * 100.0,
                Results = new LiveResult(GetLiveResultParametersOrders())
            };
        }

        private LiveResultParameters GetLiveResultParametersOrders()
        {
            return new LiveResultParameters(
                null,
                GetOrders().ToDictionary(k => k.Id, k => k),
                null, null, null, null,
                null, null, null, null);

            // What about orderevent in here??
        }

        private Dictionary<string, string> GetRuntimeStatistics()
        {
            if (_currentTime.Second.ToString().StartsWith("1") &&
                _lastSeriesPoint.ContainsKey("Strategy Equity-Equity"))
            {
                decimal equity = _lastSeriesPoint["Strategy Equity-Equity"];
                decimal perf = (decimal)((_random.NextDouble() - 0.5) * 0.2);
                decimal netProfit = perf * equity;
                decimal volume = equity * (decimal)_random.NextDouble() * 0.1m;
                decimal fees = -volume * 0.01m;
                decimal unrealized = ((decimal)_random.NextDouble() - 0.5m) * netProfit;
                decimal sr = perf / 0.4m;
                decimal holdings = equity * (decimal)_random.NextDouble();
                return new Dictionary<string, string>
                {
                    { "Probabilistic Sharpe Ratio", sr.ToString("0.###%") },
                    { "Unrealized", unrealized.ToString("C2") },
                    { "Fees", fees.ToString("C2") },
                    { "Net Profit", netProfit.ToString("C2") },
                    { "Return", perf.ToString("0.##%") },
                    { "Equity", equity.ToString("C2") },
                    { "Holdings", holdings.ToString("C2") },
                    { "Volume", volume.ToString("C2") }
                };
            }

            return new Dictionary<string, string>();
        }

        private OrderEventPacket GetOrderEventPacket()
        {
            return new OrderEventPacket(AlgorithmId, GetOrderEvent());
        }

        private OrderEvent GetOrderEvent()
        {
            if (_orders.IsEmpty) return null;
            var order = _orders[_random.Next(1, _orderId)];

            decimal lastPrice = _lastPrice[order.Symbol.ToString()];
            decimal fillPrice = 0;
            decimal fillQty = 0;
            var fees = new OrderFee(new CashAmount());

            var status = (OrderStatus)orderStatus.GetValue(_random.Next(orderStatus.Length));
            string message = $"{status}-{new string(Enumerable.Repeat(chars, _random.Next(5, 15)).Select(s => s[_random.Next(s.Length)]).ToArray())}";

            switch (status)
            {
                case OrderStatus.Filled:
                    fillPrice = Math.Round(order.Direction == OrderDirection.Buy ? lastPrice * 1.02m : order.Direction == OrderDirection.Sell ? lastPrice * 0.98m : lastPrice, 4);
                    fillQty = order.Quantity;
                    fees = new OrderFee(new CashAmount(fillPrice * fillQty * 0.01m, order.PriceCurrency));
                    break;

                case OrderStatus.None:
                case OrderStatus.PartiallyFilled:
                    status = OrderStatus.PartiallyFilled;
                    fillPrice = Math.Round(order.Direction == OrderDirection.Buy ? lastPrice * 1.02m : order.Direction == OrderDirection.Sell ? lastPrice * 0.98m : lastPrice, 4);
                    fillQty = Math.Round(order.Quantity * (decimal)_random.NextDouble(), 4);
                    fees = new OrderFee(new CashAmount(fillPrice * fillQty * 0.01m, order.PriceCurrency));
                    break;

                case OrderStatus.Invalid:
                case OrderStatus.UpdateSubmitted:
                case OrderStatus.Submitted:
                case OrderStatus.Canceled:
                case OrderStatus.CancelPending:
                default:
                    break;

                case OrderStatus.New:
                    return null;
            }

            return new OrderEvent(order.Id, order.Symbol,
                                  _currentTime, status,
                                  order.Direction, fillPrice,
                                  fillQty, fees, message);
        }

        private IEnumerable<Order> GetOrders()
        {
            if (_currentTime.Second % 2 == 0)
            {
                int orderCount = _random.Next(1, 5);

                for (int i = 0; i < orderCount; i++)
                {
                    var order = GetOrder(++_orderId);
                    if (_orders.TryAdd(order.Id, order))
                    {
                        //if (order.Direction == OrderDirection.Hold) yield break;
                        yield return order;
                    }
                }
            }
            else
            {
                yield break;
                /*
                if (_orders.Count == 0) yield break;
                var order = _orders[_random.Next(1, _orderId)];
                order.ApplyUpdateOrderRequest(new UpdateOrderRequest(_currentTime, order.Id, new UpdateOrderFields()
                {
                    Tag = "Update tag, " + order.Tag
                }));

                yield return order;
                */
            }
        }

        private Order GetOrder(int id)
        {
            var symbolStr = _symbols[_random.Next(_symbols.Length)];
            var sid = SecurityIdentifier.Parse(symbolStr);
            var symbol = new Symbol(sid, sid.Symbol);
            var type = (OrderType)orderTypes.GetValue(_random.Next(orderTypes.Length));
            var tag = new string(Enumerable.Repeat(chars, _random.Next(15, 60)).Select(s => s[_random.Next(s.Length)]).ToArray());
            var qty = Math.Round((decimal)(_random.NextDouble() * 10) * _random.Next(-1, 2), 4);

            decimal lastPrice = _lastPrice[symbolStr];
            decimal limitPrice = lastPrice * 0.95m;
            decimal stopPrice = lastPrice * 1.05m;
            decimal triggerPrice = lastPrice * 1.02m;

            MockSubmitOrderRequest request;
            switch (type)
            {
                case OrderType.Limit:
                    request = new MockSubmitOrderRequest(id, type, SecurityType.Crypto, symbol, qty, 0, limitPrice, _currentTime, tag);
                    break;

                case OrderType.OptionExercise:
                    // TO DO
                    request = new MockSubmitOrderRequest(id, OrderType.Market, SecurityType.Crypto, symbol, qty, 0, 0, _currentTime, tag);
                    break;

                case OrderType.StopLimit:
                    request = new MockSubmitOrderRequest(id, type, SecurityType.Crypto, symbol, qty, stopPrice, limitPrice, _currentTime, tag);
                    break;

                case OrderType.StopMarket:
                    request = new MockSubmitOrderRequest(id, type, SecurityType.Crypto, symbol, qty, stopPrice, 0, _currentTime, tag);
                    break;

                case OrderType.LimitIfTouched:
                    request = new MockSubmitOrderRequest(id, type, SecurityType.Crypto, symbol, qty, 0, limitPrice, triggerPrice, _currentTime, tag);
                    break;

                case OrderType.Market:
                case OrderType.MarketOnClose:
                case OrderType.MarketOnOpen:
                default:
                    request = new MockSubmitOrderRequest(id, type, SecurityType.Crypto, symbol, qty, 0, 0, _currentTime, tag);
                    break;
            }

            var response = OrderResponse.Success(request);
            request.SetResponse(response, OrderRequestStatus.Processed);
            return Order.CreateOrder(request);
        }

        private Chart GetChart(string name, params (string, SeriesType, ScatterMarkerSymbol?)[] series)
        {
            var se = series.Select(s => GetSeries(name, s.Item1, s.Item2, s.Item3)).ToDictionary(k => k.Name, k => (BaseSeries)k);
            return new Chart(name)
            {
                Series = se,
            };
        }

        private Series GetSeries(string chartName, string seriesName, SeriesType seriesType, ScatterMarkerSymbol? scatterMarkerSymbol)
        {
            string key = $"{chartName}-{seriesName}";
            // from https://github.com/QuantConnect/Lean/blob/master/ToolBox/RandomDataGenerator/RandomValueGenerator.cs#L13
            decimal referencePrice;
            Color color = Color.Black;
            if (!_lastSeriesPoint.ContainsKey(key))
            {
                referencePrice = (decimal)(_random.NextDouble() * _random.Next(1, 5_000));
                _lastSeriesPoint.Add(key, referencePrice);
                color = Color.FromArgb(_random.Next(0, 256), _random.Next(0, 256), _random.Next(0, 256));
            }

            NextChartStep();

            var series = new Series(seriesName, seriesType)
            {
                Values = new List<ISeriesPoint>()
                {
                    // Just one point for the moment
                    new ChartPoint(_currentTime,  _lastSeriesPoint[key])
                },
                Color = color
            };

            if (scatterMarkerSymbol.HasValue)
            {
                series.ScatterMarkerSymbol = scatterMarkerSymbol.Value;
            }

            if (chartName == "MACD" && seriesName == "Price")
            {
                series.Unit = "₿";
            }

            return series;
        }

        private const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz   0123456789 ";
        private static LogPacket GetLogPacket()
        {
            return new LogPacket()
            {
                AlgorithmId = AlgorithmId,
                Channel = Channel,
                Message = new string(Enumerable.Repeat(chars, _random.Next(15, 250)).Select(s => s[_random.Next(s.Length)]).ToArray())
            };
        }

        private static DebugPacket GetDebugPacket(string message = null, bool toast = false)
        {
            if (string.IsNullOrEmpty(message))
            {
                message = new string(Enumerable.Repeat(chars, _random.Next(15, 60)).Select(s => s[_random.Next(s.Length)]).ToArray());
            }

            Debug.Assert(!string.IsNullOrEmpty(message));

            return new DebugPacket(ProjectId, AlgorithmId, CompileId, message, toast);
        }

        private static HandledErrorPacket GetHandledErrorPacket()
        {
            return new HandledErrorPacket(AlgorithmId,
                new string(Enumerable.Repeat(chars, _random.Next(15, 60)).Select(s => s[_random.Next(s.Length)]).ToArray()),
                $"StackTrace - {new string(Enumerable.Repeat(chars, _random.Next(15, 60)).Select(s => s[_random.Next(s.Length)]).ToArray())}");
        }

        private static LiveNodePacket GetLiveNodePacket()
        {
            return new LiveNodePacket()
            {
                Algorithm = new byte[] { 0, 1, 2 },
                Brokerage = "MockBrokerage",
                BrokerageData = new Dictionary<string, string>() { { "mock-api-key", "sdfasfdiwohfvb" } },
                Channel = Channel,
                CompileId = CompileId,
                Controls = new Controls
                {
                    MinuteLimit = 100,
                    SecondLimit = 50,
                    TickLimit = 25,
                    RamAllocation = 512
                },
                DataChannelProvider = "DataChannelProvider",
                DataQueueHandler = "MockDataQueueHandler",
                DeployId = DeployId,
                DisableAcknowledgement = false,
                HistoryProvider = "MockBrokerageHistoryProvider",
                HostName = HostName,
                Language = Language.CSharp,
                LiveDataTypes = null,
                NotificationEvents = null,
                NotificationTargets = null,
                OrganizationId = "",
                Parameters = new Dictionary<string, string>()
                {
                    { "intrinio-username", "" },
                    { "intrinio-password", "" },
                    { "ema-fast", "10" },
                    { "ema-slow", "20" }
                },
                ProjectId = ProjectId,
                ProjectName = null,
                Redelivered = false,
                RequestSource = "WebIDE",
                ServerType = ServerType.Server1024,
                SessionId = SessionId,
                UserId = UserId,
                UserToken = "",
                Version = Globals.Version
            };
        }

        private void NextChartStep()
        {
            foreach (var key in _lastSeriesPoint.Keys)
            {
                var price = _lastSeriesPoint[key];
                decimal change = (0.0025m * ((1_000m / price) - 1m)) + (maximumPercentDeviation * (decimal)(_random.NextDouble() - 0.499));
                _lastSeriesPoint[key] = price * (1m + change);
            }
        }

        private void NextPriceStep()
        {
            foreach (var symbol in _symbols)
            {
                if (!_lastPrice.ContainsKey(symbol))
                {
                    decimal referencePrice = Math.Round((decimal)(_random.NextDouble() * _random.Next(1, 5_000)), 4);
                    _lastPrice.Add(symbol, referencePrice);
                }
                else
                {
                    decimal price = _lastPrice[symbol];
                    decimal change = (0.0025m * ((1_000m / price) - 1m)) + (maximumPercentDeviation * (decimal)(_random.NextDouble() - 0.499));
                    _lastPrice[symbol] = price * (1m + change);
                }
            }
        }

        public void Dispose()
        {
        }
        #endregion
    }
}

  • config.json
{
  // this configuration file works by first loading all top-level
  // configuration items and then will load the specified environment
  // on top, this provides a layering affect. environment names can be
  // anything, and just require definition in this file. There's
  // two predefined environments, 'backtesting' and 'live', feel free
  // to add more!

  "environment": "backtesting-desktop", // "live-paper", "backtesting", "live-interactive", "live-interactive-iqfeed"

  // algorithm class selector
  "algorithm-type-name": "aaa",

  // Algorithm language selector - options CSharp, Python
  "algorithm-language": "CSharp",

  //Physical DLL location
  "algorithm-location": "QuantConnect.Algorithm.CSharp.dll",
  //"algorithm-location": "../../../Algorithm.Python/BasicTemplateFrameworkAlgorithm.py",

  //Research notebook
  //"composer-dll-directory": ".",

  // engine
  "data-folder": "../../../Data/",

  // debugging configuration - options for debugging-method LocalCmdLine, VisualStudio, Debugpy, PyCharm
  "debugging": false,
  "debugging-method": "LocalCmdline",

  // location of a python virtual env to use libraries from
  //"python-venv": "/venv",

  // handlers
  "log-handler": "QuantConnect.Logging.CompositeLogHandler",
  //"messaging-handler": "QuantConnect.Messaging.Messaging",
  "job-queue-handler": "QuantConnect.Queues.JobQueue",
  "api-handler": "QuantConnect.Api.Api",
  "map-file-provider": "QuantConnect.Data.Auxiliary.LocalDiskMapFileProvider",
  "factor-file-provider": "QuantConnect.Data.Auxiliary.LocalDiskFactorFileProvider",
  "data-provider": "QuantConnect.Lean.Engine.DataFeeds.DefaultDataProvider",
  "data-channel-provider": "DataChannelProvider",
  "object-store": "QuantConnect.Lean.Engine.Storage.LocalObjectStore",
  "data-aggregator": "QuantConnect.Lean.Engine.DataFeeds.AggregationManager",

  // limits on number of symbols to allow
  "symbol-minute-limit": 10000,
  "symbol-second-limit": 10000,
  "symbol-tick-limit": 10000,

  // log missing data files, useful for debugging
  "show-missing-data-logs": false,

  // For live trading during warmup we limit the amount of historical data fetched from the history provider and expect the data to be on disk for older data
  "maximum-warmup-history-days-look-back": 5,

  // limits the amount of data points per chart series. Applies only for backtesting
  "maximum-data-points-per-chart-series": 4000,

  // if one uses true in following token, market hours will remain open all hours and all days.
  // if one uses false will make lean operate only during regular market hours.
  "force-exchange-always-open": false,

  // save list of transactions to the specified csv file
  "transaction-log": "",

  // To get your api access token go to quantconnect.com/account
  "job-user-id": "0",
  "api-access-token": "",
  "job-organization-id": "",

  // live data configuration
  "live-data-url": "ws://www.quantconnect.com/api/v2/live/data/",
  "live-data-port": 8020,

  // live portfolio state
  "live-cash-balance": "",
  "live-holdings": "[]",

  // interactive brokers configuration
  "ib-account": "",
  "ib-user-name": "",
  "ib-password": "",
  "ib-host": "127.0.0.1",
  "ib-port": "4002",
  "ib-agent-description": "Individual",
  "ib-tws-dir": "C:\\Jts",
  "ib-trading-mode": "paper",
  "ib-enable-delayed-streaming-data": false,
  "ib-version": "974",
  "ib-weekly-restart-utc-time": "22:00:00",

  // tradier configuration
  "tradier-environment": "paper",
  "tradier-account-id": "",
  "tradier-access-token": "",

  // oanda configuration
  "oanda-environment": "Practice",
  "oanda-access-token": "",
  "oanda-account-id": "",

  // fxcm configuration
  "fxcm-server": "http://www.fxcorporate.com/Hosts.jsp",
  "fxcm-terminal": "Demo", //Real or Demo
  "fxcm-user-name": "",
  "fxcm-password": "",
  "fxcm-account-id": "",

  // iqfeed configuration
  "iqfeed-host": "127.0.0.1",
  "iqfeed-username": "",
  "iqfeed-password": "",
  "iqfeed-productName": "",
  "iqfeed-version": "1.0",

  // gdax configuration
  "gdax-api-secret": "",
  "gdax-api-key": "",
  "gdax-passphrase": "",

  // bitfinex configuration
  "bitfinex-api-secret": "",
  "bitfinex-api-key": "",

  // binance configuration
  "binance-api-secret": "",
  "binance-api-key": "",
  // spot/margin
  "binance-api-url": "https://api.binance.com",
  "binance-websocket-url": "wss://stream.binance.com:9443/ws",
  // USDT futures
  "binance-fapi-url": "https://fapi.binance.com",
  "binance-fwebsocket-url": "wss://fstream.binance.com/ws",
  // Coin futures
  "binance-dapi-url": "https://dapi.binance.com",
  "binance-dwebsocket-url": "wss://dstream.binance.com/ws",

  // binance.us configuration
  "binanceus-api-secret": "",
  "binanceus-api-key": "",
  "binanceus-api-url": "https://api.binance.us",
  "binanceus-websocket-url": "wss://stream.binance.us:9443/ws",


  // bybit configuration
  "bybit-api-secret": "",
  "bybit-api-key": "",
  "bybit-api-url": "https://api.bybit.com",
  "bybit-websocket-url": "wss://stream.bybit.com",

  // kraken configuration
  "kraken-api-secret": "",
  "kraken-api-key": "",
  "kraken-verification-tier": "Starter", // Starter, Intermediate, Pro

  // arteyu configuration
  "atreyu-host": "",
  "atreyu-req-port": "",
  "atreyu-sub-port": "",
  "atreyu-username": "",
  "atreyu-password": "",
  "atreyu-client-id": "",
  "atreyu-broker-mpid": "",
  "atreyu-locate-rqd": "",

  // wolverine configuration
  "wolverine-host": "",
  "wolverine-port": "",
  "wolverine-account": "",
  "wolverine-sender-comp-id": "",
  "wolverine-target-comp-id": "",
  "wolverine-on-behalf-of-comp-id": "",
  "wolverine-log-fix-messages": false,

  // TDAmeritrade configuration
  "tdameritrade-account-number": "",
  "tdameritrade-api-key": "",
  "tdameritrade-access-token": "",

  // RBI configuration
  "rbi-host": "",
  "rbi-port": "",
  "rbi-account": "",
  "rbi-sender-comp-id": "",
  "rbi-target-comp-id": "",
  "rbi-on-behalf-of-comp-id": "",
  "rbi-log-fix-messages": false,

  // Trading Technologies configuration
  "tt-user-name": "",
  "tt-session-password": "",
  "tt-account-name": "",
  "tt-rest-app-key": "",
  "tt-rest-app-secret": "",
  "tt-rest-environment": "",
  "tt-market-data-sender-comp-id": "",
  "tt-market-data-target-comp-id": "",
  "tt-market-data-host": "",
  "tt-market-data-port": "",
  "tt-order-routing-sender-comp-id": "",
  "tt-order-routing-target-comp-id": "",
  "tt-order-routing-host": "",
  "tt-order-routing-port": "",
  "tt-log-fix-messages": false,

  // Exante trading configuration
  // client-id, application-id, shared-key are required to access Exante REST API
  // Exante generates them at https://exante.eu/clientsarea/dashboard/ after adding an application
  "exante-client-id": "",
  "exante-application-id": "",
  "exante-shared-key": "",
  "exante-account-id": "", // Account-id is assigned after registration at Exante
  "exante-platform-type": "", // Environment of the application: live or demo

  // Required to access data from Nasdaq
  // To get your access token go to https://data.nasdaq.com/account/profile
  "nasdaq-auth-token": "",

  // Required to access data from Tiingo
  // To get your access token go to https://www.tiingo.com
  "tiingo-auth-token": "",

  // Required to access data from US Energy Information Administration
  // To get your access token go to https://www.eia.gov/opendata
  "us-energy-information-auth-token": "",

  // Required for IEX history requests
  "iex-cloud-api-key": "",

  // Required for market data from Coin API
  "coinapi-api-key": "",
  "coinapi-product": "free", // free, startup, streamer, professional, enterprise

  // Required for streaming Polygon.io data
  // To get your access token go to https://polygon.io
  "polygon-api-key": "",

  // zerodha configuration goto https://kite.trade
  "zerodha-access-token": "",
  "zerodha-api-key": "",
  "zerodha-product-type": "MIS", //MIS(Intraday) or CNC(Delivery) or NRML(Carry Forward)
  "zerodha-trading-segment": "EQUITY", // EQUITY(NSE,BSE) or COMMODITY (MCX)
  "zerodha-history-subscription": "false", // "true" if History API Subscription available

  //samco configuration go to https://www.samco.in/stocknote-api
  "samco-client-id": "",
  "samco-client-password": "",
  "samco-year-of-birth": "",
  "samco-product-type": "MIS", //MIS(Intraday) or CNC(Delivery) or NRML(Carry Forward)
  "samco-trading-segment": "EQUITY", // EQUITY(NSE,BSE) or COMMODITY (MCX)

  // FTX configuration
  "ftx-account-tier": "Tier1", // accept "Tier1", "Tier2", "Tier3", "Tier4", "Tier5", "Tier6", "VIP1", "VIP2", "VIP3", "MM1", "MM2", "MM3"
  "ftx-api-secret": "",
  "ftx-api-key": "",

  // FTX.US configuration
  "ftxus-account-tier": "Tier1", // accept "Tier1", "Tier2", "Tier3", "Tier4", "Tier5", "Tier6", "Tier7", "Tier8", "Tier9", "VIP1", "VIP2", "MM1", "MM2", "MM3"
  "ftxus-api-secret": "",
  "ftxus-api-key": "",

  // parameters to set in the algorithm (the below are just samples)
  "parameters": {
    // Intrinio account user and password
    "intrinio-username": "",
    "intrinio-password": "",

    "ema-fast": 10,
    "ema-slow": 20
  },

  // specify supported languages when running regression tests
  "regression-test-languages": [ "CSharp", "Python" ],

  // Additional paths to include in python for import resolution
  "python-additional-paths": [],

  "environments": {

    // defines the 'backtesting' environment
    "backtesting": {
      "live-mode": false,

      "setup-handler": "QuantConnect.Lean.Engine.Setup.BacktestingSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.BacktestingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.FileSystemDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.BacktestingRealTimeHandler",
      "history-provider": [ "QuantConnect.Lean.Engine.HistoricalData.SubscriptionDataReaderHistoryProvider" ],
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BacktestingTransactionHandler"
    },

    // defines the 'backtesting' environment
    "backtesting-desktop": {
      "live-mode": false,
      "desktop-http-port": 33333,
      "messaging-handler": "QuantConnect.Messaging.DebugStreamingMessageHandler",
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BacktestingSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.BacktestingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.FileSystemDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.BacktestingRealTimeHandler",
      "history-provider": [ "QuantConnect.Lean.Engine.HistoricalData.SubscriptionDataReaderHistoryProvider" ],
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BacktestingTransactionHandler"
    },

    // defines the 'live-paper' environment
    "live-paper": {
      "live-mode": true,

      // the paper brokerage requires the BacktestingTransactionHandler
      "live-mode-brokerage": "PaperBrokerage",

      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "data-queue-handler": [ "QuantConnect.Lean.Engine.DataFeeds.Queues.LiveDataQueue" ],
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BacktestingTransactionHandler"
    },

    // defines 'live-zerodha' environment
    "live-zerodha": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "ZerodhaBrokerage",
      "data-queue-handler": [ "ZerodhaBrokerage" ],

      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
      "history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
    },

    // defines 'live-samco' environment
    "live-samco": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "SamcoBrokerage",
      "data-queue-handler": [ "SamcoBrokerage" ],

      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
      "history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
    },

    // defines the 'live-tradier' environment
    "live-tradier": {
      "live-mode": true,

      // this setting will save tradier access/refresh tokens to a tradier-tokens.txt file
      // that can be read in next time, this makes it easier to start/stop a tradier algorithm
      "tradier-save-tokens": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "TradierBrokerage",
      "data-queue-handler": [ "TradierBrokerage" ],

      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
      "history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
    },

    // defines the 'live-interactive' environment
    "live-interactive": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "InteractiveBrokersBrokerage",
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "data-queue-handler": [ "QuantConnect.Brokerages.InteractiveBrokers.InteractiveBrokersBrokerage" ],
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
      "history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
    },

    // defines the 'live-interactive-iqfeed' environment
    "live-interactive-iqfeed": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "InteractiveBrokersBrokerage",
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "data-queue-handler": [ "QuantConnect.ToolBox.IQFeed.IQFeedDataQueueHandler" ],
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
      "history-provider": [ "QuantConnect.ToolBox.IQFeed.IQFeedDataQueueHandler", "SubscriptionDataReaderHistoryProvider" ]
    },

    // defines the 'live-fxcm' environment
    "live-fxcm": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "FxcmBrokerage",
      "data-queue-handler": [ "FxcmBrokerage" ],
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
      "history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
    },

    // defines the 'live-oanda' environment
    "live-oanda": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "OandaBrokerage",
      "data-queue-handler": [ "OandaBrokerage" ],
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
      "history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
    },

    "live-gdax": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "GDAXBrokerage",
      "data-queue-handler": [ "GDAXDataQueueHandler" ],
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
      "history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
    },

    "live-bitfinex": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "BitfinexBrokerage",
      "data-queue-handler": [ "BitfinexBrokerage" ],
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
      "history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
    },

    "live-binance": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "QuantConnect.BinanceBrokerage.BinanceBrokerage",
      "data-queue-handler": [ "QuantConnect.BinanceBrokerage.BinanceBrokerage" ],
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
      "history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
    },

    "live-futures-binance": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "QuantConnect.BinanceBrokerage.BinanceFuturesBrokerage",
      "data-queue-handler": [ "QuantConnect.BinanceBrokerage.BinanceFuturesBrokerage" ],
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
      "history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
    },

    "live-coin-futures-binance": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "QuantConnect.BinanceBrokerage.BinanceCoinFuturesBrokerage",
      "data-queue-handler": [ "QuantConnect.BinanceBrokerage.BinanceCoinFuturesBrokerage" ],
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
      "history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
    },

    "live-binanceus": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "QuantConnect.BinanceBrokerage.BinanceUSBrokerage",
      "data-queue-handler": [ "QuantConnect.BinanceBrokerage.BinanceUSBrokerage" ],
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
      "history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
    },

    "live-bybit": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "QuantConnect.BybitBrokerage.BybitBrokerage",
      "data-queue-handler": [ "QuantConnect.BybitBrokerage.BybitBrokerage" ],
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
      "history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
    },

    "live-futures-bybit": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "QuantConnect.BybitBrokerage.BybitFuturesBrokerage",
      "data-queue-handler": [ "QuantConnect.BybitBrokerage.BybitFuturesBrokerage" ],
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
      "history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
    },

    // defines the 'live-atreyu' environment
    "live-atreyu": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "QuantConnect.Atreyu.AtreyuBrokerage",
      "data-queue-handler": [ "QuantConnect.Lean.Engine.DataFeeds.Queues.LiveDataQueue" ],
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler"
    },

    // defines the 'live-trading-technologies' environment
    "live-trading-technologies": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "TradingTechnologiesBrokerage",
      "data-queue-handler": [ "TradingTechnologiesBrokerage" ],
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler"
    },

    // defines the 'live-kraken' environment
    "live-kraken": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "KrakenBrokerage",
      "data-queue-handler": [ "KrakenBrokerage" ],
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
      "history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
    },

    // defines the 'live-ftx' environment
    "live-ftx": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "QuantConnect.FTXBrokerage.FTXBrokerage",
      "data-queue-handler": [ "QuantConnect.FTXBrokerage.FTXBrokerage" ],
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
      "history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
    },

    "live-exante": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "QuantConnect.ExanteBrokerage.ExanteBrokerage",
      "data-queue-handler": [ "QuantConnect.ExanteBrokerage.ExanteBrokerage" ],
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
      "history-provider": "BrokerageHistoryProvider"
    },

    // defines the 'live-ftxus' environment
    "live-ftxus": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "QuantConnect.FTXBrokerage.FTXUSBrokerage",
      "data-queue-handler": [ "QuantConnect.FTXBrokerage.FTXUSBrokerage" ],
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
      "history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
    },

    "live-wolverine": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "WolverineBrokerage",
      "history-provider": [ "SubscriptionDataReaderHistoryProvider" ],
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler"
    },

    "live-tdameritrade": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "TDAmeritradeBrokerage",
      "history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ],
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
      "data-queue-handler": [ "TDAmeritradeBrokerage" ]
    },

    "live-rbi": {
      "live-mode": true,

      // real brokerage implementations require the BrokerageTransactionHandler
      "live-mode-brokerage": "RBIBrokerage",
      "data-queue-handler": [ "QuantConnect.Lean.Engine.DataFeeds.Queues.LiveDataQueue" ],
      "history-provider": [ "SubscriptionDataReaderHistoryProvider" ],
      "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
      "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
      "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
      "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
      "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler"
    }
  }
}

  • 效果
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 补充1(重要):config.json中的messaging-handler设置为QuantConnect.Messaging.StreamingMessageHandler后,Lean的BaseResultsHandler.cs就可以将策略中的Debug等包含的message通过TCP进行Streaming传递了,所以可以将自定义信息如OHLC价格等通过策略中Debug打包进message进行TCP传播,TCP客户端将message进行解包得到OHLC,就可以进行曲线显示等GUI操作。
"desktop-http-port": 33333,
"messaging-handler": "QuantConnect.Messaging.StreamingMessageHandler",
/// <summary>
/// Processes algorithm logs.
/// Logs of the same type are batched together one per line and are sent out
/// </summary>
protected void ProcessAlgorithmLogs(int? messageQueueLimit = null)
{
    ProcessAlgorithmLogsImpl(Algorithm.DebugMessages, PacketType.Debug, messageQueueLimit);
    ProcessAlgorithmLogsImpl(Algorithm.ErrorMessages, PacketType.HandledError, messageQueueLimit);
    ProcessAlgorithmLogsImpl(Algorithm.LogMessages, PacketType.Log, messageQueueLimit);
}

private void ProcessAlgorithmLogsImpl(ConcurrentQueue<string> concurrentQueue, PacketType packetType, int? messageQueueLimit = null)
{
    if (concurrentQueue.IsEmpty)
    {
        return;
    }

    var endTime = DateTime.UtcNow.AddMilliseconds(250).Ticks;
    var currentMessageCount = -1;
    while (DateTime.UtcNow.Ticks < endTime && concurrentQueue.TryDequeue(out var message))
    {
        if (messageQueueLimit.HasValue)
        {
            if (currentMessageCount == -1)
            {
                // this is expensive, so let's get it once
                currentMessageCount = Messages.Count;
            }
            if (currentMessageCount > messageQueueLimit)
            {
                if (!_packetDroppedWarning)
                {
                    _packetDroppedWarning = true;
                    // this shouldn't happen in most cases, queue limit is high and consumed often but just in case let's not silently drop packets without a warning
                    Messages.Enqueue(new HandledErrorPacket(AlgorithmId, "Your algorithm messaging has been rate limited to prevent browser flooding."));
                }
                //if too many in the queue already skip the logging and drop the messages
                continue;
            }
        }

        if (packetType == PacketType.Debug)
        {
        	//message-handler可以将Messages进行TCP传递
            Messages.Enqueue(new DebugPacket(ProjectId, AlgorithmId, CompileId, message));
        }
        else if (packetType == PacketType.Log)
        {
            Messages.Enqueue(new LogPacket(AlgorithmId, message));
        }
        else if (packetType == PacketType.HandledError)
        {
            Messages.Enqueue(new HandledErrorPacket(AlgorithmId, message));
        }
        AddToLogStore(message);

        // increase count after we add
        currentMessageCount++;
    }
}
        /// <summary>
        /// Create a new instance of the notify debug packet:
        /// </summary>
        public DebugPacket(int projectId, string algorithmId, string compileId, string message, bool toast = false)
            : base(PacketType.Debug)
        {
            ProjectId = projectId;
            //此处message可用进行自定义打包,然后TCP客户端进行解包就可以得到策略每一时间步的信息,画曲线什么的就很容易了
            Message = message;
            CompileId = compileId;
            AlgorithmId = algorithmId;
            Toast = toast;
        }

在这里插入图片描述

  • 补充2:Linux环境下devcontainer.json发布一下端口;因为采用TCP通信,所以先把策略在docker里跑起来,再用Panoptes连上TCP,这样实测通信比较顺畅;
	"build": {
		"dockerfile": "Dockerfile"
	},
	"runArgs": ["--publish-all"],

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值