NexusFi: Find Your Edge


Home Menu

 





Design a DayTrader Scalping Order Flow Indicator


Discussion in Traders Hideout

Updated
      Top Posters
    1. looks_one hyperscalper with 136 posts (239 thanks)
    2. looks_two Chof with 22 posts (12 thanks)
    3. looks_3 Connor with 16 posts (8 thanks)
    4. looks_4 justtrader with 14 posts (8 thanks)
      Best Posters
    1. looks_one bobwest with 2 thanks per post
    2. looks_two hyperscalper with 1.8 thanks per post
    3. looks_3 SpeculatorSeth with 1 thanks per post
    4. looks_4 Chof with 0.5 thanks per post
    1. trending_up 47,260 views
    2. thumb_up 328 thanks given
    3. group 55 followers
    1. forum 248 posts
    2. attach_file 80 attachments




 
Search this Thread

Design a DayTrader Scalping Order Flow Indicator

  #111 (permalink)
 Chof 
Edmonton AB. / Canada
 
Experience: Intermediate
Platform: NT 8
Broker: Amp / CQG
Trading: NQ index
Frequency: Several times daily
Duration: Seconds
Posts: 47 since Apr 2017
Thanks Given: 29
Thanks Received: 50

I keep coming back to this as it ticks so many boxes.. At the risk of bringing logic into this..it brings a focus to key areas of interest. It incorporates features - like risk/overextension away from the volume mean and the moving box using the taper.. I look forward to running it to see it all work together. I think it will really help qualify entries with what I'm already using..

I've scoured the "Integrator" for the lines to replace with your "Code" but would only be guessing if I replaced anything.. Could you please list the line(s) to replace in the editor..? That would help out a lot.. Are you still sure you want to replace it for Raw Volume.. ? Seems to work well..

Chuck

Reply With Quote

Can you help answer these questions
from other members on NexusFi?
Exit Strategy
NinjaTrader
Trade idea based off three indicators.
Traders Hideout
MC PL editor upgrade
MultiCharts
Better Renko Gaps
The Elite Circle
Pivot Indicator like the old SwingTemp by Big Mike
NinjaTrader
 
Best Threads (Most Thanked)
in the last 7 days on NexusFi
Spoo-nalysis ES e-mini futures S&P 500
29 thanks
Just another trading journal: PA, Wyckoff & Trends
25 thanks
Tao te Trade: way of the WLD
24 thanks
Bigger Wins or Fewer Losses?
23 thanks
GFIs1 1 DAX trade per day journal
17 thanks
  #112 (permalink)
 hyperscalper 
boise idaho
 
Experience: Advanced
Platform: NinjaTrader C# Custom
Broker: NinjaTrader LeeLoo Rithmic
Trading: Nasdaq Futures NQ/MNQ
Posts: 314 since Apr 2020
Thanks Given: 15
Thanks Received: 522


Chof View Post
I keep coming back to this as it ticks so many boxes.. At the risk of bringing logic into this..it brings a focus to key areas of interest. It incorporates features - like risk/overextension away from the volume mean and the moving box using the taper.. I look forward to running it to see it all work together. I think it will really help qualify entries with what I'm already using..

I've scoured the "Integrator" for the lines to replace with your "Code" but would only be guessing if I replaced anything.. Could you please list the line(s) to replace in the editor..? That would help out a lot.. Are you still sure you want to replace it for Raw Volume.. ? Seems to work well..

Chuck

I switched off volume compression; leaving "raw" volume when
I needed to form a VWAP, leading to estimate of Risk in "delta
ticks" from VWAP, and ultimately then to estimating the Total Risk
multiplying by the Point Value of the instrument.

I'll try to get back to you on your question; but when cutting 'n
pasting "snippets" of code; that is entirely your risk; as the smallest
detail can completely change an outcome in code; so I'm "supporting"
only a full paste of text body.

However, if you have gained the expertise to take snippets; then
that's great as one of the goals of this whole exercise.

hyperscalper

Started this thread Reply With Quote
Thanked by:
  #113 (permalink)
 Chof 
Edmonton AB. / Canada
 
Experience: Intermediate
Platform: NT 8
Broker: Amp / CQG
Trading: NQ index
Frequency: Several times daily
Duration: Seconds
Posts: 47 since Apr 2017
Thanks Given: 29
Thanks Received: 50


Oh, I was mistaken when I thought you were posting code for us to snip out existing and use instead.
I'm still at a loss to see where I could "turn off" volume compression. I've obviously missed this in properties...
or the place it was put in the code.. Could you please point it out.. (my thanks for your patience with
my oversight)

Chuck

Reply With Quote
  #114 (permalink)
 hyperscalper 
boise idaho
 
Experience: Advanced
Platform: NinjaTrader C# Custom
Broker: NinjaTrader LeeLoo Rithmic
Trading: Nasdaq Futures NQ/MNQ
Posts: 314 since Apr 2020
Thanks Given: 15
Thanks Received: 522


Chof View Post
Oh, I was mistaken when I thought you were posting code for us to snip out existing and use instead.
I'm still at a loss to see where I could "turn off" volume compression. I've obviously missed this in properties...
or the place it was put in the code.. Could you please point it out.. (my thanks for your patience with
my oversight)

Chuck

 
Code
		private void reportTrade(int tradeRawVol, double price) {
			if (Math.Abs(tradeRawVol) < bigLotMinimum) return; // reject volume
			if ( LOG_RAW_VOLUME ) {
				if (tradeRawVol>0) {
					ninjaPrint("Retail Buy "+tradeRawVol+" @"+price);
				}
				else {
					ninjaPrint("       Retail Sell "+tradeRawVol+" @"+price);
				}
			}
			lastRawTradeVol = tradeRawVol;
			// do not compress volume here, due to Risk Value calculations
			double rawVol = tradeRawVol; //compressSizeMinOneSigned( tradeRawVol );
			// Super Spike Volumes test here
			//if (Math.Abs(compressedVol)>superSpikeThreshold*getLastReferenceVolume()) {
			if (Math.Abs(rawVol)> (superSpikeThresholdRatio*getLastReferenceVolume())) {
				if (rawVol>0) wasSuperSpikeBuy = true;
				else wasSuperSpikeSell = true;
			}
			reportToIntegrator( rawVol, price );
		}
I hope you can see above
that the call to compressSizeMinOneSigned
is simply commented out, so rawVol remains the same as tradeRawVol.

Hope that helps !

hyperscalper

Started this thread Reply With Quote
Thanked by:
  #115 (permalink)
 hyperscalper 
boise idaho
 
Experience: Advanced
Platform: NinjaTrader C# Custom
Broker: NinjaTrader LeeLoo Rithmic
Trading: Nasdaq Futures NQ/MNQ
Posts: 314 since Apr 2020
Thanks Given: 15
Thanks Received: 522

I PROMISED ANOTHER CODE DROP; HERE IT IS

So, this code shows a value which is "proportional to"
the Total Risk, displayed as a number.

This is only "indicative" but Bigger means More. On the
Top side, the larger the "Short Risk", the more likely
the Price will stop rising; and for the lower Pricings,
the "Long Risk" is more likely to receive "Support",
the Higher/Bigger is the Risk value.

These values are Positive all the time, and hopefully you
will be able to get a feel for how large the values may
become, and the relation to predicting "Resistance" at
the Top and "Support" at the bottom of Price peaks
and troughs...

TAKE THIS CODE AS A WHOLE, and paste into the NinjaScript
Editor. BE SURE YOU BACK UP ANY CODE YOU HAVE THAT
YOU MAY WANT TO KEEP !!!

This Indicator will show as "TradeFlowRisk 1.1", as you can
see from the code.

I struggled for a while on the Draw.Dot and Draw.Text
"tags" which MUST be unique for every drawn object you
wish to be persistent...

 
Code
#region Using declarations
using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Xml.Serialization;
using NinjaTrader.Cbi;
using NinjaTrader.Gui;
using NinjaTrader.Gui.Chart;
using NinjaTrader.Gui.SuperDom;
using NinjaTrader.Gui.Tools;
using NinjaTrader.Data;
using NinjaTrader.NinjaScript;
using NinjaTrader.Core.FloatingPoint;
using NinjaTrader.NinjaScript.DrawingTools;
#endregion

//This namespace holds Indicators in this folder and is required. Do not change it. 
namespace NinjaTrader.NinjaScript.Indicators
{
	/* Code is freely offered as tutorial examples; but is "As Is"
	and the user is fully responsible for the consequences of any
	changes.  The purpose is to encourage modifications; but be sure
	you have saved stable versions, perhaps on a daily basis, in case you break it.
	*/
	public class TradeFlowRisk : Indicator
	{
		protected override void OnStateChange()
		{
			if (State == State.SetDefaults)
			{
				Description									= @"in Futures.IO an exercise in addding Risk to a simple Trade Flow analyzer.";
				Name										= "TradeFlowRisk 1.1";
				Calculate									= Calculate.OnBarClose;
				IsOverlay									= false;
				DisplayInDataBox							= false;
				DrawOnPricePanel							= true;
				DrawHorizontalGridLines						= false;
				DrawVerticalGridLines						= false;
				PaintPriceMarkers							= true;
				ScaleJustification							= NinjaTrader.Gui.Chart.ScaleJustification.Left;
				//Disable this property if your indicator requires custom values that cumulate with each new market data event. 
				//See Help Guide for additional information.
				IsSuspendedWhileInactive					= false; // always run
				FileName					= "replaceThisFilename.txt";
				AltColor					= false; 
				//VirtualTimebaseProperty	= false; // default is Real Time only
				return; // good practice
			}
			else if (State == State.Configure)
			{
				ninjaPrint2("Configure state..."+getObjectID(this));
				Calculate									= Calculate.OnBarClose;
				indexCounter = 0; // start at zero
				// index named variables automatically assign in order
				AddPlot(new Stroke(Brushes.Black), PlotStyle.Hash, "zeroIndex"); // index
				zeroIndex = indexCounter++; // starts with zero; plot idx
				Plots[zeroIndex].Width = 2;
				
				AddPlot(new Stroke(AltColor?Brushes.Blue:Brushes.Purple), PlotStyle.Line, "inventory"); // index
				inventoryIndex = indexCounter++; // starts with zero; plot idx
				Plots[inventoryIndex].Width = 4;
				ninjaPrint2("End Configure state..."+getObjectID(this));
				return; // good practice
				
			}
			else if (State == State.Historical)
			{
				ninjaPrint2("Historical state..."+getObjectID(this));
				try {
				if (ENABLE_VIRTUAL_TIMEBASE && VirtualTimebaseProperty) {
					initOnce();
					isReady = true;
				}
				}
				catch(Exception ex) {
					ninjaPrint2("Exception "+ex.Message+" in State.Historical");
				}
				ninjaPrint2("End Historical state..."+getObjectID(this));
				return; // good practice
			}
			else if (State == State.Transition) {
				ninjaPrint2("Transition state after Historical, before RealTime..."+getObjectID(this));
				return; // good practice
			}
			else if (State == State.DataLoaded) {
				ninjaPrint2("DataLoaded state..."+getObjectID(this));
				return; // good practice
			}
			else if (State == State.Realtime)
			{
				ninjaPrint2("Realtime state..."+getObjectID(this));
				try {
				initOnce();
				isReady = true;
				}
				catch(Exception ex) {
					ninjaPrint2("Exception "+ex.Message+" in State.Realtime");
				}
				ninjaPrint2("End Realtime state..."+getObjectID(this));
				return; // good practice
			}
			else if (State == State.Terminated)
			{
				ninjaPrint2("Terminated state..."+getObjectID(this));
				isReady = false;
				deInit();
				ninjaPrint2("End Terminated state..."+getObjectID(this));
				return; // good practice
			}
		}
		
		private string getObjectID(Object o) {
			return ""+o.GetHashCode();
		}
		
		#region Properties
		[NinjaScriptProperty]
		[Display(Name="FileName.txt", Description="Properties file in default folder", Order=1, GroupName="Parameters")]
		public string FileName
		{ get; set; }

		[NinjaScriptProperty]
		[Display(Name="Alternate colors", Description="blue/orange vs red/green", Order=2, GroupName="Parameters")]
		public bool AltColor
		{ get; set; }
		
		private bool VirtualTimebaseProperty = false;
		
		/* this may come later
		[NinjaScriptProperty]
		[Display(Name="Process Historical?", Description="Use Virtual Timebase from data", Order=3, GroupName="Parameters")]
		public bool VirtualTimebaseProperty
		{ get; set; }
		*/
		#endregion

		private int indexCounter = 0;
		
		private int zeroIndex = 0; // assigned in config
		private int inventoryIndex = 0;
		
		private bool isReady = false;
		
		private bool isInitialized = false;
		
		#region Startup and Initialization
		private void initOnce() {
			if ( isInitialized ) return; // once only
			isInitialized = true;
			customRootPath = Environment.ExpandEnvironmentVariables(
					"%USERPROFILE%\\Documents\\NinjaTrader 8\\bin\\Custom\\");
			myIntegrator = new VolumePriceExpiryIntegrator(this, 1000, retentionSecs);
			// allocate objects
			if (myPropertyReader==null) {
				myPropertyReader = new SimplePropertyReader(this);
				myPropertyReader.setPropertiesFileNameDotTxt(FileName);
				myPropertyReader.setPropertiesSamplingSecs(30); // file sampling frequency
				// now assign default property values
				myPropertyReader.setKeyValue(riskThresholdTicksProp, ""+riskThresholdTicks); // stringify it
				myPropertyReader.setKeyValue(retentionSecsProp, ""+retentionSecs); // stringify it
				myPropertyReader.setKeyValue(multiplierProp, ""+multiplier);
				myPropertyReader.setKeyValue(bigLotMinimumProp, ""+bigLotMinimum);
				myPropertyReader.setKeyValue(taperSizeProp, ""+taperSize);
				myPropertyReader.setKeyValue(superSpikeThresholdRatioProp, ""+superSpikeThresholdRatio);
				myPropertyReader.start();
			}
			onBarUpdateLimiter = new SimpleRateLimiter(this, 20); // milliseconds
			superSpikeReferenceLimiter = new SimpleRateLimiter(this, 10000);
		}
		#endregion
		
		#region Synchronization and PROPERTY Management
		// begin of synchronization support
		// use getUpdateMutex()
        private object updateMutex = new object();
		
		private SimplePropertyReader myPropertyReader = null;

        // asynchronous property updates use this
        // synchronization mechanism
		// in Visual Studio, of course, we'd use
		// interface definitions, and multiple
		// files, but we're staying in a single file
		// for usage within the NinjaScript environment
        protected object getUpdateMutex() {
            return updateMutex; 
        }
		
		// CONTROL VARIABLES MODIFIABLE BY PROPERTY MANAGER
		private static string retentionSecsProp = "RETENTION_SECONDS";
		private int retentionSecs = 60; // default 60 seconds
		private static string multiplierProp = "MULTIPLIER";
		private double multiplier = 1.0; // default unity multiplier
		private static string riskThresholdTicksProp = "RISK_THRESHOLD";
		private double riskThresholdTicks = 3.0; // 
		private static string bigLotMinimumProp = "BIGLOT_MINIMUM";
		private int bigLotMinimum = 2; // default unity multiplier
		private static string taperSizeProp = "TAPER_SIZE";
		private bool taperSize = true; // default useTaper
		private static string superSpikeThresholdRatioProp = "SUPER_SPIKE_THRESHOLD_RATIO";
		private static double superSpikeThresholdRatio = 4.0;
		
		protected void updatePropertiesCallback() {
			if (myPropertyReader==null) return;
			lock(getUpdateMutex()) {
				// a thread which already holds the lock
				// will always get the same lock again....
			// the file-based property manager will
			// firstly lock(getUpdateMutex())
			// and then it will call this routine
			// where your property values can
			// be updated safely
			//	myPropertyReader.setKeyValue(retentionSecsProp, retentionSecs);
			//	myPropertyReader.setKeyValue(multiplierProp, multiplier);
			//	myPropertyReader.setKeyValue(bigLotMinimumProp, bigLotMinimum);
			//if ( !myPropertyReader.hasChanged()) return;	
			try {
				int retentionSecsTmp = myPropertyReader.getIntegerProperty(retentionSecsProp);
				if (retentionSecsTmp<10) retentionSecsTmp = 10; // ten sec min
				retentionSecs = retentionSecsTmp;
				myIntegrator.setRetentionSecs(retentionSecs); // change the Integrator
			} catch (Exception ex) { ninjaPrint2(retentionSecsProp+" error!", true); } // do nothing
			try {
				double multiplierTmp = myPropertyReader.getDoubleProperty(multiplierProp);
				if (multiplierTmp<0.1) multiplierTmp = 0.1;
				multiplier = multiplierTmp;
				// used in OnBarUpdate
			} catch (Exception ex) {ninjaPrint2( multiplierProp+" error!", true);} // do nothing
			try {
				int bigLotMinimumTmp = myPropertyReader.getIntegerProperty(bigLotMinimumProp);
				if (bigLotMinimumTmp<1) bigLotMinimumTmp = 1;
				bigLotMinimum = bigLotMinimumTmp;
				// used when reporting Signed Trade Volumes
			} catch (Exception ex) {ninjaPrint2( bigLotMinimumProp+" error!", true);} // do nothing
			try {
				taperSize = myPropertyReader.getBooleanProperty(taperSizeProp);
				myIntegrator.setUseTaper(taperSize);
			} catch (Exception ex) {ninjaPrint2( taperSizeProp+" error!", true);} // do nothing
			try {
				riskThresholdTicks = myPropertyReader.getDoubleProperty(riskThresholdTicksProp);
				if (riskThresholdTicks<0) riskThresholdTicks = 0; // no negatives
			} catch (Exception ex) {ninjaPrint2( riskThresholdTicksProp+" error!", true);} // do nothing
			try {
				double superSpikeThresholdRatioTmp = myPropertyReader.getDoubleProperty(superSpikeThresholdRatioProp);
				if (superSpikeThresholdRatioTmp<1) superSpikeThresholdRatioTmp = 1;
				superSpikeThresholdRatio = superSpikeThresholdRatioTmp;
				// used when reporting Super Spike Trade Volumes
			} catch (Exception ex) {ninjaPrint2( superSpikeThresholdRatioProp+" error!", true);} // do nothing
				
				
			} // end of any property value updates
		}
		// end of synchronization support
		#endregion

		#region NinjaScript Output Window printing
		private static bool USE_NINJA_PRINT = true; // suppress ALL printing
		
        private void ninjaPrint(string msg) { // primary tab
            if (!USE_NINJA_PRINT) return;
            PrintTo = PrintTo.OutputTab1;
            Print(msg);
        }

		private void ninjaPrint2(string msg) {
			ninjaPrint2(msg, false); // do not force
		}
        
        private void ninjaPrint2(string msg, bool force) { // secondary tab
            if ( !force && !USE_NINJA_PRINT) return;
            PrintTo = PrintTo.OutputTab2;
            Print(msg);
        }
		#endregion
		
		#region The Trade Price Volume (tapering) Integrator
		private VolumePriceExpiryIntegrator myIntegrator = null;
		
		/* This version will enforce a strict moving
		time window, and optionally taper the trailing
		portion of that window gradually to zero to
		avoid artifacts associated with size leaving
		the time window*/
		protected /*inner*/ class VolumePriceExpiryIntegrator {
			private double[] volumeArray = null;
			private double[] priceArray = null;
			private DateTime[] timestampArray = null;
			// these are treated as "circular buffers"
			bool useTaperFlag = false; // TODO
			int size = 0;
			int ptr = 0;
			int _expiryMsecs = 60000;
			TradeFlowRisk parent = null;
			public VolumePriceExpiryIntegrator(TradeFlowRisk parentRef, int sizeArg, int expirySecsArg) { // constructor
				parent = parentRef;
				size = sizeArg;
				_expiryMsecs = 1000 * expirySecsArg;
				volumeArray = new double[size];
				priceArray = new double[size];
				timestampArray = new DateTime[size];
				DateTime now = parent.DateTimeUtcNow();
				for(int i=0; i<size; i++) {
					volumeArray[i] = 0;
					priceArray[i] = 0;
					timestampArray[i] = now;
				}
			}
			
			public void addVolumePriceData(double volumeArg, double priceArg) {
				// RULE: ptr should always index the
				// next valid array index, not the
				// most recent one.  ptr always
				// increments.
				if (ptr>=size) {
					ptr = 0;
				}
				timestampArray[ptr] = parent.DateTimeUtcNow();
				priceArray[ptr] = priceArg; // including price
				volumeArray[ptr++] = volumeArg; // increment after usage
				if (ptr>=size) {
					ptr = 0;
				}
			}
			
			public void setRetentionSecs(int seconds) {
				_expiryMsecs = seconds * 1000;
			}
			
			public void setUseTaper(bool flag) {
				useTaperFlag = flag;
			}
			
			public double getAverageVolumeUnsigned() {
				return getSumVolumeImpl(0, true); // return AVERAGE UNSIGNED VOLUME
			}
			
			public double getAverageVolumeUnsigned(int msecsOverride) {
				return getSumVolumeImpl(msecsOverride, true); // return AVERAGE UNSIGNED VOLUME
			}
			
			public double getAverageVolumeInterval(int ageMsecsOverride) {
				return getSumVolumeImpl(ageMsecsOverride, true); // return AVERAGE UNSIGNED VOLUME
			}
			
			public double getSumVolume() {
				return getSumVolumeImpl(0, false); // not average, SIGNED Integration
			}
			
			private int lastSumVolume = 1;
			
			public int getLastSumVolume() {
				return lastSumVolume;
			}
			
			// this has a dual-purpose, as well as optional retention interval override
			private double getSumVolumeImpl(int ageMsecsOverride, bool returnAvgUnsignedFlag) {
				// must work backwards in time from most
				// recent until items are expired, or we
				// traverse the entire circular ring buffer
				DateTime now = parent.DateTimeUtcNow(); // TODO, use Virtual Timebase; this is REAL TIME ONLY
				double sum = 0;
				int n = 0;
				int tmpPtr = ptr - 1; // most recent
				if (tmpPtr<0) tmpPtr = size - 1;
				else if (tmpPtr>=size) tmpPtr = 0; // array bounds
				int _expiryMsecsLocal = _expiryMsecs;
				if ( ageMsecsOverride > 0) { // substitute the given window time
					_expiryMsecsLocal = ageMsecsOverride;
				}
				int taperBeginAge = (int)(_expiryMsecsLocal * 0.75); // if tapering...
				bool expired = false;
				// always using tmpPtr here !
				while( !expired && tmpPtr!=ptr) {
					DateTime ts = timestampArray[tmpPtr];
					double item = volumeArray[tmpPtr--];
					// REMINDER: we are moving from most recent
					// backward in time here !!
					if (tmpPtr<0) tmpPtr = size - 1; // array bounds
					TimeSpan elapsed = ( now - ts );
					int ageMsecs = (int)elapsed.TotalMilliseconds;
					if ( ageMsecs > _expiryMsecsLocal ) {
						expired = true; continue; // and exit loop
					}
					if ( tmpPtr == ptr ) {
						// we exhaused all of our circular buffer (unlikely)
						expired = true; continue; // and exit loop
					}
					double finalItem = item;
					if (useTaperFlag) { // taper last 25% of time window
						// OK, so we'll taper the oldest 25% of the
						// time window trade volume linearly down to zero
						// e.g. Expiry might be 10000, so begin tapering beyond age 7500
						if (ageMsecs > taperBeginAge) { // e.g. from 7500 msecs into a 10000 max window
							int numerator = _expiryMsecsLocal - ageMsecs; // progressing 2500 toward zero
							if (numerator<0) numerator = 0;
							int denominator = _expiryMsecsLocal - taperBeginAge; // e.g. 10000 - 7500 = 2500
							double ratio = (double)numerator / (double)denominator;
							finalItem *= ratio; // LINEAR TAPER
						}
					}
					sum += (returnAvgUnsignedFlag? Math.Abs(finalItem) : finalItem); 
					// summation of signed (tapered) trade volume OR unsigned Average volume
					n++;
				}
				double returnValue = 0;
				if (returnAvgUnsignedFlag) {
					lastSumVolume = (int)sum; // we won't use this
					returnValue = ( n>0? sum/n : 0 );
				}
				else { // signed summation
					returnValue = (int)sum;
					lastSumVolume = (int)Math.Abs(sum);
				}
				return returnValue;
			}

			public static int VWAP_POSITIVE_VOLUMES = 3; // arbitrary constants
			public static int VWAP_NEGATIVE_VOLUMES = 4;
			public static int VWAP_ALL_VOLUMES = 5;
			
			public double getVwapPrice(int calcMethod) { // throws Exception for ZERO valued prices
				// must work backwards in time from most
				// recent until items are expired, or we
				// traverse the entire circular ring buffer
				DateTime now = parent.DateTimeUtcNow(); // this is REAL TIME ONLY
				double sumV = 0; // SUM( abs(volume) )
				double sumPV = 0; // SUM (price times abs(volume))
				int n = 0;
				int tmpPtr = ptr - 1; // most recent
				if (tmpPtr<0) tmpPtr = size - 1;
				//else if (tmpPtr>=size) tmpPtr = 0; // array bounds
				int taperBeginAge = (int)(_expiryMsecs * 0.75); // if tapering...
				bool expired = false;
				// always using tmpPtr here !
				while( !expired && tmpPtr!=ptr) {
					DateTime ts = timestampArray[tmpPtr];
					double price = priceArray[tmpPtr];
					double volume = volumeArray[tmpPtr--];
					if (tmpPtr<0) tmpPtr = size - 1; // array bounds
					if (price==0) {
						//parent.ninjaPrint2("Price is zero");
						continue;
					}
					if (calcMethod!=VWAP_ALL_VOLUMES) {
						if (calcMethod==VWAP_POSITIVE_VOLUMES) {
							if (volume<0) continue; // ignore NEGATIVE
						}
						else if (calcMethod==VWAP_NEGATIVE_VOLUMES) {
							if (volume>0) continue; // ignore POSITIVE
						}
					}
					volume = Math.Abs(volume); // always Positive weightings
					// REMINDER: we are moving from most recent
					// backward in time here !!
					TimeSpan elapsed = ( now - ts );
					int ageMsecs = (int)elapsed.TotalMilliseconds;
					if ( ageMsecs > _expiryMsecs ) {
						expired = true; continue; // and exit loop
					}
					if ( tmpPtr == ptr ) {
						// we exhaused all of our circular buffer (unlikely)
						expired = true; continue; // and exit loop
					}
					double taperedVolume = volume;
					if (false) { // do not taper weights (useTaperFlag) { // taper last 25% of time window
						// OK, so we'll taper the oldest 25% of the
						// time window trade volume linearly down to zero
						// e.g. Expiry might be 10000, so begin tapering beyond age 7500
						if (ageMsecs > taperBeginAge) { // e.g. from 7500 msecs into a 10000 max window
							int numerator = _expiryMsecs - ageMsecs; // progressing 2500 toward zero
							if (numerator<0) numerator = 0;
							int denominator = _expiryMsecs - taperBeginAge; // e.g. 10000 - 7500 = 2500
							double ratio = (double)numerator / (double)denominator;
							taperedVolume *= ratio; // LINEAR TAPER
						}
					}
					++n;
					sumV += taperedVolume; // summation of signed (tapered) trade volume
					sumPV += ( taperedVolume * price );
				}
				// a zero return PRICE means no data
				double vwap = sumV==0 ? 0 : sumPV / sumV;
				//parent.ninjaPrint2("vwap data items: "+n+" price: "+vwap);
				return vwap;
			}

		}
		#endregion

		#region Calculations and Utilities
        private static double EXP = 0.75; // exponent less than 1 and >0.5

        // non-linear signed size compression
        private static double compressSizeMinOneSigned(double valueArg)
        {
			if ( EXP > 1 ) EXP = 1.0; // no compression at all
			if ( EXP < 0.5 ) EXP = 0.5; // square root
			bool isNegative = ( valueArg < 0 );
            double absValue = Math.Abs(valueArg); // calculate on positive
            if (absValue < 1) absValue = 1; // enforce minimum of size 1 (contract)
            double x = Math.Pow(absValue + 0.0001, EXP); // just adding a smidgeon
            // 1^0 = 1 so 1 should be the minimum possible
            if (x < 1) x = 1;
            return ( isNegative? -x : x); // restore the sign
        }
		#endregion

		#region Shutdown de-Initialization
		
		private void deInit() {
			// must properly De-Allocate ALL user allocated objects
			// at least ones explicitly allocated at the Outer Scope
			// not counting things like Plot objects; and anything
			// which is running a Thread needs to have that stopped
			myIntegrator = null;
			if (myPropertyReader!= null) {
				myPropertyReader.stop(); // kill the thread
				myPropertyReader = null;
			}
			if (onBarUpdateLimiter!=null) {
				onBarUpdateLimiter = null;
			}
			if (superSpikeReferenceLimiter!=null) {
				superSpikeReferenceLimiter = null;
			}
			if (myIntegrator !=null) {
				myIntegrator = null;
			}
			// stop and deallocate objects
		}
		#endregion
		
		#region Time Based Functions
		
		// add the ability to use a Virtual Time Base, rather
		// than "now" or Real Time, so DateTime.UtcNow is
		// not used for a Virtual Timebase; rather the function
		// call DateTimeUtcNow() returning the last Virtual
		// time; which should help the Indicator to respond
		// to Historical Data as well as Real Time
		
		private DateTime lastVirtualNowPrivate = DateTime.MinValue; // not initialized
		private Object timeMutex = new object();
		
		private static bool ENABLE_VIRTUAL_TIMEBASE = false;
		
		private bool isTimeBaseInitialized() {
			lock(timeMutex) {
			if ( !ENABLE_VIRTUAL_TIMEBASE ) return true;
			else return ( lastVirtualNowPrivate != DateTime.MinValue );
			}
			
		}
		
		private void setVirtualTime(DateTime newTime) {
			lock(timeMutex) {
				lastVirtualNowPrivate = newTime;
			}
		}
		
		private DateTime DateTimeUtcNow() {
			lock(timeMutex) {
			if (ENABLE_VIRTUAL_TIMEBASE && VirtualTimebaseProperty) {
				//throw new Exception("Virtual Time Base NOT Implemented yet...");
				if ( !isTimeBaseInitialized() ){
					//throw new Exception("Virtual Time Base No Timestamp yet...");
					return lastVirtualNowPrivate;
				}
				else {
					return lastVirtualNowPrivate;
				}
			}
			else {
				return DateTime.UtcNow;
			}
			}
		}
		
		#endregion
		
		private static bool LOG_RAW_VOLUME = true;
		
		private int lastRawTradeVol = 0;
		
		#region Processing Trade Flow
		private void reportTrade(int tradeRawVol, double price) {
			if (Math.Abs(tradeRawVol) < bigLotMinimum) return; // reject volume
			if ( LOG_RAW_VOLUME ) {
				if (tradeRawVol>0) {
					ninjaPrint("Retail Buy "+tradeRawVol+" @"+price);
				}
				else {
					ninjaPrint("       Retail Sell "+tradeRawVol+" @"+price);
				}
			}
			lastRawTradeVol = tradeRawVol;
			// do not compress volume here, due to Risk Value calculations
			double rawVol = tradeRawVol; //compressSizeMinOneSigned( tradeRawVol );
			// Super Spike Volumes test here
			//if (Math.Abs(compressedVol)>superSpikeThreshold*getLastReferenceVolume()) {
			if (Math.Abs(rawVol)> (superSpikeThresholdRatio*getLastReferenceVolume())) {
				if (rawVol>0) wasSuperSpikeBuy = true;
				else wasSuperSpikeSell = true;
			}
			reportToIntegrator( rawVol, price );
		}
		
		private void reportToIntegrator(double volArg, double priceArg) {
			myIntegrator.addVolumePriceData(volArg, priceArg);
		}
		#endregion
		
		private double lastBidPrice = 0;
		private double lastAskPrice = 0;
		private string customRootPath = null;
		
		private static int REFERENCE_MSECS = 10 * 60 * 1000; // 10 mins in Msecs
		private double lastSuperSpikeReferenceVolume = 50; // start with this pending warm-up
		private SimpleRateLimiter superSpikeReferenceLimiter = null;
		
		public double getSuperSpikeReferenceVolume() {
			if ( !isPrimed() || !superSpikeReferenceLimiter.allowUpdate() ) {
				return lastSuperSpikeReferenceVolume;
			}
			// when primed, the Super Spike reference volume updates
			lastSuperSpikeReferenceVolume = myIntegrator.getAverageVolumeInterval(REFERENCE_MSECS) ;
			// remember the last Volume summation, to use for weighting for Risk Value
			lastIntegratorAvgVolume = myIntegrator.getLastSumVolume();
			return lastSuperSpikeReferenceVolume;
		}
		
		private int lastIntegratorAvgVolume = 1;
		
		private double superSpikeThreshold = 8;
		
		private double lastReferenceVolume = 5; // updated
		private int lastReferenceVolumeCounter = 0;
		
		
		private double getLastReferenceVolume() {
			++lastReferenceVolumeCounter; if (lastReferenceVolumeCounter>10000) lastReferenceVolumeCounter=0;
			if (lastReferenceVolumeCounter%50==0) {
				lastReferenceVolume = myIntegrator.getAverageVolumeUnsigned(REFERENCE_MSECS);
				//ninjaPrint2("lastRefVol: "+lastReferenceVolume, true);
			}
			return lastReferenceVolume;
		}
		
		#region Market Depth Updates (used for timestamp only)
		
		protected sealed override void OnMarketDepth(MarketDepthEventArgs marketDepthUpdate) {
			if (DBG_MARKET_DATA) ninjaPrint2("DBG OnMarketDepth...");
			this.setVirtualTime(marketDepthUpdate.Time); // using timestamps virtual time only
		}
		
		#endregion

		#region OnMarketUpdate callback
		
		/* NinjaScript/Language Reference/Common/OnMarketData()
		If used with TickReplay, please keep in mind Tick Replay ONLY replays 
		the Last market data event, and only stores the best inside bid/ask price 
		at the time of the last trade event.  You can think of this as the 
		equivalent of the bid/ask price at the time a trade was reported.  
		As such, historical bid/ask market data events (i..e, bid/ask volume) 
		DO NOT work with Tick Replay.  To obtain those values, you need to 
		use a historical bid/ask series separately from TickReplay through 
		OnBarUpdate(). More information can be found under Developing for Tick Replay.
		*/
		private static bool DBG_MARKET_DATA = false;
		protected sealed override void OnMarketData(MarketDataEventArgs marketDataUpdate)
		{
			if (DBG_MARKET_DATA) ninjaPrint2("DBG OnMarketData...");
			if ( !isReady ) {
				if (DBG_MARKET_DATA) ninjaPrint2("DBG OnMarketData is not ready...");
				return;
			}
			try {
			lock(getUpdateMutex()) { // wait to allow property updates now
				if (DBG_MARKET_DATA) ninjaPrint2("DBG OnMarketData inside lock...");
				MarketDataType type = marketDataUpdate.MarketDataType;
				this.setVirtualTime(marketDataUpdate.Time);
				if (type==MarketDataType.Last) { // Trade
					if (DBG_MARKET_DATA) ninjaPrint2("DBG OnMarketData Last...");
					if (lastBidPrice==0) {
						if (DBG_MARKET_DATA) ninjaPrint2("DBG OnMarketData No Bid yet...");
						return;
					}
					if (lastAskPrice==0) {
						if (DBG_MARKET_DATA) ninjaPrint2("DBG OnMarketData No Ask yet...");
						return;
					}
					if (lastAskPrice <= lastBidPrice) return;
					double tradePrice = marketDataUpdate.Price; // price of the Trade
					int tradeVolume = (int)marketDataUpdate.Volume;
					lastBidPrice = marketDataUpdate.Bid; // carried with the Last event !
					lastAskPrice = marketDataUpdate.Ask; // carried with the Last event !
					double mid = 0.5 * ( lastAskPrice + lastBidPrice ); // mid price
					if (tradePrice < mid ) {
						// trade toward BID negative Retail Sell
						reportTrade( -tradeVolume, tradePrice );
					}
					else {
						// trade toward ASK/OFFER positive Retail Buy
						reportTrade( tradeVolume, tradePrice );
					}
				}
				else if (type==MarketDataType.Bid) {
					if (DBG_MARKET_DATA) ninjaPrint2("DBG OnMarketData Bid..."+marketDataUpdate.Price);
					lastBidPrice = marketDataUpdate.Price; // BID price
				}
				else if (type==MarketDataType.Ask) {
					if (DBG_MARKET_DATA) ninjaPrint2("DBG OnMarketData Ask..."+marketDataUpdate.Price);
					lastAskPrice = marketDataUpdate.Price; // ASK/OFFER price
				}
			} // end lock
			}
			catch(Exception ex) {
				ninjaPrint2("EXCEPTION OnMarketData..."+ex.Message);
			}
			if (DBG_MARKET_DATA) ninjaPrint2("DBG OnMarketData end...");
			return;
		}
		#endregion
		
		private int primedCounter = 0;
		private bool isPrimedFlag = false;
		
		private bool isPrimed() {
			return isPrimedFlag;
		}
		
		// wait some Bars before some functions active
		// in Real Time, often you want some data before
		// doing some stuff
		private void incrementPrimedCount() {
			if (isPrimed()) return;
			++primedCounter;
			if (primedCounter>20) isPrimedFlag = true;
		}
		
		private double lastInventory = 0;
		
		int sTagCounter = 0;
		int lTagCounter = 0;
		
		private string getShortDotTag() {
			return "SDot"+CurrentBar;
		}
		
		private string getLongDotTag() {
			return "LDot"+CurrentBar;
		}
		
		private string getShortSpikeTag() {
			return "SSpk"+CurrentBar;
		}
		
		private string getLongSpikeTag() {
			return "LSpk"+CurrentBar;
		}
		
		private Brush shortRiskBrushAlt = Brushes.MediumBlue;
		private Brush longRiskBrushAlt = Brushes.DarkOrange;
		private Brush shortRiskBrush = Brushes.Lime;
		private Brush longRiskBrush = Brushes.Red;
		
		private Brush superSpikeSellBrush = Brushes.Fuchsia;
		private Brush superSpikeBuyBrush = Brushes.Green;
		private bool wasSuperSpikeBuy = false;
		private bool wasSuperSpikeSell = false;
		
		private string getLongRiskTextTag() {
			return "LRisk"+CurrentBar;
		}
		
		private string getShortRiskTextTag() {
			return "SRisk"+CurrentBar;
		}
		
		private SimpleRateLimiter onBarUpdateLimiter = null; // init and deInit
		
		private static bool DBG = false;
		protected sealed override void OnBarUpdate()
		{
			if ( !isReady ) return; // must have
			if ( !isTimeBaseInitialized() ) return; // TODO Real Time versus Virtual Time
			bool doUpdate = onBarUpdateLimiter.allowUpdate();
			if (IsFirstTickOfBar) doUpdate = true; // allow on first tick of bar
			if ( !doUpdate ) return; // rate limited
			incrementPrimedCount();
			if ( !isPrimed() ) return;
			// isFirstTickOfBar can be used to clear
			// any inter-bar cumulative variables
			lock(getUpdateMutex()) { // no property updates now
				DateTime now = DateTimeUtcNow();
				Values[zeroIndex][0] = 0;
				double inventory = myIntegrator.getSumVolume();
				lastInventory = inventory;
				double multipliedInventory = inventory * multiplier;
				Values[inventoryIndex][0] = multipliedInventory;
				if (Math.Abs(multipliedInventory) < 30) return; // minimum inventory required !
				// place DOTs against Price indicating possible RISK
				{
					double tickSize = this.TickSize; // price increment of 1 price tick
					double midPrice = 0.5 * (High[0] + Low[0]); //( 0.5 * ( lastBidPrice+lastAskPrice ));
					if (wasSuperSpikeBuy) {
						wasSuperSpikeBuy = false;
						Draw.Dot( this, getShortSpikeTag(), true, 0, (midPrice+2*tickSize), 
									superSpikeBuyBrush);
					}
					if (wasSuperSpikeSell) {
						wasSuperSpikeSell = false;
						Draw.Dot( this, getLongSpikeTag(), true, 0, (midPrice-2*tickSize), 
									superSpikeSellBrush);
					}
					//double riskTicks = 0;
					if (multipliedInventory>0) { // positive inventory "Short Risk"
						double vwapPositive = 
							myIntegrator.getVwapPrice(VolumePriceExpiryIntegrator.VWAP_POSITIVE_VOLUMES);
						if ( vwapPositive != 0 ) { // valid; a zero price is INVALID
							double deltaTicksActual = (midPrice - vwapPositive) / tickSize;
							if (DBG) {
								ninjaPrint2("vwapPositive price: "+vwapPositive);
							}
							if (deltaTicksActual>=riskThresholdTicks) {
								double deltaTicks = deltaTicksActual;
								if (deltaTicks>30) deltaTicks=30; // for display
								double dotY = (midPrice+2*tickSize+(0.1*deltaTicks));
								Draw.Dot( this, getShortDotTag(), true, 0, dotY, 
									AltColor?shortRiskBrushAlt:shortRiskBrush);
								double absDeltaTicks = Math.Abs(deltaTicksActual);
								double pointValue = Instrument.MasterInstrument.PointValue;
								int ticksPerPoint = (int)( 1 / tickSize );
								double deltaPoints = absDeltaTicks * ( tickSize / 1 );
								int riskValue = (int)(deltaPoints * pointValue * lastIntegratorAvgVolume);
								Draw.Text( this, getShortRiskTextTag(),"__"+riskValue, 0, 
									(dotY+2*tickSize));
							}
						}
					}
					else { // negative inventory "Long Risk"
						double vwapNegative = 
							myIntegrator.getVwapPrice(VolumePriceExpiryIntegrator.VWAP_NEGATIVE_VOLUMES);
						if (vwapNegative != 0) { // valid; a zero price is INVALID
							double deltaTicksActual = ( vwapNegative - midPrice ) / tickSize;
							if (DBG) {
								ninjaPrint2("       vwapNegative price: "+vwapNegative);
							}
							if (deltaTicksActual>=riskThresholdTicks) {
								double deltaTicks = deltaTicksActual;
								if (deltaTicks>30) deltaTicks=30; // for display
								double dotY = (midPrice-2*tickSize-(0.1*deltaTicks));
								Draw.Dot( this, getLongDotTag(), true, 0, dotY, 
									AltColor?longRiskBrushAlt:longRiskBrush);
								double absDeltaTicks = Math.Abs(deltaTicksActual);
								double pointValue = Instrument.MasterInstrument.PointValue;
								int ticksPerPoint = (int)( 1 / tickSize );
								double deltaPoints = absDeltaTicks * ( tickSize / 1 );
								int riskValue = (int)(deltaPoints * pointValue * lastIntegratorAvgVolume);
								Draw.Text( this, getLongRiskTextTag(), "__"+riskValue, 0, 
									(dotY-2*tickSize));
							}
						}
					}
				}
			}
		}

		#region periodic SimplePropertyReader
	
	private static bool DBG_KV = false; // debug only
    protected class SimplePropertyReader {

        // USAGE: Create object
        // setPropertiesFileNameDotTxt
        //   use name "." (default directory) \\ double backslash
        //   desiredName.txt
        // if file exists, then 
        //   the given properties will be ignored, and the
        //      properties dictionary contents taken from the file
        //   otherwise, the given properties are written to
        //   a newly created properties file
        // setPropertiesSamplingSecs
        // call start() once only
        // on termination, call stop()
		TradeFlowRisk parent = null;
        public SimplePropertyReader(TradeFlowRisk parentArg) {
			parent = parentArg;
			parent.ninjaPrint2("SimplePropertyReader constructor");
            // client should set all properties, prior to start()
        }

        // please call start only once
        public void start() {
			parent.ninjaPrint2("SimplePropertyReader start()");
            ThreadStart ts = new ThreadStart(propertiesReaderLoop);
            Thread runner = new Thread(ts);
            isRunning = true;
            runner.Start();
        }

        private string fileName = "myProperties.txt";

        // if used, must do this BEFORE start()
        public void setPropertiesFileNameDotTxt(string fileNameArg) {
            fileName = fileNameArg;
        }

        // if used, do this BEFORE calling start();
        public void setPropertiesSamplingSecs(int secondsArg) {
            propertiesSampleDelayMsecs = secondsArg * 1000;
        }

        private bool isRunning = false;
        private int propertiesSampleDelayMsecs = 30000;

        public void stop() {
            isRunning = false;
            notifyUpdate(); // break thread wait; and exit
        }
        private object threadWaitObject = new object();
        private bool isNotifiedLocked = false;
        public void notifyUpdate() {
            lock(threadWaitObject) {
                    isNotifiedLocked = true;
                    Monitor.Pulse(threadWaitObject);
            }
        }

        private void propertiesReaderLoop() {
            try {
				parent.ninjaPrint("propertiesReaderLoop started");
                while (isRunning) {
                    try {
                        lock (threadWaitObject) {
                            if (!isNotifiedLocked) {
                                Monitor.Wait(threadWaitObject, propertiesSampleDelayMsecs, true); // every XX secs or sooner
                                isNotifiedLocked = false;
                            }
                            if (!isRunning) continue; // thread will stop
                                                      // read property file
                            if ( !updateFromPropertiesFile()) continue; // error in file or first time
                            lock(parent.getUpdateMutex()) {
                                  parent.updatePropertiesCallback();
                            }
                        }
                    }
                    catch(Exception ex) {

                    }
                }
            }
            catch(Exception ex) {
                // nothing
            }
        }

        // client must set ALL properties and values BEFORE start()
        public void setKeyValue(string keyArg, string value) {
            lock (propertiesDictionary) {
                string key = keyArg.ToUpper().Trim();
                string strValue = "" + value;
                if (propertiesDictionary.ContainsKey(key)) {
                    propertiesDictionary[key] = strValue;
                } else {
                    propertiesDictionary.Add(key, strValue);
                }
            }
        }

        private Dictionary<string, string> propertiesDictionary = new Dictionary<string, string>();

        public int getIntegerProperty( string key) {
            return (int)getDoubleProperty(key);
        }

        public double getDoubleProperty(string keyArg) {
            string key = keyArg.ToUpper().Trim();
			double retDbl = 0;
            lock (propertiesDictionary) {
                if (propertiesDictionary.ContainsKey(key)) {
					try {
						retDbl = Double.Parse(propertiesDictionary[key]);
					} catch(Exception ex) { throw; } // propogate exception to caller
				}
            }
			return retDbl;
        }

        public bool getBooleanProperty(string keyArg) {
            string key = keyArg.ToUpper().Trim();
            bool retBool = false;
            lock (propertiesDictionary) {
                if (propertiesDictionary.ContainsKey(key)) {
                    try {
                        retBool = Boolean.Parse(propertiesDictionary[key].Trim().ToLower());
                    } catch (Exception ex) { throw; } // propogate exception to caller
                }
            }
            return retBool;
        }

        private bool updateFromPropertiesFile() {
			parent.ninjaPrint("updateFromPropertiesFile called... ");
            // if the properties file doesn't exist, then we create one
            // and populate with the provided properties in form xxx=yyy
			bool successful = true;
            string fullFilePath = parent.customRootPath + fileName;
            if ( !File.Exists(fullFilePath)) { // default dir; backslash is special escape char
				try {
                // write a new one
				//parent.ninjaPrint("updateFromPropertiesFile new file "+fullFilePath);
                using (StreamWriter outputFile = new StreamWriter(fullFilePath)) {
                    outputFile.WriteLine("# comment line");
                    lock (propertiesDictionary) {
                        foreach (var item in propertiesDictionary) {
                            string key = item.Key;
                            string value = item.Value;
                            outputFile.WriteLine(key + "=" + value);
                        }
                        outputFile.Close();
                    }
                }
				} catch(Exception ex) { successful = false;
					parent.ninjaPrint("Exception creating file: "+ex.Message);}
                return successful;
            }
            // existing file, need to re-read and update our properties dictionary
            if (File.Exists(fullFilePath)) { // should definitely exist !!!
                                              // write a new one
				try {
                using (StreamReader inputFile = new StreamReader(fullFilePath)) {
                    lock (propertiesDictionary) {
                        //propertiesDictionary.Clear();
                        string lineRaw = " ";
                        while ((lineRaw = inputFile.ReadLine()) != null) {
							if (lineRaw==null) continue;
							string trimmedLine = lineRaw.Trim();
                            if (trimmedLine.Length == 0) continue;
							if (trimmedLine.StartsWith("#")) continue; // skip comment
                            if (!trimmedLine.Contains("=")) continue;
                            string[] splitString = trimmedLine.Split('='); // no equals character
							if ( splitString.Length != 2) continue; // must be key=value
                            string key = splitString[0].ToUpper().Trim();
                            string value = splitString[1].ToLower().Trim(); // e.g. FALSE to false
							if (DBG_KV) {
								parent.ninjaPrint("Key: "+key+" value: "+value);
							}
							if ( !propertiesDictionary.ContainsKey(key) ) {
                            	propertiesDictionary.Add(key, value);
							}
							else {
								propertiesDictionary[key] = value;
							}
                        }
                        // handled by the "using" logic... inputFile.Close();
                    }
                } // end of the "using" block
				} catch(Exception ex2) { successful = false; 
					parent.ninjaPrint("Exception: "+ex2.Message);}
            }
			return successful;
        }
    } // end class

		#endregion

	#region Simple Rate Limiter
    class SimpleRateLimiter {

        protected static Random randomizer = new Random();

        int msecs = 10; // default

        bool useRandom = false; // slightly randomize interval which
        // would untentionally de-synchronize any large collection of RateLimiters

        public void setUseRandom(bool flag) {
            useRandom = flag;
            if (!useRandom) deltaMsecsRandom = 0;
        }

        private DateTime lastUpdate = DateTime.MinValue;
		TradeFlowRisk parent = null;

        public SimpleRateLimiter(TradeFlowRisk parentRef, int msecsArg) {
			parent = parentRef;
            msecs = msecsArg;
        }

        // randomly return -1..+1
        protected static int nextDelta() {
            lock (randomizer) {
                double ran = randomizer.NextDouble();
                if (ran < 0.3333) {
                    return -1;
                } else
                if (ran < 0.6666) {
                    return 0;
                }
                return 1;
            }
        }

        public void setMsecsDelay(int msecsArg) {
            int _msecs = msecsArg;
            if (_msecs < 1) _msecs = 1;
            msecs = _msecs;
        }

        private int deltaMsecsRandom = 0;

        public bool allowUpdate() {
			if ( !parent.isTimeBaseInitialized()) return false;
            DateTime now = parent.DateTimeUtcNow();
            TimeSpan elapsed = (now - lastUpdate);
            if (elapsed.TotalMilliseconds < msecs + deltaMsecsRandom) return false;
            lastUpdate = now;
            if (useRandom) deltaMsecsRandom = nextDelta();
            return true;
        }

    }

	#endregion
	
	}
}

#region NinjaScript generated code. Neither change nor remove.

namespace NinjaTrader.NinjaScript.Indicators
{
	public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase
	{
		private TradeFlowRisk[] cacheTradeFlowRisk;
		public TradeFlowRisk TradeFlowRisk(string fileName, bool altColor)
		{
			return TradeFlowRisk(Input, fileName, altColor);
		}

		public TradeFlowRisk TradeFlowRisk(ISeries<double> input, string fileName, bool altColor)
		{
			if (cacheTradeFlowRisk != null)
				for (int idx = 0; idx < cacheTradeFlowRisk.Length; idx++)
					if (cacheTradeFlowRisk[idx] != null && cacheTradeFlowRisk[idx].FileName == fileName && cacheTradeFlowRisk[idx].AltColor == altColor && cacheTradeFlowRisk[idx].EqualsInput(input))
						return cacheTradeFlowRisk[idx];
			return CacheIndicator<TradeFlowRisk>(new TradeFlowRisk(){ FileName = fileName, AltColor = altColor }, input, ref cacheTradeFlowRisk);
		}
	}
}

namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns
{
	public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase
	{
		public Indicators.TradeFlowRisk TradeFlowRisk(string fileName, bool altColor)
		{
			return indicator.TradeFlowRisk(Input, fileName, altColor);
		}

		public Indicators.TradeFlowRisk TradeFlowRisk(ISeries<double> input , string fileName, bool altColor)
		{
			return indicator.TradeFlowRisk(input, fileName, altColor);
		}
	}
}

namespace NinjaTrader.NinjaScript.Strategies
{
	public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase
	{
		public Indicators.TradeFlowRisk TradeFlowRisk(string fileName, bool altColor)
		{
			return indicator.TradeFlowRisk(Input, fileName, altColor);
		}

		public Indicators.TradeFlowRisk TradeFlowRisk(ISeries<double> input , string fileName, bool altColor)
		{
			return indicator.TradeFlowRisk(input, fileName, altColor);
		}
	}
}

#endregion

Thanks for your interest; and this is about as far as I'll be pushing
this Indicator. It's up to you to customize anything you want;
and I'm not at all sure that I'll support Backfill of Historical data...

[EDIT] So I've attached a crude screenshot during horrible market
conditions; showing the output of the indicator; sorry if numbers write
over the tops of themselves; either Live with it, or change the Code LOL

[edit] Params were:
# comment line an NQ chart
RISK_THRESHOLD=8
RETENTION_SECONDS=180
MULTIPLIER=3.5
BIGLOT_MINIMUM=2
TAPER_SIZE=True
SUPER_SPIKE_THRESHOLD_RATIO=6


hyperscalper

Attached Thumbnails
Click image for larger version

Name:	Trade-flow-with-Risk.PNG
Views:	172
Size:	174.7 KB
ID:	319292  
Started this thread Reply With Quote
Thanked by:
  #116 (permalink)
 hyperscalper 
boise idaho
 
Experience: Advanced
Platform: NinjaTrader C# Custom
Broker: NinjaTrader LeeLoo Rithmic
Trading: Nasdaq Futures NQ/MNQ
Posts: 314 since Apr 2020
Thanks Given: 15
Thanks Received: 522

IS THIS INDICATOR IMPORTANT AND USEFUL?
HOW CAN IT BE USED?
AND HOW MIGHT IT BE IMPROVED?

So I'm suggesting that these calculations of Market Maker
relative Inventory estimates, against the Retail Population
through Time and Sales Analysis is quite useful and
important.

It's one of those things which might "grow on you" the
more you use it.

I'D SUGGEST RUNNING 2 INSTANCES AT DIFFERENT
TIMEFRAMES, even though at first that might be a bit
confusing.

BUT WHAT'S MORE IMPORTANT is that you try and run
this with timeframes (retention intervals) which more or
less "match up" with your desired Trading period.

If you're looking for lots of quick hits, during congestion;
then maybe you use 3 minutes of retention; but if you're
looking for half an hour, then consider 30 minutes retention.

REMEMBER: ALL PURELY "INTERNAL" TECHNICAL ANALYSES
like this, can be "swamped" or "overpowered" by "EXTERNAL"
factors. If a Market is just determined to RISE, then you
may find that it can seem to "push right through" the Resistance
you would expect from Short Risk. She's got "deep pockets",
your enemy, the Market Maker....

IF WE WERE TO DIG MUCH DEEPER INTO INVENTORY ANALYSIS,
which I'm not gonna do, most likely...

...then we would be able to see how Market Maker "modulates"
her Risk, and be able to calculate an estimate at least, of her
"Profit per Minute" which offsets her Open Risk on a Timeframe.

MARKETS ARE "FRACTAL" and so Inventory Analysis really needs
to be "multi timeframe". I've done all this; but we're not gonna
do it for the forum audience.

IN CASES WHERE MARKET MAKER APPARENTLY keeps pushing
through "ridiculous" levels of Risk, seemingly irrationally...
consider that the Risks and (temporary) Losses incurred by
Market Maker can be recouped LATER, and she's not always
in a big hurry to turn the market; but she will always profit
from her inventory sooner or later...

TYPICALLY, a session will end at the TOP or BOTTOM of a range;
but then in the "after market" under low trade volumes, the
Prices will be corrected; thus taking MM out of her Risk, and
comfortably into Profit.

THE MORE YOU THINK ABOUT THE PROCESSES which are likely
to be going on; the better you'll have a feeling for Market Dynamics
and that's a valuable concept to begin to understand.

BASICALLY, and you can argue with me all you want, but
Market Maker is the Mover of Prices to maximize her own Profit.
And you, as the Retail player, are doomed to Follow (or predict)
where she's going...

WATCH THE MOVIE BLADE RUNNER, where Deckard is forced
to come out of retirement where Bryant threatens him, saying:
"You know the score, if you're not Cop, you're Little People", or
something like that. Well, Market Maker controls everything;
and any ideas that Retail players are "in control" is fiction.

We can discuss these concepts; which seem simple, but the
more you use them, as part of your arsenal, the better your
trading may become.

YES, THIS INDICATOR COULD BE IMPROVED, but the level of
computing to do so, is way beyond anything postable in this
forum. But remember that you and Market Maker are
SIMILAR IN ONE RESPECT ONLY: Both you and MM must Buy
low(er) and Sell high(er); since that's the name of the game.
Understanding how Market Maker does that, helps a bit in
understanding the awesome power the Trader faces.

hyperscalper

Started this thread Reply With Quote
  #117 (permalink)
 
trendisyourfriend's Avatar
 trendisyourfriend 
Quebec Canada
Market Wizard
 
Experience: Intermediate
Platform: NinjaTrader
Broker: AMP/CQG
Trading: ES, NQ, YM
Frequency: Daily
Duration: Minutes
Posts: 4,527 since Oct 2009
Thanks Given: 4,175
Thanks Received: 6,020


hyperscalper View Post
IS THIS INDICATOR IMPORTANT AND USEFUL?
HOW CAN IT BE USED?
AND HOW MIGHT IT BE IMPROVED?
...

Hi @hyperscalper

I have been following your work with interest as i look at volume quite a bit but give equal weight to structure in my analysis. To me, the interaction of volume at key areas is what dictates if a trade is worth taking. In your examples, i only see dots which i suppose represent relatively big trades. You have circled a few areas, i suppose those are areas you would consider to trade, right? What does the purple line indicate in your examples? Could you post some of your trades on any particular day to see how you trade with this tool?

Reply With Quote
  #118 (permalink)
 hyperscalper 
boise idaho
 
Experience: Advanced
Platform: NinjaTrader C# Custom
Broker: NinjaTrader LeeLoo Rithmic
Trading: Nasdaq Futures NQ/MNQ
Posts: 314 since Apr 2020
Thanks Given: 15
Thanks Received: 522


trendisyourfriend View Post
Hi @hyperscalper

I have been following your work with interest as i look at volume quite a bit but give equal weight to structure in my analysis. To me, the interaction of volume at key areas is what dictates if a trade is worth taking. In your examples, i only see dots which i suppose represent relatively big trades. You have circled a few areas, i suppose those are areas you would consider to trade, right? What does the purple line indicate in your examples? Could you post some of your trades on any particular day to see how you trade with this tool?

Well, first of all... The Indicator tries to do several things
at once; and arguably, maybe the visual representation needs
some tweaking, but here are the things it represents:

1) Over the retention interval, the Signed Volumes (Sell is
minus, Buy is plus) are summed up. This is what some people,
including me, call the "on balance volume" or the "net volume"
within the retention interval. Often, as we approach pivot
trend-change price points, the bias toward Larger trades
tends to "accelerate", plus perhaps also there are just more
trades occurring near these tops and bottoms, often called
"the battle between Buyers and Sellers".

2) When the Volume Weighted Average price of the inventory
collection (within the moving retention window) represents
a) "short" inventory; and the actual Market Price is HIGHER
than the VWAP, then Market Maker is (temporarily only)
"underwater" or losing money, or "taking Risk", which we
call "Short Risk" then Dots begin to appear to represent the
distance between these two prices (in Ticks). In that case,
there is a predominance of Retail Buyers, against which
Market Maker sells. When prices fall, typically (not always)
most Retail players are Sellers, so Market Maker has an
imbalance or excess of Buying from Retail Sellers, so then
the Market price is actually below the VWAP Buying price
of the collection Market Maker is holding. Call that "Long risk".

3) Then if we know how much Net Inventory (futures contracts)
is being held (or an estimate); and we know the Tick separation
into Risk; then by multiplying that by the Tick (or Point) value
of the Instrument; we get a number proportional to, or
estimating, the Total Amount of Inventory Risk, expressed
in $$Dollars.

4) Also, when we see Trade sizes which are X times the
average trade size, we call them "Super Spikes or Trades"
and we show them overlain on the Price itself, and we know
that Larger individual Trade sizes tend to mark Tops and
Bottoms as well.

SO ALL THESE CONCEPTS are (maybe poorly?) represented
by this Indicator.

THE PURPLE LINE represents the Net Inventory Imbalance, in
Contracts, when Buy versus Sell transactions (from Time and
Sales) is summed up.

( Parenthetically, the Integrator which does this summation,
I think, "tapers" the Final 25% of the data in the Retention
Interval linearly down toward zero, so that Volume leaving
the Retention Window "tapers" and does not make the Net
calculations "jump" as much as that Volume expires. )

WHEW !! It does a lot of stuff. However, it does get at the
Heart of Trading, since it analyzes Time and Sales, Buyers
versus Sellers ( Retail guys versus Market Maker ), and
evaluates whether MM's "position" is "in the money" or
"out of the money" (i.e. "at Risk"). Of course Market Maker
not only a) deliberately moves into risk territory, as a
natural and expected consequence of "luring" Retail players
into the Market; but b) She is not really taking any "Risk"
at all, since She (i.e. MM) knows full well that She'll just
reverse the Price Trend and will no longer be in "Risk"..

Think about your own trading. If you are Long, and the Price
is above your Buying Price (your VWAP) then you are in
potential Profit, and are feeling OK. But when the market
Price drops below your Buying price, then you start to
panic (maybe) since you're losing money or are "at Risk"
of losing an indeterminate amount of money, and might
be worried about stopping out. (But YOU are not Market
Maker, and She doesn't even break a sweat about that...)

THERE'S A LOT TO CONSIDER IN THESE DYNAMICS...

I'm not that likely to start posting trades; since I've said
elsewhere that I might do many dozens of individual trades
within what I keep calling the "Meta Trade", and because
of that, the usual posting of Entry price versus Exit price
because mind bogglingly difficult to follow... Yes, that is
a different way of thinking about the concept of "a Trade"

Hope that helps !!

hyperscalper

Started this thread Reply With Quote
  #119 (permalink)
 hyperscalper 
boise idaho
 
Experience: Advanced
Platform: NinjaTrader C# Custom
Broker: NinjaTrader LeeLoo Rithmic
Trading: Nasdaq Futures NQ/MNQ
Posts: 314 since Apr 2020
Thanks Given: 15
Thanks Received: 522

HOW DOES THE INDICATOR DIFFER FROM A
"REAL" INVENTORY ANALYSIS ??

I guess at least some of you might be interested in
that. So let me try to take a stab at explaining
without going on too long and detailed.

As you all know, the Market Maker "makes the Spread"
which means that Retail players (by definition) in this
way of thinking, Buy the Ask price; and Sell the Bid
price. This is because most (not all) Retail players
are not able to take advantage of Limit Orders, and
typically, for simplicity, use Market Orders to get into
and out of the market. (Yes, I know...)

But Market Maker's unique feature is that She NEVER
Buys the Asking price (like Retail players mostly do);
but, instead, she always Buys the Bid. And also She
is able to Sell the Ask, unlike the bulk of Retail players.

(I have a semi-automated custom Order Processing
console I wrote, so I am able to Buy the Bid, and
Sell the Ask. Now that does give me significant
advantage over "paying the spread"; and yet that
does not qualify me as getting the Price advantage
which Market Maker enjoys. It helps me a lot,
to resolve small price movements; but Market Maker
does things on a much more awesome scale, and
"crowds out" the competition on the Depth of Market
early on, so She gets the highest Price advantage.)

This means She makes the spread. Let's say we're
trading the Nasdaq contract; and the Bid/Ask spread
is 3 ticks (which happens a lot). Market maker Buys at
Bid 1 contract, say 15966.00 from Sally the Retail Seller; and
virtually simultaneously, Sells 1 contract at ASK/Offer
price 15966.75 from Billy the Retail Buyer.

(Sally and Billy don't know each other; they are just a
part of the vast random Retail trader population.)

Note that nobody cares whether Sally is Selling to
close an open Long position she has, or to open
a new Short position. Same for Billy... Market Maker
DOESN'T CARE.... OF COURSE.

Well, well... There's a case where NQ's value is $20
per Point, but there are 4 0.25 Ticks within each Point,
so the value of Market Maker's "spread profit" is $15 !!!
That's not so much; but multiply it by the thousands
and this "making the spread" thing suddenly looks like
a pretty good Income !! LOL

The 1 contract Inventory Bought and Sold here simply
disappears from Total Inventory, as an "in pocket" profitable
transaction (making the spread) by Market Maker, and
so it would DISAPPEAR from Open Inventory in a REAL
INVENTORY ANALYSIS.

So in REAL complex Inventory Analysis; any Profitable
Transaction Volume by Market Maker is taken as Profit,
and IS TAKEN OUT of the Collection of Transactions
during the Inventory Evaluation snapshot.

So we take out all CLOSED PROFITABLE transactions
out of our pending Inventory collection; and what is
left is (theoretically) only OPEN transactions where
Market Maker has Bought Volume, but has not yet been
able to SELL THAT VOLUME AT A HIGHER PRICE.
Or has Sold Volume, but has not yet been able to
"cover it" at a lower price...

OK, I've done all this stuff and you can see how this would
form a more "accurate" representation of CLOSED VERSUS
OPEN transactions in the Evaluation of Inventory.

It also gives us a good estimate of "The Rate of Income"
which Market Maker makes "across the spread" mostly...
which Offsets Open Inventory..... this gets into more
advanced Inventory Analysis.

THEN ADD TO THAT ONE MORE FACTOR. Which is that
we need to be able to make these calculations over a
range of Retention Intervals, Simultaneously... and that
would get us nearer to a Real Inventory Analysis.
(this addresses the observation that Markets are "fractal"
and are executing these "strategies" across multiple
timeframes simultaneously.)

hyperscalper

Started this thread Reply With Quote
Thanked by:
  #120 (permalink)
 
trendisyourfriend's Avatar
 trendisyourfriend 
Quebec Canada
Market Wizard
 
Experience: Intermediate
Platform: NinjaTrader
Broker: AMP/CQG
Trading: ES, NQ, YM
Frequency: Daily
Duration: Minutes
Posts: 4,527 since Oct 2009
Thanks Given: 4,175
Thanks Received: 6,020



hyperscalper View Post
...

I'm not that likely to start posting trades; since I've said
elsewhere that I might do many dozens of individual trades
within what I keep calling the "Meta Trade", and because
of that, the usual posting of Entry price versus Exit price
because mind bogglingly difficult to follow... Yes, that is
a different way of thinking about the concept of "a Trade"

Thanks for the detailed explanation. The idea of seeing an application of your concepts using your tool is to better understand how it can be used. It is like trying to play music, we can talk about the circle of fifths and how to use it in songwriting for hours but unless i see an application of it which i can HEAR then i am afraid it might make the situation more confusing. You could take any day and point where you think MM are moderately at risk here but here they are in a severe situation where considering a trade might offer a low risk for me with a good potential reward. You don't need to show your trades but just pointing where your tool is shinning so to speak.

Reply With Quote
Thanked by:




Last Updated on January 26, 2023


© 2024 NexusFi™, s.a., All Rights Reserved.
Av Ricardo J. Alfaro, Century Tower, Panama City, Panama, Ph: +507 833-9432 (Panama and Intl), +1 888-312-3001 (USA and Canada)
All information is for educational use only and is not investment advice. There is a substantial risk of loss in trading commodity futures, stocks, options and foreign exchange products. Past performance is not indicative of future results.
About Us - Contact Us - Site Rules, Acceptable Use, and Terms and Conditions - Privacy Policy - Downloads - Top
no new posts