Source Code

screenshot of c# app

//Companion source code for Fundamental Statistical Edge Principle paper (arXiv)
//By Prof. T. Gastaldi, Dept. Statistics and Dept. Computer Science, Univ. of Rome "Sapienza"

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Text;
using System.Windows.Forms;

namespace FundStatEdgePrinciple
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            Application.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
            InitializeComponent();
            labelStatus.Text = "Waiting start";
        }

        public bool simCancelled;

        //parameters for simulation and list of instruments
        public SimulationParameters simPrms;

        //randomizer of randomizers
        public Random rndofRandomizers = new Random();
        public int maxSeed = Constants.oneMillion;
        public RandomSet randomSet = new RandomSet();

        //original strategy S and constructed (dominant) strategy S*
        public Strategy S_Original;
        public Strategy S_UsingHTI;

        //class holding charting info and visual options
        ChartControls chartControls = new ChartControls();

        //storage and print of text results
        OutputTextResults outTextRes = new OutputTextResults();

        private void button1_Click(object sender, EventArgs e)
        {
            if (this.backgroundWorker1.IsBusy) return;

            //graphics initializations
            chartControls.initGraphics(chartControls.darkMode, this.pictureBox1);

            this.TextBox1.Clear();

            labelStatus.ForeColor = Color.RoyalBlue;
            labelStatus.Text = "Running";

            this.simCancelled = false;

            //generate a known random seed (instead of letting C# use Environment.TickCount)
            //for universal replicability of simulations
            //restart randomizers (with known seed for replicability)

            randomSet.rnd_PricesAndInstrument_Seed = this.rndofRandomizers.Next(0, maxSeed);
            randomSet.rnd_PricesAndInstrument = new Random(randomSet.rnd_PricesAndInstrument_Seed);

            randomSet.rnd_Orders_Seed = this.rndofRandomizers.Next(0, maxSeed);
            randomSet.rnd_Orders = new Random(randomSet.rnd_Orders_Seed);

            randomSet.rnd_DelayB_Seed = this.rndofRandomizers.Next(0, maxSeed);
            randomSet.rnd_DelayB = new Random(randomSet.rnd_DelayB_Seed);

            //parameters for simulation and list of instruments
            this.simPrms = new SimulationParameters();
            this.simPrms.InitSimulationParametersCreateInstrument(randomSet);

            this.simPrms.instrumentTraded.priceProcess.initPriceProcessWithInitialCondition(this.simPrms.instrumentTraded, randomSet.rnd_PricesAndInstrument);

            //initialization of output space and counters
            this.outTextRes.initOutputStorage(simPrms);

            this.simPrms.instrumentTraded.appendInstrumentPrmsToOutput(outTextRes);
            this.simPrms.appendSimPrmsToOutput(outTextRes);
            this.TextBox1.AppendText(outTextRes.sb.ToString());
            outTextRes.sb.Clear();

            //original strategy S and constructed (dominant) strategy S*
            this.S_Original = new Strategy();
            this.S_UsingHTI = new Strategy();

            //table header in output storage
            outTextRes.appendTableHeader();

            this.backgroundWorker1.WorkerReportsProgress = true;
            this.backgroundWorker1.RunWorkerAsync();

            //show animation
            this.timer1.Interval = 100;
            this.timer1.Start();
        }
        private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            //Random walk for price (using Rademacher variate * instrument ticksize)

            while (this.simPrms.instrumentTraded.priceProcess.currentTickIndex < this.simPrms.maxNumberOfpriceIntervals)
            {
                //user interruption of sim
                if (this.simCancelled) break;

                //skip some ticks in graphics representations to speed up
                if (this.simPrms.instrumentTraded.priceProcess.currentTickIndex % 1000 == 0) this.simPrms.ComputePnLAddPointsToPriceAndPnLCurves(this.S_Original, this.S_UsingHTI);

                //evaluate possible execution of enqueued order
                this.simPrms.ExecutePossibleEnqueuedOrders(S_UsingHTI);

                //some enqueued executed
                if (this.simPrms.delayedOrdersExecuted.Count > 0)
                {
                    //store current price observation and PnL to avoid order detached from price curve
                    this.simPrms.ComputePnLAddPointsToPriceAndPnLCurves(this.S_Original, this.S_UsingHTI);

                    //detected end of phase
                    if (this.simPrms.queueDelayedOrders.Count == 0)
                    {
                        StrategyPhase newPhase = new StrategyPhase
                        {
                            sequentialIndex = this.simPrms.strategyPhases.Count + 1,
                            currentTickIndex = this.simPrms.instrumentTraded.priceProcess.currentTickIndex,
                            PnLInfoS_Original = S_Original.PnLInfo,
                            PnLInfoS_UsingHTI = S_UsingHTI.PnLInfo
                        };

                        this.simPrms.strategyPhases.Add(newPhase);

                        ////for paper's verification only. ok -----------------------------------------------
                        //paperVerificationComputations pVc = new paperVerificationComputations();
                        //pVc.ComputeVerificationQuantities(this.simPrms, S_UsingHTI);

                        //decimal pnlDifference = S_UsingHTI.PnLInfo.PnL - S_Original.PnLInfo.PnL;
                        //decimal pnlDifferenceForVerification = pVc.sumWeightedDifferencesOfDistancesFromThresholds;

                        //decimal pnlComputed = S_UsingHTI.PnLInfo.PnL;
                        //decimal pnlForVerification1 = pVc.alternatePnL1;
                        //decimal pnlForVerification2 = pVc.alternatePnL2;
                        //// -------------------------------------------------------------------------------

                        outTextRes.sb.Append("Empty queue ------------------------------------------" + Environment.NewLine);
                        //show new phase to user
                        this.backgroundWorker1.ReportProgress(1, newPhase);

                        if (this.simPrms.strategyPhases.Count >= this.simPrms.targetNumberOfPhases) break;
                    }
                }

                //order execution in S: Poisson arrival process for new random orders
                //Poisson process has no "memory": the submission of one order does not depend on any of the previous ones
                Order lastCreatedOrder = this.simPrms.CreateAndExecuteNewPossibleOrder(this.S_Original, this.S_UsingHTI);

                if (lastCreatedOrder != null)
                {
                    //store current price observation and PnL to avoid order detached from price curve
                    this.simPrms.ComputePnLAddPointsToPriceAndPnLCurves(this.S_Original, this.S_UsingHTI);
                }

                //append line with prices for text output, PnL and orders if any (delayed or not) executed

                if (lastCreatedOrder != null ||
                     this.simPrms.delayedOrdersExecuted.Count > 0 ||
                     this.simPrms.instrumentTraded.priceProcess.currentTickIndex < this.simPrms.numberOfInitialTicksToShow)
                {
                    //if order present do not skip line and tell number of previously skipped line
                    if (outTextRes.skippedLines > 0)
                    {
                        this.outTextRes.sb.Append("[skipped " + this.outTextRes.skippedLines.ToString("###,###") + "]" + Environment.NewLine);
                        this.outTextRes.skippedLines = 0;
                    }

                    ReportInfoStorage reportInfoStorage = new ReportInfoStorage
                    {
                        delayedOrdersExecuted = this.simPrms.delayedOrdersExecuted,
                        lastCreatedOrder = lastCreatedOrder,
                        BidString = this.simPrms.instrumentTraded.priceProcess.BidString(),
                        AskString = this.simPrms.instrumentTraded.priceProcess.AskString()
                    };

                    this.outTextRes.AddLineOrLinesWithPriceAndPossibleMultipleOrders(reportInfoStorage, this.S_Original.PnLInfo, this.S_UsingHTI.PnLInfo);
                }
                else
                {
                    this.outTextRes.skippedLines += 1;
                }

                //advance time (must precede price tick, because time is used in drift and vol calc)
                this.simPrms.instrumentTraded.priceProcess.AdvanceTime();

                //advance random price
                this.simPrms.instrumentTraded.priceProcess.AdvancePrice(randomSet.rnd_PricesAndInstrument);
            }
        }
        private void backgroundWorker1_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
        {
            this.timer1.Stop();

            if (this.simCancelled)
            {
                labelStatus.ForeColor = Color.Red;
                labelStatus.Text = "Cancelled";
            }
            else
            {
                //graphics results - final
                chartControls.showGraphicsResults(false, simPrms, S_Original, S_UsingHTI);

                //Show text results -----------------------------------------------------------------------
                outTextRes.OutputCountersAndSimInfo(this.simPrms.instrumentTraded.priceProcess.currentTickIndex);
                outTextRes.OutputPhases(simPrms.strategyPhases);
                this.TextBox1.AppendText(outTextRes.sb.ToString());

                labelStatus.ForeColor = Color.ForestGreen;
                labelStatus.Text = "Completed";
            }

            Application.DoEvents();
        }

        //sporadic async interaction with UI
        private void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
        {
            StrategyPhase newPhase = (StrategyPhase)e.UserState;
            this.TextBox1.AppendText(newPhase.AsString() + " " + simPrms.instrumentTraded.ElapsedTimeAtTickrateAsString() + Environment.NewLine);
            //outTextRes.sb.Append("Empty queue ------------------------------------------" + Environment.NewLine);
        }
        private void toolStripMenuItemSavePicture_Click(object sender, EventArgs e)
        {
            Clipboard.SetImage(chartControls.b);
        }
        private void toolStripMenuItemCopyText_Click(object sender, EventArgs e)
        {
            Clipboard.SetText(this.TextBox1.Text);
        }
        private void buttonCancel_Click(object sender, EventArgs e)
        {
            this.simCancelled = true;
            Application.DoEvents();
        }
        private void timer1_Tick(object sender, EventArgs e)
        {
            //periodically show frame of animated results for animation
            chartControls.showGraphicsResults(true, this.simPrms, S_Original, S_UsingHTI);
        }
        private void buttonClearUi_Click(object sender, EventArgs e)
        {
            labelStatus.Text = "Waiting start";
            labelStatus.ForeColor = Color.Black;
            this.TextBox1.Clear();
            chartControls.g.FillRectangle(this.chartControls.brushBackgroundColor, this.chartControls.bitmapBounds);
            chartControls.pictureBox.Image = chartControls.b;
        }
        private void radioButtonDark_Light_CheckedChanged(object sender, EventArgs e)
        {
            bool newDarkMode = false;

            if (sender == this.radioButtonDark)
            {
                newDarkMode = true;
            }
            else if (sender == this.radioButtonLight)
            {
                newDarkMode = false;
            }

            chartControls.initGraphics(newDarkMode, this.pictureBox1);

            if (this.simPrms.instrumentTraded.priceProcess == null) return;
            chartControls.showGraphicsResults(false, this.simPrms, S_Original, S_UsingHTI);
        }

    }

    //work objects
    public static class Constants
    {
        public const int oneMillion = 1_000_000;
        public const int tenMillion = 10 * oneMillion;
        public const int hoursPerTradingYear = 252 * 24;
    }
    public static class Global
    {
        public static decimal MinMax(int sign, decimal x, decimal y)
        {
            return (x + y + sign * Math.Abs(x - y)) * 0.5m;   // sign=-1 for min, sign=+1 for max, as we adding to/subracting from the average the semidistance
        }
    }
    public class RandomSet
    {
        public Random rnd_PricesAndInstrument; //instrument affects initial price and ticksize
        public Random rnd_Orders;
        public Random rnd_DelayB;

        public int rnd_PricesAndInstrument_Seed;
        public int rnd_Orders_Seed;
        public int rnd_DelayB_Seed;
    }
    public class SimulationParameters
    {
        public SortedList FinancialInstruments = new SortedList();

        public FinancialInstrument instrumentTraded;

        //sim params (arbitrary user choices)
        public int maxNumberOfpriceIntervals = Constants.tenMillion;
        public decimal avgOrderRateDay = 0.5m;                              //avg number of orders per day
        public double probabilityOfOrderPerTick;                            //derived from above
        public int targetNumberOfPhases = 4;
        public int maxLengthQueue = 2;
        public double BernoulliDelayProbability = 1;

        public decimal toleranceOfStructureViolationPerc = 0.5m;
        public decimal gainOverStructurePerc = 0.5m;
        public decimal minOrderSpacePerc = 0.6m;
        public decimal minDistanceEnqueuedOrdersSameSidePerc = 1.9m;   //a large negative % (e.g., - 1000) would vanish the constraint

        //result output option
        public int numberOfInitialTicksToShow = 20;

        //randomizers
        public RandomSet randomSet;

        //Work variables and counters

        public SynthOfHTI synthOfHTI;
        public List queueDelayedOrders;
        public List strategyPhases;
        public List delayedOrdersExecuted;

        public int countRandomOrders;
        public int countRandomOrdersUndelayed;
        public int countRandomOrdersDelayed;
        public int countRandomOrdersDelayedExecuted;

        public void InitSimulationParametersCreateInstrument(RandomSet randomSet)
        {
            //store simulation random seed
            this.randomSet = randomSet;

            //create list of sample instruments
            this.CreateListOfSimulatedInstruments();

            //take one instrument at random from list of sim instruments, examples: CL_NYMEX, SI_COMEX, ES_CME ...
            this.instrumentTraded = this.FinancialInstruments.Values[randomSet.rnd_PricesAndInstrument.Next(0, this.FinancialInstruments.Count)];

            //Poisson probability for orders
            this.probabilityOfOrderPerTick = (double)(this.avgOrderRateDay / this.instrumentTraded.tickRateInstrumentDay);

            //Work variables init

            //list for delayed orders
            this.queueDelayedOrders = new List();

            //Equivalent price compute and store facility
            this.synthOfHTI = new SynthOfHTI();

            //Phases storage
            this.strategyPhases = new List();
        }
        public void CreateDelayedOrderAndPutIntoQueue(Order originalOrder)
        {
            //save cloud structure and flag as delayed in S*
            originalOrder.orderCloudGravityCenterAtDelay = this.synthOfHTI.orderCloudGravityCenter;

            //for verification only
            originalOrder.threshold_T_AtDelay = originalOrder.orderCloudGravityCenterAtDelay - originalOrder.PnLBudgeSign * this.instrumentTraded.toleranceAbs;
            originalOrder.threshold_dT_AtDelay = originalOrder.PnLBudgeSign * (originalOrder.price - originalOrder.threshold_T_AtDelay);

            Order newDelayedOrder = new Order
            {
                OrderID = originalOrder.OrderID,
                isBuy = originalOrder.isBuy,
                PnLBudgeSign = originalOrder.PnLBudgeSign,
                quantity = originalOrder.quantity,
                originalOrderDelayed = originalOrder                     //keep track of original order in delayed order
                //price and time filled at actual execution
            };

            //enqueue order for execution in S*
            this.queueDelayedOrders.Add(newDelayedOrder);
            originalOrder.correspondingOrderWasEnqueued = true;

            this.countRandomOrdersDelayed += 1;
        }
        public void ExecutePossibleEnqueuedOrders(Strategy S_UsingHTI)
        {
            //Examine delayed orders and execute in case ---------------------------------------------------------

            this.delayedOrdersExecuted = new List();

            //backward for executed order removal from queue
            for (int i = this.queueDelayedOrders.Count - 1; i >= 0; i--)
            {
                Order delayedOrder = this.queueDelayedOrders[i];

                if (delayedOrder.ExecuteEventForDelayed(S_UsingHTI, this))
                {

                    delayedOrder.currentTickIndex = this.instrumentTraded.priceProcess.currentTickIndex;
                    //price is already set in execution event check

                    //record executed delayed orders
                    this.delayedOrdersExecuted.Add(delayedOrder);

                    //enlist delayed order in S* strategy
                    S_UsingHTI.AddOrder(delayedOrder);
                    this.countRandomOrdersDelayedExecuted += 1;

                    //removal of executed orders from queue
                    this.queueDelayedOrders.RemoveAt(i);
                }
            }
        }

        //sample financial instruments (futures, real ticksize and multiplier)
        public void CreateListOfSimulatedInstruments()
        {
            this.FinancialInstruments = new SortedList();

            FinancialInstrument f_CL = new FinancialInstrument
            {
                symbol = "CL NYMEX Light Sweet Crude Oil",
                ticksize = 0.01m,
                multiplier = 1000m,
                initPrice = 76.8M,
                annualDriftPerc = 6.71m,
                annualVolatilityPerc = 47.4m,
                commissionPerUnit = 2.37m,
                minOrder = 1,
                maxMultipleOfMinOrder = 5,
                maxSpreadAsNumberOfTicks = 3,
                rateTicksPerHour = 1400,
            };
            this.FinancialInstruments.Add(f_CL.symbol, f_CL);

            FinancialInstrument f_ES = new FinancialInstrument
            {
                symbol = "ES CME E-mini S&P 500",
                ticksize = 0.25m,
                multiplier = 50m,
                initPrice = 4552.25m,
                annualDriftPerc = 10.13m,
                annualVolatilityPerc = 10.2m,
                commissionPerUnit = 2.2m,
                minOrder = 1,
                maxMultipleOfMinOrder = 5,
                maxSpreadAsNumberOfTicks = 2,
                rateTicksPerHour = 700,
            };
            this.FinancialInstruments.Add(f_ES.symbol, f_ES);

            FinancialInstrument f_GC = new FinancialInstrument
            {
                symbol = "GC COMEX Gold",
                ticksize = 0.1m,
                multiplier = 100,
                initPrice = 2015.5m,
                annualDriftPerc = 7.78m,
                annualVolatilityPerc = 28.5m,
                commissionPerUnit = 2.42m,
                minOrder = 1,
                maxMultipleOfMinOrder = 5,
                maxSpreadAsNumberOfTicks = 3,
                rateTicksPerHour = 1100,
            };
            this.FinancialInstruments.Add(f_GC.symbol, f_GC);

            FinancialInstrument f_EUR = new FinancialInstrument
            {
                symbol = "EUR CME European Monetary Union Euro",
                ticksize = 5E-05m,
                multiplier = 125000,
                initPrice = 1.09525m,
                annualDriftPerc = -0.6m,
                annualVolatilityPerc = 6m,
                commissionPerUnit = 2.47m,
                minOrder = 1,
                maxMultipleOfMinOrder = 5,
                maxSpreadAsNumberOfTicks = 2,
                rateTicksPerHour = 750,
            };
            this.FinancialInstruments.Add(f_EUR.symbol, f_EUR);

            FinancialInstrument f_SI = new FinancialInstrument
            {
                symbol = "SI COMEX Silver Index",
                ticksize = 0.005m,
                multiplier = 5000m,
                initPrice = 24.7m,
                annualDriftPerc = 10.14m,
                annualVolatilityPerc = 24.4m,
                commissionPerUnit = 2.42m,
                minOrder = 1,
                maxMultipleOfMinOrder = 5,
                maxSpreadAsNumberOfTicks = 3,
                rateTicksPerHour = 530,
            };
            this.FinancialInstruments.Add(f_SI.symbol, f_SI);

            foreach (FinancialInstrument instr in FinancialInstruments.Values)
            {
                instr.SetWorkVariablesForInstrument(this);
            }
        }
        public void appendSimPrmsToOutput(OutputTextResults outTextRes)
        {
            outTextRes.sb.Append("-Sim params-" + Environment.NewLine);

            outTextRes.sb.Append("rnd_Prices_Seed : " + this.randomSet.rnd_PricesAndInstrument_Seed + Environment.NewLine);
            outTextRes.sb.Append("rnd_Orders_Seed : " + this.randomSet.rnd_Orders_Seed + Environment.NewLine);
            outTextRes.sb.Append("rnd_DelayB  : " + this.randomSet.rnd_DelayB_Seed + Environment.NewLine);

            outTextRes.sb.Append("maxNumberOfpriceIntervals: " + this.maxNumberOfpriceIntervals + Environment.NewLine);
            outTextRes.sb.Append("avgOrderRateDay: " + this.avgOrderRateDay + Environment.NewLine);
            outTextRes.sb.Append("probabilityOfOrderPerTick: " + this.probabilityOfOrderPerTick.ToString("0.####") + Environment.NewLine);
            outTextRes.sb.Append("targetNumberOfPhases: " + this.targetNumberOfPhases + Environment.NewLine);
            outTextRes.sb.Append("maxLengthQueue: " + this.maxLengthQueue + Environment.NewLine);
            outTextRes.sb.Append("BernoulliDelayProbability: " + this.BernoulliDelayProbability + Environment.NewLine);

            outTextRes.sb.Append("toleranceOfStructureViolationPerc: " + this.toleranceOfStructureViolationPerc + "%" + Environment.NewLine);
            outTextRes.sb.Append("gainOverStructurePerc: " + this.gainOverStructurePerc + "%" + Environment.NewLine);
            outTextRes.sb.Append("minOrderSpacePerc: " + this.minOrderSpacePerc + "%" + Environment.NewLine);
            outTextRes.sb.Append("minDistanceEnqueuedOrdersSameSidePerc: " + this.minDistanceEnqueuedOrdersSameSidePerc + "%" + Environment.NewLine);

            outTextRes.sb.Append(Environment.NewLine);
        }

        public void CreateNewPnLDecompositionAddPoints(Strategy strategy)
        {
            strategy.PnLInfo = new PnLInfo();
            strategy.PnLInfo.FillPnLDecomposition(this, strategy);
            strategy.PnLTrajectory.worldPoints.Add(new Tuple(this.instrumentTraded.priceProcess.currentTickIndex, strategy.PnLInfo.PnL));
        }
        public void ComputePnLAddPointsToPriceAndPnLCurves(Strategy S_Original, Strategy S_UsingHTI)
        {
            //store current price observation
            this.instrumentTraded.priceProcess.AddPricesToBidAskTrajectories();

            //PnLs computations with realized/unrealized decompositions
            this.CreateNewPnLDecompositionAddPoints(S_Original);
            this.CreateNewPnLDecompositionAddPoints(S_UsingHTI);
        }

        public Order CreateAndExecuteNewPossibleOrder(Strategy S_Original, Strategy S_UsingHTI)
        {
            //bernoulli trial to generate order
            if (randomSet.rnd_Orders.NextDouble() > this.probabilityOfOrderPerTick) return null;

            Order lastCreatedOrder = new Order();
            lastCreatedOrder.FillNewRandomOrder(this, randomSet.rnd_Orders);

            //order is too close: discard
            if (S_Original.Orders_All.Count > 0 && Math.Abs(lastCreatedOrder.price - S_Original.Orders_All[S_Original.Orders_All.Count - 1].price) < this.instrumentTraded.minOrderSpacingAbs)
                return null;

            //executed immediately in S
            S_Original.AddOrder(lastCreatedOrder);
            this.countRandomOrders += 1;

            //Examine if S* will be delayed 
            bool suitableForDelay = this.queueDelayedOrders.Count < this.maxLengthQueue && lastCreatedOrder.DelayEvent(S_UsingHTI, this, randomSet.rnd_DelayB);

            if (suitableForDelay)
            {
                //create delayed order to be enqueued
                this.CreateDelayedOrderAndPutIntoQueue(lastCreatedOrder);
            }
            else
            {
                //executed immediately in S* too
                S_UsingHTI.AddOrder(lastCreatedOrder); //shared equal order
                this.countRandomOrdersUndelayed += 1;
            }
            return lastCreatedOrder;
        }
    }
    public class PriceProcess
    {
        FinancialInstrument instrumentTraded;

        //storage for trajectories
        public TrajectoryOfValues priceTrajectoryBid = new TrajectoryOfValues();
        public TrajectoryOfValues priceTrajectoryAsk = new TrajectoryOfValues();

        public int currentTickIndex;
        public decimal currentElapsedHoursAtCurrentTickIndex;
        public decimal currentElapsedYearsAtCurrentTickIndex;

        public int currentRandomWalkValue;   //sum of bernoulli random variables

        public decimal currentDiscretizedBid;
        public decimal currentDiscretizedAsk;

        public void initPriceProcessWithInitialCondition(FinancialInstrument instrumentTraded, Random rnd)
        {
            this.instrumentTraded = instrumentTraded;   //parent

            this.currentTickIndex = 0;
            this.currentElapsedHoursAtCurrentTickIndex = 0;
            this.currentRandomWalkValue = 0;

            this.currentDiscretizedBid = this.instrumentTraded.initPrice;
            this.currentDiscretizedAsk = this.instrumentTraded.GetRandomAskFromBid(rnd);
        }
        public void AdvanceTime()
        {
            this.currentTickIndex += 1;

            // compute elapsed hours since start
            this.currentElapsedHoursAtCurrentTickIndex = this.currentTickIndex / this.instrumentTraded.rateTicksPerHour;
            this.currentElapsedYearsAtCurrentTickIndex = this.currentElapsedHoursAtCurrentTickIndex / Constants.hoursPerTradingYear;
        }
        public void AdvancePrice(Random rnd)
        {
            //advance price: random jump
            this.currentDiscretizedBid = this.instrumentTraded.GetNextRandomBid(rnd);
            this.currentDiscretizedAsk = this.instrumentTraded.GetRandomAskFromBid(rnd);
        }
        public void AddPricesToBidAskTrajectories()
        {
            this.priceTrajectoryBid.worldPoints.Add(new Tuple(this.currentTickIndex, this.currentDiscretizedBid));
            this.priceTrajectoryAsk.worldPoints.Add(new Tuple(this.currentTickIndex, this.currentDiscretizedAsk));
        }

        public string BidString()
        {
            return this.currentDiscretizedBid.ToString(this.instrumentTraded.priceMask).PadRight(this.instrumentTraded.priceMask.Length + 7);
        }
        public string AskString()
        {
            return this.currentDiscretizedAsk.ToString(this.instrumentTraded.priceMask).PadRight(this.instrumentTraded.priceMask.Length + 7);
        }
    }
    public class ChartControls
    {
        public Graphics g;
        public Bitmap b;
        public PictureBox pictureBox;

        public GraphicsUnit units = GraphicsUnit.Pixel;
        public RectangleF bitmapBounds;

        //graphics options
        public Color backgroundColor;
        public Color FrameColor;
        public Color colorBidPrices;
        public Color colorAskPrices;
        public Color colorPnL_Orig;
        public Color colorPnL_Edge;
        public Color colorPhases;

        public Color colorBuy = Color.DodgerBlue;
        public Color colorSell = Color.Red;
        public Color colorBuyDelayed = Color.RoyalBlue;
        public Color colorSellDelayed = Color.DeepPink;

        public Font fontFrames;
        public Pen penFrame;
        public Pen penPhases;
        public Pen penZeroPnL;
        public Brush brushFrameTitle;

        public Font fontMinMax;
        public Brush brushPriceLegend;
        public Brush brushBackgroundColor;

        public RealWorldWindow RealWorldWindowForpricesBidAsk;

        public Rectangle targetFramePrice;
        public Rectangle targetFramePnL;

        public bool darkMode;
        public void initGraphics(bool darkMode, PictureBox pictureBox)
        {
            this.pictureBox = pictureBox;
            this.darkMode = darkMode;

            this.b = new Bitmap(this.pictureBox.Width, this.pictureBox.Height);
            this.g = Graphics.FromImage(this.b);
            this.g.SmoothingMode = SmoothingMode.HighQuality;
            this.g.CompositingQuality = CompositingQuality.HighQuality;
            this.g.InterpolationMode = InterpolationMode.High;
            this.g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;

            this.bitmapBounds = this.b.GetBounds(ref this.units);

            //styles and color options

            fontFrames = new Font("Arial", 12f);

            if (darkMode)
            {
                this.backgroundColor = Color.Black;
                this.FrameColor = Color.FromArgb(100, Color.Blue);
                this.colorBidPrices = Color.FromArgb(255, Color.DeepPink);
                this.colorAskPrices = Color.FromArgb(240, Color.DarkBlue);
                this.colorPnL_Orig = Color.DodgerBlue;
                this.colorPnL_Edge = Color.Red;
                this.colorPhases = Color.FromArgb(80, Color.Green);
                this.brushFrameTitle = Brushes.WhiteSmoke;
            }
            else
            {
                this.backgroundColor = Color.White;
                this.FrameColor = Color.FromArgb(100, Color.SlateGray);
                this.colorBidPrices = Color.FromArgb(255, Color.DeepPink);
                this.colorAskPrices = Color.FromArgb(240, Color.DarkBlue);
                this.colorPnL_Orig = Color.Blue;
                this.colorPnL_Edge = Color.OrangeRed;
                this.colorPhases = Color.FromArgb(80, Color.ForestGreen);
                this.brushFrameTitle = Brushes.DarkSlateGray;
            }

            this.penFrame = new Pen(this.FrameColor);
            this.penPhases = new Pen(this.colorPhases, 1);
            this.penPhases.DashStyle = DashStyle.Dash;

            this.penZeroPnL = new Pen(Color.ForestGreen);
            this.penZeroPnL.DashStyle = DashStyle.Dot;

            this.fontMinMax = new Font("Arial", 8);
            this.brushPriceLegend = Brushes.DodgerBlue;

            this.brushBackgroundColor = new SolidBrush(this.backgroundColor);

            //frames for price and PnL charts

            int frameX = 10;
            int frameY = 10;
            int frameWidths = 900;
            int frameHeighs = 450;
            int frameSpacing = 20;

            this.targetFramePrice = new Rectangle(frameX, frameY, frameWidths, frameHeighs);
            this.targetFramePnL = new Rectangle(frameX, frameY + frameSpacing + frameHeighs, frameWidths, frameHeighs);
        }
        public void FrameAndRealWorldWindowForPriceTrajectory(SimulationParameters simPrms)
        {
            //Frame and real world window for price trajectory
            this.g.DrawRectangle(this.penFrame, targetFramePrice);

            RealWorldWindowForpricesBidAsk = new RealWorldWindow();
            RealWorldWindowForpricesBidAsk.ComputeFromListOfRealWorldPoints(simPrms.instrumentTraded.priceProcess.priceTrajectoryBid.worldPoints);

            RealWorldWindow RealWorldWindowForpricesAsk = new RealWorldWindow();
            RealWorldWindowForpricesAsk.ComputeFromListOfRealWorldPoints(simPrms.instrumentTraded.priceProcess.priceTrajectoryAsk.worldPoints);
            RealWorldWindowForpricesBidAsk.EnlargeIfNecessary(RealWorldWindowForpricesAsk);
        }
        public void showThresholdLines(SimulationParameters simPrms)
        {
            decimal gravityCenter = simPrms.synthOfHTI.orderCloudGravityCenter;

            //center
            float y = this.RealWorldWindowForpricesBidAsk.TransfY(gravityCenter, this.targetFramePrice);
            this.g.DrawLine(Pens.DeepPink, new PointF(this.targetFramePrice.Left, y), new PointF(this.targetFramePnL.Right, y));

            //gain
            float yGU = this.RealWorldWindowForpricesBidAsk.TransfY(gravityCenter + simPrms.instrumentTraded.gainAbs, this.targetFramePrice);
            this.g.DrawLine(Pens.DarkGreen, new PointF(this.targetFramePrice.Left, yGU), new PointF(this.targetFramePnL.Right, yGU));
            float yGL = this.RealWorldWindowForpricesBidAsk.TransfY(gravityCenter - simPrms.instrumentTraded.gainAbs, this.targetFramePrice);
            this.g.DrawLine(Pens.DarkGreen, new PointF(this.targetFramePrice.Left, yGL), new PointF(this.targetFramePnL.Right, yGL));

            //tolerance
            float yTU = this.RealWorldWindowForpricesBidAsk.TransfY(gravityCenter + simPrms.instrumentTraded.toleranceAbs, this.targetFramePrice);
            this.g.DrawLine(Pens.DeepSkyBlue, new PointF(this.targetFramePrice.Left, yTU), new PointF(this.targetFramePnL.Right, yTU));
            float yTL = this.RealWorldWindowForpricesBidAsk.TransfY(gravityCenter - simPrms.instrumentTraded.toleranceAbs, this.targetFramePrice);
            this.g.DrawLine(Pens.DeepSkyBlue, new PointF(this.targetFramePrice.Left, yTL), new PointF(this.targetFramePnL.Right, yTL));
        }
        public void LegendForPriceTrajectory(SimulationParameters simPrms, Strategy strategy)
        {
            this.g.DrawString("Price trajectory and orders", this.fontFrames, this.brushFrameTitle, this.targetFramePrice.X + 10, this.targetFramePrice.Y + 10);

            //init price
            float y = RealWorldWindowForpricesBidAsk.TransfY(simPrms.instrumentTraded.initPrice, this.targetFramePrice);
            this.g.DrawString(simPrms.instrumentTraded.initPrice.ToString(), this.fontMinMax, this.brushPriceLegend,
                              targetFramePrice.Left + 5, y);

            //min/max
            this.g.DrawString(RealWorldWindowForpricesBidAsk.maxY.ToString(), this.fontMinMax, this.brushPriceLegend,
                            targetFramePrice.Right - 50, targetFramePrice.Top + 5);

            this.g.DrawString(RealWorldWindowForpricesBidAsk.minY.ToString(), this.fontMinMax, this.brushPriceLegend,
                              targetFramePrice.Right - 50, targetFramePrice.Bottom - 15);

            //ticks
            string s = "elapsed: " + simPrms.instrumentTraded.priceProcess.currentElapsedHoursAtCurrentTickIndex.ToString("#0.##") + "h (" +
                       (simPrms.instrumentTraded.priceProcess.currentElapsedHoursAtCurrentTickIndex / 24).ToString("#0.##") + " days)" +
                       " ticks: " + simPrms.instrumentTraded.priceProcess.currentTickIndex.ToString("###,###") +
                       " at rate " + simPrms.instrumentTraded.rateTicksPerHour + "/h: ";
            this.g.DrawString(s, this.fontMinMax, this.brushPriceLegend,
                              targetFramePrice.Right - 600, targetFramePrice.Bottom - 15);

            //orders
            this.g.DrawString("orders: " + strategy.Orders_All.Count.ToString("###,###"), this.fontMinMax, this.brushPriceLegend,
                              targetFramePrice.Right - 250, targetFramePrice.Bottom - 15);

            //enqueued
            this.g.DrawString("enqueued: " + simPrms.queueDelayedOrders.Count.ToString(), this.fontMinMax, this.brushPriceLegend,
                              targetFramePrice.Right - 150, targetFramePrice.Bottom - 15);
        }
        public void PhasesLinesAcrossFrames(List strategyPhases)
        {
            //Phases alignment lines
            //backward to allow changes during enumeration
            for (int i = strategyPhases.Count - 1; i >= 0; i--)
            {
                StrategyPhase s = strategyPhases[i];

                //ascissa on charts
                float x = this.RealWorldWindowForpricesBidAsk.TransfX(s.currentTickIndex, this.targetFramePnL);
                //sync lines of phases encopassing both charts
                this.g.DrawLine(penPhases, new PointF(x, this.targetFramePrice.Top), new PointF(x, this.targetFramePnL.Bottom));
            }
        }
        public void FrameAndRealWorldWindowForPnL(Strategy S_Original, Strategy S_UsingHTI)
        {
            this.g.DrawRectangle(this.penFrame, this.targetFramePnL);
            this.g.DrawString("PnL of strategies S and S* (red line)", this.fontFrames, this.brushFrameTitle, this.targetFramePnL.X + 10, this.targetFramePnL.Y + 10);

            RealWorldWindow RealWorldWindowForPnL_Both = new RealWorldWindow();
            RealWorldWindowForPnL_Both.ComputeFromListOfRealWorldPoints(S_UsingHTI.PnLTrajectory.worldPoints);

            RealWorldWindow RealWorldWindowForPnL_Original = new RealWorldWindow();
            RealWorldWindowForPnL_Original.ComputeFromListOfRealWorldPoints(S_Original.PnLTrajectory.worldPoints);
            RealWorldWindowForPnL_Both.EnlargeIfNecessary(RealWorldWindowForPnL_Original);

            //x axis on PnL (representing zero level)
            float y = RealWorldWindowForPnL_Both.TransfY(0, this.targetFramePnL);
            this.g.DrawLine(this.penZeroPnL, new PointF(this.targetFramePnL.Left, y), new PointF(this.targetFramePnL.Right, y));

            //S PnL
            S_Original.PnLTrajectory.RepresentTrajectoryInTargetFrame(this.g, RealWorldWindowForPnL_Both, this.targetFramePnL, this.colorPnL_Orig);
            //S* PnL
            S_UsingHTI.PnLTrajectory.RepresentTrajectoryInTargetFrame(this.g, RealWorldWindowForPnL_Both, this.targetFramePnL, this.colorPnL_Edge);

            //min/max
            this.g.DrawString(RealWorldWindowForPnL_Both.maxY.ToString("###,###.##"), this.fontMinMax, this.brushPriceLegend,
                              targetFramePnL.Right - 80, targetFramePnL.Top + 5);

            this.g.DrawString(RealWorldWindowForPnL_Both.minY.ToString("###,###.##"), this.fontMinMax, this.brushPriceLegend,
                              targetFramePnL.Right - 80, targetFramePnL.Bottom - 15);

            //PnL strategies
            this.g.DrawString("PnL S: " + S_Original.PnLInfo.PnL.ToString("###,###.##") +
                              "              PnL S*:" + S_UsingHTI.PnLInfo.PnL.ToString("###,###.##"), this.fontMinMax, this.brushPriceLegend,
                              targetFramePnL.Right - 400, targetFramePnL.Bottom - 15);
        }

        private bool showGraphicsResults_semaphore = false;

        public void showGraphicsResults(bool showThresholdLines, SimulationParameters simPrms, Strategy S_Original, Strategy S_UsingHTI)
        {
            if (this.showGraphicsResults_semaphore) return;

            try
            {
                this.showGraphicsResults_semaphore = true;

                //Show graphics results ------------------------------------------------------------------

                //fast clear of chart area
                this.g.FillRectangle(this.brushBackgroundColor, this.bitmapBounds);

                //Frames
                this.FrameAndRealWorldWindowForPriceTrajectory(simPrms);

                //Threshold lines
                if (showThresholdLines) this.showThresholdLines(simPrms);

                //Phase lines
                this.PhasesLinesAcrossFrames(simPrms.strategyPhases);

                //Prices BID ASK trajectories
                simPrms.instrumentTraded.priceProcess.priceTrajectoryBid.RepresentTrajectoryInTargetFrame(this.g, this.RealWorldWindowForpricesBidAsk, this.targetFramePrice, this.colorBidPrices);
                simPrms.instrumentTraded.priceProcess.priceTrajectoryAsk.RepresentTrajectoryInTargetFrame(this.g, this.RealWorldWindowForpricesBidAsk, this.targetFramePrice, this.colorAskPrices);

                //S orders and S* (delayed only) orders in price frame
                this.RepresentAllOrdersInTargetFrame(S_Original, circleRadius: 4, this.colorBuy, this.colorSell, showOnlyDelayed_forEdgeStrategy: false);
                this.RepresentAllOrdersInTargetFrame(S_UsingHTI, circleRadius: 4, this.colorBuyDelayed, this.colorSellDelayed, showOnlyDelayed_forEdgeStrategy: true);

                this.FrameAndRealWorldWindowForPnL(S_Original, S_UsingHTI);

                this.LegendForPriceTrajectory(simPrms, S_UsingHTI);

                //load bitmap to display charts
                this.pictureBox.Image = this.b;
            }
            finally
            {
                this.showGraphicsResults_semaphore = false;
                Application.DoEvents();
            }
        }
        public void RepresentAllOrdersInTargetFrame(Strategy strategy, float circleRadius, Color ColorBuy, Color ColorSell, bool showOnlyDelayed_forEdgeStrategy)
        {
            this.RepresentListOfOrdersInTargetFrame(strategy.Orders_Buy, circleRadius, ColorBuy, showOnlyDelayed_forEdgeStrategy);
            this.RepresentListOfOrdersInTargetFrame(strategy.Orders_Sell, circleRadius, ColorSell, showOnlyDelayed_forEdgeStrategy);
        }
        private void RepresentListOfOrdersInTargetFrame(List OrderList, float circleRadius, Color colorOrderSide, bool showOnlyDelayed_forEdgeStrategy)
        {
            Brush myBrush = new SolidBrush(colorOrderSide);
            Pen myPen = new Pen(Color.FromArgb(220, colorOrderSide));
            myPen.DashStyle = DashStyle.Dash;

            float orderDiameter = circleRadius + circleRadius;

            if (showOnlyDelayed_forEdgeStrategy)
            {
                for (int i = OrderList.Count - 1; i >= 0; i--)  //backward to allow changes during enumeration
                {
                    Order o = OrderList[i];

                    if (o.originalOrderDelayed != null)
                    {
                        PointF orderCenter = new PointF(this.RealWorldWindowForpricesBidAsk.TransfX(o.currentTickIndex, this.targetFramePrice),
                                                        this.RealWorldWindowForpricesBidAsk.TransfY(o.price, this.targetFramePrice));

                        this.g.FillRectangle(myBrush, new RectangleF(new PointF(orderCenter.X - circleRadius, orderCenter.Y - circleRadius), new SizeF(orderDiameter, orderDiameter)));

                        //join visually to original order
                        PointF centerOriginalOrder = new PointF(this.RealWorldWindowForpricesBidAsk.TransfX(o.originalOrderDelayed.currentTickIndex, this.targetFramePrice),
                                                                this.RealWorldWindowForpricesBidAsk.TransfY(o.originalOrderDelayed.price, this.targetFramePrice));
                        this.g.DrawLine(myPen, orderCenter, centerOriginalOrder);
                    }
                }
            }
            else
            {
                for (int i = OrderList.Count - 1; i >= 0; i--)
                {
                    Order o = OrderList[i];
                    PointF orderCenter = new PointF(this.RealWorldWindowForpricesBidAsk.TransfX(o.currentTickIndex, this.targetFramePrice),
                                                    this.RealWorldWindowForpricesBidAsk.TransfY(o.price, this.targetFramePrice));
                    this.g.FillEllipse(myBrush, new RectangleF(new PointF(orderCenter.X - circleRadius, orderCenter.Y - circleRadius), new SizeF(orderDiameter, orderDiameter)));

                    //circle orders in S corresponding to delayed
                    if (o.correspondingOrderWasEnqueued)
                        this.g.DrawEllipse(Pens.Yellow, new RectangleF(new PointF(orderCenter.X - circleRadius, orderCenter.Y - circleRadius), new SizeF(orderDiameter, orderDiameter)));
                }
            }
        }
    }
    public class FinancialInstrument
    {
        SimulationParameters simPrms;

        public string symbol;
        public decimal ticksize;
        public decimal multiplier;
        public decimal initPrice;

        public decimal annualDriftPerc;
        public decimal annualVolatilityPerc;

        public decimal commissionPerUnit;
        public decimal rateTicksPerHour;

        public int minOrder;
        public int maxMultipleOfMinOrder;
        public int maxSpreadAsNumberOfTicks;

        //work variables
        public decimal TicksPerYear;

        public decimal endOfYearAbsoluteVolatility;
        public decimal endOfYearAbsoluteVolatilityAsNumberOfTicks;
        public double inverseTicksPerYear;
        public double inverseSqrtTicksPerYear;

        public decimal endOfYearAbsoluteDrift;
        public decimal endOfYearAbsoluteDriftAsNumberOfTicks;

        public decimal absoluteDriftPerTick;

        public double randomWalkVolatilityRescaleFactor;

        public decimal toleranceAbs;
        public decimal gainAbs;
        public decimal minOrderSpacingAbs;
        public decimal minDistanceEnqueuedOrdersSameSideAbs;

        public int sumOfProbabilitiesOfSpreadValues;
        public List spreadDistribution;  //assuming values 1,2,... on indexes 0,1,...

        public decimal tickRateInstrumentDay;

        public string priceMask;

        //holder for generated prices
        public PriceProcess priceProcess;

        public decimal GetNextRandomBid(Random r)
        {
            //standard random walk: sum of Rademacher r.v.'s
            this.priceProcess.currentRandomWalkValue += (r.NextDouble() < .5) ? -1 : 1;

            //scaled using volatility if requested (proportional to sqrt of fraction of annual volatility)
            decimal priceRescaledByVolatility = this.priceProcess.currentRandomWalkValue * (decimal)this.randomWalkVolatilityRescaleFactor;

            //shifted by initial price condition and rescaled to ticksize unit
            priceRescaledByVolatility = this.initPrice + priceRescaledByVolatility * this.ticksize;

            //drifting version: add proportional fraction of total drift according to elapsed tick
            if (this.annualDriftPerc != 0)
                priceRescaledByVolatility += this.priceProcess.currentTickIndex * this.absoluteDriftPerTick;

            //make sure it's a multiple of ticksize
            priceRescaledByVolatility = this.BudgeToDiscretePriceGrid(priceRescaledByVolatility);

            return priceRescaledByVolatility;
        }
        public decimal BudgeToDiscretePriceGrid(decimal valueToDiscretize)
        {
            int howManyTicks = (int)Math.Floor(valueToDiscretize / this.ticksize);
            decimal smallerMultiple = howManyTicks * this.ticksize;
            decimal largerMultiple = smallerMultiple + this.ticksize;
            return (valueToDiscretize - smallerMultiple >= largerMultiple - valueToDiscretize) ? largerMultiple : smallerMultiple;
        }
        public decimal GetRandomAskFromBid(Random r)
        {
            //add random spread to bid price to get ask price
            //using a simple decreasing probability for larger spread

            double u = r.NextDouble();  //uniform in [0,1) to select spread value 
            double sumProb = 0d;
            decimal spread = this.ticksize;

            foreach (double Prob in this.spreadDistribution)
            {
                sumProb += Prob;
                if (u <= sumProb) return this.priceProcess.currentDiscretizedBid + spread;
                spread += this.ticksize;
            }
            return this.priceProcess.currentDiscretizedBid + this.ticksize;  //impossible event

            //or more simplistic:
            //return bidPrice + r.Next(1, this.maxSpreadAsNumberOfTicks + 1) * this.ticksize;
        }
        public string ElapsedTimeAtTickrateAsString()
        {
            string s = "Elapsed hours for " + (this.priceProcess.currentTickIndex).ToString("###,###") + " new ticks at given avg tickrate (" + rateTicksPerHour + "/h): " +
                        this.priceProcess.currentElapsedHoursAtCurrentTickIndex.ToString("#0.##") + "h (" + (this.priceProcess.currentElapsedHoursAtCurrentTickIndex / 24).ToString("#0.##") + " days)";
            return s;
        }
        public void SetWorkVariablesForInstrument(SimulationParameters simPrms)
        {

            this.simPrms = simPrms;

            this.tickRateInstrumentDay = this.rateTicksPerHour * 24;
            this.TicksPerYear = this.rateTicksPerHour * Constants.hoursPerTradingYear;

            this.inverseTicksPerYear = (double)(1m / this.TicksPerYear);
            this.inverseSqrtTicksPerYear = Math.Sqrt(this.inverseTicksPerYear);

            this.endOfYearAbsoluteVolatility = this.initPrice * this.annualVolatilityPerc / 100m;
            this.endOfYearAbsoluteVolatilityAsNumberOfTicks = this.endOfYearAbsoluteVolatility / this.ticksize;

            this.endOfYearAbsoluteDrift = this.initPrice * this.annualDriftPerc / 100m;
            this.endOfYearAbsoluteDriftAsNumberOfTicks = this.endOfYearAbsoluteDrift / this.ticksize;

            this.absoluteDriftPerTick = (this.endOfYearAbsoluteDrift * (decimal)this.inverseTicksPerYear);

            this.randomWalkVolatilityRescaleFactor = (double)this.endOfYearAbsoluteVolatilityAsNumberOfTicks * this.inverseSqrtTicksPerYear;

            this.toleranceAbs = Math.Max(this.ticksize, this.initPrice * this.simPrms.toleranceOfStructureViolationPerc / 100);
            this.gainAbs = Math.Max(this.ticksize, this.initPrice * this.simPrms.gainOverStructurePerc / 100);

            this.minOrderSpacingAbs = this.simPrms.minOrderSpacePerc * this.initPrice / 100;

            this.minDistanceEnqueuedOrdersSameSideAbs = this.simPrms.minDistanceEnqueuedOrdersSameSidePerc * this.initPrice / 100;

            this.priceMask = this.PriceMask();

            //for spread generation
            this.sumOfProbabilitiesOfSpreadValues = this.maxSpreadAsNumberOfTicks * (this.maxSpreadAsNumberOfTicks + 1) / 2;

            this.spreadDistribution = new List();
            for (int i = this.maxSpreadAsNumberOfTicks; i <= 1; i--)
            {   // fill with decreasing probabilities of larger spread
                this.spreadDistribution.Add((double)i / (double)this.sumOfProbabilitiesOfSpreadValues);
            }

            //tickdata holder
            this.priceProcess = new PriceProcess();
        }
        public int DecimalsInTicksize()
        {
            return CountDecimalPlaces(this.ticksize, 0);
        }
        private int CountDecimalPlaces(decimal d, int i = 0)
        {
            decimal multiplied = (decimal)((double)d * Math.Pow(10, i));
            if (Math.Round(multiplied) == multiplied) return i;
            return CountDecimalPlaces(d, i + 1);
        }

        //proper mask to format bid/ask
        public string PriceMask()
        {
            int tickDecimals = this.DecimalsInTicksize();
            return (tickDecimals >= 1) ? ("0." + new string('0', tickDecimals)) : "##.##";
        }
        public void appendInstrumentPrmsToOutput(OutputTextResults outTextRes)
        {
            outTextRes.sb.Append(Environment.NewLine);
            outTextRes.sb.Append("-Instrument specs-" + Environment.NewLine);
            outTextRes.sb.Append("symbol: " + this.symbol + Environment.NewLine);
            outTextRes.sb.Append("ticksize: $" + this.ticksize + Environment.NewLine);
            outTextRes.sb.Append("multiplier: " + this.multiplier + Environment.NewLine);
            outTextRes.sb.Append("initPrice:  $" + this.initPrice + Environment.NewLine);

            outTextRes.sb.Append("annualDriftPerc: " + this.annualDriftPerc + "%" + Environment.NewLine);
            if (this.annualDriftPerc != 0)
            {
                outTextRes.sb.Append("endOfYearAbsoluteDrift: " + this.endOfYearAbsoluteDrift + Environment.NewLine);
                outTextRes.sb.Append("endOfYearAbsoluteDriftAsNumberOfTicks : " + this.endOfYearAbsoluteDriftAsNumberOfTicks + Environment.NewLine);
            }

            outTextRes.sb.Append("annualVolatilityPerc: " + this.annualVolatilityPerc + "%" + Environment.NewLine);
            outTextRes.sb.Append("endOfYearAbsoluteVolatility: " + this.endOfYearAbsoluteVolatility + Environment.NewLine);
            outTextRes.sb.Append("endOfYearAbsoluteVolatilityAsNumberOfTicks : " + this.endOfYearAbsoluteVolatilityAsNumberOfTicks + Environment.NewLine);

            outTextRes.sb.Append("commissionPerUnit:  $" + this.commissionPerUnit + Environment.NewLine);
            outTextRes.sb.Append("rateTicksPerHour: " + this.rateTicksPerHour + "/h" + Environment.NewLine);
            outTextRes.sb.Append("minOrder: " + this.minOrder + Environment.NewLine);
            outTextRes.sb.Append("maxMultipleOfMinOrder: " + this.maxMultipleOfMinOrder + Environment.NewLine);
            outTextRes.sb.Append("maxSpreadAsNumberOfTicks:  " + this.maxSpreadAsNumberOfTicks + Environment.NewLine);
            outTextRes.sb.Append(Environment.NewLine);
        }
    }
    public class Strategy
    {
        public List Orders_All = new List();
        public TrajectoryOfValues PnLTrajectory = new TrajectoryOfValues();

        //distinct list for buy/sell random orders for quick PnL computations
        public List Orders_Buy = new List();
        public List Orders_Sell = new List();

        public PnLInfo PnLInfo;

        public void AddOrder(Order o)
        {
            this.Orders_All.Add(o);
            if (o.isBuy) this.Orders_Buy.Add(o);
            else this.Orders_Sell.Add(o);
        }
    }
    public class Order
    {
        public int OrderID;
        public double currentTickIndex;
        public bool isBuy;
        public int PnLBudgeSign; //-1 buy, 1 sell
        public decimal price;
        public int quantity;

        //delay side
        public decimal orderCloudGravityCenterAtDelay;
        public decimal threshold_T_AtDelay;     //for verification only
        public decimal threshold_dT_AtDelay;    //for verification only

        //execution side
        public decimal orderCloudGravityCenterAtExecution;
        public decimal threshold_G_AtExecution;         //for verification only
        public decimal threshold_dG_AtExecution;        //for verification only

        //for matching 
        public int qtyRemToMatch;

        public bool correspondingOrderWasEnqueued;
        public Order originalOrderDelayed;          //only in delayed order

        public void FillNewRandomOrder(SimulationParameters simPrms, Random r)
        {
            this.OrderID = simPrms.countRandomOrders;
            this.currentTickIndex = simPrms.instrumentTraded.priceProcess.currentTickIndex;
            this.isBuy = (r.NextDouble() < .5);
            this.PnLBudgeSign = this.isBuy ? -1 : 1;
            this.price = this.ExecutionPrice(simPrms.instrumentTraded.priceProcess); // this.isBuy ? priceProcess.currentDiscretizedAsk : priceProcess.currentDiscretizedBid;
            this.quantity = simPrms.instrumentTraded.minOrder * r.Next(1, simPrms.instrumentTraded.maxMultipleOfMinOrder + 1);
        }
        public decimal ExecutionPrice(PriceProcess priceProcess)
        {
            return this.isBuy ? priceProcess.currentDiscretizedAsk : priceProcess.currentDiscretizedBid;
        }
        public bool orderTooCloseToEnqueued(SimulationParameters simPrms)
        {
            foreach (Order o in simPrms.queueDelayedOrders)
            {
                //a new buy order to be considered for removal must be above the previous enqueued(more unfavorable)
                if (o.PnLBudgeSign * (o.originalOrderDelayed.price - this.ExecutionPrice(simPrms.instrumentTraded.priceProcess)) <= simPrms.instrumentTraded.minDistanceEnqueuedOrdersSameSideAbs)
                    return true;
            }
            return false;
        }
        public bool DelayEvent(Strategy S_UsingHTI, SimulationParameters simPrms, Random rnd)
        {
            //facultative Bernoulli trial to represent arbitrary filters on delayed orders
            //if (rnd.NextDouble() >= simPrms.BernoulliDelayProbability) return false;  

            if (this.orderTooCloseToEnqueued(simPrms)) return false;

            simPrms.synthOfHTI.ComputeCloudGravityCenter(S_UsingHTI.Orders_All);
            if (simPrms.synthOfHTI.orderCloudGravityCenter == 0) return false;  //must follow computations to set orderCloudGravityCenter

            return (this.PnLBudgeSign * (simPrms.synthOfHTI.orderCloudGravityCenter - this.price)) > simPrms.instrumentTraded.toleranceAbs;
        }
        public bool ExecuteEventForDelayed(Strategy S_UsingHTI, SimulationParameters simPrms)
        {
            simPrms.synthOfHTI.ComputeCloudGravityCenter(S_UsingHTI.Orders_All);
            this.orderCloudGravityCenterAtExecution = simPrms.synthOfHTI.orderCloudGravityCenter;
            this.price = this.ExecutionPrice(simPrms.instrumentTraded.priceProcess);  //set price in advance in case eventually executed

            //for verification only
            decimal maxGravity = Global.MinMax(this.PnLBudgeSign, this.orderCloudGravityCenterAtExecution, this.originalOrderDelayed.orderCloudGravityCenterAtDelay);

            this.threshold_G_AtExecution = maxGravity + this.PnLBudgeSign * simPrms.instrumentTraded.gainAbs;
            this.threshold_dG_AtExecution = this.PnLBudgeSign * (this.price - this.threshold_G_AtExecution);

            return (this.PnLBudgeSign * (this.price - maxGravity)) > simPrms.instrumentTraded.gainAbs;
        }
        public string AsString(FinancialInstrument instrumentTraded)
        {
            string s = "Id" + this.OrderID + " " + (this.isBuy ? "Buy" : "Sell") + " " + this.quantity + "@" + this.price.ToString(instrumentTraded.priceMask);

            //delayed order executed
            if (this.originalOrderDelayed != null)
                s += " [" + this.originalOrderDelayed.price.ToString(instrumentTraded.priceMask) + "-->" + this.price.ToString(instrumentTraded.priceMask) + "]";
            else
                 //Order that was delayed
                 //in case, ⌛︎ Unicode variation selector to preserve monospacing with emoji in html
                 if (this.correspondingOrderWasEnqueued) s += " => ";

            return s;
        }
    }
    public class SynthOfHTI
    {
        public decimal orderCloudGravityCenter;
        public void ComputeCloudGravityCenter(List Orders)
        {
            decimal qBuy = 0;
            decimal valueBuy = 0;

            decimal qSell = 0;
            decimal valueSell = 0;

            foreach (Order o in Orders)
            {
                if (o.isBuy)
                {
                    qBuy += o.quantity;
                    valueBuy += o.price * o.quantity;
                }
                else
                {
                    qSell += o.quantity;
                    valueSell += o.price * o.quantity;
                }
            }

            if (qBuy + qSell > 0)
                this.orderCloudGravityCenter = (valueBuy + valueSell) / (qBuy + qSell);
            else
                this.orderCloudGravityCenter = 0;  //undefined
        }
    }
    public class paperVerificationComputations
    {
        //For paper's results verification only

        public int sumQuantitiesDelayedOrders;
        public decimal sumWeightedDifferencesOfDistancesFromThresholds;

        public decimal spreadDifference;

        public decimal alternatePnL1;  // alternate PnL as sum of single orders contributions
        public decimal alternatePnL2;  // alternate PnL as sum of single orders contributions
        public void ComputeVerificationQuantities(SimulationParameters simPrms, Strategy strategy)
        {
            //For paper's results verification only
            this.sumQuantitiesDelayedOrders = 0;
            this.sumWeightedDifferencesOfDistancesFromThresholds = 0;
            this.spreadDifference = 0;

            foreach (Order o in strategy.Orders_All)
            {
                if (o.originalOrderDelayed != null)  //delayed order
                {
                    this.sumQuantitiesDelayedOrders += o.quantity;
                    this.sumWeightedDifferencesOfDistancesFromThresholds += (o.threshold_dG_AtExecution - o.originalOrderDelayed.threshold_dT_AtDelay) * o.quantity;
                }
            }
            this.sumWeightedDifferencesOfDistancesFromThresholds += (simPrms.instrumentTraded.gainAbs + simPrms.instrumentTraded.toleranceAbs) * this.sumQuantitiesDelayedOrders;
            this.sumWeightedDifferencesOfDistancesFromThresholds *= simPrms.instrumentTraded.multiplier;

            //Alternate computations of PnL only (for cross-verification only) --------------------

            this.alternatePnL1 = 0;
            this.alternatePnL2 = 0;

            decimal commsTot = 0;
            decimal position = 0;
            decimal midPoint = 0.5m * (simPrms.instrumentTraded.priceProcess.currentDiscretizedBid + simPrms.instrumentTraded.priceProcess.currentDiscretizedAsk);
            decimal emispread = 0.5m * (simPrms.instrumentTraded.priceProcess.currentDiscretizedAsk - simPrms.instrumentTraded.priceProcess.currentDiscretizedBid);

            //for alternatePnL2
            decimal valTraded = 0;

            foreach (Order o in strategy.Orders_All)
            {
                commsTot += o.quantity * simPrms.instrumentTraded.commissionPerUnit;
                position += -o.PnLBudgeSign * o.quantity;    //o.isBuy ? o.quantity : -o.quantity;
                this.alternatePnL1 += o.PnLBudgeSign * (o.price - midPoint) * o.quantity;

                //for alternatePnL2
                valTraded += o.PnLBudgeSign * o.price * o.quantity;
            }


            //for alternatePnL2


            //add closing comms (necessary because not matched with current price)
            commsTot += Math.Abs(position) * simPrms.instrumentTraded.commissionPerUnit;

            //sum of remaining portions of spread to close position
            decimal closeSpread = Math.Abs(position) * emispread;

            this.alternatePnL1 -= closeSpread;
            this.alternatePnL1 *= simPrms.instrumentTraded.multiplier;
            this.alternatePnL1 -= commsTot;

            //for alternatePnL2
            valTraded -= closeSpread;
            valTraded *= simPrms.instrumentTraded.multiplier;
            valTraded -= commsTot;
            decimal valPos = midPoint * simPrms.instrumentTraded.multiplier * position;
            this.alternatePnL2 = valTraded + valPos;
        }
    }
    public class PnLInfo
    {
        public int position;

        public decimal PnL;
        public decimal realized;
        public decimal unrealized;

        public decimal totalCommissions;

        //PnL computation and decomposition including commissions, spread and position closing expenses
        public void FillPnLDecomposition(SimulationParameters simPrms, Strategy strategy)
        {
            //initial reset of variables to keep track of matched quantities in each order
            foreach (Order o in strategy.Orders_All) o.qtyRemToMatch = o.quantity;

            //Pnl decomposition and computation

            this.position = 0;
            this.unrealized = 0;
            this.realized = 0;
            this.totalCommissions = 0;

            int indexBuy = 0;
            int indexSell = 0;

            decimal buySidePrice;
            decimal sellSidePrice;

            int buySideQuantity;
            int sellSideQuantity;

            decimal commissionPerUnitRoundTrip = simPrms.instrumentTraded.commissionPerUnit + simPrms.instrumentTraded.commissionPerUnit;

            while (true)
            {
                //order to match lots
                Order orderBuy = null;
                Order orderSell = null;

                if (indexBuy < strategy.Orders_Buy.Count) orderBuy = strategy.Orders_Buy[indexBuy];
                if (indexSell < strategy.Orders_Sell.Count) orderSell = strategy.Orders_Sell[indexSell];

                //exit loop if no more orders available
                if (orderBuy == null && orderSell == null) break; //matching finished

                //select price and quantity to match, orders can both exists or only on one buy/sell side

                if (orderBuy != null)
                {
                    buySidePrice = orderBuy.price;
                    buySideQuantity = orderBuy.qtyRemToMatch;
                }
                else
                {
                    buySidePrice = simPrms.instrumentTraded.priceProcess.currentDiscretizedAsk;
                    buySideQuantity = int.MaxValue;  //to take matching position from other side
                }

                if (orderSell != null)
                {
                    sellSidePrice = orderSell.price;
                    sellSideQuantity = orderSell.qtyRemToMatch;
                }
                else
                {
                    sellSidePrice = simPrms.instrumentTraded.priceProcess.currentDiscretizedBid;
                    sellSideQuantity = int.MaxValue;
                }

                //lot matching (sort lists of orders for methods different from FIFO)
                int matchQuantity = Math.Min(sellSideQuantity, buySideQuantity);
                decimal matchPnL = (sellSidePrice - buySidePrice) * matchQuantity;

                this.totalCommissions += matchQuantity * commissionPerUnitRoundTrip;

                //Reduce quantity for next matches and advance index if entire order matched

                if (orderBuy != null)
                {
                    orderBuy.qtyRemToMatch -= matchQuantity;
                    if (orderBuy.qtyRemToMatch == 0) indexBuy += 1;
                }
                if (orderSell != null)
                {
                    orderSell.qtyRemToMatch -= matchQuantity;
                    if (orderSell.qtyRemToMatch == 0) indexSell += 1;
                }

                if (orderBuy != null && orderSell != null)
                {
                    this.realized += matchPnL;  //realized component
                }
                else  //only one of buy or sell side has a running position
                {
                    //unrealized component
                    this.unrealized += matchPnL;

                    //position
                    this.position += (orderBuy != null) ? matchQuantity : -matchQuantity;
                }
            }

            this.realized *= simPrms.instrumentTraded.multiplier;
            this.unrealized *= simPrms.instrumentTraded.multiplier;

            //final PnL
            this.PnL = this.realized + this.unrealized - this.totalCommissions;
        }
    }
    public class StrategyPhase
    {
        public int sequentialIndex;
        public double currentTickIndex;

        public PnLInfo PnLInfoS_Original;
        public PnLInfo PnLInfoS_UsingHTI;
        public string AsString()
        {
            //Δ for html page
            return "Phase " + sequentialIndex + " ΔPnL: $" + (this.PnLInfoS_UsingHTI.PnL - this.PnLInfoS_Original.PnL).ToString("####,###.00");
        }
    }
    public class RealWorldWindow
    {
        public double minX;
        public double maxX;
        public decimal minY;
        public decimal maxY;
        public void ComputeFromListOfRealWorldPoints(List> worldPoints)
        {
            //Find min and max X, Y of real world data for coordinate transform

            this.minX = double.MaxValue;
            this.maxX = double.MinValue;
            this.minY = decimal.MaxValue;
            this.maxY = decimal.MinValue;

            //backward to allow changes during enumeration
            for (int i = worldPoints.Count - 1; i >= 0; i--)
            {
                Tuple pointInRealWorld = worldPoints[i];

                this.minX = Math.Min(this.minX, pointInRealWorld.Item1);
                this.maxX = Math.Max(this.maxX, pointInRealWorld.Item1);
                this.minY = Math.Min(this.minY, pointInRealWorld.Item2);
                this.maxY = Math.Max(this.maxY, pointInRealWorld.Item2);
            }
        }
        public void EnlargeIfNecessary(RealWorldWindow otherRealWorldWindow)
        {
            this.minX = Math.Min(this.minX, otherRealWorldWindow.minX);
            this.maxX = Math.Max(this.maxX, otherRealWorldWindow.maxX);
            this.minY = Math.Min(this.minY, otherRealWorldWindow.minY);
            this.maxY = Math.Max(this.maxY, otherRealWorldWindow.maxY);
        }
        public float TransfX(double x, Rectangle targetFrame)
        {
            if (this.maxX - this.minX != 0) return (float)(targetFrame.Left + targetFrame.Width * (x - this.minX) / (this.maxX - this.minX));
            return targetFrame.Left;
        }
        public float TransfY(decimal y, Rectangle targetFrame)
        {
            if (this.maxY - this.minY != 0) return (float)(targetFrame.Top + targetFrame.Height * (this.maxY - y) / (this.maxY - this.minY));
            return 0.5f * (targetFrame.Top + targetFrame.Bottom);
        }
    }
    public class TrajectoryOfValues
    {
        public List> worldPoints = new List>();  //time and prices

        public List devicePoints;

        //general function used to represent both prices and PnL trajectories (or any other)
        public void RepresentTrajectoryInTargetFrame(Graphics g, RealWorldWindow WorldWdw, Rectangle devFrame, Color Color)
        {
            if (this.worldPoints.Count < 2) return;

            Pen myPen = new Pen(Color);

            //compute transformed coordinates
            this.devicePoints = new List();

            //transform real world point to device points within target frame
            for (int i = worldPoints.Count - 1; i >= 0; i--)   //backward to allow changes during enumeration
            {
                Tuple pt = worldPoints[i];
                this.devicePoints.Add(new PointF(WorldWdw.TransfX(pt.Item1, devFrame), WorldWdw.TransfY(pt.Item2, devFrame)));
            }

            g.DrawLines(myPen, this.devicePoints.ToArray());
        }
    }
    public class OutputTextResults
    {
        public StringBuilder sb;
        public SimulationParameters simPrms;

        public int skippedLines = 0;
        private int spacingForOrder = 40;
        public void initOutputStorage(SimulationParameters simPrms)
        {
            this.sb = new StringBuilder();
            this.simPrms = simPrms;
        }
        public void appendTableHeader()
        {
            int priceSpace = this.simPrms.instrumentTraded.initPrice.ToString(this.simPrms.instrumentTraded.priceMask).Length;
            string header = "Bid".PadRight(priceSpace) + "       " + "Ask".PadRight(priceSpace) +
                   "            Orders                             Position     PnL         Real        Unr" +
                   "                   Delayed (S*)              Position     PnL         Real         Unr";

            sb.Append(Environment.NewLine + header + Environment.NewLine + Environment.NewLine);
        }
        public void AddLineOrLinesWithPriceAndPossibleMultipleOrders(ReportInfoStorage reportInfoStorage, PnLInfo PnLInfoS_Original, PnLInfo PnLInfoS_UsingHTI)
        {
            //order execution string with price
            string portionLineWithPriceAndPossibleOrder = reportInfoStorage.BidString + reportInfoStorage.AskString + OrderPositionPnLStringInfo(reportInfoStorage.lastCreatedOrder, PnLInfoS_Original);

            if (reportInfoStorage.delayedOrdersExecuted.Count == 0)
            {
                //no delayed order, just tick or possibly one executed
                this.sb.Append(portionLineWithPriceAndPossibleOrder + OrderPositionPnLStringInfo(null, PnLInfoS_UsingHTI) + Environment.NewLine);
            }
            else
            {
                //delayed orders present: use filler after first one to create a vertical alignment
                bool isFirstLine = true;
                foreach (Order o in reportInfoStorage.delayedOrdersExecuted)
                {
                    if (isFirstLine)
                    {
                        this.sb.Append(portionLineWithPriceAndPossibleOrder + OrderPositionPnLStringInfo(o, PnLInfoS_UsingHTI) + Environment.NewLine);
                        isFirstLine = false;
                    }
                    else
                        this.sb.Append("".PadRight(portionLineWithPriceAndPossibleOrder.Length) + OrderPositionPnLStringInfo(o, null) + Environment.NewLine);
                }
            }
        }
        private string OrderPositionPnLStringInfo(Order o, PnLInfo PnLInfo)
        {
            string s = "  ";

            //no order filler
            if (o == null)
                s += "".PadRight(this.spacingForOrder);
            else
                s += o.AsString(this.simPrms.instrumentTraded).PadRight(this.spacingForOrder);

            if (PnLInfo != null)
                s += PnLInfo.position.ToString().PadLeft(6) + "  " + PnLInfo.PnL.ToString("0.00").PadLeft(10) + "   " +
                     PnLInfo.realized.ToString("0.00").PadLeft(10) + "  " + PnLInfo.unrealized.ToString("0.00").PadLeft(10);

            return s;
        }
        public void OutputCountersAndSimInfo(int priceIntervalCounter)
        {
            sb.Append(Environment.NewLine);
            sb.Append("-Results-" + Environment.NewLine);
            sb.Append("priceIntervalCounter:              " + priceIntervalCounter.ToString("###,###") + Environment.NewLine);
            sb.Append("countRandomOrders:                 " + this.simPrms.countRandomOrders + Environment.NewLine);
            sb.Append("countRandomOrdersUndelayed:        " + this.simPrms.countRandomOrdersUndelayed + Environment.NewLine);
            sb.Append("countRandomOrdersDelayed:          " + this.simPrms.countRandomOrdersDelayed + Environment.NewLine);
            sb.Append("countRandomOrdersDelayedExecuted:  " + this.simPrms.countRandomOrdersDelayedExecuted + Environment.NewLine);
            sb.Append("countPhases:                       " + this.simPrms.strategyPhases.Count + Environment.NewLine);
            sb.Append("queueDelayedOrders.Count:          " + this.simPrms.queueDelayedOrders.Count + Environment.NewLine);

            sb.Append(this.simPrms.instrumentTraded.ElapsedTimeAtTickrateAsString() + Environment.NewLine);
        }
        public void OutputPhases(List strategyPhases)
        {
            sb.Append(Environment.NewLine + "PnL differences at phases:" + Environment.NewLine);
            foreach (StrategyPhase s in strategyPhases)
            {
                sb.Append(s.AsString() + Environment.NewLine);
            }
        }
    }
    public class ReportInfoStorage
    {
        public Order lastCreatedOrder;
        public List delayedOrdersExecuted;

        public string BidString;
        public string AskString;
    }

} //end namespace