// SevenBandVWAP TickReactive HFT
// H1FT_EA.mq5
// Research code: validate with TICKS and broker-specific spread/volume data.
#include <default.c>
// -----------------------------------------------------------------------------
// Enumerations
// -----------------------------------------------------------------------------
#define PR_CLOSE 0
#define PR_OPEN 1
#define PR_HIGH 2
#define PR_LOW 3
#define PR_MEDIAN 4
#define PR_TYPICAL 5
#define PR_WEIGHTED 6
#define PR_AVERAGE 7
#define PR_MEDIAN_BODY 8
#define PR_TBIASED 9
#define PR_TBIASED2 10
#define ST_FLAT 0
#define ST_LONG_ARMED 1
#define ST_LONG_ACTIVE 2
#define ST_LONG_HEDGED 3
#define ST_LONG_RECOVERY 4
#define ST_SHORT_ARMED 5
#define ST_SHORT_ACTIVE 6
#define ST_SHORT_HEDGED 7
#define ST_SHORT_RECOVERY 8
#define ST_EXIT_PENDING 9
#define ST_COOLDOWN 10
#define ST_EMERGENCY 11
#define B_D3 0
#define B_D2 1
#define B_D1 2
#define B_M 3
#define B_U1 4
#define B_U2 5
#define B_U3 6
#define ROLE_PRIMARY_LONG 101
#define ROLE_PRIMARY_SHORT 102
#define ROLE_H1 201
#define ROLE_H2 202
#define ROLE_H3 203
#define ROLE_HFT_LONG 301
#define ROLE_HFT_SHORT 302
#define TV_ROLE 0
#define TV_BAND 1
#define TV_OPEN_MS 2
#define TV_SCORE 3
#define TV_CYCLE 4
#define TV_SLICE 5
// -----------------------------------------------------------------------------
// User parameters. Convert selected values to optimize(...) when desired.
// -----------------------------------------------------------------------------
int AvgPeriod = 20;
int PriceMode = PR_CLOSE;
int UseRealVolume = 0; // Zorro normally exposes tick volume through marketVol().
int DeviationSample = 0;
var DeviationMultiplier1 = 1.0;
var DeviationMultiplier2 = 2.0;
var DeviationMultiplier3 = 2.5;
var MinimumWickRatio = 0.35;
var MinimumWickDominance = 1.25;
var MinimumBodyRatio = 0.20;
var AcceptanceBodyRatio = 0.40;
var AcceptanceCloseLevel = 0.70;
int ConfirmationBars = 2;
int EnableLong = 1;
int EnableShort = 1;
int EnableHedging = 1;
int MaximumHoldingBars = 100;
int CooldownBars = 3;
var EmergencySigmaBuffer = 0.15;
var HedgeRatio1 = 0.33;
var HedgeRatio2 = 0.66;
var HedgeRatio3 = 1.00;
var PartialAtD2U2 = 0.10;
var PartialAtD1U1 = 0.10;
var PartialAtMiddle = 0.20;
var PartialAtU1D1 = 0.15;
var PartialAtU2D2 = 0.20;
int UseFixedLots = 1;
var FixedLots = 0.10;
var RiskPercent = 0.50;
var MaximumSpreadPoints = 50;
int UseBrokerEmergencyStop = 1;
int EnableTickReactiveHFT = 1;
int EnableBarStateMachine = 0;
int HFTUseFixedLots = 1;
var HFTFixedLots = 0.01;
var HFTRiskPercent = 0.05;
var HFTMaximumSpreadPoints = 200;
int HFTMaximumOpenPositions = 50;
int HFTMaximumPositionsPerSide = 25;
int HFTMaximumEntriesPerTick = 2;
int HFTMaximumClosuresPerTick = 10;
int HFTMaximumEntriesPerSecond = 10;
int HFTMaximumEntriesPerMinute = 200;
int HFTMaximumDailyEntries = 3000;
int HFTMinimumMillisecondsBetweenEntries = 50;
int HFTSignalCooldownMilliseconds = 100;
int HFTMaximumHoldingMilliseconds = 30000;
var HFTTakeProfitPoints = 20;
var HFTStopLossPoints = 30;
int HFTUseAdjacentBandTargets = 1;
int HFTCloseOnOppositeSignal = 1;
int HFTAllowOppositePositions = 1;
int HFTEnableBandCrossEntries = 1;
int HFTEnableZoneTransitionEntries = 1;
int HFTEnableWickReclaimEntries = 1;
int HFTEnableMomentumEntries = 1;
int HFTMinimumSignalScore = 2;
var HFTMinimumWickRatio = 0.10;
var HFTMinimumWickDominance = 0.75;
var HFTMinimumBodyRatio = 0.00;
var HFTMinimumTickMomentumPoints = 1;
int HFTMinimumTicksPerSecond = 1;
int HFTRequireCostEdge = 0;
var HFTMinimumEdgeCostMultiple = 1.20;
int HFTWriteDiagnostics = 1;
int WriteDiagnostics = 1;
// -----------------------------------------------------------------------------
// Data structures
// -----------------------------------------------------------------------------
typedef struct SNAPSHOT {
DATE Time;
var O,H,L,C,Selected,VWAP,Dev;
var Band[7];
var TickVolume;
var SpreadPoints;
var Range,Body,UW,LW,UWR,LWR,BR,CLV,WI;
int LowerDom,UpperDom,BullReject,BearReject;
} SNAPSHOT;
typedef struct CONTEXT {
int Mode;
int Progress;
int HedgeDepth;
int ArmAge;
int Holding;
int Cooldown;
var RejectionHigh;
var RejectionLow;
var InitialLots;
long CycleID;
} CONTEXT;
CONTEXT Ctx;
// HFT state.
var HFTPrevPrice = 0;
var HFTPrevBand[7];
int HFTPrevZone = -1;
int HFTStateReady = 0;
long HFTLastEntryMS = 0;
long HFTLastLongSignalMS[7];
long HFTLastShortSignalMS[7];
long HFTSecondWindowMS = 0;
long HFTMinuteWindowMS = 0;
long HFTTickWindowMS = 0;
int HFTEntriesSecond = 0;
int HFTEntriesMinute = 0;
int HFTTicksCurrentSecond = 0;
int HFTTicksLastSecond = 0;
int HFTDailyEntries = 0;
int HFTDayKey = 0;
long HFTTickCounter = 0;
long HFTEntryCounter = 0;
long HFTCloseCounter = 0;
// Forming candle state used by tick().
DATE FormingBarTime = 0;
var FormingOpen = 0;
var FormingHigh = 0;
var FormingLow = 0;
var FormingClose = 0;
var FormingTickVolume = 0;
// Completed bar series, initialized in run().
vars SOpen, SHigh, SLow, SClose, SVolume;
int SeriesReady = 0;
// Diagnostics handles.
int BarLog = 0;
int HFTLogFile = 0;
// -----------------------------------------------------------------------------
// Utility
// -----------------------------------------------------------------------------
long NowMS()
{
// wdate() is expressed in days. This preserves sub-second precision when
// supplied by the feed, but some Zorro feeds only timestamp to seconds.
return (long)(wdate()*86400000.0);
}
int CurrentDayKey()
{
return year()*10000 + month()*100 + day();
}
string ModeName(int Mode)
{
if(Mode == ST_FLAT) return "FLAT";
if(Mode == ST_LONG_ARMED) return "LONG_ARMED";
if(Mode == ST_LONG_ACTIVE) return "LONG_ACTIVE";
if(Mode == ST_LONG_HEDGED) return "LONG_HEDGED";
if(Mode == ST_LONG_RECOVERY) return "LONG_RECOVERY";
if(Mode == ST_SHORT_ARMED) return "SHORT_ARMED";
if(Mode == ST_SHORT_ACTIVE) return "SHORT_ACTIVE";
if(Mode == ST_SHORT_HEDGED) return "SHORT_HEDGED";
if(Mode == ST_SHORT_RECOVERY) return "SHORT_RECOVERY";
if(Mode == ST_EXIT_PENDING) return "EXIT_PENDING";
if(Mode == ST_COOLDOWN) return "COOLDOWN";
if(Mode == ST_EMERGENCY) return "EMERGENCY";
return "UNKNOWN";
}
string BandName(int Band)
{
if(Band == B_D3) return "D3";
if(Band == B_D2) return "D2";
if(Band == B_D1) return "D1";
if(Band == B_M) return "M";
if(Band == B_U1) return "U1";
if(Band == B_U2) return "U2";
if(Band == B_U3) return "U3";
return "NONE";
}
void ResetContext()
{
Ctx.Mode = ST_FLAT;
Ctx.Progress = -1;
Ctx.HedgeDepth = 0;
Ctx.ArmAge = 0;
Ctx.Holding = 0;
Ctx.Cooldown = 0;
Ctx.RejectionHigh = 0;
Ctx.RejectionLow = 0;
Ctx.InitialLots = 0;
}
var SelectedPriceAt(int Shift)
{
var O = SOpen[Shift];
var H = SHigh[Shift];
var L = SLow[Shift];
var C = SClose[Shift];
if(PriceMode == PR_OPEN) return O;
if(PriceMode == PR_HIGH) return H;
if(PriceMode == PR_LOW) return L;
if(PriceMode == PR_MEDIAN) return 0.5*(H+L);
if(PriceMode == PR_TYPICAL) return (H+L+C)/3.;
if(PriceMode == PR_WEIGHTED) return (H+L+2*C)/4.;
if(PriceMode == PR_AVERAGE) return (O+H+L+C)/4.;
if(PriceMode == PR_MEDIAN_BODY) return 0.5*(O+C);
if(PriceMode == PR_TBIASED) {
if(C > O) return 0.5*(H+C);
return 0.5*(L+C);
}
if(PriceMode == PR_TBIASED2) {
if(C>O) return H;
if(C<O) return L;
return C;
}
return C;
}
var SelectedFormingPrice()
{
if(PriceMode == PR_OPEN) return FormingOpen;
if(PriceMode == PR_HIGH) return FormingHigh;
if(PriceMode == PR_LOW) return FormingLow;
if(PriceMode == PR_MEDIAN) return 0.5*(FormingHigh+FormingLow);
if(PriceMode == PR_TYPICAL) return (FormingHigh+FormingLow+FormingClose)/3.;
if(PriceMode == PR_WEIGHTED) return (FormingHigh+FormingLow+2*FormingClose)/4.;
if(PriceMode == PR_AVERAGE) return (FormingOpen+FormingHigh+FormingLow+FormingClose)/4.;
if(PriceMode == PR_MEDIAN_BODY) return 0.5*(FormingOpen+FormingClose);
if(PriceMode == PR_TBIASED) {
if(FormingClose > FormingOpen) return 0.5*(FormingHigh+FormingClose);
return 0.5*(FormingLow+FormingClose);
}
if(PriceMode == PR_TBIASED2) {
if(FormingClose>FormingOpen) return FormingHigh;
if(FormingClose<FormingOpen) return FormingLow;
return FormingClose;
}
return FormingClose;
}
void CompleteSnapshot(SNAPSHOT* S)
{
S->Band[B_D3] = S->VWAP-DeviationMultiplier3*S->Dev;
S->Band[B_D2] = S->VWAP-DeviationMultiplier2*S->Dev;
S->Band[B_D1] = S->VWAP-DeviationMultiplier1*S->Dev;
S->Band[B_M] = S->VWAP;
S->Band[B_U1] = S->VWAP+DeviationMultiplier1*S->Dev;
S->Band[B_U2] = S->VWAP+DeviationMultiplier2*S->Dev;
S->Band[B_U3] = S->VWAP+DeviationMultiplier3*S->Dev;
S->Range = S->H-S->L;
S->Body = abs(S->C-S->O);
S->UW = S->H-max(S->O,S->C);
S->LW = min(S->O,S->C)-S->L;
var R = max(S->Range,0.1*PIP);
S->UWR = S->UW/R;
S->LWR = S->LW/R;
S->BR = S->Body/R;
S->CLV = (S->C-S->L)/R;
S->WI = (S->LW-S->UW)/R;
S->LowerDom = S->LWR>=MinimumWickRatio && S->LW>=MinimumWickDominance*max(S->UW,0.1*PIP) && S->BR>=MinimumBodyRatio;
S->UpperDom = S->UWR>=MinimumWickRatio && S->UW>=MinimumWickDominance*max(S->LW,0.1*PIP) && S->BR>=MinimumBodyRatio;
S->BullReject = S->C>S->O && S->LowerDom && S->CLV>=0.65;
S->BearReject = S->C<S->O && S->UpperDom && S->CLV<=0.35;
}
int MakeBarSnapshot(int Shift, SNAPSHOT* S)
{
if(!SeriesReady) return 0;
S->Time = 0;
S->O = SOpen[Shift];
S->H = SHigh[Shift];
S->L = SLow[Shift];
S->C = SClose[Shift];
S->Selected = SelectedPriceAt(Shift);
S->TickVolume = SVolume[Shift];
S->SpreadPoints = Spread/PIP;
var WSum = 0, VSum = 0, Mean = 0, M2 = 0;
int Count = 0;
int K;
for(K=0; K<AvgPeriod; K++) {
int I = Shift+K;
var X = SelectedPriceAt(I);
var V = max(SVolume[I],1);
WSum += V*X;
VSum += V;
Count++;
var D = X-Mean;
Mean += D/Count;
M2 += D*(X-Mean);
}
if(VSum != 0)
S->VWAP = WSum/VSum;
else
S->VWAP = S->Selected;
S->Dev = sqrt(max(M2,0)/max(Count-ifelse(DeviationSample,1,0),1));
CompleteSnapshot(S);
return 1;
}
int MakeTickSnapshot(SNAPSHOT* S)
{
if(!SeriesReady || FormingOpen <= 0) return 0;
S->Time = wdate();
S->O = FormingOpen;
S->H = FormingHigh;
S->L = FormingLow;
S->C = FormingClose;
S->Selected = SelectedFormingPrice();
S->TickVolume = FormingTickVolume;
S->SpreadPoints = Spread/PIP;
var WSum = max(FormingTickVolume,1)*S->Selected;
var VSum = max(FormingTickVolume,1);
var Mean = S->Selected;
var M2 = 0;
int Count = 1;
int K;
for(K=0; K<AvgPeriod-1; K++) {
var X = SelectedPriceAt(K);
var V = max(SVolume[K],1);
WSum += V*X;
VSum += V;
Count++;
var D = X-Mean;
Mean += D/Count;
M2 += D*(X-Mean);
}
S->VWAP = WSum/VSum;
S->Dev = sqrt(max(M2,0)/max(Count-ifelse(DeviationSample,1,0),1));
CompleteSnapshot(S);
return 1;
}
int Touch(SNAPSHOT* S,int B) { return S->L<=S->Band[B] && S->H>=S->Band[B]; }
int CrossUp(SNAPSHOT* C,SNAPSHOT* P,int B) { return P->C<=P->Band[B] && C->C>C->Band[B]; }
int CrossDown(SNAPSHOT* C,SNAPSHOT* P,int B) { return P->C>=P->Band[B] && C->C<C->Band[B]; }
int LowerReclaim(SNAPSHOT* S,int B) { return S->L<S->Band[B] && S->C>S->Band[B] && S->BullReject; }
int UpperReclaim(SNAPSHOT* S,int B) { return S->H>S->Band[B] && S->C<S->Band[B] && S->BearReject; }
int AcceptUp(SNAPSHOT* C,SNAPSHOT* P,int B)
{
if(CrossUp(C,P,B) && C->BR>=AcceptanceBodyRatio && C->CLV>=AcceptanceCloseLevel) return 1;
return C->C>C->Band[B] && P->C>P->Band[B] && C->BR>=AcceptanceBodyRatio && C->CLV>=AcceptanceCloseLevel;
}
int AcceptDown(SNAPSHOT* C,SNAPSHOT* P,int B)
{
if(CrossDown(C,P,B) && C->BR>=AcceptanceBodyRatio && C->CLV<=1.-AcceptanceCloseLevel) return 1;
return C->C<C->Band[B] && P->C<P->Band[B] && C->BR>=AcceptanceBodyRatio && C->CLV<=1.-AcceptanceCloseLevel;
}
int ZoneAt(var Price,SNAPSHOT* S)
{
if(Price<S->Band[B_D3]) return 0;
if(Price<S->Band[B_D2]) return 1;
if(Price<S->Band[B_D1]) return 2;
if(Price<S->Band[B_M]) return 3;
if(Price<S->Band[B_U1]) return 4;
if(Price<S->Band[B_U2]) return 5;
if(Price<S->Band[B_U3]) return 6;
return 7;
}
// -----------------------------------------------------------------------------
// Trade helpers. TradeVar tags replace MT5 magic numbers and comments.
// -----------------------------------------------------------------------------
int IsMacroRole(int Role) { return Role>=ROLE_PRIMARY_LONG && Role<=ROLE_H3; }
int IsHFTRole(int Role) { return Role==ROLE_HFT_LONG || Role==ROLE_HFT_SHORT; }
int CountTradesByRole(int Role)
{
int N = 0;
for(open_trades) if((int)TradeVar[TV_ROLE] == Role) N++;
return N;
}
int CountHFTTrades(int Side)
{
int N = 0;
for(open_trades) {
int Role = (int)TradeVar[TV_ROLE];
if(!IsHFTRole(Role)) continue;
if(Side>0 && !TradeIsLong) continue;
if(Side<0 && TradeIsLong) continue;
N++;
}
return N;
}
var LotsByRole(int Role)
{
var Total = 0;
for(open_trades) if((int)TradeVar[TV_ROLE] == Role) Total += TradeLots;
return Total;
}
var PrimaryLongLots() { return LotsByRole(ROLE_PRIMARY_LONG); }
var PrimaryShortLots() { return LotsByRole(ROLE_PRIMARY_SHORT); }
var PrimaryLots() { return PrimaryLongLots()+PrimaryShortLots(); }
var HedgeLots() { return LotsByRole(ROLE_H1)+LotsByRole(ROLE_H2)+LotsByRole(ROLE_H3); }
int ActualHedgeDepth()
{
if(CountTradesByRole(ROLE_H3)>0) return 3;
if(CountTradesByRole(ROLE_H2)>0) return 2;
if(CountTradesByRole(ROLE_H1)>0) return 1;
return 0;
}
var MacroEntryLots(int IsLong,SNAPSHOT* S)
{
if(UseFixedLots) return FixedLots;
var EntryPrice = priceClose();
var StopPrice;
if(IsLong)
StopPrice = S->Band[B_D3]-EmergencySigmaBuffer*S->Dev;
else
StopPrice = S->Band[B_U3]+EmergencySigmaBuffer*S->Dev;
var RiskDistance = abs(EntryPrice-StopPrice);
if(RiskDistance<=0 || PIPCost<=0) return FixedLots;
var RiskMoney = Balance*RiskPercent/100.;
return max(1, RiskMoney/(RiskDistance/PIP*PIPCost));
}
TRADE* OpenTaggedTrade(int IsLong,var TradeLotsValue,int Role,int Band,int Score,long OpenMS)
{
TradeLotsValue = max(TradeLotsValue,1);
TRADE* T;
if(IsLong)
T = enterLong(TradeLotsValue);
else
T = enterShort(TradeLotsValue);
if(T) {
T->Skill[TV_ROLE] = Role;
T->Skill[TV_BAND] = Band;
T->Skill[TV_OPEN_MS] = OpenMS;
T->Skill[TV_SCORE] = Score;
T->Skill[TV_CYCLE] = Ctx.CycleID;
}
return T;
}
int CloseRole(int Role)
{
int Closed = 0;
for(open_trades) {
if((int)TradeVar[TV_ROLE] != Role) continue;
exitTrade(ThisTrade);
Closed++;
}
return Closed;
}
void CloseAllMacro()
{
for(open_trades) if(IsMacroRole((int)TradeVar[TV_ROLE])) exitTrade(ThisTrade);
}
// Zorro partial-close behavior differs across broker plugins. This function
// uses exitTrade(ThisTrade,Lots) where supported. When unsupported, replace
// it with primary slices at entry or a broker-specific order command.
int PartialPrimary(int IsLong,var Fraction)
{
int Role;
if(IsLong)
Role = ROLE_PRIMARY_LONG;
else
Role = ROLE_PRIMARY_SHORT;
var TargetLots = Ctx.InitialLots*Fraction;
for(open_trades) {
if((int)TradeVar[TV_ROLE] != Role) continue;
var CloseLots = min(TradeLots,TargetLots);
if(CloseLots > 0) {
exitTrade(ThisTrade,CloseLots);
return 1;
}
}
return 0;
}
int OpenPrimary(int IsLong,SNAPSHOT* S)
{
var L = MacroEntryLots(IsLong,S);
Ctx.CycleID++;
int Role;
if(IsLong)
Role = ROLE_PRIMARY_LONG;
else
Role = ROLE_PRIMARY_SHORT;
TRADE* T = OpenTaggedTrade(IsLong,L,Role,-1,0,NowMS());
if(!T) return 0;
Ctx.InitialLots = L;
return 1;
}
int OpenHedge(int IsLong,int Stage,var Ratio)
{
if(!EnableHedging) return 0;
var Primary = PrimaryLots();
var Target = Ratio*Primary;
var AddLots = Target-HedgeLots();
if(AddLots <= 0) return 0;
int Role = ROLE_H3;
if(Stage == 1)
Role = ROLE_H1;
else if(Stage == 2)
Role = ROLE_H2;
return OpenTaggedTrade(IsLong,AddLots,Role,-1,0,NowMS()) != 0;
}
void EnterCooldown()
{
Ctx.Mode = ST_COOLDOWN;
Ctx.Progress = -1;
Ctx.HedgeDepth = 0;
Ctx.Holding = 0;
Ctx.Cooldown = CooldownBars;
Ctx.InitialLots = 0;
}
void CloseAllToCooldown()
{
CloseAllMacro();
EnterCooldown();
}
// -----------------------------------------------------------------------------
// Bar state machine
// -----------------------------------------------------------------------------
var LongPart(int B)
{
if(B==B_D2) return PartialAtD2U2;
if(B==B_D1) return PartialAtD1U1;
if(B==B_M) return PartialAtMiddle;
if(B==B_U1) return PartialAtU1D1;
if(B==B_U2) return PartialAtU2D2;
return 0;
}
var ShortPart(int B)
{
if(B==B_U2) return PartialAtD2U2;
if(B==B_U1) return PartialAtD1U1;
if(B==B_M) return PartialAtMiddle;
if(B==B_D1) return PartialAtU1D1;
if(B==B_D2) return PartialAtU2D2;
return 0;
}
int LongFinal(SNAPSHOT* C,SNAPSHOT* P)
{
return AcceptUp(C,P,B_U3) || UpperReclaim(C,B_U3) || (Touch(C,B_U3)&&C->UpperDom&&C->C<C->Band[B_U3]);
}
int ShortFinal(SNAPSHOT* C,SNAPSHOT* P)
{
return AcceptDown(C,P,B_D3) || LowerReclaim(C,B_D3) || (Touch(C,B_D3)&&C->LowerDom&&C->C>C->Band[B_D3]);
}
int LongEmergency(SNAPSHOT* C)
{
if(C->C<C->Band[B_D3]-EmergencySigmaBuffer*C->Dev) return 1;
if(Ctx.Progress>=B_U1 && Ctx.HedgeDepth>=3 && C->C<C->Band[B_M]) return 1;
return Ctx.Holding>=MaximumHoldingBars;
}
int ShortEmergency(SNAPSHOT* C)
{
if(C->C>C->Band[B_U3]+EmergencySigmaBuffer*C->Dev) return 1;
if(Ctx.Progress<=B_D1 && Ctx.HedgeDepth>=3 && C->C>C->Band[B_M]) return 1;
return Ctx.Holding>=MaximumHoldingBars;
}
int LAdv(int D) { return max(Ctx.Progress-D,B_D3); }
int SAdv(int D) { return min(Ctx.Progress+D,B_U3); }
int LongH1(SNAPSHOT* C,SNAPSHOT* P)
{
if(!EnableHedging || Ctx.Progress<B_M || Ctx.HedgeDepth!=0) return 0;
int B=LAdv(1); return C->C<C->Band[B] && (AcceptDown(C,P,B)||UpperReclaim(C,Ctx.Progress));
}
int LongH2(SNAPSHOT* C,SNAPSHOT* P)
{
if(Ctx.HedgeDepth!=1) return 0; int B=LAdv(2); return C->C<C->Band[B] && (AcceptDown(C,P,B)||C->BearReject);
}
int LongH3(SNAPSHOT* C,SNAPSHOT* P)
{
if(Ctx.HedgeDepth!=2) return 0; int B=LAdv(3); return C->C<C->Band[B] && AcceptDown(C,P,B);
}
int ShortH1(SNAPSHOT* C,SNAPSHOT* P)
{
if(!EnableHedging || Ctx.Progress>B_M || Ctx.HedgeDepth!=0) return 0;
int B=SAdv(1); return C->C>C->Band[B] && (AcceptUp(C,P,B)||LowerReclaim(C,Ctx.Progress));
}
int ShortH2(SNAPSHOT* C,SNAPSHOT* P)
{
if(Ctx.HedgeDepth!=1) return 0; int B=SAdv(2); return C->C>C->Band[B] && (AcceptUp(C,P,B)||C->BullReject);
}
int ShortH3(SNAPSHOT* C,SNAPSHOT* P)
{
if(Ctx.HedgeDepth!=2) return 0; int B=SAdv(3); return C->C>C->Band[B] && AcceptUp(C,P,B);
}
int AdvanceLong(SNAPSHOT* C,SNAPSHOT* P)
{
int Next=Ctx.Progress+1;
if(Next>B_U3 || !AcceptUp(C,P,Next)) return 0;
if(Next==B_U3) { CloseAllToCooldown(); return 1; }
var F=LongPart(Next); if(F>0) PartialPrimary(1,F);
Ctx.Progress=Next; return 1;
}
int AdvanceShort(SNAPSHOT* C,SNAPSHOT* P)
{
int Next=Ctx.Progress-1;
if(Next<B_D3 || !AcceptDown(C,P,Next)) return 0;
if(Next==B_D3) { CloseAllToCooldown(); return 1; }
var F=ShortPart(Next); if(F>0) PartialPrimary(0,F);
Ctx.Progress=Next; return 1;
}
void ProcessMacroBar()
{
SNAPSHOT C,P;
if(!MakeBarSnapshot(1,&C) || !MakeBarSnapshot(2,&P)) return;
if(Ctx.Mode==ST_FLAT) {
if(EnableLong && LowerReclaim(&C,B_D3)) {
Ctx.Mode=ST_LONG_ARMED; Ctx.ArmAge=0; Ctx.RejectionHigh=C.H; Ctx.RejectionLow=C.L;
} else if(EnableShort && UpperReclaim(&C,B_U3)) {
Ctx.Mode=ST_SHORT_ARMED; Ctx.ArmAge=0; Ctx.RejectionHigh=C.H; Ctx.RejectionLow=C.L;
}
}
else if(Ctx.Mode==ST_LONG_ARMED) {
Ctx.ArmAge++;
if(C.C<C.Band[B_D3]) ResetContext();
else if((AcceptUp(&C,&P,B_D2)||(C.C>Ctx.RejectionHigh&&C.C>C.O&&C.CLV>=AcceptanceCloseLevel)) && OpenPrimary(1,&C)) {
Ctx.Mode=ST_LONG_ACTIVE; Ctx.Progress=B_D3; Ctx.HedgeDepth=0; Ctx.Holding=0;
} else if(Ctx.ArmAge>ConfirmationBars) ResetContext();
}
else if(Ctx.Mode==ST_SHORT_ARMED) {
Ctx.ArmAge++;
if(C.C>C.Band[B_U3]) ResetContext();
else if((AcceptDown(&C,&P,B_U2)||(C.C<Ctx.RejectionLow&&C.C<C.O&&C.CLV<=1.-AcceptanceCloseLevel)) && OpenPrimary(0,&C)) {
Ctx.Mode=ST_SHORT_ACTIVE; Ctx.Progress=B_U3; Ctx.HedgeDepth=0; Ctx.Holding=0;
} else if(Ctx.ArmAge>ConfirmationBars) ResetContext();
}
else if(Ctx.Mode==ST_LONG_ACTIVE) {
Ctx.Holding++;
if(PrimaryLongLots()<=0 || LongEmergency(&C) || LongFinal(&C,&P)) CloseAllToCooldown();
else if(!AdvanceLong(&C,&P) && LongH1(&C,&P) && OpenHedge(0,1,HedgeRatio1)) { Ctx.HedgeDepth=1; Ctx.Mode=ST_LONG_HEDGED; }
}
else if(Ctx.Mode==ST_SHORT_ACTIVE) {
Ctx.Holding++;
if(PrimaryShortLots()<=0 || ShortEmergency(&C) || ShortFinal(&C,&P)) CloseAllToCooldown();
else if(!AdvanceShort(&C,&P) && ShortH1(&C,&P) && OpenHedge(1,1,HedgeRatio1)) { Ctx.HedgeDepth=1; Ctx.Mode=ST_SHORT_HEDGED; }
}
else if(Ctx.Mode==ST_LONG_HEDGED) {
Ctx.Holding++;
if(PrimaryLongLots()<=0 || LongEmergency(&C) || LongFinal(&C,&P)) CloseAllToCooldown();
else if(Ctx.HedgeDepth==3 && LowerReclaim(&C,LAdv(3)) && CloseRole(ROLE_H3)) Ctx.HedgeDepth=2;
else if(Ctx.HedgeDepth==2 && LowerReclaim(&C,LAdv(2)) && CloseRole(ROLE_H2)) Ctx.HedgeDepth=1;
else if(Ctx.HedgeDepth==1 && LowerReclaim(&C,LAdv(1)) && CloseRole(ROLE_H1)) { Ctx.HedgeDepth=0; Ctx.Mode=ST_LONG_RECOVERY; }
else if(LongH3(&C,&P) && OpenHedge(0,3,HedgeRatio3)) Ctx.HedgeDepth=3;
else if(LongH2(&C,&P) && OpenHedge(0,2,HedgeRatio2)) Ctx.HedgeDepth=2;
}
else if(Ctx.Mode==ST_SHORT_HEDGED) {
Ctx.Holding++;
if(PrimaryShortLots()<=0 || ShortEmergency(&C) || ShortFinal(&C,&P)) CloseAllToCooldown();
else if(Ctx.HedgeDepth==3 && UpperReclaim(&C,SAdv(3)) && CloseRole(ROLE_H3)) Ctx.HedgeDepth=2;
else if(Ctx.HedgeDepth==2 && UpperReclaim(&C,SAdv(2)) && CloseRole(ROLE_H2)) Ctx.HedgeDepth=1;
else if(Ctx.HedgeDepth==1 && UpperReclaim(&C,SAdv(1)) && CloseRole(ROLE_H1)) { Ctx.HedgeDepth=0; Ctx.Mode=ST_SHORT_RECOVERY; }
else if(ShortH3(&C,&P) && OpenHedge(1,3,HedgeRatio3)) Ctx.HedgeDepth=3;
else if(ShortH2(&C,&P) && OpenHedge(1,2,HedgeRatio2)) Ctx.HedgeDepth=2;
}
else if(Ctx.Mode==ST_LONG_RECOVERY) {
Ctx.Holding++;
if(PrimaryLongLots()<=0 || LongEmergency(&C) || LongFinal(&C,&P)) CloseAllToCooldown();
else if(AcceptUp(&C,&P,Ctx.Progress)||(C.C>C.Band[Ctx.Progress]&&C.BullReject)) Ctx.Mode=ST_LONG_ACTIVE;
else if(LongH1(&C,&P)&&OpenHedge(0,1,HedgeRatio1)) { Ctx.HedgeDepth=1; Ctx.Mode=ST_LONG_HEDGED; }
}
else if(Ctx.Mode==ST_SHORT_RECOVERY) {
Ctx.Holding++;
if(PrimaryShortLots()<=0 || ShortEmergency(&C) || ShortFinal(&C,&P)) CloseAllToCooldown();
else if(AcceptDown(&C,&P,Ctx.Progress)||(C.C<C.Band[Ctx.Progress]&&C.BearReject)) Ctx.Mode=ST_SHORT_ACTIVE;
else if(ShortH1(&C,&P)&&OpenHedge(1,1,HedgeRatio1)) { Ctx.HedgeDepth=1; Ctx.Mode=ST_SHORT_HEDGED; }
}
else if(Ctx.Mode==ST_COOLDOWN) {
if(Ctx.Cooldown>0) Ctx.Cooldown--;
if(Ctx.Cooldown<=0) ResetContext();
}
else if(Ctx.Mode==ST_EMERGENCY || Ctx.Mode==ST_EXIT_PENDING) CloseAllToCooldown();
}
// -----------------------------------------------------------------------------
// Tick-reactive engine
// -----------------------------------------------------------------------------
void UpdateFormingCandle(var Price)
{
DATE CurrentBar = floor(wdate()*24*60/BarPeriod)/(24*60./BarPeriod);
if(FormingBarTime==0 || CurrentBar!=FormingBarTime) {
FormingBarTime=CurrentBar;
FormingOpen=FormingHigh=FormingLow=FormingClose=Price;
FormingTickVolume=1;
} else {
FormingClose=Price;
FormingHigh=max(FormingHigh,Price);
FormingLow=min(FormingLow,Price);
FormingTickVolume++;
}
}
void UpdateHFTRateWindows(long NowMSValue)
{
if(!HFTSecondWindowMS) HFTSecondWindowMS=NowMSValue;
if(!HFTMinuteWindowMS) HFTMinuteWindowMS=NowMSValue;
if(!HFTTickWindowMS) HFTTickWindowMS=NowMSValue;
if(NowMSValue-HFTSecondWindowMS>=1000) { HFTEntriesSecond=0; HFTSecondWindowMS=NowMSValue; }
if(NowMSValue-HFTMinuteWindowMS>=60000) { HFTEntriesMinute=0; HFTMinuteWindowMS=NowMSValue; }
if(NowMSValue-HFTTickWindowMS>=1000) {
HFTTicksLastSecond=HFTTicksCurrentSecond;
HFTTicksCurrentSecond=0;
HFTTickWindowMS=NowMSValue;
}
HFTTicksCurrentSecond++;
int Key=CurrentDayKey();
if(Key!=HFTDayKey) { HFTDayKey=Key; HFTDailyEntries=0; }
}
int HFTCooldownReady(int IsLong,int Band,long NowMSValue)
{
long Last;
if(IsLong)
Last = HFTLastLongSignalMS[Band];
else
Last = HFTLastShortSignalMS[Band];
return !Last || NowMSValue-Last>=HFTSignalCooldownMilliseconds;
}
void HFTMarkSignal(int IsLong,int Band,long NowMSValue)
{
if(IsLong) HFTLastLongSignalMS[Band]=NowMSValue;
else HFTLastShortSignalMS[Band]=NowMSValue;
}
int HFTLowerDominant(SNAPSHOT* S)
{
return S->LWR>=HFTMinimumWickRatio && S->LW>=HFTMinimumWickDominance*max(S->UW,0.1*PIP) && S->BR>=HFTMinimumBodyRatio;
}
int HFTUpperDominant(SNAPSHOT* S)
{
return S->UWR>=HFTMinimumWickRatio && S->UW>=HFTMinimumWickDominance*max(S->LW,0.1*PIP) && S->BR>=HFTMinimumBodyRatio;
}
int HFTCanEnter(int IsLong,long NowMSValue)
{
if(Spread/PIP>HFTMaximumSpreadPoints) return 0;
if(HFTMinimumTicksPerSecond>0 && HFTTicksLastSecond>0 && HFTTicksLastSecond<HFTMinimumTicksPerSecond) return 0;
if(HFTMaximumDailyEntries>0 && HFTDailyEntries>=HFTMaximumDailyEntries) return 0;
if(HFTMaximumEntriesPerSecond>0 && HFTEntriesSecond>=HFTMaximumEntriesPerSecond) return 0;
if(HFTMaximumEntriesPerMinute>0 && HFTEntriesMinute>=HFTMaximumEntriesPerMinute) return 0;
if(HFTLastEntryMS && NowMSValue-HFTLastEntryMS<HFTMinimumMillisecondsBetweenEntries) return 0;
if(CountHFTTrades(0)>=HFTMaximumOpenPositions) return 0;
if(IsLong && CountHFTTrades(1)>=HFTMaximumPositionsPerSide) return 0;
if(!IsLong && CountHFTTrades(-1)>=HFTMaximumPositionsPerSide) return 0;
if(!HFTAllowOppositePositions && CountHFTTrades(ifelse(IsLong,-1,1))>0) return 0;
return 1;
}
var HFTEntryLots(int IsLong)
{
if(HFTUseFixedLots) return HFTFixedLots;
if(HFTStopLossPoints<=0 || PIPCost<=0) return HFTFixedLots;
var RiskMoney=Balance*HFTRiskPercent/100.;
return max(1,RiskMoney/(HFTStopLossPoints*PIPCost));
}
int HFTCostPass(int IsLong,int Band,SNAPSHOT* S,var Price)
{
if(!HFTRequireCostEdge) return 1;
int Target;
if(IsLong)
Target = min(Band+1,B_U3);
else
Target = max(Band-1,B_D3);
var Edge=abs(S->Band[Target]-Price)/PIP;
var Cost=max(Spread/PIP,1);
return Edge>=HFTMinimumEdgeCostMultiple*Cost;
}
int HFTOpen(int IsLong,int Band,int Score,SNAPSHOT* S,long NowMSValue)
{
if(!HFTCanEnter(IsLong,NowMSValue) || !HFTCooldownReady(IsLong,Band,NowMSValue) || !HFTCostPass(IsLong,Band,S,priceClose())) return 0;
int Role;
if(IsLong)
Role = ROLE_HFT_LONG;
else
Role = ROLE_HFT_SHORT;
TRADE* T=OpenTaggedTrade(IsLong,HFTEntryLots(IsLong),Role,Band,Score,NowMSValue);
if(!T) return 0;
HFTLastEntryMS=NowMSValue;
HFTEntriesSecond++;
HFTEntriesMinute++;
HFTDailyEntries++;
HFTEntryCounter++;
HFTMarkSignal(IsLong,Band,NowMSValue);
return 1;
}
int HFTLongScore(int Band,SNAPSHOT* S,var Price,var Delta,int ZoneNow)
{
int Cross=HFTPrevPrice<=HFTPrevBand[Band] && Price>S->Band[Band];
int Reclaim=S->L<S->Band[Band] && Price>S->Band[Band] && HFTLowerDominant(S);
int ZoneEvent=HFTPrevZone>=0 && ZoneNow>HFTPrevZone && Cross;
int Momentum=Delta>=HFTMinimumTickMomentumPoints;
int Score=0;
if(HFTEnableBandCrossEntries&&Cross) Score+=2;
if(HFTEnableZoneTransitionEntries&&ZoneEvent) Score++;
if(HFTEnableWickReclaimEntries&&Reclaim) Score+=2;
if(HFTEnableMomentumEntries&&Momentum) Score++;
if(HFTLowerDominant(S)) Score++;
return Score;
}
int HFTShortScore(int Band,SNAPSHOT* S,var Price,var Delta,int ZoneNow)
{
int Cross=HFTPrevPrice>=HFTPrevBand[Band] && Price<S->Band[Band];
int Reclaim=S->H>S->Band[Band] && Price<S->Band[Band] && HFTUpperDominant(S);
int ZoneEvent=HFTPrevZone>=0 && ZoneNow<HFTPrevZone && Cross;
int Momentum=Delta<=-HFTMinimumTickMomentumPoints;
int Score=0;
if(HFTEnableBandCrossEntries&&Cross) Score+=2;
if(HFTEnableZoneTransitionEntries&&ZoneEvent) Score++;
if(HFTEnableWickReclaimEntries&&Reclaim) Score+=2;
if(HFTEnableMomentumEntries&&Momentum) Score++;
if(HFTUpperDominant(S)) Score++;
return Score;
}
void ManageHFTPositions(SNAPSHOT* S,var Bid,var Ask,int OppositeLong,int OppositeShort,long NowMSValue)
{
int Closed=0;
for(open_trades) {
int Role=(int)TradeVar[TV_ROLE];
if(!IsHFTRole(Role) || Closed>=HFTMaximumClosuresPerTick) continue;
int IsLong=TradeIsLong;
int Band=(int)TradeVar[TV_BAND];
long Age=NowMSValue-(long)TradeVar[TV_OPEN_MS];
var Points;
if(IsLong)
Points = (Bid-TradePriceOpen)/PIP;
else
Points = (TradePriceOpen-Ask)/PIP;
int Close=0;
if(HFTTakeProfitPoints>0 && Points>=HFTTakeProfitPoints) Close=1;
else if(HFTStopLossPoints>0 && Points<=-HFTStopLossPoints) Close=1;
else if(HFTMaximumHoldingMilliseconds>0 && Age>=HFTMaximumHoldingMilliseconds) Close=1;
else if(HFTUseAdjacentBandTargets && Band>=B_D3 && Band<=B_U3) {
int Target;
if(IsLong)
Target = min(Band+1,B_U3);
else
Target = max(Band-1,B_D3);
if((IsLong&&Bid>=S->Band[Target])||(!IsLong&&Ask<=S->Band[Target])) Close=1;
}
if(!Close && HFTCloseOnOppositeSignal && ((IsLong&&OppositeLong)||(!IsLong&&OppositeShort))) Close=1;
if(Close) { exitTrade(ThisTrade); Closed++; HFTCloseCounter++; }
}
}
void StoreHFTState(var Price,int ZoneNow,SNAPSHOT* S)
{
HFTPrevPrice=Price;
HFTPrevZone=ZoneNow;
int B; for(B=0;B<7;B++) HFTPrevBand[B]=S->Band[B];
HFTStateReady=1;
}
void ProcessHFTTick()
{
if(!EnableTickReactiveHFT || !SeriesReady) return;
long NowMSValue=NowMS();
UpdateHFTRateWindows(NowMSValue);
HFTTickCounter++;
var Bid=priceClose();
var Ask=Bid+Spread;
UpdateFormingCandle(Bid);
SNAPSHOT S;
if(!MakeTickSnapshot(&S)) return;
int ZoneNow=ZoneAt(Bid,&S);
if(!HFTStateReady) { StoreHFTState(Bid,ZoneNow,&S); return; }
var Delta=(Bid-HFTPrevPrice)/PIP;
int OppositeLong=0,OppositeShort=0;
int B;
for(B=0;B<7;B++) {
if(HFTPrevPrice>=HFTPrevBand[B] && Bid<S.Band[B]) OppositeLong=1;
if(HFTPrevPrice<=HFTPrevBand[B] && Bid>S.Band[B]) OppositeShort=1;
}
ManageHFTPositions(&S,Bid,Ask,OppositeLong,OppositeShort,NowMSValue);
int Opened=0;
for(B=B_D3;B<=B_U3 && Opened<HFTMaximumEntriesPerTick;B++) {
int Score=HFTLongScore(B,&S,Bid,Delta,ZoneNow);
int Edge=(HFTPrevPrice<=HFTPrevBand[B]&&Bid>S.Band[B]) || (S.L<S.Band[B]&&Bid>S.Band[B]&&HFTLowerDominant(&S)) || (Delta>=HFTMinimumTickMomentumPoints&&ZoneNow>HFTPrevZone);
if(EnableLong&&Edge&&Score>=HFTMinimumSignalScore&&HFTOpen(1,B,Score,&S,NowMSValue)) Opened++;
}
for(B=B_U3;B>=B_D3 && Opened<HFTMaximumEntriesPerTick;B--) {
int Score=HFTShortScore(B,&S,Bid,Delta,ZoneNow);
int Edge=(HFTPrevPrice>=HFTPrevBand[B]&&Bid<S.Band[B]) || (S.H>S.Band[B]&&Bid<S.Band[B]&&HFTUpperDominant(&S)) || (Delta<=-HFTMinimumTickMomentumPoints&&ZoneNow<HFTPrevZone);
if(EnableShort&&Edge&&Score>=HFTMinimumSignalScore&&HFTOpen(0,B,Score,&S,NowMSValue)) Opened++;
}
StoreHFTState(Bid,ZoneNow,&S);
}
// Called by Zorro for every incoming tick when set(TICKS) is active.
void tick()
{
ProcessHFTTick();
}
// Zorro's tock() cadence is approximately one second, not an MT5 millisecond
// timer. It is used only to force time-based HFT exits during sparse ticks.
void tock()
{
if(!EnableTickReactiveHFT || !SeriesReady || FormingOpen<=0) return;
SNAPSHOT S;
if(!MakeTickSnapshot(&S)) return;
var Bid=priceClose(), Ask=Bid+Spread;
ManageHFTPositions(&S,Bid,Ask,0,0,NowMS());
}
void run()
{
BarPeriod = 1;
LookBack = max(AvgPeriod+10,100);
set(PARAMETERS,LOGFILE,TICKS);
Hedge = 2; // Permit simultaneous long and short trades.
Fill = 0; // Broker/simulator-dependent immediate fill model.
MaxLong = HFTMaximumOpenPositions + 10;
MaxShort = HFTMaximumOpenPositions + 10;
asset("US30"); // Change to the exact broker asset name.
SOpen = series(priceOpen());
SHigh = series(priceHigh());
SLow = series(priceLow());
SClose = series(priceClose());
SVolume = series(marketVol());
SeriesReady = 1;
// Initialize the forming candle from the latest bar. tick() will then
// update it with each incoming quote.
if(FormingBarTime==0) {
FormingBarTime=wdate();
FormingOpen=priceOpen();
FormingHigh=priceHigh();
FormingLow=priceLow();
FormingClose=priceClose();
FormingTickVolume=max(marketVol(),1);
ResetContext();
HFTDayKey=CurrentDayKey();
}
if(EnableBarStateMachine) ProcessMacroBar();
// Plot all seven bands from the current completed-bar snapshot.
SNAPSHOT S;
if(MakeBarSnapshot(0,&S)) {
plot("U3",S.Band[B_U3],NEW,0x00AA00);
plot("U2",S.Band[B_U2],0,0x00CC00);
plot("U1",S.Band[B_U1],0,0x00EE00);
plot("M", S.Band[B_M], 0,0xAAAAAA);
plot("D1",S.Band[B_D1],0,0xCCAA66);
plot("D2",S.Band[B_D2],0,0xCC8844);
plot("D3",S.Band[B_D3],0,0xCC6622);
}
}