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