KnotScope FX is a multi-asset ranking strategy designed to find currency pairs whose internal behavior is unusually “compact” — meaning their own feature set moves in a tightly coordinated way. Instead of predicting direction directly, it scores each pair by how structurally consistent its recent dynamics are, then reports the top candidates.

On every bar (set to 60 minutes), the strategy cycles through a fixed universe of 28 major FX pairs. For each pair it computes nine lightweight features from recent closes, including short and medium log-returns, a moving-average slope proxy, rolling volatility, momentum, an RSI-like proxy, a Bollinger-style position/range pressure measure, a simple flow proxy (absolute return), and a Markov-style persistence proxy (ratio of up closes). Each feature is pushed into a rolling window (200 samples), forming a small “behavioral fingerprint” for that pair.

Once enough history is accumulated, the strategy builds a feature graph per pair: every feature is a node, and edges are weighted by distance derived from correlation magnitude (high absolute correlation ? small distance). It then runs an all-pairs shortest path routine and computes a Wiener-like sum of path lengths. The result is converted into a compactness score: lower overall graph distance implies higher compactness. Intuitively, this favors pairs whose internal indicators agree with each other instead of drifting independently.

A second, much lighter layer computes a meta coupling penalty across pairs, based only on correlations of the first feature stream (the 1-bar return). This creates a “universe graph” that estimates how tightly the market is moving together. However, the strategy is intentionally compactness-dominant: the meta term is down-weighted so it only gently discourages selection when everything is moving as one.

Finally, a simple regime factor is blended in, and each pair gets a score passed through a sigmoid to keep values bounded and comparable. The strategy then ranks all pairs and prints the top 5 periodically, showing their compactness and final score. In short: KnotScope FX hunts for pairs with the cleanest, most internally coherent structure, while keeping cross-market influence minimal.

Code
// TGr06A_CompactDominant.cpp - Zorro64 Strategy DLL (C++)
// Strategy A: Compactness-Dominant
// Focuses purely on per-pair compactness with minimal meta-graph influence.

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

// Compactness-dominant weights: minimal coupling penalty
static const double alpha = 0.1;
static const double beta  = 0.2;
static const double gamma = 3.0;

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 CompactDominantStrategy {
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[CompactDominant] 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 CompactDominantStrategy* 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 CompactDominantStrategy();
      S->init();
    }
  }

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

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

  S->onBar();
}