# Price Structure Suite 指标开发说明_2
//+------------------------------------------------------------------+
//| Price Structure Suite.mq4 |
//| Visualizes structure, price action patterns, volume, MTF bias, |
//| and risk metrics for discretionary traders. |
//+------------------------------------------------------------------+
property strict
property indicator_chart_window
property indicator_plots 4
property indicator_label1 "Swing High"
property indicator_type1 DRAW_ARROW
property indicator_color1 clrTomato
property indicator_style1 STYLE_SOLID
property indicator_width1 1
property indicator_label2 "Swing Low"
property indicator_type2 DRAW_ARROW
property indicator_color2 clrDeepSkyBlue
property indicator_style2 STYLE_SOLID
property indicator_width2 1
property indicator_label3 "Volume Boost"
property indicator_type3 DRAW_HISTOGRAM
property indicator_color3 clrGold
property indicator_style3 STYLE_SOLID
property indicator_width3 2
property indicator_label4 "Structure Bias"
property indicator_type4 DRAW_LINE
property indicator_color4 clrSilver
property indicator_style4 STYLE_DOT
property indicator_width4 1
//--- inputs
input int SwingDepth = 5; // fractal depth
input int MaxLookbackBars = 800; // max bars processed
input int VolumeLookback = 20; // volume average window
input double VolumeFactor = 1.6; // volume spike factor
input ENUM_TIMEFRAMES HigherTF = PERIOD_H4;
input int HigherMAPeriod = 55;
input int AtrPeriod = 14;
input double RiskPercent = 1.0; // account risk per trade (percent)
input double AccountValueOverride = 0.0; // custom equity override
input double PatternTolerancePips = 20.0; // allowed deviation for patterns
input double LabelOffsetPips = 15.0; // text/arrow offset
input bool EnableHeadShoulders = true;
input bool EnableTriangles = true;
input bool EnableDoubleTopBottom = true;
//--- buffers
double SwingHighBuffer[];
double SwingLowBuffer[];
double VolumeBoostBuffer[];
double StructureBiasBuffer[];
//--- internal state
define MAX_SWINGS 20
string PatternPrefix = "PS_PATTERN";
int patternIdCounter = 0;
int swingIndex[MAX_SWINGS];
double swingPrice[MAX_SWINGS];
int swingType[MAX_SWINGS]; // 1 = high, -1 = low
datetime swingTime[MAX_SWINGS];
int swingCount = 0;
//--- pattern anchors
int lastDoubleTopIdx = -1;
int lastDoubleBottomIdx = -1;
int lastHSPatternIdx = -1;
int lastInvHSPatternIdx = -1;
int lastAscTriangleIdx = -1;
int lastDescTriangleIdx = -1;
//--- function declarations
bool IsSwingHigh(const double &high[], int index);
bool IsSwingLow(const double &low[], int index);
void RegisterSwing(int index, double price, int type, datetime time);
int DetermineStructureBias();
int HigherTimeframeBias(datetime t);
void EvaluatePatterns();
bool DetectDoubleTop(double tolerance);
bool DetectDoubleBottom(double tolerance);
bool DetectHeadAndShoulders(double tolerance);
bool DetectInverseHeadAndShoulders(double tolerance);
bool DetectAscendingTriangle(double tolerance);
bool DetectDescendingTriangle(double tolerance);
void RenderPattern(int arrayPos, const string label, color clr, bool placeAbove);
double SimpleAverageVolume(const long &tick_volume[], int index);
void UpdateRiskDashboard();
double PipValue();
//+------------------------------------------------------------------+
int OnInit()
{
IndicatorSetString(INDICATOR_SHORTNAME, "Price Structure Suite");
SetIndexBuffer(0, SwingHighBuffer, INDICATOR_DATA);
SetIndexBuffer(1, SwingLowBuffer, INDICATOR_DATA);
SetIndexBuffer(2, VolumeBoostBuffer, INDICATOR_DATA);
SetIndexBuffer(3, StructureBiasBuffer, INDICATOR_DATA);
ArraySetAsSeries(SwingHighBuffer, true);
ArraySetAsSeries(SwingLowBuffer, true);
ArraySetAsSeries(VolumeBoostBuffer, true);
ArraySetAsSeries(StructureBiasBuffer, true);
PlotIndexSetInteger(0, PLOT_ARROW, 234); // up arrow
PlotIndexSetInteger(1, PLOT_ARROW, 241); // down arrow
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
PlotIndexSetInteger(2, PLOT_SHOW_DATA, true);
PlotIndexSetInteger(3, PLOT_SHOW_DATA, true);
SetIndexDrawBegin(2, VolumeLookback + SwingDepth);
SetIndexDrawBegin(3, SwingDepth);
IndicatorDigits(Digits());
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
ObjectsDeleteAll(0, PatternPrefix);
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
if(rates_total < SwingDepth * 2 + 10)
return prev_calculated;
int barsToProcess = rates_total;
if(MaxLookbackBars > 0 && MaxLookbackBars < rates_total)
barsToProcess = MaxLookbackBars;
// reset buffers
for(int i = 0; i < rates_total; i++)
{
SwingHighBuffer[i] = EMPTY_VALUE;
SwingLowBuffer[i] = EMPTY_VALUE;
VolumeBoostBuffer[i] = 0.0;
StructureBiasBuffer[i] = EMPTY_VALUE;
}
// reset swing and pattern state
swingCount = 0;
for(int j = 0; j < MAX_SWINGS; j++)
{
swingIndex[j] = -1;
swingPrice[j] = 0.0;
swingType[j] = 0;
swingTime[j] = 0;
}
lastDoubleTopIdx = -1;
lastDoubleBottomIdx = -1;
lastHSPatternIdx = -1;
lastInvHSPatternIdx = -1;
lastAscTriangleIdx = -1;
lastDescTriangleIdx = -1;
patternIdCounter = 0;
ObjectsDeleteAll(0, PatternPrefix);
int startIndex = MathMin(rates_total - SwingDepth - 1, barsToProcess);
for(int i = startIndex; i >= SwingDepth; i--)
{
bool swingHigh = IsSwingHigh(high, i);
bool swingLow = IsSwingLow(low, i);
if(swingHigh)
{
SwingHighBuffer[i] = high[i] + LabelOffsetPips * _Point;
RegisterSwing(i, high[i], 1, time[i]);
EvaluatePatterns();
}
else if(swingLow)
{
SwingLowBuffer[i] = low[i] - LabelOffsetPips * _Point;
RegisterSwing(i, low[i], -1, time[i]);
EvaluatePatterns();
}
int bias = DetermineStructureBias();
int higherBias = HigherTimeframeBias(time[i]);
StructureBiasBuffer[i] = bias + higherBias;
double avgVol = SimpleAverageVolume(tick_volume, i);
if(avgVol > 0 && tick_volume[i] > avgVol * VolumeFactor && (bias != 0 || higherBias != 0))
VolumeBoostBuffer[i] = (double)tick_volume[i];
else
VolumeBoostBuffer[i] = 0.0;
}
UpdateRiskDashboard();
return(rates_total);
}
//+------------------------------------------------------------------+
bool IsSwingHigh(const double &high[], int index)
{
for(int k = 1; k <= SwingDepth; k++)
{
if(high[index] <= high[index - k] || high[index] < high[index + k])
return(false);
}
return(true);
}
//+------------------------------------------------------------------+
bool IsSwingLow(const double &low[], int index)
{
for(int k = 1; k <= SwingDepth; k++)
{
if(low[index] >= low[index - k] || low[index] > low[index + k])
return(false);
}
return(true);
}
//+------------------------------------------------------------------+
void RegisterSwing(int index, double price, int type, datetime t)
{
for(int i = MAX_SWINGS - 1; i > 0; i--)
{
swingIndex[i] = swingIndex[i - 1];
swingPrice[i] = swingPrice[i - 1];
swingType[i] = swingType[i - 1];
swingTime[i] = swingTime[i - 1];
}
swingIndex[0] = index;
swingPrice[0] = price;
swingType[0] = type;
swingTime[0] = t;
if(swingCount < MAX_SWINGS)
swingCount++;
}
//+------------------------------------------------------------------+
int DetermineStructureBias()
{
if(swingCount < 4)
return(0);
double lastHigh = -1.0, prevHigh = -1.0;
double lastLow = -1.0, prevLow = -1.0;
for(int i = 0; i < swingCount; i++)
{
if(swingType[i] == 1)
{
if(lastHigh < 0.0)
lastHigh = swingPrice[i];
else if(prevHigh < 0.0)
prevHigh = swingPrice[i];
}
else if(swingType[i] == -1)
{
if(lastLow < 0.0)
lastLow = swingPrice[i];
else if(prevLow < 0.0)
prevLow = swingPrice[i];
}
if(lastHigh > 0 && prevHigh > 0 && lastLow > 0 && prevLow > 0)
break;
}
if(lastHigh < 0 || prevHigh < 0 || lastLow < 0 || prevLow < 0)
return(0);
if(lastHigh > prevHigh && lastLow > prevLow)
return(1);
if(lastHigh < prevHigh && lastLow < prevLow)
return(-1);
return(0);
}
//+------------------------------------------------------------------+
int HigherTimeframeBias(datetime t)
{
if(HigherTF == PERIOD_CURRENT)
return(0);
int shift = iBarShift(NULL, HigherTF, t, true);
if(shift < 0)
return(0);
double ma = iMA(NULL, HigherTF, HigherMAPeriod, 0, MODE_EMA, PRICE_CLOSE, shift);
double closePrice = iClose(NULL, HigherTF, shift);
if(closePrice > ma)
return(1);
if(closePrice < ma)
return(-1);
return(0);
}
//+------------------------------------------------------------------+
void EvaluatePatterns()
{
if(swingCount < 3)
return;
double tolerance = PatternTolerancePips _Point;
if(tolerance <= 0)
tolerance = 10 _Point;
if(EnableDoubleTopBottom)
{
if(swingType[0] == 1 && DetectDoubleTop(tolerance) && lastDoubleTopIdx != swingIndex[0])
{
RenderPattern(0, "Double Top", clrOrangeRed, true);
lastDoubleTopIdx = swingIndex[0];
}
if(swingType[0] == -1 && DetectDoubleBottom(tolerance) && lastDoubleBottomIdx != swingIndex[0])
{
RenderPattern(0, "Double Bottom", clrDeepSkyBlue, false);
lastDoubleBottomIdx = swingIndex[0];
}
}
if(EnableHeadShoulders)
{
if(swingType[0] == 1 && DetectHeadAndShoulders(tolerance) && lastHSPatternIdx != swingIndex[0])
{
RenderPattern(0, "Head & Shoulders", clrFireBrick, true);
lastHSPatternIdx = swingIndex[0];
}
if(swingType[0] == -1 && DetectInverseHeadAndShoulders(tolerance) && lastInvHSPatternIdx != swingIndex[0])
{
RenderPattern(0, "Inverse H&S", clrDodgerBlue, false);
lastInvHSPatternIdx = swingIndex[0];
}
}
if(EnableTriangles)
{
if(swingType[0] == -1 && DetectAscendingTriangle(tolerance) && lastAscTriangleIdx != swingIndex[0])
{
RenderPattern(0, "Ascending Triangle", clrMediumSeaGreen, false);
lastAscTriangleIdx = swingIndex[0];
}
if(swingType[0] == 1 && DetectDescendingTriangle(tolerance) && lastDescTriangleIdx != swingIndex[0])
{
RenderPattern(0, "Descending Triangle", clrDarkOrange, true);
lastDescTriangleIdx = swingIndex[0];
}
}
}
//+------------------------------------------------------------------+
bool DetectDoubleTop(double tolerance)
{
int firstHigh = -1, secondHigh = -1;
for(int i = 0; i < swingCount; i++)
{
if(swingType[i] != 1)
continue;
if(firstHigh == -1)
firstHigh = i;
else
{
secondHigh = i;
break;
}
}
if(firstHigh == -1 || secondHigh == -1)
return(false);
if(MathAbs(swingPrice[firstHigh] - swingPrice[secondHigh]) > tolerance)
return(false);
for(int j = firstHigh + 1; j < secondHigh; j++)
{
if(swingType[j] == -1)
return(true);
}
return(false);
}
//+------------------------------------------------------------------+
bool DetectDoubleBottom(double tolerance)
{
int firstLow = -1, secondLow = -1;
for(int i = 0; i < swingCount; i++)
{
if(swingType[i] != -1)
continue;
if(firstLow == -1)
firstLow = i;
else
{
secondLow = i;
break;
}
}
if(firstLow == -1 || secondLow == -1)
return(false);
if(MathAbs(swingPrice[firstLow] - swingPrice[secondLow]) > tolerance)
return(false);
for(int j = firstLow + 1; j < secondLow; j++)
{
if(swingType[j] == 1)
return(true);
}
return(false);
}
//+------------------------------------------------------------------+
bool DetectHeadAndShoulders(double tolerance)
{
if(swingCount < 5)
return(false);
if(!(swingType[0] == 1 && swingType[1] == -1 && swingType[2] == 1 &&
swingType[3] == -1 && swingType[4] == 1))
return(false);
double right = swingPrice[0];
double necklineRight = swingPrice[1];
double head = swingPrice[2];
double necklineLeft = swingPrice[3];
double left = swingPrice[4];
if(MathAbs(left - right) > tolerance)
return(false);
double neckline = (necklineLeft + necklineRight) / 2.0;
if(head <= left || head <= right)
return(false);
if(MathAbs(necklineRight - necklineLeft) > tolerance)
return(false);
if(head - neckline < tolerance * 0.5)
return(false);
return(true);
}
//+------------------------------------------------------------------+
bool DetectInverseHeadAndShoulders(double tolerance)
{
if(swingCount < 5)
return(false);
if(!(swingType[0] == -1 && swingType[1] == 1 && swingType[2] == -1 &&
swingType[3] == 1 && swingType[4] == -1))
return(false);
double right = swingPrice[0];
double necklineRight = swingPrice[1];
double head = swingPrice[2];
double necklineLeft = swingPrice[3];
double left = swingPrice[4];
if(MathAbs(left - right) > tolerance)
return(false);
double neckline = (necklineLeft + necklineRight) / 2.0;
if(head >= left || head >= right)
return(false);
if(MathAbs(necklineRight - necklineLeft) > tolerance)
return(false);
if(neckline - head < tolerance * 0.5)
return(false);
return(true);
}
//+------------------------------------------------------------------+
bool DetectAscendingTriangle(double tolerance)
{
if(swingCount < 5)
return(false);
double highs[2];
double lows[3];
int hCount = 0, lCount = 0;
for(int i = 0; i < swingCount && (hCount < 2 || lCount < 3); i++)
{
if(swingType[i] == 1 && hCount < 2)
highs[hCount++] = swingPrice[i];
else if(swingType[i] == -1 && lCount < 3)
lows[lCount++] = swingPrice[i];
}
if(hCount < 2 || lCount < 3)
return(false);
if(MathAbs(highs[0] - highs[1]) > tolerance)
return(false);
if(!(lows[0] > lows[1] && lows[1] > lows[2]))
return(false);
return(true);
}
//+------------------------------------------------------------------+
bool DetectDescendingTriangle(double tolerance)
{
if(swingCount < 5)
return(false);
double highs[3];
double lows[2];
int hCount = 0, lCount = 0;
for(int i = 0; i < swingCount && (hCount < 3 || lCount < 2); i++)
{
if(swingType[i] == 1 && hCount < 3)
highs[hCount++] = swingPrice[i];
else if(swingType[i] == -1 && lCount < 2)
lows[lCount++] = swingPrice[i];
}
if(hCount < 3 || lCount < 2)
return(false);
if(!(highs[0] < highs[1] && highs[1] < highs[2]))
return(false);
if(MathAbs(lows[0] - lows[1]) > tolerance)
return(false);
return(true);
}
//+------------------------------------------------------------------+
void RenderPattern(int arrayPos, const string label, color clr, bool placeAbove)
{
if(arrayPos >= swingCount || arrayPos < 0)
return;
string name = PatternPrefix + "_" + IntegerToString(patternIdCounter++);
double offset = LabelOffsetPips * _Point;
double price = swingPrice[arrayPos];
datetime t = swingTime[arrayPos];
if(placeAbove)
price += offset;
else
price -= offset;
if(!ObjectCreate(0, name, OBJ_TEXT, 0, t, price))
return;
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_ANCHOR, placeAbove ? ANCHOR_LOWER : ANCHOR_UPPER);
ObjectSetInteger(0, name, OBJPROP_BACK, false);
ObjectSetText(name, label, 8, "Arial", clr);
}
//+------------------------------------------------------------------+
double SimpleAverageVolume(const long &tick_volume[], int index)
{
double sum = 0.0;
int count = 0;
for(int i = 0; i < VolumeLookback && (index + i) < Bars; i++)
{
sum += (double)tick_volume[index + i];
count++;
}
if(count == 0)
return(0.0);
return(sum / count);
}
//+------------------------------------------------------------------+
double PipValue()
{
double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double tickSize = MarketInfo(Symbol(), MODE_TICKSIZE);
if(tickValue <= 0.0 || tickSize <= 0.0)
return(0.0);
return(tickValue / tickSize _Point);
}
//+------------------------------------------------------------------+
void UpdateRiskDashboard()
{
double equity = (AccountValueOverride > 0.0) ? AccountValueOverride : AccountEquity();
double atr = iATR(NULL, 0, AtrPeriod, 0);
double pipVal = PipValue();
double riskMoney = equity (RiskPercent / 100.0);
double suggestedLots = 0.0;
double atrPips = 0.0;
if(atr > 0.0 && pipVal > 0.0)
{
atrPips = atr / _Point;
double riskPerLot = atr * pipVal;
if(riskPerLot > 0.0)
suggestedLots = riskMoney / riskPerLot;
}
string text = StringFormat("ATR(%d): %.1f pips | Risk %.2f%% approx %.2f | Suggested lots approx %.2f",
AtrPeriod,
atrPips,
RiskPercent,
riskMoney,
suggestedLots);
Comment(text);
}
//+------------------------------------------------------------------+
