105 lines
3.2 KiB
Python
105 lines
3.2 KiB
Python
"""
|
|
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()
|