""" Build a minimal ONNX ensemble model using raw ONNX ops (no sklearn needed). Creates a simple linear classifier + softmax as a demo. Replace this with a real trained model later (Python 3.11/3.12 + sklearn + skl2onnx). """ import json import numpy as np import onnx from onnx import helper, TensorProto, numpy_helper from pathlib import Path ROOT = Path(__file__).resolve().parent.parent MODELS_DIR = ROOT / "models" MODELS_DIR.mkdir(exist_ok=True) FEATURES = [ "returns_1", "returns_3", "returns_6", "sma_10", "sma_20", "sma_50", "macd", "macd_signal", "rsi_14", "atr_14", "atr_pct", "vol_ratio", "high_low_range", "dist_sma20", ] # Dummy weights for demo — replace with real trained weights rng = np.random.default_rng(123) W = rng.standard_normal((14, 3)).astype(np.float32) * 0.1 b = np.zeros(3, dtype=np.float32) # Normalize weights roughly W = W / np.maximum(np.abs(W).sum(axis=0, keepdims=True), 1e-6) scale = np.array([ 1000.0, 1000.0, 1000.0, 1.0, 1.0, 1.0, 10.0, 10.0, 1.0, 10.0, 1.0, 1.0, 1.0, 1.0, ], dtype=np.float32).reshape(1, 14) bias = np.zeros((1, 14), dtype=np.float32) def build_model(): # Input x = helper.make_tensor_value_info("float_input", TensorProto.FLOAT, [None, 14]) # Scale nodes: x_scaled = x * scale + bias scale_tensor = numpy_helper.from_array(scale, name="scale") bias_tensor = numpy_helper.from_array(bias, name="bias") mul_node = helper.make_node("Mul", ["float_input", "scale"], ["x_scaled"]) add_node = helper.make_node("Add", ["x_scaled", "bias"], ["x_scaled_centered"]) # Linear layer: logits = x_scaled @ W + b W_tensor = numpy_helper.from_array(W, name="W") b_tensor = numpy_helper.from_array(b, name="b") matmul_node = helper.make_node("MatMul", ["x_scaled_centered", "W"], ["logits"]) add_bias_node = helper.make_node("Add", ["logits", "b"], ["logits_biased"]) # Softmax softmax_node = helper.make_node("Softmax", ["logits_biased"], ["probs"], axis=1) # Output y = helper.make_tensor_value_info("probs", TensorProto.FLOAT, [None, 3]) graph = helper.make_graph( [mul_node, add_node, matmul_node, add_bias_node, softmax_node], "mad_turtle_ensemble", [x], [y], [scale_tensor, bias_tensor, W_tensor, b_tensor], ) model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 15)]) model.ir_version = 8 onnx.checker.check_model(model) out = MODELS_DIR / "xauusd_h1_ensemble.onnx" onnx.save(model, str(out)) print(f"Saved demo ONNX model -> {out}") def save_metadata(): meta = { "symbol": "XAUUSD", "timeframe": "H1", "features": FEATURES, "target_horizon": 3, "built_at": __import__('datetime').datetime.utcnow().isoformat(), "models": { "ensemble": { "features": FEATURES, "path": "models/xauusd_h1_ensemble.onnx", "note": "Demo model with random weights. Replace with real trained model.", } }, } out = MODELS_DIR / "metadata.json" with open(out, "w") as f: json.dump(meta, f, indent=2) print(f"Saved metadata -> {out}") if __name__ == "__main__": build_model() save_metadata()