RegimeAtlas is a regime-switching portfolio selector designed for multi-asset FX trading, built as a Zorro64 strategy DLL in C++. Instead of relying on a single indicator or a fixed set of market rules, it continuously “reads the room” by measuring how internally consistent each asset’s behavior is, and how tightly the overall market universe is moving together. The core idea is simple: when markets shift between trending, choppy, volatile, and mean-reverting conditions, a strategy should adapt by favoring instruments whose recent structure best matches the current regime.

Every bar (configured here as 60 minutes), the strategy loops through a broad basket of 28 currency pairs and extracts a compact feature set that captures multiple market dimensions. These features include short- and medium-horizon log returns, a moving-average slope proxy, realized volatility, momentum, an RSI-like compression, Bollinger-style position/range pressure, a flow proxy based on absolute short return, and a simple Markov-style “bullish persistence” measure based on up/down counts. Each feature is stored in a rolling window so that the strategy can compare how the features co-move through time.

The distinguishing mechanism is graph-based. For each asset, the strategy builds a “feature graph” where edges represent similarity (via correlation) between feature streams, converted into distances. By running an all-pairs shortest-path calculation, it derives a compactness score: assets whose features form a more cohesive, mutually consistent structure receive higher compactness. In parallel, a “universe graph” evaluates cross-asset coupling using correlations (a proxy for crowding or systemic linkage). High coupling becomes a penalty, discouraging selection when the whole market is moving as one.

Finally, a regime score blends three forces with heavy emphasis on the global regime: global compactness (regime), asset compactness, and universe coupling penalty. A sigmoid squashes the result into a clean 0–1 ranking, and the strategy prints the top candidates periodically. In short, RegimeAtlas is an adaptive selector: it aims to surface the instruments most structurally aligned with the current market regime while de-emphasizing crowded, highly coupled conditions.

Code
// TGr06C_RegimeSwitcher.cpp - Zorro64 Strategy DLL (C++)
// Strategy C: Regime-Switching
// Heavily weights global regime for adaptive market behavior.

#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;

// Regime-switching weights: high alpha
static const double alpha = 4.0;
static const double beta  = 1.5;
static const double gamma = 1.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;

  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);
  }
};

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 RegimeSwitcherStrategy {
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 x = alpha*Creg + gamma*CA - beta*Pcouple;
      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] 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  Score=%.4f",
          k+1, ASSET_NAMES[a], pairG[a].compactness, score[a]);
      }
    }
  }
};

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

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

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

  S->onBar();
}