Hypotheses
Quantiacs Stateful NN Low-Volatility S&P 500 Allocator — Faithful Port on USEQ Daily (numpy MLP, next-open fills, 23% TP / 5% SL / 217-day hold)
Faithful port of the Quantiacs open-source strategy 'Stateful-machine-learning-neural-network-strategy' (author: Quantiacs, MIT licence, commit ce91142). Credit to the author; the source's reported Sharpe 0.85…
Hypothesis
Faithful port of the Quantiacs open-source strategy 'Stateful-machine-learning-neural-network-strategy' (author: Quantiacs, MIT licence, commit ce91142). Credit to the author; the source's reported Sharpe 0.85 (2006-01-01..2024-10-17) is unverified and our own backtest with real costs decides. MARKET: US equities, venue USEQ, 1-DAY bars, long-only, no leverage (gross <= 1.0). UNIVERSE: the S&P 500 AS OF EACH DECISION DAY (point-in-time membership via index_member()/universe_at(), former members included) — this……Show moreShow less
Faithful port of the Quantiacs open-source strategy 'Stateful-machine-learning-neural-network-strategy' (author: Quantiacs, MIT licence, commit ce91142). Credit to the author; the source's reported Sharpe 0.85 (2006-01-01..2024-10-17) is unverified and our own backtest with real costs decides. MARKET: US equities, venue USEQ, 1-DAY bars, long-only, no leverage (gross <= 1.0). UNIVERSE: the S&P 500 AS OF EACH DECISION DAY (point-in-time membership via index_member()/universe_at(), former members included) — this replaces Quantiacs' 'is_liquid' field, which in the Quantiacs S&P 500 dataset flags current index membership. SPY.USEQ is the primary bar_type only as the session clock (never traded); every S&P 500 member that was ever in the index during the backtest span is subscribed as an extra instrument. MECHANISM: each day, from the index members as of the last bar, select the 15 assets with the LOWEST rolling standard deviation of daily close-to-close returns over min(450, available_bars-1) days (qnt.filter.filter_volatility metric='std', ascending=True; assets lacking a full window are not eligible). For those 15 assets (kept in alphabetical symbol order — the order Quantiacs' xarray selection yields, and the order that maps them onto the network's 15 outputs) compute 6 features per asset, flatten to a 90-vector, feed a fully connected network 90->128 ReLU->64 ReLU->15 Sigmoid, and read the 15 outputs as P(next close > today's close) for the 15 assets by position. MODEL: implemented in pure numpy (torch is outside the sandbox import allowlist; this is the only substitution in the model): PyTorch-default Linear init (weights and biases U(-1/sqrt(fan_in), +1/sqrt(fan_in))) from a local numpy Generator seeded 42 (never the global RNG), full-batch training for 100 epochs, binary cross-entropy (mean over all time x asset cells), Adam lr=0.002 (betas 0.9/0.999, eps 1e-8). Training is retrained every 90 calendar days (qnt backtest_ml retrain_interval=90) on the most recent 250 trading days (train_period=250); between retrains the frozen model predicts each day (predict_each_day=True). The training sample re-selects the 15 lowest-volatility members as of the last training day using the same selector with rolling window min(450, 249) = 249 returns. TARGET: 1 if next day's close > today's close else 0; the final training row has no next close and is labelled 0 (the source's xr.where on NaN). FEATURES (exact source formulas, in this order): f0 = (RSI7-50)*sqrt(7)/65; f1 = (RSI20-50)*sqrt(20)/65; f2 = candle = (close-open)/(close*2*max(ATR14,0.01)); f3 = (RSI60-50)*sqrt(60)/65; f4 = (RSI150-50)*sqrt(150)/65; f5 = ATR14/close. RSI and ATR are qnt.ta.rsi / qnt.ta.atr with their library-default smoothing (developer replicates the qnt implementation; ATR uses true range with previous close). Inputs are forward-filled then back-filled then NaN->1 (OHLC) / NaN->0 (indicators) within the window, as in the source. Flattening follows transpose('time','feature','asset').reshape(-1, 90): index = feature*15 + asset. PORTFOLIO STATE (the 'stateful' part): each day start from yesterday's target weights (positions carry over); for each of the 15 selected assets whose prediction > 0.7, set its target weight to prediction*0.1 (max 0.1); assets not re-predicted keep their previous weight; multiply by index membership (non-members go to 0). Exits zero a weight: max-hold 217 trading days since the position opened (qnte.max_hold_long), take-profit when close >= 1.23 x entry price, stop-loss when close <= 0.95 x entry price (qnte take_profit/stop_loss_long_percentage, close-based). If the sum of weights exceeds 1, scale all weights by 1/sum. A zeroed asset only re-enters when the model again predicts > 0.7. EXECUTION: Quantiacs applies weights decided on day t's close at day t+1's open; on USEQ that is the platform's supported next_open_fill (market order decided on the close fills at the next session's open). Entry price for TP/SL is the actual entry fill price (the next-session open), matching the source's 'open price' semantics. Target weights are fractions of current equity; held positions are rebalanced to target each day (Quantiacs fractions semantics), with an engineering band: only send a rebalance order when the share delta exceeds 10% of the target position or the target changes, to avoid micro-orders (whole shares, zero commission, spread/impact modelled). RISK CONTROL: route (b) — signal/time exits (217-day clock, close-based TP/SL) plus capped sizing (<= 10% of equity per name, gross <= 100%, unlevered); a 5% close-based stop on a <= 10% position risks ~0.5% of equity, more only on gaps. Expected per-trade arithmetic: low-vol large caps held up to 217 days with a 23%/5% asymmetric bracket; a typical round trip should capture well over 1% versus ~0.04% USEQ round-trip spread+impact cost.
Analysis
Stateful-machine-learning-neural-network-strategy
Backtest and paper results are hypothetical. Trading involves risk of loss.