Janus-12 Aegis is a dual-faced market engine: one face looks forward through learned inference, the other looks backward through disciplined risk memory. Built as a native Zorro C++ strategy with embedded LibTorch and optional OpenCL preprocessing, it transforms raw EUR/USD H4 market structure into a compact 12-dimensional state vector, then projects that state into opposing probabilities of expansion and contraction: long and short conviction. Its symbolic core is a gate between two regimes — intelligence and fallback. When the neural model is present, prediction flows through Torch; when absent, a weighted heuristic lattice preserves continuity, so the system never becomes blind.

The strategy operates as a layered filter hierarchy. First, it measures local momentum, trend strength, volatility, oscillator imbalance, Bollinger position, volume pressure, and higher-timeframe alignment. Then it imposes a second law: exposure is only permitted when conditional tail risk remains tolerable. CVaR acts as a survival boundary, not just a statistic, constraining size and suppressing trades when the loss landscape becomes too steep.

Its trade logic is symbolic of adaptive discipline: enter only when direction, regime, spread, time, and portfolio risk agree; manage positions through staged trailing, partial realization, and breakeven migration. The result is a hybrid sentinel architecture — part predictor, part guardian — designed not merely to forecast price motion, but to survive uncertainty while remaining responsive to changing market structure. In symbolic terms, it is a two-threshold, risk-bounded, regime-aware decision machine.

Code
// Janus-12Aegis.cpp
// Native Zorro C++ strategy derived from LibAegis
// Replaces the external MLBridge dependency with embedded LibTorch inference
// and optional OpenCL preprocessing.

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

#ifndef NOMINMAX
#define NOMINMAX
#endif

#include <torch/script.h>

#if defined(__has_include)
  #if __has_include(<torch/cuda.h>)
    #include <torch/cuda.h>
    #define KKIO_HAVE_TORCH_CUDA 1
  #else
    #define KKIO_HAVE_TORCH_CUDA 0
  #endif
#else
  #define KKIO_HAVE_TORCH_CUDA 0
#endif

#define CL_TARGET_OPENCL_VERSION 120
#include <CL/cl.h>

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <mutex>
#include <string>
#include <vector>

#define at zorro_at
#ifdef LOG
#undef LOG
#endif
#include <zorro.h>
#undef at

#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
#ifdef abs
#undef abs
#endif
#ifdef ref
#undef ref
#endif

#define MY_PI 3.14159265358979323846
#define HTF_BARS 6
#define MAX_CVAR_WINDOW 512
#define PARTIAL_DONE TradeInt[0]

static const int kFeatureCount = 12;
static const int kOutputCount = 2;

// -------------------------
// Parameters
// -------------------------

static int InpWindowSize = 50;
static int InpMinBars = 150;

static int InpUseRegimeFilter = 1;
static var InpTrend_ADX_Min = 18.;
static int InpTradeOnlyTrend = 1;
static int InpHTF_MA_Period = 50;
static var InpHTF_ADX_Min = 15.;
static int InpHTFNeutralAllow = 1;

static int InpW_Momentum = 90;
static int InpW_PriceRate = 75;
static int InpW_ADX = 65;
static int InpW_MACD = 60;
static int InpW_RSI = 55;
static int InpW_BB = 45;
static int InpW_Stoch = 35;
static int InpW_CCI = 30;
static int InpW_ATR = 25;
static int InpW_Volume = 20;

static var InpScoreThreshold = 0.60;
static var InpScoreHysteresis = 0.05;
static int InpMinFeatureAgree = 6;

static int InpRSI_Period = 14;
static int InpMACD_Fast = 12;
static int InpMACD_Slow = 26;
static int InpMACD_Signal = 9;
static int InpBB_Period = 20;
static var InpBB_Dev = 2.0;
static int InpATR_Period = 14;
static int InpADX_Period = 14;
static int InpSTO_K = 14;
static int InpSTO_D = 3;
static int InpCCI_Period = 20;
static int InpROC_Period = 10;
static int InpVolMA_Per = 20;

static int InpCVaR_Window = 100;
static var InpConfLevel = 0.95;
static var InpMaxCVaR_Pct = 3.0;
static var InpMaxRiskPct = 1.0;
static var InpMaxPortRisk = 5.0;

static var InpMaxLot = 1.00;
static int InpUseVolSize = 1;
static var InpVolTarget = 1.0;

static var InpSL_ATR_Mult = 2.0;
static var InpTP_ATR_Mult = 5.0;

static int InpUseTrailing = 1;
static var InpTrail_Zone1 = 1.2;
static var InpTrail_Zone2 = 0.8;
static var InpTrail_Zone3 = 0.5;
static int InpUseBreakeven = 1;
static var InpBE_ATR = 1.0;

static int InpUsePartial = 1;
static var InpPartial_ATR = 2.0;
static var InpPartialPct = 50.0;

static int InpSpreadFilter = 1;
static var InpMaxSpreadPips = 4.0;
static int InpUseTradingHours = 0;
static int InpStartHour = 7;
static int InpEndHour = 21;
static int InpNoFriday = 0;
static int InpFridayClose = 20;

static const char* InpModelPath = "Data\\Models\\eurusd_model.pt";
static int InpModelUseGPU = 1;
static int InpModelUseOpenCL = 1;

// -------------------------
// State
// -------------------------

static var Weights[10];
static var Feat[10];
static var RetBuf[MAX_CVAR_WINDOW];
static var ModelFeatures[kFeatureCount];
static var ModelOutputs[kOutputCount];

static var ScoreLong = 0;
static var ScoreShort = 0;
static var CurVaR = 0;
static var CurCVaR = 0;
static var CurVol = 0;

static int IsTrending = 0;
static int HtfRegime = 0;

static vars Closes, LogRets, Volumes, VolMAs;
static vars ATRs, ADXs, PlusDIs, MinusDIs, RSIs;
static vars MacdMains, MacdSignals, MacdHists;
static vars BBUppers, BBMiddles, BBLowers;
static vars StoKs, StoDs, CCIs;

static vars HtfOpens, HtfHighs, HtfLows, HtfCloses;
static vars HtfEMAs, HtfADXs, HtfPlusDIs, HtfMinusDIs;

// -------------------------
// Native model runtime
// -------------------------

class ModelRuntime
{
public:
    bool init(const char* modelPath, bool useGpu, bool useOpenCL)
    {
        shutdown();

        m_useGpu = useGpu;
        m_useOpenCL = useOpenCL;
        m_modelPath = modelPath ? modelPath : "";

        if(m_useOpenCL)
            initOpenCL();

        return loadTorchModel();
    }

    void shutdown()
    {
        std::lock_guard<std::mutex> guard(m_mutex);
        shutdownOpenCL();
        m_model = torch::jit::script::Module();
        m_modelReady = false;
        m_device = torch::kCPU;
        m_modelPath.clear();
        m_useGpu = false;
        m_useOpenCL = false;
    }

    bool isReady() const
    {
        return m_modelReady;
    }

    bool predict(const var* features, int numFeatures, var* outputs, int numOutputs)
    {
        if(!features || !outputs || numFeatures <= 0 || numOutputs < 2)
            return false;

        std::lock_guard<std::mutex> guard(m_mutex);

        std::vector<float> input((size_t)numFeatures);
        for(int i = 0; i < numFeatures; ++i)
            input[(size_t)i] = (float)features[i];

        if(m_openclReady)
            preprocessOpenCL(input.data(), numFeatures);

        if(m_modelReady && torchPredict(input.data(), numFeatures, outputs, numOutputs))
            return true;

        fallbackPredict(input.data(), numFeatures, outputs, numOutputs);
        return true;
    }

private:
    static double clampd(double x, double lo, double hi)
    {
        if(x < lo)
            return lo;
        if(x > hi)
            return hi;
        return x;
    }

    static double sigmoidScalar(double x)
    {
        if(x >= 0.0)
            return 1.0 / (1.0 + std::exp(-x));

        const double ex = std::exp(x);
        return ex / (1.0 + ex);
    }

    static void normalizePair(double& a, double& b)
    {
        bool alreadyProb = false;
        if(a >= 0.0 && a <= 1.0 && b >= 0.0 && b <= 1.0) {
            const double sum = a + b;
            if(std::fabs(sum - 1.0) < 0.10)
                alreadyProb = true;
        }

        if(!alreadyProb) {
            const double m = (a > b) ? a : b;
            const double ea = std::exp(a - m);
            const double eb = std::exp(b - m);
            const double s = ea + eb;
            if(s <= 0.0) {
                a = 0.5;
                b = 0.5;
                return;
            }
            a = ea / s;
            b = eb / s;
        }

        a = clampd(a, 0.0, 1.0);
        b = clampd(b, 0.0, 1.0);
        const double sum = a + b;
        if(sum > 0.0) {
            a /= sum;
            b /= sum;
        } else {
            a = 0.5;
            b = 0.5;
        }
    }

    bool loadTorchModel()
    {
        if(m_modelPath.empty())
            return false;

        chooseTorchDevice();

        try {
            m_model = torch::jit::load(m_modelPath, m_device);
            m_model.eval();
            m_modelReady = true;
            print(TO_LOG, "\n[KKio03T] Torch model loaded: %s", m_modelPath.c_str());
            return true;
        }
        catch(const std::exception& e) {
            print(TO_LOG, "\n[KKio03T] Torch model load failed: %s", e.what());
        }
        catch(...) {
            print(TO_LOG, "\n[KKio03T] Torch model load failed");
        }

        m_modelReady = false;
        m_device = torch::kCPU;
        return false;
    }

    void chooseTorchDevice()
    {
        m_device = torch::kCPU;
        if(!m_useGpu)
            return;

#if KKIO_HAVE_TORCH_CUDA
        try {
            if(torch::cuda::is_available())
                m_device = torch::Device(torch::kCUDA, 0);
        }
        catch(...) {
            m_device = torch::kCPU;
        }
#endif
    }

    bool torchPredict(const float* input, int numFeatures, var* outputs, int numOutputs)
    {
        try {
            torch::NoGradGuard noGrad;

            torch::Tensor x = torch::from_blob(
                (void*)input,
                {1, numFeatures},
                torch::TensorOptions().dtype(torch::kFloat32)
            ).clone();

            x = x.to(m_device);

            std::vector<torch::jit::IValue> inputs;
            inputs.push_back(x);

            torch::IValue outValue = m_model.forward(inputs);
            torch::Tensor y;

            if(outValue.isTensor()) {
                y = outValue.toTensor();
            } else if(outValue.isTuple()) {
                const auto elems = outValue.toTuple()->elements();
                if(elems.empty() || !elems[0].isTensor())
                    return false;
                y = elems[0].toTensor();
            } else {
                return false;
            }

            y = y.detach().to(torch::kCPU).to(torch::kDouble).contiguous().view(-1);
            if(y.numel() < 1)
                return false;

            double a = 0.5;
            double b = 0.5;

            if(y.numel() == 1) {
                a = sigmoidScalar(y[0].item<double>());
                b = 1.0 - a;
            } else {
                a = y[0].item<double>();
                b = y[1].item<double>();
                normalizePair(a, b);
            }

            if(numOutputs > 0)
                outputs[0] = (var)a;
            if(numOutputs > 1)
                outputs[1] = (var)b;
            return true;
        }
        catch(const std::exception& e) {
            print(TO_LOG, "\n[KKio03T] Torch forward failed: %s", e.what());
        }
        catch(...) {
            print(TO_LOG, "\n[KKio03T] Torch forward failed");
        }

        return false;
    }

    static void fallbackPredict(const float* features, int numFeatures, var* outputs, int numOutputs)
    {
        double f[12] = {};
        for(int i = 0; i < numFeatures && i < 12; ++i)
            f[i] = features[i];

        const double s0 = sigmoidScalar(f[0]);
        const double s1 = sigmoidScalar(f[1] * 0.50);
        const double s2 = clampd(0.50 + 0.50 * f[2], 0.0, 1.0);
        const double s3 = sigmoidScalar(f[3]);
        const double s4 = clampd(0.50 + 0.50 * f[4], 0.0, 1.0);
        const double s5 = clampd(0.50 + f[5], 0.0, 1.0);
        const double s6 = clampd(0.50 + 0.50 * f[6], 0.0, 1.0);
        const double s7 = sigmoidScalar(f[7]);
        const double s8 = clampd(f[8] / 2.0, 0.0, 1.0);
        const double s9 = clampd(f[9] / 2.0, 0.0, 1.0);
        const double s10 = clampd(0.50 + 0.40 * f[10], 0.0, 1.0);
        const double s11 = clampd(1.0 - f[11] / 5.0, 0.0, 1.0);

        double longScore =
              0.14 * s0
            + 0.10 * s1
            + 0.12 * s2
            + 0.10 * s3
            + 0.09 * s4
            + 0.08 * s5
            + 0.07 * s6
            + 0.07 * s7
            + 0.05 * s8
            + 0.05 * s9
            + 0.08 * s10
            + 0.05 * s11;

        double shortScore = 1.0 - longScore;
        normalizePair(longScore, shortScore);

        if(numOutputs > 0)
            outputs[0] = (var)longScore;
        if(numOutputs > 1)
            outputs[1] = (var)shortScore;
    }

    bool initOpenCL()
    {
        cl_int err = CL_SUCCESS;
        cl_uint numPlatforms = 0;
        cl_platform_id platforms[8] = {};

        err = clGetPlatformIDs(8, platforms, &numPlatforms);
        if(err != CL_SUCCESS || numPlatforms == 0)
            return false;

        for(cl_uint i = 0; i < numPlatforms; ++i) {
            cl_uint numDevices = 0;
            cl_device_id devices[16] = {};
            err = clGetDeviceIDs(platforms[i], CL_DEVICE_TYPE_GPU, 16, devices, &numDevices);
            if(err == CL_SUCCESS && numDevices > 0) {
                m_platform = platforms[i];
                m_deviceCL = devices[0];
                break;
            }
        }

        if(!m_deviceCL) {
            for(cl_uint i = 0; i < numPlatforms; ++i) {
                cl_uint numDevices = 0;
                cl_device_id devices[16] = {};
                err = clGetDeviceIDs(platforms[i], CL_DEVICE_TYPE_CPU, 16, devices, &numDevices);
                if(err == CL_SUCCESS && numDevices > 0) {
                    m_platform = platforms[i];
                    m_deviceCL = devices[0];
                    break;
                }
            }
        }

        if(!m_deviceCL)
            return false;

        m_contextCL = clCreateContext(0, 1, &m_deviceCL, 0, 0, &err);
        if(err != CL_SUCCESS || !m_contextCL) {
            shutdownOpenCL();
            return false;
        }

        m_queueCL = clCreateCommandQueue(m_contextCL, m_deviceCL, 0, &err);
        if(err != CL_SUCCESS || !m_queueCL) {
            shutdownOpenCL();
            return false;
        }

        static const char* kernelSource =
            "__kernel void kkio_preprocess(__global const float* in_buf,"
            "                              __global const float* min_buf,"
            "                              __global const float* max_buf,"
            "                              __global float* out_buf)"
            "{" 
            "  int i = get_global_id(0);"
            "  float x = in_buf[i];"
            "  float lo = min_buf[i];"
            "  float hi = max_buf[i];"
            "  if(x < lo) x = lo;"
            "  if(x > hi) x = hi;"
            "  out_buf[i] = x;"
            "}";

        const size_t sourceLen = std::strlen(kernelSource);
        m_programCL = clCreateProgramWithSource(m_contextCL, 1, &kernelSource, &sourceLen, &err);
        if(err != CL_SUCCESS || !m_programCL) {
            shutdownOpenCL();
            return false;
        }

        err = clBuildProgram(m_programCL, 1, &m_deviceCL, 0, 0, 0);
        if(err != CL_SUCCESS) {
            shutdownOpenCL();
            return false;
        }

        m_kernelCL = clCreateKernel(m_programCL, "kkio_preprocess", &err);
        if(err != CL_SUCCESS || !m_kernelCL) {
            shutdownOpenCL();
            return false;
        }

        const size_t bytes = sizeof(float) * (size_t)kFeatureCount;
        m_inputBufferCL = clCreateBuffer(m_contextCL, CL_MEM_READ_ONLY, bytes, 0, &err);
        if(err != CL_SUCCESS || !m_inputBufferCL) {
            shutdownOpenCL();
            return false;
        }

        m_outputBufferCL = clCreateBuffer(m_contextCL, CL_MEM_WRITE_ONLY, bytes, 0, &err);
        if(err != CL_SUCCESS || !m_outputBufferCL) {
            shutdownOpenCL();
            return false;
        }

        static const float kFeatureMin[kFeatureCount] = {
            -5.f, -10.f, -1.f, -5.f, -1.f, -2.f, -1.f, -2.f, 0.f, 0.f, -1.f, 0.f
        };
        static const float kFeatureMax[kFeatureCount] = {
            5.f, 10.f, 1.f, 5.f, 1.f, 2.f, 1.f, 2.f, 5.f, 5.f, 1.f, 5.f
        };

        m_minBufferCL = clCreateBuffer(
            m_contextCL,
            CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
            bytes,
            (void*)kFeatureMin,
            &err
        );
        if(err != CL_SUCCESS || !m_minBufferCL) {
            shutdownOpenCL();
            return false;
        }

        m_maxBufferCL = clCreateBuffer(
            m_contextCL,
            CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
            bytes,
            (void*)kFeatureMax,
            &err
        );
        if(err != CL_SUCCESS || !m_maxBufferCL) {
            shutdownOpenCL();
            return false;
        }

        m_openclReady = true;
        print(TO_LOG, "\n[KKio03T] OpenCL preprocessing ready");
        return true;
    }

    void preprocessOpenCL(float* values, int numFeatures)
    {
        if(!m_openclReady || numFeatures != kFeatureCount)
            return;

        cl_int err = CL_SUCCESS;
        const size_t bytes = sizeof(float) * (size_t)kFeatureCount;
        const size_t global = (size_t)kFeatureCount;

        err = clEnqueueWriteBuffer(m_queueCL, m_inputBufferCL, CL_TRUE, 0, bytes, values, 0, 0, 0);
        if(err != CL_SUCCESS)
            return;

        err  = clSetKernelArg(m_kernelCL, 0, sizeof(cl_mem), &m_inputBufferCL);
        err |= clSetKernelArg(m_kernelCL, 1, sizeof(cl_mem), &m_minBufferCL);
        err |= clSetKernelArg(m_kernelCL, 2, sizeof(cl_mem), &m_maxBufferCL);
        err |= clSetKernelArg(m_kernelCL, 3, sizeof(cl_mem), &m_outputBufferCL);
        if(err != CL_SUCCESS)
            return;

        err = clEnqueueNDRangeKernel(m_queueCL, m_kernelCL, 1, 0, &global, 0, 0, 0, 0);
        if(err != CL_SUCCESS)
            return;

        err = clEnqueueReadBuffer(m_queueCL, m_outputBufferCL, CL_TRUE, 0, bytes, values, 0, 0, 0);
        if(err != CL_SUCCESS)
            return;
    }

    void shutdownOpenCL()
    {
        if(m_inputBufferCL) {
            clReleaseMemObject(m_inputBufferCL);
            m_inputBufferCL = 0;
        }
        if(m_outputBufferCL) {
            clReleaseMemObject(m_outputBufferCL);
            m_outputBufferCL = 0;
        }
        if(m_minBufferCL) {
            clReleaseMemObject(m_minBufferCL);
            m_minBufferCL = 0;
        }
        if(m_maxBufferCL) {
            clReleaseMemObject(m_maxBufferCL);
            m_maxBufferCL = 0;
        }
        if(m_kernelCL) {
            clReleaseKernel(m_kernelCL);
            m_kernelCL = 0;
        }
        if(m_programCL) {
            clReleaseProgram(m_programCL);
            m_programCL = 0;
        }
        if(m_queueCL) {
            clReleaseCommandQueue(m_queueCL);
            m_queueCL = 0;
        }
        if(m_contextCL) {
            clReleaseContext(m_contextCL);
            m_contextCL = 0;
        }

        m_platform = 0;
        m_deviceCL = 0;
        m_openclReady = false;
    }

private:
    mutable std::mutex m_mutex;
    std::string m_modelPath;
    torch::Device m_device = torch::kCPU;
    torch::jit::script::Module m_model;
    bool m_modelReady = false;
    bool m_useGpu = false;
    bool m_useOpenCL = false;

    cl_platform_id m_platform = 0;
    cl_device_id m_deviceCL = 0;
    cl_context m_contextCL = 0;
    cl_command_queue m_queueCL = 0;
    cl_program m_programCL = 0;
    cl_kernel m_kernelCL = 0;
    cl_mem m_inputBufferCL = 0;
    cl_mem m_outputBufferCL = 0;
    cl_mem m_minBufferCL = 0;
    cl_mem m_maxBufferCL = 0;
    bool m_openclReady = false;
};

static ModelRuntime gModel;

// -------------------------
// Helpers
// -------------------------

static var sigmoid(var x)
{
    return 1. / (1. + exp(-x));
}

static void sortAsc(var* a, int n)
{
    for(int i = 0; i < n - 1; ++i) {
        for(int j = i + 1; j < n; ++j) {
            if(a[j] < a[i]) {
                const var t = a[i];
                a[i] = a[j];
                a[j] = t;
            }
        }
    }
}

static var priceSyncOpen(int period) { return sync(seriesO(), 1, period); }
static var priceSyncHigh(int period) { return sync(seriesH(), 2, period); }
static var priceSyncLow(int period) { return sync(seriesL(), 3, period); }
static var priceSyncClose(int period) { return sync(seriesC(), 4, period); }

static void initWeights()
{
    var sum = 0;
    sum += InpW_Momentum;
    sum += InpW_PriceRate;
    sum += InpW_ADX;
    sum += InpW_MACD;
    sum += InpW_RSI;
    sum += InpW_BB;
    sum += InpW_Stoch;
    sum += InpW_CCI;
    sum += InpW_ATR;
    sum += InpW_Volume;

    if(sum <= 0)
        sum = 1.;

    Weights[0] = InpW_Momentum / sum;
    Weights[1] = InpW_PriceRate / sum;
    Weights[2] = InpW_ADX / sum;
    Weights[3] = InpW_MACD / sum;
    Weights[4] = InpW_RSI / sum;
    Weights[5] = InpW_BB / sum;
    Weights[6] = InpW_Stoch / sum;
    Weights[7] = InpW_CCI / sum;
    Weights[8] = InpW_ATR / sum;
    Weights[9] = InpW_Volume / sum;
}

static void calcCVaR()
{
    const int n = std::min(InpCVaR_Window, MAX_CVAR_WINDOW);
    if(Bar < n + 2) {
        CurVaR = 0;
        CurCVaR = 0;
        CurVol = 0;
        return;
    }

    var mean = 0;
    for(int i = 0; i < n; ++i) {
        RetBuf[i] = LogRets[i];
        if(invalid(RetBuf[i]))
            RetBuf[i] = 0.;
        mean += RetBuf[i];
    }
    mean /= n;

    var vSum = 0;
    for(int i = 0; i < n; ++i)
        vSum += pow(RetBuf[i] - mean, 2);

    CurVol = sqrt(vSum / n);

    var z = 1.645;
    if(InpConfLevel >= 0.99)
        z = 2.326;
    else if(InpConfLevel >= 0.975)
        z = 1.960;

    CurVaR = -(mean - z * CurVol);

    const var phi = exp(-0.5 * z * z) / sqrt(2. * MY_PI);
    const var paramCVaR = -(mean - CurVol * phi / (1. - InpConfLevel));

    sortAsc(RetBuf, n);
    const int tail = std::max(1, (int)floor(n * (1. - InpConfLevel)));

    var histSum = 0;
    for(int i = 0; i < tail; ++i)
        histSum += RetBuf[i];

    const var histCVaR = -histSum / tail;
    CurCVaR = (paramCVaR + histCVaR) / 2.;
    if(CurCVaR < 0)
        CurCVaR = std::fabs((double)CurCVaR);
}

static void calcFeatureScores()
{
    const var pma = SMA(Closes, InpWindowSize);
    const var pstd = StdDev(Closes, InpWindowSize);
    Feat[0] = (pstd > 0) ? sigmoid((Closes[0] - pma) / pstd) : 0.5;

    const var roc = ROC(Closes, InpROC_Period);
    Feat[1] = sigmoid(roc * 2.5);

    const var adx = ADXs[0];
    const var diP = PlusDIs[0];
    const var diM = MinusDIs[0];
    var adxScore = 0.5;
    if(adx >= InpTrend_ADX_Min && (diP + diM) > 0)
        adxScore = 0.5 + (diP - diM) / (diP + diM) * 0.4 * std::min((double)(adx / 50.), 1.0);
    Feat[2] = clamp(adxScore, 0., 1.);

    const var mh = MacdHists[0];
    var macdScore = 0.50;
    if(crossOver(MacdHists, 0.))
        macdScore = 0.82;
    else if(crossUnder(MacdHists, 0.))
        macdScore = 0.18;
    else if(mh > 0 && mh > MacdHists[1])
        macdScore = 0.65;
    else if(mh < 0 && mh < MacdHists[1])
        macdScore = 0.35;
    else if(MacdMains[0] > 0)
        macdScore = 0.58;
    else if(MacdMains[0] < 0)
        macdScore = 0.42;
    Feat[3] = macdScore;

    const var rsi = RSIs[0];
    const var rsiPrev = RSIs[1];
    var rsiScore = 0.50;
    if(rsi < 25.)
        rsiScore = 0.82 + (25. - rsi) / 100.;
    else if(rsi < 40.)
        rsiScore = 0.65 + (40. - rsi) / 100.;
    else if(rsi < 50.)
        rsiScore = 0.52;
    else if(rsi < 60.)
        rsiScore = 0.48;
    else if(rsi < 75.)
        rsiScore = 0.35 - (rsi - 60.) / 100.;
    else
        rsiScore = 0.18 - (rsi - 75.) / 100.;

    if(rsi > rsiPrev && Closes[0] < Closes[1])
        rsiScore -= 0.05;
    if(rsi < rsiPrev && Closes[0] > Closes[1])
        rsiScore += 0.05;
    Feat[4] = clamp(rsiScore, 0., 1.);

    const var bw = BBUppers[0] - BBLowers[0];
    var bwMa = 0;
    for(int i = 0; i < 20; ++i)
        bwMa += BBUppers[i] - BBLowers[i];
    bwMa /= 20.;

    const var bPos = (bw > 0) ? (Closes[0] - BBLowers[0]) / bw : 0.5;
    var bbScore = 0.50;
    if(bPos < 0.15)
        bbScore = 0.80;
    else if(bPos < 0.30)
        bbScore = 0.65;
    else if(bPos < 0.45)
        bbScore = 0.55;
    else if(bPos < 0.55)
        bbScore = 0.50;
    else if(bPos < 0.70)
        bbScore = 0.45;
    else if(bPos < 0.85)
        bbScore = 0.35;
    else
        bbScore = 0.20;

    if(bw > bwMa * 1.2) {
        if(bPos > 0.5)
            bbScore = std::min((double)(bbScore + 0.06), 1.0);
        else
            bbScore = std::max((double)(bbScore - 0.06), 0.0);
    }
    Feat[5] = clamp(bbScore, 0., 1.);

    var stoScore = 0.50;
    if(StoKs[0] < 20 && StoDs[0] < 20 && crossOver(StoKs, StoDs))
        stoScore = 0.85;
    else if(StoKs[0] > 80 && StoDs[0] > 80 && crossUnder(StoKs, StoDs))
        stoScore = 0.15;
    else if(StoKs[0] < 25 && StoDs[0] < 25)
        stoScore = 0.68;
    else if(StoKs[0] > 75 && StoDs[0] > 75)
        stoScore = 0.32;
    else if(StoKs[0] > StoDs[0] && StoKs[0] > StoKs[1])
        stoScore = 0.60;
    else if(StoKs[0] < StoDs[0] && StoKs[0] < StoKs[1])
        stoScore = 0.40;
    Feat[6] = stoScore;

    const var cci = CCIs[0];
    const var cciPrev = CCIs[1];
    var cciScore = 0.50;
    if(cci < -150.)
        cciScore = 0.82;
    else if(cci < -75.)
        cciScore = 0.65;
    else if(cci < -25.)
        cciScore = 0.55;
    else if(cci < 25.)
        cciScore = 0.50;
    else if(cci < 75.)
        cciScore = 0.45;
    else if(cci < 150.)
        cciScore = 0.35;
    else
        cciScore = 0.18;

    if(cci > cciPrev && cci < 0)
        cciScore += 0.05;
    if(cci < cciPrev && cci > 0)
        cciScore -= 0.05;
    Feat[7] = clamp(cciScore, 0., 1.);

    const var atrMa = SMA(ATRs, InpWindowSize);
    const var atrRel = (atrMa > 0) ? ATRs[0] / atrMa : 1.0;
    var atrScore = 0.50;
    if(atrRel < 0.50)
        atrScore = 0.35;
    else if(atrRel < 0.80)
        atrScore = 0.45;
    else if(atrRel < 1.30)
        atrScore = 0.55;
    else if(atrRel < 2.00)
        atrScore = 0.50;
    else
        atrScore = 0.38;

    if(Closes[0] > pma)
        atrScore += 0.05;
    else
        atrScore -= 0.05;
    Feat[8] = clamp(atrScore, 0., 1.);

    const var volRel = (VolMAs[0] > 0) ? Volumes[0] / VolMAs[0] : 1.0;
    var volScore = 0.50;
    if(volRel > 2.0)
        volScore = ifelse(Closes[0] > Closes[1], 0.78, 0.22);
    else if(volRel > 1.4)
        volScore = ifelse(Closes[0] > Closes[1], 0.65, 0.35);
    else if(volRel > 1.0)
        volScore = ifelse(Closes[0] > Closes[1], 0.57, 0.43);
    Feat[9] = volScore;
}

static void buildModelFeatures()
{
    const var pma = SMA(Closes, InpWindowSize);
    const var pstd = StdDev(Closes, InpWindowSize);
    const var bw = BBUppers[0] - BBLowers[0];
    const var atrMa = SMA(ATRs, InpWindowSize);
    const var volRel = (VolMAs[0] > 0) ? Volumes[0] / VolMAs[0] : 1.0;
    const var rocScaled = ROC(Closes, InpROC_Period);

    var diScore = 0.;
    if((PlusDIs[0] + MinusDIs[0]) > 0)
        diScore = (PlusDIs[0] - MinusDIs[0]) / (PlusDIs[0] + MinusDIs[0]);

    const var rsiCentered = (RSIs[0] - 50.) / 50.;
    const var stochDelta = (StoKs[0] - StoDs[0]) / 100.;
    const var cciScaled = CCIs[0] / 200.;
    const var cvarScaled = (InpMaxCVaR_Pct > 0) ? (CurCVaR * 100.) / InpMaxCVaR_Pct : 0.;

    ModelFeatures[0] = (pstd > 0) ? clamp((Closes[0] - pma) / pstd, -5., 5.) : 0.;
    ModelFeatures[1] = clamp(rocScaled, -10., 10.);
    ModelFeatures[2] = clamp(diScore, -1., 1.);
    ModelFeatures[3] = clamp(MacdHists[0], -5., 5.);
    ModelFeatures[4] = clamp(rsiCentered, -1., 1.);
    ModelFeatures[5] = (bw > 0) ? clamp((Closes[0] - BBMiddles[0]) / bw, -2., 2.) : 0.;
    ModelFeatures[6] = clamp(stochDelta, -1., 1.);
    ModelFeatures[7] = clamp(cciScaled, -2., 2.);
    ModelFeatures[8] = (atrMa > 0) ? clamp(ATRs[0] / atrMa, 0., 5.) : 1.;
    ModelFeatures[9] = clamp(volRel, 0., 5.);
    ModelFeatures[10] = (var)HtfRegime;
    ModelFeatures[11] = clamp(cvarScaled, 0., 5.);
}

static int scoreWithModel()
{
    buildModelFeatures();
    ModelOutputs[0] = 0.;
    ModelOutputs[1] = 0.;
    if(!gModel.predict(ModelFeatures, kFeatureCount, ModelOutputs, kOutputCount))
        return 0;

    ScoreLong = clamp(ModelOutputs[0], 0., 1.);
    ScoreShort = clamp(ModelOutputs[1], 0., 1.);
    return gModel.isReady() ? 1 : 0;
}

static var calcPortfolioRiskPct()
{
    var riskPct = 0;
    for(open_trades)
        if(TradeStopLimit != 0)
            riskPct += std::fabs((double)(TradePriceOpen - TradeStopLimit)) * TradeUnits / std::max((double)Equity, 1.0) * 100.;
    return riskPct;
}

static var calcAmountLots(var stopDist)
{
    if(stopDist <= 0)
        return 0;

    var amountLots = 0;
    if(InpUseVolSize && CurVol > 0) {
        const var posValueStdLot = priceClose(0) * 100000.;
        if(posValueStdLot > 0)
            amountLots = (Equity * (InpVolTarget / 100.)) / (CurVol * posValueStdLot);
        if(CurCVaR > 0)
            amountLots *= std::min(1.0, (double)((InpMaxCVaR_Pct / 100.) / CurCVaR));
    } else {
        const var riskCash = Balance * InpMaxRiskPct / 100.;
        const var riskPerStdLot = stopDist * PIPCost / PIP * (100000. / std::max((double)LotAmount, 1.0));
        if(riskPerStdLot > 0)
            amountLots = riskCash / riskPerStdLot;
    }

    amountLots = std::max(0.0, (double)amountLots);
    amountLots = std::min((double)amountLots, (double)InpMaxLot);
    return amountLots;
}

DLLFUNC int LgbmManage()
{
    if(!TradeIsOpen)
        return 0;

    const var atr = ATRs[1];
    if(atr <= 0)
        return 0;

    const var curPx = priceC(0);
    const var profitMove = std::fabs((double)(curPx - TradePriceOpen));
    const var profitATR = profitMove / fix0(atr);

    if(InpUsePartial && !PARTIAL_DONE && profitATR >= InpPartial_ATR) {
        const int closeLots = (int)floor(TradeLots * InpPartialPct / 100.);
        if(closeLots >= 1 && closeLots < TradeLots)
            exitTrade(ThisTrade, 0, closeLots);
        PARTIAL_DONE = 1;
        return 16;
    }

    if(InpUseBreakeven && profitATR >= InpBE_ATR) {
        if(TradeIsLong)
            TradeStopLimit = std::max((double)TradeStopLimit, (double)TradePriceOpen);
        else
            TradeStopLimit = std::min((double)TradeStopLimit, (double)TradePriceOpen);
    }

    if(InpUseTrailing) {
        var trailDist = 0;
        if(profitATR >= 3.)
            trailDist = InpTrail_Zone3 * atr;
        else if(profitATR >= 2.)
            trailDist = InpTrail_Zone2 * atr;
        else if(profitATR >= 1.)
            trailDist = InpTrail_Zone1 * atr;

        if(trailDist > 0) {
            if(TradeIsLong)
                TradeStopLimit = std::max((double)TradeStopLimit, (double)(curPx - trailDist));
            else
                TradeStopLimit = std::min((double)TradeStopLimit, (double)(curPx + trailDist));
        }
    }

    return 0;
}

DLLFUNC int run()
{
    if(is(EXITRUN)) {
        gModel.shutdown();
        return 0;
    }

    if(is(INITRUN)) {
        BarPeriod = 240;
        LookBack = 500;
        MaxLong = 1;
        MaxShort = 1;
        FrameOffset = 0;
        set(TICKS);
        assetList("AssetsFix");
        initWeights();

        const bool modelReady = gModel.init(InpModelPath, InpModelUseGPU != 0, InpModelUseOpenCL != 0);
        print(TO_LOG, "\n[KKio03T] Native model ready: %s", modelReady ? "YES" : "NO (heuristic fallback active)");
        return 0;
    }

    asset("EUR/USD");
    algo("LGBM_CVaR_TorchCL");

    Closes = series(priceClose(0));
    LogRets = series(ROCL(Closes, 1));

    var rawVol = marketVol(0);
    if(rawVol <= 0)
        rawVol = 1.;
    Volumes = series(rawVol);
    VolMAs = series(SMAP(Volumes, InpVolMA_Per));

    const var rawSpread = marketVal(0);
    if(rawSpread > 0)
        Spread = rawSpread;

    ATRs = series(ATR(InpATR_Period));
    ADXs = series(ADX(InpADX_Period));
    PlusDIs = series(PlusDI(InpADX_Period));
    MinusDIs = series(MinusDI(InpADX_Period));
    RSIs = series(RSI(Closes, InpRSI_Period));

    MACD(Closes, InpMACD_Fast, InpMACD_Slow, InpMACD_Signal);
    MacdMains = series(rMACD);
    MacdSignals = series(rMACDSignal);
    MacdHists = series(rMACDHist);

    BBands(Closes, InpBB_Period, InpBB_Dev, InpBB_Dev, MAType_SMA);
    BBUppers = series(rRealUpperBand);
    BBMiddles = series(rRealMiddleBand);
    BBLowers = series(rRealLowerBand);

    StochF(InpSTO_K, InpSTO_D, MAType_SMA);
    StoKs = series(rFastK);
    StoDs = series(rFastD);
    CCIs = series(CCI(InpCCI_Period));

    HtfOpens = series(priceSyncOpen(HTF_BARS));
    HtfHighs = series(priceSyncHigh(HTF_BARS));
    HtfLows = series(priceSyncLow(HTF_BARS));
    HtfCloses = series(priceSyncClose(HTF_BARS));
    HtfEMAs = series(EMA(HtfCloses, InpHTF_MA_Period));
    HtfADXs = series(ADX(HtfOpens, HtfHighs, HtfLows, HtfCloses, InpADX_Period));
    HtfPlusDIs = series(PlusDI(HtfOpens, HtfHighs, HtfLows, HtfCloses, InpADX_Period));
    HtfMinusDIs = series(MinusDI(HtfOpens, HtfHighs, HtfLows, HtfCloses, InpADX_Period));

    if(Bar < std::max(InpMinBars, (int)(LookBack / 2)))
        return 0;

    IsTrending = 0;
    if(ADXs[0] >= InpTrend_ADX_Min && std::fabs((double)(PlusDIs[0] - MinusDIs[0])) > 4.)
        IsTrending = 1;

    HtfRegime = 0;
    if(HtfADXs[0] >= InpHTF_ADX_Min) {
        if(HtfCloses[0] > HtfEMAs[0] && HtfEMAs[0] > HtfEMAs[2] && HtfPlusDIs[0] > HtfMinusDIs[0])
            HtfRegime = 1;
        else if(HtfCloses[0] < HtfEMAs[0] && HtfEMAs[0] < HtfEMAs[2] && HtfMinusDIs[0] > HtfPlusDIs[0])
            HtfRegime = -1;
    }

    calcCVaR();

    int agreeLong = 0;
    int agreeShort = 0;
    const int modelUsed = scoreWithModel();
    if(!modelUsed) {
        calcFeatureScores();
        ScoreLong = 0;
        ScoreShort = 0;
        for(int i = 0; i < 10; ++i) {
            ScoreLong += Weights[i] * Feat[i];
            ScoreShort += Weights[i] * (1. - Feat[i]);
            if(Feat[i] > 0.55)
                agreeLong++;
            if(Feat[i] < 0.45)
                agreeShort++;
        }
    }

    int longOk = 0;
    int shortOk = 0;
    if(modelUsed) {
        if(ScoreLong >= InpScoreThreshold)
            longOk = 1;
        if(ScoreShort >= InpScoreThreshold)
            shortOk = 1;
    } else {
        if(ScoreLong >= InpScoreThreshold && agreeLong >= InpMinFeatureAgree)
            longOk = 1;
        if(ScoreShort >= InpScoreThreshold && agreeShort >= InpMinFeatureAgree)
            shortOk = 1;
    }

    if(InpUseRegimeFilter && InpTradeOnlyTrend && !IsTrending) {
        longOk = 0;
        shortOk = 0;
    }

    if(HtfRegime > 0 && shortOk)
        shortOk = 0;
    if(HtfRegime < 0 && longOk)
        longOk = 0;
    if(HtfRegime == 0 && !InpHTFNeutralAllow) {
        longOk = 0;
        shortOk = 0;
    }

    if(CurCVaR * 100. > InpMaxCVaR_Pct) {
        longOk = 0;
        shortOk = 0;
    }

    if(InpSpreadFilter) {
        const var spreadPips = Spread / PIP;
        if(spreadPips > InpMaxSpreadPips) {
            longOk = 0;
            shortOk = 0;
        }
    }

    if(InpUseTradingHours) {
        if(InpNoFriday && dow(0) == FRIDAY && hour(0) >= InpFridayClose) {
            exitLong();
            exitShort();
            longOk = 0;
            shortOk = 0;
        }
        if(hour(0) < InpStartHour || hour(0) >= InpEndHour) {
            longOk = 0;
            shortOk = 0;
        }
    }

    if(calcPortfolioRiskPct() + InpMaxRiskPct > InpMaxPortRisk) {
        longOk = 0;
        shortOk = 0;
    }

    for(open_trades) {
        if(TradeIsLong && ScoreShort > InpScoreThreshold + InpScoreHysteresis && HtfRegime < 0)
            exitTrade(ThisTrade);
        if(TradeIsShort && ScoreLong > InpScoreThreshold + InpScoreHysteresis && HtfRegime > 0)
            exitTrade(ThisTrade);
    }

    if(NumOpenLong == 0 && NumOpenShort == 0) {
        int direction = 0;
        if(longOk)
            direction = 1;
        else if(shortOk)
            direction = -1;

        if(direction != 0) {
            const var stopDist = InpSL_ATR_Mult * ATRs[0];
            Stop = stopDist;
            TakeProfit = InpTP_ATR_Mult * ATRs[0];
            Amount = calcAmountLots(stopDist);

            if(Amount > 0) {
                if(direction > 0)
                    enterLong(LgbmManage);
                else
                    enterShort(LgbmManage);
            }

            Amount = 0;
        }
    }

    plot("ScoreLong", ScoreLong, NEW, 0);
    plot("ScoreShort", ScoreShort, 0, 0);
    plot("CVaR%", CurCVaR * 100., NEW, 0);
    plot("HTFRegime", HtfRegime, NEW, 0);
    plot("ModelUsed", modelUsed, NEW, 0);
    return 0;
}


Last edited by TipmyPip; 03/28/26 14:01.