NexusVol Navigator (a.k.a. VolAdjuster v2) is a multi-asset, volatility-aware ranking engine designed for Zorro64 as a C++ strategy DLL. Instead of producing direct trade entries, it continuously scores and selects the most “attractive” FX pairs from a predefined universe of 28 major and cross currency pairs. Its core idea is that market opportunities improve when (1) the current regime is orderly, (2) an individual pair’s internal feature relationships are coherent, and (3) the broader universe is not tightly coupled in a way that amplifies systemic risk.

The strategy builds two layers of graph structure. At the pair level, each asset maintains a rolling window of nine engineered features (short- and medium-horizon log returns, moving-average slope, realized volatility, oscillator-style proxies, range pressure, volatility-of-volatility, and a simple Markov-style “up probability” proxy). These features feed an “aspect graph” where nodes are features and edge weights are correlation-derived distances. By running an all-pairs shortest-path calculation, the graph becomes a compact representation of how tightly the features move together. The result is a compactness score (higher when the feature network is more cohesive), plus extra diagnostics such as mean edge weight, variance of edges (used as an entropy-like stability measure), volatility centrality, a regime probability proxy, and a simple bandwidth ratio (current volatility vs. a lagged reference).

At the universe level, a second graph links pairs to each other using a blend of (a) return correlation distances and (b) a currency-exposure distance that measures how similar two pairs are in terms of shared base/quote currency risk. This produces a “coupling penalty” (higher when everything moves together) and a rough clustering score.

Every few bars, the strategy combines: a lightweight regime compactness signal, each pair’s graph compactness, the universe coupling penalty, and a volatility dampener. The combined value is passed through a sigmoid to create a 0–1 score, then the top 5 pairs are ranked and periodically printed for monitoring. The practical intent is portfolio allocation and risk throttling: favor pairs with structured, consistent behavior while reducing exposure when the market becomes overly correlated or volatile.

Code
// TGr06D_VolAdjuster_v2.cpp - Zorro64 Strategy DLL (C++)
// Strategy D v2: VolAdjuster 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 = 2.0;
static const double beta  = 1.5;
static const double gamma = 2.0;
static const double VOL_SCALE = 0.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;
  double volatility;
};

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; pf.volatility=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);
    pf.volatility = feat[3].get(0);
    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 VolAdjusterStrategyV2 {
public:
  PairAspectGraph pairG[N_ASSET_NAMES];
  PairUniverseGraph metaG;
  int lastUpdateBar;
  VolAdjusterStrategyV2():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 vol=pairG[a].pf.volatility;
      double volFactor=clamp01(1.0 - vol * VOL_SCALE);
      double x=(alpha*Creg + gamma*CA - beta*metaG.couplingPenalty) * volFactor;
      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[VolAdjuster_v2] Bar=%d Creg=%.4f Pcouple=%.4f Cluster=%.4f",Bar,Creg,metaG.couplingPenalty,metaG.clusterScore);
      for(int k=0;k<TOP_K;k++){int a=idx[k]; printf("\n  #%d %s CA=%.4f Vol=%.4f BW=%.4f Score=%.4f",k+1,ASSET_NAMES[a],pairG[a].pf.compactness,pairG[a].pf.volatility,pairG[a].pf.bandwidth,score[a]);}
    }
  }
};

static VolAdjusterStrategyV2* 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 VolAdjusterStrategyV2();S->init();}}
  if(is(EXITRUN)){if(S){S->shutdown(); delete S; S=0;} return;}
  if(!S || Bar<LookBack) return;
  S->onBar();
}