|
|
Order Book Fuzzy Strangle
[Re: TipmyPip]
#489701
10/13/26 17:08
10/13/26 17:08
|
Joined: Sep 2017
Posts: 341
TipmyPip
OP
Senior Member
|
OP
Senior Member
Joined: Sep 2017
Posts: 341
|
We can extend your original strategy into a market-depth-aware options trading system, combining Zorro's native Level II order-book analysis, fuzzy logic, volatility estimation, and dynamic options selection. The important improvement is that the strategy will no longer rely exclusively on historical closing prices. It will also examine the current distribution of buy and sell liquidity before deciding whether to open an options position. 1. Zorro's built-in order-book functions Zorro provides three native functions for market-depth analysis, documented in its official manual. They require Zorro S. Function Purpose orderUpdate() Retrieves market-depth quotes from the broker or historical data orderCVD() Constructs a cumulative distribution of order-book liquidity cpd() Measures cumulative relative bid or ask volume at a price These can be combined into three analytical signals: - Bid pressure: Relative liquidity on the buying side. - Ask pressure: Relative liquidity on the selling side. - Order-book imbalance: Difference between buying and selling pressure, normalized by their combined liquidity. A useful mathematical representation is: \[ I_t=\frac{B_t-A_t}{B_t+A_t} \] Where \(B_t\) and \(A_t\) are comparable bid-side and ask-side cumulative liquidity measurements. A positive imbalance indicates more visible bid liquidity; a negative imbalance indicates more visible ask liquidity. Neither guarantees the next price movement, because displayed orders can be canceled or moved. (Latex formula) // OrderBookFuzzyStrangle_v1.c
// Zorro S / lite-C -- SPY long strangle with underlying order-book filter.
// Requires synchronized historical .t2 book snapshots and .t8 options chains
// for Test/Train; GET_BOOK and options support from broker for Trade.
// Research prototype: validate fills, execution, and broker behavior in demo.
#include <contract.c>
#define BOOK_HANDLE 1
#define VOL_PERIOD 20
#define MIN_BOOK_QUOTES 6
#define MIN_DTE 30
#define EXIT_DTE 10
#define MIN_IMBALANCE 0.15
#define EXIT_IMBALANCE 0.07
#define MIN_FUZZY 0.65
#define MOVE_MULTIPLIER 1.10
#define MAX_DEBIT_USD 1500.0
#define PROFIT_FRACTION 0.50
#define LOSS_FRACTION 0.35
// Average absolute close-to-close change, in SPY dollars per hourly bar.
var meanAbsoluteMove(vars Prices, int Period)
{
var Sum = 0;
int i;
for(i = 1; i <= Period; i++)
Sum += abs(Prices[i-1] - Prices[i]);
return Sum / Period;
}
// Returns 1 if a usable book snapshot exists and fills the imbalance output.
int readBookImbalance(var Spot, var* Imbalance)
{
int N = orderUpdate(0,BOOK_HANDLE); // History\SPY.t2 or broker GET_BOOK
if(N < MIN_BOOK_QUOTES) return 0;
T2* Quotes = dataStr(BOOK_HANDLE,OrderRow,0);
if(!Quotes) return 0;
var Distance = 0.01*Spot; // visible liquidity within +/-1% of SPY
if(orderCVD(Quotes,N,Distance) < MIN_BOOK_QUOTES) return 0;
// cpd returns values in 0..100 normalized to the larger book side.
var BidDepth = cpd(-(Spot-Distance));
var AskDepth = cpd(Spot+Distance);
var Total = BidDepth+AskDepth;
if(Total <= 0) return 0;
*Imbalance = (BidDepth-AskDepth)/Total;
return 1;
}
// Returns 1 if open options should be liquidated on risk/expiration rules.
int exitByRisk()
{
var Cost = 0;
var OpenPnL = 0;
int ExpireSoon = 0;
int Legs = 0;
for(current_trades) {
if(!TradeIsOpen || !TradeIsLong || !TradeIsContract) continue;
Cost += TradePriceOpen*TradeUnits;
OpenPnL += TradeProfit;
if(contractDays(ThisTrade) <= EXIT_DTE) ExpireSoon = 1;
Legs++;
}
if(!Legs) return 0;
if(ExpireSoon) return 1;
if(Cost <= 0) return 0;
if(OpenPnL >= PROFIT_FRACTION*Cost) return 1;
if(OpenPnL <= -LOSS_FRACTION*Cost) return 1;
return 0;
}
void buyStrangle(var Spot, var AvgMove)
{
// Dynamic OTM strike targets in dollars. Select nearest listed strikes.
var Gap = max(0.01*Spot,8*AvgMove);
var CallTarget = Spot+Gap;
var PutTarget = Spot-Gap;
if(PutTarget <= 0) return;
// 6 means select by strike (the 4th contractFind parameter is mandatory).
CONTRACT* Call = contractFind(CALL,MIN_DTE,CallTarget,6);
CONTRACT* Put = contractFind(PUT,MIN_DTE,PutTarget,6);
if(!Call || !Put) return;
if(Call->fStrike <= Spot || Put->fStrike >= Spot) return;
if(Call->Expiry != Put->Expiry) return;
if(contractDays(Call) <= EXIT_DTE || contractDays(Put) <= EXIT_DTE)
return;
if(contractPrice(Call) <= 0) return;
if(contractPrice(Put) <= 0) return;
if(Call->fAsk <= 0 || Put->fAsk <= 0) return;
if(Call->fBid <= 0 || Put->fBid <= 0) return;
var Debit = (Call->fAsk+Put->fAsk)*Multiplier;
if(Debit <= 0 || Debit > MAX_DEBIT_USD) return;
if(combo(Call,1,Put,1,0,0,0,0) != 2) return;
MarginCost = comboMargin(1,3); // Stock/ETF options; long positions.
// The broker receives the combo when the second leg is submitted, if
// SET_COMBO_LEG is supported. Otherwise separate orders can be submitted.
enterLong(comboLeg(1));
enterLong(comboLeg(2));
printf("\nStrangle order attempted: SPY %.2f, call %.2f, put %.2f, debit %.2f",
Spot,Call->fStrike,Put->fStrike,Debit);
}
void run()
{
set(LOGFILE);
BarPeriod = 60;
LookBack = 100;
StartDate = 2020;
EndDate = 2024;
History = ".t8"; // Workshop 8: historical options/unadjusted underlying.
assetList("AssetsIB");
asset("SPY");
algo("OBStrangle");
Multiplier = 100; // Standard SPY option contract; verify with broker.
Capital = 100000; // Research backtest account, not a live risk control.
if(is(EXITRUN)) return;
static int LastChainDate = 0;
if(is(INITRUN)) {
LastChainDate = 0;
return;
}
vars Prices = series(priceClose());
var AvgMove = meanAbsoluteMove(Prices,VOL_PERIOD);
if(AvgMove <= 0) return;
FuzzyRange = max(0.01,0.25*AvgMove);
var Bullish = max(risingF(Prices),valleyF(Prices));
var Bearish = max(fallingF(Prices),peakF(Prices));
// In historical tests, options must be refreshed every bar.
// For live mode, cache the costly IB chain download for the session day.
int Today = ymd(wdate(0));
if(!Live || Today != LastChainDate || NumContracts <= 0) {
if(!contractUpdate(Asset,0,CALL|PUT)) return;
LastChainDate = Today;
}
// These exits must work even when the current order book is unavailable.
if(NumOpenLong > 0 && exitByRisk()) {
exitLong("cp"); // Only call and put contracts of this asset/algo.
return;
}
var Imbalance = 0;
if(!readBookImbalance(priceClose(),&Imbalance)) return;
plot("Order book imbalance",Imbalance,NEW,BLUE);
if(NumOpenLong > 0 || NumPendingLong > 0) {
// Fuzzy bearish price signal plus disappearing book pressure.
if(Bearish > MIN_FUZZY && abs(Imbalance) < EXIT_IMBALANCE)
exitLong("cp");
return;
}
// Require a meaningful move, visible bid/ask pressure, and corresponding
// fuzzy price direction. Long strangle still buys BOTH call and put.
if(abs(Prices[0]-Prices[1]) < MOVE_MULTIPLIER*AvgMove) return;
if(abs(Imbalance) < MIN_IMBALANCE) return;
if(Imbalance > 0 && Bullish >= MIN_FUZZY)
buyStrangle(priceClose(),AvgMove);
else if(Imbalance < 0 && Bearish >= MIN_FUZZY)
buyStrangle(priceClose(),AvgMove);
}
|
|
|
Markov Rulebook v.01
[Re: TipmyPip]
#489704
10/15/26 08:28
10/15/26 08:28
|
Joined: Sep 2017
Posts: 341
TipmyPip
OP
Senior Member
|
OP
Senior Member
Joined: Sep 2017
Posts: 341
|
VWAP Markov State Trading Strategy for Zorro The Strategy consist of an algorithmic trading strategy that combines VWAP volatility bands, candlestick price action, and Markov state-transition probabilities to identify trading opportunities across 28 currency pairs. How the strategy works 1. VWAP Analysis: Calculates VWAP and six volatility bands at ±1, ±2, and ±2.5 standard deviations to identify important market price zones. 2. Market Classification: Classifies completed candles according to their bullish, bearish, or doji structure and their positions relative to the VWAP bands. 3. Markov Probability Model: Learns historical transitions between market states and estimates the probability that the next candle will be bullish, bearish, or neutral. 4. Trading Decisions: Supports 36 predefined research strategies, 300 VWAP event/action rules, custom filters, and Markov-directed trading. 5. Risk Management: Includes configurable stop-loss levels, pending entries, VWAP-based exits, and portfolio position limits. Key technical characteristics Feature Configuration Currency pairs 28 Default timeframe 5 minutes (configurable to H4) Markov states 780 Transition matrix 780 × 780 per pair Historical learning window 1,024 observations VWAP period 20 Default execution Market orders Default stop-loss buffer 10% of candle range Important observations Although the filename says Markov720, the actual code defines 780 states: 330 bullish, 330 bearish, and 120 doji. The current default configuration runs research strategy M00 while the Markov model monitors transitions. Markov-directed trading must be explicitly enabled. I also noticed that the code's sweep-direction logic does not match your earlier bullish-candle SELL / bearish-candle BUY reversal specification. That should be corrected if the original reversal behavior is intended. // VWAP_Rulebook_28Pair_Markov780.c
#include <default.c>
// ---- EDIT THESE CONFIGURATION PARAMETERS (native lite-C, no MQL input syntax) ----
#define ENGINE_MODE 0 // 0 M00-M35, 1 Catalog 0..299, 2 S/B custom, 3 Markov
#define RESEARCH_ID 0 // M00..M35
#define CATALOG_ID 0 // 0..299 (100 observable events x BUY/SELL/EXIT)
#define SELL_STATIC_ID 0 // 0..30
#define BUY_STATIC_ID 0 // 0..30
#define SELL_EVENT_ID 1 // 1..12
#define BUY_EVENT_ID 1 // 1..12
#define CANDLE_RULE_ID 0 // 0 disabled, 1..25
#define SIGNATURE_FILTER -1 // -1 disabled; 0..779
#define TIMEFRAME_MINUTES 5 // 5 M5 default, 240 for H4
#define SINGLE_ASSET 0 // 0 = 28 assets, 1 = named symbol only
#define SINGLE_ASSET_NAME "EUR/USD"
#define ASSET_LIST "AssetsForex" // Broker-compatible list MUST contain requested pairs
#define ENTRY_STYLE 0 // 0 market; 1 Zorro-managed Entry stop
#define PENDING_PERCENT 10.0
#define STOP_PERCENT 10.0
#define ENTRY_WAIT_BARS 2
#define ENABLE_BROKER_STOP 1 // Zorro StopFactor=1, when broker plugin supports it
#define ENABLE_SOFTWARE_EXIT 1
#define EXIT_RULE_ID 14 // X01..X20
#define MAX_SPREAD_PIPS 0.0 // 0 disabled
#define ORDER_LOTS 1 // Zorro INTEGER micro/mini/standard lots, NOT MQL "0.01"
#define MAX_PORTFOLIO_POSITIONS 28
#define VWAP_PERIOD 20
#define VWAP_MATCH_MQL 1 // N+1 VWAP bars, N STD bars
#define VWAP_VOLUME_MODE 1 // 1 use marketVol (Zorro S required), 0 unweighted approximation
#define VWAP_PRICE_MODE 0 // 0 close; 1 open; 2 high; 3 low; 4 median; 5 typical; 6 weighted
#define DEVIATION_1 1.0
#define DEVIATION_2 2.0
#define DEVIATION_3 2.5
#define MARKOV_MODE 0 // 0 monitor only, 1 gate existing engines
#define MARKOV_MIN_COUNT 8
#define MARKOV_MIN_DIRECTION 0.50
#define MARKOV_PRIOR 0.01
#define MARKOV_BACKOFF 1
#define MARKOV_BLOCK_SPARSE 0
#define MARKOV_NEED_SWEEP 1
#define MK_WINDOW 1024
#define MAX_PAIRS 28
#define MK_STATES 780
#define Z_THRESHOLD 1.0
#define WICK_THRESHOLD 0.35
#define EPSILON_PIPS 1.0
#define MIN_REWARD_PIPS 1.0
#define MIN_RISK_REWARD 1.0
#define SMALL_CANDLE_SPREAD_MULTIPLE 3.0
#define SWEEP_TIMEOUT_SECONDS 60
#define MIN_RETURN_TICKS 3
#define EXIT_SIGMA_MULTIPLE 1.0
#define EXIT_RANGE_MULTIPLE 1.0
#define EXIT_RANGE_PERCENT 10.0
#define EXIT_WAIT_BARS 3
#define EXIT_DEADLINE_MINUTES 120
#define PARTIAL_PERCENT 50.0
#define TRAIL_SIGMA_MULTIPLE 1.0
#define PH_SWEEP 0
#define PH_RETURN 1
#define PH_READY 2
#define PH_RETEST 3
// Exact 28-currency universe, Zorro's usual slash format.
string ForexNames[28];
void initForexNames()
{
// lite-C requires explicit initialization for arrays of string pointers.
ForexNames[0]="EUR/USD";ForexNames[1]="GBP/USD";ForexNames[2]="USD/JPY";
ForexNames[3]="GBP/JPY";ForexNames[4]="USD/CAD";ForexNames[5]="EUR/AUD";
ForexNames[6]="EUR/JPY";ForexNames[7]="AUD/CAD";ForexNames[8]="AUD/JPY";
ForexNames[9]="AUD/NZD";ForexNames[10]="AUD/USD";ForexNames[11]="CAD/JPY";
ForexNames[12]="EUR/CAD";ForexNames[13]="EUR/CHF";ForexNames[14]="EUR/GBP";
ForexNames[15]="EUR/NZD";ForexNames[16]="GBP/CAD";ForexNames[17]="GBP/CHF";
ForexNames[18]="NZD/CAD";ForexNames[19]="NZD/JPY";ForexNames[20]="NZD/USD";
ForexNames[21]="USD/CHF";ForexNames[22]="CHF/JPY";ForexNames[23]="AUD/CHF";
ForexNames[24]="GBP/NZD";ForexNames[25]="NZD/CHF";ForexNames[26]="CAD/CHF";
ForexNames[27]="GBP/AUD";
}
typedef struct
{
int usable;
string symbol;
int valid,signature,dir;
int phase,crossMask,retouch,quoteKnown,activeBefore;
int observedBar,lastEntryBar,lastPersistBar;
int ringHead,ringSize;
int predictionSource,nEffective,topNext,topCount;
int seenPosition;
int partialDone;
var o,h,l,c,range,body,uw,lw;
var v,sigma,band[7]; // D3 D2 D1 VWAP U1 U2 U3
var previousAsk,sweepStamp;
var probabilityBull,probabilityBear,probabilityDoji;
var frozenCenter,highWater,lowWater;
var lastEntryLevel,entryStamp;
} FXSTATE;
FXSTATE FX[MAX_PAIRS];
int* MarkovEdges = 0;
int* MarkovOutgoing = 0;
int* MarkovBull = 0;
int* MarkovBear = 0;
int* MarkovDoji = 0;
int* MarkovRing = 0;
int MarkovDirection[MK_STATES],MarkovCloseZone[MK_STATES];
int ActivePairs = 0;
// ---- Signature ranking: 0..329 bullish, 330..659 bearish, 660..779 doji ----
// a=zone(L), b=zone(min(O,C)), c=zone(max(O,C)), d=zone(H).
int signatureRank(int a,int b,int c,int d,int doji)
{
int n=0,i,j,k,h;
if(doji) {
for(i=0;i<8;i++)for(j=i;j<8;j++)for(k=j;k<8;k++) {
if(i==a && j==b && k==d)return n;
n++;
}
return -1;
}
for(i=0;i<8;i++)for(j=i;j<8;j++)
for(k=j;k<8;k++)for(h=k;h<8;h++) {
if(i==a && j==b && k==c && h==d)return n;
n++;
}
return -1;
}
void makeMetadata()
{
int n=0,i,j,k,h;
for(i=0;i<8;i++)for(j=i;j<8;j++)
for(k=j;k<8;k++)for(h=k;h<8;h++) {
MarkovDirection[n]=1; MarkovCloseZone[n]=k;
MarkovDirection[330+n]=-1; MarkovCloseZone[330+n]=j;
n++;
}
n=0;
for(i=0;i<8;i++)for(j=i;j<8;j++)for(k=j;k<8;k++) {
MarkovDirection[660+n]=0; MarkovCloseZone[660+n]=j;
n++;
}
}
int initMarkov()
{
int edgeCount = ActivePairs*MK_STATES*MK_STATES;
int rowCount=ActivePairs*MK_STATES;
MarkovEdges = malloc(edgeCount*sizeof(int));
MarkovOutgoing = malloc(rowCount*sizeof(int));
MarkovBull = malloc(rowCount*sizeof(int));
MarkovBear = malloc(rowCount*sizeof(int));
MarkovDoji = malloc(rowCount*sizeof(int));
MarkovRing = malloc(ActivePairs*MK_WINDOW*sizeof(int));
if(!MarkovEdges || !MarkovOutgoing || !MarkovBull || !MarkovBear ||
!MarkovDoji || !MarkovRing) return 0;
memset(MarkovEdges,0,edgeCount*sizeof(int));
memset(MarkovOutgoing,0,rowCount*sizeof(int));
memset(MarkovBull,0,rowCount*sizeof(int));
memset(MarkovBear,0,rowCount*sizeof(int));
memset(MarkovDoji,0,rowCount*sizeof(int));
int i;
for(i=0;i<ActivePairs*MK_WINDOW;i++)MarkovRing[i]=-1;
makeMetadata();
return 1;
}
void releaseMarkov()
{
if(MarkovEdges)free(MarkovEdges);
if(MarkovOutgoing)free(MarkovOutgoing);
if(MarkovBull)free(MarkovBull);
if(MarkovBear)free(MarkovBear);
if(MarkovDoji)free(MarkovDoji);
if(MarkovRing)free(MarkovRing);
MarkovEdges=0;MarkovOutgoing=0;MarkovBull=0;MarkovBear=0;
MarkovDoji=0;MarkovRing=0;
}
int mkRow(int id,int from) {return id*MK_STATES+from;}
int mkEdge(int id,int from,int to) {return (id*MK_STATES+from)*MK_STATES+to;}
int mkRingIndex(int id,int offset) {return id*MK_WINDOW+offset;}
void adjustTransition(int id,int from,int to,int delta)
{
if(from<0 || to<0 || from>=MK_STATES || to>=MK_STATES)return;
int e=mkEdge(id,from,to),r=mkRow(id,from);
if(delta<0 && MarkovEdges[e]<=0)return;
MarkovEdges[e]+=delta;MarkovOutgoing[r]+=delta;
if(to<330)MarkovBull[r]+=delta;
else if(to<660)MarkovBear[r]+=delta;
else MarkovDoji[r]+=delta;
}
void addObservation(int id,int sig)
{
if(sig<-1 || sig>=MK_STATES)return;
int head=FX[id].ringHead,n=FX[id].ringSize;
if(n>=MK_WINDOW) {
int old=MarkovRing[mkRingIndex(id,head)];
int next=MarkovRing[mkRingIndex(id,(head+1)%MK_WINDOW)];
adjustTransition(id,old,next,-1);
head=(head+1)%MK_WINDOW;n--;
}
if(n>0) {
int last=MarkovRing[mkRingIndex(id,(head+n-1)%MK_WINDOW)];
adjustTransition(id,last,sig,1);
}
MarkovRing[mkRingIndex(id,(head+n)%MK_WINDOW)]=sig;
FX[id].ringHead=head;FX[id].ringSize=n+1;
}
int zoneAt(int id,var p)
{
int j;for(j=0;j<7;j++)if(p<FX[id].band[j])return j;
return 7;
}
int signatureOf(int id)
{
int zl=zoneAt(id,FX[id].l);
int zb=zoneAt(id,min(FX[id].o,FX[id].c));
int zt=zoneAt(id,max(FX[id].o,FX[id].c));
int zh=zoneAt(id,FX[id].h);
if(FX[id].dir==0)return 660+signatureRank(zl,zb,zt,zh,1);
if(FX[id].dir>0)return signatureRank(zl,zb,zt,zh,0);
return 330+signatureRank(zl,zb,zt,zh,0);
}
void predict(int id)
{
FXSTATE* F=&FX[id];
int st=F->signature;
F->nEffective=0;F->predictionSource=0;
F->probabilityBull=0;F->probabilityBear=0;F->probabilityDoji=0;
F->topNext=-1;F->topCount=0;
if(st<0 || st>=MK_STATES)return;
int row=mkRow(id,st),n=MarkovOutgoing[row],bull=MarkovBull[row];
int bear=MarkovBear[row],doji=MarkovDoji[row],source=1,k,r;
if(MARKOV_BACKOFF && n<MARKOV_MIN_COUNT) {
n=0;bull=0;bear=0;doji=0;source=2;
for(k=0;k<MK_STATES;k++)
if(MarkovDirection[k]==MarkovDirection[st] &&
MarkovCloseZone[k]==MarkovCloseZone[st]) {
r=mkRow(id,k);
n+=MarkovOutgoing[r];bull+=MarkovBull[r];
bear+=MarkovBear[r];doji+=MarkovDoji[r];
}
if(n<MARKOV_MIN_COUNT) {
n=0;bull=0;bear=0;doji=0;source=3;
for(k=0;k<MK_STATES;k++)if(MarkovDirection[k]==MarkovDirection[st]) {
r=mkRow(id,k);
n+=MarkovOutgoing[r];bull+=MarkovBull[r];
bear+=MarkovBear[r];doji+=MarkovDoji[r];
}
}
if(n<MARKOV_MIN_COUNT) {
n=0;bull=0;bear=0;doji=0;source=4;
for(k=0;k<MK_STATES;k++) {
r=mkRow(id,k);
n+=MarkovOutgoing[r];bull+=MarkovBull[r];
bear+=MarkovBear[r];doji+=MarkovDoji[r];
}
}
}
F->predictionSource=source;F->nEffective=n;
var denom=n+MK_STATES*MARKOV_PRIOR;
if(denom<=0)return;
F->probabilityBull=(bull+330.0*MARKOV_PRIOR)/denom;
F->probabilityBear=(bear+330.0*MARKOV_PRIOR)/denom;
F->probabilityDoji=(doji+120.0*MARKOV_PRIOR)/denom;
for(k=0;k<MK_STATES;k++) {
int count=MarkovEdges[mkEdge(id,st,k)];
if(count>F->topCount) { F->topCount=count;F->topNext=k; }
}
}
int markovDecision(int id)
{
FXSTATE* F=&FX[id];
if(F->nEffective<MARKOV_MIN_COUNT)return 0;
if(F->probabilityBear>=MARKOV_MIN_DIRECTION &&
F->probabilityBear>F->probabilityBull)return -1;
if(F->probabilityBull>=MARKOV_MIN_DIRECTION &&
F->probabilityBull>F->probabilityBear)return 1;
return 0;
}
int passMarkov(int id,int side)
{
if(ENGINE_MODE!=3 && MARKOV_MODE==0)return 1;
if(FX[id].nEffective<MARKOV_MIN_COUNT)
return (ENGINE_MODE!=3 && !MARKOV_BLOCK_SPARSE);
return markovDecision(id)==side;
}
// VWAP original: N+1 prices in numerator but N deviations around same center.
// In Zorro, marketVol may be unavailable without Zorro S or compatible .t6 feed.
// VWAP_VOLUME_MODE=0 intentionally uses equal weights, NOT exact MQL tick-volume VWAP.
// marketVol() in Zorro S can be trade volume, quote rate or historical fVol.
// Matching MQL's iVolume() requires verified matching tick-volume history.
var selectedPrice(int offset)
{
var o=priceO(offset),h=priceH(offset),l=priceL(offset),c=priceC(offset);
if(VWAP_PRICE_MODE==1)return o;
if(VWAP_PRICE_MODE==2)return h;
if(VWAP_PRICE_MODE==3)return l;
if(VWAP_PRICE_MODE==4)return (h+l)*0.5;
if(VWAP_PRICE_MODE==5)return (h+l+c)/3.;
if(VWAP_PRICE_MODE==6)return (h+l+2*c)/4.;
return c;
}
int calculateBands(int id)
{
FXSTATE* F=&FX[id];
int window=VWAP_PERIOD, j;
if(VWAP_MATCH_MQL)window++;
var sv=0,pv=0,p,w;
for(j=0;j<window;j++) {
p=selectedPrice(j);
if(p<=0)return 0;
w=1;
if(VWAP_VOLUME_MODE==1)w=marketVol(j);
if(w<=0)return 0;
pv+=p*w;sv+=w;
}
if(sv<=0)return 0;
F->v=pv/sv;
var sq=0;
for(j=0;j<VWAP_PERIOD;j++) {
p=selectedPrice(j);
sq+=(p-F->v)*(p-F->v);
}
F->sigma=sqrt(sq/VWAP_PERIOD);
if(F->sigma<=0)return 0;
F->band[0]=F->v-DEVIATION_3*F->sigma;
F->band[1]=F->v-DEVIATION_2*F->sigma;
F->band[2]=F->v-DEVIATION_1*F->sigma;
F->band[3]=F->v;
F->band[4]=F->v+DEVIATION_1*F->sigma;
F->band[5]=F->v+DEVIATION_2*F->sigma;
F->band[6]=F->v+DEVIATION_3*F->sigma;
for(j=1;j<7;j++)if(F->band[j]<=F->band[j-1])return 0;
return 1;
}
void resetPhase(int id)
{
FX[id].phase=PH_SWEEP;FX[id].crossMask=0;
FX[id].sweepStamp=0;FX[id].retouch=0;
}
int findFX(string name)
{
int id;for(id=0;id<ActivePairs;id++)
if(FX[id].usable && !strcmp(FX[id].symbol,name))return id;
return -1;
}
var clockSeconds() {return wdate(0)*86400.;} // simulated/bar clock, not PC wall time
void completeCandle(int id)
{
FXSTATE* F=&FX[id];
F->o=priceO(0);F->h=priceH(0);F->l=priceL(0);F->c=priceC(0);
F->range=F->h-F->l;
F->valid=0;F->signature=-1;
F->dir=0;F->body=abs(F->c-F->o);
F->uw=F->h-max(F->o,F->c);F->lw=min(F->o,F->c)-F->l;
if(F->c>F->o)F->dir=1; else if(F->c<F->o)F->dir=-1;
if(F->range>0)F->valid=calculateBands(id);
if(F->valid)F->signature=signatureOf(id);
addObservation(id,F->signature);
predict(id);
if(!is(LOOKBACK) && F->valid)
printf("\n%s VWAP %.5f sig %i N %i Pbull %.3f Pbear %.3f",
F->symbol,F->v,F->signature,F->nEffective,
F->probabilityBull,F->probabilityBear);
resetPhase(id);
F->quoteKnown=0;
F->observedBar=Bar;
}
int crossedMask(int id,var before,var after)
{
int k,m=0;
for(k=0;k<7;k++) {
var b=FX[id].band[k];
if(before<b && after>=b)m |= (1<<k);
if(before>b && after<=b)m |= (1<<(7+k));
}
return m;
}
int crossedBit(int level,int up)
{
if(up)return (1<<level);
return (1<<(level+7));
}
void sweepEvent(int id,var before,var after,int newQuote)
{
if(!newQuote || FX[id].dir==0)return;
FXSTATE* F=&FX[id];
var t=PIP;
int m=crossedMask(id,before,after);
if(F->phase!=PH_SWEEP)F->crossMask |= m;
if(F->phase==PH_SWEEP) {
if(F->dir<0 && before<F->h && after>=F->h) {
F->phase=PH_RETURN;F->sweepStamp=clockSeconds();F->crossMask=m;
} else if(F->dir>0 && before>=F->l && after<F->l) {
F->phase=PH_RETURN;F->sweepStamp=clockSeconds();F->crossMask=m;
}
return;
}
if(ENGINE_MODE==0 && RESEARCH_ID==30 && SWEEP_TIMEOUT_SECONDS>0 &&
clockSeconds()-F->sweepStamp>SWEEP_TIMEOUT_SECONDS) {
resetPhase(id);return;
}
if(F->phase==PH_RETURN) {
if(F->dir<0 && after<F->h) {
F->phase=PH_READY;
if(ENGINE_MODE==0 && RESEARCH_ID==24)F->phase=PH_RETEST;
} else if(F->dir>0 && after>F->l) {
F->phase=PH_READY;
if(ENGINE_MODE==0 && RESEARCH_ID==24)F->phase=PH_RETEST;
}
return;
}
if(F->phase==PH_RETEST) {
if(F->dir<0) {
if(after>F->h+t) {resetPhase(id);return;}
if(after>=F->h-t)F->retouch=1;
if(F->retouch && before>after && after<F->h-t)F->phase=PH_READY;
} else {
if(after<F->l-t) {resetPhase(id);return;}
if(after<=F->l+t)F->retouch=1;
if(F->retouch && before<after && after>F->l+t)F->phase=PH_READY;
}
return;
}
if(F->phase==PH_READY) {
if(F->dir<0 && after>=F->h-t && after<F->h)F->retouch=1;
if(F->dir>0 && after<=F->l+t && after>F->l)F->retouch=1;
if(F->dir<0 && after>=F->h)resetPhase(id);
if(F->dir>0 && after<=F->l)resetPhase(id);
}
}
// All filters use side=+1 BUY and -1 SELL; candle sign is separate.
int payoffOK(int id,int side,var ask,var minPips)
{
if(side<0)return (ask-FX[id].v)>=minPips*PIP;
return (FX[id].v-ask)>=minPips*PIP;
}
int anatomy(int id,int rule,var ask)
{
FXSTATE* F=&FX[id];var R=F->range,B=F->body;
var u=F->uw,l=F->lw,o=F->o,c=F->c,h=F->h,lo=F->l;
if(rule==0)return 1;
if(R<=0)return 0;
if(rule==1)return c>o;
if(rule==2)return c<o;
if(rule==3)return c==o;
if(rule==4)return B/R>=0.70;
if(rule==5)return B/R<=0.20;
if(rule==6)return u/R>=0.50;
if(rule==7)return l/R>=0.50;
if(rule==8)return u>=2*l;
if(rule==9)return l>=2*u;
if(rule==10)return (c-lo)/R>=0.90;
if(rule==11)return (c-lo)/R<=0.10;
if(rule==12)return (o-lo)/R>=0.90;
if(rule==13)return (o-lo)/R<=0.10;
if(rule==14)return min(o,c)>lo+0.5*R;
if(rule==15)return max(o,c)<lo+0.5*R;
if(rule==16)return min(o,c)<=lo+0.5*R && max(o,c)>=lo+0.5*R;
if(rule==17)return abs(u-l)/R<=0.10;
if(rule==18)return B/R>=0.80;
if(rule==19) {if(F->dir<0)return u>=B;return l>=B;}
if(rule==20)return R<=SMALL_CANDLE_SPREAD_MULTIPLE*Spread;
if(rule==21)return abs(h-o)/R<=0.10 || abs(o-lo)/R<=0.10;
if(rule==22)return abs(h-c)/R<=0.10 || abs(c-lo)/R<=0.10;
if(rule==23)return c>o && u>B;
if(rule==24)return c<o && l>B;
if(rule==25)return F->sigma>0 && R/F->sigma>=Z_THRESHOLD;
return 0;
}
int bandRule(int id,int side,int n,var q,var ask)
{
FXSTATE* F=&FX[id];
var O=F->o,H=F->h,L=F->l,C=F->c,R=F->range;
var B=F->body,W=F->lw;if(side<0)W=F->uw;
var v=F->v,sd=F->sigma,d1=F->band[2],d2=F->band[1],d3=F->band[0];
var u1=F->band[4],u2=F->band[5],u3=F->band[6];
var eps=EPSILON_PIPS*PIP;
if(side<0) {
if(n==0)return q<=u3;
if(n==1)return H>=u1;if(n==2)return H>=u2;if(n==3)return H>=u3;
if(n==4)return C>=v;if(n==5)return C>=u1;
if(n==6)return O<v && C>=v;
if(n==7)return O<u1 && C>=u1;
if(n==8)return O<u2 && C>=u2;
if(n==9)return H>=u3 && C<u3;
if(n==10)return H>=u3 && C<u2;
if(n==11)return H>=u2 && C<u1;
if(n==12)return H>=u1 && C<v;
if(n==13)return q>=v && q<=u1;
if(n==14)return q>=u1 && q<=u2;
if(n==15)return q>=u2 && q<=u3;
if(n==16)return q<=v;
if(n==17)return q>v && q<=u3;
if(n==18)return H>=u3 && q>v && q<=u2;
if(n==19)return H>=u3 && q>v && q<=u1;
if(n==20)return H>=u2 && W>B;
if(n==21)return H>=u3 && W/R>=0.35;
if(n==22)return sd>0 && (H-v)/sd>=Z_THRESHOLD;
if(n==23)return sd>0 && (q-v)/sd>=0.5 && (q-v)/sd<=2.5;
if(n==24)return abs(q-u2)<=eps;
if(n==25)return abs(H-u3)<=eps;
if(n==26)return H>u3 && q<u3;
if(n==27)return q<u3 && H>=u3 && C>O;
if(n==28)return sd>0 && R/sd>=Z_THRESHOLD;
if(n==29)return W/R>=WICK_THRESHOLD && q>v;
if(n==30)return q>v && payoffOK(id,side,ask,MIN_REWARD_PIPS);
} else {
if(n==0)return q>=d3;
if(n==1)return L<=d1;if(n==2)return L<=d2;if(n==3)return L<=d3;
if(n==4)return C<=v;if(n==5)return C<=d1;
if(n==6)return O>v && C<=v;
if(n==7)return O>d1 && C<=d1;
if(n==8)return O>d2 && C<=d2;
if(n==9)return L<=d3 && C>d3;
if(n==10)return L<=d3 && C>d2;
if(n==11)return L<=d2 && C>d1;
if(n==12)return L<=d1 && C>v;
if(n==13)return q>=d1 && q<=v;
if(n==14)return q>=d2 && q<=d1;
if(n==15)return q>=d3 && q<=d2;
if(n==16)return q>=v;
if(n==17)return q>=d3 && q<v;
if(n==18)return L<=d3 && q>=d2 && q<v;
if(n==19)return L<=d3 && q>=d1 && q<v;
if(n==20)return L<=d2 && W>B;
if(n==21)return L<=d3 && W/R>=0.35;
if(n==22)return sd>0 && (v-L)/sd>=Z_THRESHOLD;
if(n==23)return sd>0 && (q-v)/sd>=-2.5 && (q-v)/sd<=-0.5;
if(n==24)return abs(q-d2)<=eps;
if(n==25)return abs(L-d3)<=eps;
if(n==26)return L<d3 && q>d3;
if(n==27)return q>d3 && L<=d3 && C<O;
if(n==28)return sd>0 && R/sd>=Z_THRESHOLD;
if(n==29)return W/R>=WICK_THRESHOLD && q<v;
if(n==30)return q<v && payoffOK(id,side,ask,MIN_REWARD_PIPS);
}
return 0;
}
int confirmation(int id,int side,int n,var q)
{
FXSTATE* F=&FX[id];
if(n==1)return 1;
if(n>=2 && n<=5) {
int b=3;
if(side<0) {
if(n==2)b=6;if(n==3)b=5;if(n==4)b=4;
return (F->crossMask & crossedBit(b,0))!=0;
}
if(n==2)b=0;if(n==3)b=1;if(n==4)b=2;
return (F->crossMask & crossedBit(b,1))!=0;
}
if(n>=6 && n<=8) {
var ratio=0.10;
if(n==7)ratio=0.25;
if(n==8)ratio=0.50;
if(side<0)return q<=F->h-ratio*F->range;
return q>=F->l+ratio*F->range;
}
if(n==9)return F->sweepStamp>0 && !F->retouch &&
clockSeconds()-F->sweepStamp>=SWEEP_TIMEOUT_SECONDS;
if(n==10)return F->retouch;
if(n==11) {
if(side<0)return (F->crossMask & crossedBit(6,0))!=0 &&
q>=F->band[5] && q<=F->band[6];
return (F->crossMask & crossedBit(0,1))!=0 &&
q>=F->band[0] && q<=F->band[1];
}
if(n==12) {
if(side<0)return (F->crossMask & crossedBit(5,0))!=0 &&
q>=F->band[4] && q<=F->band[5];
return (F->crossMask & crossedBit(1,1))!=0 &&
q>=F->band[1] && q<=F->band[2];
}
return 0;
}
var protectiveStop(int id,int side)
{
if(side<0)return FX[id].h+STOP_PERCENT*0.01*FX[id].range;
return FX[id].l-STOP_PERCENT*0.01*FX[id].range;
}
int currencyConflict(int id)
{
// All 28 are known 6-letter FX roots. Split by currency strings.
int k;
string a=ForexNames[id];
string base=strmid(a,0,3),quote=strmid(a,4,3);
for(k=0;k<ActivePairs;k++) {
if(k==id || !FX[k].usable)continue;
if(!asset(FX[k].symbol))continue;
algo("MK780");
if(NumOpenLong+NumOpenShort<=0)continue;
string b=strmid(ForexNames[k],0,3),q=strmid(ForexNames[k],4,3);
if(!strcmp(base,b) || !strcmp(base,q) ||
!strcmp(quote,b) || !strcmp(quote,q)) {
asset(FX[id].symbol);algo("MK780");return 1;
}
}
asset(FX[id].symbol);algo("MK780");
return 0;
}
int researchGate(int id,int n,int side,var q,var ask)
{
FXSTATE* F=&FX[id];int s=(side<0);
var o=F->o,h=F->h,l=F->l,c=F->c,r=F->range;
var u1=F->band[4],u2=F->band[5],u3=F->band[6];
var d1=F->band[2],d2=F->band[1],d3=F->band[0],v=F->v;
var wick=F->lw;if(s)wick=F->uw;
if(n==0 || n==26)return bandRule(id,side,0,q,ask);
if(n>=1 && n<=3)return bandRule(id,side,n,q,ask);
if(n==4) {if(s)return h>=u3 && q<u3;return l<=d3 && q>d3;}
if(n==5 || n==19) {if(s)return h>=u3 && q<=u2;return l<=d3 && q>=d2;}
if(n==6 || n==20) {if(s)return h>=u3 && q<=u1;return l<=d3 && q>=d1;}
if(n==7) {if(s)return h>=u2 && wick>F->body;return l<=d2 && wick>F->body;}
if(n==8) {if(s)return h>=u3 && wick/r>=0.35;return l<=d3 && wick/r>=0.35;}
if(n==9) {if(s)return h>=u2 && c<u1;return l<=d2 && c>d1;}
if(n==10) {if(s)return o<u1 && c>=u1;return o>d1 && c<=d1;}
if(n==11) {if(s)return o<u2 && c>=u2;return o>d2 && c<=d2;}
if(n==12) {if(s)return q>v && q<=u1;return q>=d1 && q<v;}
if(n==13) {if(s)return q>=u1 && q<=u2;return q>=d2 && q<=d1;}
if(n==14) {if(s)return q>=u2 && q<=u3;return q>=d3 && q<=d2;}
if(n==15)return payoffOK(id,side,ask,MIN_REWARD_PIPS);
if(n==16)return r/F->sigma>=1.;
if(n==17)return r/F->sigma<=1.;
if(n==18) {if(s)return h>=u3 && wick/r>=0.4;return l<=d3 && wick/r>=0.4;}
if(n>=21 && n<=23) {
if(s && h<u3)return 0;
if(!s && l>d3)return 0;
return confirmation(id,side,n-19,q);
}
if(n==24) {
if(s)return h>=u3 && F->retouch && F->phase==PH_READY;
return l<=d3 && F->retouch && F->phase==PH_READY;
}
if(n==25) {if(s)return h>=u3 && q<=h-0.1*r;return l<=d3 && q>=l+0.1*r;}
if(n==27) {
var stop=protectiveStop(id,side);
var risk=ask-stop,reward=v-ask;
if(s){risk=stop-ask;reward=ask-v;}
return risk>0 && reward>0 && reward/risk>=MIN_RISK_REWARD;
}
if(n==28) {
if(s)return h>=u3 && c>=u2 && zoneAt(id,q)==6;
return l<=d3 && c<=d2 && zoneAt(id,q)==1;
}
if(n==29) {
if(s)return h>=u3 && c<u2 && zoneAt(id,q)==5;
return l<=d3 && c>d2 && zoneAt(id,q)==2;
}
if(n==30) {
if(s && h<u3)return 0;if(!s && l>d3)return 0;
return clockSeconds()-F->sweepStamp<=60.;
}
if(n==31) {
if(s)return h>=u3 && q<=h-MIN_RETURN_TICKS*PIP;
return l<=d3 && q>=l+MIN_RETURN_TICKS*PIP;
}
if(n==32) {
if(s)return h>=u3 && Spread<=PIP;
return l<=d3 && Spread<=PIP;
}
if(n==33) {
if(s && h<u3)return 0;if(!s && l>d3)return 0;
return !currencyConflict(id);
}
if(n==34 || n==35) {if(s)return h>=u3;return l<=d3;}
return 0;
}
// Universal 300-event event catalog from uploaded MQL4:
// event=CATALOG_ID/3 (0..99), action=CATALOG_ID%3 (BUY/SELL/EXIT).
int catalogEvent(int id,var prior,var current,int observed,int newBar)
{
FXSTATE* F=&FX[id];
int evt=CATALOG_ID/3;
int beforeZone=7-zoneAt(id,prior),nowZone=7-zoneAt(id,current);
int bandIndex, up, source,dest,counter;
var line;
if(evt<8)return observed && beforeZone!=nowZone && nowZone==evt;
if(evt<22) {
if(!observed)return 0;
bandIndex=(evt-8)/2;up=((evt-8)%2)==0;
line=F->band[6-bandIndex];
if(up)return prior<=line && current>line;
return prior>=line && current<line;
}
if(evt<36) {
if(!newBar)return 0;
bandIndex=(evt-22)/2;up=((evt-22)%2)==0;
line=F->band[6-bandIndex];
if(up)return F->h>=line && F->c<line;
return F->l<=line && F->c>line;
}
if(evt<92) {
if(!observed || beforeZone==nowZone)return 0;
counter=evt-36;
for(source=0;source<8;source++)for(dest=0;dest<8;dest++) {
if(source==dest)continue;
if(counter==0)return beforeZone==source && nowZone==dest;
counter--;
}
return 0;
}
// One persistence signal per completed bar after two observations in same zone.
if(!observed || nowZone!=evt-92 || beforeZone!=nowZone ||
F->lastPersistBar==Bar)return 0;
F->lastPersistBar=Bar;return 1;
}
int effectiveExit()
{
if(ENGINE_MODE==0 && (RESEARCH_ID==18 || RESEARCH_ID==19))return 2;
if(ENGINE_MODE==0 && RESEARCH_ID==34)return 1;
return EXIT_RULE_ID;
}
var exitTarget(int id,int side,var entry,int rule)
{
FXSTATE* F=&FX[id];
var v=F->v,sd=F->sigma,r=F->range;
if(rule==2) {if(side<0)return F->band[4];return F->band[2];}
if(rule==3) {if(side<0)return F->band[5];return F->band[1];}
if(rule==4) {if(side<0)return F->band[2];return F->band[4];}
if(rule==5) {if(side<0)return F->band[1];return F->band[5];}
if(rule==6) {if(side<0)return F->band[0];return F->band[6];}
if(rule==7)return (entry+v)*0.5;
if(rule==8)return entry+side*EXIT_SIGMA_MULTIPLE*sd;
if(rule==9)return entry+side*EXIT_RANGE_MULTIPLE*r;
if(rule==10)return v+side*EXIT_RANGE_PERCENT*0.01*r;
return v;
}
int crossedExit(int side,var entry,var price,var target,int allowAdverse)
{
if(side<0) {
if(target<entry)return price<=target;
return allowAdverse && price>=target;
}
if(target>entry)return price>=target;
return allowAdverse && price<=target;
}
int managePosition(int id,int newBar)
{
FXSTATE* F=&FX[id];
int rule=effectiveExit();
int wanted=0;
// for(current_trades) loops only current Asset AND Algo.
for(current_trades) {
if(!TradeIsOpen || TradeIsPhantom)continue;
int side=1;
if(TradeIsShort)side=-1;
var entry=TradePriceOpen; // Zorro's ask-based entry reference
var ask=AskPrice,bid=BidPrice;
var target=exitTarget(id,side,entry,rule);
if(ENGINE_MODE==0 && RESEARCH_ID==34 && F->frozenCenter>0)
target=F->frozenCenter;
wanted=0;
if(rule>=1 && rule<=15) {
if(rule!=15 || TradeBars>=EXIT_WAIT_BARS)
if(side<0)wanted=crossedExit(side,entry,ask,target,rule==14);
else wanted=crossedExit(side,entry,bid,target,rule==14);
}
if(rule==16)wanted=(TradeBars*TIMEFRAME_MINUTES>=EXIT_DEADLINE_MINUTES);
if(rule==17 && newBar)wanted=(F->dir==-side);
if(rule==18)wanted=(F->dir==-side && F->phase==PH_READY);
if(rule==19) {
if(!F->partialDone) {
var first=exitTarget(id,side,entry,2);
int hitFirst=0;
if(side<0)hitFirst=crossedExit(side,entry,ask,first,0);
else hitFirst=crossedExit(side,entry,bid,first,0);
if(hitFirst) {
int partial=(int)(TradeLots*PARTIAL_PERCENT/100.);
if(partial>=1 && TradeLots-partial>=1) {
if(exitTrade(ThisTrade,0,partial))F->partialDone=1;
} else F->partialDone=1;
}
}
if(F->partialDone)
{ if(side<0)wanted=crossedExit(side,entry,ask,F->v,0);
else wanted=crossedExit(side,entry,bid,F->v,0); }
}
if(rule==20) {
var gap=TRAIL_SIGMA_MULTIPLE*F->sigma;
if(F->highWater==0 || bid>F->highWater)F->highWater=bid;
if(F->lowWater==0 || ask<F->lowWater)F->lowWater=ask;
if(side>0 && F->highWater>entry+gap)
wanted=(bid<=max(F->v-gap,F->highWater-gap));
if(side<0 && F->lowWater<entry-gap)
wanted=(ask>=min(F->v+gap,F->lowWater+gap));
}
if(wanted && ENABLE_SOFTWARE_EXIT)
exitTrade(ThisTrade); // auto-retry on subsequent quote if still open
}
return wanted;
}
int countPortfolio()
{
int n=0;
for(open_trades)
if(TradeIsOpen && !TradeIsPhantom && !strcmp(TradeAlgo,"MK780"))n++;
return n;
}
int entryEligible(int id,int side,var ask)
{
if(!FX[id].valid || FX[id].range<=0)return 0;
if(NumOpenLong+NumOpenShort+NumPendingLong+NumPendingShort>0)return 0;
if(countPortfolio()>=MAX_PORTFOLIO_POSITIONS)return 0;
var spreadLimit=MAX_SPREAD_PIPS;
if(spreadLimit>0 && Spread>spreadLimit*PIP)return 0;
if(side<0 && protectiveStop(id,side)<=ask+PIP)return 0;
if(side>0 && protectiveStop(id,side)>=ask-Spread-PIP)return 0;
return 1;
}
int placeSignal(int id,int side,var ask)
{
FXSTATE* F=&FX[id];
if(!entryEligible(id,side,ask))return 0;
var stop=protectiveStop(id,side);
var trigger=0;
if(ENTRY_STYLE==1) {
if(side<0)trigger=F->h-PENDING_PERCENT*0.01*F->range;
else trigger=F->l+PENDING_PERCENT*0.01*F->range;
// Zorro Entry is always an ASK price; SELL stop needs ask conversion.
// Current Spread is only an estimate of future spread at execution.
if(side<0)trigger+=Spread;
if(side<0 && !(trigger<ask-PIP))return 0;
if(side>0 && !(trigger>ask+PIP))return 0;
}
Entry=trigger;
Stop=stop; // absolute ASK-level; original MQL broker stops may differ
TakeProfit=0; // software VWAP exits, do not set a wrong-side TP
StopFactor=0;
if(ENABLE_BROKER_STOP)StopFactor=1;
EntryTime=ENTRY_WAIT_BARS;
MaxLong=1;MaxShort=1;
Lots=ORDER_LOTS;
TRADE* tr=0;
if(side<0)tr=enterShort();
else tr=enterLong();
Entry=0;Stop=0;TakeProfit=0;
if(!tr)return 0;
F->activeBefore=1;
F->frozenCenter=F->v;
F->highWater=0;F->lowWater=0;
F->partialDone=0;
F->lastEntryBar=Bar;
F->lastEntryLevel=trigger;
F->entryStamp=clockSeconds();
resetPhase(id);
return 1;
}
void cancelOldPending()
{
// Called after a new completed candle (reference levels changed).
for(current_trades) {
if(TradeIsPending)exitTrade(ThisTrade);
}
}
void onQuote(int id,int newBar)
{
FXSTATE* F=&FX[id];
if(!F->usable || !F->valid || F->signature<0)return;
// Set the current asset/algo context before invoking trade statistics/functions.
if(!asset(F->symbol))return;
algo("MK780");
var ask=AskPrice;
if(ask<=0)return;
var spreadLimit=MAX_SPREAD_PIPS;
if(spreadLimit>0 && Spread>spreadLimit*PIP)return;
int present=NumOpenLong+NumOpenShort+NumPendingLong+NumPendingShort;
if(F->activeBefore && present==0) {
resetPhase(id);
F->quoteKnown=0;F->activeBefore=0;
F->partialDone=0;F->frozenCenter=0;
}
if(present>0)F->activeBefore=1;
int observed=F->quoteKnown && abs(ask-F->previousAsk)>PIP*0.001;
var prev=F->previousAsk;
F->previousAsk=ask;
F->quoteKnown=1;
if(observed && (present==0 || effectiveExit()==18))
sweepEvent(id,prev,ask,observed);
int cat=0;
if(ENGINE_MODE==1)cat=catalogEvent(id,prev,ask,observed,newBar);
if(NumOpenLong+NumOpenShort>0) {
if(ENGINE_MODE==1 && cat && CATALOG_ID%3==2) {
exitLong();exitShort();
return;
}
managePosition(id,newBar);
return;
}
if(NumPendingLong+NumPendingShort>0)return;
if(is(LOOKBACK))return;
if(SIGNATURE_FILTER>=0 && F->signature!=SIGNATURE_FILTER)return;
if(!anatomy(id,CANDLE_RULE_ID,ask))return;
if(ENGINE_MODE==3 && !MARKOV_NEED_SWEEP) {
if(F->lastEntryBar==Bar)return;
int decision=markovDecision(id);
if(decision && passMarkov(id,decision))placeSignal(id,decision,ask);
return;
}
if(ENGINE_MODE==1) {
if(!cat || CATALOG_ID%3==2)return;
int side=-1;
if(CATALOG_ID%3==0)side=1;
if(passMarkov(id,side))placeSignal(id,side,ask);
return;
}
if(F->phase!=PH_READY || F->dir==0)return;
int side=F->dir;
if(!passMarkov(id,side))return;
if(ENGINE_MODE==0 && !researchGate(id,RESEARCH_ID,side,ask,ask))return;
if(ENGINE_MODE==2) {
if(side<0 && (!bandRule(id,side,SELL_STATIC_ID,ask,ask) ||
!confirmation(id,side,SELL_EVENT_ID,ask)))return;
if(side>0 && (!bandRule(id,side,BUY_STATIC_ID,ask,ask) ||
!confirmation(id,side,BUY_EVENT_ID,ask)))return;
}
placeSignal(id,side,ask);
}
// Zorro run() is called after each completed bar. In run, [0] is the
// finished bar; in tick, [0] is still changing, and must NOT update VWAP.
void run()
{
if(is(INITRUN)) {
initForexNames();
BarPeriod=TIMEFRAME_MINUTES;
LookBack=MK_WINDOW+VWAP_PERIOD+40;
set(TICKS);
TickTime=0;
MaxLong=1;MaxShort=1;
ActivePairs=MAX_PAIRS;
if(SINGLE_ASSET)ActivePairs=1;
if(!initMarkov()) {quit("!Cannot allocate 780-state Markov tables");return;}
int loaded=assetList(ASSET_LIST);
if(!loaded) {quit("!Asset list unavailable");return;}
int i;
for(i=0;i<ActivePairs;i++) {
FX[i].symbol=ForexNames[i];
if(SINGLE_ASSET)FX[i].symbol=SINGLE_ASSET_NAME;
FX[i].usable=0;FX[i].signature=-1;FX[i].lastEntryBar=-1;
FX[i].lastPersistBar=-1;FX[i].ringHead=0;FX[i].ringSize=0;
resetPhase(i);
// Assets must be selected in INITRUN to load/subscribed histories.
if(asset(FX[i].symbol)) {
FX[i].usable=1;
algo("MK780");
} else
printf("\nUnavailable asset: %s (check %s)",FX[i].symbol,ASSET_LIST);
}
return;
}
if(is(EXITRUN)) {releaseMarkov();return;}
int i;
for(i=0;i<ActivePairs;i++) {
if(!FX[i].usable)continue;
if(!asset(FX[i].symbol))continue;
algo("MK780");
// reference bar data + new Markov state, no future data or files.
completeCandle(i);
if(!is(LOOKBACK)) {
cancelOldPending(); // new candle invalidates prior candle's STOP level
onQuote(i,1); // completed-candle rejection and X17 exit
}
}
}
void tick()
{
if(is(INITRUN) || is(EXITRUN))return;
// tick() is called for the subscribed asset that just got a new quote.
int id=findFX(Asset);
if(id<0)return;
onQuote(id,0);
}
|
|
|
Term Structure Covariation Strategy
[Re: TipmyPip]
#489722
1 hour ago
1 hour ago
|
Joined: Sep 2017
Posts: 341
TipmyPip
OP
Senior Member
|
OP
Senior Member
Joined: Sep 2017
Posts: 341
|
Term Structure Covariation Strategy The strategy can be understood as a sequence of lenses, filters, and gates. Each stage transforms ordinary Forex prices into a structured picture of market movement, then decides whether that picture permits a trade. The observation window A moving window collects closing prices from completed candles. The prices are arranged from oldest to newest, preventing unfinished bars from influencing the analysis. Logarithmic price changes describe movement proportionally rather than through raw price differences. The maturity lenses The strategy views each price change through several synthetic maturity lenses. Shorter and longer maturity intervals receive different amplification weights. These lenses do not observe separate financial instruments. They reshape the same Forex return into several related channels, creating an artificial maturity structure rather than measuring an actual bond yield curve. The noise filter The analyzer measures the combined strength of each observation across those channels. It sorts these measurements and uses the quieter portion of the sample to estimate a preliminary background scale. An adjustable truncation setting converts that scale into a boundary. Ordinary observations remain inside the boundary, while unusually large movements fall outside it and become potential jumps. The two market portraits The strategy constructs two covariation portraits. The first includes every observation and represents total measured movement. The second excludes observations beyond the truncation boundary and represents movement after large shocks have been filtered out. Both portraits retain the original sample length when averaging. Filtering therefore removes contributions without increasing the weight of the remaining observations. The factor prism A Jacobi decomposition separates the filtered portrait into principal directions and their relative strengths. The strongest direction becomes the first factor. Additional factors are counted until their combined contribution reaches the configured explanation target. The analyzer also calculates a matrix magnitude called the Hilbert Schmidt norm. Despite its volatility label in the original implementation, this quantity measures covariance magnitude, not conventional annualized volatility. The trading compass The first factor passes through a maturity weighted scoring rule. A sufficiently positive score requests a purchase. A sufficiently negative score requests a sale. Scores inside the decision boundary remain neutral. An optional setting adjusts that boundary according to the covariance magnitude. The protection gates Before entry, the strategy checks drawdown, factor count, underlying movement, existing positions, and signal spacing. An opposing position must close before reversal. Order size follows either a fixed Forex volume or a stop based risk budget, subject to available margin. Stop loss, take profit, and stepped trailing protection manage exits. The essential warning Every synthetic channel originates from the same return stream. Consequently, the unregularized structure contains only one independent direction. Its orientation does not establish economic buying or selling meaning. The preserved trading compass is therefore experimental rather than a validated forecast. #include <default.c>
#ifndef TERM_STRUCTURE_LIB_H
#define TERM_STRUCTURE_LIB_H
// Zorro 32-bit lite-C. Singleton analyzer; all matrices are row-major.
// Forex spot proxy, NOT observations of a bond term structure.
#define TS_MAX_MATURITIES 16
#define TS_MAX_PAIRS 15
#define TS_MATRIX_SIZE 225
#define TS_MAX_PRICES 2048
#define TS_RETURN_SIZE 30705
int TS_Maturities[TS_MAX_MATURITIES];
int TS_MaturityCount = 0;
int TS_Lookback = 250;
int TS_TimeSteps = 0;
int TS_Pairs = 0;
int TS_Initialized = 0;
int TS_NumFactors = 0;
int TS_JumpCount = 0;
int TS_KeptCount = 0;
int TS_Debug = 0;
int TS_DataDriven = 1;
int TS_Regularize = 1;
var TS_Alpha = 3.;
var TS_VarianceTarget = 0.85;
var TS_PreliminaryVariance = 0.;
var TS_Threshold = 0.;
var TS_IntegratedVolatility = 0.;
var TS_RealizedTrace = 0.;
var TS_Returns[TS_RETURN_SIZE];
var TS_Norms[TS_MAX_PRICES];
var TS_SortedNorms[TS_MAX_PRICES];
var TS_LogPrices[TS_MAX_PRICES];
var TS_Realized[TS_MATRIX_SIZE];
var TS_Truncated[TS_MATRIX_SIZE];
var TS_Eigenvalues[TS_MAX_PAIRS];
var TS_ExplainedVariance[TS_MAX_PAIRS];
var TS_Eigenvectors[TS_MATRIX_SIZE];
var TS_WorkMatrix[TS_MATRIX_SIZE];
var TS_RawEigenvalues[TS_MAX_PAIRS];
void TS_ResetResults()
{
TS_Initialized = 0;
TS_NumFactors = 0;
TS_TimeSteps = 0;
TS_Pairs = 0;
TS_JumpCount = 0;
TS_KeptCount = 0;
TS_PreliminaryVariance = 0.;
TS_Threshold = 0.;
TS_IntegratedVolatility = 0.;
TS_RealizedTrace = 0.;
}
int TS_SetMaturities(int* Tenors, int TenorCount)
{
int K;
TS_ResetResults();
if(TenorCount < 2 || TenorCount > TS_MAX_MATURITIES) return 0;
for(K = 0; K < TenorCount; K++) {
if(Tenors[K] <= 0) return 0;
if(K > 0 && Tenors[K] <= Tenors[K-1]) return 0;
}
for(K = 0; K < TenorCount; K++) TS_Maturities[K] = Tenors[K];
TS_MaturityCount = TenorCount;
return 1;
}
var TS_MatrixTrace(vars MatrixData, int Dimension)
{
int K;
var Total = 0.;
for(K = 0; K < Dimension; K++) Total += MatrixData[K*Dimension+K];
return Total;
}
var TS_FrobeniusNorm(vars MatrixData, int Dimension)
{
int K;
var Total = 0.;
for(K = 0; K < Dimension*Dimension; K++)
Total += MatrixData[K]*MatrixData[K];
return sqrt(Total);
}
// Symmetric Jacobi solver: columns of Vectors are orthonormal eigenvectors.
// Unlike the source, non-convergence is a failure, not a successful result.
int TS_EigenDecomposition(vars MatrixData, int Dimension,
vars Values, vars Vectors)
{
int I, J, K, Iteration, P, Q, MaxIndex, Converged;
var Scale, Tolerance, Largest, App, Aqq, Apq;
var Theta, Rotation, Cosine, Sine, Aip, Aiq, Vip, Viq, Temp;
if(Dimension < 1 || Dimension > TS_MAX_PAIRS) return 0;
Scale = 0.;
for(I = 0; I < Dimension*Dimension; I++) {
TS_WorkMatrix[I] = MatrixData[I];
Vectors[I] = 0.;
Scale = max(Scale,abs(MatrixData[I]));
}
for(I = 0; I < Dimension; I++) Vectors[I*Dimension+I] = 1.;
Tolerance = max(1.e-30,Scale*1.e-12);
Converged = 0;
for(Iteration = 0; Iteration < 150*Dimension*Dimension; Iteration++) {
Largest = 0.;
P = 0;
Q = 0;
for(I = 0; I < Dimension; I++) {
for(J = I+1; J < Dimension; J++) {
Temp = abs(TS_WorkMatrix[I*Dimension+J]);
if(Temp > Largest) { Largest = Temp; P = I; Q = J; }
}
}
if(Largest <= Tolerance) { Converged = 1; break; }
App = TS_WorkMatrix[P*Dimension+P];
Aqq = TS_WorkMatrix[Q*Dimension+Q];
Apq = TS_WorkMatrix[P*Dimension+Q];
Theta = (Aqq-App)/(2.*Apq);
// Stable equivalent to sign(theta)/(abs(theta)+sqrt(1+theta^2)).
if(abs(Theta) > 1.e100) Rotation = 0.5/Theta;
else {
Rotation = 1./(abs(Theta)+sqrt(1.+Theta*Theta));
if(Theta < 0.) Rotation = -Rotation;
}
Cosine = 1./sqrt(1.+Rotation*Rotation);
Sine = Rotation*Cosine;
TS_WorkMatrix[P*Dimension+P] = App-Rotation*Apq;
TS_WorkMatrix[Q*Dimension+Q] = Aqq+Rotation*Apq;
TS_WorkMatrix[P*Dimension+Q] = 0.;
TS_WorkMatrix[Q*Dimension+P] = 0.;
for(I = 0; I < Dimension; I++) {
if(I != P && I != Q) {
Aip = TS_WorkMatrix[I*Dimension+P];
Aiq = TS_WorkMatrix[I*Dimension+Q];
TS_WorkMatrix[I*Dimension+P] = Cosine*Aip-Sine*Aiq;
TS_WorkMatrix[P*Dimension+I] = TS_WorkMatrix[I*Dimension+P];
TS_WorkMatrix[I*Dimension+Q] = Sine*Aip+Cosine*Aiq;
TS_WorkMatrix[Q*Dimension+I] = TS_WorkMatrix[I*Dimension+Q];
}
Vip = Vectors[I*Dimension+P];
Viq = Vectors[I*Dimension+Q];
Vectors[I*Dimension+P] = Cosine*Vip-Sine*Viq;
Vectors[I*Dimension+Q] = Sine*Vip+Cosine*Viq;
}
}
if(!Converged) return 0;
for(I = 0; I < Dimension; I++) Values[I] = TS_WorkMatrix[I*Dimension+I];
for(I = 0; I < Dimension-1; I++) {
MaxIndex = I;
for(J = I+1; J < Dimension; J++)
if(abs(Values[J]) > abs(Values[MaxIndex])) MaxIndex = J;
if(MaxIndex != I) {
Temp = Values[I]; Values[I] = Values[MaxIndex]; Values[MaxIndex] = Temp;
for(K = 0; K < Dimension; K++) {
Temp = Vectors[K*Dimension+I];
Vectors[K*Dimension+I] = Vectors[K*Dimension+MaxIndex];
Vectors[K*Dimension+MaxIndex] = Temp;
}
}
}
return 1;
}
int TS_ComputePreliminaryEstimate()
{
int N, J, K, ValidSamples, CutoffIndex;
var Temporary, Quantile, TraceSum;
for(N = 0; N < TS_TimeSteps; N++) TS_SortedNorms[N] = TS_Norms[N];
// Insertion sort: no dependency on MQL ArraySort or C qsort callbacks.
for(N = 1; N < TS_TimeSteps; N++) {
Temporary = TS_SortedNorms[N];
K = N-1;
while(K >= 0) {
if(TS_SortedNorms[K] <= Temporary) break;
TS_SortedNorms[K+1] = TS_SortedNorms[K];
K--;
}
TS_SortedNorms[K+1] = Temporary;
}
CutoffIndex = (int)(TS_TimeSteps*0.75);
Quantile = TS_SortedNorms[CutoffIndex];
ValidSamples = 0;
TraceSum = 0.;
// Only the trace is needed; equivalent to constructing prelimCov.
for(N = 0; N < TS_TimeSteps; N++) {
if(TS_Norms[N] <= Quantile) {
ValidSamples++;
for(J = 0; J < TS_Pairs; J++) {
Temporary = TS_Returns[N*TS_Pairs+J];
TraceSum += Temporary*Temporary;
}
}
}
TS_PreliminaryVariance = 0.;
if(ValidSamples > 0)
TS_PreliminaryVariance = TraceSum/(ValidSamples*TS_Pairs);
return TS_PreliminaryVariance > 1.e-15;
}
// Prices must be oldest -> newest. Uses only the last TS_Lookback prices.
int TS_ComputeCovariations(vars Prices, int PriceCount)
{
int I, J, N, Offset, PreliminaryOK;
var LogReturn, Amplification, SumSquares, Product, Total, Cumulative;
TS_ResetResults();
if(TS_Lookback < 2 || TS_Lookback > TS_MAX_PRICES) return 0;
if(PriceCount < TS_Lookback || TS_MaturityCount < 2) return 0;
if(TS_Alpha <= 0. || TS_VarianceTarget <= 0. || TS_VarianceTarget > 1.) return 0;
Offset = PriceCount-TS_Lookback;
for(I = 0; I < TS_Lookback; I++) {
// Comparisons also reject NaN; upper bound prevents overflow inputs.
if(!(Prices[Offset+I] > 0.) || Prices[Offset+I] > 1.e100) return 0;
TS_LogPrices[I] = log(Prices[Offset+I]);
}
TS_TimeSteps = TS_Lookback-1;
TS_Pairs = TS_MaturityCount-1;
for(N = 0; N < TS_TimeSteps; N++) {
LogReturn = TS_LogPrices[N+1]-TS_LogPrices[N];
SumSquares = 0.;
for(J = 0; J < TS_Pairs; J++) {
Amplification = sqrt((TS_Maturities[J+1]-TS_Maturities[J])/30.)*(J+1)*2.;
Product = LogReturn*Amplification;
TS_Returns[N*TS_Pairs+J] = Product;
SumSquares += Product*Product;
}
TS_Norms[N] = sqrt(SumSquares);
}
PreliminaryOK = TS_ComputePreliminaryEstimate();
TS_Threshold = TS_Alpha*sqrt(TS_PreliminaryVariance);
if(TS_DataDriven && PreliminaryOK) TS_Threshold *= sqrt(TS_Pairs);
// When DataDriven=0 use alpha*sqrt(preliminaryVariance), in norm units.
for(N = 0; N < TS_TimeSteps; N++) {
if(TS_Norms[N] < TS_Threshold) TS_KeptCount++;
if(TS_Norms[N] > TS_Threshold) TS_JumpCount++;
}
for(I = 0; I < TS_Pairs; I++) {
for(J = I; J < TS_Pairs; J++) {
Total = 0.;
Cumulative = 0.;
for(N = 0; N < TS_TimeSteps; N++) {
Product = TS_Returns[N*TS_Pairs+I]*TS_Returns[N*TS_Pairs+J];
Total += Product;
if(TS_Norms[N] < TS_Threshold) Cumulative += Product;
}
TS_Realized[I*TS_Pairs+J] = Total/TS_TimeSteps;
TS_Realized[J*TS_Pairs+I] = Total/TS_TimeSteps;
TS_Truncated[I*TS_Pairs+J] = Cumulative/TS_TimeSteps;
TS_Truncated[J*TS_Pairs+I] = Cumulative/TS_TimeSteps;
}
}
TS_RealizedTrace = TS_MatrixTrace(TS_Realized,TS_Pairs);
if(TS_Regularize && TS_MatrixTrace(TS_Truncated,TS_Pairs) < 1.e-12)
for(I = 0; I < TS_Pairs; I++) TS_Truncated[I*TS_Pairs+I] += 1.e-9;
if(!TS_EigenDecomposition(TS_Truncated,TS_Pairs,
TS_RawEigenvalues,TS_Eigenvectors)) return 0;
TS_IntegratedVolatility = TS_FrobeniusNorm(TS_Truncated,TS_Pairs);
Total = 0.;
for(I = 0; I < TS_Pairs; I++) Total += abs(TS_RawEigenvalues[I]);
if(Total < 1.e-15) return 0;
Cumulative = 0.;
for(I = 0; I < TS_Pairs; I++) {
TS_Eigenvalues[I] = abs(TS_RawEigenvalues[I])/Total;
Cumulative += TS_Eigenvalues[I];
TS_ExplainedVariance[I] = Cumulative;
if(TS_NumFactors == 0 && Cumulative+1.e-14 >= TS_VarianceTarget)
TS_NumFactors = I+1;
}
if(TS_NumFactors == 0) TS_NumFactors = TS_Pairs;
TS_Initialized = 1;
if(TS_Debug)
printf("\nTS: HS=%.10g factors=%d kept=%d/%d jumps=%d threshold=%.8g",
TS_IntegratedVolatility,TS_NumFactors,TS_KeptCount,TS_TimeSteps,
TS_JumpCount,TS_Threshold);
return 1;
}
var TS_GetIntegratedVolatility()
{
if(!TS_Initialized) return 0.;
return TS_IntegratedVolatility;
}
int TS_GetNumberOfFactors()
{
if(!TS_Initialized) return 0;
return TS_NumFactors;
}
int TS_GetEigenvalues(vars Destination, int Capacity)
{
int K;
if(!TS_Initialized || Capacity < TS_Pairs) return 0;
for(K = 0; K < TS_Pairs; K++) Destination[K] = TS_Eigenvalues[K];
return TS_Pairs;
}
int TS_GetFactorLoadings(int FactorIndex, vars Destination, int Capacity)
{
int K;
if(!TS_Initialized || FactorIndex < 0 || FactorIndex >= TS_Pairs) return 0;
if(Capacity < TS_Pairs) return 0;
for(K = 0; K < TS_Pairs; K++)
Destination[K] = TS_Eigenvectors[K*TS_Pairs+FactorIndex];
return TS_Pairs;
}
var TS_GetExplainedVariance(int FactorCount)
{
if(!TS_Initialized || FactorCount <= 0 || FactorCount > TS_Pairs) return 0.;
return TS_ExplainedVariance[FactorCount-1];
}
// Returns count; -1 = invalid/uninitialized/insufficient output capacity.
// Indices are zero-based return indices in the oldest -> newest window.
int TS_DetectJumps(vars Indices, vars Sizes, int Capacity)
{
int N, K;
if(!TS_Initialized || Capacity < TS_JumpCount) return -1;
K = 0;
for(N = 0; N < TS_TimeSteps; N++) {
if(TS_Norms[N] > TS_Threshold) {
Indices[K] = N;
Sizes[K] = TS_Norms[N];
K++;
}
}
return K;
}
void TS_PrintDiagnostics()
{
int K;
if(!TS_Initialized) { printf("\nTS: analyzer not initialized"); return; }
printf("\nTERM STRUCTURE SPOT-PROXY DIAGNOSTICS");
printf("\nSteps=%d pairs=%d HS=%.10g factors=%d target=%.1f%%",
TS_TimeSteps,TS_Pairs,TS_IntegratedVolatility,TS_NumFactors,
TS_VarianceTarget*100.);
printf("\nPreliminary=%.10g threshold=%.10g jumps=%d excluded=%d",
TS_PreliminaryVariance,TS_Threshold,TS_JumpCount,TS_TimeSteps-TS_KeptCount);
for(K = 0; K < TS_Pairs; K++)
printf("\n lambda[%d]=%.5f%% cumulative=%.5f%%",K,
TS_Eigenvalues[K]*100.,TS_ExplainedVariance[K]*100.);
}
#endif
// ===== End TermStructureLib.h =====
// Configuration. Forex maturity proxy is preserved from the supplied EA.
string InpAsset = "EUR/USD";
int InpBarPeriod = 60;
int InpStartDate = 20240101;
int InpEndDate = 20241231;
int InpMaturity1 = 30;
int InpMaturity2 = 90;
int InpMaturity3 = 180;
int InpMaturity4 = 360;
int InpLookbackBars = 100;
var InpTruncLevel = 3.;
var InpVarianceThreshold = 0.85;
int InpUseDataDrivenTruncation = 1;
int InpRegularizeSmallTrace = 1;
var InpSignalThreshold = 0.000001;
int InpUseVolatilityScaling = 0;
var InpVolatilityScaleFactor = 3.;
int InpMinBarsBetweenSignals = 1;
int InpMinFactorsRequired = 1;
var InpLotSize = 0.1; // MT5 standard Forex lots, 100000 units each.
int InpUseAdaptiveLots = 0;
var InpRiskPercent = 2.;
var InpInitialCapital = 10000.;
var InpMaxDrawdownPercent = 15.;
var InpStopLossPips = 50.;
var InpTakeProfitPips = 125.;
var InpRiskRewardRatio = 2.5;
int InpUseTrailingStop = 1;
var InpTrailingStopPips = 40.;
var InpTrailingStepPips = 10.;
// Zorro plots replace MT5 objects; native trade markers show fills/exits.
int InpShowPanel = 1;
int InpShowSignals = 1;
int InpShowDebugLogs = 0;
int InpShowDiagnostics = 0;
int InpBuyColor = GREEN;
int InpSellColor = RED;
var TS_InputPrices[TS_MAX_PRICES];
var TS_FirstFactor[TS_MAX_PAIRS];
int TS_ConfigMaturities[4];
int TS_LastTradeBar = -1000000;
int TS_TotalSignals = 0;
int TS_WinningTrades = 0;
int TS_LosingTrades = 0;
var TS_CurrentDrawdown = 0.;
var TS_SlopeFactor = 0.;
int TS_Configure()
{
if(InpLookbackBars < 2 || InpLookbackBars > TS_MAX_PRICES) return 0;
if(InpTruncLevel <= 0. || InpVarianceThreshold <= 0. || InpVarianceThreshold > 1.) return 0;
if(InpLotSize <= 0. || InpInitialCapital <= 0. || InpBarPeriod <= 0) return 0;
if(InpRiskPercent <= 0. || InpRiskPercent > 100. || InpRiskRewardRatio <= 0.) return 0;
if(InpMinFactorsRequired < 1 || InpMinFactorsRequired > 3) return 0;
if(InpMinBarsBetweenSignals < 0 || InpSignalThreshold <= 0.) return 0;
if(InpMaxDrawdownPercent < 0. || InpVolatilityScaleFactor < 0.) return 0;
if(InpUseTrailingStop && (InpTrailingStopPips <= 0. || InpTrailingStepPips < 0.)) return 0;
TS_ConfigMaturities[0] = InpMaturity1;
TS_ConfigMaturities[1] = InpMaturity2;
TS_ConfigMaturities[2] = InpMaturity3;
TS_ConfigMaturities[3] = InpMaturity4;
if(!TS_SetMaturities(TS_ConfigMaturities,4)) return 0;
TS_Lookback = InpLookbackBars;
TS_Alpha = InpTruncLevel;
TS_VarianceTarget = InpVarianceThreshold;
TS_DataDriven = InpUseDataDrivenTruncation;
TS_Regularize = InpRegularizeSmallTrace;
TS_Debug = InpShowDebugLogs;
TS_LastTradeBar = -1000000;
TS_TotalSignals = 0;
TS_WinningTrades = 0;
TS_LosingTrades = 0;
return 1;
}
// Called by Zorro for price updates and closures, including SL/TP exits.
int TS_ManageTrade()
{
var ExitPrice, Candidate, Distance, StepDistance;
if(TradeIsClosed) {
if(TradeProfit > 0.) TS_WinningTrades++;
if(TradeProfit < 0.) TS_LosingTrades++;
return 0;
}
if(!TradeIsOpen || !InpUseTrailingStop || InpStopLossPips <= 0.) return 0;
if(TradeStopLimit == 0.) return 0;
Distance = InpTrailingStopPips*PIP;
StepDistance = InpTrailingStepPips*PIP;
ExitPrice = TradePriceClose;
if(TradeIsLong) {
// Zorro prices are ask; long exits use bid, short exits use ask.
ExitPrice -= Spread;
Candidate = roundto(ExitPrice-Distance,PIP/10.);
if(ExitPrice > TradePriceOpen+Distance && Candidate > TradeStopLimit+StepDistance)
TradeStopLimit = Candidate;
} else {
Candidate = roundto(ExitPrice+Distance,PIP/10.);
if(ExitPrice < TradePriceOpen-Distance && Candidate < TradeStopLimit-StepDistance)
TradeStopLimit = Candidate;
}
return 0;
}
int TS_UpdateAnalysis()
{
int K;
// Only completed candles, newest at index N-1. No MQL series-flag reversal.
for(K = 0; K < InpLookbackBars; K++)
TS_InputPrices[K] = priceClose(InpLookbackBars-K);
return TS_ComputeCovariations(TS_InputPrices,InpLookbackBars);
}
var TS_CalculateDrawdown()
{
if(Balance <= 0.) return 0.;
return max(0.,100.*(Balance-Equity)/Balance);
}
int TS_CheckTradingConditions()
{
TS_CurrentDrawdown = TS_CalculateDrawdown();
if(TS_CurrentDrawdown > InpMaxDrawdownPercent) return 0;
if(TS_GetNumberOfFactors() < InpMinFactorsRequired) return 0;
// Prevent regularization from manufacturing a tradable flat-price signal.
if(TS_RealizedTrace <= 1.e-20) return 0;
if(TS_GetIntegratedVolatility() < 1.e-10) return 0;
return 1;
}
int TS_GenerateSignal()
{
int K, Count;
var WeightSum, Threshold;
Count = TS_GetFactorLoadings(0,TS_FirstFactor,TS_MAX_PAIRS);
if(Count < 2) return 0;
TS_SlopeFactor = 0.;
WeightSum = 0.;
for(K = 0; K < Count; K++) {
TS_SlopeFactor += TS_FirstFactor[K]*(K+1.);
WeightSum += K+1.;
}
TS_SlopeFactor /= WeightSum;
Threshold = InpSignalThreshold;
if(InpUseVolatilityScaling)
Threshold = max(Threshold,TS_GetIntegratedVolatility()*InpVolatilityScaleFactor);
if(InpShowDebugLogs)
printf("\nTS slope=%.8g threshold=%.8g",TS_SlopeFactor,Threshold);
// Compatibility only: eigenvector signs are not economic directions.
if(TS_SlopeFactor > Threshold) return 1;
if(TS_SlopeFactor < -Threshold) return -1;
return 0;
}
int TS_ShouldTakeSignal(int Direction)
{
if(Direction > 0 && NumOpenLong > 0) return 0;
if(Direction < 0 && NumOpenShort > 0) return 0;
// The source permits reversal immediately, bypassing the cooldown.
if(Direction > 0 && NumOpenShort > 0) return 1;
if(Direction < 0 && NumOpenLong > 0) return 1;
return Bar-TS_LastTradeBar >= InpMinBarsBetweenSignals;
}
int TS_ExecuteTrade(int Direction)
{
int OrderLots;
var StopDistance, ProfitDistance, Available, RiskPerLot, LotCount;
TRADE* Entered;
if(Direction > 0) {
if(NumOpenShort > 0) exitShort();
if(NumOpenShort > 0) return 0; // Do not hedge a pending/failed reversal.
if(NumOpenLong > 0) return 0;
} else {
if(NumOpenLong > 0) exitLong();
if(NumOpenLong > 0) return 0;
if(NumOpenShort > 0) return 0;
}
if(PIP <= 0. || LotAmount <= 0. || MarginCost <= 0.) return 0;
StopDistance = InpStopLossPips*PIP;
if(InpStopLossPips <= 0.)
StopDistance = max(TS_GetIntegratedVolatility()*3000.*PIP/10.,30.*PIP);
ProfitDistance = InpTakeProfitPips*PIP;
if(InpTakeProfitPips <= 0.) ProfitDistance = StopDistance*InpRiskRewardRatio;
LotCount = floor(InpLotSize*100000./LotAmount+1.e-9);
if(InpUseAdaptiveLots) {
if(PIPCost <= 0.) return 0;
// Use the ACTUAL stop (plus spread/commission), not a different proxy SL.
RiskPerLot = (StopDistance+Spread)/PIP*PIPCost+Commission*LotAmount/10000.;
if(RiskPerLot <= 0.) return 0;
LotCount = floor(max(0.,Balance)*InpRiskPercent/100./RiskPerLot);
}
Available = max(0.,Equity-MarginVal)*0.8;
LotCount = min(LotCount,floor(Available/MarginCost));
// Never round UP to the minimum lot if it breaches risk or margin caps.
if(LotCount < 1. || LotCount > 2000000000.) return 0;
OrderLots = (int)LotCount;
Stop = StopDistance;
TakeProfit = ProfitDistance;
Trail = 0.; // Exact pip-step trailing implemented in the TMF, not TrailStep.
TrailStep = 0.;
Margin = 0.;
Risk = 0.;
Lots = OrderLots;
if(Direction > 0) Entered = enterLong(TS_ManageTrade);
else Entered = enterShort(TS_ManageTrade);
if(!Entered) return 0;
// Market orders only. Do not treat an unfilled/rejected order as a fill.
if(!(Entered->flags & TR_OPEN)) return 0;
TS_LastTradeBar = Bar;
if(InpShowSignals) {
if(Direction > 0) plot("TS Buy",priceClose(0),MAIN|TRIANGLE,InpBuyColor);
else plot("TS Sell",priceClose(0),MAIN|TRIANGLE,InpSellColor);
}
return 1;
}
void TS_UpdateDisplay()
{
if(!InpShowPanel) return;
// HS is a covariance norm, not a volatility or basis-point quote.
plot("TS HS covariance norm",TS_GetIntegratedVolatility(),NEW,BLUE);
plot("TS factors",TS_GetNumberOfFactors(),NEW,BLACK);
plot("TS PC1 variance %",TS_Eigenvalues[0]*100.,NEW,BLUE);
plot("TS drawdown %",TS_CurrentDrawdown,NEW,RED);
}
// Deterministic in-engine tests; invoked with -d TS_SELFTEST, never orders.
// ===== Embedded TermStructureSelfTest.h =====
#ifndef TERM_STRUCTURE_SELF_TEST_H
#define TERM_STRUCTURE_SELF_TEST_H
int TS_TestFailures = 0;
int TS_TestCount = 0;
var TS_TestMatrix[9];
var TS_TestVectors[9];
var TS_TestValues[3];
var TS_TestPrices[100];
var TS_TestJumpIndices[100];
var TS_TestJumpSizes[100];
void TS_TestAssert(int Passed, string Description)
{
TS_TestCount++;
if(!Passed) {
TS_TestFailures++;
printf("\nFAIL: %s",Description);
file_append("Log/TermStructureSingleFileSelfTest.txt",strf("FAIL: %s\n",Description));
}
}
void TS_RunSelfTests()
{
int I, J, K, OK, Count;
var Residual, Left, Right, Dot, Expected, Cumulative;
TS_TestCount = 0;
TS_TestFailures = 0;
file_write("Log/TermStructureSingleFileSelfTest.txt","32-bit Zorro lite-C self-tests\n",0);
TS_TestAssert(sizeof(int) == 4,"32-bit integer");
TS_TestAssert(sizeof(void*) == 4,"32-bit host pointer");
for(I = 0; I < 9; I++) TS_TestMatrix[I] = 0.;
TS_TestMatrix[0] = 2.; TS_TestMatrix[1] = 1.;
TS_TestMatrix[3] = 1.; TS_TestMatrix[4] = 2.; TS_TestMatrix[8] = 0.5;
OK = TS_EigenDecomposition(TS_TestMatrix,3,TS_TestValues,TS_TestVectors);
TS_TestAssert(OK,"Jacobi converges");
TS_TestAssert(abs(TS_TestValues[0]-3.) < 1.e-10,"largest eigenvalue = 3");
TS_TestAssert(abs(TS_TestValues[1]-1.) < 1.e-10,"second eigenvalue = 1");
Residual = 0.;
for(K = 0; K < 3; K++) {
for(I = 0; I < 3; I++) {
Left = 0.;
for(J = 0; J < 3; J++) Left += TS_TestMatrix[I*3+J]*TS_TestVectors[J*3+K];
Right = TS_TestValues[K]*TS_TestVectors[I*3+K];
Residual = max(Residual,abs(Left-Right));
}
}
TS_TestAssert(Residual < 1.e-10,"A*v=lambda*v residual");
for(I = 0; I < 3; I++) {
for(J = 0; J < 3; J++) {
Dot = 0.;
for(K = 0; K < 3; K++) Dot += TS_TestVectors[K*3+I]*TS_TestVectors[K*3+J];
Expected = 0.;
if(I == J) Expected = 1.;
TS_TestAssert(abs(Dot-Expected) < 1.e-10,"eigenvector orthonormality");
}
}
// Small-scale covariance must not be accepted as already diagonal.
for(I = 0; I < 9; I++) TS_TestMatrix[I] *= 1.e-14;
TS_TestAssert(TS_EigenDecomposition(TS_TestMatrix,3,TS_TestValues,TS_TestVectors),
"Jacobi small-scale convergence");
TS_TestAssert(abs(TS_TestValues[0]/3.e-14-1.) < 1.e-10,"relative Jacobi tolerance");
TS_TestAssert(TS_Configure(),"default configuration");
TS_Lookback = 100;
TS_Regularize = 0;
for(I = 0; I < 100; I++) TS_TestPrices[I] = exp(0.001*I);
TS_TestAssert(TS_ComputeCovariations(TS_TestPrices,100),"spot proxy analysis");
file_append("Log/TermStructureSingleFileSelfTest.txt",
strf("MEASURED: trace=%.15g HS=%.15g C00=%.15g\n",
TS_RealizedTrace,TS_IntegratedVolatility,TS_Truncated[0]));
TS_TestAssert(TS_TimeSteps == 99 && TS_Pairs == 3,"sample dimensions");
// Amplification squared: 8,48,216. Trace = 272*0.001^2.
// lite-C numeric literals can enter arithmetic at float precision.
TS_TestAssert(abs(TS_RealizedTrace/0.000272-1.) < 1.e-6,"realized covariance normalization");
TS_TestAssert(abs(TS_Truncated[0]-0.000008) < 1.e-12,"truncated covariance normalization");
TS_TestAssert(TS_NumFactors == 1 && TS_Eigenvalues[0] > 0.999999,"rank-one spot proxy");
TS_TestAssert(abs(TS_IntegratedVolatility/0.000272-1.) < 1.e-6,"Hilbert-Schmidt norm");
Cumulative = 0.;
for(I = 0; I < 3; I++) {
Cumulative += TS_Eigenvalues[I];
TS_TestAssert(abs(TS_GetExplainedVariance(I+1)-Cumulative) < 1.e-12,
"all cumulative explained variances initialized");
}
TS_TestAssert(TS_DetectJumps(TS_TestJumpIndices,TS_TestJumpSizes,100) == 0,
"no jumps in constant small returns");
for(I = 50; I < 100; I++) TS_TestPrices[I] *= exp(0.1);
TS_TestAssert(TS_ComputeCovariations(TS_TestPrices,100),"jump sample analysis");
Count = TS_DetectJumps(TS_TestJumpIndices,TS_TestJumpSizes,100);
TS_TestAssert(Count == 1 && TS_TestJumpIndices[0] == 49.,"jump index and count");
TS_TestAssert(TS_KeptCount == 98,"jump removed from truncated estimate");
TS_TestAssert(TS_DetectJumps(TS_TestJumpIndices,TS_TestJumpSizes,0) == -1,
"jump output capacity guard");
TS_TestAssert(TS_GetEigenvalues(TS_TestValues,2) == 0,"eigenvalue capacity guard");
TS_TestPrices[99] = 0.;
TS_TestAssert(!TS_ComputeCovariations(TS_TestPrices,100) && !TS_Initialized,
"invalid prices clear stale results");
TS_TestAssert(!TS_ComputeCovariations(TS_TestPrices,99),"insufficient samples");
for(I = 0; I < 100; I++) TS_TestPrices[I] = 1.;
TS_Regularize = 1;
TS_TestAssert(TS_ComputeCovariations(TS_TestPrices,100),"flat-data regularization compatibility");
TS_TestAssert(TS_RealizedTrace == 0.,"flat-data guard trace");
TS_TestAssert(!TS_CheckTradingConditions(),"flat data cannot trade after regularization");
TS_ConfigMaturities[1] = 30;
TS_TestAssert(!TS_SetMaturities(TS_ConfigMaturities,4),"duplicate maturities rejected");
printf("\nTS SELFTEST: %d checks, %d failures",TS_TestCount,TS_TestFailures);
file_append("Log/TermStructureSingleFileSelfTest.txt",
strf("RESULT: %d checks, %d failures\n",TS_TestCount,TS_TestFailures));
}
#endif
// ===== End TermStructureSelfTest.h =====
function run()
{
#ifdef TS_SELFTEST
if(is(INITRUN)) {
TS_RunSelfTests();
quit("TS self-test complete; see Log/TermStructureSingleFileSelfTest.txt");
}
return;
#endif
BarPeriod = InpBarPeriod;
StartDate = InpStartDate;
EndDate = InpEndDate;
LookBack = InpLookbackBars+1;
Capital = InpInitialCapital;
Hedge = 0;
MaxLong = 1;
MaxShort = 1;
set(TICKS);
if(is(INITRUN)) {
if(!TS_Configure()) { quit("Invalid TS configuration"); return; }
printf("\nTermStructureCovariation: spot proxy; factor-sign signal is experimental.");
}
if(!asset(InpAsset)) return;
algo("TS");
if(is(EXITRUN)) {
printf("\nTS summary: signals=%d wins=%d losses=%d",
TS_TotalSignals,TS_WinningTrades,TS_LosingTrades);
return;
}
if(is(LOOKBACK)) return;
if(!TS_UpdateAnalysis()) return;
TS_CurrentDrawdown = TS_CalculateDrawdown();
TS_UpdateDisplay();
if(!TS_CheckTradingConditions()) return;
int Direction = TS_GenerateSignal();
if(Direction != 0 && TS_ShouldTakeSignal(Direction)) {
TS_TotalSignals++;
if(InpShowDiagnostics) TS_PrintDiagnostics();
TS_ExecuteTrade(Direction);
}
}
Last edited by TipmyPip; 1 hour ago.
|
|
|
|