Price_Distribution
Download scriptThis script is designed to analyze and visualize price behavior on a trading chart by identifying specific time windows and mapping out where the most trading activity occurred. Instead of just showing you where the price went, it helps you see where the price "spent the most time" or had the highest density of action.
Core Functionality
The script identifies a user-defined time range (for example, from 10:00 AM to 11:59 PM) for each day over a specified number of past days. For every one of these daily boxes, it performs three distinct visual actions:
1. Range Identification (The Box) The script scans the historical data for the high and low prices reached during your chosen time window. It then draws a large rectangular box on your chart to clearly define the boundaries of that day's price action.
2. Extremes Mapping (High/Low Lines) To make the boundaries even clearer, it draws horizontal lines at the highest and lowest points reached during that specific period. This allows you to see at a glance what the "ceiling" and "floor" were for that session.
3. Price Distribution Analysis (The Density Map) This is the most advanced part of the logic. The script takes the space between the High and Low and divides it into 50 equal "buckets" or zones. It then looks at every single minute of data within your time window to see which zones the price entered.
- Frequency Counting: If the price touched a specific zone, that zone gets a "tick."
- Visual Density: The script then draws smaller rectangles for each of these 50 buckets. The length of these rectangles is proportional to how many times the price visited that specific area.
How to Read the Results
When you look at the chart, you will see a series of boxes and horizontal lines. Within those boundaries, you will see a "distribution" of smaller bars:
- Longer Bars: Indicate areas where price spent a significant amount of time or experienced heavy concentration. These are your high-activity zones.
- Shorter/No Bars: Indicate areas where the price moved through very quickly or rarely visited.
By using this tool, you can visually identify "support" and "resistance" zones based on actual historical density rather than just single price points, helping you understand where the market feels most "crowded."
//+------------------------------------------------------------------+
//| Price_Distribution.mq4 |
//| Copyright © 2006, MetaQuotes Software Corp. |
//| http://www.metaquotes.net |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2006, MetaQuotes Software Corp."
#property link "http://www.metaquotes.net"
#define SECONDS_IN_MINUTE 60
#define SECONDS_IN_HOUR 3600
#define SECONDS_IN_DAY 86400
#define OBJNAME_PREPEND "ValueInRange"
#property indicator_chart_window
extern string BoxStart = "10:00";
extern string BoxEnd = "23:59";
extern string LineEnd = "10:00";
extern double PlotMagnification = 300.0;
extern color BoxColor = C'17,28,28';
extern color LineColor = DarkSlateGray;
extern color PlotColor = SlateGray;
extern int DaysToPlot = 10;
int init() {
}
int deinit() {
DeleteObjects(OBJNAME_PREPEND);
return(0);
}
int start() {
int iPlots = 0;
// Determine how far back to iterate
// Always recalc last complete bar
int iBarsToCalc = Bars - IndicatorCounted();
if (iBarsToCalc < Bars) iBarsToCalc++;
// Determine start time for current (or just closed) box
datetime dtBoxStart = StrToTime(TimeToStr(TimeCurrent(),TIME_DATE) + " " + BoxStart);
if (dtBoxStart > TimeCurrent()) dtBoxStart -= SECONDS_IN_DAY;
// Determine end time for current (or just closed) box
datetime dtBoxEnd = StrToTime(TimeToStr(dtBoxStart,TIME_DATE) + " " + BoxEnd);
if (dtBoxEnd < dtBoxStart) dtBoxEnd += SECONDS_IN_DAY;
// Simply loop, there are multiple ways to break
// While the box close time is after the oldest bar which needs calculation,
// calculate the box then add a day and repeat
while (true) {
// If the box close time is before the oldest bar which needs calculation, exit
if (dtBoxEnd < Time[iBarsToCalc]) return(0);
// If max number of iterations reached, exit
if (iPlots >= DaysToPlot) return(0);
// If the range to be plotted has data, plot it
if (iBarShift(NULL,PERIOD_M1,dtBoxStart,false) != iBarShift(NULL,PERIOD_M1,dtBoxEnd,false)) {
Print(TimeToStr(dtBoxStart), " ", TimeToStr(dtBoxEnd));
doCalcValueInRange(dtBoxStart);
iPlots++;
}
else {
Print("No data for ", TimeToStr(dtBoxStart), " ", TimeToStr(dtBoxEnd));
}
// Decrement dates
dtBoxStart -= SECONDS_IN_DAY;
dtBoxEnd -= SECONDS_IN_DAY;
}
}
void doCalcValueInRange(datetime dtBoxStart) {
// Determine box end time based on box start time
// Insure it's after box start
datetime dtBoxEnd = StrToTime(TimeToStr(dtBoxStart,TIME_DATE) + " " + BoxEnd);
if (dtBoxEnd < dtBoxStart) dtBoxEnd += SECONDS_IN_DAY;
// Determine line end time based on box end time
// Insure it's after box end
datetime dtLineEnd = StrToTime(TimeToStr(dtBoxEnd,TIME_DATE) + " " + LineEnd);
if (dtLineEnd < dtBoxEnd) dtLineEnd += SECONDS_IN_DAY;
// Figure out start and end bars for box on the minute chart
// Make sure the timestamp of start bar is >= box start time
int iStart = iBarShift(NULL,PERIOD_M1,dtBoxStart,false);
int iEnd = iBarShift(NULL,PERIOD_M1,dtBoxEnd,false);
while (iTime(NULL,PERIOD_M1,iStart) < dtBoxStart) iStart--;
// Get the high and low prices for the day
double dHigh = iHigh(NULL,PERIOD_M1,iHighest(NULL,PERIOD_M1,MODE_HIGH,iStart-iEnd,iEnd));
double dLow = iLow (NULL,PERIOD_M1,iLowest (NULL,PERIOD_M1,MODE_LOW ,iStart-iEnd,iEnd));
// Draw the box
doDrawBox(dtBoxStart,dtBoxEnd,dHigh,dLow);
// Draw the high/low lines
doDrawRangeLines(dtBoxStart,dtLineEnd,dHigh,dLow);
// Draw the price distribution
doDrawDistribution(dtBoxStart,dtLineEnd,dHigh,dLow);
}
void doDrawBox(datetime dtStart, datetime dtEnd, double dHigh, double dLow) {
// Draw the box
string sObjName = OBJNAME_PREPEND + " " + TimeToStr(dtStart);
ObjectCreate(sObjName,OBJ_RECTANGLE,0,dtStart,dHigh,dtEnd,dLow);
ObjectSet(sObjName,OBJPROP_COLOR,BoxColor);
}
void doDrawRangeLines(datetime dtStart, datetime dtEnd, double dHigh, double dLow) {
string sObjName;
// Draw range high
sObjName = OBJNAME_PREPEND + " " + TimeToStr(dtStart) + " " + "High";
ObjectCreate(sObjName,OBJ_TREND,0,dtStart,dHigh,dtEnd,dHigh);
ObjectSet(sObjName,OBJPROP_RAY,false);
ObjectSet(sObjName,OBJPROP_COLOR,LineColor);
// Draw range low
sObjName = OBJNAME_PREPEND + " " + TimeToStr(dtStart) + " " + "Low";
ObjectCreate(sObjName,OBJ_TREND,0,dtStart,dLow,dtEnd,dLow);
ObjectSet(sObjName,OBJPROP_RAY,false);
ObjectSet(sObjName,OBJPROP_COLOR,LineColor);
}
void doDrawDistribution(datetime dtStart, datetime dtEnd, double dHigh, double dLow) {
// Buckets to place 'ticks' into.
double dBuckets[50];
ArrayInitialize(dBuckets,0.0);
// Var to tally total 'ticks'
int iTicks = 0;
// Figure out the value to use to "split" prices into different buckets
double dSplitter = (dHigh - dLow) / ArraySize(dBuckets);
// Get the offset for the first minute bar of the range.
double iStartBar = iBarShift(NULL,PERIOD_M1,dtStart,false);
while (iTime(NULL,PERIOD_M1,iStartBar) < dtStart) iStartBar--;
// Get the offset for the last minute bar of the range.
double iEndBar = iBarShift(NULL,PERIOD_M1,dtEnd,false);
// Iterate all minutes of the day and place a 'tick' in each bucket
// that price touched
for (int i=iStartBar;i>=iEndBar;i--) {
for (int j=0;j<=ArraySize(dBuckets)-1;j++) {
double dBucketHigh = dHigh - (j) *dSplitter;
double dBucketLow = dHigh - (j+1)*dSplitter;
// If price was above bucket range, no tick
if (iLow(NULL,PERIOD_M1,i) > dBucketHigh)
continue;
// If price was below bucket range, no tick
if (iHigh(NULL,PERIOD_M1,i) <= dBucketLow)
continue;
// bucket gets a tick
dBuckets[j] += 1;
iTicks++;
}
}
//string sObjName = OBJNAME_PREPEND + " " + TimeToStr(dtStart) + " TickTotal";
//ObjectCreate(sObjName,OBJ_TEXT,0,dtStart,(dHigh+dLow)*0.5);
//ObjectSetText(sObjName,"Ticks: " + iTicks);
// Draw Bucket Lines
for (j=0;j<=ArraySize(dBuckets)-1;j++) {
dBucketHigh = dHigh - (j) *dSplitter;
dBucketLow = dHigh - (j+1)*dSplitter;
// Create a name for the bucket object
string sObjName = OBJNAME_PREPEND + " " + TimeToStr(dtStart) + " Bucket " + j+1;
// Determine how many bars long it will be. Each bar indicates bucket contained 5% of the price action
int iObjLength = 1;
if (iTicks > 0) iObjLength = (dBuckets[j]/iTicks)*PlotMagnification;
ObjectCreate(sObjName,OBJ_RECTANGLE,0,dtStart,dBucketHigh,dtStart+Period()*60*iObjLength,dBucketLow);
ObjectSet(sObjName,OBJPROP_COLOR,PlotColor);
}
}
void DeleteObjects(string sObjNamePrepend) {
// delete objects
for (int i=ObjectsTotal()-1;i>=0;i--) {
string sObjName = ObjectName(i);
if (StringFind(sObjName,sObjNamePrepend) == 0)
ObjectDelete(sObjName);
}
}
Comments