// TGr06E_MomentumBias.cpp - Zorro64 Strategy DLL (C++)
// Strategy E: Momentum-Biased
// Adds momentum filter: only trades pairs with positive Ret1.
#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, momentum filter applied in scoring
static const double alpha = 2.0;
static const double beta = 1.5;
static const double gamma = 2.0;
static const double MOMENTUM_THRESHOLD = 0.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;
double momentum = 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);
momentum = feat[0].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 MomentumBiasStrategy {
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 mom = pairG[a].momentum;
double momFactor = (mom > MOMENTUM_THRESHOLD) ? 1.0 : 0.0;
double x = (alpha*Creg + gamma*CA - beta*Pcouple) * momFactor;
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[MomentumBias] 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 Mom=%.4f Score=%.4f",
k+1, ASSET_NAMES[a], pairG[a].compactness, pairG[a].momentum, score[a]);
}
}
}
};
static MomentumBiasStrategy* 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 MomentumBiasStrategy();
S->init();
}
}
if(is(EXITRUN))
{
if(S){
S->shutdown();
delete S;
S = 0;
}
return;
}
if(!S) return;
if(Bar < LookBack) return;
S->onBar();
}