Design a DayTrader Scalping Order Flow Indicator - futures io
futures io



Design a DayTrader Scalping Order Flow Indicator


Discussion in Traders Hideout

Updated
      Top Posters
    1. looks_one hyperscalper with 111 posts (156 thanks)
    2. looks_two Chof with 22 posts (6 thanks)
    3. looks_3 justtrader with 14 posts (3 thanks)
    4. looks_4 TWDsje with 13 posts (9 thanks)
      Best Posters
    1. looks_one bobwest with 1.5 thanks per post
    2. looks_two hyperscalper with 1.4 thanks per post
    3. looks_3 TWDsje with 0.7 thanks per post
    4. looks_4 Chof with 0.3 thanks per post
    1. trending_up 11,754 views
    2. thumb_up 197 thanks given
    3. group 35 followers
    1. forum 193 posts
    2. attach_file 64 attachments




Welcome to futures io: the largest futures trading community on the planet, with well over 125,000 members
  • 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 and simple.

-- Big Mike, Site Administrator

(If you already have an account, login at the top of the page)

 
Search this Thread
 

Design a DayTrader Scalping Order Flow Indicator

(login for full post details)
  #171 (permalink)
 TheTradeSlinger 
Huntington WV
 
Experience: Advanced
Platform: TradeStation
Trading: ES, CL
 
TheTradeSlinger's Avatar
 
Posts: 475 since Jun 2015
Thanks: 825 given, 645 received


hyperscalper View Post
First image attached to bottom of this quoted post

Hyperscalper,

Is there any value to finding the delta between the various groupings/bunchings of readings?

Looking at the first image attached to the bottom of the quoted post, there are three separate areas (circled). Could a delta reading between the areas give any useful information?

For example: maybe the first group has a reading of (making this number up) 15, the second circled bunch has a reading of let's say 10, and the final group has a reading of 5... maybe that could mean "declining buying pressure"?

Just throwing something out there after reading through this very interesting thread.

Thanks!


EDIT: Maybe a historical tracker or log of values over time could be compared against the current reading to determine "relative strength" of the pressure? (I could be entirely missing what this indicator does, feel free to let me know lol)

Follow me on Twitter Visit my futures io Trade Journal Reply With Quote

Can you help answer these questions
from other members on futures io?
What are the main Eurobond futures and their respective …
Traders Hideout
Does anyone have the code for Anchored Vwap to use in Qu …
Traders Hideout
Google Keyword Search Alerts
Traders Hideout
 
Best Threads (Most Thanked)
in the last 7 days on futures io
Vinny E-Mini & Algobox Review TRADE ROOM
45 thanks
Design a DayTrader Scalping Order Flow Indicator
22 thanks
Daytrading.Coach Review
15 thanks
AMA: FuturesTrader71 (FT71) / Morad Askar - Ask Me Anything
9 thanks
The Wheel Strategy on Options
9 thanks
 
(login for full post details)
  #172 (permalink)
 hyperscalper 
boise idaho
 
Experience: Advanced
Platform: NinjaTrader C# Custom
Broker: NinjaTrader LeeLoo Rithmic
Trading: Nasdaq Futures NQ/MNQ
 
Posts: 250 since Apr 2020
Thanks: 14 given, 334 received


TheTradeSlinger View Post
Hyperscalper,

Is there any value to finding the delta between the various groupings/bunchings of readings?

Looking at the first image attached to the bottom of the quoted post, there are three separate areas (circled). Could a delta reading between the areas give any useful information?

For example: maybe the first group has a reading of (making this number up) 15, the second circled bunch has a reading of let's say 10, and the final group has a reading of 5... maybe that could mean "declining buying pressure"?

Just throwing something out there after reading through this very interesting thread.

Thanks!

The answer is.....wait for it ! ..... YES.

Well, in general, there's always a way to "synthesize" an interesting Indicator.

Unfortunately, the NUMERICAL VALUES on the Screenshots are Obscured by
being too tightly packed and overwritten.

Those numerical values are Total Risk (in something proportional to $$$)
which Market Maker is experiencing at any moment in time.

Your instincts are right, because, as always, we are looking for "distortions"
or "deltas" from an expected value. And we nearly always look for some
concept like "regression to the mean" or simple "relaxation of distortions"
in the Market. These imbalances should resolve themselves.

The "sawtooth" behavior of a "normal market" is like a "heartbeat" where
distortions or irregularities in Net inventory are pushed to Positive and
Negative values; which are also associated with Market Maker "dipping into Risk"
on both sides of this "sawtooth" alternating "distortion".

So.... Yep; that is how we should be thinking here; and with a "normal"
market, the Technical Indicators will perform better; than today's Nasdaq
where Insanity Rules, and most Technicals will fail.

SO, DO WE NEED TO "ENHANCE" THIS INDICATOR? There's always room
to improve either the presentation, and/or the calculations... I'm pretty busy
right now, but I do use this indicator; pending getting my more advanced
Inventory Analysis in shape; and even considering all of the limitations of
this TradeFlowRisk Indicator so far...

[edit] Attached screenshot more "spread out" so these values
of Total Risk in Monopoly Money Dollars are shown. The Bigger,
the more likely this Short Risk shown will result in a turn down...

[edit2] So what's happening here is that a VWAP for the "Short Inventory"
is formed. That VWAP is a PRICE of MM Selling to Retail Buyers. Obviously
as PRICE is higher and higher against that VWAP; then the Risk level
is HIGHER; since this idea of "risk" is Delta Market Price versus Cost Price of Inventory,
in *Delta Points*, or something closely approximating the idea of Cost Price of Inventory, MULTIPLIED
by the estimated Point Value of this estimated Amount of Inventory, obviously

hyperscalper

Attached Thumbnails
Click image for larger version

Name:	TFR-Total-Risk-Values.PNG
Views:	44
Size:	151.5 KB
ID:	319908  
Started this thread Reply With Quote
The following 2 users say Thank You to hyperscalper for this post:
 
(login for full post details)
  #173 (permalink)
 hyperscalper 
boise idaho
 
Experience: Advanced
Platform: NinjaTrader C# Custom
Broker: NinjaTrader LeeLoo Rithmic
Trading: Nasdaq Futures NQ/MNQ
 
Posts: 250 since Apr 2020
Thanks: 14 given, 334 received


ANOTHER DAY OF THE NASDAQ CRASH

Was it hyper-reaction to Powell's re-nomination?
Was it long term interest rates?

As a technical trader, I don't really care.

One thing's for sure, the TradeFlowRisk Indicator
doesn't find an "overall" bottom here, since there
isn't one in the Panic !!! LOL

However, I must say that 1 of my many criteria for
triggering includes "Net Inventory" imbalance, which
is a 30-second based Inventory fluctuation.

Although I also sometimes trigger on Big Lots, and
also require a deflection of about 3-4 POINTS and
a "snap" of a POINT before striking; and then I am
using MNQ over a range of Limit placements, with
Automatic target management.... etc. So I can surf
this with very low risk; and high "hit rates". That's
my custom integrated platform, with a colocated
server, for the Nasdaq. [edit] I should clarify that
NinjaTrader is sitting on the colocated server, within
which my analysis and trigger platform runs as an
interactive semi-autonomous Strategy.

BUT MY POINT HERE is that Net inventory Imbalance,
not even taking into consideration the Total Risk values,
which I also could trigger on, is a significant factor in
finding Relative tops and bottoms in the action...
Honest Injun, yes, that is a significant factor in
even Micro-Scalping the "sawtooth" moves... lol

[edit2] Just by way of trying to understand Market Dynamics
a bit more... Market Maker will drive prices until the Retail Players
"fall into line"; meaning as price drives down; MM converts Retail
traders predominately into Sellers, thus driving Net Inventory down,
and often MM will not stop the down move until the Retail players
respond. Of course MM is Buying from them, so MM automatically
"measures" the Net Inventory herself, as she is the "consumer" of
all Retail Sell or Buy volume. The "sawtooths" are really a result of
this phenomenon or behavior by MM. And typically that will drive
also not just into Net Inventory imbalance; but also, more critically,
into Risk which is often significant. It's not really "Risky" to MM,
since She knows how to get out of it; it's more just driving "over the
edge" to get Retail players to follow... (to their peril), etc. lol

hyperscalper

Attached Thumbnails
Click image for larger version

Name:	Nasdaq-crash-continues.PNG
Views:	34
Size:	336.8 KB
ID:	319933  
Started this thread Reply With Quote
The following user says Thank You to hyperscalper for this post:
 
(login for full post details)
  #174 (permalink)
 Chof 
Edmonton AB. / Canada
 
Experience: Intermediate
Platform: NT 8
Broker: Amp / CQG
Trading: NQ index
 
Posts: 28 since Apr 2017
Thanks: 25 given, 28 received


hyperscalper View Post
ANOTHER DAY OF THE NASDAQ CRASH
Was it hyper-reaction to Powell's re-nomination?
Was it long term interest rates?

Oh, I think you nailed it on both those...

Hi HS,

I studied your TradeFlowRisk code and thought I recognized a quick and easy way to
calculate VWAP in one of your "loops". I set to work and found no joy trying to use "OMD".
(I didn't try to use an indicator in the indicator so no "Series Doubles") This time I'll post it

Quoting 
#region Using declarations
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
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.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
{
/// <summary>
/// This indicator uses volume data gathered from the "tape". It uses "OnMarketData"
/// hence "OMD" from the instantaneous "bid" and "ask" information gathered on every
/// trade as its published in the "Time and Sales" box as the trades fly by..
///
/// Calculate on BarClose is super important as there could be hundreds of calc's in
/// every bar to get the final print on the close of the bar (maybe thousands)
///
/// Also this is probably not going to work properly on historical data.. The data
/// gathering "asks" won't get all the data from what is kept historically
/// </summary>
public class VWAPOMD : Indicator
{
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Description = "Attempt at using OMD volume data to calculate VWAP";
Name = "VWAPOMD";
Calculate = Calculate.OnBarClose;
IsOverlay = false;
IsSuspendedWhileInactive = false;
IsAutoScale = true;
DisplayInDataBox = true;
DrawOnPricePanel = true;
DrawHorizontalGridLines = true;
DrawVerticalGridLines = true;
PaintPriceMarkers = true;
ScaleJustification = NinjaTrader.Gui.Chart.ScaleJustification.Right;

AddPlot(Brushes.Black, "vwap");
Plots[0].Width = 2;
}

else if (State == State.Configure)
{


}

else if (State == State.DataLoaded)
{

}

}
protected sealed override void OnMarketData(MarketDataEventArgs dataPacket) {

MarketDataType type = dataPacket.MarketDataType;

if ( type == MarketDataType.Last) {

int tradeLotSize = 0;
double sumV = 0.0;
double sumPV = 0.0;
double Alt = 0.0;
double tradePrice = dataPacket.Price;
double bidPrice = dataPacket.Bid;
double askPrice = dataPacket.Ask;
double midPrice = 0.5 * ( bidPrice + askPrice );
double volume = ( bidPrice + askPrice );

tradeLotSize = (int) dataPacket.Volume;

sumPV += tradeLotSize * tradePrice ;
sumV += tradeLotSize;
// a zero return PRICE means no data
double vwap = sumV==0 ? 0 : sumPV / sumV;
return ;
}


}
protected override void OnBarUpdate()
{
if (CurrentBar < 1)
return;
}

#region Properties


#endregion
}
}

I'll await your ideas.. no doubt I'm missing a bunch of stuff I've never heard of.. Oh, I should
say that I'm not getting errors in the "log".. It shows up in the data box as N/A. I should have asked
was it 'BobWest' how to put in a code box.. don't see a button for that -

Chuck

Reply With Quote
The following user says Thank You to Chof for this post:
 
(login for full post details)
  #175 (permalink)
 hyperscalper 
boise idaho
 
Experience: Advanced
Platform: NinjaTrader C# Custom
Broker: NinjaTrader LeeLoo Rithmic
Trading: Nasdaq Futures NQ/MNQ
 
Posts: 250 since Apr 2020
Thanks: 14 given, 334 received


Chof View Post
Oh, I think you nailed it on both those...

Hi HS,

I studied your TradeFlowRisk code and thought I recognized a quick and easy way to
calculate VWAP in one of your "loops". I set to work and found no joy trying to use "OMD".
(I didn't try to use an indicator in the indicator so no "Series Doubles") This time I'll post it

I'll await your ideas.. no doubt I'm missing a bunch of stuff I've never heard of.. Oh, I should
say that I'm not getting errors in the "log".. It shows up in the data box as N/A. I should have asked
was it 'BobWest' how to put in a code box.. don't see a button for that -

Chuck

Again, you need to review the concept of "scope" and
"cumulation variables" such as

 
Code
double sumV = 0.0;
double sumPV = 0.0;
will need to be in the Outer Scope; not within the block which
defines OnMarketData.

Of course, if you're going to implement a moving window, you'll
likely need to Timestamp your data; and have some plan how that
is to be handled.

Maybe keep a list of incoming items, as in (typos maybe, I'm in a rush)

 
Code
public class TradeItem {
    public int signedVolume=0;
    public double price = 0;
    public DateTime timestamp = new DateTime.UtcNow;
    public TradeItem(int signedVolumeArg, double priceArg) {
        this.signedVolume = signedVolumeArg;
        price = priceArg;
        // automatically timestamped in real time
    }
}
THERE MAY BE TYPOS HERE; just giving you the concept
of aging a list of data periodically

 
Code
// inseert all items in an "outer scope" list
private List<TradeItem> tradesList = new List<TradeItem>();
 
Code
// add a trade item (checked for signed volume) into the List
tradesList.Add(new TradeItem(signedVolume, tradePrice));
 
Code
// to trim your list to hold only data within a period of msecs
long maxAgeInMsecs = 120000; // 2 minutes max age in msecs
// recent items are added to the end of the list, so let's
// eliminate any earlier items older than 2 minutes
DateTime now = DateTime.UtcNow;
while(tradesList.Count != 0) {
    TradeItem item = tradesList[0]; // first item
    TimeSpan elapsed = ( now - item.timestamp );
    if (elapsed.TotalMilliseconds > maxAgeInMsecs) {
        tradesList.RemoveAt(0); // discard oldest from list; expired, too old
    }
    else {
         break; // assuming all further data is OK
    }
}
And I don't see any categorization of data as a "Retail Buy"
versus a "Retail Sell"

If you have an aged list like this; you can just iterate through
it and do your VWAP calculations as you wish.

 
Code
sumPV = 0;
sumV = 0;
foreach( TradeItem item in tradesList) {
     // example of positive volumes only
     if (item.signedVolume>0) { // positive numbers only
           double absVolume = Math.Abs(item.signedVolume);
           sumPV += ( item.price * absVolume);
           sumV += absVolume;
     }
}
// now you have what you need for the VWAP
Just a few ideas about this code; keep on learning !

hyperscalper

Started this thread Reply With Quote
The following 2 users say Thank You to hyperscalper for this post:
 
(login for full post details)
  #176 (permalink)
 bobwest 
Site Moderator
Sarasota FL
 
Experience: Advanced
Platform: Sierra Chart
Trading: ES, YM
 
bobwest's Avatar
 
Posts: 7,020 since Jan 2013
Thanks: 51,534 given, 23,347 received


Chof View Post
I should have asked
was it 'BobWest' how to put in a code box.. don't see a button for that

It's not real clear by itself, but the code button is the one that says "#" -- don't ask me why, but it does.

If you let the mouse hover over each of the buttons, a little box will pop up that says what each one does. This one says, "Wrap CODE tags around selected text."

Bob.

When one door closes, another opens.
-- Cervantes, Don Quixote
Reply With Quote
The following 2 users say Thank You to bobwest for this post:
 
(login for full post details)
  #177 (permalink)
 hyperscalper 
boise idaho
 
Experience: Advanced
Platform: NinjaTrader C# Custom
Broker: NinjaTrader LeeLoo Rithmic
Trading: Nasdaq Futures NQ/MNQ
 
Posts: 250 since Apr 2020
Thanks: 14 given, 334 received

WHEN NASDAQ IS UNHAPPY; NOBODY WINS

Try trading this krap... I decided it's a "coding day"
and not a "trading day"

hyperscalper

Attached Thumbnails
Click image for larger version

Name:	Nasdaq-temper-tantrum.PNG
Views:	25
Size:	88.0 KB
ID:	319978   Click image for larger version

Name:	Untradable-crap-maybe.PNG
Views:	20
Size:	261.6 KB
ID:	319979  
Started this thread Reply With Quote
The following user says Thank You to hyperscalper for this post:
 
(login for full post details)
  #178 (permalink)
 hyperscalper 
boise idaho
 
Experience: Advanced
Platform: NinjaTrader C# Custom
Broker: NinjaTrader LeeLoo Rithmic
Trading: Nasdaq Futures NQ/MNQ
 
Posts: 250 since Apr 2020
Thanks: 14 given, 334 received

LET'S TALK ABOUT "BIG LOTS" IN TIME AND SALES

Traders always talk about things like "icebergs", those
hidden pools of volume which "trickle" out into the
trade stream in "stealth mode" so we don't see them.

How useful it is to know/speculate like this; I couldn't
say...

But "BIG LOTS" are part of an indication of a major TOP
or BOTTOM in short term Trending. WHY MIGHT THAT BE ??

Well, let's think about a possible theory here. Market Maker
"knows" where she wants the Top to be or the Bottom to
be; but it's a bit more "dynamic" than that.

MM is always "checking the pulse" of the Retail players.
As I've alleged (claimed, speculated?) MM moves Prices
UP until not only are Retail players "on board" as Buyers,
but even the BIGGER FISH are tempted in as Buyers.

These "bigger fish" tend to unleash larger Lot Sizes, and
as soon as they are driven (by FOMO -- Fear of Missing Out)
to Buy as the Price is rising; then that is exactly what
Market Maker wants to see.

TWO things happen here: 1) Market Maker moves "Book Size"
closer to the Market, since She wants to interact here, and
grab as much Selling Volume (from Retail Buyers) at TOPS,
as she can get, before she reverses the trend to down... and
2) the Bigger Fish also come in at what are often Breakout
or Breakdown opportunities, with their higher-than-average
Lot Sizes.

Whether those are "pieces" of their "icebergs" or not... Well,
I don't want to speculate; since I don't think it's helpful to
think that way.

But we know that MM will keep prices lingering at Tops and
Bottoms; and that's commonly called "the Battle between
Buyers and Sellers". Some are expecting the Breakout and
they BUY; and some are expecting it to be a Top so they
SELL. A LARGE AMOUNT of Volume is exchanged at these
points, so NO WONDER that Market Maker wants to "linger"
there as long as possible.

(Remember that "spread transactions" by Market Maker provide
a lot of income, but at a Top, Market Maker is SELLING against
frantic Retail Buyers, and that provides MM's "short" Net
Inventory, often "In Risk" as well)

SO, WHAT ABOUT BIG LOTS? I trigger on Big Lots as one of
several simultaneous criteria for reversals. At a Top, I'm looking
for Big Lot Retail BUYERs. Now, sometimes at a Top you'll
see a Big Lot Retail SELLER who apparently has correctly judged
the Top, and is either going Retail Short, or Closing a Retail Long.

THE IMPORTANT ONES to look for are one or multiple Retail Buyers
with Big Lots. Why??? The Market Maker trades AGAINST the
Retail population; "take the other side" of every trade. So,
when Big Lots appear, and they are Buying a Top, or they are
Selling a Bottom...

...THESE are your main clues that a Trend Reversal is in progress,
being planned.

ODDLY ENOUGH, in the Nasdaq Futures, I find that you'll not see
regularly many 10 Lot Big Lots... It's almost like nobody wants
to go to that threshold. So I often filter for 9 or 8 contracts in
the Trade Lot Size. These will appear pretty well at Tops and
Bottoms... ARE THEY HIDING SOMETHING? Let the Conspiracy
Theories begin !!! LOL

Of course Net Inventory will also tend to follow along; but the Real
Tell are the Big Lots, but they must be Retail Buyers buying "too high"
or Retail Sellers selling "too low" when Price is relatively up or
down, respectively; in order to be really good indicators.

I think our TradeFlowRisk indicator tries to throw Big Lots, but it may
not do it well enough, or the presentation might not be great...
I'll have to look at that.

[edit] I LOOKED AT THE CODE, and it's really not as intended.
bigLotMinimum is used only to REJECT incoming volumes; and
it should be used for detecting large big lots. Another variable
should be used for filtering mimimum volumes... SORRY about
that, as it wasn't intended. I'll post an updated codebase for
that with those changes, in the next week or so; or y'all can
code them yourselves ??? just kidding, but really, why not ??

[edit2] Yeah, I see the existing thing was a bit "dynamic" in trying
to find large sizes. The next version will abandon that "fancy"
approach, in favor of a specific Big Lot value that can be specified.

I could show you with my custom charting; what I mean here, but
it's not easy to decode.

hyperscalper

Started this thread Reply With Quote
The following 3 users say Thank You to hyperscalper for this post:
 
(login for full post details)
  #179 (permalink)
 hyperscalper 
boise idaho
 
Experience: Advanced
Platform: NinjaTrader C# Custom
Broker: NinjaTrader LeeLoo Rithmic
Trading: Nasdaq Futures NQ/MNQ
 
Posts: 250 since Apr 2020
Thanks: 14 given, 334 received

TRADE FLOW RISK V1.2

Okay, I did some quick hacking and I will post the
full code here as an edit; once I've verified everything.

CHANGES will be:
The whole SUPER_SPIKE_THRESHOLD_RATIO and
related concepts (including the property in the file)
are eliminated completely, and replaced by:

BIGLOT_MINIMUM now truly identifying "Big Lots", with

added property MINIMUM_SIZE which allows you to reject
sizes in the analysis which are Smaller than that value.

SUPPRESS_RISK_MARKERS=False is a default false,
switch which will allow you to Suppress (not draw) the
Risk Dots, but the Total Risk Text will always be printed.
So you can suppress these, as they can be difficult to
read.

BIG LOT values will be printed directly on the BAR for
the Price, in the same way as the old obsolete Super
Spike stuff was marked. Big Lots are shown as ARROWS.

THIS CODE WILL IDENTIFY ITSELF AS V1.2

Save your existing Property values; and allow the Indicator
to write a New file; OR... just edit the new properties in.

The new property set should be:
 
Code
# comment line(s)
RISK_THRESHOLD=30
RETENTION_SECONDS=600
MULTIPLIER=3.5
BIGLOT_MINIMUM=5
MINIMUM_SIZE=1
TAPER_SIZE=True
SUPPRESS_RISK_MARKERS=False
If the property file does not exist (or you rename it out
of the way) then the Indicator always writes a NEW properties
file with default values, WHEN IT STARTS UP;
just in case you need to check.

PROPERTY FILE ERROR MESSAGES ARE THROWN INTO
THE NINJASCRIPT OUTPUT WINDOW #2, AS BEFORE
just in case you think some property isn't working properly.

I use Notepad++ and keep my property files open in tabs.
I can then change any property, and CNTL-S to save the
updates, which should be picked up by the Indicator instance.

[edit-code-posted]
A few comments here:
1) MINIMUM_SIZE defines an interesting mode you might use.
You could decide to elevate that threshold to exclude nearly ALL
small lot sizes from analysis.

2) the UP-ARROW is drawn for a Big Lot which is Retail SELLING.
My reason for that is that MM trades against Retail Sellers; so the
prediction is that lower price will tend to rise on these Big Lot Retail sales.
SUPPRESS_RISK_MARKERS=True
will suppress the "noise" of Risk Marker "Dots", leaving only the numerical
Total Risk numbers; and then you'll see the "Big Lot" events.

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

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

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

		private int indexCounter = 0;
		
		private int zeroIndex = 0; // assigned in config
		private int inventoryIndex = 0;
		
		private bool isReady = false;
		
		private bool isInitialized = false;
		
		#region Startup and Initialization
		private void initOnce() {
			if ( isInitialized ) return; // once only
			isInitialized = true;
			customRootPath = Environment.ExpandEnvironmentVariables(
					"%USERPROFILE%\\Documents\\NinjaTrader 8\\bin\\Custom\\");
			myIntegrator = new VolumePriceExpiryIntegrator(this, 1000, retentionSecs);
			// allocate objects
			if (myPropertyReader==null) {
				myPropertyReader = new SimplePropertyReader(this);
				myPropertyReader.setPropertiesFileNameDotTxt(FileName);
				myPropertyReader.setPropertiesSamplingSecs(30); // file sampling frequency
				// now assign default property values
				myPropertyReader.setKeyValue(riskThresholdTicksProp, ""+riskThresholdTicks); // stringify it
				myPropertyReader.setKeyValue(retentionSecsProp, ""+retentionSecs); // stringify it
				myPropertyReader.setKeyValue(multiplierProp, ""+multiplier);
				myPropertyReader.setKeyValue(bigLotMinimumProp, ""+bigLotMinimum);
				myPropertyReader.setKeyValue(minimumSizeProp, ""+minimumSize); // new
				myPropertyReader.setKeyValue(taperSizeProp, ""+taperSize);
				myPropertyReader.setKeyValue(suppressRiskMarkersProp, ""+suppressRiskMarkers);
				myPropertyReader.start();
			}
			onBarUpdateLimiter = new SimpleRateLimiter(this, 20); // milliseconds
		}
		#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 = 8; // big lot filter minimum
		private static string minimumSizeProp = "MINIMUM_SIZE";
		private int minimumSize = 1; // minimum size to process
		private static string taperSizeProp = "TAPER_SIZE";
		private bool taperSize = true; // default useTaper
		private static string suppressRiskMarkersProp = "SUPPRESS_RISK_MARKERS";
		private bool suppressRiskMarkers = false; // default draw markers
		
		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 Big Lot trade volumes
			} catch (Exception ex) {ninjaPrint2( bigLotMinimumProp+" error!", true);} // do nothing
			try {
				int minimumSizeTmp = myPropertyReader.getIntegerProperty(minimumSizeProp);
				if (minimumSizeTmp<1) minimumSizeTmp = 1;
				minimumSize = minimumSizeTmp;
				// used to REJECT smaller trade volumes
			} catch (Exception ex) {ninjaPrint2( minimumSizeProp+" error!", true);} // do nothing
			try {
				taperSize = myPropertyReader.getBooleanProperty(taperSizeProp);
				myIntegrator.setUseTaper(taperSize);
			} catch (Exception ex) {ninjaPrint2( taperSizeProp+" error!", true);} // do nothing
			try {
				suppressRiskMarkers = myPropertyReader.getBooleanProperty(suppressRiskMarkersProp);
			} catch (Exception ex) {ninjaPrint2( suppressRiskMarkersProp+" 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
				
				
			} // end of any property value updates
		}
		// end of synchronization support
		#endregion

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

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

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

		}
		#endregion

		#region Shutdown de-Initialization
		
		private void deInit() {
			// must properly De-Allocate ALL user allocated objects
			// at least ones explicitly allocated at the Outer Scope
			// not counting things like Plot objects; and anything
			// which is running a Thread needs to have that stopped
			myIntegrator = null;
			if (myPropertyReader!= null) {
				myPropertyReader.stop(); // kill the thread
				myPropertyReader = null;
			}
			if (onBarUpdateLimiter!=null) {
				onBarUpdateLimiter = null;
			}
			if (myIntegrator !=null) {
				myIntegrator = null;
			}
			// stop and deallocate objects
		}
		#endregion
		
		#region Time Based Functions
		
		// add the ability to use a Virtual Time Base, rather
		// than "now" or Real Time, so DateTime.UtcNow is
		// not used for a Virtual Timebase; rather the function
		// call DateTimeUtcNow() returning the last Virtual
		// time; which should help the Indicator to respond
		// to Historical Data as well as Real Time
		
		private DateTime lastVirtualNowPrivate = DateTime.MinValue; // not initialized
		private Object timeMutex = new object();
		
		private static bool ENABLE_VIRTUAL_TIMEBASE = false;
		
		private bool isTimeBaseInitialized() {
			lock(timeMutex) {
			if ( !ENABLE_VIRTUAL_TIMEBASE ) return true;
			else return ( lastVirtualNowPrivate != DateTime.MinValue );
			}
			
		}
		
		private void setVirtualTime(DateTime newTime) {
			lock(timeMutex) {
				lastVirtualNowPrivate = newTime;
			}
		}
		
		private DateTime DateTimeUtcNow() {
			lock(timeMutex) {
			if (ENABLE_VIRTUAL_TIMEBASE && VirtualTimebaseProperty) {
				//throw new Exception("Virtual Time Base NOT Implemented yet...");
				if ( !isTimeBaseInitialized() ){
					//throw new Exception("Virtual Time Base No Timestamp yet...");
					return lastVirtualNowPrivate;
				}
				else {
					return lastVirtualNowPrivate;
				}
			}
			else {
				return DateTime.UtcNow;
			}
			}
		}
		
		#endregion
		
		private static bool LOG_RAW_VOLUME = true;
		
		private int lastRawTradeVol = 0;
		
		#region Processing Trade Flow
		private void reportTrade(int tradeSignedRawVol, double price) {
			// THESE COME IN AS SIGNED TRADE VOLUMES (BUY VS SELL)
			int absTradeRawVol = (int)Math.Abs(tradeSignedRawVol); // UNSIGNED MAGNITUDES
			if (absTradeRawVol >= bigLotMinimum) {
				if ( !wasBigLotSell && tradeSignedRawVol <= -bigLotMinimum) {
					wasBigLotSell = true;
				}
				else if ( !wasBigLotBuy && tradeSignedRawVol >= bigLotMinimum) {
					wasBigLotBuy = true;
				}
			}
			if (absTradeRawVol < minimumSize) return; // reject volume
			if ( LOG_RAW_VOLUME ) {
				if (tradeSignedRawVol>0) {
					ninjaPrint("Retail Buy "+tradeSignedRawVol+" @"+price);
				}
				else {
					ninjaPrint("       Retail Sell "+tradeSignedRawVol+" @"+price);
				}
			}
			lastRawTradeVol = tradeSignedRawVol;
			//
			reportToIntegrator( tradeSignedRawVol, price );
		}
		
		private void reportToIntegrator(double volSignedArg, double priceArg) {
			myIntegrator.addVolumePriceData(volSignedArg, priceArg);
		}
		#endregion
		
		private double lastBidPrice = 0;
		private double lastAskPrice = 0;
		private string customRootPath = null;
		
		private int lastIntegratorAvgVolume = 1;
		
		
		#region Market Depth Updates (used for timestamp only)
		
		protected sealed override void OnMarketDepth(MarketDepthEventArgs marketDepthUpdate) {
			if (DBG_MARKET_DATA) ninjaPrint2("DBG OnMarketDepth...");
			this.setVirtualTime(marketDepthUpdate.Time); // using timestamps virtual time only
		}
		
		#endregion

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

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

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

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

        private string fileName = "myProperties.txt";

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

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

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

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

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

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

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

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

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

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

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

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

		#endregion

	#region Simple Rate Limiter
    class SimpleRateLimiter {

        protected static Random randomizer = new Random();

        int msecs = 10; // default

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

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

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

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

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

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

        private int deltaMsecsRandom = 0;

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

    }

	#endregion
	
	}
}

#region NinjaScript generated code. Neither change nor remove.

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

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

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

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

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

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

#endregion
hyperscalper

Started this thread Reply With Quote
The following 3 users say Thank You to hyperscalper for this post:
 
(login for full post details)
  #180 (permalink)
 Chof 
Edmonton AB. / Canada
 
Experience: Intermediate
Platform: NT 8
Broker: Amp / CQG
Trading: NQ index
 
Posts: 28 since Apr 2017
Thanks: 25 given, 28 received


Hi HS,

Thanks for this - I know you've been wanting these changes for awhile. I'm seeing
that the overall design of your indi's has been necessitated by the data (Time and Sales),
That's due to the limits of the normal function of "Series doubles" that collect bar
data. I think the obvious need is for another totally separate "function" that will
collect what we want it to, if we want to code "old fashioned" types of indicators..

It may defeat the purpose, but it would be easier on the brain to start working with T&S.

I got a good start on my "VWAPOMD" as you gave me 90% of the components. I've
run into a (very new to me) couple of errors to do with 'OnBarUdate' and 'OnMarketData'.
Both "Classes" (If I have that right) are throwing the error "No suitable Method found
to Override". But in general I'm pleased with how well it compiled so far.. code is:


Quoting 

#region Using declarations
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
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.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
{
/// <summary>
/// This indicator uses volume data gathered from the "tape". It uses "OnMarketData"
/// hence "OMD" from the instantaneous "bid" and "ask" information gathered on every
/// trade as its published in the "Time and Sales" box as the trades fly by..
///
/// Calculate on BarClose is super important as there could be hundreds of calc's in
/// every bar to get the final print on the close of the bar (maybe thousands)
///
/// Also this is probably not going to work properly on historical data.. The data
/// gathering "asks" won't get all the data from what is kept historically
/// </summary>
public class VWAPOMD : Indicator
{
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Description = "Attempt at using OMD volume data to calculate VWAP";
Name = "VWAPOMD";
Calculate = Calculate.OnBarClose;
IsOverlay = false;
IsSuspendedWhileInactive = false;
IsAutoScale = true;
DisplayInDataBox = true;
DrawOnPricePanel = true;
DrawHorizontalGridLines = true;
DrawVerticalGridLines = true;
PaintPriceMarkers = true;
ScaleJustification = NinjaTrader.Gui.Chart.ScaleJustification.Right;

AddPlot(Brushes.Yellow, "vwap");
Plots[0].Width = 2;
}

//else if (State == State.Configure)
//else if (State == State.DataLoaded)
}
// inseert all items in an "outer scope" list
private List<TradeItem> tradesList = new List<TradeItem>();
public class TradeItem {
public int signedVolume=0;
public double price = 0.0;
public DateTime timestamp = DateTime.UtcNow();
public TradeItem(int signedVolumeArg, double priceArg) {
this.signedVolume = signedVolumeArg;
price = priceArg;
// automatically timestamped in real time

// add a trade item (checked for signed volume) into the List
tradesList.Add(new TradeItem(signedVolume, tradePrice));

// to trim your list to hold only data within a period of msecs
long maxAgeInMsecs = 120000; // 2 minutes max age in msecs
// recent items are added to the end of the list, so let's
// eliminate any earlier items older than 2 minutes
DateTime now = DateTime.UtcNow;
while(tradesList.Count != 0) {
TradeItem item = tradesList[0]; // first item
TimeSpan elapsed = ( now - item.timestamp );
if (elapsed.TotalMilliseconds > maxAgeInMsecs) {
tradesList.RemoveAt(0); // discard oldest from list; expired, too old
}
else {
break; // assuming all further data is OK
}
}

double sumPV =0.0;
double sumV = 0.0;
foreach ( TradeItem item in tradesList) {
// example of positive volumes only
if (item.signedVolume>0) { // positive numbers only
double absVolume = Math.Abs(item.signedVolume);
sumPV += ( item.price * absVolume);
sumV += absVolume;

// a zero return PRICE means no data
double vwap = sumV==0 ? 0 : sumPV / sumV;
//parent.ninjaPrint2("vwap data items: "+n+" price: "+vwap);
return vwap;
}
}
// now you have what you need for the VWAP
}
protected sealed override void OnMarketData(MarketDataEventArgs dataPacket) {

MarketDataType type = dataPacket.MarketDataType;

if ( type == MarketDataType.Last) {


double price = dataPacket.Price;
double bidPrice = dataPacket.Bid;
double askPrice = dataPacket.Ask;
double midPrice = 0.5 * ( bidPrice + askPrice );
int tradeLotSize = (int)dataPacket.Volume;
if ( tradePrice < midPrice ) {
(int) signedVolume = (int) tradeLotSize ;
}
else {
(int) signedVolume = (int) tradeLotSize * -1 ;
}

}


}


protected override void OnBarUpdate()
{
if (CurrentBar < 1)
return;
}

#region Properties
#endregion

}
}
}

#region NinjaScript generated code. Neither change nor remove.

namespace NinjaTrader.NinjaScript.Indicators
{
public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase
{
private VWAPOMD[] cacheVWAPOMD;
public VWAPOMD VWAPOMD()
{
return VWAPOMD(Input);
}

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

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

public Indicators.VWAPOMD VWAPOMD(ISeries<double> input )
{
return indicator.VWAPOMD(input);
}
}
}

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

public Indicators.VWAPOMD VWAPOMD(ISeries<double> input )
{
return indicator.VWAPOMD(input);
}
}
}

#endregion


Looking forward to using your version 1.2 and thanks for your help HS

Chuck

Reply With Quote
The following user says Thank You to Chof for this post:


futures io Trading Community Traders Hideout > Design a DayTrader Scalping Order Flow Indicator


Last Updated on November 29, 2021


Upcoming Webinars and Events
 

NinjaTrader Indicator Challenge!

Ongoing
     



Copyright © 2021 by futures io, s.a., Av Ricardo J. Alfaro, Century Tower, Panama, Ph: +507 833-9432 (Panama and Intl), +1 888-312-3001 (USA and Canada), info@futures.io
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.
no new posts