Source Code
//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