Seeking AFL script help for CVD similar to NinjaTrader 8 Implementation

I've found an AFL script that calculates a variant of the Cumulative Volume Delta (CVD). However, the script doesn't completely align with the CVD computation in NinjaTrader 8. I am hoping someone can help me modify this AFL script to better replicate the logic used in NinjaTrader 8.

Here is the script:

_SECTION_BEGIN("Cumulative Volume Delta V.1");

X = High - Close;
Y = High - Open;
Z = Open - Low;
A = Close - Low;

uw= IIf(C>O, X, Y);
lw= IIf(C>O, Z, A);
spread= IIf(High!=Low, High - Low, Null);

bl= (spread - (uw+lw));
puw= (uw/spread);
plw= (lw/spread);
pbl= (bl/spread);

vx= (pbl+(puw+plw)/2*Volume);
vy= ((puw+plw)/2*Volume);
 
bv= IIf(C>O, vx, vy);
sv= IIf(C<O, vx, vy);

cl= Param("Cumulative Length", 14, 10, 100);
cbv= EMA(bv, cl);
csv= EMA(sv, cl);
cvd= (cbv-csv);

PlotGrid(0, colorBrown, pattern = 9, width = 2, label = True);
cvd_color = IIf(cvd>0, colorGreen, colorRed);
SetBarFillColor(cvd_color);
Plot(cvd, "Cumulative Volme Delta", cvd_color, styleArea);
_SECTION_END();

This script calculates buy and sell volumes based on relationships between open, high, low, and close prices and applies an EMA to these volumes. However, the CVD in NinjaTrader 8 calculates the difference between the number of contracts traded at the ask and the bid summed over a certain period.

Here are the key discrepancies I've noticed:

  1. The AFL script is based on OHLC bar data, whereas the NinjaTrader CVD typically uses tick data (and I have tick data available with DTN IQFeed).
  2. NinjaTrader's CVD evaluates volume traded at bid or ask, not merely whether the close price is higher or lower than the open price.
  3. The AFL script uses an EMA of buy and sell volumes over a defined length, whereas NinjaTrader's version sums the buy and sell volumes over the trading session.

Given these differences, the AFL script does not yield identical results to NinjaTrader's CVD. If anyone has insights on modifying this AFL script to better match NinjaTrader's logic or could guide me to an existing implementation, it would be greatly appreciated.

Thank you in advance for your assistance!

if you have IQFeed data it is possible. But it is not so easy.

Jorgen (@BJW) has been pioneering on this. He posted some code on the forum. I could also help but it is quite a bit of work to get it started because you need to collect the data in the tick chart and then call them in your main chart (which has a higher timeframe for instance 5min TF). It is not easy to get it to run but it works fine.

So maybe search the forum first and maybe contact @BJW.

1 Like

Yea, that you do not want since it's moving into "a fantasy land"! If you use IQFeed you just have to setup a Tick Database. Then you identify the actual Volumes as follows:

BuyVolume = IIf( Close >= Aux2 AND Aux2 != Aux1, Volume, 0 );
SellVolume = IIf( Close <= Aux1 AND Aux2 != Aux1, Volume, 0 );

You can create a Separate 1-Tick Window for the calculations and then use StaticVars to bring the Data to whatever time frame you trade in.

Since Tomasz latest IQFeed Plugin has the <Consolidate trades with same TickID? option, you can identify individual Filled Orders. Great way to actually see "The Big Boys" filled orders! With a simple but effective approximation, you can also divide the Buy/Sell Filled Orders in Market and Limit Orders. Then things start to be interesting!
smile

Regards,

Jorgen

2 Likes

Thanks so much for the reply! I'm excited to be getting a reply from someone who's knowledgeable on this topic. I have some questions still that I'm hoping you can help with:

  1. Could you provide more details on how to set up a Tick Database using IQFeed? Are there any resources or guides you could point me towards?
  2. In your formula, you reference 'Aux2' and 'Aux1'. What specifically do these represent and how would I retrieve these values?
  3. You mentioned creating a separate 1-Tick Window for calculations. Could you expand on this process? How can I set this up in Amibroker?
  4. You suggest using StaticVars to transfer data between time frames. Could you provide an example of how this is done? I'm interested in understanding this process better.
  5. Can you provide more details on how to utilize the 'Consolidate trades with same TickID' option in the latest IQFeed plugin? What is the advantage of identifying individual filled orders?
  6. Lastly, you mentioned a method to divide Buy/Sell Filled Orders into Market and Limit Orders. Could you explain this process in more detail or provide an example? I'm interested in understanding how this could provide more insight into trading activity.

I know this is a lot, but if you can even help with some of these questions, I would greatly appreciate it. Thank you again for your expertise and willingness to help!

Hi kyleforb,

1.) It seems like you have limited experience with AmiBroker. If so no problem, but I highly recommend you to use AB Help and go to Contents. Then I assume you are already subscribing to IQFeed Data- correct? If so, then it's just to create a new DB and specify the Database Settings, Data source as DTN IQFeed data Plug-in and the Base time interval as Tick. When you create your Tick DB specify the number of bars to 4,000,000.

2.) In a Tick DB, Tomasz added the feature it will save the ASK and BID Prizes at Trade in Aux1 and Aux2, so this is how you retrieve the data.

3.) So you create a Tick DB and have one window with the Tick Interval. Then you lock this Window Interval. Then open a new Blank Chart and Symbol link that one with the Tick Window. So this new window is where you trade- 1, 5, 15 Minutes or so.

4.) Once you have setup the Tick DB and IQFeed, let me know and I will give you the code.

5.) You go File- Database settings - Configure - and tick the box 'Consolidate trades with same TickID'. As we know, "Smart Money" or as I call them- "The Big Boys", don't want us Retail trader to know what they are doing. I can be wrong, but that's probably why the data is unbundled and sent out. As unbundled Volumes you can still identify the Buy and Sell Volumes. But if you use the option to Consolidate trades you can see each individual order. Let's say a "Big Boy" place an order for 100 ES contracts, the unbundled data will just show you plenty of smaller Volumes and you have no idea where they comes from. But with this option enabled you will see the individual filled order of 100 contracts and you can then identify that order as coming from the Big Boys. Nice- right? :smile:

6,) I can provide you with the code once you have setup your Tick DB. Meanwhile here is why it could be of interest to identify Market and Limit Orders:

Market orders is often used by the Big Boys/Market Makers to push the price where they want it to go. Market orders change the price- most of the time. Limit Orders do not change price (majority of the time). So the Big Boys could for example use a series of small MKT orders to push up the price, which will make the "Herd" (mainly retail traders) to Buy and meanwhile the Big Boys have placed their big LMT Short orders to absorb the Buy Orders and we have the start of a potential price reversal. Basically you can read everywhere that to be successful in trading, you have follow the Smart Money. But that's not easy, unless you have the data and can read the chart and understand what are the Big Boys planning to do. So by identifying the Filled Buy/Sell Market and Limit orders you can create a MKT and LMT Cumulative or Wave Delta Volumes, which could like this:

The Yellow Dashed Line is the Standard Cumulative Delta Volume. Each Symbol has its own "personality" and much of the personality depends on the LMT/MKT order Ratio. ES for example has way more LMT Order Volume than MKT Order Volume. With NQ it's the other way around and that's why NQ could be seen to be little bit "fickle minded". :laughing:

There are obviously plenty of ways to plot the extra information we now can have thanks to Tomasz updates! Use your imagination and creativity!

Then just an heads-up! It's not just to setup the chart and plot the data. That's the easy part. The way more difficult part is to understand what you actually see and what it means for the Order Flow. Big Boys uses Price levels where they try to hide their business! Other levels they use to manipulate. Which means that you have to learn to read and understand what the big boys are doing over time, not just right now, but what have they done during the last 2- 3 days will give you insight of the planning going on. Big Boys go long at Discounted Prices and go Short at Premium Prices. So be prepared that you will have a long but interesting journey in front of you. At end of that journey you will have the potential to be in the small 3- 5% group of traders that constantly make profits (if you study the charts hard and long!). It's not an easy journey, but could be well worth it. :money_mouth_face:

There are lots of misleading information about trading on the internet, so be careful! Such as "For every Buyer, there must be a Seller". That's a very simplified way to put it! Or that the market is random! That's in my opinion as wrong as it gets! More or less every move in the market is carefully planned by the guys that makes the Big Bucks! So that's why it's a good idea to figure out what they are doing. :smile:

I am actually a very busy guy, so I have to run. Enjoy the chart setup!

Regards,

Jorgen

4 Likes
  1. I do have limited experience with amibroker. I'm having a lot of trouble with databases. I was able to set up a tick database, but when I go back to my other database my algo strats performance is all different than it was before so I'm not sure if I've done it right. Basically all my strats are performing much worse than before, so I don't know if the old results were wrong, or if I've screwed something up and can't replicate their performances. I can confirm I do have IQFeed data.
  2. I don't see a lock interval option, but I was able to create two different windows, one displaying tick data and another displaying 1 minute data. I was able to symbol link the two windows.
  3. Yes please
  4. When I go to file - database settings - configure - I don't see an option that says 'consolidate trades with the same TickID'. I'm using 6.40, do I need to install one of the beta versions?
  5. Yes please

Thank you so much again!

Hi kyleforb,

  1. ) Your performance might be affected by the fact that your Tick DB is handling 4 Million data points and I am sure that AmiBroker is the only program that can comfortably do that. Whch Symbols will you trade? I am trading @ES# only and 4 Million Ticks gives me 3 - 5 days of data depending how heavily ES is traded. @MES# gives me about 8- 10 days of data. So you can adjust the Tick Size of your DB to suit your needs. The lesser ticks, the faster processing. We will soon know if you have set it up properly.

2.) The Locking of Interval is the Pad Lock Symbol:

INTERVAL LOCK

3.) Let's start with the basics. Place this code in your Tick Chart (empottasch and I have fine-tuned this code together) :

_SECTION_BEGIN( "DELTA VOLUME MASTER" );
SetBarsRequired( sbrAll, sbrAll );
BI 		= BarIndex();
LastBI	= LastValue( BI );
Ticker	= Name();
TF 		= StaticVarGet( "~ChartTF" );
Ticker	= Name();

function SumSinceInclusive( Condition, Array )
{
    return SumSince( Condition, Array ) + ValueWhen( Condition, Array );
}

if( TF > 0 )
{
    LastBarOfPeriod = Nz( TimeFrameExpand( 1, TF, expandPoint ) );
    FirstBarOfPeriod = Ref( LastBarOfPeriod, -1 );
}
else
{
    ChartBI 				= Nz( StaticVarGet( "~ChartBI", True ) );
    LastBarOfPeriod 	= ChartBI != Ref( ChartBI, -1 );
    FirstBarOfPeriod 	= Ref( LastBarOfPeriod, -1 );
    LastBarOfPeriod[ BarCount - 1 ] = 1;
}

BuyVolume 	= IIf( Close >= Aux2 AND Aux2 != Aux1, Volume, 0 );
SellVolume 	= IIf( Close <= Aux1 AND Aux2 != Aux1, Volume, 0 );

CumBuyVolume 						= SumSinceInclusive( FirstBarOfPeriod, BuyVolume );
CumBuyVolumeTotal					= Ref( ValueWhen( LastBarOfPeriod OR BI == ( BarCount - 1 ), CumBuyVolume, 0 ), -1 );
LastValueCumBuyVolumeTotal	= LastValue( CumBuyVolumeTotal );

CumSellVolume 						= SumSinceInclusive( FirstBarOfPeriod, SellVolume );
CumSellVolumeTotal 					= Ref( ValueWhen( LastBarOfPeriod OR BI == ( BarCount - 1 ), CumSellVolume, 0 ), -1 );
LastValueCumSellVolumeTotal	= LastValue( CumSellVolumeTotal );

StaticVarSet( "~BuyVolume" + Ticker + TF, CumBuyVolumeTotal, False );
StaticVarSet( "~SellVolume" + Ticker + TF, CumSellVolumeTotal, False );
StaticVarSet( "~LastBuyVolume" + Ticker + TF, LastValueCumBuyVolumeTotal, False );
StaticVarSet( "~LastSellVolume" + Ticker + TF, LastValueCumSellVolumeTotal, False );

// calculate OHLC
DeltaVolume = CumBuyVolume - CumSellVolume; // intrabar DeltaVolume

// Intrabar Delta Volume Open
DeltaVolumeOpen = ValueWhen( FirstBarOfPeriod, DeltaVolume );
DeltaVolumeOpen = Ref( ValueWhen( LastBarOfPeriod OR BI == ( BarCount - 1 ), DeltaVolumeOpen, 0 ), -1 );
LastValueDeltaVolumeOpen = LastValue( DeltaVolumeOpen );

// Intrabar Delta Volume High
DeltaVolumeHigh = HighestSince( FirstBarOfPeriod, DeltaVolume );
DeltaVolumeHigh = Ref( ValueWhen( LastBarOfPeriod OR BI == ( BarCount - 1 ), DeltaVolumeHigh, 0 ), -1 );
LastValueDeltaVolumeHigh = LastValue( DeltaVolumeHigh );

// Intrabar Delta Volume Low
DeltaVolumeLow = LowestSince( FirstBarOfPeriod, DeltaVolume );
DeltaVolumeLow = Ref( ValueWhen( LastBarOfPeriod OR BI == ( BarCount - 1 ), DeltaVolumeLow, 0 ), -1 );
LastValueDeltaVolumeLow = LastValue( DeltaVolumeLow );

// Intrabar Delta Volume Close
DeltaVolumeClose = Ref( ValueWhen( LastBarOfPeriod OR BI == ( BarCount - 1 ), DeltaVolume, 0 ), -1 );
LastValueDeltaVolumeClose = LastValue( DeltaVolumeClose );

StaticVarSet( "~DeltaOpen" + Ticker + TF, DeltaVolumeOpen );
StaticVarSet( "~DeltaHigh" + Ticker + TF, DeltaVolumeHigh );
StaticVarSet( "~DeltaLow" + Ticker + TF, DeltaVolumeLow );
StaticVarSet( "~DeltaClose" + Ticker + TF, DeltaVolumeClose );

StaticVarSet( "~LastDeltaOpen" + Ticker + TF, LastValueDeltaVolumeOpen );
StaticVarSet( "~LastDeltaHigh" + Ticker + TF, LastValueDeltaVolumeHigh );
StaticVarSet( "~LastDeltaLow" + Ticker + TF, LastValueDeltaVolumeLow );
StaticVarSet( "~LastDeltaClose" + Ticker + TF, LastValueDeltaVolumeClose );

// PLOT THE TICK DATA:
SetBarFillColor( colorBrightGreen );
PlotOHLC( 0, BuyVolume, 0, BuyVolume, "BUY", colorBrightGreen, styleCandle, Null, Null, Null, 0, 1 );

SetBarFillColor( colorRed );
PlotOHLC( 0, SellVolume, 0, SellVolume, "SELL", colorRed, styleCandle, Null, Null, Null, 0, 1 );

Plot( Close, "CLOSE", colorWhite, styleLeftAxisScale | styleStaircase, Null, Null, Null, 2, 1 );

Plot( Aux2, "", colorPaleGreen, styleLeftAxisScale | styleDashed | styleStaircase, Null, Null, Null, 3, 1 );
Plot( Aux1, "", colorPink, styleLeftAxisScale | styleDashed | styleStaircase, Null, Null, Null, 3, 1 );

Title 		= "DELTA VOLUME MASTER";

_SECTION_END();

Then in your 1-Minute chart, Symbol linked to your Tick Chart, you add this code to it's own pane:

_SECTION_BEGIN( "MAIN CHART" );
DeltaType					= ParamToggle( "Delta Plot Style", "OHLC|LINE", 1 );
BI 							= BarIndex();
LastBI						= LastValue( BI );
Ticker						= Name();
TF 							= Interval( 1 );

// DATA FOR THE TICK CHART:
StaticVarSet( "~ChartTF", TF );
StaticVarSet( "~ChartBI", BI );

BuyVolume				= StaticVarGet( "~BuyVolume" + Ticker + TF );
LastBuyVolume			= StaticVarGet( "~LastBuyVolume" + Ticker + TF );
BuyVolume[ LastBI ]	= LastBuyVolume;

SellVolume				= StaticVarGet( "~SellVolume" + Ticker + TF );
LastSellVolume			= StaticVarGet( "~LastSellVolume" + Ticker + TF );
SellVolume[ LastBI ]	= LastSellVolume;

if( DeltaType )
{
    // STANDARD DELTA VOLUME LINE PLOT:
    DeltaVolume 			= Cum( BuyVolume - SellVolume );
    Plot( DeltaVolume, "DELTA VOLUME",  colorYellow, styleLine, Null, Null, 0, 0, 1 );
}
else
{
    // OHLC DELTA VOLUME PLOT:
    DeltaVolumeOpen					= StaticVarGet( "~DeltaOpen"  + Ticker + TF );
    LastValueDeltaVolumeOpen	= StaticVarGet( "~LastDeltaOpen" + Ticker + TF );
    DeltaVolumeOpen[ LastBI ]	= LastValueDeltaVolumeOpen;

    DeltaVolumeHigh					= StaticVarGet( "~DeltaHigh"  + Ticker + TF );
    LastValueDeltaVolumeHigh	= StaticVarGet( "~LastDeltaHigh" + Ticker + TF );
    DeltaVolumeHigh[ LastBI ]		= LastValueDeltaVolumeHigh;

    DeltaVolumeLow					= StaticVarGet( "~DeltaLow"  + Ticker + TF );
    LastValueDeltaVolumeLow		= StaticVarGet( "~LastDeltaLow" + Ticker + TF );
    DeltaVolumeLow[ LastBI ]		= LastValueDeltaVolumeLow;

    DeltaVolumeClose					= StaticVarGet( "~DeltaClose"  + Ticker + TF );
    LastValueDeltaVolumeClose	= StaticVarGet( "~LastDeltaClose" + Ticker + TF );
    DeltaVolumeClose[ LastBI ]	= LastValueDeltaVolumeClose;

    // CREATING THE CUMULATIVE DELTA OHLC:
    CumDeltaClose			= Cum( DeltaVolumeClose );
    CumDeltaOpen			= Ref( CumDeltaClose, -1 );
    CumDeltaHigh			= IIf( DeltaVolumeClose > DeltaVolumeOpen, CumDeltaClose + abs( DeltaVolumeHigh - DeltaVolumeClose ), CumDeltaOpen + abs( DeltaVolumeHigh - DeltaVolumeOpen ) );
    CumDeltaLow			= IIf( DeltaVolumeClose > DeltaVolumeOpen, CumDeltaOpen - abs( DeltaVolumeLow - DeltaVolumeOpen ), CumDeltaClose - abs( DeltaVolumeLow - DeltaVolumeClose ) );

    BarColor	= IIf( CumDeltaClose >= CumDeltaOpen, colorWhite, colorGrey50 );
    SetBarFillColor( IIf( CumDeltaClose >= CumDeltaOpen, colorWhite, colorGrey50 ) );

    PlotOHLC( CumDeltaOpen, CumDeltaHigh, CumDeltaLow, CumDeltaClose, "DELTA VOLUME", BarColor, styleCandle, Null, Null, 0, 0, 1 );
}

_SECTION_END();

So now you should be able to view the Cumulative Delta Volume with the option to plot it as Line or as a OHLC Plot. The MKT/LMT Volumes we take later once you have this up and running.

4.) Yes, you have to install the latest AB Beta Version. The new IQFeed Plugin is not yet officially launched.

No Problem, you are welcome! My thinking is like this: Tomasz is giving his AmiBroker Community the most amazing and solid support and I have worked with him several times when he created some solutions such as being able to identify Buy/Sell Volumes and Consolidate Trades in the IQFeed Plugin! So this is now my way to thank Tomasz by contributing my knowledge, so more users start to use his great features for more advanced analysis. The days when we could make profits using lagging data is long gone! As you know, it's all about Give and Take. So I would highly appreciate that anyone using my code will also share what they came up with. :smiley:

It would for sure be interesting if we could get a dialog going in the Forum discussing Advance Analysis on how to beat the "Big Boys" in their own game using the data we now have access to. The "Big Boys" have help of hundreds or programmers, we Retail Traders are on our own. So the more people on this Forum start to discuss this, the higher is the chance of trading success.

Regards,

Jorgen

5 Likes

Thank you so much for sharing!

I will definitely share if I figure something insightful out!

PS - Since DTN/IQFeed offers 6 months of tick data, I kind of thought I would be able to optimize parameters on a CVD strategy. But after seeing that 4 million bars is needed to display just a few days I'm not so sure. Is this kind of optimization possible? Maybe we could extend the barsize to 5000 ticks or something?

Hi kyleforb,

What Symbols are you Trading? As I mentioned, I am trading @ES# and that's why I get 4- 5 days only of data using 4 million tick bars.

If I understand your concern correctly, then you have the right people supporting you! :smiley:
Because empottasch and I had the same problem a few years ago, so we have the solution!

Please correct me if I am wrong, but your concern is that you need to run an Optimization on your CDV Strategy Variables and 5 days or so of data will not be enough and it runs not as fast using a Tick DB- correct?

The solution is to use your Tick DB for Trading and collection of daily Tick Data. Then you just export your CDV data etc to 1-Minute data which you import to your 1 Minute "Research DB" where you do your optimizations etc.

But you don't want to have a 1-Minute "Research DB" starting with with only a few days and then wait for 6 months to have a decent amount of data to Optimize- right?

What you do is basically using a Python Script and download the last 6 Month of Tick Data from IQFeed. Then another Python Script to convert this data to 1-Minute data which includes the Delta Volume. empottasch is an expert on this! :+1:

So you then import the 1-Minute Data into your 1-Minute "Research DB" where you do your CDV Optimization using 6 Months worth of data. Problem solved! :smiley:

You can do more or less anything when you use AmiBroker!

So did you manage to get my code up and running in your Tick DB? Let's focus on that for now and once it's running nicely, we can look into the 6 Months 1-Minute data solution.

Regards,

Jorgen

2 Likes

I am trading the @MES# for now until I build my trading account up enough to trade on the full @ES#. Yes that is exactly my concern!
I was able to get the two windows linked one for the tick and the other for the 1 minute and symbol link them, but in doing so I screwed something up and broke my amibroker. I emailed support, and they were able to help me fix it, and I'm still working with them on it. In the meantime I'm hesitant to change my base time interval or even setup a new database until I'm confident I won't break something. I'm like a monkey with a hammer in a server room :joy:
I did save the code and will let you know when I can get it running

Thanks for everything :pray: ,
Kyle

Sounds like a plan!

If my code is causing any problems, please let me know. I had to clean it up before I gave it to you, so it could be something in my code that needs to be fixed...

But you will be able to achieve what you want using AmiBroker- that's for sure!

Regards,

Jorgen

1 Like