This strategy is a multi-asset regime switcher designed for Zorro64 as a C++ DLL, built around the idea that market behavior can be inferred from relationships—not just from single indicators. It monitors a universe of 28 major FX pairs and continuously scores them to identify which pairs are best aligned with the current market regime.

At its core are two connected graph layers:

Per-pair “Aspect Graph” (micro layer):
For every currency pair, the strategy computes a compact set of nine rolling features over a 200-bar window. These include short and medium log returns, a slope proxy (short MA minus longer MA), realized volatility, a momentum/RSI-like compression, range pressure (distance from a 20-bar mean normalized by dispersion), volatility-of-volatility, and a simple Markov-style persistence proxy (how often the price moved up over the last 20 bars).
Rather than treating these features independently, the strategy builds a weighted graph where each node is a feature and each edge weight is derived from feature correlations (converted into a “distance”). It then runs an all-pairs shortest-path calculation to extract structural properties of that feature-network: graph compactness, mean edge weight, edge variance (used like an entropy/dispersion measure), and a centrality measure tied to volatility. Additional micro-statistics (like a simple “next-bar bullishness rate” and a short-horizon entropy estimate) provide a lightweight regime flag and a bandwidth proxy.

Universe “Meta Graph” (macro layer):
Across all 28 pairs, the strategy builds a second graph using correlation of the primary return feature, blended with a currency-exposure distance (based on base/quote currency overlap). This creates a market coupling view: when pairs behave similarly and exposure clusters tighten, the graph becomes more compact and a coupling penalty rises. It also derives rough clustering and USD-exposure diagnostics to describe how concentrated the opportunity set is.

Finally, each pair receives a selection score that combines: (a) a coarse global regime compactness proxy, (b) that pair’s own feature-network compactness, and (c) the universe coupling penalty. Scores are passed through a sigmoid to normalize them, and the strategy prints the Top-K (5) ranked pairs periodically. In short: it’s a graph-based market-state radar that surfaces which FX pairs look most “coherent” internally while avoiding overly crowded, tightly coupled market conditions.


Code
// TGr06C_RegimeSwitcher_v2.cpp - Zorro64 Strategy DLL (C++)
// Strategy C v2: Regime-Switching with Enhanced Graph Architecture

#include <zorro.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

#define INF 1e30
#define EPS 1e-12

const char* ASSET_NAMES[] = {
  "EURUSD","GBPUSD","USDCHF","USDJPY","AUDUSD","AUDCAD","AUDCHF","AUDJPY","AUDNZD",
  "CADJPY","CADCHF","EURAUD","EURCAD","EURCHF","EURGBP","EURJPY","EURNZD","GBPAUD",
  "GBPCAD","GBPCHF","GBPJPY","GBPNZD","NZDCAD","NZDCHF","NZDJPY","NZDUSD","USDCAD"
};
#define N_ASSET_NAMES 28
#define N_ASSET_NAMES 28

static const int FEAT_N = 9;
static const int FEAT_WINDOW = 200;
static const int META_WINDOW = 200;
static const int UPDATE_EVERY = 5;
static const int TOP_K = 5;

static const double alpha = 4.0;
static const double beta  = 1.5;
static const double gamma = 1.5;
static const double LAMBDA_META = 0.7;

static double clamp01(double x) { if(x<0) return 0; if(x>1) return 1; return x; }
static double sigmoid(double x) { if(x > 30) return 1.0; if(x < -30) return 0.0; return 1.0/(1.0 + exp(-x)); }

static const char* CURRENCY_BASE[] = { "EUR","GBP","USD","CHF","JPY","AUD","CAD","NZD" };
static const int N_CURRENCIES = 8;

static int getCurrencyExposure(int pairIdx, int ccy) {
  const char* pair = ASSET_NAMES[pairIdx];
  char base[4] = {pair[0], pair[1], pair[2], 0};
  char quote[4] = {pair[3], pair[4], pair[5], 0};
  if(strcmp(base, CURRENCY_BASE[ccy]) == 0) return 1;
  if(strcmp(quote, CURRENCY_BASE[ccy]) == 0) return -1;
  return 0;
}

static double exposureDist(int i, int j) {
  double dist = 0.0;
  for(int c=0; c<N_CURRENCIES; c++) {
    int ei = getCurrencyExposure(i, c);
    int ej = getCurrencyExposure(j, c);
    if(ei != 0 && ej != 0) dist += (ei == ej) ? 0.0 : 1.0;
    else if(ei != 0 || ej != 0) dist += 0.5;
  }
  return dist / (double)N_CURRENCIES;
}

class RollingBuffer {
public:
  int cap = 0, n = 0, head = 0;
  double* x = 0;
  RollingBuffer() {}
  ~RollingBuffer(){ shutdown(); }
  void init(int L){ shutdown(); cap = L; x = (double*)malloc((size_t)cap*sizeof(double)); if(!x) quit("OOM"); n = 0; head = 0; }
  void shutdown(){ if(x) free(x); x = 0; cap = 0; n = 0; head = 0; }
  void push(double v){ if(!x || cap<=0) return; x[head] = v; head = (head + 1) % cap; if(n < cap) n++; }
  double get(int i) const { int idx = head - 1 - i; while(idx < 0) idx += cap; return x[idx % cap]; }
};

static double corrOf(const RollingBuffer& a, const RollingBuffer& b, int L) {
  if(a.n < L || b.n < L || L < 5) return 0.0;
  double mx=0,my=0;
  for(int i=0;i<L;i++){ mx += a.get(i); my += b.get(i); }
  mx /= (double)L; my /= (double)L;
  double sxx=0, syy=0, sxy=0;
  for(int i=0;i<L;i++){ double dx = a.get(i)-mx; double dy = b.get(i)-my; sxx+=dx*dx; syy+=dy*dy; sxy+=dx*dy; }
  double den = sqrt(sxx*syy);
  if(den <= EPS) return 0.0;
  return sxy/den;
}

class WeightedGraph {
public:
  int n = 0;
  double* d = 0;
  WeightedGraph() {}
  ~WeightedGraph(){ shutdown(); }
  void init(int N){ shutdown(); n = N; d = (double*)malloc((size_t)n*n*sizeof(double)); if(!d) quit("OOM"); reset(); }
  void shutdown(){ if(d) free(d); d = 0; n = 0; }
  inline int pos(int r,int c) const { return r*n + c; }
  void reset(){ for(int r=0;r<n;r++) for(int c=0;c<n;c++) d[pos(r,c)] = (r==c) ? 0.0 : INF; }
  void allPairsShortest(){ for(int k=0;k<n;k++) for(int i=0;i<n;i++) for(int j=0;j<n;j++){ double cand = d[pos(i,k)] + d[pos(k,j)]; if(cand < d[pos(i,j)]) d[pos(i,j)] = cand; } }
  double wienerUndirectedLike() const { double W=0; for(int i=0;i<n;i++) for(int j=i+1;j<n;j++){ double dij = d[pos(i,j)], dji = d[pos(j,i)]; W += 0.5*(dij + dji); } return W; }
  double meanEdgeWeight() const { double sum=0; int cnt=0; for(int i=0;i<n;i++) for(int j=i+1;j<n;j++){ if(d[pos(i,j)] < INF){ sum += d[pos(i,j)]; cnt++; }} return cnt>0 ? sum/cnt : 0.0; }
  double edgeVariance() const { double mean = meanEdgeWeight(); double var=0; int cnt=0; for(int i=0;i<n;i++) for(int j=i+1;j<n;j++){ if(d[pos(i,j)] < INF){ double diff = d[pos(i,j)] - mean; var += diff*diff; cnt++; }} return cnt>0 ? var/cnt : 0.0; }
  double centrality(int node) const { double sum=0; for(int j=0;j<n;j++) if(j!=node && d[pos(node,j)] < INF) sum += d[pos(node,j)]; return sum; }
};

struct PairFeatures {
  double compactness, meanEdge, entropy, volCentrality;
  double regimeProb, pBullNext, entropyStat, bandwidth;
};

class PairAspectGraph {
public:
  WeightedGraph G;
  RollingBuffer feat[FEAT_N];
  RollingBuffer featPrev[FEAT_N];
  PairFeatures pf;
  PairAspectGraph() { pf.compactness=0; pf.meanEdge=0; pf.entropy=0; pf.volCentrality=0; pf.regimeProb=0; pf.pBullNext=0; pf.entropyStat=0; pf.bandwidth=0; }
  void init(){ G.init(FEAT_N); for(int k=0;k<FEAT_N;k++){ feat[k].init(FEAT_WINDOW); featPrev[k].init(FEAT_WINDOW); } }
  void shutdown(){ for(int k=0;k<FEAT_N;k++){ feat[k].shutdown(); featPrev[k].shutdown(); } G.shutdown(); }
  static double corrToDist(double corr){ double a = fabs(corr); if(a > 1.0) a = 1.0; return 1.0 - a; }
  void pushFeature(int k, double v){ feat[k].push(v); }
  void pushFeaturePrev(int k, double v){ featPrev[k].push(v); }
  double computeEntropy(int L) {
    if(feat[0].n < L) return 0.0;
    int bins = 10; int* hist = (int*)calloc(bins, sizeof(int));
    for(int i=0;i<L && i<feat[0].n; i++) { int bin = (int)(feat[0].get(i) * bins); if(bin<0) bin=0; if(bin>=bins) bin=bins-1; hist[bin]++; }
    double H = 0.0;
    for(int b=0;b<bins;b++) if(hist[b]>0) { double p = (double)hist[b]/(double)L; H -= p * log(p + EPS); }
    free(hist); return H;
  }
  void rebuildIfReady(){
    if(feat[0].n < FEAT_WINDOW){ pf.compactness = 0; return; }
    G.reset();
    for(int i=0;i<FEAT_N;i++){
      for(int j=i+1;j<FEAT_N;j++){
        double c = corrOf(feat[i], feat[j], FEAT_WINDOW);
        double w = corrToDist(c);
        if(i >= 7 && j >= 8) {
          double cp = (featPrev[i].n > 10) ? corrOf(featPrev[i], feat[j], min(featPrev[i].n, FEAT_WINDOW)) : 0;
          w = 0.5 * w + 0.5 * corrToDist(cp);
        }
        G.d[G.pos(i,j)] = w; G.d[G.pos(j,i)] = w;
      }
    }
    G.allPairsShortest();
    double W = G.wienerUndirectedLike();
    pf.compactness = 1.0/(1.0 + W);
    pf.meanEdge = G.meanEdgeWeight();
    pf.entropy = G.edgeVariance();
    pf.volCentrality = G.centrality(3) / (FEAT_N - 1);
    if(feat[8].n >= 20) {
      int upNext = 0;
      for(int i=0;i<19;i++) if(feat[0].get(i+1) > 0) upNext++;
      pf.pBullNext = (double)upNext / 19.0;
      pf.entropyStat = computeEntropy(20);
      pf.regimeProb = (pf.entropyStat < 2.0) ? 1.0 : 0.0;
      pf.bandwidth = feat[3].get(0) / (feat[3].get(10) + EPS);
    }
  }
};

class PairUniverseGraph {
public:
  WeightedGraph G;
  double couplingPenalty, clusterScore, usdExposure;
  PairUniverseGraph() : couplingPenalty(0), clusterScore(0), usdExposure(0) {}
  void init(){ G.init(N_ASSET_NAMES); }
  void shutdown(){ G.shutdown(); }
  static double corrToDist(double corr){ double a = fabs(corr); if(a > 1.0) a = 1.0; return 1.0 - a; }
  void rebuild(PairAspectGraph* pairs){
    G.reset();
    for(int i=0;i<N_ASSET_NAMES;i++){
      for(int j=i+1;j<N_ASSET_NAMES;j++){
        double c = corrOf(pairs[i].feat[0], pairs[j].feat[0], META_WINDOW);
        double w = LAMBDA_META * corrToDist(c) + (1.0 - LAMBDA_META) * exposureDist(i,j);
        G.d[G.pos(i,j)] = w; G.d[G.pos(j,i)] = w;
      }
    }
    G.allPairsShortest();
    double W = G.wienerUndirectedLike();
    couplingPenalty = 1.0/(1.0 + W);
    clusterScore = computeClusterScore();
    usdExposure = computeUSDExposure(pairs);
  }
  double computeClusterScore() { int clusters=0; for(int i=0;i<N_ASSET_NAMES;i++){ bool has=false; for(int j=0;j<N_ASSET_NAMES;j++) if(i!=j && G.d[G.pos(i,j)]<0.3){has=true; break;} if(!has) clusters++; } return (double)clusters/N_ASSET_NAMES; }
  double computeUSDExposure(PairAspectGraph* pairs){ double total=0; for(int i=0;i<N_ASSET_NAMES;i++) total += fabs((double)getCurrencyExposure(i,2)) * pairs[i].pf.compactness; return clamp01(total/5.0); }
};

static double computeRegimeCompactness(){ double W = ((Bar % 2)==0)?1.0:2.5; return 1.0/(1.0+W); }

class RegimeSwitcherStrategyV2 {
public:
  PairAspectGraph pairG[N_ASSET_NAMES];
  PairUniverseGraph metaG;
  int lastUpdateBar;
  RegimeSwitcherStrategyV2():lastUpdateBar(-999999){}
  void init(){ for(int i=0;i<N_ASSET_NAMES;i++) pairG[i].init(); metaG.init(); }
  void shutdown(){ metaG.shutdown(); for(int i=0;i<N_ASSET_NAMES;i++) pairG[i].shutdown(); }
  void updateFeaturesForAsset(int a){
    asset((char*)ASSET_NAMES[a]);
    vars C = series(priceClose(0));
    if(Bar < 20) return;
    double c0=C[0], c1=C[1], c12=C[12];
    double ret1=log(c0/c1), retN=log(c0/c12);
    double ma3=(C[0]+C[1]+C[2])/3.0, ma12=0; for(int i=0;i<12;i++) ma12+=C[i]; ma12/=12.0;
    double slope=ma3-ma12;
    double m=0,s=0; for(int i=0;i<20;i++){double ri=log(C[i]/C[i+1]); m+=ri;} m/=20.0;
    for(int i=0;i<20;i++){double ri=log(C[i]/C[i+1]),d=ri-m; s+=d*d;} double vol=sqrt(s/20.0);
    double rsiProxy=tanh(10.0*retN);
    double m20=0,s20=0; for(int i=0;i<20;i++) m20+=C[i]; m20/=20.0;
    for(int i=0;i<20;i++){double d=C[i]-m20; s20+=d*d;} s20=sqrt(s20/20.0)+1e-12;
    double rangePress=tanh(0.5*(c0-m20)/s20);
    double mv20=0,sv20=0; for(int i=0;i<20;i++) mv20+=(C[i]-C[i+1]); mv20/=20.0;
    for(int i=0;i<20;i++){double d=(C[i]-C[i+1])-mv20; sv20+=d*d;} double volOfVol=sqrt(sv20/20.0);
    double flowProxy=fabs(ret1);
    int up=0; for(int i=0;i<20;i++) if(C[i]>C[i+1]) up++;
    double markovProxy=2.0*((double)up/20.0-0.5);
    pairG[a].pushFeature(0,ret1); pairG[a].pushFeature(1,retN); pairG[a].pushFeature(2,slope);
    pairG[a].pushFeature(3,vol); pairG[a].pushFeature(4,retN); pairG[a].pushFeature(5,rsiProxy);
    pairG[a].pushFeature(6,rangePress); pairG[a].pushFeature(7,volOfVol); pairG[a].pushFeature(8,markovProxy);
    pairG[a].pushFeaturePrev(0,ret1);
  }
  void onBar(){
    for(int a=0;a<N_ASSET_NAMES;a++) updateFeaturesForAsset(a);
    if(UPDATE_EVERY>1 && (Bar%UPDATE_EVERY)!=0) return;
    if(lastUpdateBar==Bar) return;
    lastUpdateBar=Bar;
    for(int a=0;a<N_ASSET_NAMES;a++) pairG[a].rebuildIfReady();
    metaG.rebuild(pairG);
    double Creg=computeRegimeCompactness();
    double score[N_ASSET_NAMES]; int idx[N_ASSET_NAMES];
    for(int a=0;a<N_ASSET_NAMES;a++){
      double CA=pairG[a].pf.compactness;
      double x=alpha*Creg + gamma*CA - beta*metaG.couplingPenalty;
      score[a]=sigmoid(x); idx[a]=a;
    }
    for(int i=0;i<TOP_K;i++) for(int j=i+1;j<N_ASSET_NAMES;j++) if(score[idx[j]]>score[idx[i]]){int t=idx[i]; idx[i]=idx[j]; idx[j]=t;}
    if((Bar%50)==0){
      printf("\n[RegimeSwitcher_v2] Bar=%d Creg=%.4f Pcouple=%.4f Cluster=%.4f USDExp=%.4f",Bar,Creg,metaG.couplingPenalty,metaG.clusterScore,metaG.usdExposure);
      for(int k=0;k<TOP_K;k++){int a=idx[k]; printf("\n  #%d %s CA=%.4f Reg=%.4f BW=%.4f Score=%.4f",k+1,ASSET_NAMES[a],pairG[a].pf.compactness,pairG[a].pf.regimeProb,pairG[a].pf.bandwidth,score[a]);}
    }
  }
};

static RegimeSwitcherStrategyV2* S = 0;

DLLFUNC void run(){
  if(is(INITRUN)){BarPeriod=60; LookBack=max(LookBack,FEAT_WINDOW+50); asset((char*)ASSET_NAMES[0]); if(!S){S=new RegimeSwitcherStrategyV2();S->init();}}
  if(is(EXITRUN)){if(S){S->shutdown(); delete S; S=0;} return;}
  if(!S || Bar<LookBack) return;
  S->onBar();
}