VolSieve is a volatility-adjusted, multi-asset ranking engine designed to continuously scan a broad FX universe and surface the most “structurally attractive” pairs while automatically de-emphasizing those experiencing excessive turbulence. Rather than relying on a single indicator, the strategy builds a compact, information-rich fingerprint for every currency pair using a small set of price-derived features calculated on each bar. These features capture short- and medium-horizon returns, trend structure, volatility, momentum, mean-reversion pressure, and a simple regime persistence proxy. Each feature is stored in a rolling buffer so the strategy maintains a recent history of how the pair has behaved.

The core idea is graph-based: for every pair, VolSieve treats the nine feature streams as nodes in a weighted graph. It measures how strongly features co-move by computing correlations across a fixed window and converts correlation strength into “distance.” Highly related features become close neighbors; unrelated features are farther apart. The strategy then runs an all-pairs shortest-path calculation across this feature graph and summarizes its overall connectivity via a Wiener-style distance total. A more tightly connected graph (lower total distance) is interpreted as higher internal coherence—i.e., the pair’s recent behavior is more consistent across different “views” of the market (returns, trend, pressure, and so on). This becomes the pair’s compactness score.

On top of individual pair structure, the strategy also builds a meta-graph across the entire universe. Here, the nodes are the currency pairs themselves, and the edges are determined by correlations of a chosen feature stream across assets. This produces a coupling penalty that represents how entangled the market currently is: when many pairs behave similarly, diversification and signal uniqueness tend to degrade, so the strategy reduces its enthusiasm.

Finally, VolSieve applies a direct volatility adjustment. Each pair’s score is multiplied by a volatility factor that shrinks toward zero as volatility rises, preventing high-variance pairs from dominating purely because their signals look “strong.” The final score blends three components—market regime compactness, pair compactness, and market coupling—then compresses it through a sigmoid into a stable 0–1 ranking metric. At regular update intervals, the strategy sorts the universe and reports the top candidates, producing a clean, risk-aware shortlist suitable for downstream execution or position sizing logic.


Code
// TGr06D_VolAdjuster.cpp - Zorro64 Strategy DLL (C++)
// Strategy D: Volatility-Adjusted
// Adjusts position sizing based on per-pair volatility.

#include <zorro.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.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

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;

// Standard weights, volatility adjustment done in scoring
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 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));
}

class RollingBuffer {
public:
  int cap = 0;
  int n = 0;
  int 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: RollingBuffer");
    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*(size_t)n*sizeof(double));
    if(!d) quit("OOM: WeightedGraph");
    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)];
        double dji = d[pos(j,i)];
        W += 0.5*(dij + dji);
      }
    return W;
  }
};

class PairAspectGraph {
public:
  WeightedGraph G;
  RollingBuffer feat[FEAT_N];
  double compactness = 0.0;
  double volatility = 0.0;

  PairAspectGraph() {}

  void init(){
    G.init(FEAT_N);
    for(int k=0;k<FEAT_N;k++) feat[k].init(FEAT_WINDOW);
  }

  void shutdown(){
    for(int k=0;k<FEAT_N;k++) feat[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 rebuildIfReady(){
    if(feat[0].n < FEAT_WINDOW) { compactness = 0.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);
        G.d[G.pos(i,j)] = w;
        G.d[G.pos(j,i)] = w;
      }
    }
    G.allPairsShortest();
    double W = G.wienerUndirectedLike();
    compactness = 1.0/(1.0 + W);
    volatility = feat[3].get(0);
  }
};

class PairUniverseGraph {
public:
  WeightedGraph G;
  double couplingPenalty = 0;

  PairUniverseGraph(){}
  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 = corrToDist(c);
        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);
  }
};

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

class VolAdjusterStrategy {
public:
  PairAspectGraph pairG[N_ASSET_NAMES];
  PairUniverseGraph metaG;
  int 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];
    double c1 = C[1];
    double c12 = C[12];

    double ret1 = log(c0/c1);
    double retN = log(c0/c12);
    double ma3 = (C[0]+C[1]+C[2]) / 3.0;
    double 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]);
      double d = ri - m;
      s += d*d;
    }
    double vol = sqrt(s/20.0);

    double mom = retN;
    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 bollPos = (c0 - m20)/s20;
    double rangePress = tanh(0.5*bollPos);
    double flowProxy = fabs(ret1);

    int up=0;
    for(int i=0;i<20;i++){
      if(C[i] > C[i+1]) up++;
    }
    double pBull = (double)up/20.0;
    double markovProxy = 2.0*(pBull - 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, mom);
    pairG[a].pushFeature(5, rsiProxy);
    pairG[a].pushFeature(6, rangePress);
    pairG[a].pushFeature(7, flowProxy);
    pairG[a].pushFeature(8, markovProxy);
  }

  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 Pcouple = metaG.couplingPenalty;

    double score[N_ASSET_NAMES];
    int idx[N_ASSET_NAMES];
    for(int a=0;a<N_ASSET_NAMES;a++){
      double CA = pairG[a].compactness;
      double vol = pairG[a].volatility;
      double volFactor = clamp01(1.0 - vol * VOL_SCALE);
      double x = (alpha*Creg + gamma*CA - beta*Pcouple) * 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] Bar=%d Creg=%.4f Pcouple=%.4f", Bar, Creg, Pcouple);
      for(int k=0;k<TOP_K;k++){
        int a = idx[k];
        printf("\n  #%d %s  CA=%.4f Vol=%.4f Score=%.4f",
          k+1, ASSET_NAMES[a], pairG[a].compactness, pairG[a].volatility, score[a]);
      }
    }
  }
};

static VolAdjusterStrategy* 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 VolAdjusterStrategy();
      S->init();
    }
  }

  if(is(EXITRUN))
  {
    if(S){
      S->shutdown();
      delete S;
      S = 0;
    }
    return;
  }

  if(!S) return;
  if(Bar < LookBack) return;

  S->onBar();
}