Dark Theme
Light Theme
Trading Articles
Article Categories
Article Tools
Welcome to NexusFi: the best trading community on the planet, with over 150,000 members Sign Up Now for Free
Genuine reviews from real traders, not fake reviews from stealth vendors
Quality education from leading professional traders
We are a friendly, helpful, and positive community
We do not tolerate rude behavior, trolling, or vendors advertising in posts
We are here to help, just let us know what you need
You'll need to
register in order to view the content of the threads and start contributing to our community.
It's free for basic access, or support us by becoming an Elite Member -- see if you qualify for a discount below.
-- Big Mike, Site Administrator
(If you already have an account, login at the top of the page)
Updated January 27, 2023
Top Posters
looks_one
hyperscalper
with 136 posts (239 thanks)
looks_two
Chof
with 22 posts (12 thanks)
looks_3
Connor
with 16 posts (8 thanks)
looks_4
justtrader
with 14 posts (8 thanks)
Best Posters
looks_one
bobwest
with 2 thanks per post
looks_two
hyperscalper
with 1.8 thanks per post
looks_3
SpeculatorSeth
with 1 thanks per post
looks_4
Chof
with 0.5 thanks per post
trending_up
52,397 views
thumb_up
328 thanks given
group
55 followers
forum
248 posts
attach_file
80 attachments
October 3rd, 2021, 07:23 PM
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
@
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
Can you help answer these questions from other members on NexusFi?
Best Threads (Most Thanked) in the last 7 days on NexusFi
October 3rd, 2021, 09:10 PM
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
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
October 3rd, 2021, 10:42 PM
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
October 4th, 2021, 11:44 AM
Posts: 781 since Apr 2016
Thanks Given: 22
Thanks Received: 1,021
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.
October 4th, 2021, 01:30 PM
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
Attached Files
Elite Membership required to download: TradeFlowRisk.zip
October 4th, 2021, 03:35 PM
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
October 6th, 2021, 06:23 PM
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
Attached Files
Elite Membership required to download: TradeFlowRisk.zip
October 8th, 2021, 11:21 AM
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
October 8th, 2021, 01:58 PM
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
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
October 9th, 2021, 03:40 PM
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 )
Last Updated on January 26, 2023