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,448 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

  #71 (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


lolu View Post
@hyperscalper,

I got AutoHotkey.dll but I'm still unable to find AutoHotkey.Interop.dll.

What about my question on AutoDragLeft.AHK above ?

Lolu

https://github.com/amazing-andrew/AutoHotkey.Interop

Like I said, the AHK Community is pretty big.

The script I posted, but I think ultimately I might not have used it; but
used Win32 calls instead; I can't remember, but you'll quickly find all
about the AHK Community if you just poke around on the InterWebs LOL

hyperscalper

Started this thread Reply With Quote

Can you help answer these questions
from other members on NexusFi?
Better Renko Gaps
The Elite Circle
MC PL editor upgrade
MultiCharts
What broker to use for trading palladium futures
Commodities
Trade idea based off three indicators.
Traders Hideout
How to apply profiles
Traders Hideout
 
  #72 (permalink)
 
lolu's Avatar
 lolu 
Lagos, Nigeria
Market Wizard
 
Experience: Intermediate
Platform: NinjaTrader, SierraChart
Trading: Euro Currency & Oil
Frequency: Daily
Duration: Hours
Posts: 2,552 since Jun 2009
Thanks Given: 1,049
Thanks Received: 1,678


hyperscalper View Post
https://github.com/amazing-andrew/AutoHotkey.Interop

Like I said, the AHK Community is pretty big.

The script I posted, but I think ultimately I might not have used it; but
used Win32 calls instead; I can't remember, but you'll quickly find all
about the AHK Community if you just poke around on the InterWebs LOL

hyperscalper

@hyperscalper,

Yes, I got the AutoHotkey.dll from the github link ... the link also has some stuff on AutoHotkey.Interop but no AutoHotkey.Interop.dll to download.

Meanwhile the compilation errors from the code --> AutoDragLeftAHK.cs are as shown below;

The type or namespace name 'AutoHotkey' could not be found (are you missing a using directive or an assembly reference?) CS0246 --->Line 27

The type or namespace name 'AutoHotkeyEngine' could not be found (are you missing a using directive or an assembly reference?) CS0246 --->Line 122

Line 7 is ... using AutoHotKey.Interop;

Line 122 is ... private AutoHotkeyEngine ahk = null; // in init() AutoHotkeyEngine.Instance;

Lolu

Visit my NexusFi Trade Journal Reply With Quote
  #73 (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


AUTOHOTKEY SETUP

Attached is the file I have; not sure what it offers...
OK, the .exe is a setup for STANDALONE AutoHotKey facility.
It doesn't have an Interop file...

When I find that, I'll edit this post.

There's a forum; and this link will dump you into it:
https://www.autohotkey.com/boards/viewtopic.php?style=19&f=67&t=28342&p=132999

I think the code is available here, maybe
https://github.com/amazing-andrew/AutoHotkey.Interop

That's about all the help I can offer; but that may provide the assembly
you need to reference.

Looks like a Visual Studio project to generate the required runtime file...
or "assembly" whatever it's called
Enjoy !

hyperscalper

Attached Files
Elite Membership required to download: AutoHotkey_1.1.32.00_setup.zip
Elite Membership required to download: AutoHotkey.Interop-master.zip
Started this thread Reply With Quote
Thanked by:
  #74 (permalink)
 SpeculatorSeth   is a Vendor
 
Posts: 780 since Apr 2016
Thanks Given: 22
Thanks Received: 1,018

So I should note that I also looked at calculating market maker risk and positioning. I spent several months testing different strategies. I was unable to find a solid edge from just the cross of the lines or any kind of bet on it coming back to the lines. Surprising since it seems to predict where they'll find balance quite well. I believe the problem is that as with everything else there's a fat tailed distribution to it. So for any system like this the real key seems to be coming up with an effective measure predicting informed trading. Something that humans seem to do ok, but writing an algorithm for seems to be quite difficult.

- SpeculatorSeth
Reply With Quote
Thanked by:
  #75 (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

*******************************
PHASE TWO OF TRADE FLOW ANALYSIS, introducing the concept
of RISK
*******************************

DO NOT CUT N PASTE FROM THIS VERSION INTO ANOTHER
VERSION, unless YOU know what you're doing.

An Indicator of any complexity is a Delicate instrument; and this
one begins to ratchet up the Concepts of what most would
call "Order Flow", but which is strictly speaking "Trade Flow"
as it derives from TIME AND SALES DATA.

I WILL POST THIS CODE, followed by a discussion of what it's
doing, and a bit about WHY we are introducing the Concept
of Market Maker's "Risk" as a potential simple indicator of
when a Micro Trend is likely to Change Trend Direction...

[EDIT] LET ME EMPHASIZE, this is by no means anywhere close to
what I'd call "Full Inventory Analysis" and the fact that it yields any
helpful clues at all, would be an unexpected Success !!! We need
to be modest and objective, since no Single Indicator is likely to
tell the whole story. Markets are DYNAMIC, so "Get used to it!" LOL

ONE OF MY GOALS HERE is to "turn you on" and to get you "Thinking
Outside of the Box" where most Traders are stuck. You are able to
empower yourself, if you gain the skills to do this sort of thing; and
it will at the very least make things very Interesting along the way...

 
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";
				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					= "uniqueTradeFlowProps.txt";
			}
			else if (State == State.Configure)
			{
				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(Brushes.Purple), PlotStyle.Line, "inventory"); // index
				inventoryIndex = indexCounter++; // starts with zero; plot idx
				Plots[inventoryIndex].Width = 4;
				
			}
			else if (State == State.Realtime)
			{
				init();
				isReady = true;
			}
			else if (State == State.Terminated)
			{
				isReady = false;
				deInit();
			}
		}
		
		#region Properties
		[NinjaScriptProperty]
		[Display(Name="FileName.txt", Description="Properties file in default folder", Order=1, GroupName="Parameters")]
		public string FileName
		{ get; set; }
		#endregion

		private int indexCounter = 0;
		
		private int zeroIndex = 0; // assigned in config
		private int inventoryIndex = 0;
		
		private bool isReady = false;
		
		#region Startup and Initialization
		private void init() {
			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(retentionSecsProp, ""+retentionSecs); // stringify it
				myPropertyReader.setKeyValue(multiplierProp, ""+multiplier);
				myPropertyReader.setKeyValue(bigLotMinimumProp, ""+bigLotMinimum);
				myPropertyReader.setKeyValue(taperSizeProp, ""+taperSize);
				myPropertyReader.start();
			}
			onBarUpdateLimiter = new SimpleRateLimiter(20); // milliseconds
			superSpikeReferenceLimiter = new SimpleRateLimiter(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 bigLotMinimumProp = "BIGLOT_MINIMUM";
		private int bigLotMinimum = 2; // default unity multiplier
		private static string taperSizeProp = "TAPER_SIZE";
		private bool taperSize = true; // default useTaper
		
		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) {} // do nothing
			try {
				double multiplierTmp = myPropertyReader.getDoubleProperty(multiplierProp);
				if (multiplierTmp<0.1) multiplierTmp = 0.1;
				multiplier = multiplierTmp;
				// used in OnBarUpdate
			} catch (Exception ex) {} // do nothing
			try {
				int bigLotMinimumTmp = myPropertyReader.getIntegerProperty(bigLotMinimumProp);
				if (bigLotMinimumTmp<1) bigLotMinimumTmp = 1;
				bigLotMinimum = bigLotMinimumTmp;
				// used when reporting Signed Trade Volumes
			} catch (Exception ex) {} // do nothing
			try {
				taperSize = myPropertyReader.getBooleanProperty(taperSizeProp);
				myIntegrator.setUseTaper(taperSize);
			} catch (Exception ex) {} // 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) { // secondary tab
            if (!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 = DateTime.UtcNow;
				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] = DateTime.UtcNow;
				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 getAverageVolumeInterval(int ageMsecsOverride) {
				return getSumVolumeImpl(ageMsecsOverride, true); // return AVERAGE UNSIGNED VOLUME
			}
			
			public double getSumVolume() {
				return getSumVolumeImpl(0, false); // not average, SIGNED Integration
			}
			
			// 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 = DateTime.UtcNow; // 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) {
					returnValue = ( n>0? sum/n : 0 );
				}
				else { // signed summation
					returnValue = 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 = DateTime.UtcNow; // 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() {
			myIntegrator = null;
			if (myPropertyReader!= null) {
				myPropertyReader.stop(); // kill the thread
				myPropertyReader = null;
			}
			onBarUpdateLimiter = null;
			superSpikeReferenceLimiter = null;
			// stop and deallocate objects
		}
		#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;
			double compressedVol = compressSizeMinOneSigned( tradeRawVol );
			reportToIntegrator( compressedVol, price );
		}
		
		private void reportToIntegrator(double compressedVolArg, double priceArg) {
			myIntegrator.addVolumePriceData(compressedVolArg, priceArg);
		}
		#endregion
		
		private double lastBidPrice = 0;
		private double lastAskPrice = 0;
		private string customRootPath = null;
		
		private static double SUPER_SPIKE_RATIO = 10.0;
		private static int REFERENCE_MSECS = 5 * 60 * 1000; // 5 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) ;
			return lastSuperSpikeReferenceVolume;
		}

		#region OnMarketUpdate callback
		protected sealed override void OnMarketData(MarketDataEventArgs marketDataUpdate)
		{
			if ( !isReady ) return;
			lock(getUpdateMutex()) { // wait to allow property updates now
				MarketDataType type = marketDataUpdate.MarketDataType;
				if (type==MarketDataType.Last) { // Trade
					if (lastBidPrice==0) return;
					if (lastAskPrice==0) return;
					if (lastAskPrice <= lastBidPrice) return;
					double tradePrice = marketDataUpdate.Price; // price of the Trade
					int tradeVolume = (int)marketDataUpdate.Volume;
					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) {
					lastBidPrice = marketDataUpdate.Price; // BID price
				}
				else if (type==MarketDataType.Ask) {
					lastAskPrice = marketDataUpdate.Price; // ASK/OFFER price
				}
			} // end lock
			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 tagCounter = 0;
		
		private string getShortTag() {
			return "SRisk"+tagCounter++;
		}
		
		private string getLongTag() {
			return "LRisk"+tagCounter++;
		}
		
		private Brush shortRiskBrush = Brushes.MediumBlue; //.Lime;
		private Brush longRiskBrush = Brushes.DarkOrange; //.Red;

		private SimpleRateLimiter onBarUpdateLimiter = null; // init and deInit
		
		private static bool DBG = false;
		protected sealed override void OnBarUpdate()
		{
			if ( !isReady ) return; // must have
			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
				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 * ( lastBidPrice+lastAskPrice ));
					//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 deltaTicks = (midPrice - vwapPositive) / tickSize;
							if (DBG) {
								ninjaPrint2("vwapPositive price: "+vwapPositive);
							}
							if (deltaTicks>0) {
								if (deltaTicks>30) deltaTicks=30;
								Draw.Dot( this, getShortTag(), true, 0, (midPrice+4+(0.25*deltaTicks)), shortRiskBrush);
							}
						}
					}
					else { // negative inventory "Long Risk"
						double vwapNegative = 
							myIntegrator.getVwapPrice(VolumePriceExpiryIntegrator.VWAP_NEGATIVE_VOLUMES);
						if (vwapNegative != 0) { // valid; a zero price is INVALID
							double deltaTicks = ( vwapNegative - midPrice ) / tickSize;
							if (DBG) {
								ninjaPrint2("       vwapNegative price: "+vwapNegative);
							}
							if (deltaTicks>0) {
								if (deltaTicks>30) deltaTicks=30;
								Draw.Dot( this, getLongTag(), true, 0, (midPrice-4 - (0.25*deltaTicks)), longRiskBrush);
							}
						}
					}
				}
			}
		}

		#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;
            // client should set all properties, prior to start()
        }

        // please call start only once
        public void 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 line = " ";
                        while ((line = inputFile.ReadLine()) != null) {
							if (line.StartsWith("#")) continue; // skip comment
                            string trimmedLine = line.Trim();
                            if (trimmedLine.Length == 0) continue;
                            if (!trimmedLine.Contains("=")) return false;
                            string[] splitString = line.Split('=');
                            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;
							}
                        }
                        inputFile.Close();
                    }
                }
				} 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.UtcNow;

        public SimpleRateLimiter(int msecsArg) {
            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() {
            DateTime now = DateTime.UtcNow;
            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)
		{
			return TradeFlowRisk(Input, fileName);
		}

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

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

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

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

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

#endregion
You can be SURE that this version, extending the Concepts; will be generating a lot of
Criticism and Questions; but that's how we learn something new.

[EDIT2] YOU MAY NOTICE HINTS OF A CONCEPT CALLED "SUPER SPIKES" but I
don't think we're leveraging that concept right now in the code... Maybe later...

Attached is the C# source code file, suitable for Pasting into NinjaScript Editor.

THE PARAMETER SETTINGS YOU ARE LOOKING AT ARE:
 
Code
# comment line
RETENTION_SECONDS=180
MULTIPLIER=2
BIGLOT_MINIMUM=3
TAPER_SIZE=True

ALSO ATTACHED IS A SCREENSHOT showing a glimpse of what it looks like when the
Indicator is "dragged" onto the Price Panel. In the Screenshot, BLUE dots indicate
what we will term "Short Risk" (a potential Top) and ORANGE dots indicate
"Long Risk" (a potential Bottom).

These Terms "Long" and "Short" are seen from MARKET MAKER'S perspective. She
becomes (relatively) "long" when Buying from Retail Sellers; and generally lowering
the Price in the Process... more in detail later.

hyperscalper

Attached Thumbnails
Click image for larger version

Name:	TradeFlowRiskAnalysisOverlain.PNG
Views:	272
Size:	148.0 KB
ID:	318287  
Attached Files
Elite Membership required to download: TradeFlowRisk.zip
Started this thread Reply With Quote
  #76 (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

LET'S TALK SIMPLE CONCEPTS AND RATIONALE FOR OUR MEASUREMENTS

As we've said, "Trading is a Battle beween Buyers and Sellers."

"Fair enough, you might say...." but you'd be very very wrong.

There's NOTHING fair about Trading, and the first thing you need to realize
is that Market Maker has ALL the Power and Advantage over you.

The major Advantage which MM enjoys, is that She is able to "Move prices
at Will" and, in doing so, she leverages Retail Buying and Selling liquidity
in order to optimize and reach her Objectives.

But of course, many of you will say "That's ridiculous, and an over-simplification".

And I would say, whether or not you believe that; you're encouraged to begin
your understanding of Market Dynamics by taking that as a Provisional
Assumption, so we can see where this assumption leads us...

MUCH more could be said here, but I'm not gonna go down that Rabbit Hole.

LET'S CONSIDER HOW YOU FEEL WHEN YOU'RE TRADING...

You've bought some futures contracts as low as possible and so we know
that YOU are "Long" (an unfortunate, but sticky term, that we use).

Your goal is to SELL your Long-acquired Inventory at as High a Price above
your Buying Price (average Buying Price?) as is possible.

In fact, EVERY Trader wants to do that; and, in fact, on aggregate, Every
Trader MUST Do That, cuz that's how Money is made.

When you are comfortably "in profit" you're feeling fine; Hopeful that price
will continue to Rise and Bless you with the ability to Sell at a higher Price
which satisfies you.

BUT WHAT ABOUT "Price Adversity" when the price starts to Drop, and does
move BELOW your Buying Price(s) ??? You then start to feel various horrible
emotions, like "stupid", you feel miserable, you feel discouraged; and you
begin to calculate in your mind just How Much price adversity can I take, before
I should SELL at a LOSS... YOU ARE "AT RISK" of Losing potentially a significant
amount of money (depending upon your Account Size, and other more psychological
factors, etc).

You are a DEFENSIVE TRADER, let's say; which is someone who "Cuts his Losses,
and Let's his Profits Run". The Perfect Trader, protecting his Downside, but
looking for those moments to leverage a significant Upside.

But when you're "underwater" in a Trade; you are "Losing Money" even if it's
only "paper money" or an "unrealized loss"; it still feels like a Loss to YOU,
or to most of you...

ALL OF THAT IS PERFECTLY NORMAL; YOU'RE HUMAN.

But Market Maker is NOT HUMAN; certainly not like you are. She has very very
"deep pockets" and takes frequently "very large positions"; but She takes them
in a way which is Difficult for an Individual Trader to understand...

NEVERTHELESS, when Market Maker's VWAP (Volume Weighted Average Price)
against Sellers (in this case) is currently HIGHER than the Market's Price;
then MM is "losing money on paper" or what I call "in Long Risk".

The DIRTY LITTLE SECRET HERE, is that MM Deliberately, and Methodically causes
Prices to fall, Knowing that most Retail Players are SELLERs, and by Lowering the
Bid price; it is INEVITABLE and DESIRABLE for MM to "dip into" this "Long Risk".

IT'S ALL PART OF THE GAME PLAN. Why isn't she worried ???? Because, that
"Risk" is only temporary; and She's Guaranteed to be able to LIFT the Price to get
out of Risk, and further to Convert a Population dominated by Retail Sellers, into
a population dominated by Retail Buyers, to whom SHE will happily SELL her
"Long inventory" when She decides to lift the Price.

If all of that makes sense to you, then You are Beginning to understand something
about Market Dynamics.

BACK TO OUR LITTLE INDICATOR, and the Concepts of Long Risk and Short Risk.

Micro Trends are "sawtooth" in shape, precisely because of this Dynamic; and our
(admittedly simple) Indicator has something of a handle on identifying these
Tops and Bottoms for these Micro Trends.

And now we Understand why RISK is part of MM's Game Plan. But it's NOT REALLY
a RISK; because SHE has All of the Power to pull herself out of "Long Risk" by lifting
Price; and out of "Short Risk" by lowering Price.

This recent version of TradeFlowRisk Analyzer; begins to "tap into" how MM operates;
and Why we expect to see these "sawtooth" oscillations which MAYBE we can better
identify because of these measurements on Time and Sales, and Price at which
Trades take place...

KNOWING THAT MM IS RELATIVELY LONG OR SHORT, may be Interesting; but it's
not telling you when She begins to feel "some pain" and is motivated to change the
Trend. However, if we know She's "underwater" on a Trend direction; then we can
begin to anticipate Her trend change. ODDLY ENOUGH, Market Maker usually does
NOT want to be underwater; at least not by very much.

In NQ versus ES, the same dynamics exist; but in ES a lot of it happens with much
more overall liquidity, as well as much smaller moves in price; but it's all the same !!

It all sounds too simple; and it is, in fact, a Simplification of Reality; but this Assumption
has the consequences which we expect; and which we can actually (at least partially)
measure, by writing the right Indicator !!

hyperscalper

Started this thread Reply With Quote
  #77 (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

AN UPDATED VERSION OF TRADE FLOW RISK WITH ENHANCEMENTS

This version contains some enhancements:
1) an "alternate color" checkbox so that 2 instances can be more easily distinguished
2) a "super spike" high volume marker, relative to average volume
3) a threshold parameter for Risk level, before markers are shown

Anyway, it's all in the code...

The new parameters file looks like this:

 
Code
# your comment lines
RISK_THRESHOLD=10
RETENTION_SECONDS=120
MULTIPLIER=1
BIGLOT_MINIMUM=2
TAPER_SIZE=True
SUPER_SPIKE_THRESHOLD_RATIO=1.8
 
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";
				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					= "uniqueTradeFlowProps.txt";
				AltColor					= false; 
			}
			else if (State == State.Configure)
			{
				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;
				
			}
			else if (State == State.Realtime)
			{
				init();
				isReady = true;
			}
			else if (State == State.Terminated)
			{
				isReady = false;
				deInit();
			}
		}
		
		#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; }
		#endregion

		private int indexCounter = 0;
		
		private int zeroIndex = 0; // assigned in config
		private int inventoryIndex = 0;
		
		private bool isReady = false;
		
		#region Startup and Initialization
		private void init() {
			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(20); // milliseconds
			superSpikeReferenceLimiter = new SimpleRateLimiter(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 = DateTime.UtcNow;
				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] = DateTime.UtcNow;
				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
			}
			
			// 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 = DateTime.UtcNow; // 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) {
					returnValue = ( n>0? sum/n : 0 );
				}
				else { // signed summation
					returnValue = 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 = DateTime.UtcNow; // 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() {
			myIntegrator = null;
			if (myPropertyReader!= null) {
				myPropertyReader.stop(); // kill the thread
				myPropertyReader = null;
			}
			onBarUpdateLimiter = null;
			superSpikeReferenceLimiter = null;
			// stop and deallocate objects
		}
		#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;
			double compressedVol = compressSizeMinOneSigned( tradeRawVol );
			// Super Spike Volumes test here
			//if (Math.Abs(compressedVol)>superSpikeThreshold*getLastReferenceVolume()) {
			if (Math.Abs(compressedVol)> (superSpikeThresholdRatio*getLastReferenceVolume())) {
				if (compressedVol>0) wasSuperSpikeBuy = true;
				else wasSuperSpikeSell = true;
			}
			reportToIntegrator( compressedVol, price );
		}
		
		private void reportToIntegrator(double compressedVolArg, double priceArg) {
			myIntegrator.addVolumePriceData(compressedVolArg, priceArg);
		}
		#endregion
		
		private double lastBidPrice = 0;
		private double lastAskPrice = 0;
		private string customRootPath = null;
		
		private static int REFERENCE_MSECS = 10 * 60 * 1000; // 5 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) ;
			return lastSuperSpikeReferenceVolume;
		}
		
		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 OnMarketUpdate callback
		protected sealed override void OnMarketData(MarketDataEventArgs marketDataUpdate)
		{
			if ( !isReady ) return;
			lock(getUpdateMutex()) { // wait to allow property updates now
				MarketDataType type = marketDataUpdate.MarketDataType;
				if (type==MarketDataType.Last) { // Trade
					if (lastBidPrice==0) return;
					if (lastAskPrice==0) return;
					if (lastAskPrice <= lastBidPrice) return;
					double tradePrice = marketDataUpdate.Price; // price of the Trade
					int tradeVolume = (int)marketDataUpdate.Volume;
					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) {
					lastBidPrice = marketDataUpdate.Price; // BID price
				}
				else if (type==MarketDataType.Ask) {
					lastAskPrice = marketDataUpdate.Price; // ASK/OFFER price
				}
			} // end lock
			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 tagCounter = 0;
		
		private string getShortTag() {
			return "SRisk"+tagCounter++;
		}
		
		private string getLongTag() {
			return "LRisk"+tagCounter++;
		}
		
		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 SimpleRateLimiter onBarUpdateLimiter = null; // init and deInit
		
		private static bool DBG = false;
		protected sealed override void OnBarUpdate()
		{
			if ( !isReady ) return; // must have
			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
				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 * ( lastBidPrice+lastAskPrice ));
					if (wasSuperSpikeBuy) {
						wasSuperSpikeBuy = false;
						Draw.Dot( this, getShortTag(), true, 0, (midPrice+2*tickSize), 
									superSpikeBuyBrush);
					}
					if (wasSuperSpikeSell) {
						wasSuperSpikeSell = false;
						Draw.Dot( this, getLongTag(), 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 deltaTicks = (midPrice - vwapPositive) / tickSize;
							if (DBG) {
								ninjaPrint2("vwapPositive price: "+vwapPositive);
							}
							if (deltaTicks>=riskThresholdTicks) {
								if (deltaTicks>30) deltaTicks=30;
								Draw.Dot( this, getShortTag(), true, 0, (midPrice+4+(0.25*deltaTicks)), 
									AltColor?shortRiskBrushAlt:shortRiskBrush);
							}
						}
					}
					else { // negative inventory "Long Risk"
						double vwapNegative = 
							myIntegrator.getVwapPrice(VolumePriceExpiryIntegrator.VWAP_NEGATIVE_VOLUMES);
						if (vwapNegative != 0) { // valid; a zero price is INVALID
							double deltaTicks = ( vwapNegative - midPrice ) / tickSize;
							if (DBG) {
								ninjaPrint2("       vwapNegative price: "+vwapNegative);
							}
							if (deltaTicks>=riskThresholdTicks) {
								if (deltaTicks>30) deltaTicks=30;
								Draw.Dot( this, getLongTag(), true, 0, (midPrice-4 - (0.25*deltaTicks)), 
									AltColor?longRiskBrushAlt:longRiskBrush);
							}
						}
					}
				}
			}
		}

		#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;
            // client should set all properties, prior to start()
        }

        // please call start only once
        public void 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 line = " ";
                        while ((line = inputFile.ReadLine()) != null) {
							if (line.StartsWith("#")) continue; // skip comment
                            string trimmedLine = line.Trim();
                            if (trimmedLine.Length == 0) continue;
                            if (!trimmedLine.Contains("=")) return false;
                            string[] splitString = line.Split('=');
                            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;
							}
                        }
                        inputFile.Close();
                    }
                }
				} 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.UtcNow;

        public SimpleRateLimiter(int msecsArg) {
            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() {
            DateTime now = DateTime.UtcNow;
            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
IN THE SCREENSHOT, you can see 2 Instances of TradeFlowRisk running, with the 2 possible Alt (alternate)
Colors checkbox settings on startup. Red/Green dots ON THE PRICE represent "Super Spike Volumes"
relative to average volume by the ratio SUPER_SPIKE_THRESHOLD_RATIO=1.8.

[EDIT] Added another screenshot; these are "after hours" conditions...

KEEP IN MIND THAT THIS IS NOT "RAW VOLUME" but "compressed" volume; in
sort of a logarithmic fashion, where the exponent is hard coded... So the "ratio"
for threshold trigger is a ratio of 2 logarithmically compressed raw Volumes...

IF YOU ARE RUNNING 2 INSTANCES, set one of them with a super spike threshold ratio
which triggers; and set THE OTHER ONE to a very high value, e.g. 10 so it will NOT be
triggering. There's no point in 2 things producing the same markers !

THIS VERSION PRINTS IN THE SECONDARY NINJASCRIPT OUTPUT WINDOW, if there is a problem
using ANY of the Parameters in the parameters file...

I'll also attach a ZIP file containing the .cs code above, which can be pasted into the NinjaScript Editor.

THE SYSTEM WILL AUTO-GENERATE the Parameters file, if the filename is not found; which
is the best way to get it, and then to subsequently to Edit it with unique filename for each
instance of the Indicator.

Hope this helps someone! Oddly enough, it's helped me a lot, so that's a nice side-effect
of posting this thread !

hyperscalper

Attached Thumbnails
Click image for larger version

Name:	TradeFlowRiskWithAltColors.PNG
Views:	190
Size:	151.5 KB
ID:	318400   Click image for larger version

Name:	TradeFlowRiskWithAltColors2.PNG
Views:	168
Size:	236.7 KB
ID:	318402  
Attached Files
Elite Membership required to download: TradeFlowRisk.zip
Started this thread Reply With Quote
  #78 (permalink)
 
poseidon's Avatar
 poseidon 
Stockholm, Sweden
 
Experience: Beginner
Platform: NinjaTrader
Broker: IB
Trading: ES
Posts: 265 since Jan 2010
Thanks Given: 266
Thanks Received: 267

Big thanks to @hyperscalper for this very interesting thread and generous contribution of insights and code.

After reading the thread I have two questions.

1) What is the reason for retail buying larger lot sizes at tops and selling larger lot sizes at bottoms? Aren’t larger orders submitted by “smart money” like institutions? Is this the motivation for the size filter?


Quoting 
WE ARE THEREFORE LOOKING FOR an Acceleration of Signed Volume MM Selling Distribution at a
"top" (more Retail Buying in larger lot sizes); and the opposite at a bottom,
an Acceleration of Signed Retail Selling volume, and increases in Big Lot sizes as Retail Sellers accelerate their
Selling at the bottom, enabling MM to "scoop up" Buying volume at as low a Price as possible
before turning the Price trend.

AS A GENERAL SUMMARY, we are looking for an Acceleration in Distribution by MM Selling at Tops,
and an Acceleration in Accumulation by MM at Bottoms; using the Retail population as the
unfortunate "sheep-like" counter-party because, as we've said, the Retail population most
generally Buys when Price rises (especially if a Break Out seems possible); and Sells when
Price falls (especially if a Break Down seems probable).

IT IS THIS ACCELERATION IN "BIG LOT" *RATES* OF ACCUMULATION AND DISTRIBUTION
that we are seeking to characterize by this Indicator.

2) Why do we need too compress raw signed volume in the indicator?

Again, thanks for a very interesting thread. I would love to read more of your thoughts on this topic.

/poseidon

Visit my NexusFi Trade Journal Reply With Quote
Thanked by:
  #79 (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


poseidon View Post
Big thanks to @hyperscalper for this very interesting thread and generous contribution of insights and code.

After reading the thread I have two questions.

1) What is the reason for retail buying larger lot sizes at tops and selling larger lot sizes at bottoms? Aren’t larger orders submitted by “smart money” like institutions? Is this the motivation for the size filter?



2) Why do we need too compress raw signed volume in the indicator?

Again, thanks for a very interesting thread. I would love to read more of your thoughts on this topic.

/poseidon

Thanks for looking at this; now to try and answer your queries...

"1) What is the reason for retail buying larger lot sizes at tops and selling larger lot sizes at bottoms? Aren’t larger orders submitted by “smart money” like institutions? Is this the motivation for the size filter?"

OK, this is a bit tricky, but let's dive in ! Retail Buyers may experience FOMO (fear of missing out) and believe
a Breakout is about to happen... So they go all in; but at exactly the wrong time. And also Market
Maker "absolutely knows" that She is about to drop the Price from the peak; and is only luring in the
last of the Retail Buyers (who are doomed) by appearing to be starting a Breakout. Market Maker
also moves SIZE (on the inner Tiers of the Book) much closer into the Inside Market, because She
wants to Interact, SELLING against those excited (but Doomed) Retail Buyers.

As for the motivation for the size filter... First of all "Smart Money" you mention, and "Institutions"
are not necessarily very "Smart" and if they are Buying the ASK Price, hoping for a Breakout; then
they are, by definition, mere Retail players (not Market Maker) even though they have have a lot
of money they can afford to waste on chasing Breakouts that never happen, for example LOL

We are anticipating an "acceleration" in the population of Big Lot Trades at Tops and Bottoms, due to
this interaction between Retail Players and Market Maker's intentiions to (soon?) instantly change the
trend to down... to begin the process of shaking out the unfortunate Breakout Hopefuls... who
will generally be stopped out, etc..... That's Market Dynamics in action.

"2) Why do we need too compress raw signed volume in the indicator?"

There's not much to this, except that outliers are generally better-managed through Compression,
with some "logarithmic" basis to them. It's not strictly Necessary to take a Compressed Volume
and, if you want to mod the code, be my guest !!! (that's what the exercise is trying to encourage).
Certainly, there could be legitimate reason to work on "Raw Volumes" as opposed to "Compressed
Volumes"... so I wouldn't rule that out for specific reasons. [edit] You can easily change
the Exponent of the compression upwards closer to 1.0 to ease the magnitude of the
compression effect, or just modify it to return its input value unchanged... up to you !

hyperscalper

Started this thread Reply With Quote
Thanked by:
  #80 (permalink)
 
justtrader's Avatar
 justtrader 
San Francisco, CA
 
Experience: Intermediate
Platform: Ninja Trader, TOS
Trading: es, rty, cl, gc, nq, ym
Posts: 184 since May 2011
Thanks Given: 104
Thanks Received: 173


@hyperscalper
I wonder if you can modify the code to access the properties file only after it gets modified. And/Or perhaps check for file re-written with your timer and if true then read it. Most of the time the properties file will not change for a long time. Hence why read it all the time (save some CPU energy).

Some pseudo code:
if (lastModifiedTime != File.GetLastWriteTimeUtc) {
ReadFile();
lastModifiedTime = File.GetLastWriteTimeUtc;
}

TWYS NWYT (Price Advertises Opportunity; Time Regulates it; Volume Measures its Success/Failure ---- Dalton)
Reply With Quote




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