From 61a0d5b596f2ae787d22af416706eb17fe536087 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 6 Apr 2026 12:08:29 +0000 Subject: [PATCH] feat: expand quant templates to fifty projects --- README.md | 64 ++- templates/adausdt-volatility-rf-15m/README.md | 29 ++ .../quant.config.json | 22 + .../adausdt-volatility-rf-15m/strategy.py | 72 +++ templates/audjpy-randomforest-1h/README.md | 29 ++ .../audjpy-randomforest-1h/quant.config.json | 22 + templates/audjpy-randomforest-1h/strategy.py | 72 +++ templates/audusd-logistic-30m/README.md | 29 ++ .../audusd-logistic-30m/quant.config.json | 22 + templates/audusd-logistic-30m/strategy.py | 73 +++ .../avaxusdt-volatility-rf-30m/README.md | 29 ++ .../quant.config.json | 22 + .../avaxusdt-volatility-rf-30m/strategy.py | 72 +++ templates/bnbusdt-trend-rf-30m/README.md | 29 ++ .../bnbusdt-trend-rf-30m/quant.config.json | 22 + templates/bnbusdt-trend-rf-30m/strategy.py | 72 +++ templates/brentoil-trend-rf-1h/README.md | 29 ++ .../brentoil-trend-rf-1h/quant.config.json | 22 + templates/brentoil-trend-rf-1h/strategy.py | 72 +++ templates/btcusdt-breakout-rf-15m/README.md | 29 ++ .../btcusdt-breakout-rf-15m/quant.config.json | 22 + templates/btcusdt-breakout-rf-15m/strategy.py | 69 +++ .../btcusdt-lightgbm-1h/quant.config.json | 31 +- templates/btcusdt-mean-reversion-5m/README.md | 29 ++ .../quant.config.json | 21 + .../btcusdt-mean-reversion-5m/strategy.py | 46 ++ templates/cadjpy-breakout-1h/README.md | 29 ++ .../cadjpy-breakout-1h/quant.config.json | 22 + templates/cadjpy-breakout-1h/strategy.py | 69 +++ templates/chfjpy-mean-reversion-1h/README.md | 29 ++ .../quant.config.json | 21 + .../chfjpy-mean-reversion-1h/strategy.py | 46 ++ .../copperusd-mean-reversion-1h/README.md | 29 ++ .../quant.config.json | 21 + .../copperusd-mean-reversion-1h/strategy.py | 46 ++ .../dogeusdt-scalp-logistic-1m/README.md | 29 ++ .../quant.config.json | 22 + .../dogeusdt-scalp-logistic-1m/strategy.py | 73 +++ .../dotusdt-mean-reversion-30m/README.md | 29 ++ .../quant.config.json | 21 + .../dotusdt-mean-reversion-30m/strategy.py | 46 ++ .../quant.config.json | 29 +- .../eurgbp-range-classifier-30m/README.md | 29 ++ .../quant.config.json | 22 + .../eurgbp-range-classifier-30m/strategy.py | 73 +++ templates/eurjpy-trend-rf-1h/README.md | 29 ++ .../eurjpy-trend-rf-1h/quant.config.json | 22 + templates/eurjpy-trend-rf-1h/strategy.py | 72 +++ templates/eurnzd-volatility-rf-4h/README.md | 29 ++ .../eurnzd-volatility-rf-4h/quant.config.json | 22 + templates/eurnzd-volatility-rf-4h/strategy.py | 72 +++ .../eurusd-logistic-15m/quant.config.json | 19 +- templates/eurusd-xgboost-1h/quant.config.json | 31 +- templates/gbpjpy-breakout-rf-1h/README.md | 29 ++ .../gbpjpy-breakout-rf-1h/quant.config.json | 22 + templates/gbpjpy-breakout-rf-1h/strategy.py | 69 +++ .../gbpusd-breakout-rf/quant.config.json | 29 +- templates/gbpusd-logistic-15m/README.md | 29 ++ .../gbpusd-logistic-15m/quant.config.json | 22 + templates/gbpusd-logistic-15m/strategy.py | 73 +++ templates/ger40-breakout-rf-30m/README.md | 29 ++ .../ger40-breakout-rf-30m/quant.config.json | 22 + templates/ger40-breakout-rf-30m/strategy.py | 69 +++ .../quant.config.json | 31 +- .../linkusdt-trend-logistic-30m/README.md | 29 ++ .../quant.config.json | 22 + .../linkusdt-trend-logistic-30m/strategy.py | 73 +++ templates/ltcusdt-trend-rf-1h/README.md | 29 ++ .../ltcusdt-trend-rf-1h/quant.config.json | 22 + templates/ltcusdt-trend-rf-1h/strategy.py | 72 +++ .../maticusdt-scalp-baseline-5m/README.md | 29 ++ .../quant.config.json | 21 + .../maticusdt-scalp-baseline-5m/strategy.py | 53 ++ templates/nas100-breakout-rf-15m/README.md | 29 ++ .../nas100-breakout-rf-15m/quant.config.json | 22 + templates/nas100-breakout-rf-15m/strategy.py | 69 +++ .../naturalgas-volatility-rf-4h/README.md | 29 ++ .../quant.config.json | 22 + .../naturalgas-volatility-rf-4h/strategy.py | 72 +++ templates/nzdusd-mean-reversion-1h/README.md | 29 ++ .../quant.config.json | 21 + .../nzdusd-mean-reversion-1h/strategy.py | 46 ++ templates/oilwtico-atr-breakout-1h/README.md | 29 ++ .../quant.config.json | 21 + .../oilwtico-atr-breakout-1h/strategy.py | 53 ++ .../quant.config.json | 29 +- .../solusdt-scalp-baseline/quant.config.json | 15 +- templates/spx500-trend-logistic-1h/README.md | 29 ++ .../quant.config.json | 22 + .../spx500-trend-logistic-1h/strategy.py | 73 +++ .../quant.config.json | 27 +- templates/uk100-range-logistic-1h/README.md | 29 ++ .../uk100-range-logistic-1h/quant.config.json | 22 + templates/uk100-range-logistic-1h/strategy.py | 73 +++ templates/us30-mean-reversion-30m/README.md | 29 ++ .../us30-mean-reversion-30m/quant.config.json | 21 + templates/us30-mean-reversion-30m/strategy.py | 46 ++ templates/usdcad-trend-rf-1h/README.md | 29 ++ .../usdcad-trend-rf-1h/quant.config.json | 22 + templates/usdcad-trend-rf-1h/strategy.py | 72 +++ templates/usdchf-range-logistic-1h/README.md | 29 ++ .../quant.config.json | 22 + .../usdchf-range-logistic-1h/strategy.py | 73 +++ .../usdjpy-mean-reversion/quant.config.json | 24 +- templates/usdmxn-volatility-rf-4h/README.md | 29 ++ .../usdmxn-volatility-rf-4h/quant.config.json | 22 + templates/usdmxn-volatility-rf-4h/strategy.py | 72 +++ templates/xagusd-atr-breakout-1h/README.md | 29 ++ .../xagusd-atr-breakout-1h/quant.config.json | 21 + templates/xagusd-atr-breakout-1h/strategy.py | 53 ++ templates/xagusd-mean-reversion-30m/README.md | 29 ++ .../quant.config.json | 21 + .../xagusd-mean-reversion-30m/strategy.py | 46 ++ .../xauusd-atr-breakout/quant.config.json | 15 +- .../xauusd-london-breakout-15m/README.md | 29 ++ .../quant.config.json | 22 + .../xauusd-london-breakout-15m/strategy.py | 69 +++ .../quant.config.json | 21 +- templates/xauusd-scalp-logistic-5m/README.md | 29 ++ .../quant.config.json | 22 + .../xauusd-scalp-logistic-5m/strategy.py | 73 +++ templates/xptusd-trend-rf-1h/README.md | 29 ++ .../xptusd-trend-rf-1h/quant.config.json | 22 + templates/xptusd-trend-rf-1h/strategy.py | 72 +++ templates/xrpusdt-breakout-rf-15m/README.md | 29 ++ .../xrpusdt-breakout-rf-15m/quant.config.json | 22 + templates/xrpusdt-breakout-rf-15m/strategy.py | 69 +++ tools/generate_template_batch.py | 480 ++++++++++++++++++ 128 files changed, 5226 insertions(+), 32 deletions(-) create mode 100644 templates/adausdt-volatility-rf-15m/README.md create mode 100644 templates/adausdt-volatility-rf-15m/quant.config.json create mode 100644 templates/adausdt-volatility-rf-15m/strategy.py create mode 100644 templates/audjpy-randomforest-1h/README.md create mode 100644 templates/audjpy-randomforest-1h/quant.config.json create mode 100644 templates/audjpy-randomforest-1h/strategy.py create mode 100644 templates/audusd-logistic-30m/README.md create mode 100644 templates/audusd-logistic-30m/quant.config.json create mode 100644 templates/audusd-logistic-30m/strategy.py create mode 100644 templates/avaxusdt-volatility-rf-30m/README.md create mode 100644 templates/avaxusdt-volatility-rf-30m/quant.config.json create mode 100644 templates/avaxusdt-volatility-rf-30m/strategy.py create mode 100644 templates/bnbusdt-trend-rf-30m/README.md create mode 100644 templates/bnbusdt-trend-rf-30m/quant.config.json create mode 100644 templates/bnbusdt-trend-rf-30m/strategy.py create mode 100644 templates/brentoil-trend-rf-1h/README.md create mode 100644 templates/brentoil-trend-rf-1h/quant.config.json create mode 100644 templates/brentoil-trend-rf-1h/strategy.py create mode 100644 templates/btcusdt-breakout-rf-15m/README.md create mode 100644 templates/btcusdt-breakout-rf-15m/quant.config.json create mode 100644 templates/btcusdt-breakout-rf-15m/strategy.py create mode 100644 templates/btcusdt-mean-reversion-5m/README.md create mode 100644 templates/btcusdt-mean-reversion-5m/quant.config.json create mode 100644 templates/btcusdt-mean-reversion-5m/strategy.py create mode 100644 templates/cadjpy-breakout-1h/README.md create mode 100644 templates/cadjpy-breakout-1h/quant.config.json create mode 100644 templates/cadjpy-breakout-1h/strategy.py create mode 100644 templates/chfjpy-mean-reversion-1h/README.md create mode 100644 templates/chfjpy-mean-reversion-1h/quant.config.json create mode 100644 templates/chfjpy-mean-reversion-1h/strategy.py create mode 100644 templates/copperusd-mean-reversion-1h/README.md create mode 100644 templates/copperusd-mean-reversion-1h/quant.config.json create mode 100644 templates/copperusd-mean-reversion-1h/strategy.py create mode 100644 templates/dogeusdt-scalp-logistic-1m/README.md create mode 100644 templates/dogeusdt-scalp-logistic-1m/quant.config.json create mode 100644 templates/dogeusdt-scalp-logistic-1m/strategy.py create mode 100644 templates/dotusdt-mean-reversion-30m/README.md create mode 100644 templates/dotusdt-mean-reversion-30m/quant.config.json create mode 100644 templates/dotusdt-mean-reversion-30m/strategy.py create mode 100644 templates/eurgbp-range-classifier-30m/README.md create mode 100644 templates/eurgbp-range-classifier-30m/quant.config.json create mode 100644 templates/eurgbp-range-classifier-30m/strategy.py create mode 100644 templates/eurjpy-trend-rf-1h/README.md create mode 100644 templates/eurjpy-trend-rf-1h/quant.config.json create mode 100644 templates/eurjpy-trend-rf-1h/strategy.py create mode 100644 templates/eurnzd-volatility-rf-4h/README.md create mode 100644 templates/eurnzd-volatility-rf-4h/quant.config.json create mode 100644 templates/eurnzd-volatility-rf-4h/strategy.py create mode 100644 templates/gbpjpy-breakout-rf-1h/README.md create mode 100644 templates/gbpjpy-breakout-rf-1h/quant.config.json create mode 100644 templates/gbpjpy-breakout-rf-1h/strategy.py create mode 100644 templates/gbpusd-logistic-15m/README.md create mode 100644 templates/gbpusd-logistic-15m/quant.config.json create mode 100644 templates/gbpusd-logistic-15m/strategy.py create mode 100644 templates/ger40-breakout-rf-30m/README.md create mode 100644 templates/ger40-breakout-rf-30m/quant.config.json create mode 100644 templates/ger40-breakout-rf-30m/strategy.py create mode 100644 templates/linkusdt-trend-logistic-30m/README.md create mode 100644 templates/linkusdt-trend-logistic-30m/quant.config.json create mode 100644 templates/linkusdt-trend-logistic-30m/strategy.py create mode 100644 templates/ltcusdt-trend-rf-1h/README.md create mode 100644 templates/ltcusdt-trend-rf-1h/quant.config.json create mode 100644 templates/ltcusdt-trend-rf-1h/strategy.py create mode 100644 templates/maticusdt-scalp-baseline-5m/README.md create mode 100644 templates/maticusdt-scalp-baseline-5m/quant.config.json create mode 100644 templates/maticusdt-scalp-baseline-5m/strategy.py create mode 100644 templates/nas100-breakout-rf-15m/README.md create mode 100644 templates/nas100-breakout-rf-15m/quant.config.json create mode 100644 templates/nas100-breakout-rf-15m/strategy.py create mode 100644 templates/naturalgas-volatility-rf-4h/README.md create mode 100644 templates/naturalgas-volatility-rf-4h/quant.config.json create mode 100644 templates/naturalgas-volatility-rf-4h/strategy.py create mode 100644 templates/nzdusd-mean-reversion-1h/README.md create mode 100644 templates/nzdusd-mean-reversion-1h/quant.config.json create mode 100644 templates/nzdusd-mean-reversion-1h/strategy.py create mode 100644 templates/oilwtico-atr-breakout-1h/README.md create mode 100644 templates/oilwtico-atr-breakout-1h/quant.config.json create mode 100644 templates/oilwtico-atr-breakout-1h/strategy.py create mode 100644 templates/spx500-trend-logistic-1h/README.md create mode 100644 templates/spx500-trend-logistic-1h/quant.config.json create mode 100644 templates/spx500-trend-logistic-1h/strategy.py create mode 100644 templates/uk100-range-logistic-1h/README.md create mode 100644 templates/uk100-range-logistic-1h/quant.config.json create mode 100644 templates/uk100-range-logistic-1h/strategy.py create mode 100644 templates/us30-mean-reversion-30m/README.md create mode 100644 templates/us30-mean-reversion-30m/quant.config.json create mode 100644 templates/us30-mean-reversion-30m/strategy.py create mode 100644 templates/usdcad-trend-rf-1h/README.md create mode 100644 templates/usdcad-trend-rf-1h/quant.config.json create mode 100644 templates/usdcad-trend-rf-1h/strategy.py create mode 100644 templates/usdchf-range-logistic-1h/README.md create mode 100644 templates/usdchf-range-logistic-1h/quant.config.json create mode 100644 templates/usdchf-range-logistic-1h/strategy.py create mode 100644 templates/usdmxn-volatility-rf-4h/README.md create mode 100644 templates/usdmxn-volatility-rf-4h/quant.config.json create mode 100644 templates/usdmxn-volatility-rf-4h/strategy.py create mode 100644 templates/xagusd-atr-breakout-1h/README.md create mode 100644 templates/xagusd-atr-breakout-1h/quant.config.json create mode 100644 templates/xagusd-atr-breakout-1h/strategy.py create mode 100644 templates/xagusd-mean-reversion-30m/README.md create mode 100644 templates/xagusd-mean-reversion-30m/quant.config.json create mode 100644 templates/xagusd-mean-reversion-30m/strategy.py create mode 100644 templates/xauusd-london-breakout-15m/README.md create mode 100644 templates/xauusd-london-breakout-15m/quant.config.json create mode 100644 templates/xauusd-london-breakout-15m/strategy.py create mode 100644 templates/xauusd-scalp-logistic-5m/README.md create mode 100644 templates/xauusd-scalp-logistic-5m/quant.config.json create mode 100644 templates/xauusd-scalp-logistic-5m/strategy.py create mode 100644 templates/xptusd-trend-rf-1h/README.md create mode 100644 templates/xptusd-trend-rf-1h/quant.config.json create mode 100644 templates/xptusd-trend-rf-1h/strategy.py create mode 100644 templates/xrpusdt-breakout-rf-15m/README.md create mode 100644 templates/xrpusdt-breakout-rf-15m/quant.config.json create mode 100644 templates/xrpusdt-breakout-rf-15m/strategy.py create mode 100644 tools/generate_template_batch.py diff --git a/README.md b/README.md index ceb3624..39859f6 100644 --- a/README.md +++ b/README.md @@ -26,20 +26,60 @@ Every project includes: ## Templates +This repository currently contains 50 PyP Quant strategy templates. + | Template | Pair | Timeframe | Model family | Use case | | --- | --- | --- | --- | --- | -| `eurusd-logistic-15m` | EURUSD | 15m | sklearn | Baseline directional classifier | -| `eurusd-xgboost-1h` | EURUSD | 1h | XGBoost | Feature-rich trend classifier | -| `gbpusd-breakout-rf` | GBPUSD | 30m | sklearn RandomForest | Range breakout classifier | -| `usdjpy-mean-reversion` | USDJPY | 1h | sklearn | Mean reversion baseline | -| `xauusd-regime-xgboost-v11` | XAUUSD | 1h | XGBoost | Less restrictive Au-79-style gold model | -| `xauusd-atr-breakout` | XAUUSD | 15m | custom Python | ATR breakout rules | -| `btcusdt-lightgbm-1h` | BTCUSDT | 1h | LightGBM | Crypto trend classifier | -| `ethusdt-volatility-classifier` | ETHUSDT | 30m | sklearn | Volatility regime classifier | -| `solusdt-scalp-baseline` | SOLUSDT | 1m | custom Python | High-volatility scalp baseline | -| `onnx-export-sklearn-starter` | EURUSD | 1h | sklearn to ONNX | ONNX export starter | -| `statsmodels-arima-direction` | EURUSD | 1h | statsmodels | Statistical direction baseline | -| `lightgbm-fx-multifeature` | EURUSD | 30m | LightGBM | Multi-feature FX classifier | +| `adausdt-volatility-rf-15m` | ADAUSDT | 15m | sklearn RandomForest | sklearn RandomForest PyP Quant template for ADAUSDT 15m. | +| `audjpy-randomforest-1h` | AUDJPY | 1h | sklearn RandomForest | sklearn RandomForest PyP Quant template for AUDJPY 1h. | +| `audusd-logistic-30m` | AUDUSD | 30m | sklearn LogisticRegression | sklearn LogisticRegression PyP Quant template for AUDUSD 30m. | +| `avaxusdt-volatility-rf-30m` | AVAXUSDT | 30m | sklearn RandomForest | sklearn RandomForest PyP Quant template for AVAXUSDT 30m. | +| `bnbusdt-trend-rf-30m` | BNBUSDT | 30m | sklearn RandomForest | sklearn RandomForest PyP Quant template for BNBUSDT 30m. | +| `brentoil-trend-rf-1h` | BRENTUSD | 1h | sklearn RandomForest | sklearn RandomForest PyP Quant template for BRENTUSD 1h. | +| `btcusdt-breakout-rf-15m` | BTCUSDT | 15m | sklearn RandomForest | sklearn RandomForest PyP Quant template for BTCUSDT 15m. | +| `btcusdt-lightgbm-1h` | BTCUSDT | 1h | LightGBM | BTCUSDT LightGBM crypto trend classifier | +| `btcusdt-mean-reversion-5m` | BTCUSDT | 5m | custom Python | custom Python PyP Quant template for BTCUSDT 5m. | +| `cadjpy-breakout-1h` | CADJPY | 1h | sklearn RandomForest | sklearn RandomForest PyP Quant template for CADJPY 1h. | +| `chfjpy-mean-reversion-1h` | CHFJPY | 1h | custom Python | custom Python PyP Quant template for CHFJPY 1h. | +| `copperusd-mean-reversion-1h` | COPPERUSD | 1h | custom Python | custom Python PyP Quant template for COPPERUSD 1h. | +| `dogeusdt-scalp-logistic-1m` | DOGEUSDT | 1m | sklearn LogisticRegression | sklearn LogisticRegression PyP Quant template for DOGEUSDT 1m. | +| `dotusdt-mean-reversion-30m` | DOTUSDT | 30m | custom Python | custom Python PyP Quant template for DOTUSDT 30m. | +| `ethusdt-volatility-classifier` | ETHUSDT | 30m | sklearn RandomForest | ETHUSDT volatility-aware directional classifier | +| `eurgbp-range-classifier-30m` | EURGBP | 30m | sklearn LogisticRegression | sklearn LogisticRegression PyP Quant template for EURGBP 30m. | +| `eurjpy-trend-rf-1h` | EURJPY | 1h | sklearn RandomForest | sklearn RandomForest PyP Quant template for EURJPY 1h. | +| `eurnzd-volatility-rf-4h` | EURNZD | 4h | sklearn RandomForest | sklearn RandomForest PyP Quant template for EURNZD 4h. | +| `eurusd-logistic-15m` | EURUSD | 15m | sklearn LogisticRegression | EURUSD logistic regression baseline classifier | +| `eurusd-xgboost-1h` | EURUSD | 1h | XGBoost | EURUSD feature-rich XGBoost trend classifier | +| `gbpjpy-breakout-rf-1h` | GBPJPY | 1h | sklearn RandomForest | sklearn RandomForest PyP Quant template for GBPJPY 1h. | +| `gbpusd-breakout-rf` | GBPUSD | 30m | sklearn RandomForest | GBPUSD range breakout random forest classifier | +| `gbpusd-logistic-15m` | GBPUSD | 15m | sklearn LogisticRegression | sklearn LogisticRegression PyP Quant template for GBPUSD 15m. | +| `ger40-breakout-rf-30m` | GER40 | 30m | sklearn RandomForest | sklearn RandomForest PyP Quant template for GER40 30m. | +| `lightgbm-fx-multifeature` | EURUSD | 30m | LightGBM | Multi-feature FX LightGBM classifier | +| `linkusdt-trend-logistic-30m` | LINKUSDT | 30m | sklearn LogisticRegression | sklearn LogisticRegression PyP Quant template for LINKUSDT 30m. | +| `ltcusdt-trend-rf-1h` | LTCUSDT | 1h | sklearn RandomForest | sklearn RandomForest PyP Quant template for LTCUSDT 1h. | +| `maticusdt-scalp-baseline-5m` | MATICUSDT | 5m | custom Python | custom Python PyP Quant template for MATICUSDT 5m. | +| `nas100-breakout-rf-15m` | NAS100 | 15m | sklearn RandomForest | sklearn RandomForest PyP Quant template for NAS100 15m. | +| `naturalgas-volatility-rf-4h` | NATGAS | 4h | sklearn RandomForest | sklearn RandomForest PyP Quant template for NATGAS 4h. | +| `nzdusd-mean-reversion-1h` | NZDUSD | 1h | custom Python | custom Python PyP Quant template for NZDUSD 1h. | +| `oilwtico-atr-breakout-1h` | WTICOUSD | 1h | custom Python | custom Python PyP Quant template for WTICOUSD 1h. | +| `onnx-export-sklearn-starter` | EURUSD | 1h | sklearn to ONNX | Sklearn classifier starter for ONNX export | +| `solusdt-scalp-baseline` | SOLUSDT | 1m | custom Python | SOLUSDT high-volatility scalp baseline | +| `spx500-trend-logistic-1h` | SPX500 | 1h | sklearn LogisticRegression | sklearn LogisticRegression PyP Quant template for SPX500 1h. | +| `statsmodels-arima-direction` | EURUSD | 1h | statsmodels | ARIMA-style statistical direction baseline | +| `uk100-range-logistic-1h` | UK100 | 1h | sklearn LogisticRegression | sklearn LogisticRegression PyP Quant template for UK100 1h. | +| `us30-mean-reversion-30m` | US30 | 30m | custom Python | custom Python PyP Quant template for US30 30m. | +| `usdcad-trend-rf-1h` | USDCAD | 1h | sklearn RandomForest | sklearn RandomForest PyP Quant template for USDCAD 1h. | +| `usdchf-range-logistic-1h` | USDCHF | 1h | sklearn LogisticRegression | sklearn LogisticRegression PyP Quant template for USDCHF 1h. | +| `usdjpy-mean-reversion` | USDJPY | 1h | custom Python | USDJPY mean-reversion baseline | +| `usdmxn-volatility-rf-4h` | USDMXN | 4h | sklearn RandomForest | sklearn RandomForest PyP Quant template for USDMXN 4h. | +| `xagusd-atr-breakout-1h` | XAGUSD | 1h | custom Python | custom Python PyP Quant template for XAGUSD 1h. | +| `xagusd-mean-reversion-30m` | XAGUSD | 30m | custom Python | custom Python PyP Quant template for XAGUSD 30m. | +| `xauusd-atr-breakout` | XAUUSD | 15m | custom Python | XAUUSD ATR breakout rule baseline | +| `xauusd-london-breakout-15m` | XAUUSD | 15m | custom Python | custom Python PyP Quant template for XAUUSD 15m. | +| `xauusd-regime-xgboost-v11` | XAUUSD | 1h | XGBoost | Less restrictive Au-79-style XAUUSD regime classifier | +| `xauusd-scalp-logistic-5m` | XAUUSD | 5m | sklearn LogisticRegression | sklearn LogisticRegression PyP Quant template for XAUUSD 5m. | +| `xptusd-trend-rf-1h` | XPTUSD | 1h | sklearn RandomForest | sklearn RandomForest PyP Quant template for XPTUSD 1h. | +| `xrpusdt-breakout-rf-15m` | XRPUSDT | 15m | sklearn RandomForest | sklearn RandomForest PyP Quant template for XRPUSDT 15m. | ## Use With PyP diff --git a/templates/adausdt-volatility-rf-15m/README.md b/templates/adausdt-volatility-rf-15m/README.md new file mode 100644 index 0000000..fb49d42 --- /dev/null +++ b/templates/adausdt-volatility-rf-15m/README.md @@ -0,0 +1,29 @@ +# adausdt-volatility-rf-15m + +ADA volatility classifier for PyP Quant Mode. + +This is an educational starter template for `ADAUSDT` on the `15m` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `ADAUSDT` +- Timeframe: `15m` +- Family: `sklearn RandomForest` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/adausdt-volatility-rf-15m/quant.config.json b/templates/adausdt-volatility-rf-15m/quant.config.json new file mode 100644 index 0000000..e609f2f --- /dev/null +++ b/templates/adausdt-volatility-rf-15m/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "adausdt-volatility-rf-15m", + "description": "sklearn RandomForest PyP Quant template for ADAUSDT 15m.", + "symbol": "ADAUSDT", + "timeframe": "15m", + "model_family": "sklearn RandomForest", + "artifact_target": "joblib", + "parameters": { + "lookback": 100, + "horizon": 4, + "threshold": 0.004, + "min_confidence": 0.5, + "sl_percent": 0.4, + "tp_percent": 0.8 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/adausdt-volatility-rf-15m/strategy.py b/templates/adausdt-volatility-rf-15m/strategy.py new file mode 100644 index 0000000..c9f250c --- /dev/null +++ b/templates/adausdt-volatility-rf-15m/strategy.py @@ -0,0 +1,72 @@ +import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + + +SYMBOL = "ADAUSDT" +MODEL_NAME = "adausdt-volatility-rf-15m" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + v = df["volume"] + f = pd.DataFrame(index=df.index) + for n in [1, 4, 8, 16, 32, 64]: + f[f"ret{n}"] = c.pct_change(n) + f["ema_12_48"] = (c.ewm(span=12, adjust=False).mean() - c.ewm(span=48, adjust=False).mean()) / c + f["ema_24_96"] = (c.ewm(span=24, adjust=False).mean() - c.ewm(span=96, adjust=False).mean()) / c + f["volatility_fast"] = c.pct_change().rolling(16).std() + f["volatility_slow"] = c.pct_change().rolling(64).std() + f["volatility_ratio"] = f["volatility_fast"] / (f["volatility_slow"] + 1e-9) + f["volume_z"] = (v - v.rolling(48).mean()) / (v.rolling(48).std() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 6)) + threshold = float(p.get("threshold", 0.004)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", RandomForestClassifier(n_estimators=350, max_depth=8, min_samples_leaf=8, class_weight="balanced_subsample", random_state=42, n_jobs=-1)), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 100)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/audjpy-randomforest-1h/README.md b/templates/audjpy-randomforest-1h/README.md new file mode 100644 index 0000000..80c30f9 --- /dev/null +++ b/templates/audjpy-randomforest-1h/README.md @@ -0,0 +1,29 @@ +# audjpy-randomforest-1h + +AUDJPY carry-risk classifier for PyP Quant Mode. + +This is an educational starter template for `AUDJPY` on the `1h` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `AUDJPY` +- Timeframe: `1h` +- Family: `sklearn RandomForest` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/audjpy-randomforest-1h/quant.config.json b/templates/audjpy-randomforest-1h/quant.config.json new file mode 100644 index 0000000..b8d5d29 --- /dev/null +++ b/templates/audjpy-randomforest-1h/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "audjpy-randomforest-1h", + "description": "sklearn RandomForest PyP Quant template for AUDJPY 1h.", + "symbol": "AUDJPY", + "timeframe": "1h", + "model_family": "sklearn RandomForest", + "artifact_target": "joblib", + "parameters": { + "lookback": 140, + "horizon": 6, + "threshold": 0.0018, + "min_confidence": 0.5, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/audjpy-randomforest-1h/strategy.py b/templates/audjpy-randomforest-1h/strategy.py new file mode 100644 index 0000000..8e42381 --- /dev/null +++ b/templates/audjpy-randomforest-1h/strategy.py @@ -0,0 +1,72 @@ +import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + + +SYMBOL = "AUDJPY" +MODEL_NAME = "audjpy-randomforest-1h" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + v = df["volume"] + f = pd.DataFrame(index=df.index) + for n in [1, 4, 8, 16, 32, 64]: + f[f"ret{n}"] = c.pct_change(n) + f["ema_12_48"] = (c.ewm(span=12, adjust=False).mean() - c.ewm(span=48, adjust=False).mean()) / c + f["ema_24_96"] = (c.ewm(span=24, adjust=False).mean() - c.ewm(span=96, adjust=False).mean()) / c + f["volatility_fast"] = c.pct_change().rolling(16).std() + f["volatility_slow"] = c.pct_change().rolling(64).std() + f["volatility_ratio"] = f["volatility_fast"] / (f["volatility_slow"] + 1e-9) + f["volume_z"] = (v - v.rolling(48).mean()) / (v.rolling(48).std() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 6)) + threshold = float(p.get("threshold", 0.0018)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", RandomForestClassifier(n_estimators=350, max_depth=8, min_samples_leaf=8, class_weight="balanced_subsample", random_state=42, n_jobs=-1)), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 140)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/audusd-logistic-30m/README.md b/templates/audusd-logistic-30m/README.md new file mode 100644 index 0000000..16bbb26 --- /dev/null +++ b/templates/audusd-logistic-30m/README.md @@ -0,0 +1,29 @@ +# audusd-logistic-30m + +AUDUSD momentum classifier for PyP Quant Mode. + +This is an educational starter template for `AUDUSD` on the `30m` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `AUDUSD` +- Timeframe: `30m` +- Family: `sklearn LogisticRegression` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/audusd-logistic-30m/quant.config.json b/templates/audusd-logistic-30m/quant.config.json new file mode 100644 index 0000000..f3f51c2 --- /dev/null +++ b/templates/audusd-logistic-30m/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "audusd-logistic-30m", + "description": "sklearn LogisticRegression PyP Quant template for AUDUSD 30m.", + "symbol": "AUDUSD", + "timeframe": "30m", + "model_family": "sklearn LogisticRegression", + "artifact_target": "joblib", + "parameters": { + "lookback": 120, + "horizon": 4, + "threshold": 0.0012, + "min_confidence": 0.48, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/audusd-logistic-30m/strategy.py b/templates/audusd-logistic-30m/strategy.py new file mode 100644 index 0000000..518db2e --- /dev/null +++ b/templates/audusd-logistic-30m/strategy.py @@ -0,0 +1,73 @@ +import numpy as np +import pandas as pd +from sklearn.linear_model import LogisticRegression +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler + + +SYMBOL = "AUDUSD" +MODEL_NAME = "audusd-logistic-30m" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + rng = (df["high"] - df["low"]).replace(0, np.nan) + f = pd.DataFrame(index=df.index) + for n in [1, 3, 6, 12, 24]: + f[f"ret{n}"] = c.pct_change(n) + f["range_pct"] = rng / c + f["body_pct"] = (c - df["open"]) / (rng + 1e-9) + f["close_pos"] = (c - df["low"]) / (rng + 1e-9) + f["ema_8_21"] = (c.ewm(span=8, adjust=False).mean() - c.ewm(span=21, adjust=False).mean()) / c + f["ema_21_55"] = (c.ewm(span=21, adjust=False).mean() - c.ewm(span=55, adjust=False).mean()) / c + f["volatility"] = c.pct_change().rolling(24).std() + f["volume_ratio"] = df["volume"] / (df["volume"].rolling(24).mean() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 4)) + threshold = float(p.get("threshold", 0.0012)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", LogisticRegression(max_iter=1000, class_weight="balanced")), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 120)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.48)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/avaxusdt-volatility-rf-30m/README.md b/templates/avaxusdt-volatility-rf-30m/README.md new file mode 100644 index 0000000..840e931 --- /dev/null +++ b/templates/avaxusdt-volatility-rf-30m/README.md @@ -0,0 +1,29 @@ +# avaxusdt-volatility-rf-30m + +AVAX volatility classifier for PyP Quant Mode. + +This is an educational starter template for `AVAXUSDT` on the `30m` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `AVAXUSDT` +- Timeframe: `30m` +- Family: `sklearn RandomForest` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/avaxusdt-volatility-rf-30m/quant.config.json b/templates/avaxusdt-volatility-rf-30m/quant.config.json new file mode 100644 index 0000000..e0ca732 --- /dev/null +++ b/templates/avaxusdt-volatility-rf-30m/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "avaxusdt-volatility-rf-30m", + "description": "sklearn RandomForest PyP Quant template for AVAXUSDT 30m.", + "symbol": "AVAXUSDT", + "timeframe": "30m", + "model_family": "sklearn RandomForest", + "artifact_target": "joblib", + "parameters": { + "lookback": 120, + "horizon": 4, + "threshold": 0.004, + "min_confidence": 0.5, + "sl_percent": 0.4, + "tp_percent": 0.8 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/avaxusdt-volatility-rf-30m/strategy.py b/templates/avaxusdt-volatility-rf-30m/strategy.py new file mode 100644 index 0000000..e2edd6c --- /dev/null +++ b/templates/avaxusdt-volatility-rf-30m/strategy.py @@ -0,0 +1,72 @@ +import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + + +SYMBOL = "AVAXUSDT" +MODEL_NAME = "avaxusdt-volatility-rf-30m" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + v = df["volume"] + f = pd.DataFrame(index=df.index) + for n in [1, 4, 8, 16, 32, 64]: + f[f"ret{n}"] = c.pct_change(n) + f["ema_12_48"] = (c.ewm(span=12, adjust=False).mean() - c.ewm(span=48, adjust=False).mean()) / c + f["ema_24_96"] = (c.ewm(span=24, adjust=False).mean() - c.ewm(span=96, adjust=False).mean()) / c + f["volatility_fast"] = c.pct_change().rolling(16).std() + f["volatility_slow"] = c.pct_change().rolling(64).std() + f["volatility_ratio"] = f["volatility_fast"] / (f["volatility_slow"] + 1e-9) + f["volume_z"] = (v - v.rolling(48).mean()) / (v.rolling(48).std() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 6)) + threshold = float(p.get("threshold", 0.004)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", RandomForestClassifier(n_estimators=350, max_depth=8, min_samples_leaf=8, class_weight="balanced_subsample", random_state=42, n_jobs=-1)), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 120)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/bnbusdt-trend-rf-30m/README.md b/templates/bnbusdt-trend-rf-30m/README.md new file mode 100644 index 0000000..3bacf37 --- /dev/null +++ b/templates/bnbusdt-trend-rf-30m/README.md @@ -0,0 +1,29 @@ +# bnbusdt-trend-rf-30m + +BNB trend classifier for PyP Quant Mode. + +This is an educational starter template for `BNBUSDT` on the `30m` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `BNBUSDT` +- Timeframe: `30m` +- Family: `sklearn RandomForest` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/bnbusdt-trend-rf-30m/quant.config.json b/templates/bnbusdt-trend-rf-30m/quant.config.json new file mode 100644 index 0000000..ace06bd --- /dev/null +++ b/templates/bnbusdt-trend-rf-30m/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "bnbusdt-trend-rf-30m", + "description": "sklearn RandomForest PyP Quant template for BNBUSDT 30m.", + "symbol": "BNBUSDT", + "timeframe": "30m", + "model_family": "sklearn RandomForest", + "artifact_target": "joblib", + "parameters": { + "lookback": 120, + "horizon": 4, + "threshold": 0.004, + "min_confidence": 0.5, + "sl_percent": 0.4, + "tp_percent": 0.8 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/bnbusdt-trend-rf-30m/strategy.py b/templates/bnbusdt-trend-rf-30m/strategy.py new file mode 100644 index 0000000..0cf3e88 --- /dev/null +++ b/templates/bnbusdt-trend-rf-30m/strategy.py @@ -0,0 +1,72 @@ +import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + + +SYMBOL = "BNBUSDT" +MODEL_NAME = "bnbusdt-trend-rf-30m" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + v = df["volume"] + f = pd.DataFrame(index=df.index) + for n in [1, 4, 8, 16, 32, 64]: + f[f"ret{n}"] = c.pct_change(n) + f["ema_12_48"] = (c.ewm(span=12, adjust=False).mean() - c.ewm(span=48, adjust=False).mean()) / c + f["ema_24_96"] = (c.ewm(span=24, adjust=False).mean() - c.ewm(span=96, adjust=False).mean()) / c + f["volatility_fast"] = c.pct_change().rolling(16).std() + f["volatility_slow"] = c.pct_change().rolling(64).std() + f["volatility_ratio"] = f["volatility_fast"] / (f["volatility_slow"] + 1e-9) + f["volume_z"] = (v - v.rolling(48).mean()) / (v.rolling(48).std() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 6)) + threshold = float(p.get("threshold", 0.004)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", RandomForestClassifier(n_estimators=350, max_depth=8, min_samples_leaf=8, class_weight="balanced_subsample", random_state=42, n_jobs=-1)), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 120)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/brentoil-trend-rf-1h/README.md b/templates/brentoil-trend-rf-1h/README.md new file mode 100644 index 0000000..a72f5ba --- /dev/null +++ b/templates/brentoil-trend-rf-1h/README.md @@ -0,0 +1,29 @@ +# brentoil-trend-rf-1h + +Brent crude trend classifier for PyP Quant Mode. + +This is an educational starter template for `BRENTUSD` on the `1h` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `BRENTUSD` +- Timeframe: `1h` +- Family: `sklearn RandomForest` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/brentoil-trend-rf-1h/quant.config.json b/templates/brentoil-trend-rf-1h/quant.config.json new file mode 100644 index 0000000..e262c2b --- /dev/null +++ b/templates/brentoil-trend-rf-1h/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "brentoil-trend-rf-1h", + "description": "sklearn RandomForest PyP Quant template for BRENTUSD 1h.", + "symbol": "BRENTUSD", + "timeframe": "1h", + "model_family": "sklearn RandomForest", + "artifact_target": "joblib", + "parameters": { + "lookback": 140, + "horizon": 6, + "threshold": 0.003, + "min_confidence": 0.5, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/brentoil-trend-rf-1h/strategy.py b/templates/brentoil-trend-rf-1h/strategy.py new file mode 100644 index 0000000..cc46307 --- /dev/null +++ b/templates/brentoil-trend-rf-1h/strategy.py @@ -0,0 +1,72 @@ +import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + + +SYMBOL = "BRENTUSD" +MODEL_NAME = "brentoil-trend-rf-1h" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + v = df["volume"] + f = pd.DataFrame(index=df.index) + for n in [1, 4, 8, 16, 32, 64]: + f[f"ret{n}"] = c.pct_change(n) + f["ema_12_48"] = (c.ewm(span=12, adjust=False).mean() - c.ewm(span=48, adjust=False).mean()) / c + f["ema_24_96"] = (c.ewm(span=24, adjust=False).mean() - c.ewm(span=96, adjust=False).mean()) / c + f["volatility_fast"] = c.pct_change().rolling(16).std() + f["volatility_slow"] = c.pct_change().rolling(64).std() + f["volatility_ratio"] = f["volatility_fast"] / (f["volatility_slow"] + 1e-9) + f["volume_z"] = (v - v.rolling(48).mean()) / (v.rolling(48).std() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 6)) + threshold = float(p.get("threshold", 0.003)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", RandomForestClassifier(n_estimators=350, max_depth=8, min_samples_leaf=8, class_weight="balanced_subsample", random_state=42, n_jobs=-1)), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 140)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/btcusdt-breakout-rf-15m/README.md b/templates/btcusdt-breakout-rf-15m/README.md new file mode 100644 index 0000000..5c9c59a --- /dev/null +++ b/templates/btcusdt-breakout-rf-15m/README.md @@ -0,0 +1,29 @@ +# btcusdt-breakout-rf-15m + +BTC breakout classifier for PyP Quant Mode. + +This is an educational starter template for `BTCUSDT` on the `15m` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `BTCUSDT` +- Timeframe: `15m` +- Family: `sklearn RandomForest` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/btcusdt-breakout-rf-15m/quant.config.json b/templates/btcusdt-breakout-rf-15m/quant.config.json new file mode 100644 index 0000000..1b98e12 --- /dev/null +++ b/templates/btcusdt-breakout-rf-15m/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "btcusdt-breakout-rf-15m", + "description": "sklearn RandomForest PyP Quant template for BTCUSDT 15m.", + "symbol": "BTCUSDT", + "timeframe": "15m", + "model_family": "sklearn RandomForest", + "artifact_target": "joblib", + "parameters": { + "lookback": 100, + "horizon": 4, + "threshold": 0.004, + "min_confidence": 0.5, + "sl_percent": 0.4, + "tp_percent": 0.8 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/btcusdt-breakout-rf-15m/strategy.py b/templates/btcusdt-breakout-rf-15m/strategy.py new file mode 100644 index 0000000..88eb75b --- /dev/null +++ b/templates/btcusdt-breakout-rf-15m/strategy.py @@ -0,0 +1,69 @@ +import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier + + +SYMBOL = "BTCUSDT" +MODEL_NAME = "btcusdt-breakout-rf-15m" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + h = df["high"] + l = df["low"] + f = pd.DataFrame(index=df.index) + high_break = h.rolling(36).max().shift() + low_break = l.rolling(36).min().shift() + f["breakout_up"] = (c - high_break) / c + f["breakout_down"] = (c - low_break) / c + f["ret4"] = c.pct_change(4) + f["ret12"] = c.pct_change(12) + f["volatility"] = c.pct_change().rolling(24).std() + f["range_pct"] = (h - l) / c + f["ema_slope"] = c.ewm(span=12, adjust=False).mean().pct_change(6) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 4)) + threshold = float(p.get("threshold", 0.004)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + clf = RandomForestClassifier(n_estimators=300, max_depth=7, min_samples_leaf=6, class_weight="balanced_subsample", random_state=42, n_jobs=-1) + clf.fit(x.values.astype(np.float32), y.values) + pred = clf.predict(x.values.astype(np.float32)) + return {"model": clf, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 100)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/btcusdt-lightgbm-1h/quant.config.json b/templates/btcusdt-lightgbm-1h/quant.config.json index b396e81..8acecc6 100644 --- a/templates/btcusdt-lightgbm-1h/quant.config.json +++ b/templates/btcusdt-lightgbm-1h/quant.config.json @@ -1 +1,30 @@ -{"pair":"BTCUSDT","timeframe":"1h","model_family":"lightgbm","runtime_target":"modal","artifact_format":"joblib_bundle","parameters":{"lookback":120,"horizon":3,"threshold":0.006,"min_confidence":0.46},"training_requirements":["numpy","pandas","scikit-learn","lightgbm","joblib"],"inference_requirements":["numpy","pandas","scikit-learn","lightgbm","joblib"]} +{ + "pair": "BTCUSDT", + "timeframe": "1h", + "model_family": "LightGBM", + "runtime_target": "modal", + "artifact_format": "joblib_bundle", + "parameters": { + "lookback": 120, + "horizon": 3, + "threshold": 0.006, + "min_confidence": 0.46 + }, + "training_requirements": [ + "numpy", + "pandas", + "scikit-learn", + "lightgbm", + "joblib" + ], + "inference_requirements": [ + "numpy", + "pandas", + "scikit-learn", + "lightgbm", + "joblib" + ], + "symbol": "BTCUSDT", + "description": "BTCUSDT LightGBM crypto trend classifier", + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/btcusdt-mean-reversion-5m/README.md b/templates/btcusdt-mean-reversion-5m/README.md new file mode 100644 index 0000000..eb7810e --- /dev/null +++ b/templates/btcusdt-mean-reversion-5m/README.md @@ -0,0 +1,29 @@ +# btcusdt-mean-reversion-5m + +BTC mean-reversion baseline for PyP Quant Mode. + +This is an educational starter template for `BTCUSDT` on the `5m` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `BTCUSDT` +- Timeframe: `5m` +- Family: `custom Python` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/btcusdt-mean-reversion-5m/quant.config.json b/templates/btcusdt-mean-reversion-5m/quant.config.json new file mode 100644 index 0000000..4300a8a --- /dev/null +++ b/templates/btcusdt-mean-reversion-5m/quant.config.json @@ -0,0 +1,21 @@ +{ + "name": "btcusdt-mean-reversion-5m", + "description": "custom Python PyP Quant template for BTCUSDT 5m.", + "symbol": "BTCUSDT", + "timeframe": "5m", + "model_family": "custom Python", + "artifact_target": "python", + "parameters": { + "lookback": 100, + "horizon": 4, + "threshold": 0.004, + "min_confidence": 0.5, + "sl_percent": 0.4, + "tp_percent": 0.8 + }, + "requirements": [ + "numpy", + "pandas" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/btcusdt-mean-reversion-5m/strategy.py b/templates/btcusdt-mean-reversion-5m/strategy.py new file mode 100644 index 0000000..d200dd7 --- /dev/null +++ b/templates/btcusdt-mean-reversion-5m/strategy.py @@ -0,0 +1,46 @@ +import numpy as np +import pandas as pd + + +SYMBOL = "BTCUSDT" +MODEL_NAME = "btcusdt-mean-reversion-5m" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _zscore(close, window): + mean = close.rolling(window).mean() + std = close.rolling(window).std() + return (close - mean) / (std + 1e-9) + + +def train(data, config): + p = config.get("parameters", {}) + return {"window": int(p.get("window", 48)), "entry_z": float(p.get("entry_z", 1.4)), "exit_z": float(p.get("exit_z", 0.3)), "symbol": SYMBOL}, {"model_family": "rule_based_mean_reversion", "training_bars": int(len(data))} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + lookback = int(p.get("lookback", 100)) + if len(candles) < lookback: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + window = int(model.get("window", p.get("window", 48))) + z = float(_zscore(df["close"], window).iloc[-1]) + entry_z = float(model.get("entry_z", p.get("entry_z", 1.4))) + signal = "HOLD" + if z <= -entry_z: + signal = "UP" + elif z >= entry_z: + signal = "DOWN" + confidence = min(abs(z) / max(entry_z, 1e-9), 1.0) + return {"signal": signal, "confidence": round(float(confidence), 4), "metadata": {"zscore": round(z, 4), "window": window, "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/cadjpy-breakout-1h/README.md b/templates/cadjpy-breakout-1h/README.md new file mode 100644 index 0000000..52c99fb --- /dev/null +++ b/templates/cadjpy-breakout-1h/README.md @@ -0,0 +1,29 @@ +# cadjpy-breakout-1h + +CADJPY breakout classifier for PyP Quant Mode. + +This is an educational starter template for `CADJPY` on the `1h` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `CADJPY` +- Timeframe: `1h` +- Family: `sklearn RandomForest` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/cadjpy-breakout-1h/quant.config.json b/templates/cadjpy-breakout-1h/quant.config.json new file mode 100644 index 0000000..410350c --- /dev/null +++ b/templates/cadjpy-breakout-1h/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "cadjpy-breakout-1h", + "description": "sklearn RandomForest PyP Quant template for CADJPY 1h.", + "symbol": "CADJPY", + "timeframe": "1h", + "model_family": "sklearn RandomForest", + "artifact_target": "joblib", + "parameters": { + "lookback": 140, + "horizon": 6, + "threshold": 0.0018, + "min_confidence": 0.5, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/cadjpy-breakout-1h/strategy.py b/templates/cadjpy-breakout-1h/strategy.py new file mode 100644 index 0000000..91b6c8a --- /dev/null +++ b/templates/cadjpy-breakout-1h/strategy.py @@ -0,0 +1,69 @@ +import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier + + +SYMBOL = "CADJPY" +MODEL_NAME = "cadjpy-breakout-1h" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + h = df["high"] + l = df["low"] + f = pd.DataFrame(index=df.index) + high_break = h.rolling(36).max().shift() + low_break = l.rolling(36).min().shift() + f["breakout_up"] = (c - high_break) / c + f["breakout_down"] = (c - low_break) / c + f["ret4"] = c.pct_change(4) + f["ret12"] = c.pct_change(12) + f["volatility"] = c.pct_change().rolling(24).std() + f["range_pct"] = (h - l) / c + f["ema_slope"] = c.ewm(span=12, adjust=False).mean().pct_change(6) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 4)) + threshold = float(p.get("threshold", 0.0018)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + clf = RandomForestClassifier(n_estimators=300, max_depth=7, min_samples_leaf=6, class_weight="balanced_subsample", random_state=42, n_jobs=-1) + clf.fit(x.values.astype(np.float32), y.values) + pred = clf.predict(x.values.astype(np.float32)) + return {"model": clf, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 140)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/chfjpy-mean-reversion-1h/README.md b/templates/chfjpy-mean-reversion-1h/README.md new file mode 100644 index 0000000..8288e7e --- /dev/null +++ b/templates/chfjpy-mean-reversion-1h/README.md @@ -0,0 +1,29 @@ +# chfjpy-mean-reversion-1h + +CHFJPY mean-reversion baseline for PyP Quant Mode. + +This is an educational starter template for `CHFJPY` on the `1h` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `CHFJPY` +- Timeframe: `1h` +- Family: `custom Python` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/chfjpy-mean-reversion-1h/quant.config.json b/templates/chfjpy-mean-reversion-1h/quant.config.json new file mode 100644 index 0000000..0caff64 --- /dev/null +++ b/templates/chfjpy-mean-reversion-1h/quant.config.json @@ -0,0 +1,21 @@ +{ + "name": "chfjpy-mean-reversion-1h", + "description": "custom Python PyP Quant template for CHFJPY 1h.", + "symbol": "CHFJPY", + "timeframe": "1h", + "model_family": "custom Python", + "artifact_target": "python", + "parameters": { + "lookback": 140, + "horizon": 6, + "threshold": 0.0018, + "min_confidence": 0.5, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/chfjpy-mean-reversion-1h/strategy.py b/templates/chfjpy-mean-reversion-1h/strategy.py new file mode 100644 index 0000000..f00bd11 --- /dev/null +++ b/templates/chfjpy-mean-reversion-1h/strategy.py @@ -0,0 +1,46 @@ +import numpy as np +import pandas as pd + + +SYMBOL = "CHFJPY" +MODEL_NAME = "chfjpy-mean-reversion-1h" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _zscore(close, window): + mean = close.rolling(window).mean() + std = close.rolling(window).std() + return (close - mean) / (std + 1e-9) + + +def train(data, config): + p = config.get("parameters", {}) + return {"window": int(p.get("window", 48)), "entry_z": float(p.get("entry_z", 1.4)), "exit_z": float(p.get("exit_z", 0.3)), "symbol": SYMBOL}, {"model_family": "rule_based_mean_reversion", "training_bars": int(len(data))} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + lookback = int(p.get("lookback", 140)) + if len(candles) < lookback: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + window = int(model.get("window", p.get("window", 48))) + z = float(_zscore(df["close"], window).iloc[-1]) + entry_z = float(model.get("entry_z", p.get("entry_z", 1.4))) + signal = "HOLD" + if z <= -entry_z: + signal = "UP" + elif z >= entry_z: + signal = "DOWN" + confidence = min(abs(z) / max(entry_z, 1e-9), 1.0) + return {"signal": signal, "confidence": round(float(confidence), 4), "metadata": {"zscore": round(z, 4), "window": window, "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/copperusd-mean-reversion-1h/README.md b/templates/copperusd-mean-reversion-1h/README.md new file mode 100644 index 0000000..a8fd6a9 --- /dev/null +++ b/templates/copperusd-mean-reversion-1h/README.md @@ -0,0 +1,29 @@ +# copperusd-mean-reversion-1h + +Copper mean-reversion baseline for PyP Quant Mode. + +This is an educational starter template for `COPPERUSD` on the `1h` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `COPPERUSD` +- Timeframe: `1h` +- Family: `custom Python` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/copperusd-mean-reversion-1h/quant.config.json b/templates/copperusd-mean-reversion-1h/quant.config.json new file mode 100644 index 0000000..a45eb21 --- /dev/null +++ b/templates/copperusd-mean-reversion-1h/quant.config.json @@ -0,0 +1,21 @@ +{ + "name": "copperusd-mean-reversion-1h", + "description": "custom Python PyP Quant template for COPPERUSD 1h.", + "symbol": "COPPERUSD", + "timeframe": "1h", + "model_family": "custom Python", + "artifact_target": "python", + "parameters": { + "lookback": 140, + "horizon": 6, + "threshold": 0.003, + "min_confidence": 0.5, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/copperusd-mean-reversion-1h/strategy.py b/templates/copperusd-mean-reversion-1h/strategy.py new file mode 100644 index 0000000..c51a52d --- /dev/null +++ b/templates/copperusd-mean-reversion-1h/strategy.py @@ -0,0 +1,46 @@ +import numpy as np +import pandas as pd + + +SYMBOL = "COPPERUSD" +MODEL_NAME = "copperusd-mean-reversion-1h" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _zscore(close, window): + mean = close.rolling(window).mean() + std = close.rolling(window).std() + return (close - mean) / (std + 1e-9) + + +def train(data, config): + p = config.get("parameters", {}) + return {"window": int(p.get("window", 48)), "entry_z": float(p.get("entry_z", 1.4)), "exit_z": float(p.get("exit_z", 0.3)), "symbol": SYMBOL}, {"model_family": "rule_based_mean_reversion", "training_bars": int(len(data))} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + lookback = int(p.get("lookback", 140)) + if len(candles) < lookback: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + window = int(model.get("window", p.get("window", 48))) + z = float(_zscore(df["close"], window).iloc[-1]) + entry_z = float(model.get("entry_z", p.get("entry_z", 1.4))) + signal = "HOLD" + if z <= -entry_z: + signal = "UP" + elif z >= entry_z: + signal = "DOWN" + confidence = min(abs(z) / max(entry_z, 1e-9), 1.0) + return {"signal": signal, "confidence": round(float(confidence), 4), "metadata": {"zscore": round(z, 4), "window": window, "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/dogeusdt-scalp-logistic-1m/README.md b/templates/dogeusdt-scalp-logistic-1m/README.md new file mode 100644 index 0000000..88e4c16 --- /dev/null +++ b/templates/dogeusdt-scalp-logistic-1m/README.md @@ -0,0 +1,29 @@ +# dogeusdt-scalp-logistic-1m + +DOGE high-volatility scalp classifier for PyP Quant Mode. + +This is an educational starter template for `DOGEUSDT` on the `1m` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `DOGEUSDT` +- Timeframe: `1m` +- Family: `sklearn LogisticRegression` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/dogeusdt-scalp-logistic-1m/quant.config.json b/templates/dogeusdt-scalp-logistic-1m/quant.config.json new file mode 100644 index 0000000..32acd17 --- /dev/null +++ b/templates/dogeusdt-scalp-logistic-1m/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "dogeusdt-scalp-logistic-1m", + "description": "sklearn LogisticRegression PyP Quant template for DOGEUSDT 1m.", + "symbol": "DOGEUSDT", + "timeframe": "1m", + "model_family": "sklearn LogisticRegression", + "artifact_target": "joblib", + "parameters": { + "lookback": 120, + "horizon": 4, + "threshold": 0.003, + "min_confidence": 0.48, + "sl_percent": 0.4, + "tp_percent": 0.8 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/dogeusdt-scalp-logistic-1m/strategy.py b/templates/dogeusdt-scalp-logistic-1m/strategy.py new file mode 100644 index 0000000..cf6153f --- /dev/null +++ b/templates/dogeusdt-scalp-logistic-1m/strategy.py @@ -0,0 +1,73 @@ +import numpy as np +import pandas as pd +from sklearn.linear_model import LogisticRegression +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler + + +SYMBOL = "DOGEUSDT" +MODEL_NAME = "dogeusdt-scalp-logistic-1m" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + rng = (df["high"] - df["low"]).replace(0, np.nan) + f = pd.DataFrame(index=df.index) + for n in [1, 3, 6, 12, 24]: + f[f"ret{n}"] = c.pct_change(n) + f["range_pct"] = rng / c + f["body_pct"] = (c - df["open"]) / (rng + 1e-9) + f["close_pos"] = (c - df["low"]) / (rng + 1e-9) + f["ema_8_21"] = (c.ewm(span=8, adjust=False).mean() - c.ewm(span=21, adjust=False).mean()) / c + f["ema_21_55"] = (c.ewm(span=21, adjust=False).mean() - c.ewm(span=55, adjust=False).mean()) / c + f["volatility"] = c.pct_change().rolling(24).std() + f["volume_ratio"] = df["volume"] / (df["volume"].rolling(24).mean() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 4)) + threshold = float(p.get("threshold", 0.003)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", LogisticRegression(max_iter=1000, class_weight="balanced")), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 120)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.48)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/dotusdt-mean-reversion-30m/README.md b/templates/dotusdt-mean-reversion-30m/README.md new file mode 100644 index 0000000..db4bae1 --- /dev/null +++ b/templates/dotusdt-mean-reversion-30m/README.md @@ -0,0 +1,29 @@ +# dotusdt-mean-reversion-30m + +DOT mean-reversion baseline for PyP Quant Mode. + +This is an educational starter template for `DOTUSDT` on the `30m` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `DOTUSDT` +- Timeframe: `30m` +- Family: `custom Python` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/dotusdt-mean-reversion-30m/quant.config.json b/templates/dotusdt-mean-reversion-30m/quant.config.json new file mode 100644 index 0000000..061d600 --- /dev/null +++ b/templates/dotusdt-mean-reversion-30m/quant.config.json @@ -0,0 +1,21 @@ +{ + "name": "dotusdt-mean-reversion-30m", + "description": "custom Python PyP Quant template for DOTUSDT 30m.", + "symbol": "DOTUSDT", + "timeframe": "30m", + "model_family": "custom Python", + "artifact_target": "python", + "parameters": { + "lookback": 120, + "horizon": 4, + "threshold": 0.004, + "min_confidence": 0.5, + "sl_percent": 0.4, + "tp_percent": 0.8 + }, + "requirements": [ + "numpy", + "pandas" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/dotusdt-mean-reversion-30m/strategy.py b/templates/dotusdt-mean-reversion-30m/strategy.py new file mode 100644 index 0000000..58ef12d --- /dev/null +++ b/templates/dotusdt-mean-reversion-30m/strategy.py @@ -0,0 +1,46 @@ +import numpy as np +import pandas as pd + + +SYMBOL = "DOTUSDT" +MODEL_NAME = "dotusdt-mean-reversion-30m" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _zscore(close, window): + mean = close.rolling(window).mean() + std = close.rolling(window).std() + return (close - mean) / (std + 1e-9) + + +def train(data, config): + p = config.get("parameters", {}) + return {"window": int(p.get("window", 48)), "entry_z": float(p.get("entry_z", 1.4)), "exit_z": float(p.get("exit_z", 0.3)), "symbol": SYMBOL}, {"model_family": "rule_based_mean_reversion", "training_bars": int(len(data))} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + lookback = int(p.get("lookback", 120)) + if len(candles) < lookback: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + window = int(model.get("window", p.get("window", 48))) + z = float(_zscore(df["close"], window).iloc[-1]) + entry_z = float(model.get("entry_z", p.get("entry_z", 1.4))) + signal = "HOLD" + if z <= -entry_z: + signal = "UP" + elif z >= entry_z: + signal = "DOWN" + confidence = min(abs(z) / max(entry_z, 1e-9), 1.0) + return {"signal": signal, "confidence": round(float(confidence), 4), "metadata": {"zscore": round(z, 4), "window": window, "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/ethusdt-volatility-classifier/quant.config.json b/templates/ethusdt-volatility-classifier/quant.config.json index d37e347..900f30c 100644 --- a/templates/ethusdt-volatility-classifier/quant.config.json +++ b/templates/ethusdt-volatility-classifier/quant.config.json @@ -1 +1,28 @@ -{"pair":"ETHUSDT","timeframe":"30m","model_family":"sklearn","runtime_target":"edge","artifact_format":"weights_bundle","parameters":{"lookback":120,"horizon":4,"threshold":0.005,"min_confidence":0.5},"training_requirements":["numpy","pandas","scikit-learn","joblib"],"inference_requirements":["numpy","pandas","scikit-learn","joblib"]} +{ + "pair": "ETHUSDT", + "timeframe": "30m", + "model_family": "sklearn RandomForest", + "runtime_target": "edge", + "artifact_format": "weights_bundle", + "parameters": { + "lookback": 120, + "horizon": 4, + "threshold": 0.005, + "min_confidence": 0.5 + }, + "training_requirements": [ + "numpy", + "pandas", + "scikit-learn", + "joblib" + ], + "inference_requirements": [ + "numpy", + "pandas", + "scikit-learn", + "joblib" + ], + "symbol": "ETHUSDT", + "description": "ETHUSDT volatility-aware directional classifier", + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/eurgbp-range-classifier-30m/README.md b/templates/eurgbp-range-classifier-30m/README.md new file mode 100644 index 0000000..976eefb --- /dev/null +++ b/templates/eurgbp-range-classifier-30m/README.md @@ -0,0 +1,29 @@ +# eurgbp-range-classifier-30m + +EURGBP range classifier for PyP Quant Mode. + +This is an educational starter template for `EURGBP` on the `30m` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `EURGBP` +- Timeframe: `30m` +- Family: `sklearn LogisticRegression` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/eurgbp-range-classifier-30m/quant.config.json b/templates/eurgbp-range-classifier-30m/quant.config.json new file mode 100644 index 0000000..6112c0f --- /dev/null +++ b/templates/eurgbp-range-classifier-30m/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "eurgbp-range-classifier-30m", + "description": "sklearn LogisticRegression PyP Quant template for EURGBP 30m.", + "symbol": "EURGBP", + "timeframe": "30m", + "model_family": "sklearn LogisticRegression", + "artifact_target": "joblib", + "parameters": { + "lookback": 120, + "horizon": 4, + "threshold": 0.0012, + "min_confidence": 0.48, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/eurgbp-range-classifier-30m/strategy.py b/templates/eurgbp-range-classifier-30m/strategy.py new file mode 100644 index 0000000..0f1932a --- /dev/null +++ b/templates/eurgbp-range-classifier-30m/strategy.py @@ -0,0 +1,73 @@ +import numpy as np +import pandas as pd +from sklearn.linear_model import LogisticRegression +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler + + +SYMBOL = "EURGBP" +MODEL_NAME = "eurgbp-range-classifier-30m" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + rng = (df["high"] - df["low"]).replace(0, np.nan) + f = pd.DataFrame(index=df.index) + for n in [1, 3, 6, 12, 24]: + f[f"ret{n}"] = c.pct_change(n) + f["range_pct"] = rng / c + f["body_pct"] = (c - df["open"]) / (rng + 1e-9) + f["close_pos"] = (c - df["low"]) / (rng + 1e-9) + f["ema_8_21"] = (c.ewm(span=8, adjust=False).mean() - c.ewm(span=21, adjust=False).mean()) / c + f["ema_21_55"] = (c.ewm(span=21, adjust=False).mean() - c.ewm(span=55, adjust=False).mean()) / c + f["volatility"] = c.pct_change().rolling(24).std() + f["volume_ratio"] = df["volume"] / (df["volume"].rolling(24).mean() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 4)) + threshold = float(p.get("threshold", 0.0012)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", LogisticRegression(max_iter=1000, class_weight="balanced")), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 120)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.48)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/eurjpy-trend-rf-1h/README.md b/templates/eurjpy-trend-rf-1h/README.md new file mode 100644 index 0000000..88a954f --- /dev/null +++ b/templates/eurjpy-trend-rf-1h/README.md @@ -0,0 +1,29 @@ +# eurjpy-trend-rf-1h + +EURJPY trend classifier for PyP Quant Mode. + +This is an educational starter template for `EURJPY` on the `1h` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `EURJPY` +- Timeframe: `1h` +- Family: `sklearn RandomForest` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/eurjpy-trend-rf-1h/quant.config.json b/templates/eurjpy-trend-rf-1h/quant.config.json new file mode 100644 index 0000000..54718ff --- /dev/null +++ b/templates/eurjpy-trend-rf-1h/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "eurjpy-trend-rf-1h", + "description": "sklearn RandomForest PyP Quant template for EURJPY 1h.", + "symbol": "EURJPY", + "timeframe": "1h", + "model_family": "sklearn RandomForest", + "artifact_target": "joblib", + "parameters": { + "lookback": 140, + "horizon": 6, + "threshold": 0.0018, + "min_confidence": 0.5, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/eurjpy-trend-rf-1h/strategy.py b/templates/eurjpy-trend-rf-1h/strategy.py new file mode 100644 index 0000000..e8e4513 --- /dev/null +++ b/templates/eurjpy-trend-rf-1h/strategy.py @@ -0,0 +1,72 @@ +import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + + +SYMBOL = "EURJPY" +MODEL_NAME = "eurjpy-trend-rf-1h" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + v = df["volume"] + f = pd.DataFrame(index=df.index) + for n in [1, 4, 8, 16, 32, 64]: + f[f"ret{n}"] = c.pct_change(n) + f["ema_12_48"] = (c.ewm(span=12, adjust=False).mean() - c.ewm(span=48, adjust=False).mean()) / c + f["ema_24_96"] = (c.ewm(span=24, adjust=False).mean() - c.ewm(span=96, adjust=False).mean()) / c + f["volatility_fast"] = c.pct_change().rolling(16).std() + f["volatility_slow"] = c.pct_change().rolling(64).std() + f["volatility_ratio"] = f["volatility_fast"] / (f["volatility_slow"] + 1e-9) + f["volume_z"] = (v - v.rolling(48).mean()) / (v.rolling(48).std() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 6)) + threshold = float(p.get("threshold", 0.0018)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", RandomForestClassifier(n_estimators=350, max_depth=8, min_samples_leaf=8, class_weight="balanced_subsample", random_state=42, n_jobs=-1)), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 140)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/eurnzd-volatility-rf-4h/README.md b/templates/eurnzd-volatility-rf-4h/README.md new file mode 100644 index 0000000..a515cac --- /dev/null +++ b/templates/eurnzd-volatility-rf-4h/README.md @@ -0,0 +1,29 @@ +# eurnzd-volatility-rf-4h + +EURNZD volatility classifier for PyP Quant Mode. + +This is an educational starter template for `EURNZD` on the `4h` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `EURNZD` +- Timeframe: `4h` +- Family: `sklearn RandomForest` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/eurnzd-volatility-rf-4h/quant.config.json b/templates/eurnzd-volatility-rf-4h/quant.config.json new file mode 100644 index 0000000..ee2477a --- /dev/null +++ b/templates/eurnzd-volatility-rf-4h/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "eurnzd-volatility-rf-4h", + "description": "sklearn RandomForest PyP Quant template for EURNZD 4h.", + "symbol": "EURNZD", + "timeframe": "4h", + "model_family": "sklearn RandomForest", + "artifact_target": "joblib", + "parameters": { + "lookback": 180, + "horizon": 6, + "threshold": 0.0012, + "min_confidence": 0.5, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/eurnzd-volatility-rf-4h/strategy.py b/templates/eurnzd-volatility-rf-4h/strategy.py new file mode 100644 index 0000000..69758ac --- /dev/null +++ b/templates/eurnzd-volatility-rf-4h/strategy.py @@ -0,0 +1,72 @@ +import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + + +SYMBOL = "EURNZD" +MODEL_NAME = "eurnzd-volatility-rf-4h" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + v = df["volume"] + f = pd.DataFrame(index=df.index) + for n in [1, 4, 8, 16, 32, 64]: + f[f"ret{n}"] = c.pct_change(n) + f["ema_12_48"] = (c.ewm(span=12, adjust=False).mean() - c.ewm(span=48, adjust=False).mean()) / c + f["ema_24_96"] = (c.ewm(span=24, adjust=False).mean() - c.ewm(span=96, adjust=False).mean()) / c + f["volatility_fast"] = c.pct_change().rolling(16).std() + f["volatility_slow"] = c.pct_change().rolling(64).std() + f["volatility_ratio"] = f["volatility_fast"] / (f["volatility_slow"] + 1e-9) + f["volume_z"] = (v - v.rolling(48).mean()) / (v.rolling(48).std() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 6)) + threshold = float(p.get("threshold", 0.0012)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", RandomForestClassifier(n_estimators=350, max_depth=8, min_samples_leaf=8, class_weight="balanced_subsample", random_state=42, n_jobs=-1)), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 180)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/eurusd-logistic-15m/quant.config.json b/templates/eurusd-logistic-15m/quant.config.json index 7e1e75e..fedb5e2 100644 --- a/templates/eurusd-logistic-15m/quant.config.json +++ b/templates/eurusd-logistic-15m/quant.config.json @@ -1,7 +1,7 @@ { "pair": "EURUSD", "timeframe": "15m", - "model_family": "sklearn", + "model_family": "sklearn LogisticRegression", "runtime_target": "edge", "artifact_format": "weights_bundle", "parameters": { @@ -10,6 +10,19 @@ "threshold": 0.0008, "min_confidence": 0.48 }, - "training_requirements": ["numpy", "pandas", "scikit-learn", "joblib"], - "inference_requirements": ["numpy", "pandas", "scikit-learn", "joblib"] + "training_requirements": [ + "numpy", + "pandas", + "scikit-learn", + "joblib" + ], + "inference_requirements": [ + "numpy", + "pandas", + "scikit-learn", + "joblib" + ], + "symbol": "EURUSD", + "description": "EURUSD logistic regression baseline classifier", + "disclaimer": "Educational template only. Not financial advice." } diff --git a/templates/eurusd-xgboost-1h/quant.config.json b/templates/eurusd-xgboost-1h/quant.config.json index 7138dc4..70d4dbf 100644 --- a/templates/eurusd-xgboost-1h/quant.config.json +++ b/templates/eurusd-xgboost-1h/quant.config.json @@ -1 +1,30 @@ -{"pair":"EURUSD","timeframe":"1h","model_family":"xgboost","runtime_target":"modal","artifact_format":"joblib_bundle","parameters":{"lookback":160,"horizon":3,"threshold":0.001,"min_confidence":0.48},"training_requirements":["numpy","pandas","scikit-learn","xgboost","joblib"],"inference_requirements":["numpy","pandas","scikit-learn","xgboost","joblib"]} +{ + "pair": "EURUSD", + "timeframe": "1h", + "model_family": "XGBoost", + "runtime_target": "modal", + "artifact_format": "joblib_bundle", + "parameters": { + "lookback": 160, + "horizon": 3, + "threshold": 0.001, + "min_confidence": 0.48 + }, + "training_requirements": [ + "numpy", + "pandas", + "scikit-learn", + "xgboost", + "joblib" + ], + "inference_requirements": [ + "numpy", + "pandas", + "scikit-learn", + "xgboost", + "joblib" + ], + "symbol": "EURUSD", + "description": "EURUSD feature-rich XGBoost trend classifier", + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/gbpjpy-breakout-rf-1h/README.md b/templates/gbpjpy-breakout-rf-1h/README.md new file mode 100644 index 0000000..e2f5be9 --- /dev/null +++ b/templates/gbpjpy-breakout-rf-1h/README.md @@ -0,0 +1,29 @@ +# gbpjpy-breakout-rf-1h + +GBPJPY breakout classifier for PyP Quant Mode. + +This is an educational starter template for `GBPJPY` on the `1h` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `GBPJPY` +- Timeframe: `1h` +- Family: `sklearn RandomForest` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/gbpjpy-breakout-rf-1h/quant.config.json b/templates/gbpjpy-breakout-rf-1h/quant.config.json new file mode 100644 index 0000000..d99fb77 --- /dev/null +++ b/templates/gbpjpy-breakout-rf-1h/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "gbpjpy-breakout-rf-1h", + "description": "sklearn RandomForest PyP Quant template for GBPJPY 1h.", + "symbol": "GBPJPY", + "timeframe": "1h", + "model_family": "sklearn RandomForest", + "artifact_target": "joblib", + "parameters": { + "lookback": 140, + "horizon": 6, + "threshold": 0.0018, + "min_confidence": 0.5, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/gbpjpy-breakout-rf-1h/strategy.py b/templates/gbpjpy-breakout-rf-1h/strategy.py new file mode 100644 index 0000000..15841a8 --- /dev/null +++ b/templates/gbpjpy-breakout-rf-1h/strategy.py @@ -0,0 +1,69 @@ +import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier + + +SYMBOL = "GBPJPY" +MODEL_NAME = "gbpjpy-breakout-rf-1h" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + h = df["high"] + l = df["low"] + f = pd.DataFrame(index=df.index) + high_break = h.rolling(36).max().shift() + low_break = l.rolling(36).min().shift() + f["breakout_up"] = (c - high_break) / c + f["breakout_down"] = (c - low_break) / c + f["ret4"] = c.pct_change(4) + f["ret12"] = c.pct_change(12) + f["volatility"] = c.pct_change().rolling(24).std() + f["range_pct"] = (h - l) / c + f["ema_slope"] = c.ewm(span=12, adjust=False).mean().pct_change(6) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 4)) + threshold = float(p.get("threshold", 0.0018)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + clf = RandomForestClassifier(n_estimators=300, max_depth=7, min_samples_leaf=6, class_weight="balanced_subsample", random_state=42, n_jobs=-1) + clf.fit(x.values.astype(np.float32), y.values) + pred = clf.predict(x.values.astype(np.float32)) + return {"model": clf, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 140)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/gbpusd-breakout-rf/quant.config.json b/templates/gbpusd-breakout-rf/quant.config.json index 174d8bd..e3cd16a 100644 --- a/templates/gbpusd-breakout-rf/quant.config.json +++ b/templates/gbpusd-breakout-rf/quant.config.json @@ -1 +1,28 @@ -{"pair":"GBPUSD","timeframe":"30m","model_family":"sklearn","runtime_target":"edge","artifact_format":"weights_bundle","parameters":{"lookback":120,"horizon":4,"threshold":0.0012,"min_confidence":0.5},"training_requirements":["numpy","pandas","scikit-learn","joblib"],"inference_requirements":["numpy","pandas","scikit-learn","joblib"]} +{ + "pair": "GBPUSD", + "timeframe": "30m", + "model_family": "sklearn RandomForest", + "runtime_target": "edge", + "artifact_format": "weights_bundle", + "parameters": { + "lookback": 120, + "horizon": 4, + "threshold": 0.0012, + "min_confidence": 0.5 + }, + "training_requirements": [ + "numpy", + "pandas", + "scikit-learn", + "joblib" + ], + "inference_requirements": [ + "numpy", + "pandas", + "scikit-learn", + "joblib" + ], + "symbol": "GBPUSD", + "description": "GBPUSD range breakout random forest classifier", + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/gbpusd-logistic-15m/README.md b/templates/gbpusd-logistic-15m/README.md new file mode 100644 index 0000000..107b393 --- /dev/null +++ b/templates/gbpusd-logistic-15m/README.md @@ -0,0 +1,29 @@ +# gbpusd-logistic-15m + +GBPUSD directional baseline for PyP Quant Mode. + +This is an educational starter template for `GBPUSD` on the `15m` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `GBPUSD` +- Timeframe: `15m` +- Family: `sklearn LogisticRegression` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/gbpusd-logistic-15m/quant.config.json b/templates/gbpusd-logistic-15m/quant.config.json new file mode 100644 index 0000000..e3ae143 --- /dev/null +++ b/templates/gbpusd-logistic-15m/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "gbpusd-logistic-15m", + "description": "sklearn LogisticRegression PyP Quant template for GBPUSD 15m.", + "symbol": "GBPUSD", + "timeframe": "15m", + "model_family": "sklearn LogisticRegression", + "artifact_target": "joblib", + "parameters": { + "lookback": 100, + "horizon": 4, + "threshold": 0.0012, + "min_confidence": 0.48, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/gbpusd-logistic-15m/strategy.py b/templates/gbpusd-logistic-15m/strategy.py new file mode 100644 index 0000000..1d8eada --- /dev/null +++ b/templates/gbpusd-logistic-15m/strategy.py @@ -0,0 +1,73 @@ +import numpy as np +import pandas as pd +from sklearn.linear_model import LogisticRegression +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler + + +SYMBOL = "GBPUSD" +MODEL_NAME = "gbpusd-logistic-15m" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + rng = (df["high"] - df["low"]).replace(0, np.nan) + f = pd.DataFrame(index=df.index) + for n in [1, 3, 6, 12, 24]: + f[f"ret{n}"] = c.pct_change(n) + f["range_pct"] = rng / c + f["body_pct"] = (c - df["open"]) / (rng + 1e-9) + f["close_pos"] = (c - df["low"]) / (rng + 1e-9) + f["ema_8_21"] = (c.ewm(span=8, adjust=False).mean() - c.ewm(span=21, adjust=False).mean()) / c + f["ema_21_55"] = (c.ewm(span=21, adjust=False).mean() - c.ewm(span=55, adjust=False).mean()) / c + f["volatility"] = c.pct_change().rolling(24).std() + f["volume_ratio"] = df["volume"] / (df["volume"].rolling(24).mean() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 4)) + threshold = float(p.get("threshold", 0.0012)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", LogisticRegression(max_iter=1000, class_weight="balanced")), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 100)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.48)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/ger40-breakout-rf-30m/README.md b/templates/ger40-breakout-rf-30m/README.md new file mode 100644 index 0000000..efadffb --- /dev/null +++ b/templates/ger40-breakout-rf-30m/README.md @@ -0,0 +1,29 @@ +# ger40-breakout-rf-30m + +DAX breakout classifier for PyP Quant Mode. + +This is an educational starter template for `GER40` on the `30m` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `GER40` +- Timeframe: `30m` +- Family: `sklearn RandomForest` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/ger40-breakout-rf-30m/quant.config.json b/templates/ger40-breakout-rf-30m/quant.config.json new file mode 100644 index 0000000..c8da6f5 --- /dev/null +++ b/templates/ger40-breakout-rf-30m/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "ger40-breakout-rf-30m", + "description": "sklearn RandomForest PyP Quant template for GER40 30m.", + "symbol": "GER40", + "timeframe": "30m", + "model_family": "sklearn RandomForest", + "artifact_target": "joblib", + "parameters": { + "lookback": 120, + "horizon": 4, + "threshold": 0.0025, + "min_confidence": 0.5, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/ger40-breakout-rf-30m/strategy.py b/templates/ger40-breakout-rf-30m/strategy.py new file mode 100644 index 0000000..a210133 --- /dev/null +++ b/templates/ger40-breakout-rf-30m/strategy.py @@ -0,0 +1,69 @@ +import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier + + +SYMBOL = "GER40" +MODEL_NAME = "ger40-breakout-rf-30m" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + h = df["high"] + l = df["low"] + f = pd.DataFrame(index=df.index) + high_break = h.rolling(36).max().shift() + low_break = l.rolling(36).min().shift() + f["breakout_up"] = (c - high_break) / c + f["breakout_down"] = (c - low_break) / c + f["ret4"] = c.pct_change(4) + f["ret12"] = c.pct_change(12) + f["volatility"] = c.pct_change().rolling(24).std() + f["range_pct"] = (h - l) / c + f["ema_slope"] = c.ewm(span=12, adjust=False).mean().pct_change(6) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 4)) + threshold = float(p.get("threshold", 0.0025)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + clf = RandomForestClassifier(n_estimators=300, max_depth=7, min_samples_leaf=6, class_weight="balanced_subsample", random_state=42, n_jobs=-1) + clf.fit(x.values.astype(np.float32), y.values) + pred = clf.predict(x.values.astype(np.float32)) + return {"model": clf, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 120)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/lightgbm-fx-multifeature/quant.config.json b/templates/lightgbm-fx-multifeature/quant.config.json index d43ca67..64f228d 100644 --- a/templates/lightgbm-fx-multifeature/quant.config.json +++ b/templates/lightgbm-fx-multifeature/quant.config.json @@ -1 +1,30 @@ -{"pair":"EURUSD","timeframe":"30m","model_family":"lightgbm","runtime_target":"modal","artifact_format":"joblib_bundle","parameters":{"lookback":140,"horizon":4,"threshold":0.001,"min_confidence":0.48},"training_requirements":["numpy","pandas","scikit-learn","lightgbm","joblib"],"inference_requirements":["numpy","pandas","scikit-learn","lightgbm","joblib"]} +{ + "pair": "EURUSD", + "timeframe": "30m", + "model_family": "LightGBM", + "runtime_target": "modal", + "artifact_format": "joblib_bundle", + "parameters": { + "lookback": 140, + "horizon": 4, + "threshold": 0.001, + "min_confidence": 0.48 + }, + "training_requirements": [ + "numpy", + "pandas", + "scikit-learn", + "lightgbm", + "joblib" + ], + "inference_requirements": [ + "numpy", + "pandas", + "scikit-learn", + "lightgbm", + "joblib" + ], + "symbol": "EURUSD", + "description": "Multi-feature FX LightGBM classifier", + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/linkusdt-trend-logistic-30m/README.md b/templates/linkusdt-trend-logistic-30m/README.md new file mode 100644 index 0000000..1118cf8 --- /dev/null +++ b/templates/linkusdt-trend-logistic-30m/README.md @@ -0,0 +1,29 @@ +# linkusdt-trend-logistic-30m + +LINK trend classifier for PyP Quant Mode. + +This is an educational starter template for `LINKUSDT` on the `30m` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `LINKUSDT` +- Timeframe: `30m` +- Family: `sklearn LogisticRegression` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/linkusdt-trend-logistic-30m/quant.config.json b/templates/linkusdt-trend-logistic-30m/quant.config.json new file mode 100644 index 0000000..bb0e716 --- /dev/null +++ b/templates/linkusdt-trend-logistic-30m/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "linkusdt-trend-logistic-30m", + "description": "sklearn LogisticRegression PyP Quant template for LINKUSDT 30m.", + "symbol": "LINKUSDT", + "timeframe": "30m", + "model_family": "sklearn LogisticRegression", + "artifact_target": "joblib", + "parameters": { + "lookback": 120, + "horizon": 4, + "threshold": 0.003, + "min_confidence": 0.48, + "sl_percent": 0.4, + "tp_percent": 0.8 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/linkusdt-trend-logistic-30m/strategy.py b/templates/linkusdt-trend-logistic-30m/strategy.py new file mode 100644 index 0000000..29fde0e --- /dev/null +++ b/templates/linkusdt-trend-logistic-30m/strategy.py @@ -0,0 +1,73 @@ +import numpy as np +import pandas as pd +from sklearn.linear_model import LogisticRegression +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler + + +SYMBOL = "LINKUSDT" +MODEL_NAME = "linkusdt-trend-logistic-30m" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + rng = (df["high"] - df["low"]).replace(0, np.nan) + f = pd.DataFrame(index=df.index) + for n in [1, 3, 6, 12, 24]: + f[f"ret{n}"] = c.pct_change(n) + f["range_pct"] = rng / c + f["body_pct"] = (c - df["open"]) / (rng + 1e-9) + f["close_pos"] = (c - df["low"]) / (rng + 1e-9) + f["ema_8_21"] = (c.ewm(span=8, adjust=False).mean() - c.ewm(span=21, adjust=False).mean()) / c + f["ema_21_55"] = (c.ewm(span=21, adjust=False).mean() - c.ewm(span=55, adjust=False).mean()) / c + f["volatility"] = c.pct_change().rolling(24).std() + f["volume_ratio"] = df["volume"] / (df["volume"].rolling(24).mean() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 4)) + threshold = float(p.get("threshold", 0.003)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", LogisticRegression(max_iter=1000, class_weight="balanced")), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 120)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.48)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/ltcusdt-trend-rf-1h/README.md b/templates/ltcusdt-trend-rf-1h/README.md new file mode 100644 index 0000000..1192b68 --- /dev/null +++ b/templates/ltcusdt-trend-rf-1h/README.md @@ -0,0 +1,29 @@ +# ltcusdt-trend-rf-1h + +LTC trend classifier for PyP Quant Mode. + +This is an educational starter template for `LTCUSDT` on the `1h` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `LTCUSDT` +- Timeframe: `1h` +- Family: `sklearn RandomForest` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/ltcusdt-trend-rf-1h/quant.config.json b/templates/ltcusdt-trend-rf-1h/quant.config.json new file mode 100644 index 0000000..338ab0b --- /dev/null +++ b/templates/ltcusdt-trend-rf-1h/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "ltcusdt-trend-rf-1h", + "description": "sklearn RandomForest PyP Quant template for LTCUSDT 1h.", + "symbol": "LTCUSDT", + "timeframe": "1h", + "model_family": "sklearn RandomForest", + "artifact_target": "joblib", + "parameters": { + "lookback": 140, + "horizon": 6, + "threshold": 0.004, + "min_confidence": 0.5, + "sl_percent": 0.4, + "tp_percent": 0.8 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/ltcusdt-trend-rf-1h/strategy.py b/templates/ltcusdt-trend-rf-1h/strategy.py new file mode 100644 index 0000000..cae3004 --- /dev/null +++ b/templates/ltcusdt-trend-rf-1h/strategy.py @@ -0,0 +1,72 @@ +import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + + +SYMBOL = "LTCUSDT" +MODEL_NAME = "ltcusdt-trend-rf-1h" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + v = df["volume"] + f = pd.DataFrame(index=df.index) + for n in [1, 4, 8, 16, 32, 64]: + f[f"ret{n}"] = c.pct_change(n) + f["ema_12_48"] = (c.ewm(span=12, adjust=False).mean() - c.ewm(span=48, adjust=False).mean()) / c + f["ema_24_96"] = (c.ewm(span=24, adjust=False).mean() - c.ewm(span=96, adjust=False).mean()) / c + f["volatility_fast"] = c.pct_change().rolling(16).std() + f["volatility_slow"] = c.pct_change().rolling(64).std() + f["volatility_ratio"] = f["volatility_fast"] / (f["volatility_slow"] + 1e-9) + f["volume_z"] = (v - v.rolling(48).mean()) / (v.rolling(48).std() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 6)) + threshold = float(p.get("threshold", 0.004)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", RandomForestClassifier(n_estimators=350, max_depth=8, min_samples_leaf=8, class_weight="balanced_subsample", random_state=42, n_jobs=-1)), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 140)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/maticusdt-scalp-baseline-5m/README.md b/templates/maticusdt-scalp-baseline-5m/README.md new file mode 100644 index 0000000..cd50cee --- /dev/null +++ b/templates/maticusdt-scalp-baseline-5m/README.md @@ -0,0 +1,29 @@ +# maticusdt-scalp-baseline-5m + +MATIC scalp baseline for PyP Quant Mode. + +This is an educational starter template for `MATICUSDT` on the `5m` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `MATICUSDT` +- Timeframe: `5m` +- Family: `custom Python` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/maticusdt-scalp-baseline-5m/quant.config.json b/templates/maticusdt-scalp-baseline-5m/quant.config.json new file mode 100644 index 0000000..6596e5b --- /dev/null +++ b/templates/maticusdt-scalp-baseline-5m/quant.config.json @@ -0,0 +1,21 @@ +{ + "name": "maticusdt-scalp-baseline-5m", + "description": "custom Python PyP Quant template for MATICUSDT 5m.", + "symbol": "MATICUSDT", + "timeframe": "5m", + "model_family": "custom Python", + "artifact_target": "python", + "parameters": { + "lookback": 100, + "horizon": 4, + "threshold": 0.004, + "min_confidence": 0.5, + "sl_percent": 0.4, + "tp_percent": 0.8 + }, + "requirements": [ + "numpy", + "pandas" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/maticusdt-scalp-baseline-5m/strategy.py b/templates/maticusdt-scalp-baseline-5m/strategy.py new file mode 100644 index 0000000..8727d46 --- /dev/null +++ b/templates/maticusdt-scalp-baseline-5m/strategy.py @@ -0,0 +1,53 @@ +import numpy as np +import pandas as pd + + +SYMBOL = "MATICUSDT" +MODEL_NAME = "maticusdt-scalp-baseline-5m" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _atr(df, n=14): + h, l, c = df["high"], df["low"], df["close"] + tr = pd.concat([(h - l), (h - c.shift()).abs(), (l - c.shift()).abs()], axis=1).max(axis=1) + return tr.ewm(span=n, adjust=False).mean() + + +def train(data, config): + p = config.get("parameters", {}) + return {"atr_window": int(p.get("atr_window", 14)), "breakout_window": int(p.get("breakout_window", 36)), "atr_mult": float(p.get("atr_mult", 0.25)), "symbol": SYMBOL}, {"model_family": "rule_based_atr_breakout", "training_bars": int(len(data))} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + lookback = int(p.get("lookback", 100)) + if len(candles) < lookback: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + atr_window = int(model.get("atr_window", p.get("atr_window", 14))) + breakout_window = int(model.get("breakout_window", p.get("breakout_window", 36))) + atr_mult = float(model.get("atr_mult", p.get("atr_mult", 0.25))) + atr = _atr(df, atr_window) + close = float(df["close"].iloc[-1]) + upper = float(df["high"].rolling(breakout_window).max().shift().iloc[-1] + atr.iloc[-1] * atr_mult) + lower = float(df["low"].rolling(breakout_window).min().shift().iloc[-1] - atr.iloc[-1] * atr_mult) + signal = "HOLD" + distance = 0.0 + if close > upper: + signal = "UP" + distance = (close - upper) / max(atr.iloc[-1], 1e-9) + elif close < lower: + signal = "DOWN" + distance = (lower - close) / max(atr.iloc[-1], 1e-9) + confidence = min(max(distance, 0.0), 1.0) + return {"signal": signal, "confidence": round(float(confidence), 4), "metadata": {"upper": round(upper, 6), "lower": round(lower, 6), "close": round(close, 6), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/nas100-breakout-rf-15m/README.md b/templates/nas100-breakout-rf-15m/README.md new file mode 100644 index 0000000..8c39877 --- /dev/null +++ b/templates/nas100-breakout-rf-15m/README.md @@ -0,0 +1,29 @@ +# nas100-breakout-rf-15m + +NASDAQ index breakout classifier for PyP Quant Mode. + +This is an educational starter template for `NAS100` on the `15m` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `NAS100` +- Timeframe: `15m` +- Family: `sklearn RandomForest` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/nas100-breakout-rf-15m/quant.config.json b/templates/nas100-breakout-rf-15m/quant.config.json new file mode 100644 index 0000000..74b2744 --- /dev/null +++ b/templates/nas100-breakout-rf-15m/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "nas100-breakout-rf-15m", + "description": "sklearn RandomForest PyP Quant template for NAS100 15m.", + "symbol": "NAS100", + "timeframe": "15m", + "model_family": "sklearn RandomForest", + "artifact_target": "joblib", + "parameters": { + "lookback": 100, + "horizon": 4, + "threshold": 0.0025, + "min_confidence": 0.5, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/nas100-breakout-rf-15m/strategy.py b/templates/nas100-breakout-rf-15m/strategy.py new file mode 100644 index 0000000..c9839ec --- /dev/null +++ b/templates/nas100-breakout-rf-15m/strategy.py @@ -0,0 +1,69 @@ +import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier + + +SYMBOL = "NAS100" +MODEL_NAME = "nas100-breakout-rf-15m" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + h = df["high"] + l = df["low"] + f = pd.DataFrame(index=df.index) + high_break = h.rolling(36).max().shift() + low_break = l.rolling(36).min().shift() + f["breakout_up"] = (c - high_break) / c + f["breakout_down"] = (c - low_break) / c + f["ret4"] = c.pct_change(4) + f["ret12"] = c.pct_change(12) + f["volatility"] = c.pct_change().rolling(24).std() + f["range_pct"] = (h - l) / c + f["ema_slope"] = c.ewm(span=12, adjust=False).mean().pct_change(6) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 4)) + threshold = float(p.get("threshold", 0.0025)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + clf = RandomForestClassifier(n_estimators=300, max_depth=7, min_samples_leaf=6, class_weight="balanced_subsample", random_state=42, n_jobs=-1) + clf.fit(x.values.astype(np.float32), y.values) + pred = clf.predict(x.values.astype(np.float32)) + return {"model": clf, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 100)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/naturalgas-volatility-rf-4h/README.md b/templates/naturalgas-volatility-rf-4h/README.md new file mode 100644 index 0000000..eadaa0f --- /dev/null +++ b/templates/naturalgas-volatility-rf-4h/README.md @@ -0,0 +1,29 @@ +# naturalgas-volatility-rf-4h + +Natural gas volatility classifier for PyP Quant Mode. + +This is an educational starter template for `NATGAS` on the `4h` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `NATGAS` +- Timeframe: `4h` +- Family: `sklearn RandomForest` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/naturalgas-volatility-rf-4h/quant.config.json b/templates/naturalgas-volatility-rf-4h/quant.config.json new file mode 100644 index 0000000..216f957 --- /dev/null +++ b/templates/naturalgas-volatility-rf-4h/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "naturalgas-volatility-rf-4h", + "description": "sklearn RandomForest PyP Quant template for NATGAS 4h.", + "symbol": "NATGAS", + "timeframe": "4h", + "model_family": "sklearn RandomForest", + "artifact_target": "joblib", + "parameters": { + "lookback": 180, + "horizon": 6, + "threshold": 0.003, + "min_confidence": 0.5, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/naturalgas-volatility-rf-4h/strategy.py b/templates/naturalgas-volatility-rf-4h/strategy.py new file mode 100644 index 0000000..f60a1f9 --- /dev/null +++ b/templates/naturalgas-volatility-rf-4h/strategy.py @@ -0,0 +1,72 @@ +import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + + +SYMBOL = "NATGAS" +MODEL_NAME = "naturalgas-volatility-rf-4h" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + v = df["volume"] + f = pd.DataFrame(index=df.index) + for n in [1, 4, 8, 16, 32, 64]: + f[f"ret{n}"] = c.pct_change(n) + f["ema_12_48"] = (c.ewm(span=12, adjust=False).mean() - c.ewm(span=48, adjust=False).mean()) / c + f["ema_24_96"] = (c.ewm(span=24, adjust=False).mean() - c.ewm(span=96, adjust=False).mean()) / c + f["volatility_fast"] = c.pct_change().rolling(16).std() + f["volatility_slow"] = c.pct_change().rolling(64).std() + f["volatility_ratio"] = f["volatility_fast"] / (f["volatility_slow"] + 1e-9) + f["volume_z"] = (v - v.rolling(48).mean()) / (v.rolling(48).std() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 6)) + threshold = float(p.get("threshold", 0.003)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", RandomForestClassifier(n_estimators=350, max_depth=8, min_samples_leaf=8, class_weight="balanced_subsample", random_state=42, n_jobs=-1)), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 180)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/nzdusd-mean-reversion-1h/README.md b/templates/nzdusd-mean-reversion-1h/README.md new file mode 100644 index 0000000..b0c1453 --- /dev/null +++ b/templates/nzdusd-mean-reversion-1h/README.md @@ -0,0 +1,29 @@ +# nzdusd-mean-reversion-1h + +NZDUSD mean-reversion baseline for PyP Quant Mode. + +This is an educational starter template for `NZDUSD` on the `1h` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `NZDUSD` +- Timeframe: `1h` +- Family: `custom Python` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/nzdusd-mean-reversion-1h/quant.config.json b/templates/nzdusd-mean-reversion-1h/quant.config.json new file mode 100644 index 0000000..06ab63e --- /dev/null +++ b/templates/nzdusd-mean-reversion-1h/quant.config.json @@ -0,0 +1,21 @@ +{ + "name": "nzdusd-mean-reversion-1h", + "description": "custom Python PyP Quant template for NZDUSD 1h.", + "symbol": "NZDUSD", + "timeframe": "1h", + "model_family": "custom Python", + "artifact_target": "python", + "parameters": { + "lookback": 140, + "horizon": 6, + "threshold": 0.0012, + "min_confidence": 0.5, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/nzdusd-mean-reversion-1h/strategy.py b/templates/nzdusd-mean-reversion-1h/strategy.py new file mode 100644 index 0000000..0885ecf --- /dev/null +++ b/templates/nzdusd-mean-reversion-1h/strategy.py @@ -0,0 +1,46 @@ +import numpy as np +import pandas as pd + + +SYMBOL = "NZDUSD" +MODEL_NAME = "nzdusd-mean-reversion-1h" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _zscore(close, window): + mean = close.rolling(window).mean() + std = close.rolling(window).std() + return (close - mean) / (std + 1e-9) + + +def train(data, config): + p = config.get("parameters", {}) + return {"window": int(p.get("window", 48)), "entry_z": float(p.get("entry_z", 1.4)), "exit_z": float(p.get("exit_z", 0.3)), "symbol": SYMBOL}, {"model_family": "rule_based_mean_reversion", "training_bars": int(len(data))} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + lookback = int(p.get("lookback", 140)) + if len(candles) < lookback: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + window = int(model.get("window", p.get("window", 48))) + z = float(_zscore(df["close"], window).iloc[-1]) + entry_z = float(model.get("entry_z", p.get("entry_z", 1.4))) + signal = "HOLD" + if z <= -entry_z: + signal = "UP" + elif z >= entry_z: + signal = "DOWN" + confidence = min(abs(z) / max(entry_z, 1e-9), 1.0) + return {"signal": signal, "confidence": round(float(confidence), 4), "metadata": {"zscore": round(z, 4), "window": window, "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/oilwtico-atr-breakout-1h/README.md b/templates/oilwtico-atr-breakout-1h/README.md new file mode 100644 index 0000000..48a7e70 --- /dev/null +++ b/templates/oilwtico-atr-breakout-1h/README.md @@ -0,0 +1,29 @@ +# oilwtico-atr-breakout-1h + +WTI crude oil ATR breakout baseline for PyP Quant Mode. + +This is an educational starter template for `WTICOUSD` on the `1h` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `WTICOUSD` +- Timeframe: `1h` +- Family: `custom Python` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/oilwtico-atr-breakout-1h/quant.config.json b/templates/oilwtico-atr-breakout-1h/quant.config.json new file mode 100644 index 0000000..dd10303 --- /dev/null +++ b/templates/oilwtico-atr-breakout-1h/quant.config.json @@ -0,0 +1,21 @@ +{ + "name": "oilwtico-atr-breakout-1h", + "description": "custom Python PyP Quant template for WTICOUSD 1h.", + "symbol": "WTICOUSD", + "timeframe": "1h", + "model_family": "custom Python", + "artifact_target": "python", + "parameters": { + "lookback": 140, + "horizon": 6, + "threshold": 0.003, + "min_confidence": 0.5, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/oilwtico-atr-breakout-1h/strategy.py b/templates/oilwtico-atr-breakout-1h/strategy.py new file mode 100644 index 0000000..e2ded0a --- /dev/null +++ b/templates/oilwtico-atr-breakout-1h/strategy.py @@ -0,0 +1,53 @@ +import numpy as np +import pandas as pd + + +SYMBOL = "WTICOUSD" +MODEL_NAME = "oilwtico-atr-breakout-1h" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _atr(df, n=14): + h, l, c = df["high"], df["low"], df["close"] + tr = pd.concat([(h - l), (h - c.shift()).abs(), (l - c.shift()).abs()], axis=1).max(axis=1) + return tr.ewm(span=n, adjust=False).mean() + + +def train(data, config): + p = config.get("parameters", {}) + return {"atr_window": int(p.get("atr_window", 14)), "breakout_window": int(p.get("breakout_window", 36)), "atr_mult": float(p.get("atr_mult", 0.25)), "symbol": SYMBOL}, {"model_family": "rule_based_atr_breakout", "training_bars": int(len(data))} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + lookback = int(p.get("lookback", 140)) + if len(candles) < lookback: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + atr_window = int(model.get("atr_window", p.get("atr_window", 14))) + breakout_window = int(model.get("breakout_window", p.get("breakout_window", 36))) + atr_mult = float(model.get("atr_mult", p.get("atr_mult", 0.25))) + atr = _atr(df, atr_window) + close = float(df["close"].iloc[-1]) + upper = float(df["high"].rolling(breakout_window).max().shift().iloc[-1] + atr.iloc[-1] * atr_mult) + lower = float(df["low"].rolling(breakout_window).min().shift().iloc[-1] - atr.iloc[-1] * atr_mult) + signal = "HOLD" + distance = 0.0 + if close > upper: + signal = "UP" + distance = (close - upper) / max(atr.iloc[-1], 1e-9) + elif close < lower: + signal = "DOWN" + distance = (lower - close) / max(atr.iloc[-1], 1e-9) + confidence = min(max(distance, 0.0), 1.0) + return {"signal": signal, "confidence": round(float(confidence), 4), "metadata": {"upper": round(upper, 6), "lower": round(lower, 6), "close": round(close, 6), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/onnx-export-sklearn-starter/quant.config.json b/templates/onnx-export-sklearn-starter/quant.config.json index 8511f8c..2c1aec0 100644 --- a/templates/onnx-export-sklearn-starter/quant.config.json +++ b/templates/onnx-export-sklearn-starter/quant.config.json @@ -1 +1,28 @@ -{"pair":"EURUSD","timeframe":"1h","model_family":"sklearn","runtime_target":"container","artifact_format":"onnx","parameters":{"lookback":120,"horizon":3,"threshold":0.001,"min_confidence":0.5},"training_requirements":["numpy","pandas","scikit-learn","joblib","skl2onnx","onnx"],"inference_requirements":["numpy","onnxruntime"]} +{ + "pair": "EURUSD", + "timeframe": "1h", + "model_family": "sklearn to ONNX", + "runtime_target": "container", + "artifact_format": "onnx", + "parameters": { + "lookback": 120, + "horizon": 3, + "threshold": 0.001, + "min_confidence": 0.5 + }, + "training_requirements": [ + "numpy", + "pandas", + "scikit-learn", + "joblib", + "skl2onnx", + "onnx" + ], + "inference_requirements": [ + "numpy", + "onnxruntime" + ], + "symbol": "EURUSD", + "description": "Sklearn classifier starter for ONNX export", + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/solusdt-scalp-baseline/quant.config.json b/templates/solusdt-scalp-baseline/quant.config.json index fb036a8..f4dd861 100644 --- a/templates/solusdt-scalp-baseline/quant.config.json +++ b/templates/solusdt-scalp-baseline/quant.config.json @@ -1,7 +1,7 @@ { "pair": "SOLUSDT", "timeframe": "1m", - "model_family": "custom_python", + "model_family": "custom Python", "runtime_target": "edge", "artifact_format": "python_bundle", "parameters": { @@ -11,6 +11,15 @@ "vol_window": 20, "min_move": 0.0006 }, - "training_requirements": ["numpy", "pandas"], - "inference_requirements": ["numpy", "pandas"] + "training_requirements": [ + "numpy", + "pandas" + ], + "inference_requirements": [ + "numpy", + "pandas" + ], + "symbol": "SOLUSDT", + "description": "SOLUSDT high-volatility scalp baseline", + "disclaimer": "Educational template only. Not financial advice." } diff --git a/templates/spx500-trend-logistic-1h/README.md b/templates/spx500-trend-logistic-1h/README.md new file mode 100644 index 0000000..d7c4b56 --- /dev/null +++ b/templates/spx500-trend-logistic-1h/README.md @@ -0,0 +1,29 @@ +# spx500-trend-logistic-1h + +S&P 500 trend classifier for PyP Quant Mode. + +This is an educational starter template for `SPX500` on the `1h` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `SPX500` +- Timeframe: `1h` +- Family: `sklearn LogisticRegression` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/spx500-trend-logistic-1h/quant.config.json b/templates/spx500-trend-logistic-1h/quant.config.json new file mode 100644 index 0000000..a786253 --- /dev/null +++ b/templates/spx500-trend-logistic-1h/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "spx500-trend-logistic-1h", + "description": "sklearn LogisticRegression PyP Quant template for SPX500 1h.", + "symbol": "SPX500", + "timeframe": "1h", + "model_family": "sklearn LogisticRegression", + "artifact_target": "joblib", + "parameters": { + "lookback": 140, + "horizon": 6, + "threshold": 0.0025, + "min_confidence": 0.48, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/spx500-trend-logistic-1h/strategy.py b/templates/spx500-trend-logistic-1h/strategy.py new file mode 100644 index 0000000..6476e49 --- /dev/null +++ b/templates/spx500-trend-logistic-1h/strategy.py @@ -0,0 +1,73 @@ +import numpy as np +import pandas as pd +from sklearn.linear_model import LogisticRegression +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler + + +SYMBOL = "SPX500" +MODEL_NAME = "spx500-trend-logistic-1h" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + rng = (df["high"] - df["low"]).replace(0, np.nan) + f = pd.DataFrame(index=df.index) + for n in [1, 3, 6, 12, 24]: + f[f"ret{n}"] = c.pct_change(n) + f["range_pct"] = rng / c + f["body_pct"] = (c - df["open"]) / (rng + 1e-9) + f["close_pos"] = (c - df["low"]) / (rng + 1e-9) + f["ema_8_21"] = (c.ewm(span=8, adjust=False).mean() - c.ewm(span=21, adjust=False).mean()) / c + f["ema_21_55"] = (c.ewm(span=21, adjust=False).mean() - c.ewm(span=55, adjust=False).mean()) / c + f["volatility"] = c.pct_change().rolling(24).std() + f["volume_ratio"] = df["volume"] / (df["volume"].rolling(24).mean() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 4)) + threshold = float(p.get("threshold", 0.0025)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", LogisticRegression(max_iter=1000, class_weight="balanced")), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 140)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.48)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/statsmodels-arima-direction/quant.config.json b/templates/statsmodels-arima-direction/quant.config.json index 6e380d1..6538f75 100644 --- a/templates/statsmodels-arima-direction/quant.config.json +++ b/templates/statsmodels-arima-direction/quant.config.json @@ -1 +1,26 @@ -{"pair":"EURUSD","timeframe":"1h","model_family":"statsmodels","runtime_target":"modal","artifact_format":"joblib_bundle","parameters":{"lookback":96,"entry_threshold":0.0004},"training_requirements":["numpy","pandas","statsmodels","joblib"],"inference_requirements":["numpy","pandas","statsmodels","joblib"]} +{ + "pair": "EURUSD", + "timeframe": "1h", + "model_family": "statsmodels", + "runtime_target": "modal", + "artifact_format": "joblib_bundle", + "parameters": { + "lookback": 96, + "entry_threshold": 0.0004 + }, + "training_requirements": [ + "numpy", + "pandas", + "statsmodels", + "joblib" + ], + "inference_requirements": [ + "numpy", + "pandas", + "statsmodels", + "joblib" + ], + "symbol": "EURUSD", + "description": "ARIMA-style statistical direction baseline", + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/uk100-range-logistic-1h/README.md b/templates/uk100-range-logistic-1h/README.md new file mode 100644 index 0000000..1775970 --- /dev/null +++ b/templates/uk100-range-logistic-1h/README.md @@ -0,0 +1,29 @@ +# uk100-range-logistic-1h + +FTSE range classifier for PyP Quant Mode. + +This is an educational starter template for `UK100` on the `1h` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `UK100` +- Timeframe: `1h` +- Family: `sklearn LogisticRegression` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/uk100-range-logistic-1h/quant.config.json b/templates/uk100-range-logistic-1h/quant.config.json new file mode 100644 index 0000000..0e2a23b --- /dev/null +++ b/templates/uk100-range-logistic-1h/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "uk100-range-logistic-1h", + "description": "sklearn LogisticRegression PyP Quant template for UK100 1h.", + "symbol": "UK100", + "timeframe": "1h", + "model_family": "sklearn LogisticRegression", + "artifact_target": "joblib", + "parameters": { + "lookback": 140, + "horizon": 6, + "threshold": 0.0025, + "min_confidence": 0.48, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/uk100-range-logistic-1h/strategy.py b/templates/uk100-range-logistic-1h/strategy.py new file mode 100644 index 0000000..eb0ab4f --- /dev/null +++ b/templates/uk100-range-logistic-1h/strategy.py @@ -0,0 +1,73 @@ +import numpy as np +import pandas as pd +from sklearn.linear_model import LogisticRegression +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler + + +SYMBOL = "UK100" +MODEL_NAME = "uk100-range-logistic-1h" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + rng = (df["high"] - df["low"]).replace(0, np.nan) + f = pd.DataFrame(index=df.index) + for n in [1, 3, 6, 12, 24]: + f[f"ret{n}"] = c.pct_change(n) + f["range_pct"] = rng / c + f["body_pct"] = (c - df["open"]) / (rng + 1e-9) + f["close_pos"] = (c - df["low"]) / (rng + 1e-9) + f["ema_8_21"] = (c.ewm(span=8, adjust=False).mean() - c.ewm(span=21, adjust=False).mean()) / c + f["ema_21_55"] = (c.ewm(span=21, adjust=False).mean() - c.ewm(span=55, adjust=False).mean()) / c + f["volatility"] = c.pct_change().rolling(24).std() + f["volume_ratio"] = df["volume"] / (df["volume"].rolling(24).mean() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 4)) + threshold = float(p.get("threshold", 0.0025)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", LogisticRegression(max_iter=1000, class_weight="balanced")), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 140)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.48)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/us30-mean-reversion-30m/README.md b/templates/us30-mean-reversion-30m/README.md new file mode 100644 index 0000000..0234af7 --- /dev/null +++ b/templates/us30-mean-reversion-30m/README.md @@ -0,0 +1,29 @@ +# us30-mean-reversion-30m + +Dow index mean-reversion baseline for PyP Quant Mode. + +This is an educational starter template for `US30` on the `30m` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `US30` +- Timeframe: `30m` +- Family: `custom Python` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/us30-mean-reversion-30m/quant.config.json b/templates/us30-mean-reversion-30m/quant.config.json new file mode 100644 index 0000000..dccee57 --- /dev/null +++ b/templates/us30-mean-reversion-30m/quant.config.json @@ -0,0 +1,21 @@ +{ + "name": "us30-mean-reversion-30m", + "description": "custom Python PyP Quant template for US30 30m.", + "symbol": "US30", + "timeframe": "30m", + "model_family": "custom Python", + "artifact_target": "python", + "parameters": { + "lookback": 120, + "horizon": 4, + "threshold": 0.0025, + "min_confidence": 0.5, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/us30-mean-reversion-30m/strategy.py b/templates/us30-mean-reversion-30m/strategy.py new file mode 100644 index 0000000..bc97206 --- /dev/null +++ b/templates/us30-mean-reversion-30m/strategy.py @@ -0,0 +1,46 @@ +import numpy as np +import pandas as pd + + +SYMBOL = "US30" +MODEL_NAME = "us30-mean-reversion-30m" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _zscore(close, window): + mean = close.rolling(window).mean() + std = close.rolling(window).std() + return (close - mean) / (std + 1e-9) + + +def train(data, config): + p = config.get("parameters", {}) + return {"window": int(p.get("window", 48)), "entry_z": float(p.get("entry_z", 1.4)), "exit_z": float(p.get("exit_z", 0.3)), "symbol": SYMBOL}, {"model_family": "rule_based_mean_reversion", "training_bars": int(len(data))} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + lookback = int(p.get("lookback", 120)) + if len(candles) < lookback: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + window = int(model.get("window", p.get("window", 48))) + z = float(_zscore(df["close"], window).iloc[-1]) + entry_z = float(model.get("entry_z", p.get("entry_z", 1.4))) + signal = "HOLD" + if z <= -entry_z: + signal = "UP" + elif z >= entry_z: + signal = "DOWN" + confidence = min(abs(z) / max(entry_z, 1e-9), 1.0) + return {"signal": signal, "confidence": round(float(confidence), 4), "metadata": {"zscore": round(z, 4), "window": window, "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/usdcad-trend-rf-1h/README.md b/templates/usdcad-trend-rf-1h/README.md new file mode 100644 index 0000000..139d48a --- /dev/null +++ b/templates/usdcad-trend-rf-1h/README.md @@ -0,0 +1,29 @@ +# usdcad-trend-rf-1h + +USDCAD trend classifier for PyP Quant Mode. + +This is an educational starter template for `USDCAD` on the `1h` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `USDCAD` +- Timeframe: `1h` +- Family: `sklearn RandomForest` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/usdcad-trend-rf-1h/quant.config.json b/templates/usdcad-trend-rf-1h/quant.config.json new file mode 100644 index 0000000..3a13482 --- /dev/null +++ b/templates/usdcad-trend-rf-1h/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "usdcad-trend-rf-1h", + "description": "sklearn RandomForest PyP Quant template for USDCAD 1h.", + "symbol": "USDCAD", + "timeframe": "1h", + "model_family": "sklearn RandomForest", + "artifact_target": "joblib", + "parameters": { + "lookback": 140, + "horizon": 6, + "threshold": 0.0012, + "min_confidence": 0.5, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/usdcad-trend-rf-1h/strategy.py b/templates/usdcad-trend-rf-1h/strategy.py new file mode 100644 index 0000000..adb69e0 --- /dev/null +++ b/templates/usdcad-trend-rf-1h/strategy.py @@ -0,0 +1,72 @@ +import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + + +SYMBOL = "USDCAD" +MODEL_NAME = "usdcad-trend-rf-1h" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + v = df["volume"] + f = pd.DataFrame(index=df.index) + for n in [1, 4, 8, 16, 32, 64]: + f[f"ret{n}"] = c.pct_change(n) + f["ema_12_48"] = (c.ewm(span=12, adjust=False).mean() - c.ewm(span=48, adjust=False).mean()) / c + f["ema_24_96"] = (c.ewm(span=24, adjust=False).mean() - c.ewm(span=96, adjust=False).mean()) / c + f["volatility_fast"] = c.pct_change().rolling(16).std() + f["volatility_slow"] = c.pct_change().rolling(64).std() + f["volatility_ratio"] = f["volatility_fast"] / (f["volatility_slow"] + 1e-9) + f["volume_z"] = (v - v.rolling(48).mean()) / (v.rolling(48).std() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 6)) + threshold = float(p.get("threshold", 0.0012)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", RandomForestClassifier(n_estimators=350, max_depth=8, min_samples_leaf=8, class_weight="balanced_subsample", random_state=42, n_jobs=-1)), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 140)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/usdchf-range-logistic-1h/README.md b/templates/usdchf-range-logistic-1h/README.md new file mode 100644 index 0000000..64de572 --- /dev/null +++ b/templates/usdchf-range-logistic-1h/README.md @@ -0,0 +1,29 @@ +# usdchf-range-logistic-1h + +USDCHF range classifier for PyP Quant Mode. + +This is an educational starter template for `USDCHF` on the `1h` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `USDCHF` +- Timeframe: `1h` +- Family: `sklearn LogisticRegression` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/usdchf-range-logistic-1h/quant.config.json b/templates/usdchf-range-logistic-1h/quant.config.json new file mode 100644 index 0000000..6e22b84 --- /dev/null +++ b/templates/usdchf-range-logistic-1h/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "usdchf-range-logistic-1h", + "description": "sklearn LogisticRegression PyP Quant template for USDCHF 1h.", + "symbol": "USDCHF", + "timeframe": "1h", + "model_family": "sklearn LogisticRegression", + "artifact_target": "joblib", + "parameters": { + "lookback": 140, + "horizon": 6, + "threshold": 0.0012, + "min_confidence": 0.48, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/usdchf-range-logistic-1h/strategy.py b/templates/usdchf-range-logistic-1h/strategy.py new file mode 100644 index 0000000..27dc994 --- /dev/null +++ b/templates/usdchf-range-logistic-1h/strategy.py @@ -0,0 +1,73 @@ +import numpy as np +import pandas as pd +from sklearn.linear_model import LogisticRegression +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler + + +SYMBOL = "USDCHF" +MODEL_NAME = "usdchf-range-logistic-1h" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + rng = (df["high"] - df["low"]).replace(0, np.nan) + f = pd.DataFrame(index=df.index) + for n in [1, 3, 6, 12, 24]: + f[f"ret{n}"] = c.pct_change(n) + f["range_pct"] = rng / c + f["body_pct"] = (c - df["open"]) / (rng + 1e-9) + f["close_pos"] = (c - df["low"]) / (rng + 1e-9) + f["ema_8_21"] = (c.ewm(span=8, adjust=False).mean() - c.ewm(span=21, adjust=False).mean()) / c + f["ema_21_55"] = (c.ewm(span=21, adjust=False).mean() - c.ewm(span=55, adjust=False).mean()) / c + f["volatility"] = c.pct_change().rolling(24).std() + f["volume_ratio"] = df["volume"] / (df["volume"].rolling(24).mean() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 4)) + threshold = float(p.get("threshold", 0.0012)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", LogisticRegression(max_iter=1000, class_weight="balanced")), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 140)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.48)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/usdjpy-mean-reversion/quant.config.json b/templates/usdjpy-mean-reversion/quant.config.json index 755eea5..b74334f 100644 --- a/templates/usdjpy-mean-reversion/quant.config.json +++ b/templates/usdjpy-mean-reversion/quant.config.json @@ -1 +1,23 @@ -{"pair":"USDJPY","timeframe":"1h","model_family":"custom_python","runtime_target":"edge","artifact_format":"python_bundle","parameters":{"lookback":100,"z_window":40,"entry_z":1.4},"training_requirements":["numpy","pandas"],"inference_requirements":["numpy","pandas"]} +{ + "pair": "USDJPY", + "timeframe": "1h", + "model_family": "custom Python", + "runtime_target": "edge", + "artifact_format": "python_bundle", + "parameters": { + "lookback": 100, + "z_window": 40, + "entry_z": 1.4 + }, + "training_requirements": [ + "numpy", + "pandas" + ], + "inference_requirements": [ + "numpy", + "pandas" + ], + "symbol": "USDJPY", + "description": "USDJPY mean-reversion baseline", + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/usdmxn-volatility-rf-4h/README.md b/templates/usdmxn-volatility-rf-4h/README.md new file mode 100644 index 0000000..c5853d0 --- /dev/null +++ b/templates/usdmxn-volatility-rf-4h/README.md @@ -0,0 +1,29 @@ +# usdmxn-volatility-rf-4h + +USDMXN volatility classifier for PyP Quant Mode. + +This is an educational starter template for `USDMXN` on the `4h` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `USDMXN` +- Timeframe: `4h` +- Family: `sklearn RandomForest` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/usdmxn-volatility-rf-4h/quant.config.json b/templates/usdmxn-volatility-rf-4h/quant.config.json new file mode 100644 index 0000000..165254a --- /dev/null +++ b/templates/usdmxn-volatility-rf-4h/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "usdmxn-volatility-rf-4h", + "description": "sklearn RandomForest PyP Quant template for USDMXN 4h.", + "symbol": "USDMXN", + "timeframe": "4h", + "model_family": "sklearn RandomForest", + "artifact_target": "joblib", + "parameters": { + "lookback": 180, + "horizon": 6, + "threshold": 0.003, + "min_confidence": 0.5, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/usdmxn-volatility-rf-4h/strategy.py b/templates/usdmxn-volatility-rf-4h/strategy.py new file mode 100644 index 0000000..742cda8 --- /dev/null +++ b/templates/usdmxn-volatility-rf-4h/strategy.py @@ -0,0 +1,72 @@ +import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + + +SYMBOL = "USDMXN" +MODEL_NAME = "usdmxn-volatility-rf-4h" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + v = df["volume"] + f = pd.DataFrame(index=df.index) + for n in [1, 4, 8, 16, 32, 64]: + f[f"ret{n}"] = c.pct_change(n) + f["ema_12_48"] = (c.ewm(span=12, adjust=False).mean() - c.ewm(span=48, adjust=False).mean()) / c + f["ema_24_96"] = (c.ewm(span=24, adjust=False).mean() - c.ewm(span=96, adjust=False).mean()) / c + f["volatility_fast"] = c.pct_change().rolling(16).std() + f["volatility_slow"] = c.pct_change().rolling(64).std() + f["volatility_ratio"] = f["volatility_fast"] / (f["volatility_slow"] + 1e-9) + f["volume_z"] = (v - v.rolling(48).mean()) / (v.rolling(48).std() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 6)) + threshold = float(p.get("threshold", 0.003)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", RandomForestClassifier(n_estimators=350, max_depth=8, min_samples_leaf=8, class_weight="balanced_subsample", random_state=42, n_jobs=-1)), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 180)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/xagusd-atr-breakout-1h/README.md b/templates/xagusd-atr-breakout-1h/README.md new file mode 100644 index 0000000..ccaef1f --- /dev/null +++ b/templates/xagusd-atr-breakout-1h/README.md @@ -0,0 +1,29 @@ +# xagusd-atr-breakout-1h + +Silver ATR breakout baseline for PyP Quant Mode. + +This is an educational starter template for `XAGUSD` on the `1h` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `XAGUSD` +- Timeframe: `1h` +- Family: `custom Python` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/xagusd-atr-breakout-1h/quant.config.json b/templates/xagusd-atr-breakout-1h/quant.config.json new file mode 100644 index 0000000..2bfcbe5 --- /dev/null +++ b/templates/xagusd-atr-breakout-1h/quant.config.json @@ -0,0 +1,21 @@ +{ + "name": "xagusd-atr-breakout-1h", + "description": "custom Python PyP Quant template for XAGUSD 1h.", + "symbol": "XAGUSD", + "timeframe": "1h", + "model_family": "custom Python", + "artifact_target": "python", + "parameters": { + "lookback": 140, + "horizon": 6, + "threshold": 0.003, + "min_confidence": 0.5, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/xagusd-atr-breakout-1h/strategy.py b/templates/xagusd-atr-breakout-1h/strategy.py new file mode 100644 index 0000000..7d1a097 --- /dev/null +++ b/templates/xagusd-atr-breakout-1h/strategy.py @@ -0,0 +1,53 @@ +import numpy as np +import pandas as pd + + +SYMBOL = "XAGUSD" +MODEL_NAME = "xagusd-atr-breakout-1h" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _atr(df, n=14): + h, l, c = df["high"], df["low"], df["close"] + tr = pd.concat([(h - l), (h - c.shift()).abs(), (l - c.shift()).abs()], axis=1).max(axis=1) + return tr.ewm(span=n, adjust=False).mean() + + +def train(data, config): + p = config.get("parameters", {}) + return {"atr_window": int(p.get("atr_window", 14)), "breakout_window": int(p.get("breakout_window", 36)), "atr_mult": float(p.get("atr_mult", 0.25)), "symbol": SYMBOL}, {"model_family": "rule_based_atr_breakout", "training_bars": int(len(data))} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + lookback = int(p.get("lookback", 140)) + if len(candles) < lookback: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + atr_window = int(model.get("atr_window", p.get("atr_window", 14))) + breakout_window = int(model.get("breakout_window", p.get("breakout_window", 36))) + atr_mult = float(model.get("atr_mult", p.get("atr_mult", 0.25))) + atr = _atr(df, atr_window) + close = float(df["close"].iloc[-1]) + upper = float(df["high"].rolling(breakout_window).max().shift().iloc[-1] + atr.iloc[-1] * atr_mult) + lower = float(df["low"].rolling(breakout_window).min().shift().iloc[-1] - atr.iloc[-1] * atr_mult) + signal = "HOLD" + distance = 0.0 + if close > upper: + signal = "UP" + distance = (close - upper) / max(atr.iloc[-1], 1e-9) + elif close < lower: + signal = "DOWN" + distance = (lower - close) / max(atr.iloc[-1], 1e-9) + confidence = min(max(distance, 0.0), 1.0) + return {"signal": signal, "confidence": round(float(confidence), 4), "metadata": {"upper": round(upper, 6), "lower": round(lower, 6), "close": round(close, 6), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/xagusd-mean-reversion-30m/README.md b/templates/xagusd-mean-reversion-30m/README.md new file mode 100644 index 0000000..f18ca4e --- /dev/null +++ b/templates/xagusd-mean-reversion-30m/README.md @@ -0,0 +1,29 @@ +# xagusd-mean-reversion-30m + +Silver mean-reversion baseline for PyP Quant Mode. + +This is an educational starter template for `XAGUSD` on the `30m` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `XAGUSD` +- Timeframe: `30m` +- Family: `custom Python` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/xagusd-mean-reversion-30m/quant.config.json b/templates/xagusd-mean-reversion-30m/quant.config.json new file mode 100644 index 0000000..78b195d --- /dev/null +++ b/templates/xagusd-mean-reversion-30m/quant.config.json @@ -0,0 +1,21 @@ +{ + "name": "xagusd-mean-reversion-30m", + "description": "custom Python PyP Quant template for XAGUSD 30m.", + "symbol": "XAGUSD", + "timeframe": "30m", + "model_family": "custom Python", + "artifact_target": "python", + "parameters": { + "lookback": 120, + "horizon": 4, + "threshold": 0.003, + "min_confidence": 0.5, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/xagusd-mean-reversion-30m/strategy.py b/templates/xagusd-mean-reversion-30m/strategy.py new file mode 100644 index 0000000..112b44a --- /dev/null +++ b/templates/xagusd-mean-reversion-30m/strategy.py @@ -0,0 +1,46 @@ +import numpy as np +import pandas as pd + + +SYMBOL = "XAGUSD" +MODEL_NAME = "xagusd-mean-reversion-30m" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _zscore(close, window): + mean = close.rolling(window).mean() + std = close.rolling(window).std() + return (close - mean) / (std + 1e-9) + + +def train(data, config): + p = config.get("parameters", {}) + return {"window": int(p.get("window", 48)), "entry_z": float(p.get("entry_z", 1.4)), "exit_z": float(p.get("exit_z", 0.3)), "symbol": SYMBOL}, {"model_family": "rule_based_mean_reversion", "training_bars": int(len(data))} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + lookback = int(p.get("lookback", 120)) + if len(candles) < lookback: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + window = int(model.get("window", p.get("window", 48))) + z = float(_zscore(df["close"], window).iloc[-1]) + entry_z = float(model.get("entry_z", p.get("entry_z", 1.4))) + signal = "HOLD" + if z <= -entry_z: + signal = "UP" + elif z >= entry_z: + signal = "DOWN" + confidence = min(abs(z) / max(entry_z, 1e-9), 1.0) + return {"signal": signal, "confidence": round(float(confidence), 4), "metadata": {"zscore": round(z, 4), "window": window, "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/xauusd-atr-breakout/quant.config.json b/templates/xauusd-atr-breakout/quant.config.json index 830ec40..23dc25a 100644 --- a/templates/xauusd-atr-breakout/quant.config.json +++ b/templates/xauusd-atr-breakout/quant.config.json @@ -1,7 +1,7 @@ { "pair": "XAUUSD", "timeframe": "15m", - "model_family": "custom_python", + "model_family": "custom Python", "runtime_target": "edge", "artifact_format": "python_bundle", "parameters": { @@ -10,6 +10,15 @@ "breakout_window": 24, "atr_mult": 0.25 }, - "training_requirements": ["numpy", "pandas"], - "inference_requirements": ["numpy", "pandas"] + "training_requirements": [ + "numpy", + "pandas" + ], + "inference_requirements": [ + "numpy", + "pandas" + ], + "symbol": "XAUUSD", + "description": "XAUUSD ATR breakout rule baseline", + "disclaimer": "Educational template only. Not financial advice." } diff --git a/templates/xauusd-london-breakout-15m/README.md b/templates/xauusd-london-breakout-15m/README.md new file mode 100644 index 0000000..391e82b --- /dev/null +++ b/templates/xauusd-london-breakout-15m/README.md @@ -0,0 +1,29 @@ +# xauusd-london-breakout-15m + +Gold London breakout baseline for PyP Quant Mode. + +This is an educational starter template for `XAUUSD` on the `15m` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `XAUUSD` +- Timeframe: `15m` +- Family: `custom Python` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/xauusd-london-breakout-15m/quant.config.json b/templates/xauusd-london-breakout-15m/quant.config.json new file mode 100644 index 0000000..91c9334 --- /dev/null +++ b/templates/xauusd-london-breakout-15m/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "xauusd-london-breakout-15m", + "description": "custom Python PyP Quant template for XAUUSD 15m.", + "symbol": "XAUUSD", + "timeframe": "15m", + "model_family": "custom Python", + "artifact_target": "joblib", + "parameters": { + "lookback": 100, + "horizon": 4, + "threshold": 0.003, + "min_confidence": 0.5, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/xauusd-london-breakout-15m/strategy.py b/templates/xauusd-london-breakout-15m/strategy.py new file mode 100644 index 0000000..a618045 --- /dev/null +++ b/templates/xauusd-london-breakout-15m/strategy.py @@ -0,0 +1,69 @@ +import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier + + +SYMBOL = "XAUUSD" +MODEL_NAME = "xauusd-london-breakout-15m" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + h = df["high"] + l = df["low"] + f = pd.DataFrame(index=df.index) + high_break = h.rolling(36).max().shift() + low_break = l.rolling(36).min().shift() + f["breakout_up"] = (c - high_break) / c + f["breakout_down"] = (c - low_break) / c + f["ret4"] = c.pct_change(4) + f["ret12"] = c.pct_change(12) + f["volatility"] = c.pct_change().rolling(24).std() + f["range_pct"] = (h - l) / c + f["ema_slope"] = c.ewm(span=12, adjust=False).mean().pct_change(6) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 4)) + threshold = float(p.get("threshold", 0.003)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + clf = RandomForestClassifier(n_estimators=300, max_depth=7, min_samples_leaf=6, class_weight="balanced_subsample", random_state=42, n_jobs=-1) + clf.fit(x.values.astype(np.float32), y.values) + pred = clf.predict(x.values.astype(np.float32)) + return {"model": clf, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 100)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/xauusd-regime-xgboost-v11/quant.config.json b/templates/xauusd-regime-xgboost-v11/quant.config.json index 0fdcc3c..d178cf6 100644 --- a/templates/xauusd-regime-xgboost-v11/quant.config.json +++ b/templates/xauusd-regime-xgboost-v11/quant.config.json @@ -1,7 +1,7 @@ { "pair": "XAUUSD", "timeframe": "1h", - "model_family": "xgboost", + "model_family": "XGBoost", "runtime_target": "modal", "artifact_format": "joblib_bundle", "parameters": { @@ -12,6 +12,21 @@ "min_direction_prob": 0.38, "min_edge": 0.03 }, - "training_requirements": ["numpy", "pandas", "scikit-learn", "xgboost", "joblib"], - "inference_requirements": ["numpy", "pandas", "scikit-learn", "xgboost", "joblib"] + "training_requirements": [ + "numpy", + "pandas", + "scikit-learn", + "xgboost", + "joblib" + ], + "inference_requirements": [ + "numpy", + "pandas", + "scikit-learn", + "xgboost", + "joblib" + ], + "symbol": "XAUUSD", + "description": "Less restrictive Au-79-style XAUUSD regime classifier", + "disclaimer": "Educational template only. Not financial advice." } diff --git a/templates/xauusd-scalp-logistic-5m/README.md b/templates/xauusd-scalp-logistic-5m/README.md new file mode 100644 index 0000000..55d0a4f --- /dev/null +++ b/templates/xauusd-scalp-logistic-5m/README.md @@ -0,0 +1,29 @@ +# xauusd-scalp-logistic-5m + +Gold scalp classifier for PyP Quant Mode. + +This is an educational starter template for `XAUUSD` on the `5m` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `XAUUSD` +- Timeframe: `5m` +- Family: `sklearn LogisticRegression` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/xauusd-scalp-logistic-5m/quant.config.json b/templates/xauusd-scalp-logistic-5m/quant.config.json new file mode 100644 index 0000000..83477e8 --- /dev/null +++ b/templates/xauusd-scalp-logistic-5m/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "xauusd-scalp-logistic-5m", + "description": "sklearn LogisticRegression PyP Quant template for XAUUSD 5m.", + "symbol": "XAUUSD", + "timeframe": "5m", + "model_family": "sklearn LogisticRegression", + "artifact_target": "joblib", + "parameters": { + "lookback": 100, + "horizon": 4, + "threshold": 0.003, + "min_confidence": 0.48, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/xauusd-scalp-logistic-5m/strategy.py b/templates/xauusd-scalp-logistic-5m/strategy.py new file mode 100644 index 0000000..c23d4a1 --- /dev/null +++ b/templates/xauusd-scalp-logistic-5m/strategy.py @@ -0,0 +1,73 @@ +import numpy as np +import pandas as pd +from sklearn.linear_model import LogisticRegression +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler + + +SYMBOL = "XAUUSD" +MODEL_NAME = "xauusd-scalp-logistic-5m" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + rng = (df["high"] - df["low"]).replace(0, np.nan) + f = pd.DataFrame(index=df.index) + for n in [1, 3, 6, 12, 24]: + f[f"ret{n}"] = c.pct_change(n) + f["range_pct"] = rng / c + f["body_pct"] = (c - df["open"]) / (rng + 1e-9) + f["close_pos"] = (c - df["low"]) / (rng + 1e-9) + f["ema_8_21"] = (c.ewm(span=8, adjust=False).mean() - c.ewm(span=21, adjust=False).mean()) / c + f["ema_21_55"] = (c.ewm(span=21, adjust=False).mean() - c.ewm(span=55, adjust=False).mean()) / c + f["volatility"] = c.pct_change().rolling(24).std() + f["volume_ratio"] = df["volume"] / (df["volume"].rolling(24).mean() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 4)) + threshold = float(p.get("threshold", 0.003)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", LogisticRegression(max_iter=1000, class_weight="balanced")), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 100)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.48)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/xptusd-trend-rf-1h/README.md b/templates/xptusd-trend-rf-1h/README.md new file mode 100644 index 0000000..d5df05f --- /dev/null +++ b/templates/xptusd-trend-rf-1h/README.md @@ -0,0 +1,29 @@ +# xptusd-trend-rf-1h + +Platinum trend classifier for PyP Quant Mode. + +This is an educational starter template for `XPTUSD` on the `1h` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `XPTUSD` +- Timeframe: `1h` +- Family: `sklearn RandomForest` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/xptusd-trend-rf-1h/quant.config.json b/templates/xptusd-trend-rf-1h/quant.config.json new file mode 100644 index 0000000..a5e8277 --- /dev/null +++ b/templates/xptusd-trend-rf-1h/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "xptusd-trend-rf-1h", + "description": "sklearn RandomForest PyP Quant template for XPTUSD 1h.", + "symbol": "XPTUSD", + "timeframe": "1h", + "model_family": "sklearn RandomForest", + "artifact_target": "joblib", + "parameters": { + "lookback": 140, + "horizon": 6, + "threshold": 0.003, + "min_confidence": 0.5, + "sl_percent": 0.25, + "tp_percent": 0.5 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/xptusd-trend-rf-1h/strategy.py b/templates/xptusd-trend-rf-1h/strategy.py new file mode 100644 index 0000000..e70ae76 --- /dev/null +++ b/templates/xptusd-trend-rf-1h/strategy.py @@ -0,0 +1,72 @@ +import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + + +SYMBOL = "XPTUSD" +MODEL_NAME = "xptusd-trend-rf-1h" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + v = df["volume"] + f = pd.DataFrame(index=df.index) + for n in [1, 4, 8, 16, 32, 64]: + f[f"ret{n}"] = c.pct_change(n) + f["ema_12_48"] = (c.ewm(span=12, adjust=False).mean() - c.ewm(span=48, adjust=False).mean()) / c + f["ema_24_96"] = (c.ewm(span=24, adjust=False).mean() - c.ewm(span=96, adjust=False).mean()) / c + f["volatility_fast"] = c.pct_change().rolling(16).std() + f["volatility_slow"] = c.pct_change().rolling(64).std() + f["volatility_ratio"] = f["volatility_fast"] / (f["volatility_slow"] + 1e-9) + f["volume_z"] = (v - v.rolling(48).mean()) / (v.rolling(48).std() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 6)) + threshold = float(p.get("threshold", 0.003)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", RandomForestClassifier(n_estimators=350, max_depth=8, min_samples_leaf=8, class_weight="balanced_subsample", random_state=42, n_jobs=-1)), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {"model": model, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 140)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/templates/xrpusdt-breakout-rf-15m/README.md b/templates/xrpusdt-breakout-rf-15m/README.md new file mode 100644 index 0000000..787ec35 --- /dev/null +++ b/templates/xrpusdt-breakout-rf-15m/README.md @@ -0,0 +1,29 @@ +# xrpusdt-breakout-rf-15m + +XRP breakout classifier for PyP Quant Mode. + +This is an educational starter template for `XRPUSDT` on the `15m` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `XRPUSDT` +- Timeframe: `15m` +- Family: `sklearn RandomForest` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. diff --git a/templates/xrpusdt-breakout-rf-15m/quant.config.json b/templates/xrpusdt-breakout-rf-15m/quant.config.json new file mode 100644 index 0000000..141d588 --- /dev/null +++ b/templates/xrpusdt-breakout-rf-15m/quant.config.json @@ -0,0 +1,22 @@ +{ + "name": "xrpusdt-breakout-rf-15m", + "description": "sklearn RandomForest PyP Quant template for XRPUSDT 15m.", + "symbol": "XRPUSDT", + "timeframe": "15m", + "model_family": "sklearn RandomForest", + "artifact_target": "joblib", + "parameters": { + "lookback": 100, + "horizon": 4, + "threshold": 0.004, + "min_confidence": 0.5, + "sl_percent": 0.4, + "tp_percent": 0.8 + }, + "requirements": [ + "numpy", + "pandas", + "scikit-learn" + ], + "disclaimer": "Educational template only. Not financial advice." +} diff --git a/templates/xrpusdt-breakout-rf-15m/strategy.py b/templates/xrpusdt-breakout-rf-15m/strategy.py new file mode 100644 index 0000000..12efdd1 --- /dev/null +++ b/templates/xrpusdt-breakout-rf-15m/strategy.py @@ -0,0 +1,69 @@ +import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier + + +SYMBOL = "XRPUSDT" +MODEL_NAME = "xrpusdt-breakout-rf-15m" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + h = df["high"] + l = df["low"] + f = pd.DataFrame(index=df.index) + high_break = h.rolling(36).max().shift() + low_break = l.rolling(36).min().shift() + f["breakout_up"] = (c - high_break) / c + f["breakout_down"] = (c - low_break) / c + f["ret4"] = c.pct_change(4) + f["ret12"] = c.pct_change(12) + f["volatility"] = c.pct_change().rolling(24).std() + f["range_pct"] = (h - l) / c + f["ema_slope"] = c.ewm(span=12, adjust=False).mean().pct_change(6) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 4)) + threshold = float(p.get("threshold", 0.004)) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + clf = RandomForestClassifier(n_estimators=300, max_depth=7, min_samples_leaf=6, class_weight="balanced_subsample", random_state=42, n_jobs=-1) + clf.fit(x.values.astype(np.float32), y.values) + pred = clf.predict(x.values.astype(np.float32)) + return {"model": clf, "features": list(x.columns), "symbol": SYMBOL}, {"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())} + + +def predict(model, market_data, config): + p = config.get("parameters", {}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", 100)): + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles", "model": MODEL_NAME}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "no_features", "model": MODEL_NAME}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {0: "DOWN", 1: "HOLD", 2: "UP"}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {"signal": signal, "confidence": round(conf, 4), "metadata": {"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}} diff --git a/tools/generate_template_batch.py b/tools/generate_template_batch.py new file mode 100644 index 0000000..df0bc9e --- /dev/null +++ b/tools/generate_template_batch.py @@ -0,0 +1,480 @@ +from __future__ import annotations + +import json +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +TEMPLATE_ROOT = ROOT / "templates" + + +SPECS = [ + ("audusd-logistic-30m", "AUDUSD", "30m", "sklearn LogisticRegression", "AUDUSD momentum classifier", "logistic"), + ("audjpy-randomforest-1h", "AUDJPY", "1h", "sklearn RandomForest", "AUDJPY carry-risk classifier", "forest"), + ("cadjpy-breakout-1h", "CADJPY", "1h", "sklearn RandomForest", "CADJPY breakout classifier", "breakout"), + ("chfjpy-mean-reversion-1h", "CHFJPY", "1h", "custom Python", "CHFJPY mean-reversion baseline", "mean_reversion"), + ("eurgbp-range-classifier-30m", "EURGBP", "30m", "sklearn LogisticRegression", "EURGBP range classifier", "logistic"), + ("eurjpy-trend-rf-1h", "EURJPY", "1h", "sklearn RandomForest", "EURJPY trend classifier", "forest"), + ("eurnzd-volatility-rf-4h", "EURNZD", "4h", "sklearn RandomForest", "EURNZD volatility classifier", "forest"), + ("gbpjpy-breakout-rf-1h", "GBPJPY", "1h", "sklearn RandomForest", "GBPJPY breakout classifier", "breakout"), + ("gbpusd-logistic-15m", "GBPUSD", "15m", "sklearn LogisticRegression", "GBPUSD directional baseline", "logistic"), + ("nzdusd-mean-reversion-1h", "NZDUSD", "1h", "custom Python", "NZDUSD mean-reversion baseline", "mean_reversion"), + ("usdcad-trend-rf-1h", "USDCAD", "1h", "sklearn RandomForest", "USDCAD trend classifier", "forest"), + ("usdchf-range-logistic-1h", "USDCHF", "1h", "sklearn LogisticRegression", "USDCHF range classifier", "logistic"), + ("usdmxn-volatility-rf-4h", "USDMXN", "4h", "sklearn RandomForest", "USDMXN volatility classifier", "forest"), + ("xagusd-atr-breakout-1h", "XAGUSD", "1h", "custom Python", "Silver ATR breakout baseline", "atr_breakout"), + ("xagusd-mean-reversion-30m", "XAGUSD", "30m", "custom Python", "Silver mean-reversion baseline", "mean_reversion"), + ("xauusd-london-breakout-15m", "XAUUSD", "15m", "custom Python", "Gold London breakout baseline", "breakout"), + ("xauusd-scalp-logistic-5m", "XAUUSD", "5m", "sklearn LogisticRegression", "Gold scalp classifier", "logistic"), + ("xptusd-trend-rf-1h", "XPTUSD", "1h", "sklearn RandomForest", "Platinum trend classifier", "forest"), + ("btcusdt-breakout-rf-15m", "BTCUSDT", "15m", "sklearn RandomForest", "BTC breakout classifier", "breakout"), + ("btcusdt-mean-reversion-5m", "BTCUSDT", "5m", "custom Python", "BTC mean-reversion baseline", "mean_reversion"), + ("bnbusdt-trend-rf-30m", "BNBUSDT", "30m", "sklearn RandomForest", "BNB trend classifier", "forest"), + ("dogeusdt-scalp-logistic-1m", "DOGEUSDT", "1m", "sklearn LogisticRegression", "DOGE high-volatility scalp classifier", "logistic"), + ("adausdt-volatility-rf-15m", "ADAUSDT", "15m", "sklearn RandomForest", "ADA volatility classifier", "forest"), + ("xrpusdt-breakout-rf-15m", "XRPUSDT", "15m", "sklearn RandomForest", "XRP breakout classifier", "breakout"), + ("linkusdt-trend-logistic-30m", "LINKUSDT", "30m", "sklearn LogisticRegression", "LINK trend classifier", "logistic"), + ("avaxusdt-volatility-rf-30m", "AVAXUSDT", "30m", "sklearn RandomForest", "AVAX volatility classifier", "forest"), + ("maticusdt-scalp-baseline-5m", "MATICUSDT", "5m", "custom Python", "MATIC scalp baseline", "atr_breakout"), + ("dotusdt-mean-reversion-30m", "DOTUSDT", "30m", "custom Python", "DOT mean-reversion baseline", "mean_reversion"), + ("ltcusdt-trend-rf-1h", "LTCUSDT", "1h", "sklearn RandomForest", "LTC trend classifier", "forest"), + ("nas100-breakout-rf-15m", "NAS100", "15m", "sklearn RandomForest", "NASDAQ index breakout classifier", "breakout"), + ("us30-mean-reversion-30m", "US30", "30m", "custom Python", "Dow index mean-reversion baseline", "mean_reversion"), + ("spx500-trend-logistic-1h", "SPX500", "1h", "sklearn LogisticRegression", "S&P 500 trend classifier", "logistic"), + ("ger40-breakout-rf-30m", "GER40", "30m", "sklearn RandomForest", "DAX breakout classifier", "breakout"), + ("uk100-range-logistic-1h", "UK100", "1h", "sklearn LogisticRegression", "FTSE range classifier", "logistic"), + ("oilwtico-atr-breakout-1h", "WTICOUSD", "1h", "custom Python", "WTI crude oil ATR breakout baseline", "atr_breakout"), + ("brentoil-trend-rf-1h", "BRENTUSD", "1h", "sklearn RandomForest", "Brent crude trend classifier", "forest"), + ("naturalgas-volatility-rf-4h", "NATGAS", "4h", "sklearn RandomForest", "Natural gas volatility classifier", "forest"), + ("copperusd-mean-reversion-1h", "COPPERUSD", "1h", "custom Python", "Copper mean-reversion baseline", "mean_reversion"), +] + + +STRATEGIES = { + "logistic": '''import numpy as np +import pandas as pd +from sklearn.linear_model import LogisticRegression +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler + + +SYMBOL = "{symbol}" +MODEL_NAME = "{slug}" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + rng = (df["high"] - df["low"]).replace(0, np.nan) + f = pd.DataFrame(index=df.index) + for n in [1, 3, 6, 12, 24]: + f[f"ret{{n}}"] = c.pct_change(n) + f["range_pct"] = rng / c + f["body_pct"] = (c - df["open"]) / (rng + 1e-9) + f["close_pos"] = (c - df["low"]) / (rng + 1e-9) + f["ema_8_21"] = (c.ewm(span=8, adjust=False).mean() - c.ewm(span=21, adjust=False).mean()) / c + f["ema_21_55"] = (c.ewm(span=21, adjust=False).mean() - c.ewm(span=55, adjust=False).mean()) / c + f["volatility"] = c.pct_change().rolling(24).std() + f["volume_ratio"] = df["volume"] / (df["volume"].rolling(24).mean() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {{}}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 4)) + threshold = float(p.get("threshold", {threshold})) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", LogisticRegression(max_iter=1000, class_weight="balanced")), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {{"model": model, "features": list(x.columns), "symbol": SYMBOL}}, {{"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())}} + + +def predict(model, market_data, config): + p = config.get("parameters", {{}}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", {lookback})): + return {{"signal": "HOLD", "confidence": 0.0, "metadata": {{"reason": "not_enough_candles", "model": MODEL_NAME}}}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {{"signal": "HOLD", "confidence": 0.0, "metadata": {{"reason": "no_features", "model": MODEL_NAME}}}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {{0: "DOWN", 1: "HOLD", 2: "UP"}}[klass] + if conf < float(p.get("min_confidence", 0.48)): + signal = "HOLD" + return {{"signal": signal, "confidence": round(conf, 4), "metadata": {{"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}}}} +''', + "forest": '''import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier +from sklearn.preprocessing import StandardScaler +from sklearn.pipeline import Pipeline + + +SYMBOL = "{symbol}" +MODEL_NAME = "{slug}" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + v = df["volume"] + f = pd.DataFrame(index=df.index) + for n in [1, 4, 8, 16, 32, 64]: + f[f"ret{{n}}"] = c.pct_change(n) + f["ema_12_48"] = (c.ewm(span=12, adjust=False).mean() - c.ewm(span=48, adjust=False).mean()) / c + f["ema_24_96"] = (c.ewm(span=24, adjust=False).mean() - c.ewm(span=96, adjust=False).mean()) / c + f["volatility_fast"] = c.pct_change().rolling(16).std() + f["volatility_slow"] = c.pct_change().rolling(64).std() + f["volatility_ratio"] = f["volatility_fast"] / (f["volatility_slow"] + 1e-9) + f["volume_z"] = (v - v.rolling(48).mean()) / (v.rolling(48).std() + 1e-9) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {{}}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 6)) + threshold = float(p.get("threshold", {threshold})) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + model = Pipeline([ + ("scaler", StandardScaler()), + ("clf", RandomForestClassifier(n_estimators=350, max_depth=8, min_samples_leaf=8, class_weight="balanced_subsample", random_state=42, n_jobs=-1)), + ]) + model.fit(x.values.astype(np.float32), y.values) + pred = model.predict(x.values.astype(np.float32)) + return {{"model": model, "features": list(x.columns), "symbol": SYMBOL}}, {{"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())}} + + +def predict(model, market_data, config): + p = config.get("parameters", {{}}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", {lookback})): + return {{"signal": "HOLD", "confidence": 0.0, "metadata": {{"reason": "not_enough_candles", "model": MODEL_NAME}}}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {{"signal": "HOLD", "confidence": 0.0, "metadata": {{"reason": "no_features", "model": MODEL_NAME}}}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {{0: "DOWN", 1: "HOLD", 2: "UP"}}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {{"signal": signal, "confidence": round(conf, 4), "metadata": {{"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}}}} +''', + "breakout": '''import numpy as np +import pandas as pd +from sklearn.ensemble import RandomForestClassifier + + +SYMBOL = "{symbol}" +MODEL_NAME = "{slug}" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _features(df): + c = df["close"] + h = df["high"] + l = df["low"] + f = pd.DataFrame(index=df.index) + high_break = h.rolling(36).max().shift() + low_break = l.rolling(36).min().shift() + f["breakout_up"] = (c - high_break) / c + f["breakout_down"] = (c - low_break) / c + f["ret4"] = c.pct_change(4) + f["ret12"] = c.pct_change(12) + f["volatility"] = c.pct_change().rolling(24).std() + f["range_pct"] = (h - l) / c + f["ema_slope"] = c.ewm(span=12, adjust=False).mean().pct_change(6) + return f.replace([np.inf, -np.inf], np.nan).dropna() + + +def train(data, config): + p = config.get("parameters", {{}}) + df = _prep(data) + x = _features(df) + horizon = int(p.get("horizon", 4)) + threshold = float(p.get("threshold", {threshold})) + fwd = df["close"].pct_change(horizon).shift(-horizon) + y = pd.Series(1, index=df.index) + y[fwd > threshold] = 2 + y[fwd < -threshold] = 0 + y = y.reindex(x.index).fillna(1).astype(int) + clf = RandomForestClassifier(n_estimators=300, max_depth=7, min_samples_leaf=6, class_weight="balanced_subsample", random_state=42, n_jobs=-1) + clf.fit(x.values.astype(np.float32), y.values) + pred = clf.predict(x.values.astype(np.float32)) + return {{"model": clf, "features": list(x.columns), "symbol": SYMBOL}}, {{"training_bars": int(len(x)), "feature_count": int(x.shape[1]), "buy_signals": int((pred == 2).sum()), "sell_signals": int((pred == 0).sum()), "hold_signals": int((pred == 1).sum())}} + + +def predict(model, market_data, config): + p = config.get("parameters", {{}}) + candles = market_data.get("candles", []) + if len(candles) < int(p.get("lookback", {lookback})): + return {{"signal": "HOLD", "confidence": 0.0, "metadata": {{"reason": "not_enough_candles", "model": MODEL_NAME}}}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + row = _features(df).tail(1) + if row.empty: + return {{"signal": "HOLD", "confidence": 0.0, "metadata": {{"reason": "no_features", "model": MODEL_NAME}}}} + prob = model["model"].predict_proba(row[model["features"]].values.astype(np.float32))[0] + klass = int(np.argmax(prob)) + conf = float(np.max(prob)) + signal = {{0: "DOWN", 1: "HOLD", 2: "UP"}}[klass] + if conf < float(p.get("min_confidence", 0.5)): + signal = "HOLD" + return {{"signal": signal, "confidence": round(conf, 4), "metadata": {{"p_sell": round(float(prob[0]), 4), "p_hold": round(float(prob[1]), 4), "p_buy": round(float(prob[2]), 4), "model": MODEL_NAME, "symbol": SYMBOL}}}} +''', + "mean_reversion": '''import numpy as np +import pandas as pd + + +SYMBOL = "{symbol}" +MODEL_NAME = "{slug}" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _zscore(close, window): + mean = close.rolling(window).mean() + std = close.rolling(window).std() + return (close - mean) / (std + 1e-9) + + +def train(data, config): + p = config.get("parameters", {{}}) + return {{"window": int(p.get("window", 48)), "entry_z": float(p.get("entry_z", 1.4)), "exit_z": float(p.get("exit_z", 0.3)), "symbol": SYMBOL}}, {{"model_family": "rule_based_mean_reversion", "training_bars": int(len(data))}} + + +def predict(model, market_data, config): + p = config.get("parameters", {{}}) + candles = market_data.get("candles", []) + lookback = int(p.get("lookback", {lookback})) + if len(candles) < lookback: + return {{"signal": "HOLD", "confidence": 0.0, "metadata": {{"reason": "not_enough_candles", "model": MODEL_NAME}}}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + window = int(model.get("window", p.get("window", 48))) + z = float(_zscore(df["close"], window).iloc[-1]) + entry_z = float(model.get("entry_z", p.get("entry_z", 1.4))) + signal = "HOLD" + if z <= -entry_z: + signal = "UP" + elif z >= entry_z: + signal = "DOWN" + confidence = min(abs(z) / max(entry_z, 1e-9), 1.0) + return {{"signal": signal, "confidence": round(float(confidence), 4), "metadata": {{"zscore": round(z, 4), "window": window, "model": MODEL_NAME, "symbol": SYMBOL}}}} +''', + "atr_breakout": '''import numpy as np +import pandas as pd + + +SYMBOL = "{symbol}" +MODEL_NAME = "{slug}" + + +def _prep(data): + df = data.copy() + df.columns = [str(c).strip().lower() for c in df.columns] + if "volume" not in df.columns: + df["volume"] = 1.0 + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + return df.dropna(subset=["open", "high", "low", "close"]).reset_index(drop=True) + + +def _atr(df, n=14): + h, l, c = df["high"], df["low"], df["close"] + tr = pd.concat([(h - l), (h - c.shift()).abs(), (l - c.shift()).abs()], axis=1).max(axis=1) + return tr.ewm(span=n, adjust=False).mean() + + +def train(data, config): + p = config.get("parameters", {{}}) + return {{"atr_window": int(p.get("atr_window", 14)), "breakout_window": int(p.get("breakout_window", 36)), "atr_mult": float(p.get("atr_mult", 0.25)), "symbol": SYMBOL}}, {{"model_family": "rule_based_atr_breakout", "training_bars": int(len(data))}} + + +def predict(model, market_data, config): + p = config.get("parameters", {{}}) + candles = market_data.get("candles", []) + lookback = int(p.get("lookback", {lookback})) + if len(candles) < lookback: + return {{"signal": "HOLD", "confidence": 0.0, "metadata": {{"reason": "not_enough_candles", "model": MODEL_NAME}}}} + df = _prep(pd.DataFrame(candles, columns=["open", "high", "low", "close", "volume"])) + atr_window = int(model.get("atr_window", p.get("atr_window", 14))) + breakout_window = int(model.get("breakout_window", p.get("breakout_window", 36))) + atr_mult = float(model.get("atr_mult", p.get("atr_mult", 0.25))) + atr = _atr(df, atr_window) + close = float(df["close"].iloc[-1]) + upper = float(df["high"].rolling(breakout_window).max().shift().iloc[-1] + atr.iloc[-1] * atr_mult) + lower = float(df["low"].rolling(breakout_window).min().shift().iloc[-1] - atr.iloc[-1] * atr_mult) + signal = "HOLD" + distance = 0.0 + if close > upper: + signal = "UP" + distance = (close - upper) / max(atr.iloc[-1], 1e-9) + elif close < lower: + signal = "DOWN" + distance = (lower - close) / max(atr.iloc[-1], 1e-9) + confidence = min(max(distance, 0.0), 1.0) + return {{"signal": signal, "confidence": round(float(confidence), 4), "metadata": {{"upper": round(upper, 6), "lower": round(lower, 6), "close": round(close, 6), "model": MODEL_NAME, "symbol": SYMBOL}}}} +''', +} + + +def default_threshold(symbol: str, family: str) -> float: + if symbol.endswith("USDT"): + return 0.004 if family != "logistic" else 0.003 + if symbol in {"XAUUSD", "XAGUSD", "XPTUSD", "WTICOUSD", "BRENTUSD", "NATGAS", "COPPERUSD"}: + return 0.003 + if symbol in {"NAS100", "US30", "SPX500", "GER40", "UK100"}: + return 0.0025 + if "JPY" in symbol: + return 0.0018 + if symbol.endswith("MXN"): + return 0.003 + return 0.0012 + + +def default_lookback(timeframe: str, family: str) -> int: + if timeframe == "1m": + return 120 + if timeframe in {"5m", "15m"}: + return 100 + if timeframe == "30m": + return 120 + if timeframe == "4h": + return 180 + return 140 + + +def readme(slug: str, symbol: str, timeframe: str, model_family: str, purpose: str) -> str: + return f"""# {slug} + +{purpose} for PyP Quant Mode. + +This is an educational starter template for `{symbol}` on the `{timeframe}` timeframe. It implements the PyP Quant contract: + +```python +train(data, config) +predict(model, market_data, config) +``` + +Use it as a baseline, then validate with PPE before any live deployment. + +## Model + +- Symbol: `{symbol}` +- Timeframe: `{timeframe}` +- Family: `{model_family}` +- Output: `UP`, `DOWN`, or `HOLD` + +## PyP Links + +- Quant docs: https://pyp.stanl.ink/docs/quant/what-is-quant-mode +- Quant landing page: https://pyp.stanl.ink/for-quant-traders +- Create project: https://pyp.stanlink.online/projects/quant/new + +## Risk + +This is not financial advice and is not a verified profitable strategy. +""" + + +def config(slug: str, symbol: str, timeframe: str, model_family: str, family: str) -> dict: + threshold = default_threshold(symbol, family) + lookback = default_lookback(timeframe, family) + requirements = ["numpy", "pandas"] + if family == "logistic": + requirements.append("scikit-learn") + if family in {"forest", "breakout"}: + requirements.append("scikit-learn") + return { + "name": slug, + "description": f"{model_family} PyP Quant template for {symbol} {timeframe}.", + "symbol": symbol, + "timeframe": timeframe, + "model_family": model_family, + "artifact_target": "joblib" if family in {"logistic", "forest", "breakout"} else "python", + "parameters": { + "lookback": lookback, + "horizon": 4 if timeframe in {"1m", "5m", "15m", "30m"} else 6, + "threshold": threshold, + "min_confidence": 0.48 if family == "logistic" else 0.5, + "sl_percent": 0.4 if symbol.endswith("USDT") else 0.25, + "tp_percent": 0.8 if symbol.endswith("USDT") else 0.5, + }, + "requirements": requirements, + "disclaimer": "Educational template only. Not financial advice.", + } + + +def main() -> None: + created = 0 + for slug, symbol, timeframe, model_family, purpose, family in SPECS: + target = TEMPLATE_ROOT / slug + if target.exists(): + continue + target.mkdir(parents=True) + threshold = default_threshold(symbol, family) + lookback = default_lookback(timeframe, family) + (target / "README.md").write_text(readme(slug, symbol, timeframe, model_family, purpose), encoding="utf-8") + (target / "quant.config.json").write_text(json.dumps(config(slug, symbol, timeframe, model_family, family), indent=2) + "\n", encoding="utf-8") + strategy = STRATEGIES[family].format(slug=slug, symbol=symbol, threshold=threshold, lookback=lookback) + (target / "strategy.py").write_text(strategy, encoding="utf-8") + created += 1 + print(f"Created {created} templates") + + +if __name__ == "__main__": + main()