Spaces:
Running
Running
Khanna, Videh Rakesh Rakesh commited on
Commit ·
8d1eb7b
1
Parent(s): 4bb9928
Add NVIDIA NIM as free-tier LLM provider; research + UI updates
Browse files- Wire NVIDIA NIM (build.nvidia.com) into llm_client provider chain as
a zero-cost 7th cloud provider via shared _try_openai_compatible driver
(inherits timeout/retry/cooldown/daily-exhausted machinery)
- Default to fast nemotron-super-49b (70b slug read-times-out on free tier)
- Update CLAUDE.md, project_context.json provider-chain metadata
- gitignore paper_trading.db.local_backup* and models_holdout_eval/
- Misc research scripts + UI/database updates
- .gitignore +2 -0
- CLAUDE.md +7 -1
- database.py +19 -0
- llm_client.py +31 -4
- project_context.json +129 -0
- research/ml_selection_backtest.py +54 -3
- research/ml_selection_results.csv +721 -62
- research/model_family_compare.py +162 -0
- research/strategy_combo_swing.csv +0 -0
- research/strategy_combo_swing.py +263 -0
- research/strategy_validation_funnel.csv +31 -0
- research/strategy_validation_funnel.py +260 -0
- research/watchlist_forward_eval.csv +28 -0
- research/watchlist_forward_eval.py +383 -0
- static/app.js +61 -14
- templates/index.html +2 -2
.gitignore
CHANGED
|
@@ -24,10 +24,12 @@ venv/
|
|
| 24 |
paper_trading.db.bak*
|
| 25 |
paper_trading.db.backup
|
| 26 |
paper_trading.db.pre_regrade*
|
|
|
|
| 27 |
server.log
|
| 28 |
learnings.json
|
| 29 |
ohlcv_cache.db
|
| 30 |
ml_predictor/training_data.csv
|
|
|
|
| 31 |
ml_predictor/training_data_extra.csv
|
| 32 |
ml_predictor/models/manifest.json.bak*
|
| 33 |
research/ml_backtest_results.csv
|
|
|
|
| 24 |
paper_trading.db.bak*
|
| 25 |
paper_trading.db.backup
|
| 26 |
paper_trading.db.pre_regrade*
|
| 27 |
+
paper_trading.db.local_backup*
|
| 28 |
server.log
|
| 29 |
learnings.json
|
| 30 |
ohlcv_cache.db
|
| 31 |
ml_predictor/training_data.csv
|
| 32 |
+
ml_predictor/models_holdout_eval/
|
| 33 |
ml_predictor/training_data_extra.csv
|
| 34 |
ml_predictor/models/manifest.json.bak*
|
| 35 |
research/ml_backtest_results.csv
|
CLAUDE.md
CHANGED
|
@@ -108,7 +108,7 @@ Inspired by TauricResearch/TradingAgents multi-agent debate pattern.
|
|
| 108 |
- `{provider}:{model}` — single-call fallback
|
| 109 |
- `heuristic` — no API key
|
| 110 |
|
| 111 |
-
**LLM backends (provider chain):** OpenRouter free tier → Groq (llama-3.3-70b-versatile → llama-3.1-8b-instant) → Cerebras (llama-3.3-70b → llama-3.1-8b) → HuggingFace Router (novita, llama-3.1-8b-instruct) → **Gemini (2.5-flash, ~1,500/day) → SambaNova (70B, persistent free)** → Ollama (local only, `OLLAMA_ENDPOINT` must be set). GitHub Models removed — `GITHUB_TOKEN` unused. Gemini/SambaNova are appended LAST in `_CLOUD_PROVIDERS` so the happy path is unchanged, but the dynamic availability sort auto-promotes them when the first four degrade — adding large independent daily capacity so `_all_cloud_daily_exhausted()` (which triggers the slow single-Ollama funnel) rarely fires.
|
| 112 |
|
| 113 |
**AI unavailable root cause** — "⚠ AI unavailable" means ALL cloud providers failed AND Ollama failed/unavailable. Most common cause: free-tier rate limits exhausted during a 150-stock batch scan (~150–450 LLM calls). Fix: ensure OpenRouter + Groq + Cerebras + HF keys are all set in HF Spaces Secrets. The "Signals active: 0" part of the error is a separate issue — the stock has no technical strategy signals firing, independent of AI.
|
| 114 |
|
|
@@ -399,6 +399,12 @@ SAMBANOVA_API_KEY 6th fallback. SambaNova Cloud — persistent free 70B/405B.
|
|
| 399 |
https://cloud.sambanova.ai. No-op if unset.
|
| 400 |
SAMBANOVA_MODEL Default Meta-Llama-3.3-70B-Instruct.
|
| 401 |
SAMBANOVA_FALLBACK_MODELS Comma-separated fallbacks (default: Meta-Llama-3.1-8B-Instruct).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 402 |
FRED_API_KEY Optional. Free FRED API key for US macro indicators (fred_data.py).
|
| 403 |
Falls back to yfinance proxies if not set.
|
| 404 |
Get one at: https://fred.stlouisfed.org/docs/api/api_key.html
|
|
|
|
| 108 |
- `{provider}:{model}` — single-call fallback
|
| 109 |
- `heuristic` — no API key
|
| 110 |
|
| 111 |
+
**LLM backends (provider chain):** OpenRouter free tier → Groq (llama-3.3-70b-versatile → llama-3.1-8b-instant) → Cerebras (llama-3.3-70b → llama-3.1-8b) → HuggingFace Router (novita, llama-3.1-8b-instruct) → **Gemini (2.5-flash, ~1,500/day) → SambaNova (70B, persistent free) → NVIDIA NIM (nemotron-super-49b, free)** → Ollama (local only, `OLLAMA_ENDPOINT` must be set). GitHub Models removed — `GITHUB_TOKEN` unused. Gemini/SambaNova/NVIDIA are appended LAST in `_CLOUD_PROVIDERS` so the happy path is unchanged, but the dynamic availability sort auto-promotes them when the first four degrade — adding large independent daily capacity so `_all_cloud_daily_exhausted()` (which triggers the slow single-Ollama funnel) rarely fires. All three no-op without their API key. NVIDIA routes through the shared `_try_openai_compatible` driver, so it inherits the identical timeout/retry/cooldown/daily-exhausted machinery.
|
| 112 |
|
| 113 |
**AI unavailable root cause** — "⚠ AI unavailable" means ALL cloud providers failed AND Ollama failed/unavailable. Most common cause: free-tier rate limits exhausted during a 150-stock batch scan (~150–450 LLM calls). Fix: ensure OpenRouter + Groq + Cerebras + HF keys are all set in HF Spaces Secrets. The "Signals active: 0" part of the error is a separate issue — the stock has no technical strategy signals firing, independent of AI.
|
| 114 |
|
|
|
|
| 399 |
https://cloud.sambanova.ai. No-op if unset.
|
| 400 |
SAMBANOVA_MODEL Default Meta-Llama-3.3-70B-Instruct.
|
| 401 |
SAMBANOVA_FALLBACK_MODELS Comma-separated fallbacks (default: Meta-Llama-3.1-8B-Instruct).
|
| 402 |
+
NVIDIA_API_KEY 7th fallback. NVIDIA NIM (build.nvidia.com) — free API key, OpenAI-compatible,
|
| 403 |
+
large independent free-tier capacity (Llama-3.3-70B / Nemotron / DeepSeek /
|
| 404 |
+
Qwen). Zero cost. Get one at https://build.nvidia.com. No-op if unset.
|
| 405 |
+
NVIDIA_MODEL Default nvidia/llama-3.3-nemotron-super-49b-v1 (the meta/llama-3.3-70b-instruct
|
| 406 |
+
slug read-times-out >30s on the free tier — dropped 2026-07-24).
|
| 407 |
+
NVIDIA_FALLBACK_MODELS Comma-separated fallbacks (default: meta/llama-3.1-8b-instruct).
|
| 408 |
FRED_API_KEY Optional. Free FRED API key for US macro indicators (fred_data.py).
|
| 409 |
Falls back to yfinance proxies if not set.
|
| 410 |
Get one at: https://fred.stlouisfed.org/docs/api/api_key.html
|
database.py
CHANGED
|
@@ -129,7 +129,22 @@ def _atomic_snapshot(dest: str) -> bool:
|
|
| 129 |
return False
|
| 130 |
|
| 131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
def hf_upload_db():
|
|
|
|
|
|
|
| 133 |
token = _hf_token()
|
| 134 |
if not token or not os.path.exists(DB_PATH):
|
| 135 |
return
|
|
@@ -177,6 +192,10 @@ def setup_hf_persistence():
|
|
| 177 |
_hf_download_db()
|
| 178 |
import atexit
|
| 179 |
atexit.register(_checkpoint_on_exit)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
threading.Thread(target=_backup_loop, daemon=True).start()
|
| 181 |
|
| 182 |
|
|
|
|
| 129 |
return False
|
| 130 |
|
| 131 |
|
| 132 |
+
def _backup_enabled() -> bool:
|
| 133 |
+
"""Only the real HF Space should back the DB up to the shared Hub repo.
|
| 134 |
+
|
| 135 |
+
Otherwise a stray local `python app.py` that has the production HF_TOKEN in .env will
|
| 136 |
+
also run the backup loop and clobber the Space's data (multi-writer race → data loss).
|
| 137 |
+
HF Spaces always set SPACE_ID / SPACE_HOST. Set FORCE_DB_BACKUP=1 to override for a
|
| 138 |
+
single, intentional non-Space writer.
|
| 139 |
+
"""
|
| 140 |
+
if os.environ.get("FORCE_DB_BACKUP") == "1":
|
| 141 |
+
return True
|
| 142 |
+
return bool(os.environ.get("SPACE_ID") or os.environ.get("SPACE_HOST"))
|
| 143 |
+
|
| 144 |
+
|
| 145 |
def hf_upload_db():
|
| 146 |
+
if not _backup_enabled():
|
| 147 |
+
return
|
| 148 |
token = _hf_token()
|
| 149 |
if not token or not os.path.exists(DB_PATH):
|
| 150 |
return
|
|
|
|
| 192 |
_hf_download_db()
|
| 193 |
import atexit
|
| 194 |
atexit.register(_checkpoint_on_exit)
|
| 195 |
+
if not _backup_enabled():
|
| 196 |
+
print("[DB] Backup loop disabled (not an HF Space; set FORCE_DB_BACKUP=1 to override). "
|
| 197 |
+
"Startup restore still ran; this instance will NOT upload to HF Hub.", flush=True)
|
| 198 |
+
return
|
| 199 |
threading.Thread(target=_backup_loop, daemon=True).start()
|
| 200 |
|
| 201 |
|
llm_client.py
CHANGED
|
@@ -60,11 +60,13 @@ _PROVIDER_STATUS: dict = {
|
|
| 60 |
"cerebras": {"avail_at": 0.0, "daily_exhausted": False, "fail_streak": 0},
|
| 61 |
"huggingface": {"avail_at": 0.0, "daily_exhausted": False, "fail_streak": 0},
|
| 62 |
# Extra free tiers — large independent daily capacity (Gemini ~1,500 req/day, SambaNova
|
| 63 |
-
# persistent free 70B). Appended LAST so the happy path is
|
| 64 |
-
# availability sort auto-promotes them to the front the moment the
|
| 65 |
-
# which is exactly when the exhaustion→single-Ollama funnel used to bite.
|
|
|
|
| 66 |
"gemini": {"avail_at": 0.0, "daily_exhausted": False, "fail_streak": 0},
|
| 67 |
"sambanova": {"avail_at": 0.0, "daily_exhausted": False, "fail_streak": 0},
|
|
|
|
| 68 |
}
|
| 69 |
_PROVIDER_DAILY_RESET: str = "" # "YYYY-MM-DD" IST string; reset daily flags on date change
|
| 70 |
|
|
@@ -105,7 +107,7 @@ _OLLAMA_WARMUP_BACKOFF_SECS: int = 45
|
|
| 105 |
_OLLAMA_CHAT_TIMEOUT: int = 70 # 70s: warmup confirms model is loaded, so 70s is enough for 512 tokens
|
| 106 |
|
| 107 |
# Canonical cloud provider order (original preference before runtime reordering)
|
| 108 |
-
_CLOUD_PROVIDERS = ["openrouter", "groq", "cerebras", "huggingface", "gemini", "sambanova"]
|
| 109 |
_PROVIDER_ORDER = _CLOUD_PROVIDERS
|
| 110 |
|
| 111 |
|
|
@@ -261,6 +263,7 @@ _PROBE_CONFIG: dict = {
|
|
| 261 |
"huggingface": ("HF_TOKEN", "https://router.huggingface.co/novita/v3/openai/chat/completions", "HF_INFERENCE_MODEL", "meta-llama/Llama-3.1-8B-Instruct"),
|
| 262 |
"gemini": ("GEMINI_API_KEY", "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", "GEMINI_MODEL", "gemini-flash-latest"),
|
| 263 |
"sambanova": ("SAMBANOVA_API_KEY", "https://api.sambanova.ai/v1/chat/completions", "SAMBANOVA_MODEL", "Meta-Llama-3.3-70B-Instruct"),
|
|
|
|
| 264 |
}
|
| 265 |
|
| 266 |
|
|
@@ -694,6 +697,29 @@ def make_chat_call(
|
|
| 694 |
api_key, models_to_try, daily_on_429=False,
|
| 695 |
)
|
| 696 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 697 |
# ── Provider name → function map ─────────────────────────────────────────
|
| 698 |
_PROVIDER_FNS = {
|
| 699 |
"openrouter": _try_openrouter,
|
|
@@ -702,6 +728,7 @@ def make_chat_call(
|
|
| 702 |
"huggingface": _try_huggingface,
|
| 703 |
"gemini": _try_gemini,
|
| 704 |
"sambanova": _try_sambanova,
|
|
|
|
| 705 |
}
|
| 706 |
|
| 707 |
# ── Ollama (own server, no rate limits — handled outside cloud semaphore) ─
|
|
|
|
| 60 |
"cerebras": {"avail_at": 0.0, "daily_exhausted": False, "fail_streak": 0},
|
| 61 |
"huggingface": {"avail_at": 0.0, "daily_exhausted": False, "fail_streak": 0},
|
| 62 |
# Extra free tiers — large independent daily capacity (Gemini ~1,500 req/day, SambaNova
|
| 63 |
+
# persistent free 70B, NVIDIA NIM free 70B/Nemotron). Appended LAST so the happy path is
|
| 64 |
+
# unchanged, but the dynamic availability sort auto-promotes them to the front the moment the
|
| 65 |
+
# first four degrade — which is exactly when the exhaustion→single-Ollama funnel used to bite.
|
| 66 |
+
# No-op without keys.
|
| 67 |
"gemini": {"avail_at": 0.0, "daily_exhausted": False, "fail_streak": 0},
|
| 68 |
"sambanova": {"avail_at": 0.0, "daily_exhausted": False, "fail_streak": 0},
|
| 69 |
+
"nvidia": {"avail_at": 0.0, "daily_exhausted": False, "fail_streak": 0},
|
| 70 |
}
|
| 71 |
_PROVIDER_DAILY_RESET: str = "" # "YYYY-MM-DD" IST string; reset daily flags on date change
|
| 72 |
|
|
|
|
| 107 |
_OLLAMA_CHAT_TIMEOUT: int = 70 # 70s: warmup confirms model is loaded, so 70s is enough for 512 tokens
|
| 108 |
|
| 109 |
# Canonical cloud provider order (original preference before runtime reordering)
|
| 110 |
+
_CLOUD_PROVIDERS = ["openrouter", "groq", "cerebras", "huggingface", "gemini", "sambanova", "nvidia"]
|
| 111 |
_PROVIDER_ORDER = _CLOUD_PROVIDERS
|
| 112 |
|
| 113 |
|
|
|
|
| 263 |
"huggingface": ("HF_TOKEN", "https://router.huggingface.co/novita/v3/openai/chat/completions", "HF_INFERENCE_MODEL", "meta-llama/Llama-3.1-8B-Instruct"),
|
| 264 |
"gemini": ("GEMINI_API_KEY", "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", "GEMINI_MODEL", "gemini-flash-latest"),
|
| 265 |
"sambanova": ("SAMBANOVA_API_KEY", "https://api.sambanova.ai/v1/chat/completions", "SAMBANOVA_MODEL", "Meta-Llama-3.3-70B-Instruct"),
|
| 266 |
+
"nvidia": ("NVIDIA_API_KEY", "https://integrate.api.nvidia.com/v1/chat/completions", "NVIDIA_MODEL", "nvidia/llama-3.3-nemotron-super-49b-v1"),
|
| 267 |
}
|
| 268 |
|
| 269 |
|
|
|
|
| 697 |
api_key, models_to_try, daily_on_429=False,
|
| 698 |
)
|
| 699 |
|
| 700 |
+
def _try_nvidia():
|
| 701 |
+
# NVIDIA NIM (build.nvidia.com) — free API key, OpenAI-compatible, large independent
|
| 702 |
+
# daily capacity across many models (Llama-3.3-70B, Nemotron, DeepSeek, Qwen). Zero cost.
|
| 703 |
+
# daily_on_429=False: NIM free-tier 429s are per-minute RPM limits that recover in seconds,
|
| 704 |
+
# so a short cooldown keeps it in rotation instead of benching it until midnight.
|
| 705 |
+
if not _is_provider_available("nvidia", fast_fail_on_rate_limit):
|
| 706 |
+
return None
|
| 707 |
+
api_key = os.environ.get("NVIDIA_API_KEY", "").strip()
|
| 708 |
+
if not api_key:
|
| 709 |
+
logger.debug("NVIDIA NIM skipped — NVIDIA_API_KEY not set")
|
| 710 |
+
return None
|
| 711 |
+
primary = (os.environ.get("NVIDIA_MODEL") or "nvidia/llama-3.3-nemotron-super-49b-v1").strip()
|
| 712 |
+
fallback_raw = os.environ.get(
|
| 713 |
+
"NVIDIA_FALLBACK_MODELS",
|
| 714 |
+
"meta/llama-3.1-8b-instruct",
|
| 715 |
+
)
|
| 716 |
+
fallbacks = [m.strip() for m in fallback_raw.split(",") if m.strip() and m.strip() != primary]
|
| 717 |
+
models_to_try = [primary] + fallbacks[:3]
|
| 718 |
+
return _try_openai_compatible(
|
| 719 |
+
"nvidia", "https://integrate.api.nvidia.com/v1",
|
| 720 |
+
api_key, models_to_try, daily_on_429=False,
|
| 721 |
+
)
|
| 722 |
+
|
| 723 |
# ── Provider name → function map ─────────────────────────────────────────
|
| 724 |
_PROVIDER_FNS = {
|
| 725 |
"openrouter": _try_openrouter,
|
|
|
|
| 728 |
"huggingface": _try_huggingface,
|
| 729 |
"gemini": _try_gemini,
|
| 730 |
"sambanova": _try_sambanova,
|
| 731 |
+
"nvidia": _try_nvidia,
|
| 732 |
}
|
| 733 |
|
| 734 |
# ── Ollama (own server, no rate limits — handled outside cloud semaphore) ─
|
project_context.json
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"projectInfo": {
|
| 3 |
+
"name": "PaperTrade",
|
| 4 |
+
"description": "NSE Indian-equity short-term price-direction prediction engine + paper-trading book. Predicts 1D/3D/INTRADAY direction using backtested technical strategies, a standalone supervised ML quantile model, macro gates, news sentiment, and an LLM bull/bear/fundamentals debate. Served via a Flask web app and an MCP server.",
|
| 5 |
+
"platform": "Python backend (Flask web app + MCP server) with an offline-trained scikit-learn ML model",
|
| 6 |
+
"language": "Python 3.13",
|
| 7 |
+
"deployment": {
|
| 8 |
+
"primary": "Hugging Face Spaces (persistent /data disk, container). Think HF Spaces first for storage/paths/env.",
|
| 9 |
+
"local": "python app.py from project root",
|
| 10 |
+
"notes": [
|
| 11 |
+
"Use os.path.dirname(__file__) or HF paths for data files; NOT pathlib.Path(__file__).parent.",
|
| 12 |
+
"Pickle/file caches do NOT survive HF container restarts — use SQLite (paper_trading.db) instead.",
|
| 13 |
+
"Static JS/CSS is inline in templates (HF CSP blocks external CDNs).",
|
| 14 |
+
"Secrets: .env locally; HF Spaces Secrets tab in prod; sync via export_env_secrets.py."
|
| 15 |
+
]
|
| 16 |
+
}
|
| 17 |
+
},
|
| 18 |
+
"architecturalKnowledge": {
|
| 19 |
+
"pattern": "Layered prediction pipeline + service modules (not MVC/MVVM)",
|
| 20 |
+
"confidence": 95,
|
| 21 |
+
"layers": [
|
| 22 |
+
"Entry points: app.py (Flask UI + paper-trading book + validation), stock_predictor_mcp.py (MCP tools)",
|
| 23 |
+
"Prediction core: predictor_core.py (public API predict_stock_v2 / rank_stocks_v2)",
|
| 24 |
+
"Signal/feature layer: trial_run.py (S1..S20 strategy signals), ml_combiner.py (ML feature funcs)",
|
| 25 |
+
"Context providers: macro_context.py, fred_data.py, news_sentiment.py, fundamentals.py, sector_pulse.py, fii_flow.py, social_sentiment.py, intraday_live.py, price_targets.py",
|
| 26 |
+
"LLM layer: ai_forecast.py (bull/bear/fundamentals debate) -> llm_client.py (provider chain) / ollama_client.py",
|
| 27 |
+
"Data layer: data_sources.py (multi-source OHLCV + live price), universe.py (dynamic NSE universe), database.py (SQLite: trades, orders, snapshots, postmortems)",
|
| 28 |
+
"Standalone ML: ml_predictor/ (features -> dataset -> train -> infer, 21 committed joblib estimators)",
|
| 29 |
+
"Research/backtests: research/ (offline analysis, never imported by prod prediction path)"
|
| 30 |
+
],
|
| 31 |
+
"dataFlowPredictionPipeline": [
|
| 32 |
+
"1. Market gates (VIX>25 hard block; VIX 20-25 size cut; Nifty<EMA200 -40% expected; macro/FRED risk-off cuts)",
|
| 33 |
+
"2. Strategy signals (20+ NSE-backtested booleans, fire if triggered in last 5 bars)",
|
| 34 |
+
"3. ML feature score (11 weighted features -> 0-100 + logistic prob)",
|
| 35 |
+
"4. News sentiment (Claude Haiku -> BULLISH/NEUTRAL/BEARISH)",
|
| 36 |
+
"5. Sector pulse (NSE sector heatmap -> leading/lagging flag)",
|
| 37 |
+
"6. Fundamentals (PE/D-E/ROE/FCF score, 24h cache)",
|
| 38 |
+
"7. AI forecast (up to 4-call LLM debate -> synthesis JSON) with heuristic fallback",
|
| 39 |
+
"8. Confidence scoring (additive -> HIGH/MEDIUM/LOW)",
|
| 40 |
+
"9. Risk (ATR14 stop, R:R targets scaled to timeframe)"
|
| 41 |
+
]
|
| 42 |
+
},
|
| 43 |
+
"technologyStack": {
|
| 44 |
+
"web": "Flask (server-rendered templates/index.html, inline JS/CSS in static/)",
|
| 45 |
+
"mcp": "stock_predictor_mcp.py exposes predict_stocks, rank_best_stocks",
|
| 46 |
+
"ml": "scikit-learn HistGradientBoosting quantile regressors + isotonic-calibrated direction classifier; joblib persistence. No lightgbm/xgboost/torch (HF-safe).",
|
| 47 |
+
"dataSources": "yfinance + NSE archives + Twelve Data + Alpha Vantage (fallback chain in data_sources.py)",
|
| 48 |
+
"llmProviderChain": "OpenRouter free -> Groq -> Cerebras -> HuggingFace Router -> Gemini -> SambaNova -> NVIDIA NIM -> Ollama (local last-resort). GitHub Models removed.",
|
| 49 |
+
"database": "SQLite (paper_trading.db) — trades, pending orders, prediction snapshots, postmortems, ohlcv_cache blob",
|
| 50 |
+
"testing": "pytest (tests/test_api_contract.py — Flask endpoint schema/type checks)",
|
| 51 |
+
"caches": [
|
| 52 |
+
".universe_cache.json (24h fresh / 7d stale, /data on HF)",
|
| 53 |
+
"fred_macro_cache.json (24h)",
|
| 54 |
+
"fundamentals_cache.json (24h/ticker)",
|
| 55 |
+
"sector pulse (5-min in-memory)",
|
| 56 |
+
"ohlcv_cache table in paper_trading.db (SQLite blob, NOT file-based)"
|
| 57 |
+
]
|
| 58 |
+
},
|
| 59 |
+
"moduleGraph": {
|
| 60 |
+
"predictor_core.py": {"role": "Main prediction API (used by MCP + Flask). MUST stay stable.", "publicApi": ["predict_stock_v2(ticker, start_date, end_date, ...)", "rank_stocks_v2(...)", "timeframe_to_dates(tf)", "get_ml_feature_score()", "DEFAULT_UNIVERSE"]},
|
| 61 |
+
"trial_run.py": {"role": "All strategy signal generators S1..S20, S_CTRIO, S_CAPFLOW, S_SEASONAL etc. NSE-verified stats in predictor_core._STRATEGY_STATS_DEFAULT — do not change without re-running backtest.", "publicApi": ["gen_s1..gen_s20", "gen_s_confluence_trio", "gen_mfs", "gen_nira", "gen_ped", "gen_supertrend"]},
|
| 62 |
+
"ml_combiner.py": {"role": "ML feature functions used by predictor_core.get_ml_feature_score", "publicApi": ["bollinger_position", "ema_stack_score", "shadow_flag", "build_feature_matrix"]},
|
| 63 |
+
"ai_forecast.py": {"role": "LLM bull/bear/fundamentals debate -> synthesis. Trigger guardrails + ATR clamp (prod-only). Output: should_buy, entry_price, direction, ranges.", "publicApi": ["get_ai_forecast(...)"]},
|
| 64 |
+
"llm_client.py": {"role": "Provider chain + Ollama fallback + rate-limit handling", "publicApi": ["reset_ollama_state()"]},
|
| 65 |
+
"data_sources.py": {"role": "Multi-source OHLCV + live price with fallback chains. Do NOT change signatures.", "publicApi": ["fetch_ohlcv(ticker, period)", "fetch_live_price(ticker, allow_delayed=True)", "cached_tickers(period)", "fetch_market_data(period_days)"]},
|
| 66 |
+
"universe.py": {"role": "Dynamic full-NSE universe (~2062 EQ stocks). Replaces old nse_universe.py.", "publicApi": ["get_universe(force_refresh=False)", "refresh_universe()"]},
|
| 67 |
+
"database.py": {"role": "SQLite paper-trading book + prediction validation audit trail", "publicApi": ["get_open_trades_with_live_prices()", "save_prediction_snapshot(...)", "get_prediction_snapshots(...)", "get_validation_summary(...)", "save_postmortem(...)"]},
|
| 68 |
+
"macro_context.py": {"role": "Macro gates (S&P500, USD/INR, crude) + FRED regime gate", "publicApi": ["get_macro_gate()", "global_risk_on"]},
|
| 69 |
+
"fred_data.py": {"role": "US macro indicators", "publicApi": ["get_fred_macro()", "get_fred_gate()"]},
|
| 70 |
+
"fundamentals.py": {"role": "Stock fundamentals scorer (PE/D-E/rev/FCF/ROE). MUST stay stable.", "publicApi": ["get_fundamentals(ticker)"]},
|
| 71 |
+
"sector_pulse.py": {"role": "NSE 10-sector heatmap + rotation. MUST stay stable.", "publicApi": ["get_sector_pulse()", "get_sector_for_ticker(ticker)"]},
|
| 72 |
+
"risk_engine.py": {"role": "Portfolio risk metrics (Sharpe, drawdown, beta, Kelly)", "publicApi": ["compute_risk_metrics()"]},
|
| 73 |
+
"top5_picker.py": {"role": "Top picks (up to 20) INTRADAY/1D/3D, concurrent, ATR-ranked, progressive streaming", "publicApi": ["get_top5_picks(top_n=20, _universe_size=700, progress_cb=None)", "get_weekly_picks(...)"]},
|
| 74 |
+
"app.py": {"role": "Flask UI + paper-trading book + validation + all /api routes", "publicApi": ["Flask endpoints (see flaskEndpoints)"]},
|
| 75 |
+
"stock_predictor_mcp.py": {"role": "MCP server entry point. Imports predictor_core.", "publicApi": ["predict_stocks", "rank_best_stocks"]},
|
| 76 |
+
"ml_predictor/features.py": {"role": "Shared point-in-time feature builder (37 lookahead-safe features)", "publicApi": ["compute_features(...)", "FEATURE_COLUMNS"]},
|
| 77 |
+
"ml_predictor/infer.py": {"role": "MLPredictor — quantile forecast per TF. Batch via _raw_predict (fast); per-row _predict_tf is ~1000x slower.", "publicApi": ["get_ml_predictor()", "MLPredictor.predict_all_tf(ticker, live_price, today_high, news_score)", "MLPredictor._raw_predict(tf, X)", "MLPredictor._derive(...)"]},
|
| 78 |
+
"ml_predictor/train.py": {"role": "Fit 21 estimators + manifest.json (offline)", "publicApi": ["train"]},
|
| 79 |
+
"ml_predictor/dataset.py": {"role": "Build training_data.csv from ohlcv cache (offline). Labels up/dn = max/min excursion; dir = excess-of-Nifty.", "publicApi": ["_fwd_labels(...)"]}
|
| 80 |
+
},
|
| 81 |
+
"flaskEndpoints": {
|
| 82 |
+
"predictions": ["POST /api/predict", "POST /api/rank", "GET /api/top5", "GET /api/watchlist-picks", "GET /api/watchlist-pick/<ticker>", "GET /api/universe", "POST /api/universe/refresh", "GET /api/search", "GET /api/chart/<ticker>", "GET /api/live-price/<ticker>", "GET /api/ml-predict/<ticker>"],
|
| 83 |
+
"context": ["GET /api/sector-pulse", "GET /api/fundamentals/<ticker>", "GET /api/portfolio", "GET /api/portfolio-insight/<ticker>", "GET /api/signal-accuracy"],
|
| 84 |
+
"paperTrading": ["GET /api/open-trades", "GET /api/trades/open", "GET /api/trades/history", "POST /api/trades", "POST /api/trades/<id>/close", "GET /api/trades/<id>/price", "POST /api/trades/check-stops", "GET /api/orders/pending", "POST /api/orders/check", "POST /api/orders/<id>/cancel", "GET|POST /api/watchlist", "DELETE /api/watchlist/<ticker>"],
|
| 85 |
+
"validation": ["GET /api/prediction-snapshots", "GET /api/prediction-validation", "GET /api/prediction-misses", "GET /api/validation/pending", "POST /api/validation/execute", "GET /api/validation/summary", "GET /api/postmortems", "POST /api/postmortem"]
|
| 86 |
+
},
|
| 87 |
+
"criticalConstraintsDoNotBreak": [
|
| 88 |
+
"predict_stock_v2 / rank_stocks_v2 / timeframe_to_dates signatures (used by MCP + Flask).",
|
| 89 |
+
"data_sources.fetch_ohlcv / fetch_live_price signatures.",
|
| 90 |
+
"universe.get_universe, fundamentals.get_fundamentals, sector_pulse.get_sector_pulse, fred_data.get_fred_macro/get_fred_gate signatures.",
|
| 91 |
+
"ml_predictor: compute_features, MLPredictor.predict_all_tf, MLPredictor.predict signatures.",
|
| 92 |
+
"trial_run.py strategy stats in predictor_core._STRATEGY_STATS_DEFAULT are NSE-verified — do not change without re-running the backtest.",
|
| 93 |
+
"Do not add lightgbm/xgboost/torch (HF Spaces image safety).",
|
| 94 |
+
"Do not put data files behind pathlib.Path(__file__).parent; do not rely on file/pickle caches surviving HF restarts."
|
| 95 |
+
],
|
| 96 |
+
"codeGenerationGuidelines": {
|
| 97 |
+
"paths": "os.path.dirname(__file__) or HF /data paths; never pathlib(__file__).parent for data.",
|
| 98 |
+
"persistence": "SQLite (paper_trading.db) for anything that must survive HF restarts — not pickle/file caches.",
|
| 99 |
+
"frontend": "Inline JS/CSS only (HF CSP blocks external URLs). Bump cache-buster ?v=YYYYMMDD<letter> in templates/index.html on JS/CSS change.",
|
| 100 |
+
"stability": "Keep listed public signatures stable; prod prediction path must not import research/.",
|
| 101 |
+
"ml": "Batch model inference with MLPredictor._raw_predict over a full matrix; never loop _predict_tf per row in bulk jobs.",
|
| 102 |
+
"testing": "Add/extend tests/test_api_contract.py for new endpoints (schema + field types).",
|
| 103 |
+
"backtestMetrics": "Distinguish MidHit (band-midpoint, ~90%, soft) from DirAcc/DirHit (directional, ~40-50%, the real metric). Use training_data_extra.csv (has 12 extra features); training_data.csv lacks them."
|
| 104 |
+
},
|
| 105 |
+
"envVars": {
|
| 106 |
+
"llmKeys": ["OPENROUTER_API_KEY", "GROQ_API_KEY", "CEREBRAS_API_KEY", "HF_TOKEN", "GEMINI_API_KEY", "SAMBANOVA_API_KEY", "NVIDIA_API_KEY", "OLLAMA_ENDPOINT", "OLLAMA_MODEL"],
|
| 107 |
+
"dataKeys": ["FRED_API_KEY (optional)", "ALPHA_VANTAGE_API_KEY (optional)"],
|
| 108 |
+
"unused": ["GITHUB_TOKEN (GitHub Models removed)", "ANTHROPIC_API_KEY (currently empty)"],
|
| 109 |
+
"tuning": ["BACKTEST_LLM_PACE_SECS", "ML_EXCESS_LABELS", "ML_INTRADAY_FAR_MULT/MED_MULT/NEAR_MULT", "HF_ML_MODEL_REPO_ID"]
|
| 110 |
+
},
|
| 111 |
+
"researchScripts": {
|
| 112 |
+
"note": "Offline analysis only; never imported by the production prediction path.",
|
| 113 |
+
"key": ["research/backtest.py (LLM prompt accuracy 1D/3D/5D)", "research/ml_backtest.py (ML accuracy + target-exit P&L; authoritative DirAcc/MidHit table)", "research/ml_selection_backtest.py (top-N selection edge)", "research/ml_intraday_backtest.py (true 15-min intraday)", "research/validate_on_trades.py (validate on real paper-trade dates)", "research/strategy_combo_swing.py (strategy-confluence swing study — batched inference)"]
|
| 114 |
+
},
|
| 115 |
+
"knowledgeBaseAndMemory": {
|
| 116 |
+
"livingTruth": ["CLAUDE.md (authoritative architecture + pipeline + calibration notes)", "project_context.json (this file — structured index)"],
|
| 117 |
+
"memoryDir": "memory/ (thin pointers + legacy findings)",
|
| 118 |
+
"repoScopedNotes": "/memories/repo/ (agent notes: metrics, calibration, provider chain, gotchas)",
|
| 119 |
+
"refreshPolicy": "Update CLAUDE.md + project_context.json after each meaningful feature (see .github/skills/context-updater)."
|
| 120 |
+
},
|
| 121 |
+
"contextMetadata": {
|
| 122 |
+
"generatedDate": "2026-07-24",
|
| 123 |
+
"commitHash": "4bb9928",
|
| 124 |
+
"pythonFilesAtRoot": 30,
|
| 125 |
+
"source": "Derived from CLAUDE.md (633 lines) + repository structure",
|
| 126 |
+
"maturity": "Established/Enterprise (30+ root modules, ml_predictor package, research suite)",
|
| 127 |
+
"pathPolicy": "Relative paths only; no machine-specific or personal directories."
|
| 128 |
+
}
|
| 129 |
+
}
|
research/ml_selection_backtest.py
CHANGED
|
@@ -37,6 +37,7 @@ if _PROJ_ROOT not in sys.path:
|
|
| 37 |
from ml_predictor.features import FEATURE_COLUMNS # noqa: E402
|
| 38 |
from ml_predictor.infer import MLPredictor # noqa: E402
|
| 39 |
from ml_predictor.dataset import _cached_tickers, _load_ticker, DEFAULT_STEP # noqa: E402
|
|
|
|
| 40 |
|
| 41 |
DEFAULT_CSV = os.path.join(_PROJ_ROOT, "ml_predictor", "training_data.csv")
|
| 42 |
OUT_CSV = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ml_selection_results.csv")
|
|
@@ -102,7 +103,8 @@ def _fwd_ret(close: pd.Series, date: pd.Timestamp, h: int) -> float:
|
|
| 102 |
|
| 103 |
def run(csv_path: str = DEFAULT_CSV, top_n: int = 10, step: int = 1,
|
| 104 |
one_date: str | None = None, rank_mode: str = "expmove",
|
| 105 |
-
min_conf: str = "LOW", filters: set | None = None
|
|
|
|
| 106 |
filters = filters or set()
|
| 107 |
predictor = MLPredictor()
|
| 108 |
if not predictor.available:
|
|
@@ -111,7 +113,12 @@ def run(csv_path: str = DEFAULT_CSV, top_n: int = 10, step: int = 1,
|
|
| 111 |
df = pd.read_csv(csv_path)
|
| 112 |
df["date"] = pd.to_datetime(df["date"])
|
| 113 |
holdout_start = predictor.manifest.get("holdout_start")
|
| 114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
print(f" Model: rank by {_SELECTOR_TF} BULLISH · mode={rank_mode} · min_conf={min_conf} · "
|
| 116 |
f"filters={sorted(filters) or 'none'} · top-{top_n} picks/day · cost {ROUND_TRIP_COST_PCT}% round-trip")
|
| 117 |
print(f" Out-of-sample rows: {len(oos):,} · loading close series for realized fwd returns…")
|
|
@@ -198,10 +205,51 @@ def run(csv_path: str = DEFAULT_CSV, top_n: int = 10, step: int = 1,
|
|
| 198 |
_one_day_report(one_date, picks_df, days_df)
|
| 199 |
else:
|
| 200 |
_summary(days_df, picks_df, top_n)
|
|
|
|
|
|
|
| 201 |
print(f"\n ✓ Wrote per-pick detail → {OUT_CSV}")
|
| 202 |
return days_df
|
| 203 |
|
| 204 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
def _summary(days_df: pd.DataFrame, picks_df: pd.DataFrame, top_n: int):
|
| 206 |
active = days_df[days_df["n_picks"] > 0]
|
| 207 |
print("\n" + "=" * 78)
|
|
@@ -263,10 +311,13 @@ def main():
|
|
| 263 |
help="only pick stocks at/above this confidence")
|
| 264 |
ap.add_argument("--filters", default="", help="comma-separated quality gates: "
|
| 265 |
"trend,momentum,adx,trigger,lowvol,notob")
|
|
|
|
|
|
|
| 266 |
args = ap.parse_args()
|
| 267 |
filters = {f.strip() for f in args.filters.split(",") if f.strip()}
|
| 268 |
run(args.csv, top_n=args.top, step=args.step, one_date=args.date,
|
| 269 |
-
rank_mode=args.rank, min_conf=args.min_conf, filters=filters
|
|
|
|
| 270 |
|
| 271 |
|
| 272 |
if __name__ == "__main__":
|
|
|
|
| 37 |
from ml_predictor.features import FEATURE_COLUMNS # noqa: E402
|
| 38 |
from ml_predictor.infer import MLPredictor # noqa: E402
|
| 39 |
from ml_predictor.dataset import _cached_tickers, _load_ticker, DEFAULT_STEP # noqa: E402
|
| 40 |
+
from research.strategy_validation_funnel import _sharpe, _max_drawdown # noqa: E402
|
| 41 |
|
| 42 |
DEFAULT_CSV = os.path.join(_PROJ_ROOT, "ml_predictor", "training_data.csv")
|
| 43 |
OUT_CSV = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ml_selection_results.csv")
|
|
|
|
| 103 |
|
| 104 |
def run(csv_path: str = DEFAULT_CSV, top_n: int = 10, step: int = 1,
|
| 105 |
one_date: str | None = None, rank_mode: str = "expmove",
|
| 106 |
+
min_conf: str = "LOW", filters: set | None = None,
|
| 107 |
+
six_filter: bool = False) -> pd.DataFrame:
|
| 108 |
filters = filters or set()
|
| 109 |
predictor = MLPredictor()
|
| 110 |
if not predictor.available:
|
|
|
|
| 113 |
df = pd.read_csv(csv_path)
|
| 114 |
df["date"] = pd.to_datetime(df["date"])
|
| 115 |
holdout_start = predictor.manifest.get("holdout_start")
|
| 116 |
+
# --six-filter validates the ML SELECTOR itself through the doc's funnel, which needs an
|
| 117 |
+
# in-sample (IS) leg too — so evaluate the FULL date range and split at holdout_start.
|
| 118 |
+
if six_filter:
|
| 119 |
+
oos = df.copy()
|
| 120 |
+
else:
|
| 121 |
+
oos = df[df["date"] >= pd.to_datetime(holdout_start)].copy() if holdout_start else df
|
| 122 |
print(f" Model: rank by {_SELECTOR_TF} BULLISH · mode={rank_mode} · min_conf={min_conf} · "
|
| 123 |
f"filters={sorted(filters) or 'none'} · top-{top_n} picks/day · cost {ROUND_TRIP_COST_PCT}% round-trip")
|
| 124 |
print(f" Out-of-sample rows: {len(oos):,} · loading close series for realized fwd returns…")
|
|
|
|
| 205 |
_one_day_report(one_date, picks_df, days_df)
|
| 206 |
else:
|
| 207 |
_summary(days_df, picks_df, top_n)
|
| 208 |
+
if six_filter and not one_date:
|
| 209 |
+
_six_filter_verdict(days_df, pd.to_datetime(holdout_start) if holdout_start else None)
|
| 210 |
print(f"\n ✓ Wrote per-pick detail → {OUT_CSV}")
|
| 211 |
return days_df
|
| 212 |
|
| 213 |
|
| 214 |
+
def _six_filter_verdict(days_df: pd.DataFrame, split):
|
| 215 |
+
"""Run the '9,120-backtest' doc's 6-filter funnel on the ML SELECTION basket itself.
|
| 216 |
+
Treats each decision day's top-N basket 3-day return as one trade; splits IS/OOS at the
|
| 217 |
+
model's holdout_start. NOTE: the OOS leg is only as long as the manifest holdout window —
|
| 218 |
+
if that is a handful of days the verdict is directional, not conclusive."""
|
| 219 |
+
HOLD = 3
|
| 220 |
+
d = days_df.copy()
|
| 221 |
+
d = d[d["n_picks"] > 0]
|
| 222 |
+
d["_dt"] = pd.to_datetime(d["date"])
|
| 223 |
+
col = "basket_3d"
|
| 224 |
+
if split is None:
|
| 225 |
+
is_r = []
|
| 226 |
+
oos_r = list(d[col].dropna())
|
| 227 |
+
else:
|
| 228 |
+
is_r = list(d[d["_dt"] < split][col].dropna())
|
| 229 |
+
oos_r = list(d[d["_dt"] >= split][col].dropna())
|
| 230 |
+
is_s, oos_s = _sharpe(is_r, HOLD), _sharpe(oos_r, HOLD)
|
| 231 |
+
mdd = _max_drawdown(oos_r)
|
| 232 |
+
n_oos = len(oos_r)
|
| 233 |
+
checks = [
|
| 234 |
+
("[01] OOS Sharpe > 0.5", oos_s > 0.5, f"{oos_s:+.2f}"),
|
| 235 |
+
("[02] Max DD better than -35%", mdd > -35.0, f"{mdd:.1f}%"),
|
| 236 |
+
("[03] OOS Sharpe < 2.5 (not absurd)", oos_s < 2.5, f"{oos_s:+.2f}"),
|
| 237 |
+
("[04] OOS <= IS*1.3 + 0.5 (not overfit)", oos_s <= is_s * 1.3 + 0.5, f"OOS {oos_s:+.2f} / IS {is_s:+.2f}"),
|
| 238 |
+
("[05] At least 30 OOS trades", n_oos >= 30, f"{n_oos}"),
|
| 239 |
+
("[06] IS Sharpe > 0", is_s > 0, f"{is_s:+.2f}"),
|
| 240 |
+
]
|
| 241 |
+
print("\n" + "=" * 78)
|
| 242 |
+
print(" 6-FILTER VALIDATION — is the ML top-N SELECTION strategy a real OOS edge?")
|
| 243 |
+
print(f" IS trades={len(is_r)} OOS trades={n_oos} (3-day basket return per decision day)")
|
| 244 |
+
print("=" * 78)
|
| 245 |
+
for label, ok, val in checks:
|
| 246 |
+
print(f" {'PASS' if ok else 'FAIL'} {label:<42} {val}")
|
| 247 |
+
verdict = "SURVIVES all 6 filters" if all(c[1] for c in checks) else "does NOT survive"
|
| 248 |
+
print(f"\n → The ML selection strategy {verdict}.")
|
| 249 |
+
if n_oos < 30:
|
| 250 |
+
print(" ⚠ OOS window is short (manifest holdout is small) — treat as directional only.")
|
| 251 |
+
|
| 252 |
+
|
| 253 |
def _summary(days_df: pd.DataFrame, picks_df: pd.DataFrame, top_n: int):
|
| 254 |
active = days_df[days_df["n_picks"] > 0]
|
| 255 |
print("\n" + "=" * 78)
|
|
|
|
| 311 |
help="only pick stocks at/above this confidence")
|
| 312 |
ap.add_argument("--filters", default="", help="comma-separated quality gates: "
|
| 313 |
"trend,momentum,adx,trigger,lowvol,notob")
|
| 314 |
+
ap.add_argument("--six-filter", action="store_true",
|
| 315 |
+
help="validate the ML selection basket through the doc's 6-filter funnel (IS vs OOS)")
|
| 316 |
args = ap.parse_args()
|
| 317 |
filters = {f.strip() for f in args.filters.split(",") if f.strip()}
|
| 318 |
run(args.csv, top_n=args.top, step=args.step, one_date=args.date,
|
| 319 |
+
rank_mode=args.rank, min_conf=args.min_conf, filters=filters,
|
| 320 |
+
six_filter=args.six_filter)
|
| 321 |
|
| 322 |
|
| 323 |
if __name__ == "__main__":
|
research/ml_selection_results.csv
CHANGED
|
@@ -1,63 +1,722 @@
|
|
| 1 |
date,ticker,exp_up_q50,confidence,ret_1d_net,ret_3d_net,ret_5d_net
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
date,ticker,exp_up_q50,confidence,ret_1d_net,ret_3d_net,ret_5d_net
|
| 2 |
+
2022-05-12,AYMSYNTEX.NS,14.38,MEDIUM,4.784,10.444,6.356
|
| 3 |
+
2022-05-12,ZENITHSTL.NS,13.84,MEDIUM,3.824,14.133,24.442
|
| 4 |
+
2022-05-12,LAMBODHARA.NS,13.84,MEDIUM,5.141,14.334,8.581
|
| 5 |
+
2022-05-12,ADSL.NS,13.8,MEDIUM,9.533,27.297,27.529
|
| 6 |
+
2022-05-12,SONAL.NS,13.65,MEDIUM,16.16,17.868,11.035
|
| 7 |
+
2022-05-19,ZENITHSTL.NS,13.48,MEDIUM,4.659,14.576,25.32
|
| 8 |
+
2022-05-19,KOHINOOR.NS,13.01,MEDIUM,4.561,15.151,26.783
|
| 9 |
+
2022-05-19,ASIANTILES.NS,10.44,MEDIUM,0.441,-12.491,-19.493
|
| 10 |
+
2022-05-19,NITIRAJ.NS,9.04,MEDIUM,-3.066,-3.211,-8.306
|
| 11 |
+
2022-05-19,ATLANTAA.NS,8.55,LOW,3.379,2.71,-2.976
|
| 12 |
+
2022-06-09,SIL.NS,13.58,MEDIUM,4.692,15.301,15.145
|
| 13 |
+
2022-06-09,ZENITHSTL.NS,13.42,MEDIUM,-5.259,-13.936,-21.788
|
| 14 |
+
2022-06-09,KOHINOOR.NS,12.95,MEDIUM,4.675,15.383,27.187
|
| 15 |
+
2022-06-09,ACCURACY.NS,12.44,MEDIUM,19.649,24.719,19.522
|
| 16 |
+
2022-06-09,GICL.NS,10.92,MEDIUM,4.633,14.947,26.606
|
| 17 |
+
2022-06-14,CHEMPLASTS.NS,6.81,MEDIUM,-1.15,-10.252,-8.31
|
| 18 |
+
2022-06-14,APTUS.NS,6.7,MEDIUM,0.221,2.804,5.257
|
| 19 |
+
2022-06-30,KOHINOOR.NS,12.93,MEDIUM,-5.263,-14.499,-22.839
|
| 20 |
+
2022-06-30,HECPROJECT.NS,10.26,MEDIUM,-6.952,2.275,-1.802
|
| 21 |
+
2022-06-30,IMAGICAA.NS,10.24,MEDIUM,4.7,15.45,4.2
|
| 22 |
+
2022-06-30,LASA.NS,9.61,MEDIUM,19.544,14.291,20.128
|
| 23 |
+
2022-06-30,BCG.NS,8.48,MEDIUM,4.644,15.17,26.973
|
| 24 |
+
2022-07-21,SPCENET.NS,13.19,MEDIUM,4.017,14.088,24.88
|
| 25 |
+
2022-07-21,PCJEWELLER.NS,12.12,MEDIUM,-5.252,-14.462,-22.715
|
| 26 |
+
2022-07-21,KEEPLEARN.NS,12.05,MEDIUM,3.271,12.2,22.914
|
| 27 |
+
2022-07-21,REGENCERAM.NS,11.8,MEDIUM,-0.3,8.302,24.431
|
| 28 |
+
2022-07-21,AKASH.NS,11.45,MEDIUM,4.613,15.258,27.029
|
| 29 |
+
2022-08-12,KEEPLEARN.NS,13.54,MEDIUM,4.417,13.851,25.172
|
| 30 |
+
2022-08-12,SPCENET.NS,13.15,MEDIUM,4.351,14.816,26.057
|
| 31 |
+
2022-08-12,REGENCERAM.NS,12.75,MEDIUM,4.645,15.085,26.623
|
| 32 |
+
2022-08-12,JETFREIGHT.NS,9.97,MEDIUM,-2.388,-7.261,-3.316
|
| 33 |
+
2022-08-12,EKC.NS,8.92,MEDIUM,2.52,6.097,-3.625
|
| 34 |
+
2022-09-06,KEEPLEARN.NS,13.89,MEDIUM,4.462,14.938,26.367
|
| 35 |
+
2022-09-06,REGENCERAM.NS,13.31,MEDIUM,4.565,15.105,26.727
|
| 36 |
+
2022-09-06,SAKHTISUG.NS,12.91,MEDIUM,-7.621,-16.371,-17.621
|
| 37 |
+
2022-09-06,SERVOTECH.NS,10.76,MEDIUM,4.665,15.391,15.657
|
| 38 |
+
2022-09-06,KRITIKA.NS,10.19,MEDIUM,4.609,15.268,14.847
|
| 39 |
+
2022-09-19,PRITIKAUTO.NS,5.71,LOW,3.49,-0.008,-7.589
|
| 40 |
+
2022-09-27,REGENCERAM.NS,13.17,MEDIUM,4.575,4.311,-5.702
|
| 41 |
+
2022-09-27,MKPL.NS,11.81,MEDIUM,4.692,15.448,27.291
|
| 42 |
+
2022-09-27,VARDHACRLC.NS,9.58,MEDIUM,2.884,2.785,3.979
|
| 43 |
+
2022-09-27,63MOONS.NS,8.89,MEDIUM,4.212,2.772,7.412
|
| 44 |
+
2022-09-27,SIMPLEXINF.NS,8.89,MEDIUM,1.899,7.791,3.13
|
| 45 |
+
2022-09-30,FINOPB.NS,4.3,LOW,-3.34,-7.489,-9.708
|
| 46 |
+
2022-10-06,KOTYARK.NS,6.69,MEDIUM,4.659,3.591,4.438
|
| 47 |
+
2022-10-06,SMLT.NS,6.24,LOW,-1.495,-5.117,-6.543
|
| 48 |
+
2022-10-14,PASUPTAC.NS,4.58,LOW,0.462,-0.148,0.005
|
| 49 |
+
2022-10-19,RKDL.NS,10.81,MEDIUM,-5.146,-14.177,-14.397
|
| 50 |
+
2022-10-19,ORIENTALTL.NS,10.2,MEDIUM,-3.729,-2.586,-0.3
|
| 51 |
+
2022-10-19,DELPHIFX.NS,10.02,LOW,-6.45,-8.276,-3.765
|
| 52 |
+
2022-10-19,LANDSMILL.NS,9.87,MEDIUM,-0.3,-0.3,-0.3
|
| 53 |
+
2022-10-19,ZENITHEXPO.NS,9.84,MEDIUM,-5.291,-14.528,-22.876
|
| 54 |
+
2022-10-28,NYKAA.NS,8.55,MEDIUM,16.903,16.939,12.013
|
| 55 |
+
2022-11-02,KPIGREEN.NS,5.26,LOW,-2.244,0.966,14.701
|
| 56 |
+
2022-11-07,EUROBOND.NS,4.53,LOW,-2.269,-4.238,-3.945
|
| 57 |
+
2022-11-11,MKPL.NS,13.14,MEDIUM,4.7,14.43,22.738
|
| 58 |
+
2022-11-11,RGL.NS,11.78,MEDIUM,-6.564,9.013,1.529
|
| 59 |
+
2022-11-11,KARMAENG.NS,9.32,LOW,4.642,0.194,-4.748
|
| 60 |
+
2022-11-11,AURIGROW.NS,9.31,MEDIUM,-5.062,-12.998,-6.649
|
| 61 |
+
2022-11-11,EVERESTIND.NS,9.15,MEDIUM,-3.918,5.015,1.566
|
| 62 |
+
2022-11-16,ROLEXRINGS.NS,3.73,LOW,2.612,5.848,3.893
|
| 63 |
+
2022-11-21,KOTYARK.NS,6.21,LOW,0.63,-1.152,-1.374
|
| 64 |
+
2022-11-29,PAYTM.NS,6.32,LOW,-0.755,10.663,6.055
|
| 65 |
+
2022-11-29,DATAPATTNS.NS,4.14,LOW,2.971,3.581,0.936
|
| 66 |
+
2022-12-02,SKIPPER.NS,11.24,MEDIUM,6.005,5.544,18.522
|
| 67 |
+
2022-12-02,WILLAMAGOR.NS,10.66,MEDIUM,4.542,-5.353,-13.563
|
| 68 |
+
2022-12-02,PIGL.NS,10.43,MEDIUM,-5.225,-12.24,-16.419
|
| 69 |
+
2022-12-02,NILASPACES.NS,10.14,MEDIUM,-3.871,-5.062,-9.824
|
| 70 |
+
2022-12-02,DRCSYSTEMS.NS,9.58,MEDIUM,1.713,11.378,8.291
|
| 71 |
+
2022-12-12,KOTYARK.NS,5.52,LOW,3.7,-0.899,3.137
|
| 72 |
+
2022-12-23,CELEBRITY.NS,14.39,MEDIUM,19.7,21.033,19.367
|
| 73 |
+
2022-12-23,WEIZMANIND.NS,13.99,MEDIUM,17.721,21.386,23.891
|
| 74 |
+
2022-12-23,SIMPLEXINF.NS,13.58,MEDIUM,5.628,12.236,11.459
|
| 75 |
+
2022-12-23,BRNL.NS,13.56,MEDIUM,16.941,24.465,22.741
|
| 76 |
+
2022-12-23,DEVIT.NS,13.41,MEDIUM,19.657,33.585,36.626
|
| 77 |
+
2022-12-28,AWL.NS,4.31,LOW,4.352,4.135,0.746
|
| 78 |
+
2022-12-28,IONEXCHANG.NS,3.99,LOW,-2.613,3.19,2.829
|
| 79 |
+
2023-01-02,KOTYARK.NS,8.48,MEDIUM,1.443,7.508,2.321
|
| 80 |
+
2023-01-02,BAJAJHCARE.NS,3.47,LOW,0.254,-3.998,-6.212
|
| 81 |
+
2023-01-13,SPECTRUM.NS,9.34,MEDIUM,4.664,9.882,21.126
|
| 82 |
+
2023-01-13,TOUCHWOOD.NS,8.96,MEDIUM,4.677,15.397,27.227
|
| 83 |
+
2023-01-13,KAMATHOTEL.NS,8.6,LOW,-2.311,5.158,7.373
|
| 84 |
+
2023-01-13,BTML.NS,8.54,MEDIUM,2.6,7.059,-0.768
|
| 85 |
+
2023-01-13,SHRADHA.NS,8.3,MEDIUM,4.686,15.41,26.898
|
| 86 |
+
2023-01-23,NYKAA.NS,5.94,MEDIUM,7.395,0.622,9.279
|
| 87 |
+
2023-01-23,NRL.NS,4.68,LOW,-0.185,-18.159,-11.007
|
| 88 |
+
2023-01-27,POLICYBZR.NS,4.95,LOW,4.407,-0.063,6.288
|
| 89 |
+
2023-02-01,MEDICAMEQ.NS,6.31,MEDIUM,1.886,-2.389,1.463
|
| 90 |
+
2023-02-01,GMRP&UI.NS,5.02,LOW,-1.083,-0.3,-1.083
|
| 91 |
+
2023-02-01,ETERNAL.NS,4.32,LOW,-1.65,-1.131,12.473
|
| 92 |
+
2023-02-01,STARHEALTH.NS,4.3,LOW,2.7,3.985,5.53
|
| 93 |
+
2023-02-06,SWANCORP.NS,11.4,MEDIUM,3.124,6.528,6.895
|
| 94 |
+
2023-02-06,SECURKLOUD.NS,10.46,MEDIUM,1.271,-1.609,-6.19
|
| 95 |
+
2023-02-06,PAISALO.NS,10.41,MEDIUM,-2.41,-2.086,-5.332
|
| 96 |
+
2023-02-06,PANACHE.NS,9.62,MEDIUM,4.659,10.385,20.774
|
| 97 |
+
2023-02-06,HECPROJECT.NS,9.5,MEDIUM,-3.801,-4.714,-2.126
|
| 98 |
+
2023-02-09,KRISHIVAL.NS,6.58,LOW,4.093,0.784,0.784
|
| 99 |
+
2023-02-09,AWL.NS,5.75,MEDIUM,-1.164,-10.83,-5.372
|
| 100 |
+
2023-02-09,OBCL.NS,5.57,LOW,-2.542,-6.258,-13.817
|
| 101 |
+
2023-02-14,BAJAJHCARE.NS,6.23,LOW,-2.246,-3.738,-4.875
|
| 102 |
+
2023-02-14,RITCO.NS,4.67,LOW,-3.492,-0.028,-4.375
|
| 103 |
+
2023-02-17,SBC.NS,8.3,MEDIUM,4.144,2.24,4.144
|
| 104 |
+
2023-02-17,NPST.NS,5.21,LOW,-0.3,15.442,21.729
|
| 105 |
+
2023-02-17,GOCOLORS.NS,4.13,LOW,2.694,-1.145,-4.593
|
| 106 |
+
2023-02-22,GUJRAFFIA.NS,5.63,LOW,2.652,-1.592,-2.514
|
| 107 |
+
2023-02-22,RATEGAIN.NS,4.34,LOW,-0.981,-1.952,1.019
|
| 108 |
+
2023-02-22,DATAPATTNS.NS,4.22,LOW,-3.642,-6.813,3.601
|
| 109 |
+
2023-02-27,TREEHOUSE.NS,11.98,MEDIUM,9.61,31.682,34.385
|
| 110 |
+
2023-02-27,SAMBHAAV.NS,11.87,MEDIUM,-0.3,12.427,8.791
|
| 111 |
+
2023-02-27,SOMATEX.NS,11.71,MEDIUM,4.689,15.346,21.695
|
| 112 |
+
2023-02-27,NECCLTD.NS,11.47,MEDIUM,-3.927,-9.626,-4.704
|
| 113 |
+
2023-02-27,SPIC.NS,10.86,MEDIUM,-0.127,2.291,8.422
|
| 114 |
+
2023-03-02,OBCL.NS,9.66,MEDIUM,-5.661,16.854,54.281
|
| 115 |
+
2023-03-02,AWL.NS,4.95,LOW,4.692,15.441,13.459
|
| 116 |
+
2023-03-13,PRITIKAUTO.NS,5.03,LOW,1.68,0.36,-5.581
|
| 117 |
+
2023-03-16,KRISHNADEF.NS,3.83,LOW,-1.356,-1.039,-1.004
|
| 118 |
+
2023-03-21,SOMATEX.NS,12.9,MEDIUM,4.643,4.298,-5.817
|
| 119 |
+
2023-03-21,ARIHANTCAP.NS,10.09,MEDIUM,0.959,-2.678,-4.776
|
| 120 |
+
2023-03-21,ABMINTLLTD.NS,10.01,MEDIUM,4.693,-5.504,-14.716
|
| 121 |
+
2023-03-21,ADANIGREEN.NS,9.76,MEDIUM,4.7,15.221,4.705
|
| 122 |
+
2023-03-21,SUMEETINDS.NS,9.39,MEDIUM,4.245,13.336,4.245
|
| 123 |
+
2023-03-24,OBCL.NS,6.43,MEDIUM,-2.956,-8.542,-6.436
|
| 124 |
+
2023-03-29,NRL.NS,9.3,MEDIUM,5.739,17.516,17.013
|
| 125 |
+
2023-03-29,CARTRADE.NS,6.36,LOW,5.983,9.753,8.128
|
| 126 |
+
2023-03-29,BAJAJHCARE.NS,6.13,MEDIUM,6.024,7.115,9.263
|
| 127 |
+
2023-03-29,INDOBORAX.NS,5.59,LOW,1.345,8.424,9.421
|
| 128 |
+
2023-03-29,MEDPLUS.NS,5.53,LOW,2.088,0.614,5.492
|
| 129 |
+
2023-04-05,EMUDHRA.NS,3.89,LOW,5.12,10.233,5.559
|
| 130 |
+
2023-04-17,TPHQ.NS,12.39,MEDIUM,4.369,14.695,25.96
|
| 131 |
+
2023-04-17,WSI.NS,12.04,MEDIUM,4.611,15.269,27.077
|
| 132 |
+
2023-04-17,ABINFRA.NS,11.5,MEDIUM,4.567,15.187,14.892
|
| 133 |
+
2023-04-17,REGENCERAM.NS,10.82,MEDIUM,4.582,4.402,-5.725
|
| 134 |
+
2023-04-17,MARINE.NS,10.38,MEDIUM,-0.3,2.041,-0.523
|
| 135 |
+
2023-04-20,GUJRAFFIA.NS,7.56,LOW,-2.779,-4.928,-9.556
|
| 136 |
+
2023-04-20,AXITA.NS,2.79,MEDIUM,1.284,6.036,11.615
|
| 137 |
+
2023-04-25,GRAUWEIL.NS,4.33,LOW,2.696,1.788,0.926
|
| 138 |
+
2023-04-25,NYKAA.NS,3.71,LOW,-0.3,4.531,5.771
|
| 139 |
+
2023-05-09,TPHQ.NS,13.58,MEDIUM,4.561,14.978,26.552
|
| 140 |
+
2023-05-09,MICEL.NS,11.24,MEDIUM,2.542,-7.535,-7.794
|
| 141 |
+
2023-05-09,ASMS.NS,10.06,MEDIUM,4.528,14.183,25.217
|
| 142 |
+
2023-05-09,MAGNUM.NS,9.72,LOW,4.563,15.202,26.904
|
| 143 |
+
2023-05-09,SHRIPISTON.NS,8.97,MEDIUM,4.7,15.456,23.255
|
| 144 |
+
2023-05-22,FIBERWEB.NS,4.39,LOW,4.638,-2.152,-1.072
|
| 145 |
+
2023-05-22,MALLCOM.NS,4.15,LOW,-1.988,-1.545,13.436
|
| 146 |
+
2023-05-30,VENUSREM.NS,11.33,MEDIUM,0.397,1.58,-3.764
|
| 147 |
+
2023-05-30,ACL.NS,10.96,MEDIUM,4.679,15.173,10.73
|
| 148 |
+
2023-05-30,JYOTISTRUC.NS,10.6,MEDIUM,4.298,4.872,4.298
|
| 149 |
+
2023-05-30,LAMBODHARA.NS,9.49,MEDIUM,1.568,-1.221,-1.058
|
| 150 |
+
2023-05-30,JITFINFRA.NS,9.43,MEDIUM,4.693,15.437,22.142
|
| 151 |
+
2023-06-02,AXITA.NS,4.58,LOW,-5.308,-7.813,-8.647
|
| 152 |
+
2023-06-02,RAINBOW.NS,3.84,LOW,-0.959,3.646,-1.158
|
| 153 |
+
2023-06-20,JITFINFRA.NS,12.27,MEDIUM,4.691,15.44,27.291
|
| 154 |
+
2023-06-20,KAVDEFENCE.NS,10.55,MEDIUM,-5.012,-13.913,-22.29
|
| 155 |
+
2023-06-20,MINDTECK.NS,10.48,LOW,-3.542,-12.128,-13.599
|
| 156 |
+
2023-06-20,CSLFINANCE.NS,10.42,LOW,-2.641,0.648,-0.346
|
| 157 |
+
2023-06-20,BCG.NS,10.18,MEDIUM,4.685,-5.436,-14.65
|
| 158 |
+
2023-06-23,AXITA.NS,4.35,LOW,0.268,-1.626,-1.626
|
| 159 |
+
2023-06-28,ASIANENE.NS,7.52,LOW,4.667,5.248,5.248
|
| 160 |
+
2023-06-28,HARDWYN.NS,6.97,MEDIUM,4.614,15.238,14.308
|
| 161 |
+
2023-07-04,CHOICEIN.NS,4.59,LOW,0.899,1.847,0.944
|
| 162 |
+
2023-07-12,GOLDTECH.NS,10.82,MEDIUM,-5.284,-0.3,-0.3
|
| 163 |
+
2023-07-12,PCJEWELLER.NS,9.96,MEDIUM,-5.214,-14.377,-21.681
|
| 164 |
+
2023-07-12,PREMEXPLN.NS,9.47,MEDIUM,1.944,22.349,25.227
|
| 165 |
+
2023-07-12,SEPC.NS,8.83,LOW,0.152,-2.562,0.605
|
| 166 |
+
2023-07-12,SECURKLOUD.NS,8.82,MEDIUM,9.642,13.033,2.624
|
| 167 |
+
2023-07-20,KOTYARK.NS,5.21,LOW,-3.109,6.126,7.317
|
| 168 |
+
2023-07-20,ROTO.NS,4.58,MEDIUM,-1.877,-3.656,-6.116
|
| 169 |
+
2023-07-25,NPST.NS,6.74,MEDIUM,4.699,15.457,27.319
|
| 170 |
+
2023-07-28,SPORTKING.NS,3.52,LOW,-0.743,-1.236,-1.717
|
| 171 |
+
2023-08-02,ACL.NS,9.31,MEDIUM,0.089,-7.471,-6.434
|
| 172 |
+
2023-08-02,SKMEGGPROD.NS,8.93,LOW,4.696,9.108,20.307
|
| 173 |
+
2023-08-02,KILITCH.NS,8.69,LOW,-2.078,-2.566,-3.905
|
| 174 |
+
2023-08-02,WSI.NS,8.13,MEDIUM,-1.348,-5.215,-8.938
|
| 175 |
+
2023-08-02,JYOTISTRUC.NS,8.09,MEDIUM,4.439,11.548,11.548
|
| 176 |
+
2023-08-07,IONEXCHANG.NS,4.85,MEDIUM,0.29,4.787,7.673
|
| 177 |
+
2023-08-07,EMIL.NS,4.14,LOW,-2.93,2.687,8.082
|
| 178 |
+
2023-08-10,RAMRAT.NS,3.9,LOW,1.712,-3.073,-5.406
|
| 179 |
+
2023-08-16,JSLL.NS,8.42,MEDIUM,4.699,-4.391,-9.595
|
| 180 |
+
2023-08-16,KPIGREEN.NS,4.48,LOW,-1.337,-0.707,-1.578
|
| 181 |
+
2023-08-16,SYRMA.NS,4.04,LOW,2.472,3.785,5.989
|
| 182 |
+
2023-08-21,RATEGAIN.NS,4.36,LOW,3.868,3.587,4.647
|
| 183 |
+
2023-08-24,DOLPHIN.NS,11.19,MEDIUM,4.683,15.442,27.258
|
| 184 |
+
2023-08-24,RKDL.NS,10.85,MEDIUM,4.674,15.407,27.187
|
| 185 |
+
2023-08-24,TNTELE.NS,9.19,MEDIUM,4.7,14.7,26.129
|
| 186 |
+
2023-08-24,IRISDOREME.NS,8.87,MEDIUM,-1.535,-1.467,3.681
|
| 187 |
+
2023-08-24,KALYANIFRG.NS,8.46,MEDIUM,4.692,6.062,16.945
|
| 188 |
+
2023-08-29,VENUSPIPES.NS,4.15,LOW,-0.093,0.71,1.519
|
| 189 |
+
2023-08-29,WINDLAS.NS,3.71,LOW,1.801,8.536,9.426
|
| 190 |
+
2023-08-29,TEGA.NS,3.63,LOW,0.219,2.586,1.02
|
| 191 |
+
2023-09-06,ABCOTS.NS,7.43,MEDIUM,-0.694,-0.694,-1.73
|
| 192 |
+
2023-09-06,EMUDHRA.NS,4.71,MEDIUM,3.183,3.7,-1.725
|
| 193 |
+
2023-09-06,STEELCAS.NS,4.26,LOW,6.736,11.258,6.352
|
| 194 |
+
2023-09-06,NPST.NS,2.97,LOW,-4.239,-7.631,-6.224
|
| 195 |
+
2023-09-14,UNITECH.NS,12.23,MEDIUM,3.404,10.811,21.922
|
| 196 |
+
2023-09-14,DOLPHIN.NS,10.92,MEDIUM,1.693,5.789,10.059
|
| 197 |
+
2023-09-14,LGHL.NS,8.54,MEDIUM,3.758,12.717,17.924
|
| 198 |
+
2023-09-14,THOMASCOTT.NS,7.54,LOW,4.659,8.846,13.199
|
| 199 |
+
2023-09-14,SPMLINFRA.NS,7.51,MEDIUM,4.649,14.447,10.003
|
| 200 |
+
2023-09-20,IONEXCHANG.NS,4.01,LOW,-1.648,-2.745,-3.715
|
| 201 |
+
2023-09-25,GRAUWEIL.NS,3.43,LOW,0.96,2.928,1.039
|
| 202 |
+
2023-09-25,GOYALALUM.NS,2.31,LOW,3.229,13.229,24.406
|
| 203 |
+
2023-09-28,JSLL.NS,4.56,LOW,-1.775,1.8,10.151
|
| 204 |
+
2023-09-28,ABCOTS.NS,3.33,MEDIUM,-0.3,9.486,14.929
|
| 205 |
+
2023-10-04,AKSHAR.NS,6.42,LOW,1.326,0.513,7.017
|
| 206 |
+
2023-10-04,KRISHNADEF.NS,4.39,LOW,7.136,3.736,4.064
|
| 207 |
+
2023-10-09,RUCHINFRA.NS,11.39,MEDIUM,4.7,7.641,3.524
|
| 208 |
+
2023-10-09,STEELXIND.NS,10.95,MEDIUM,5.989,21.084,14.165
|
| 209 |
+
2023-10-09,BEDMUTHA.NS,10.64,MEDIUM,1.863,-2.168,-6.001
|
| 210 |
+
2023-10-09,NKIND.NS,10.43,LOW,19.7,44.785,67.158
|
| 211 |
+
2023-10-09,FLEXITUFF.NS,9.5,MEDIUM,-2.246,-6.039,-9.736
|
| 212 |
+
2023-10-20,RELCHEMQ.NS,4.66,LOW,-0.346,2.834,7.006
|
| 213 |
+
2023-10-20,NPST.NS,3.92,LOW,4.7,14.72,26.479
|
| 214 |
+
2023-10-20,MAPMYINDIA.NS,3.0,LOW,-4.811,0.435,5.032
|
| 215 |
+
2023-10-26,GMRP&UI.NS,6.27,LOW,4.594,0.516,4.431
|
| 216 |
+
2023-10-26,TARSONS.NS,6.26,MEDIUM,3.987,4.714,4.233
|
| 217 |
+
2023-10-26,KAYNES.NS,4.93,MEDIUM,3.527,3.708,2.855
|
| 218 |
+
2023-10-26,PASUPTAC.NS,4.89,LOW,0.476,-3.922,-4.052
|
| 219 |
+
2023-10-26,DCMSRIND.NS,4.41,LOW,1.728,1.444,0.065
|
| 220 |
+
2023-10-31,ABMINTLLTD.NS,9.27,MEDIUM,4.667,4.38,-5.744
|
| 221 |
+
2023-10-31,TICL.NS,8.82,MEDIUM,-0.046,20.82,26.926
|
| 222 |
+
2023-10-31,RMDRIP.NS,8.63,MEDIUM,4.623,15.358,15.035
|
| 223 |
+
2023-10-31,MAANALU.NS,8.43,LOW,3.767,-6.09,3.511
|
| 224 |
+
2023-10-31,CTE.NS,8.08,LOW,-6.322,-6.389,-5.442
|
| 225 |
+
2023-11-03,IONEXCHANG.NS,4.81,LOW,0.296,5.975,4.735
|
| 226 |
+
2023-11-03,KRISHIVAL.NS,4.41,LOW,-0.3,-5.3,-9.748
|
| 227 |
+
2023-11-08,PRUDENT.NS,4.06,LOW,-1.029,1.736,7.178
|
| 228 |
+
2023-11-13,DYCL.NS,7.94,MEDIUM,4.688,8.393,4.034
|
| 229 |
+
2023-11-17,GMRP&UI.NS,7.66,LOW,-5.265,-1.364,0.764
|
| 230 |
+
2023-11-17,RADHIKAJWE.NS,5.99,LOW,5.978,2.727,2.615
|
| 231 |
+
2023-11-17,AKSHAR.NS,4.84,LOW,4.388,14.544,11.419
|
| 232 |
+
2023-11-22,UNITECH.NS,11.37,MEDIUM,4.144,13.033,24.144
|
| 233 |
+
2023-11-22,MADHUCON.NS,10.43,MEDIUM,4.298,-5.472,-6.047
|
| 234 |
+
2023-11-22,KEEPLEARN.NS,10.11,MEDIUM,4.602,2.641,12.445
|
| 235 |
+
2023-11-22,63MOONS.NS,8.69,MEDIUM,4.699,10.417,21.754
|
| 236 |
+
2023-11-22,GAYAHWS.NS,8.39,MEDIUM,-4.146,-7.992,-7.992
|
| 237 |
+
2023-11-28,AWL.NS,7.0,LOW,-0.63,-2.653,8.941
|
| 238 |
+
2023-12-01,ASIANENE.NS,7.66,LOW,4.693,14.542,18.917
|
| 239 |
+
2023-12-01,GATEWAY.NS,4.02,LOW,-1.18,0.58,-0.398
|
| 240 |
+
2023-12-06,JSLL.NS,5.2,LOW,-2.342,5.933,0.638
|
| 241 |
+
2023-12-06,INTLCONV.NS,4.62,LOW,-1.654,-3.603,-3.061
|
| 242 |
+
2023-12-11,GMRP&UI.NS,7.99,MEDIUM,-0.003,4.547,-3.861
|
| 243 |
+
2023-12-11,PAYTM.NS,5.16,MEDIUM,-6.407,-7.991,-6.521
|
| 244 |
+
2023-12-11,DCMSRIND.NS,4.48,LOW,-3.327,0.624,-0.141
|
| 245 |
+
2023-12-14,ICDSLTD.NS,11.22,MEDIUM,4.618,15.274,10.993
|
| 246 |
+
2023-12-14,KAMATHOTEL.NS,9.45,MEDIUM,-5.297,-4.812,-3.948
|
| 247 |
+
2023-12-14,HAVISHA.NS,9.27,MEDIUM,-4.004,-11.411,-7.707
|
| 248 |
+
2023-12-14,DRCSYSTEMS.NS,8.82,MEDIUM,4.62,3.267,-0.669
|
| 249 |
+
2023-12-14,HECPROJECT.NS,8.75,MEDIUM,4.674,15.355,27.205
|
| 250 |
+
2023-12-19,AXITA.NS,4.84,LOW,-6.957,10.643,5.802
|
| 251 |
+
2023-12-22,MONARCH.NS,3.88,LOW,0.736,-2.507,-2.192
|
| 252 |
+
2023-12-28,SBGLP.NS,3.67,LOW,1.274,3.161,1.68
|
| 253 |
+
2024-01-02,HYBRIDFIN.NS,8.55,MEDIUM,4.65,4.65,0.69
|
| 254 |
+
2024-01-05,GTL.NS,10.98,MEDIUM,4.434,4.138,-4.146
|
| 255 |
+
2024-01-05,CGCL.NS,10.04,MEDIUM,14.307,8.073,8.691
|
| 256 |
+
2024-01-05,CUPID.NS,9.88,MEDIUM,4.698,15.437,27.296
|
| 257 |
+
2024-01-05,MCL.NS,9.46,LOW,9.48,-1.179,-2.168
|
| 258 |
+
2024-01-05,GLOBE.NS,8.69,MEDIUM,4.194,6.442,1.947
|
| 259 |
+
2024-01-18,ABCOTS.NS,1.83,LOW,4.7,20.66,33.02
|
| 260 |
+
2024-01-24,SUKHJITS.NS,3.7,LOW,2.066,9.496,10.102
|
| 261 |
+
2024-01-30,GANGAFORGE.NS,11.44,MEDIUM,4.528,14.872,17.976
|
| 262 |
+
2024-01-30,URJA.NS,10.91,MEDIUM,9.588,26.335,14.214
|
| 263 |
+
2024-01-30,HUBTOWN.NS,10.81,MEDIUM,4.691,6.096,3.545
|
| 264 |
+
2024-01-30,LAMBODHARA.NS,10.63,LOW,1.334,-3.671,-3.058
|
| 265 |
+
2024-01-30,UNITECH.NS,10.54,MEDIUM,4.508,15.085,18.61
|
| 266 |
+
2024-02-02,FINOPB.NS,4.19,LOW,19.07,19.748,0.975
|
| 267 |
+
2024-02-07,TIPSFILMS.NS,5.31,LOW,1.165,-7.39,-8.051
|
| 268 |
+
2024-02-07,NYKAA.NS,4.07,LOW,-3.366,-7.263,-2.057
|
| 269 |
+
2024-02-12,ABCOTS.NS,10.35,MEDIUM,4.7,4.7,27.283
|
| 270 |
+
2024-02-12,FIBERWEB.NS,7.39,MEDIUM,-0.582,7.446,9.841
|
| 271 |
+
2024-02-12,NPST.NS,6.95,MEDIUM,4.699,6.358,-2.398
|
| 272 |
+
2024-02-12,INTLCONV.NS,6.68,MEDIUM,-3.651,3.37,1.243
|
| 273 |
+
2024-02-12,GANESHBE.NS,6.23,MEDIUM,6.535,7.853,6.562
|
| 274 |
+
2024-02-15,INDOAMIN.NS,5.79,LOW,3.302,0.923,0.787
|
| 275 |
+
2024-02-15,KAMOPAINTS.NS,5.23,LOW,-0.813,2.292,1.83
|
| 276 |
+
2024-02-15,ETHOSLTD.NS,3.44,LOW,-0.252,3.841,11.447
|
| 277 |
+
2024-02-20,ZEELEARN.NS,9.34,MEDIUM,-2.362,-5.455,-8.547
|
| 278 |
+
2024-02-20,TIJARIA.NS,9.17,MEDIUM,4.103,14.165,25.486
|
| 279 |
+
2024-02-20,AURIGROW.NS,9.06,LOW,-6.55,-0.3,5.95
|
| 280 |
+
2024-02-20,TARMAT.NS,8.97,MEDIUM,19.687,72.439,94.13
|
| 281 |
+
2024-02-20,MANAKCOAT.NS,8.63,LOW,8.915,-2.417,-9.266
|
| 282 |
+
2024-02-28,RHL.NS,6.52,LOW,1.651,7.854,7.804
|
| 283 |
+
2024-02-28,VIRINCHI.NS,4.92,LOW,-0.553,-0.934,-5.243
|
| 284 |
+
2024-02-28,FORCEMOT.NS,3.5,LOW,4.614,0.386,-8.573
|
| 285 |
+
2024-03-07,INDOAMIN.NS,5.52,LOW,-7.207,-15.419,-15.688
|
| 286 |
+
2024-03-07,FUSION.NS,5.47,LOW,-3.514,-9.64,-4.985
|
| 287 |
+
2024-03-07,GSLSU.NS,4.96,LOW,-2.535,-15.281,-11.474
|
| 288 |
+
2024-03-07,DCMSRIND.NS,4.48,LOW,-4.894,-9.625,-4.374
|
| 289 |
+
2024-03-07,RATEGAIN.NS,3.71,LOW,1.121,-6.169,-5.928
|
| 290 |
+
2024-03-13,PATINTLOG.NS,14.07,MEDIUM,8.86,6.57,2.245
|
| 291 |
+
2024-03-13,ABMINTLLTD.NS,13.95,MEDIUM,5.249,14.572,16.903
|
| 292 |
+
2024-03-13,ELGIRUBCO.NS,13.92,MEDIUM,8.125,14.908,6.046
|
| 293 |
+
2024-03-13,ORIENTCER.NS,13.74,MEDIUM,4.7,10.589,9.7
|
| 294 |
+
2024-03-13,SONAL.NS,13.71,MEDIUM,12.649,15.012,15.993
|
| 295 |
+
2024-03-18,TRU.NS,8.0,LOW,-0.574,13.947,7.28
|
| 296 |
+
2024-03-18,USK.NS,6.75,MEDIUM,-2.441,0.414,-0.198
|
| 297 |
+
2024-03-18,EXXARO.NS,5.92,LOW,-1.108,-4.772,-7.466
|
| 298 |
+
2024-03-18,SHRIRAMPPS.NS,5.88,LOW,-3.565,0.166,6.043
|
| 299 |
+
2024-03-18,INOXGREEN.NS,4.68,LOW,-4.591,-4.019,-2.589
|
| 300 |
+
2024-03-21,GATECHDVR.NS,9.3,MEDIUM,-1.729,-10.3,-10.3
|
| 301 |
+
2024-03-21,VIRINCHI.NS,7.47,MEDIUM,3.373,1.703,9.717
|
| 302 |
+
2024-03-21,SHAH.NS,5.93,LOW,4.539,-1.913,2.926
|
| 303 |
+
2024-03-21,KOTYARK.NS,5.76,LOW,-2.132,12.531,13.082
|
| 304 |
+
2024-03-21,SMLT.NS,5.55,LOW,-3.423,3.359,4.641
|
| 305 |
+
2024-03-27,VCL.NS,7.93,MEDIUM,-5.3,4.7,14.7
|
| 306 |
+
2024-03-27,AERONEU.NS,7.13,LOW,-2.108,6.495,7.097
|
| 307 |
+
2024-03-27,MEGASTAR.NS,5.33,MEDIUM,-4.504,3.904,3.396
|
| 308 |
+
2024-03-27,RELCHEMQ.NS,5.26,LOW,-0.94,2.593,21.205
|
| 309 |
+
2024-03-27,FIBERWEB.NS,5.0,LOW,-3.18,14.58,15.06
|
| 310 |
+
2024-04-02,AKSHAR.NS,7.91,MEDIUM,3.782,11.945,7.863
|
| 311 |
+
2024-04-02,GMRP&UI.NS,4.97,LOW,4.652,15.308,23.381
|
| 312 |
+
2024-04-05,IZMO.NS,10.37,LOW,-5.661,-6.823,-13.565
|
| 313 |
+
2024-04-05,TARIL.NS,10.0,MEDIUM,1.568,11.54,22.94
|
| 314 |
+
2024-04-05,SIMPLEXINF.NS,8.35,MEDIUM,1.669,5.724,9.936
|
| 315 |
+
2024-04-05,CUPID.NS,8.08,MEDIUM,-1.732,2.913,-5.875
|
| 316 |
+
2024-04-05,SPARC.NS,7.98,LOW,4.693,-1.211,-10.862
|
| 317 |
+
2024-04-10,TECILCHEM.NS,4.51,LOW,-5.322,-2.047,-0.3
|
| 318 |
+
2024-04-16,SOLEX.NS,9.68,MEDIUM,4.697,15.453,5.775
|
| 319 |
+
2024-04-16,TPHQ.NS,6.83,MEDIUM,3.404,-4.004,-7.707
|
| 320 |
+
2024-04-16,GICL.NS,6.51,LOW,4.635,13.919,12.626
|
| 321 |
+
2024-04-16,HARDWYN.NS,5.65,LOW,-0.769,-4.206,-3.581
|
| 322 |
+
2024-04-16,RHL.NS,5.33,LOW,1.747,3.142,3.39
|
| 323 |
+
2024-04-22,KPIGREEN.NS,7.59,LOW,4.7,15.46,4.176
|
| 324 |
+
2024-04-22,GUJRAFFIA.NS,5.26,MEDIUM,0.808,3.578,14.104
|
| 325 |
+
2024-04-22,PRITIKAUTO.NS,2.77,LOW,1.682,5.646,6.006
|
| 326 |
+
2024-04-25,GSLSU.NS,6.31,LOW,-1.355,13.201,4.76
|
| 327 |
+
2024-04-25,ALIVUS.NS,4.08,LOW,8.288,10.146,10.286
|
| 328 |
+
2024-04-30,TIJARIA.NS,11.28,MEDIUM,1.565,5.294,9.49
|
| 329 |
+
2024-04-30,SKYGOLD.NS,9.13,MEDIUM,4.699,11.282,10.947
|
| 330 |
+
2024-04-30,ATALREAL.NS,8.96,MEDIUM,4.565,-5.165,-6.246
|
| 331 |
+
2024-04-30,JITFINFRA.NS,8.62,LOW,4.699,6.658,2.019
|
| 332 |
+
2024-04-30,KAVDEFENCE.NS,8.6,MEDIUM,4.462,14.938,26.684
|
| 333 |
+
2024-05-06,INOXGREEN.NS,4.9,LOW,-5.08,-11.465,-11.764
|
| 334 |
+
2024-05-06,TECILCHEM.NS,2.18,LOW,-1.331,-2.98,-4.424
|
| 335 |
+
2024-05-09,MONARCH.NS,6.15,MEDIUM,-0.252,6.346,8.063
|
| 336 |
+
2024-05-09,NRL.NS,5.91,LOW,1.6,-1.046,0.039
|
| 337 |
+
2024-05-09,ELDEHSG.NS,5.54,MEDIUM,5.321,9.565,13.172
|
| 338 |
+
2024-05-09,NDLVENTURE.NS,5.32,LOW,3.384,2.842,2.138
|
| 339 |
+
2024-05-09,SOLEX.NS,4.78,MEDIUM,-4.122,5.731,16.599
|
| 340 |
+
2024-05-14,SYRMA.NS,6.23,LOW,-0.112,-1.053,7.77
|
| 341 |
+
2024-05-14,GANESHBE.NS,4.4,LOW,0.547,-0.495,-0.105
|
| 342 |
+
2024-05-17,KAYNES.NS,10.33,LOW,9.957,8.21,8.314
|
| 343 |
+
2024-05-17,HYBRIDFIN.NS,9.1,MEDIUM,9.7,20.7,32.7
|
| 344 |
+
2024-05-17,MWL.NS,4.66,LOW,0.88,-0.233,0.273
|
| 345 |
+
2024-05-23,RADAAN.NS,9.77,MEDIUM,3.7,-0.3,-8.3
|
| 346 |
+
2024-05-23,DREDGECORP.NS,8.53,LOW,4.697,-4.789,-5.028
|
| 347 |
+
2024-05-23,MICEL.NS,8.53,LOW,-5.3,-12.581,-14.335
|
| 348 |
+
2024-05-23,GLOBAL.NS,8.34,MEDIUM,-2.76,-8.091,-6.692
|
| 349 |
+
2024-05-23,SUPREMEINF.NS,8.3,MEDIUM,1.663,1.619,-2.353
|
| 350 |
+
2024-05-31,RELIABLE.NS,2.42,LOW,-5.212,-5.212,-5.212
|
| 351 |
+
2024-06-05,PAKKA.NS,6.38,MEDIUM,1.407,11.77,13.745
|
| 352 |
+
2024-06-05,PRITIKAUTO.NS,6.04,MEDIUM,4.529,7.104,14.187
|
| 353 |
+
2024-06-05,SBGLP.NS,5.61,LOW,6.793,7.196,8.518
|
| 354 |
+
2024-06-05,MEGASTAR.NS,5.6,LOW,5.766,6.954,9.339
|
| 355 |
+
2024-06-05,INTLCONV.NS,5.41,LOW,5.338,7.109,7.633
|
| 356 |
+
2024-06-10,DATAPATTNS.NS,5.05,LOW,0.283,2.529,15.866
|
| 357 |
+
2024-06-10,ATL.NS,4.7,LOW,-0.165,-0.204,5.52
|
| 358 |
+
2024-06-13,SANOFI.NS,11.53,MEDIUM,4.7,10.105,4.846
|
| 359 |
+
2024-06-13,TCIFINANCE.NS,10.98,MEDIUM,4.682,15.28,26.964
|
| 360 |
+
2024-06-13,ZENITHEXPO.NS,9.64,MEDIUM,4.698,15.458,27.318
|
| 361 |
+
2024-06-13,KAYA.NS,9.59,LOW,14.611,11.45,20.921
|
| 362 |
+
2024-06-13,DRCSYSTEMS.NS,9.2,MEDIUM,16.858,18.314,14.177
|
| 363 |
+
2024-06-19,TECILCHEM.NS,10.3,MEDIUM,4.697,-5.566,-14.835
|
| 364 |
+
2024-06-19,TRU.NS,6.85,LOW,3.884,-1.173,3.133
|
| 365 |
+
2024-06-24,TPHQ.NS,8.65,MEDIUM,4.245,14.094,7.276
|
| 366 |
+
2024-06-24,TIPSFILMS.NS,5.1,MEDIUM,4.757,-0.156,0.773
|
| 367 |
+
2024-06-27,STARTECK.NS,5.12,LOW,-1.074,-0.02,1.448
|
| 368 |
+
2024-06-27,HARSHA.NS,4.55,LOW,1.272,3.206,7.479
|
| 369 |
+
2024-06-27,DHARMAJ.NS,4.53,MEDIUM,1.547,21.645,25.51
|
| 370 |
+
2024-07-05,GTLINFRA.NS,12.25,MEDIUM,-5.385,-14.828,-23.302
|
| 371 |
+
2024-07-05,CENTEXT.NS,10.54,MEDIUM,-5.337,-4.019,5.822
|
| 372 |
+
2024-07-05,SUVEN.NS,10.17,MEDIUM,9.415,5.091,4.283
|
| 373 |
+
2024-07-05,MADHUCON.NS,9.97,MEDIUM,4.65,8.061,18.964
|
| 374 |
+
2024-07-05,COUNCODOS.NS,9.74,LOW,-2.437,-8.562,-0.3
|
| 375 |
+
2024-07-10,TECILCHEM.NS,5.72,LOW,4.698,9.921,-0.885
|
| 376 |
+
2024-07-10,INOXGREEN.NS,4.39,LOW,1.391,1.78,0.515
|
| 377 |
+
2024-07-10,AARTECH.NS,2.77,LOW,1.699,3.738,-2.385
|
| 378 |
+
2024-07-15,GATECHDVR.NS,8.91,MEDIUM,4.665,15.066,14.594
|
| 379 |
+
2024-07-15,RELIABLE.NS,6.88,LOW,4.696,14.769,26.453
|
| 380 |
+
2024-07-15,SONAL.NS,5.49,LOW,1.984,-0.395,-2.393
|
| 381 |
+
2024-07-15,RHL.NS,5.49,LOW,-2.693,-4.076,-4.041
|
| 382 |
+
2024-07-15,NETWEB.NS,4.18,LOW,2.743,-0.869,-0.76
|
| 383 |
+
2024-07-19,AERONEU.NS,9.52,MEDIUM,1.784,14.091,12.474
|
| 384 |
+
2024-07-19,HARSHA.NS,4.39,LOW,1.438,5.023,4.11
|
| 385 |
+
2024-07-24,ETHOSLTD.NS,4.54,LOW,-2.668,-2.956,-3.256
|
| 386 |
+
2024-07-24,DIAMINESQ.NS,4.27,LOW,6.186,0.145,2.515
|
| 387 |
+
2024-07-24,SRGHFL.NS,3.52,LOW,-3.798,0.002,-0.325
|
| 388 |
+
2024-07-29,NIBL.NS,10.56,LOW,7.677,-8.347,-4.791
|
| 389 |
+
2024-07-29,FLEXITUFF.NS,10.42,MEDIUM,4.696,15.449,10.858
|
| 390 |
+
2024-07-29,SUMEETINDS.NS,10.06,MEDIUM,4.573,7.751,3.302
|
| 391 |
+
2024-07-29,ESTER.NS,9.71,MEDIUM,2.975,8.114,-0.17
|
| 392 |
+
2024-07-29,HUBTOWN.NS,9.3,MEDIUM,4.697,11.832,23.324
|
| 393 |
+
2024-08-01,TECILCHEM.NS,5.18,LOW,-3.795,0.998,-2.946
|
| 394 |
+
2024-08-06,TIPSFILMS.NS,7.33,MEDIUM,-0.268,19.582,15.866
|
| 395 |
+
2024-08-06,TPHQ.NS,6.6,LOW,3.297,8.333,4.736
|
| 396 |
+
2024-08-06,SONAL.NS,6.39,LOW,0.959,-1.511,0.862
|
| 397 |
+
2024-08-06,RHL.NS,6.17,LOW,8.527,1.335,-0.962
|
| 398 |
+
2024-08-06,ATAM.NS,5.84,LOW,8.674,9.322,4.038
|
| 399 |
+
2024-08-09,KPIGREEN.NS,5.51,LOW,2.883,-6.188,-5.887
|
| 400 |
+
2024-08-09,EMSLIMITED.NS,4.69,LOW,5.355,1.932,-0.469
|
| 401 |
+
2024-08-09,GUJRAFFIA.NS,4.53,LOW,-4.179,-1.983,0.87
|
| 402 |
+
2024-08-09,SUPRIYA.NS,4.51,LOW,13.592,11.147,18.449
|
| 403 |
+
2024-08-09,DHARMAJ.NS,4.49,LOW,3.635,-1.865,2.055
|
| 404 |
+
2024-08-14,AKSHAR.NS,6.9,MEDIUM,1.238,15.597,12.52
|
| 405 |
+
2024-08-14,LEMERITE.NS,6.12,LOW,-0.525,-0.25,-0.125
|
| 406 |
+
2024-08-14,GSLSU.NS,5.88,MEDIUM,-4.227,1.655,0.731
|
| 407 |
+
2024-08-14,RADHIKAJWE.NS,5.1,MEDIUM,8.35,40.598,40.168
|
| 408 |
+
2024-08-14,REDTAPE.NS,4.99,LOW,0.04,2.725,8.033
|
| 409 |
+
2024-08-20,VINNY.NS,12.2,MEDIUM,-5.342,-14.754,-23.325
|
| 410 |
+
2024-08-20,E2E.NS,10.84,MEDIUM,4.7,11.11,9.48
|
| 411 |
+
2024-08-20,MURUDCERA.NS,10.64,MEDIUM,-6.206,-4.479,-7.077
|
| 412 |
+
2024-08-20,HGM.NS,10.25,LOW,4.424,2.283,0.884
|
| 413 |
+
2024-08-20,KINGFA.NS,9.99,LOW,-5.771,-6.002,-8.792
|
| 414 |
+
2024-08-28,SOLEX.NS,5.24,LOW,-4.41,5.594,-16.817
|
| 415 |
+
2024-08-28,RELIABLE.NS,3.67,LOW,1.698,5.805,8.851
|
| 416 |
+
2024-09-05,LEMERITE.NS,2.76,LOW,1.024,1.244,1.048
|
| 417 |
+
2024-09-10,INDOTHAI.NS,11.11,MEDIUM,4.693,15.447,14.586
|
| 418 |
+
2024-09-10,MODIRUBBER.NS,10.06,MEDIUM,-5.302,-9.67,-9.146
|
| 419 |
+
2024-09-10,SIMPLEXINF.NS,7.54,LOW,4.7,15.456,9.615
|
| 420 |
+
2024-09-10,AYMSYNTEX.NS,7.26,MEDIUM,1.697,5.817,10.103
|
| 421 |
+
2024-09-10,NORBTEAEXP.NS,7.11,MEDIUM,4.655,15.363,27.137
|
| 422 |
+
2024-09-18,CCCL.NS,12.46,MEDIUM,4.695,15.433,27.244
|
| 423 |
+
2024-09-23,IRIS.NS,4.75,MEDIUM,1.697,5.814,10.096
|
| 424 |
+
2024-09-26,ALIVUS.NS,4.16,MEDIUM,0.754,9.876,12.657
|
| 425 |
+
2024-09-26,KAMOPAINTS.NS,3.03,MEDIUM,-20.3,-42.716,-53.668
|
| 426 |
+
2024-10-01,ABCOTS.NS,8.55,MEDIUM,3.614,9.813,4.313
|
| 427 |
+
2024-10-01,SURANASOL.NS,8.51,MEDIUM,4.694,-5.551,-5.792
|
| 428 |
+
2024-10-01,RPOWER.NS,7.75,MEDIUM,4.691,-5.565,-5.82
|
| 429 |
+
2024-10-01,AQYLON.NS,5.27,MEDIUM,1.698,5.813,10.094
|
| 430 |
+
2024-10-01,AYMSYNTEX.NS,4.85,MEDIUM,-2.222,-4.077,-5.999
|
| 431 |
+
2024-10-07,SHRIRAMPPS.NS,5.88,MEDIUM,6.105,6.959,5.408
|
| 432 |
+
2024-10-07,INOXGREEN.NS,5.66,MEDIUM,5.955,5.051,1.28
|
| 433 |
+
2024-10-07,LORDSCHLO.NS,5.44,MEDIUM,4.522,12.538,7.788
|
| 434 |
+
2024-10-07,AMNPLST.NS,4.84,LOW,2.432,3.798,0.541
|
| 435 |
+
2024-10-07,BIKAJI.NS,4.61,LOW,0.678,-0.446,0.338
|
| 436 |
+
2024-10-23,CREATIVEYE.NS,10.45,MEDIUM,4.676,-5.659,-14.941
|
| 437 |
+
2024-10-23,AQYLON.NS,5.99,MEDIUM,1.698,5.813,10.098
|
| 438 |
+
2024-10-23,TARAPUR.NS,5.97,MEDIUM,1.691,-2.392,-6.307
|
| 439 |
+
2024-10-23,SUMEETINDS.NS,0.85,LOW,-0.3,-0.3,-0.3
|
| 440 |
+
2024-10-31,GATECHDVR.NS,6.48,LOW,2.761,6.843,-0.3
|
| 441 |
+
2024-10-31,RAMRAT.NS,4.68,LOW,2.606,1.249,0.839
|
| 442 |
+
2024-10-31,FIVESTAR.NS,4.65,LOW,0.459,-5.65,-8.293
|
| 443 |
+
2024-11-05,SBGLP.NS,3.53,LOW,5.236,6.061,3.376
|
| 444 |
+
2024-11-13,LANDSMILL.NS,11.28,MEDIUM,4.388,14.544,25.481
|
| 445 |
+
2024-11-13,TERASOFT.NS,10.6,MEDIUM,4.695,15.453,27.306
|
| 446 |
+
2024-11-13,AMBICAAGAR.NS,8.23,LOW,-4.038,-1.71,-5.152
|
| 447 |
+
2024-11-13,SUPERSPIN.NS,8.22,LOW,1.695,-0.619,-4.61
|
| 448 |
+
2024-11-13,TFL.NS,7.91,MEDIUM,-1.905,-0.68,-6.423
|
| 449 |
+
2024-11-25,RELIABLE.NS,5.62,LOW,2.116,4.938,2.037
|
| 450 |
+
2024-11-25,ASIANENE.NS,5.01,LOW,-1.191,4.373,6.342
|
| 451 |
+
2024-11-25,COMSYN.NS,4.94,LOW,-0.271,7.292,4.264
|
| 452 |
+
2024-11-25,GATECHDVR.NS,4.63,LOW,0.876,6.759,16.171
|
| 453 |
+
2024-11-25,MVGJL.NS,4.52,LOW,1.184,3.642,2.712
|
| 454 |
+
2024-11-28,DHARMAJ.NS,4.49,LOW,-0.956,0.268,5.819
|
| 455 |
+
2024-11-28,IRIS.NS,3.41,LOW,1.698,4.555,6.735
|
| 456 |
+
2024-12-03,KAMOPAINTS.NS,4.0,LOW,1.651,5.737,5.615
|
| 457 |
+
2024-12-06,THOMASCOTT.NS,10.27,LOW,4.698,15.456,27.028
|
| 458 |
+
2024-12-06,TOUCHWOOD.NS,9.93,MEDIUM,5.126,-7.204,-5.426
|
| 459 |
+
2024-12-06,MANAKCOAT.NS,9.51,MEDIUM,4.692,6.497,1.399
|
| 460 |
+
2024-12-06,CTE.NS,8.74,LOW,0.852,0.075,-0.242
|
| 461 |
+
2024-12-06,ENERGYDEV.NS,8.59,LOW,4.675,15.426,15.126
|
| 462 |
+
2024-12-11,DIGIDRIVE.NS,9.06,LOW,3.378,3.735,-3.221
|
| 463 |
+
2024-12-11,AVONMORE.NS,7.16,LOW,-4.038,5.368,6.169
|
| 464 |
+
2024-12-11,TRU.NS,4.79,MEDIUM,-1.209,1.639,12.064
|
| 465 |
+
2024-12-16,TPHQ.NS,10.96,MEDIUM,3.955,13.317,4.806
|
| 466 |
+
2024-12-24,GMRP&UI.NS,3.99,LOW,4.697,15.457,27.312
|
| 467 |
+
2024-12-30,FLEXITUFF.NS,9.07,LOW,4.693,7.724,3.43
|
| 468 |
+
2024-12-30,ASIANHOTNR.NS,8.87,MEDIUM,4.699,7.369,-3.114
|
| 469 |
+
2024-12-30,GVPTECH.NS,8.72,LOW,4.676,15.298,9.269
|
| 470 |
+
2024-12-30,AKI.NS,8.68,LOW,1.375,-0.539,-6.361
|
| 471 |
+
2024-12-30,TARACHAND.NS,8.56,LOW,-4.116,-3.098,-8.313
|
| 472 |
+
2025-01-02,AARTECH.NS,9.92,LOW,4.693,15.452,4.163
|
| 473 |
+
2025-01-07,ATLASCYCLE.NS,11.22,MEDIUM,4.698,20.962,39.752
|
| 474 |
+
2025-01-10,IRIS.NS,6.8,LOW,4.354,6.962,2.717
|
| 475 |
+
2025-01-15,ACUTAAS.NS,4.43,LOW,-0.009,-0.82,-3.929
|
| 476 |
+
2025-01-15,RATNAVEER.NS,4.17,LOW,2.121,12.458,5.848
|
| 477 |
+
2025-01-15,GSLSU.NS,3.75,LOW,6.001,17.696,7.484
|
| 478 |
+
2025-01-15,AKSHAR.NS,3.39,LOW,-1.67,-1.67,-5.779
|
| 479 |
+
2025-01-20,TERASOFT.NS,6.25,MEDIUM,1.696,5.808,10.092
|
| 480 |
+
2025-01-20,SUPERSPIN.NS,5.86,LOW,4.584,3.792,-6.373
|
| 481 |
+
2025-01-20,RAJTV.NS,5.43,LOW,1.081,5.308,2.093
|
| 482 |
+
2025-01-20,PANACHE.NS,5.43,MEDIUM,1.688,5.79,4.037
|
| 483 |
+
2025-01-20,SVLL.NS,4.93,MEDIUM,1.694,5.726,1.535
|
| 484 |
+
2025-01-28,PRUDENT.NS,8.31,LOW,-2.33,1.921,0.704
|
| 485 |
+
2025-01-28,ABSLAMC.NS,8.26,LOW,7.121,11.354,6.769
|
| 486 |
+
2025-01-28,PRUDMOULI.NS,8.24,LOW,2.611,6.344,5.478
|
| 487 |
+
2025-01-28,DJML.NS,7.3,LOW,0.296,5.003,9.518
|
| 488 |
+
2025-01-28,RHL.NS,6.32,LOW,-4.805,-1.201,-1.201
|
| 489 |
+
2025-01-31,MANYAVAR.NS,6.5,LOW,3.941,2.34,2.093
|
| 490 |
+
2025-02-07,NORBTEAEXP.NS,4.82,MEDIUM,4.691,15.407,27.239
|
| 491 |
+
2025-02-07,BOHRAIND.NS,0.64,LOW,4.693,4.693,4.693
|
| 492 |
+
2025-02-17,ORTINGLOBE.NS,8.5,MEDIUM,-11.968,-5.559,-2.765
|
| 493 |
+
2025-02-17,NRL.NS,8.24,LOW,-7.087,-3.583,-6.24
|
| 494 |
+
2025-02-17,ASIANENE.NS,7.86,LOW,-5.878,-0.174,15.234
|
| 495 |
+
2025-02-17,ATAM.NS,7.47,LOW,-6.285,2.347,0.738
|
| 496 |
+
2025-02-17,NETWEB.NS,7.32,LOW,-2.464,17.788,12.106
|
| 497 |
+
2025-03-03,NAGREEKEXP.NS,12.94,MEDIUM,5.795,17.671,12.087
|
| 498 |
+
2025-03-03,MANGALAM.NS,12.6,MEDIUM,-0.679,11.043,7.023
|
| 499 |
+
2025-03-03,VASWANI.NS,12.54,MEDIUM,-4.624,8.458,7.211
|
| 500 |
+
2025-03-03,WANBURY.NS,12.49,MEDIUM,9.071,23.002,28.323
|
| 501 |
+
2025-03-03,SGL.NS,12.22,MEDIUM,6.338,19.875,12.451
|
| 502 |
+
2025-03-06,RBZJEWEL.NS,8.77,LOW,-0.803,-6.896,-7.635
|
| 503 |
+
2025-03-06,PYRAMID.NS,7.73,LOW,1.258,-5.448,-8.298
|
| 504 |
+
2025-03-06,MANOMAY.NS,7.33,LOW,6.299,7.294,13.224
|
| 505 |
+
2025-03-06,HPAL.NS,6.86,LOW,0.902,-7.591,-11.0
|
| 506 |
+
2025-03-06,SIGNATURE.NS,6.44,LOW,4.345,4.828,6.261
|
| 507 |
+
2025-03-17,SBGLP.NS,8.59,MEDIUM,-0.3,15.382,20.011
|
| 508 |
+
2025-03-17,DYCL.NS,8.43,LOW,-0.3,-1.822,4.794
|
| 509 |
+
2025-03-17,BALUFORGE.NS,7.17,LOW,-0.3,14.978,49.941
|
| 510 |
+
2025-03-17,PAKKA.NS,6.66,LOW,-0.3,9.89,6.432
|
| 511 |
+
2025-03-17,MANYAVAR.NS,5.76,LOW,-0.3,5.957,4.745
|
| 512 |
+
2025-03-25,SUPREMEINF.NS,6.29,LOW,4.699,4.649,8.882
|
| 513 |
+
2025-03-25,BOHRAIND.NS,5.91,MEDIUM,-2.311,-6.205,-9.946
|
| 514 |
+
2025-03-25,AQYLON.NS,5.83,MEDIUM,1.696,5.809,1.617
|
| 515 |
+
2025-03-28,EXXARO.NS,6.89,LOW,2.798,6.757,4.003
|
| 516 |
+
2025-03-28,USK.NS,6.44,LOW,3.07,5.998,0.75
|
| 517 |
+
2025-03-28,DAVANGERE.NS,6.21,LOW,3.384,-0.037,-5.037
|
| 518 |
+
2025-03-28,MODTHREAD.NS,6.17,LOW,-4.683,2.229,2.229
|
| 519 |
+
2025-03-28,DIGIDRIVE.NS,5.89,LOW,2.105,11.169,2.956
|
| 520 |
+
2025-04-03,KOTYARK.NS,6.14,MEDIUM,4.692,1.16,-2.859
|
| 521 |
+
2025-04-03,CURAA.NS,5.28,LOW,4.673,15.167,39.394
|
| 522 |
+
2025-04-08,STEELCAS.NS,11.5,MEDIUM,-5.996,14.302,11.645
|
| 523 |
+
2025-04-08,RKSWAMY.NS,11.1,LOW,-0.871,2.769,3.473
|
| 524 |
+
2025-04-08,SYRMA.NS,11.07,MEDIUM,-0.668,12.959,14.902
|
| 525 |
+
2025-04-08,MALLCOM.NS,10.68,MEDIUM,0.849,6.099,11.167
|
| 526 |
+
2025-04-08,DYCL.NS,10.29,MEDIUM,-4.613,9.114,7.351
|
| 527 |
+
2025-04-15,SOLEX.NS,6.07,LOW,1.694,5.812,10.079
|
| 528 |
+
2025-04-21,NACLIND.NS,11.15,MEDIUM,4.699,-5.54,-14.78
|
| 529 |
+
2025-04-21,UEL.NS,8.26,LOW,4.69,15.446,9.659
|
| 530 |
+
2025-04-21,SHREERAMA.NS,7.47,MEDIUM,4.693,1.73,-4.552
|
| 531 |
+
2025-04-21,SADBHAV.NS,7.4,MEDIUM,4.676,15.35,15.19
|
| 532 |
+
2025-04-21,HERANBA.NS,7.36,LOW,4.697,0.536,-3.463
|
| 533 |
+
2025-04-29,CURAA.NS,7.02,MEDIUM,1.699,5.797,10.066
|
| 534 |
+
2025-05-05,KFINTECH.NS,4.87,LOW,-4.538,-2.405,2.467
|
| 535 |
+
2025-05-05,IRIS.NS,4.49,LOW,-1.156,-6.767,-4.932
|
| 536 |
+
2025-05-05,TRU.NS,4.19,LOW,0.714,4.628,11.874
|
| 537 |
+
2025-05-08,GMRP&UI.NS,10.7,LOW,-3.403,4.837,11.125
|
| 538 |
+
2025-05-08,MEDICAMEQ.NS,10.04,LOW,-0.973,3.267,5.972
|
| 539 |
+
2025-05-08,RISHABH.NS,9.46,LOW,-3.029,4.835,13.691
|
| 540 |
+
2025-05-08,DATAPATTNS.NS,9.33,LOW,3.918,12.55,18.894
|
| 541 |
+
2025-05-08,SENCO.NS,9.32,LOW,-0.539,5.635,7.931
|
| 542 |
+
2025-05-13,AARTISURF.NS,10.88,LOW,9.691,20.959,13.386
|
| 543 |
+
2025-05-13,BYKE.NS,10.41,LOW,2.885,6.444,9.209
|
| 544 |
+
2025-05-13,ZENTEC.NS,8.87,LOW,4.698,15.456,22.319
|
| 545 |
+
2025-05-13,AVROIND.NS,8.05,LOW,-0.213,9.591,6.492
|
| 546 |
+
2025-05-13,TNTELE.NS,7.94,LOW,4.084,3.145,-1.031
|
| 547 |
+
2025-05-21,CURAA.NS,6.03,MEDIUM,1.693,5.808,7.92
|
| 548 |
+
2025-05-26,TRU.NS,6.66,MEDIUM,4.681,8.8,13.11
|
| 549 |
+
2025-05-26,KRISHIVAL.NS,4.4,LOW,4.699,7.634,8.808
|
| 550 |
+
2025-05-29,SOLEX.NS,6.0,LOW,1.7,5.816,10.1
|
| 551 |
+
2025-06-03,ARROWGREEN.NS,6.58,LOW,-1.344,-5.255,-9.015
|
| 552 |
+
2025-06-03,UEL.NS,5.68,LOW,4.694,15.437,21.218
|
| 553 |
+
2025-06-11,CURAA.NS,5.98,MEDIUM,1.696,5.81,10.089
|
| 554 |
+
2025-06-16,TRU.NS,6.11,MEDIUM,1.646,5.676,9.915
|
| 555 |
+
2025-06-24,WSI.NS,5.21,LOW,1.697,5.805,10.075
|
| 556 |
+
2025-06-24,SUMEETINDS.NS,4.78,LOW,4.688,15.42,27.263
|
| 557 |
+
2025-06-24,MACPOWER.NS,4.29,LOW,1.699,4.821,0.671
|
| 558 |
+
2025-07-02,WAAREEINDO.NS,6.86,LOW,-0.3,4.699,4.699
|
| 559 |
+
2025-07-02,CURAA.NS,6.4,MEDIUM,1.697,5.812,10.092
|
| 560 |
+
2025-07-07,NIRAJISPAT.NS,10.33,MEDIUM,1.691,5.8,7.918
|
| 561 |
+
2025-07-07,TRU.NS,6.15,MEDIUM,1.672,5.772,1.516
|
| 562 |
+
2025-07-10,DIGJAMLMTD.NS,7.64,MEDIUM,1.7,5.782,10.031
|
| 563 |
+
2025-07-10,TPHQ.NS,5.18,MEDIUM,0.824,3.071,1.947
|
| 564 |
+
2025-07-15,SAMPANN.NS,9.81,LOW,9.69,5.798,8.263
|
| 565 |
+
2025-07-15,CENTRUM.NS,8.27,LOW,-1.224,-1.599,-3.647
|
| 566 |
+
2025-07-15,GAYAHWS.NS,7.78,LOW,-2.56,0.83,3.09
|
| 567 |
+
2025-07-15,LANDSMILL.NS,7.57,LOW,4.462,13.986,2.557
|
| 568 |
+
2025-07-15,DIACABS.NS,6.05,MEDIUM,2.69,-2.648,-3.371
|
| 569 |
+
2025-07-18,TECILCHEM.NS,5.16,LOW,4.669,15.39,18.237
|
| 570 |
+
2025-07-23,CURAA.NS,5.87,MEDIUM,1.697,5.813,10.093
|
| 571 |
+
2025-07-23,WAAREEINDO.NS,2.53,LOW,-0.3,4.699,4.699
|
| 572 |
+
2025-08-05,DNAMEDIA.NS,7.41,LOW,19.553,23.72,22.004
|
| 573 |
+
2025-08-05,UEL.NS,5.62,LOW,4.688,12.933,12.933
|
| 574 |
+
2025-08-05,GAYAHWS.NS,4.77,MEDIUM,1.543,5.23,0.622
|
| 575 |
+
2025-08-13,GATECH.NS,5.85,LOW,-0.3,8.211,16.721
|
| 576 |
+
2025-08-13,CURAA.NS,5.08,MEDIUM,1.697,5.813,10.097
|
| 577 |
+
2025-08-13,KOTYARK.NS,5.0,LOW,19.116,71.639,70.802
|
| 578 |
+
2025-08-13,WAAREEINDO.NS,3.24,MEDIUM,-0.3,4.697,4.697
|
| 579 |
+
2025-08-19,DHARMAJ.NS,4.92,LOW,1.689,5.796,3.923
|
| 580 |
+
2025-08-28,DRCSYSTEMS.NS,9.15,LOW,-3.775,1.775,7.688
|
| 581 |
+
2025-08-28,NRAIL.NS,8.02,LOW,-5.301,-9.533,-10.069
|
| 582 |
+
2025-08-28,RADAAN.NS,7.75,LOW,4.534,8.461,6.649
|
| 583 |
+
2025-08-28,KRITIKA.NS,7.72,LOW,19.674,24.084,23.695
|
| 584 |
+
2025-08-28,JITFINFRA.NS,6.85,LOW,-6.332,-1.702,24.695
|
| 585 |
+
2025-09-05,WAAREEINDO.NS,1.15,LOW,4.691,4.691,4.691
|
| 586 |
+
2025-09-15,RVTH.NS,6.86,LOW,4.696,15.453,27.311
|
| 587 |
+
2025-09-18,KAVDEFENCE.NS,8.28,MEDIUM,1.745,12.193,23.714
|
| 588 |
+
2025-09-18,IZMO.NS,8.26,MEDIUM,4.697,15.452,27.314
|
| 589 |
+
2025-09-18,BAFNAPH.NS,7.76,MEDIUM,4.7,15.451,27.304
|
| 590 |
+
2025-09-18,GAYAHWS.NS,6.3,MEDIUM,4.515,14.885,26.367
|
| 591 |
+
2025-09-18,NORBTEAEXP.NS,5.79,MEDIUM,1.692,-2.368,-1.609
|
| 592 |
+
2025-09-26,PRUDMOULI.NS,8.9,LOW,0.518,-3.819,-4.708
|
| 593 |
+
2025-09-26,WAAREEINDO.NS,1.35,LOW,4.694,9.934,21.224
|
| 594 |
+
2025-10-10,SECMARK.NS,6.87,LOW,3.564,1.491,9.303
|
| 595 |
+
2025-10-29,UYFINCORP.NS,3.55,LOW,2.996,8.255,9.448
|
| 596 |
+
2025-11-03,SOMATEX.NS,9.13,MEDIUM,4.698,-4.607,-13.939
|
| 597 |
+
2025-11-17,NIRAJISPAT.NS,10.05,LOW,4.197,4.535,3.027
|
| 598 |
+
2025-11-20,TARSONS.NS,3.63,LOW,-1.477,2.756,1.805
|
| 599 |
+
2025-11-25,PANACHE.NS,7.36,LOW,2.971,12.74,13.027
|
| 600 |
+
2025-11-25,FLEXITUFF.NS,6.76,MEDIUM,4.693,15.373,27.162
|
| 601 |
+
2025-11-25,AJOONI.NS,6.09,LOW,9.997,10.226,9.997
|
| 602 |
+
2025-11-25,MICEL.NS,5.81,LOW,-1.225,3.054,9.161
|
| 603 |
+
2025-11-25,THEMISMED.NS,5.46,LOW,3.951,4.632,2.599
|
| 604 |
+
2025-12-08,VCL.NS,10.13,MEDIUM,4.659,-5.259,-13.936
|
| 605 |
+
2025-12-08,TRANSRAILL.NS,6.12,LOW,3.957,1.197,7.74
|
| 606 |
+
2025-12-11,KAYNES.NS,6.2,LOW,5.242,3.288,-0.176
|
| 607 |
+
2025-12-11,DIGJAMLMTD.NS,5.93,LOW,4.688,12.332,5.703
|
| 608 |
+
2025-12-16,ARVEE.NS,7.3,LOW,19.697,65.453,39.556
|
| 609 |
+
2025-12-19,TECILCHEM.NS,5.18,LOW,7.255,4.289,8.486
|
| 610 |
+
2025-12-19,STALLION.NS,4.64,LOW,3.34,7.896,18.978
|
| 611 |
+
2025-12-19,USK.NS,4.33,LOW,4.45,4.147,-0.43
|
| 612 |
+
2025-12-19,CHEMPLASTS.NS,3.21,LOW,6.816,5.203,2.865
|
| 613 |
+
2025-12-19,AXITA.NS,2.84,LOW,0.011,1.02,2.107
|
| 614 |
+
2025-12-30,PVSL.NS,5.86,LOW,1.293,6.145,4.047
|
| 615 |
+
2025-12-30,NAVKARURB.NS,4.51,LOW,4.336,14.27,22.879
|
| 616 |
+
2025-12-30,TRANSRAILL.NS,3.34,LOW,1.519,4.288,-0.803
|
| 617 |
+
2026-01-02,KAMOPAINTS.NS,3.62,LOW,0.518,18.358,14.921
|
| 618 |
+
2026-01-12,KALAMANDIR.NS,5.5,LOW,0.382,-0.358,5.119
|
| 619 |
+
2026-01-12,CHEMPLASTS.NS,3.64,LOW,15.548,14.194,10.529
|
| 620 |
+
2026-01-15,SMLT.NS,10.22,LOW,0.036,-7.892,-7.581
|
| 621 |
+
2026-01-15,KOTYARK.NS,10.15,LOW,-2.425,-6.489,-6.232
|
| 622 |
+
2026-01-15,VIRINCHI.NS,9.91,LOW,-2.57,-8.925,-7.926
|
| 623 |
+
2026-01-15,MASTERTR.NS,9.5,LOW,0.406,-5.79,-2.72
|
| 624 |
+
2026-01-15,ASIANENE.NS,8.93,LOW,-1.447,-3.139,-2.089
|
| 625 |
+
2026-01-20,KRISHIVAL.NS,7.09,LOW,0.713,1.695,-0.585
|
| 626 |
+
2026-01-20,OLAELEC.NS,7.08,LOW,0.714,-2.173,-1.774
|
| 627 |
+
2026-01-20,XTGLOBAL.NS,6.86,LOW,-4.914,-6.277,-4.704
|
| 628 |
+
2026-01-20,YATRA.NS,6.33,LOW,3.818,-1.533,3.343
|
| 629 |
+
2026-01-20,BALUFORGE.NS,5.57,LOW,-3.657,-9.012,-14.548
|
| 630 |
+
2026-01-29,SUVIDHAA.NS,7.51,LOW,9.911,3.925,6.39
|
| 631 |
+
2026-01-29,UNITECH.NS,6.72,LOW,-2.8,-2.8,14.018
|
| 632 |
+
2026-01-29,IZMO.NS,6.42,LOW,-0.007,14.502,18.552
|
| 633 |
+
2026-01-29,LAXMIDENTL.NS,5.89,LOW,2.25,13.786,12.326
|
| 634 |
+
2026-01-29,EPACK.NS,5.83,LOW,2.075,5.676,10.761
|
| 635 |
+
2026-02-03,SOLEX.NS,5.57,LOW,1.55,0.464,11.121
|
| 636 |
+
2026-02-11,LORDSCHLO.NS,4.4,LOW,0.301,-3.668,-7.222
|
| 637 |
+
2026-02-16,IXIGO.NS,6.15,LOW,0.382,4.908,-4.848
|
| 638 |
+
2026-02-19,DELPHIFX.NS,7.32,LOW,1.045,2.706,5.238
|
| 639 |
+
2026-02-19,RMDRIP.NS,3.24,LOW,0.386,2.143,-34.732
|
| 640 |
+
2026-02-27,TOUCHWOOD.NS,8.4,LOW,-3.805,-2.168,-4.928
|
| 641 |
+
2026-02-27,BYKE.NS,6.57,LOW,-4.354,-2.303,-5.738
|
| 642 |
+
2026-03-05,EMUDHRA.NS,5.48,LOW,1.672,2.535,6.394
|
| 643 |
+
2026-03-05,PROTEAN.NS,5.0,LOW,-1.225,-2.691,0.708
|
| 644 |
+
2026-03-10,SRD.NS,6.63,LOW,-2.679,-0.553,-5.857
|
| 645 |
+
2026-03-10,CURAA.NS,6.55,MEDIUM,4.696,15.456,27.309
|
| 646 |
+
2026-03-10,INDOFARM.NS,6.18,LOW,-1.561,-2.094,-1.71
|
| 647 |
+
2026-03-10,EPIGRAL.NS,5.98,LOW,-1.696,-2.191,-4.234
|
| 648 |
+
2026-03-10,NOVAAGRI.NS,5.72,LOW,-2.561,-4.54,-5.918
|
| 649 |
+
2026-03-13,INFOBEAN.NS,11.71,LOW,8.363,14.151,12.823
|
| 650 |
+
2026-03-13,SUNDRMBRAK.NS,11.25,LOW,0.637,0.04,-2.901
|
| 651 |
+
2026-03-13,RICOAUTO.NS,11.23,LOW,-0.224,6.748,2.983
|
| 652 |
+
2026-03-13,CAPACITE.NS,11.21,MEDIUM,-4.767,16.445,18.337
|
| 653 |
+
2026-03-13,LUMAXIND.NS,11.14,MEDIUM,1.539,9.288,4.728
|
| 654 |
+
2026-03-18,HYBRIDFIN.NS,9.15,LOW,-0.791,-0.791,0.744
|
| 655 |
+
2026-03-18,FINOPB.NS,7.6,LOW,-2.887,-8.226,-19.276
|
| 656 |
+
2026-03-18,SOLEX.NS,6.3,LOW,-2.725,2.204,3.624
|
| 657 |
+
2026-03-18,CHEMPLASTS.NS,5.93,LOW,-0.423,-6.43,-2.601
|
| 658 |
+
2026-03-18,GUJRAFFIA.NS,5.87,LOW,-3.466,-2.402,-2.109
|
| 659 |
+
2026-03-23,XELPMOC.NS,12.63,LOW,7.859,-0.413,-0.109
|
| 660 |
+
2026-03-23,RAMANEWS.NS,12.53,MEDIUM,3.632,1.199,0.433
|
| 661 |
+
2026-03-23,CAMLINFINE.NS,12.23,MEDIUM,2.537,-4.844,-8.548
|
| 662 |
+
2026-03-23,BTML.NS,12.07,MEDIUM,-0.13,2.085,6.174
|
| 663 |
+
2026-03-23,BALAJITELE.NS,11.96,MEDIUM,2.191,-1.161,3.626
|
| 664 |
+
2026-03-27,KRISHNADEF.NS,11.18,LOW,-3.416,5.558,5.45
|
| 665 |
+
2026-03-27,TRU.NS,10.9,LOW,-8.069,1.294,11.453
|
| 666 |
+
2026-03-27,SRGHFL.NS,10.83,LOW,3.978,4.709,12.512
|
| 667 |
+
2026-03-27,KROSS.NS,10.57,MEDIUM,-3.362,-2.376,0.937
|
| 668 |
+
2026-03-27,YATRA.NS,10.38,LOW,-6.768,2.349,0.952
|
| 669 |
+
2026-04-02,PUNJABCHEM.NS,11.44,LOW,-0.481,5.621,9.98
|
| 670 |
+
2026-04-02,SRD.NS,11.24,LOW,0.845,-1.243,-0.21
|
| 671 |
+
2026-04-02,SPECTRUM.NS,11.18,LOW,-2.838,-8.184,-12.981
|
| 672 |
+
2026-04-02,LAL.NS,10.79,LOW,1.367,3.172,8.45
|
| 673 |
+
2026-04-02,IXIGO.NS,10.75,LOW,-1.999,6.92,1.747
|
| 674 |
+
2026-04-08,SEPC.NS,10.21,MEDIUM,7.25,0.697,8.674
|
| 675 |
+
2026-04-08,JHS.NS,8.97,LOW,1.215,3.197,9.024
|
| 676 |
+
2026-04-08,NACLIND.NS,8.61,LOW,-0.048,-2.879,5.693
|
| 677 |
+
2026-04-08,SUVIDHAA.NS,8.54,LOW,7.947,8.978,4.855
|
| 678 |
+
2026-04-08,GREENPLY.NS,8.43,LOW,0.638,1.438,9.492
|
| 679 |
+
2026-04-13,SURAJLTD.NS,10.31,LOW,3.204,-1.359,-5.529
|
| 680 |
+
2026-04-13,CHEMPLASTS.NS,10.19,LOW,1.266,1.223,5.984
|
| 681 |
+
2026-04-13,GSLSU.NS,10.16,LOW,4.402,3.297,2.851
|
| 682 |
+
2026-04-13,GUJRAFFIA.NS,9.76,LOW,-0.602,-1.004,1.635
|
| 683 |
+
2026-04-13,DDEVPLSTIK.NS,9.72,LOW,4.161,6.694,4.253
|
| 684 |
+
2026-04-17,DJML.NS,8.71,LOW,-1.65,0.704,-1.185
|
| 685 |
+
2026-04-17,SADBHAV.NS,6.88,LOW,1.616,3.148,-0.875
|
| 686 |
+
2026-04-17,DBSTOCKBRO.NS,6.47,LOW,2.531,1.943,0.104
|
| 687 |
+
2026-04-17,ABMINTLLTD.NS,5.6,LOW,4.59,-5.611,-13.727
|
| 688 |
+
2026-04-17,21STCENMGM.NS,4.76,LOW,1.675,5.778,10.036
|
| 689 |
+
2026-04-22,TECILCHEM.NS,8.39,LOW,-0.158,-7.686,-9.249
|
| 690 |
+
2026-04-22,SRGHFL.NS,6.53,LOW,-4.462,-5.12,-6.646
|
| 691 |
+
2026-04-22,MAPMYINDIA.NS,6.16,LOW,-1.999,-3.025,-4.035
|
| 692 |
+
2026-04-22,LORDSCHLO.NS,6.06,LOW,0.821,-0.557,-1.569
|
| 693 |
+
2026-04-22,DIAMINESQ.NS,5.56,LOW,-1.262,-0.067,-3.687
|
| 694 |
+
2026-04-27,EPIGRAL.NS,6.85,LOW,-1.351,-1.619,7.558
|
| 695 |
+
2026-04-27,SHIVAUM.NS,6.38,LOW,-5.3,-2.234,-2.731
|
| 696 |
+
2026-04-27,GATECHDVR.NS,5.86,LOW,-0.3,-2.428,1.828
|
| 697 |
+
2026-04-27,SRD.NS,5.81,LOW,-1.48,-0.767,-0.723
|
| 698 |
+
2026-04-27,DENTA.NS,5.72,LOW,0.109,-0.617,1.123
|
| 699 |
+
2026-04-30,GVPIL.NS,7.5,LOW,-0.3,3.655,10.174
|
| 700 |
+
2026-04-30,AQYLON.NS,7.39,LOW,1.69,4.178,2.188
|
| 701 |
+
2026-04-30,KRISHANA.NS,5.07,LOW,-0.3,5.332,13.098
|
| 702 |
+
2026-04-30,JINDALSAW.NS,4.82,LOW,-0.3,4.293,8.548
|
| 703 |
+
2026-04-30,IFGLEXPOR.NS,4.39,LOW,-0.3,1.286,-1.143
|
| 704 |
+
2026-05-05,FINOPB.NS,5.03,LOW,1.157,1.265,-3.744
|
| 705 |
+
2026-05-13,SYRMA.NS,5.25,LOW,-0.971,-6.644,-3.356
|
| 706 |
+
2026-05-13,AZAD.NS,4.56,LOW,2.75,-9.625,-5.508
|
| 707 |
+
2026-05-18,PRAENG.NS,8.28,LOW,4.622,6.815,1.552
|
| 708 |
+
2026-05-18,THEINVEST.NS,6.85,LOW,1.546,0.103,-0.129
|
| 709 |
+
2026-05-18,PANSARI.NS,6.71,LOW,-0.512,0.878,10.086
|
| 710 |
+
2026-05-18,AEROENTER.NS,6.21,LOW,-0.885,-2.989,-2.999
|
| 711 |
+
2026-05-18,MANUGRAPH.NS,6.21,LOW,3.954,25.763,20.968
|
| 712 |
+
2026-05-21,EXICOM.NS,10.41,LOW,14.151,6.98,6.444
|
| 713 |
+
2026-05-21,BCG.NS,8.58,LOW,0.366,-1.822,-2.393
|
| 714 |
+
2026-06-08,SUMEETINDS.NS,6.82,LOW,-3.17,0.988,8.409
|
| 715 |
+
2026-06-11,HUBTOWN.NS,7.02,LOW,2.737,11.864,9.415
|
| 716 |
+
2026-06-11,TDPOWERSYS.NS,5.16,LOW,5.633,3.563,12.4
|
| 717 |
+
2026-06-16,GICL.NS,6.11,LOW,-2.765,-4.252,-7.142
|
| 718 |
+
2026-06-16,SHIVAUM.NS,4.65,LOW,0.768,1.042,-3.066
|
| 719 |
+
2026-06-19,VIPULLTD.NS,8.2,LOW,0.598,10.741,16.217
|
| 720 |
+
2026-06-19,ZEELEARN.NS,6.15,MEDIUM,4.689,5.936,0.947
|
| 721 |
+
2026-06-19,GRMOVER.NS,6.0,LOW,-0.429,-2.733,-1.447
|
| 722 |
+
2026-06-19,AVANTIFEED.NS,5.24,LOW,-1.468,0.795,-0.868
|
research/model_family_compare.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""research/model_family_compare.py — settle "would Random Forest / KNN / a merged-strategy
|
| 3 |
+
model beat the current gradient-boosted trees?" with an EMPIRICAL out-of-sample A/B.
|
| 4 |
+
|
| 5 |
+
Trains several model families on the SAME features + SAME time-split as the production ML
|
| 6 |
+
model and compares them on the metric that actually matters for swing trades: OUT-OF-SAMPLE
|
| 7 |
+
DIRECTION ACCURACY (predicted dir vs realised excess-of-Nifty dir_1D / dir_3D), plus the
|
| 8 |
+
tradeable BULLISH-precision (of the stocks it calls BULLISH, how many actually were).
|
| 9 |
+
|
| 10 |
+
Model families compared:
|
| 11 |
+
• GBT — HistGradientBoostingClassifier (what production uses)
|
| 12 |
+
• RandForest — RandomForestClassifier
|
| 13 |
+
• KNN — KNeighborsClassifier (standardised features)
|
| 14 |
+
• LogReg — LogisticRegression (linear baseline, standardised)
|
| 15 |
+
• +Strat — GBT with the S1..S20 strategy trigger flags ADDED (tests "merge strategies")
|
| 16 |
+
|
| 17 |
+
Also runs a META-LABELING probe: train a 2nd model to predict whether the GBT's own call is
|
| 18 |
+
correct, then check if gating to its high-confidence subset raises DirAcc (López de Prado's
|
| 19 |
+
meta-labeling — the principled way to "self-learn which contexts are reliable").
|
| 20 |
+
|
| 21 |
+
Research only; reads training_data_extra.csv; never touches the production model.
|
| 22 |
+
|
| 23 |
+
Usage:
|
| 24 |
+
python research/model_family_compare.py # dir_3D, 6-month OOS
|
| 25 |
+
python research/model_family_compare.py --tf 1D --holdout-months 6
|
| 26 |
+
"""
|
| 27 |
+
from __future__ import annotations
|
| 28 |
+
|
| 29 |
+
import argparse
|
| 30 |
+
import os
|
| 31 |
+
import sys
|
| 32 |
+
import warnings
|
| 33 |
+
|
| 34 |
+
import numpy as np
|
| 35 |
+
import pandas as pd
|
| 36 |
+
|
| 37 |
+
warnings.filterwarnings("ignore")
|
| 38 |
+
|
| 39 |
+
_PROJ_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 40 |
+
if _PROJ_ROOT not in sys.path:
|
| 41 |
+
sys.path.insert(0, _PROJ_ROOT)
|
| 42 |
+
|
| 43 |
+
from ml_predictor.features import FEATURE_COLUMNS # noqa: E402
|
| 44 |
+
|
| 45 |
+
_CSV = os.path.join(_PROJ_ROOT, "ml_predictor", "training_data_extra.csv")
|
| 46 |
+
_DIRC = {"1D": "dir_1D", "3D": "dir_3D"}
|
| 47 |
+
# Strategy trigger flags already present in the feature CSV (the "merge strategies" inputs).
|
| 48 |
+
_TRIG_COLS = [f"trigger_T{n}" for n in range(1, 8)]
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def _metrics(y_true, y_pred, label=""):
|
| 52 |
+
from sklearn.metrics import accuracy_score, f1_score
|
| 53 |
+
acc = accuracy_score(y_true, y_pred)
|
| 54 |
+
f1 = f1_score(y_true, y_pred, average="macro")
|
| 55 |
+
# BULLISH precision — of everything called BULLISH, how many really were (the tradeable edge)
|
| 56 |
+
bull_mask = y_pred == "BULLISH"
|
| 57 |
+
bull_prec = float((y_true[bull_mask] == "BULLISH").mean()) if bull_mask.sum() else float("nan")
|
| 58 |
+
bull_n = int(bull_mask.sum())
|
| 59 |
+
return {"model": label, "acc": acc, "macro_f1": f1, "bull_prec": bull_prec, "bull_n": bull_n}
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def run(tf: str, holdout_months: int):
|
| 63 |
+
from sklearn.ensemble import HistGradientBoostingClassifier, RandomForestClassifier
|
| 64 |
+
from sklearn.neighbors import KNeighborsClassifier
|
| 65 |
+
from sklearn.linear_model import LogisticRegression
|
| 66 |
+
from sklearn.preprocessing import StandardScaler
|
| 67 |
+
from sklearn.pipeline import make_pipeline
|
| 68 |
+
|
| 69 |
+
df = pd.read_csv(_CSV)
|
| 70 |
+
df["date"] = pd.to_datetime(df["date"])
|
| 71 |
+
target = _DIRC[tf]
|
| 72 |
+
feats = [c for c in FEATURE_COLUMNS if c in df.columns]
|
| 73 |
+
df = df.dropna(subset=feats + [target])
|
| 74 |
+
cutoff = df["date"].max() - pd.DateOffset(months=holdout_months)
|
| 75 |
+
tr = df[df["date"] <= cutoff]
|
| 76 |
+
te = df[df["date"] > cutoff]
|
| 77 |
+
print(f" TF={tf} · target={target} · features={len(feats)}")
|
| 78 |
+
print(f" Train ≤ {cutoff.date()}: {len(tr):,} rows · OOS > {cutoff.date()}: {len(te):,} rows")
|
| 79 |
+
print(f" OOS class balance: " + ", ".join(f"{k} {v:.0%}" for k, v in te[target].value_counts(normalize=True).items()))
|
| 80 |
+
if len(te) < 200:
|
| 81 |
+
raise SystemExit("OOS too small — lower --holdout-months or rebuild the CSV.")
|
| 82 |
+
|
| 83 |
+
Xtr, ytr = tr[feats].to_numpy(float), tr[target].to_numpy()
|
| 84 |
+
Xte, yte = te[feats].to_numpy(float), te[target].to_numpy()
|
| 85 |
+
|
| 86 |
+
results = []
|
| 87 |
+
# 1) GBT — production family
|
| 88 |
+
gbt = HistGradientBoostingClassifier(max_iter=300, learning_rate=0.06, class_weight="balanced", random_state=0)
|
| 89 |
+
gbt.fit(Xtr, ytr)
|
| 90 |
+
results.append(_metrics(yte, gbt.predict(Xte), "GBT (production family)"))
|
| 91 |
+
# 2) Random Forest
|
| 92 |
+
rf = RandomForestClassifier(n_estimators=400, max_depth=12, class_weight="balanced", n_jobs=-1, random_state=0)
|
| 93 |
+
rf.fit(Xtr, ytr)
|
| 94 |
+
results.append(_metrics(yte, rf.predict(Xte), "RandomForest"))
|
| 95 |
+
# 3) KNN (standardised)
|
| 96 |
+
knn = make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=45, weights="distance", n_jobs=-1))
|
| 97 |
+
knn.fit(Xtr, ytr)
|
| 98 |
+
results.append(_metrics(yte, knn.predict(Xte), "KNN (k=45, scaled)"))
|
| 99 |
+
# 4) Logistic Regression (linear baseline)
|
| 100 |
+
lr = make_pipeline(StandardScaler(), LogisticRegression(max_iter=2000, class_weight="balanced"))
|
| 101 |
+
lr.fit(Xtr, ytr)
|
| 102 |
+
results.append(_metrics(yte, lr.predict(Xte), "LogReg (linear)"))
|
| 103 |
+
# 5) GBT + explicit strategy trigger flags ("merge strategies")
|
| 104 |
+
trig = [c for c in _TRIG_COLS if c in df.columns]
|
| 105 |
+
if trig:
|
| 106 |
+
feats2 = feats + trig
|
| 107 |
+
gbt2 = HistGradientBoostingClassifier(max_iter=300, learning_rate=0.06, class_weight="balanced", random_state=0)
|
| 108 |
+
gbt2.fit(tr[feats2].to_numpy(float), ytr)
|
| 109 |
+
results.append(_metrics(yte, gbt2.predict(te[feats2].to_numpy(float)), f"GBT + {len(trig)} strat flags"))
|
| 110 |
+
|
| 111 |
+
# ── Majority-class baseline (what you beat by doing nothing) ──
|
| 112 |
+
maj = pd.Series(ytr).mode()[0]
|
| 113 |
+
results.append(_metrics(yte, np.array([maj] * len(yte)), f"Baseline (always {maj})"))
|
| 114 |
+
|
| 115 |
+
print("\n" + "=" * 78)
|
| 116 |
+
print(f" MODEL-FAMILY A/B — out-of-sample direction accuracy ({target})")
|
| 117 |
+
print("=" * 78)
|
| 118 |
+
print(f" {'Model':<28}{'DirAcc':>8}{'MacroF1':>9}{'BULLprec':>10}{'BULL_n':>8}")
|
| 119 |
+
print(" " + "-" * 66)
|
| 120 |
+
for r in results:
|
| 121 |
+
bp = f"{r['bull_prec']:.0%}" if r["bull_prec"] == r["bull_prec"] else "—"
|
| 122 |
+
print(f" {r['model']:<28}{r['acc']:>7.1%}{r['macro_f1']:>9.2f}{bp:>10}{r['bull_n']:>8}")
|
| 123 |
+
|
| 124 |
+
# ── META-LABELING probe: can a 2nd model predict when GBT is right? ──
|
| 125 |
+
print("\n" + "=" * 78)
|
| 126 |
+
print(" META-LABELING PROBE — gate to contexts where GBT is predicted reliable")
|
| 127 |
+
print("=" * 78)
|
| 128 |
+
# In-sample cross-fitted 'GBT correct?' labels to avoid leakage: refit GBT on a sub-split.
|
| 129 |
+
from sklearn.model_selection import cross_val_predict
|
| 130 |
+
base = HistGradientBoostingClassifier(max_iter=300, learning_rate=0.06, class_weight="balanced", random_state=0)
|
| 131 |
+
tr_pred = cross_val_predict(base, Xtr, ytr, cv=3, method="predict")
|
| 132 |
+
correct = (tr_pred == ytr).astype(int)
|
| 133 |
+
meta = HistGradientBoostingClassifier(max_iter=300, learning_rate=0.06, random_state=0)
|
| 134 |
+
meta.fit(Xtr, correct)
|
| 135 |
+
# Base GBT already fit above (gbt). Its OOS calls + meta's P(correct):
|
| 136 |
+
p_correct = meta.predict_proba(Xte)[:, 1]
|
| 137 |
+
base_pred = gbt.predict(Xte)
|
| 138 |
+
base_acc = (base_pred == yte).mean()
|
| 139 |
+
for thr in (0.5, 0.6, 0.7):
|
| 140 |
+
keep = p_correct >= thr
|
| 141 |
+
if keep.sum() < 20:
|
| 142 |
+
print(f" P(correct)≥{thr:.1f}: too few kept ({int(keep.sum())})")
|
| 143 |
+
continue
|
| 144 |
+
gated_acc = (base_pred[keep] == yte[keep]).mean()
|
| 145 |
+
bull = keep & (base_pred == "BULLISH")
|
| 146 |
+
bull_prec = (yte[bull] == "BULLISH").mean() if bull.sum() else float("nan")
|
| 147 |
+
print(f" P(correct)≥{thr:.1f}: kept {keep.mean():>4.0%} of rows · DirAcc {gated_acc:.1%} "
|
| 148 |
+
f"(vs {base_acc:.1%} ungated) · BULLprec {bull_prec:.0%} (n={int(bull.sum())})")
|
| 149 |
+
print("\n Read: if gating to high P(correct) raises DirAcc above ungated, meta-labeling is the")
|
| 150 |
+
print(" real lever — a 2nd model that learns WHICH setups to trust (self-learns from outcomes).")
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def main():
|
| 154 |
+
ap = argparse.ArgumentParser()
|
| 155 |
+
ap.add_argument("--tf", default="3D", choices=["1D", "3D"])
|
| 156 |
+
ap.add_argument("--holdout-months", type=int, default=6)
|
| 157 |
+
args = ap.parse_args()
|
| 158 |
+
run(args.tf, args.holdout_months)
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
if __name__ == "__main__":
|
| 162 |
+
main()
|
research/strategy_combo_swing.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
research/strategy_combo_swing.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""research/strategy_combo_swing.py — does STRATEGY CONFLUENCE (2-3 signals firing
|
| 3 |
+
together) improve SWING-trade (1D / 3D) hit rate?
|
| 4 |
+
|
| 5 |
+
For a broad sample of already-cached NSE tickers this walks each trading day in a lookback
|
| 6 |
+
window (point-in-time, no lookahead), records:
|
| 7 |
+
• which strategy signals (S1..S20, S_CTRIO, MFS, …) were active that day (5-bar window,
|
| 8 |
+
exactly like predictor_core.run_strategy_signals)
|
| 9 |
+
• the ML model's 1D/3D call + its median target
|
| 10 |
+
• whether the median target was actually hit over the forward window
|
| 11 |
+
|
| 12 |
+
then answers three swing-trading questions:
|
| 13 |
+
1. Does median-hit RISE with the NUMBER of strategies co-firing (0 / 1 / 2 / 3+)?
|
| 14 |
+
2. Which specific 2-strategy PAIRS give the best median-hit?
|
| 15 |
+
3. Which specific 3-strategy TRIPLES give the best median-hit?
|
| 16 |
+
Reported for ALL calls and for ML-BULLISH-only calls (the actual swing-long entries).
|
| 17 |
+
|
| 18 |
+
No network: uses the OHLCV cache (fetch_ohlcv hits the SQLite cache for warmed tickers).
|
| 19 |
+
|
| 20 |
+
Usage:
|
| 21 |
+
python research/strategy_combo_swing.py # 200 cached tickers, 90-day window
|
| 22 |
+
python research/strategy_combo_swing.py --tickers 350 --days 120
|
| 23 |
+
python research/strategy_combo_swing.py --tfs 3D --min-n 40 --bullish-only
|
| 24 |
+
"""
|
| 25 |
+
from __future__ import annotations
|
| 26 |
+
|
| 27 |
+
import argparse
|
| 28 |
+
import itertools
|
| 29 |
+
import os
|
| 30 |
+
import sys
|
| 31 |
+
from collections import defaultdict
|
| 32 |
+
|
| 33 |
+
import numpy as np
|
| 34 |
+
import pandas as pd
|
| 35 |
+
|
| 36 |
+
_PROJ_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 37 |
+
if _PROJ_ROOT not in sys.path:
|
| 38 |
+
sys.path.insert(0, _PROJ_ROOT)
|
| 39 |
+
|
| 40 |
+
from ml_predictor.features import FEATURE_COLUMNS, compute_features # noqa: E402
|
| 41 |
+
from ml_predictor.infer import MLPredictor # noqa: E402
|
| 42 |
+
from research.ml_backtest import _graded_hit # noqa: E402
|
| 43 |
+
from data_sources import cached_tickers, fetch_ohlcv # noqa: E402
|
| 44 |
+
import trial_run as T # noqa: E402
|
| 45 |
+
|
| 46 |
+
_HORIZON = {"1D": 1, "3D": 3}
|
| 47 |
+
_LOOKBACK = 5 # a signal is "active" for 5 bars, matching run_strategy_signals
|
| 48 |
+
_DIRC = {"1D": "dir_1D", "3D": "dir_3D"} # realized excess-of-Nifty direction label
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
# gen(name → callable(sc,sh,sl,sv,nifty,vix)) mirroring predictor_core.run_strategy_signals
|
| 52 |
+
def _gen_map():
|
| 53 |
+
n = lambda f: (lambda sc, sh, sl, sv, ni, vx: f(sc, sh, sl, sv, ni))
|
| 54 |
+
b = lambda f: (lambda sc, sh, sl, sv, ni, vx: f(sc, sh, sl, sv))
|
| 55 |
+
v = lambda f: (lambda sc, sh, sl, sv, ni, vx: f(sc, sh, sl, sv, vx))
|
| 56 |
+
nv = lambda f: (lambda sc, sh, sl, sv, ni, vx: f(sc, sh, sl, sv, ni, vx))
|
| 57 |
+
return {
|
| 58 |
+
"S1": n(T.gen_s1), "S2": n(T.gen_s2), "S3": b(T.gen_s3), "MFS": n(T.gen_mfs),
|
| 59 |
+
"NIRA": n(T.gen_nira), "PED": b(T.gen_ped),
|
| 60 |
+
"SUPER": (lambda sc, sh, sl, sv, ni, vx: T.gen_supertrend(sc, sh, sl)),
|
| 61 |
+
"S4": v(T.gen_s4), "S5": v(T.gen_s5), "S6": nv(T.gen_s6),
|
| 62 |
+
"S4V2": nv(T.gen_s4v2), "S5V2": nv(T.gen_s5v2), "S6V2": nv(T.gen_s6v2),
|
| 63 |
+
"S7": nv(T.gen_s7), "S8": nv(T.gen_s8), "S9": nv(T.gen_s9), "S10": nv(T.gen_s10),
|
| 64 |
+
"S11": nv(T.gen_s11), "S_CAPFLOW": nv(T.gen_s_capflow),
|
| 65 |
+
"S_CTRIO": nv(T.gen_s_confluence_trio), "S_SEASONAL": nv(T.gen_s_seasonal),
|
| 66 |
+
"S12": nv(T.gen_s12), "S13": nv(T.gen_s13), "S14": nv(T.gen_s14), "S15": nv(T.gen_s15),
|
| 67 |
+
"S16": nv(T.gen_s16), "S17": nv(T.gen_s17), "S18": nv(T.gen_s18), "S19": nv(T.gen_s19),
|
| 68 |
+
"S20": nv(T.gen_s20),
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def _active_by_pos(tk, sc, sh, sl, sv, nifty_c, vix_c, n_bars: int, gens) -> list[set]:
|
| 73 |
+
"""active_by_pos[p] = set of strategies active at bar position p (fired within last 5 bars)."""
|
| 74 |
+
active = [set() for _ in range(n_bars)]
|
| 75 |
+
pos_of = {d: i for i, d in enumerate(sc.index)}
|
| 76 |
+
for name, fn in gens.items():
|
| 77 |
+
try:
|
| 78 |
+
sigs = fn(sc, sh, sl, sv, nifty_c, vix_c)
|
| 79 |
+
except Exception:
|
| 80 |
+
continue
|
| 81 |
+
for d, t in sigs:
|
| 82 |
+
p = pos_of.get(d)
|
| 83 |
+
if p is None:
|
| 84 |
+
continue
|
| 85 |
+
for q in range(p, min(p + _LOOKBACK, n_bars)):
|
| 86 |
+
active[q].add(name)
|
| 87 |
+
return active
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def _indices():
|
| 91 |
+
try:
|
| 92 |
+
import yfinance as yf
|
| 93 |
+
raw = yf.download(["^NSEI", "^INDIAVIX"], period="2y", auto_adjust=True, progress=False)
|
| 94 |
+
return raw["Close"]["^NSEI"].dropna(), raw["Close"]["^INDIAVIX"].dropna()
|
| 95 |
+
except Exception:
|
| 96 |
+
return None, None
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
_CSV = os.path.join(_PROJ_ROOT, "ml_predictor", "training_data_extra.csv")
|
| 100 |
+
_UP = {"1D": "up_1D", "3D": "up_3D"}
|
| 101 |
+
_DN = {"1D": "dn_1D", "3D": "dn_3D"}
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def run(n_tickers: int, days: int, tfs, min_n: int, bullish_only: bool, seed: int):
|
| 105 |
+
predictor = MLPredictor()
|
| 106 |
+
if not predictor.available:
|
| 107 |
+
raise SystemExit("ml_predictor model not loaded — run `python ml_predictor/train.py` first.")
|
| 108 |
+
# FAST PATH: features + realized excursions come precomputed from training_data_extra.csv
|
| 109 |
+
# (no per-day recompute, no forward fetch); only the strategy active-sets need OHLCV frames.
|
| 110 |
+
print(f" Loading precomputed features {_CSV} …")
|
| 111 |
+
data = pd.read_csv(_CSV)
|
| 112 |
+
data["date"] = pd.to_datetime(data["date"])
|
| 113 |
+
csv_tickers = set(data["ticker"].unique())
|
| 114 |
+
cached = cached_tickers("2y")
|
| 115 |
+
pool = sorted(csv_tickers & cached) # need OHLCV (strategy signals) AND feature rows
|
| 116 |
+
if not pool:
|
| 117 |
+
raise SystemExit("no overlap between cached OHLCV and feature CSV.")
|
| 118 |
+
import random
|
| 119 |
+
random.Random(seed).shuffle(pool)
|
| 120 |
+
pool = sorted(pool[:n_tickers])
|
| 121 |
+
data = data[data["ticker"].isin(pool)].copy()
|
| 122 |
+
nifty_c, vix_c = _indices()
|
| 123 |
+
gens = _gen_map()
|
| 124 |
+
|
| 125 |
+
# ── Pass 1: collect every sample's feature row + metadata (strategy set, realized excursions).
|
| 126 |
+
# Model inference is BATCHED afterwards (one _raw_predict call per TF over the whole matrix)
|
| 127 |
+
# instead of one 7-estimator call per row — the row-by-row path is ~1000× slower.
|
| 128 |
+
print(f"\n Confluence swing study — {len(pool)} tickers · last {days} rows/ticker · "
|
| 129 |
+
f"TFs {','.join(tfs)} · model cutoff {predictor.manifest.get('train_cutoff')}")
|
| 130 |
+
feat_rows = [] # list[np.ndarray] (one per sample)
|
| 131 |
+
meta = [] # list[dict] strats + realized up/dn per tf
|
| 132 |
+
for ti, tk in enumerate(pool, 1):
|
| 133 |
+
if ti % 25 == 0 or ti == len(pool):
|
| 134 |
+
print(f" … {ti}/{len(pool)} tickers ({len(feat_rows)} rows collected)")
|
| 135 |
+
sub = data[data["ticker"] == tk].sort_values("date")
|
| 136 |
+
if days and days > 0:
|
| 137 |
+
sub = sub.tail(days)
|
| 138 |
+
if sub.empty:
|
| 139 |
+
continue
|
| 140 |
+
try:
|
| 141 |
+
sc, sh, sl, sv = fetch_ohlcv(tk, "2y")
|
| 142 |
+
except Exception:
|
| 143 |
+
continue
|
| 144 |
+
active = _active_by_pos(tk, sc, sh, sl, sv, nifty_c, vix_c, len(sc), gens)
|
| 145 |
+
pos_of = {d: i for i, d in enumerate(sc.index)}
|
| 146 |
+
feat_mat = sub[FEATURE_COLUMNS].values
|
| 147 |
+
dates = sub["date"].values
|
| 148 |
+
up_vals = {tf: sub[_UP[tf]].values for tf in tfs}
|
| 149 |
+
dn_vals = {tf: sub[_DN[tf]].values for tf in tfs}
|
| 150 |
+
dirc_vals = {tf: sub[_DIRC[tf]].values for tf in tfs}
|
| 151 |
+
for ri in range(len(sub)):
|
| 152 |
+
full_p = pos_of.get(pd.Timestamp(dates[ri]))
|
| 153 |
+
strat_set = active[full_p] if full_p is not None else set()
|
| 154 |
+
feat_rows.append(feat_mat[ri])
|
| 155 |
+
meta.append({"strats": frozenset(strat_set),
|
| 156 |
+
"up": {tf: float(up_vals[tf][ri]) for tf in tfs},
|
| 157 |
+
"dn": {tf: float(dn_vals[tf][ri]) for tf in tfs},
|
| 158 |
+
"true_dir": {tf: str(dirc_vals[tf][ri]) for tf in tfs}})
|
| 159 |
+
if not feat_rows:
|
| 160 |
+
raise SystemExit("no samples collected.")
|
| 161 |
+
|
| 162 |
+
# ── Pass 2: BATCH model inference per TF, then cheap pure-Python derivation + grading.
|
| 163 |
+
X = np.asarray(feat_rows, dtype=float)
|
| 164 |
+
print(f" running batched inference on {len(X):,} rows × {len(tfs)} TFs …")
|
| 165 |
+
rows = [] # {tf, strats:frozenset, dir, median}
|
| 166 |
+
for tf in tfs:
|
| 167 |
+
median_w = float(predictor.manifest.get("tf", {}).get(tf, {}).get("median_train_width", 1.5)) or 1.5
|
| 168 |
+
q, proba_m, classes = predictor._raw_predict(tf, X) # ONE batch call per TF
|
| 169 |
+
for i, mrow in enumerate(meta):
|
| 170 |
+
row_q = {k: float(v[i]) for k, v in q.items()}
|
| 171 |
+
pr = predictor._derive(row_q, proba_m[i], classes, tf, 100.0, 1.5,
|
| 172 |
+
median_w, None, None, 0, 100.0)
|
| 173 |
+
if bullish_only and pr["direction"] != "BULLISH":
|
| 174 |
+
continue
|
| 175 |
+
g = _graded_hit(pr["direction"], 100.0, pr["target_price_lo"],
|
| 176 |
+
pr["target_price_hi"], mrow["up"][tf], mrow["dn"][tf])
|
| 177 |
+
rows.append({"tf": tf, "strats": mrow["strats"],
|
| 178 |
+
"dir": pr["direction"],
|
| 179 |
+
"dir_correct": 1 if pr["direction"] == mrow["true_dir"][tf] else 0,
|
| 180 |
+
"median": 1 if g == "MIDPOINT_HIT" else 0})
|
| 181 |
+
df = pd.DataFrame(rows)
|
| 182 |
+
if df.empty:
|
| 183 |
+
raise SystemExit("no samples collected.")
|
| 184 |
+
_report(df, tfs, min_n, bullish_only)
|
| 185 |
+
out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "strategy_combo_swing.csv")
|
| 186 |
+
df.assign(strats=df["strats"].apply(lambda s: "|".join(sorted(s)))).to_csv(out, index=False)
|
| 187 |
+
print(f"\n ✓ samples written → {out}")
|
| 188 |
+
return df
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def _report(df: pd.DataFrame, tfs, min_n: int, bullish_only: bool):
|
| 192 |
+
scope = "ML-BULLISH calls only" if bullish_only else "ALL ML calls"
|
| 193 |
+
print("\n" + "═" * 96)
|
| 194 |
+
print(f" STRATEGY CONFLUENCE FOR SWING TRADES — does co-firing lift the swing metrics? ({scope})")
|
| 195 |
+
print(" DIR-ACC = predicted direction matched the realised move (the metric that's actually ~40-50%")
|
| 196 |
+
print(" and worth improving). MID-HIT = band-midpoint reached (already ~90%, little headroom).")
|
| 197 |
+
print("═" * 96)
|
| 198 |
+
|
| 199 |
+
def bucket(n):
|
| 200 |
+
return "0" if n == 0 else ("1" if n == 1 else ("2" if n == 2 else "3+"))
|
| 201 |
+
|
| 202 |
+
for tf in tfs:
|
| 203 |
+
t = df[df["tf"] == tf]
|
| 204 |
+
if t.empty:
|
| 205 |
+
continue
|
| 206 |
+
base_dir = t["dir_correct"].mean()
|
| 207 |
+
base_med = t["median"].mean()
|
| 208 |
+
print(f"\n ── {tf} ── baseline DIR-ACC {base_dir:.0%} | MID-HIT {base_med:.0%} (N={len(t):,})")
|
| 209 |
+
|
| 210 |
+
t = t.assign(k=t["strats"].apply(lambda s: bucket(len(s))))
|
| 211 |
+
print(" confluence count → DIR-ACC (MID-HIT):")
|
| 212 |
+
for kb in ["0", "1", "2", "3+"]:
|
| 213 |
+
g = t[t["k"] == kb]
|
| 214 |
+
if len(g):
|
| 215 |
+
da, mh = g["dir_correct"].mean(), g["median"].mean()
|
| 216 |
+
print(f" {kb:<3} signals N={len(g):>6,} DIR-ACC {da:>4.0%}"
|
| 217 |
+
f" lift {da - base_dir:>+4.0%} (MID-HIT {mh:>4.0%})")
|
| 218 |
+
|
| 219 |
+
# best PAIRS / TRIPLES ranked by directional accuracy (the improvable metric)
|
| 220 |
+
_combo_table(t, 2, min_n, base_dir, "PAIRS")
|
| 221 |
+
_combo_table(t, 3, min_n, base_dir, "TRIPLES")
|
| 222 |
+
|
| 223 |
+
print("\n Read: if DIR-ACC climbs from '1 signal' → '2' → '3+', confluence sharpens the swing")
|
| 224 |
+
print(" DIRECTION call (the real edge). The best PAIRS/TRIPLES with enough N are the combos to trade.")
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
def _combo_table(t: pd.DataFrame, k: int, min_n: int, base: float, label: str):
|
| 228 |
+
counts = defaultdict(lambda: [0, 0]) # combo → [dir-correct hits, n]
|
| 229 |
+
for strats, dc in zip(t["strats"], t["dir_correct"]):
|
| 230 |
+
if len(strats) < k:
|
| 231 |
+
continue
|
| 232 |
+
for combo in itertools.combinations(sorted(strats), k):
|
| 233 |
+
c = counts[combo]
|
| 234 |
+
c[0] += dc
|
| 235 |
+
c[1] += 1
|
| 236 |
+
scored = [(combo, hn[1], hn[0] / hn[1]) for combo, hn in counts.items() if hn[1] >= min_n]
|
| 237 |
+
scored.sort(key=lambda r: r[2], reverse=True)
|
| 238 |
+
print(f" best {label} by DIR-ACC (min N={min_n}):")
|
| 239 |
+
if not scored:
|
| 240 |
+
print(f" (no {k}-combo reached N={min_n} in this sample — widen --tickers/--days or lower --min-n)")
|
| 241 |
+
return
|
| 242 |
+
for combo, n, mh in scored[:8]:
|
| 243 |
+
print(f" {'+'.join(combo):<26} N={n:>5} DIR-ACC {mh:>4.0%} lift {mh - base:>+4.0%}"
|
| 244 |
+
f"{' ⟵' if mh - base > 0.08 else ''}")
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
def main():
|
| 248 |
+
ap = argparse.ArgumentParser()
|
| 249 |
+
ap.add_argument("--tickers", type=int, default=200, help="how many cached tickers to sample")
|
| 250 |
+
ap.add_argument("--days", type=int, default=90, help="lookback trading days per ticker")
|
| 251 |
+
ap.add_argument("--tfs", default="1D,3D", help="swing timeframes (subset of 1D,3D)")
|
| 252 |
+
ap.add_argument("--min-n", type=int, default=30, help="min samples for a combo to be reported")
|
| 253 |
+
ap.add_argument("--bullish-only", action="store_true", help="restrict to ML-BULLISH (swing-long) calls")
|
| 254 |
+
ap.add_argument("--seed", type=int, default=7)
|
| 255 |
+
args = ap.parse_args()
|
| 256 |
+
tfs = [x.strip().upper() for x in args.tfs.split(",") if x.strip().upper() in _HORIZON]
|
| 257 |
+
if not tfs:
|
| 258 |
+
raise SystemExit("no valid --tfs (choose from 1D,3D)")
|
| 259 |
+
run(args.tickers, args.days, tfs, args.min_n, args.bullish_only, args.seed)
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
if __name__ == "__main__":
|
| 263 |
+
main()
|
research/strategy_validation_funnel.csv
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
strategy,category,n_is,n_oos,is_sharpe,oos_sharpe,oos_winrate,oos_mean_ret,max_dd,survived,first_fail
|
| 2 |
+
MFS,Composite,862,3403,-1.06,-0.05,48.0,-0.03,-100.0,False,01 OOS>0.5
|
| 3 |
+
S5V2,MeanRev,847,393,-0.54,0.45,51.0,0.2,-67.5,False,01 OOS>0.5
|
| 4 |
+
S5,MeanRev,3302,2051,-1.08,0.04,49.0,0.02,-99.9,False,01 OOS>0.5
|
| 5 |
+
S1,MeanRev,1,1,0.0,0.0,0.0,-0.74,0.0,False,01 OOS>0.5
|
| 6 |
+
S_CAPFLOW,MeanRev,220,107,-0.37,-0.25,41.0,-0.19,-60.4,False,01 OOS>0.5
|
| 7 |
+
S4,MeanRev,365,144,-0.9,-1.16,47.0,-0.68,-84.1,False,01 OOS>0.5
|
| 8 |
+
S8,MeanRev,40,12,-1.0,-2.29,33.0,-1.31,-9.6,False,01 OOS>0.5
|
| 9 |
+
S10,MeanRev,244,91,-1.16,-2.55,41.0,-1.29,-78.1,False,01 OOS>0.5
|
| 10 |
+
S6,MeanRev,77,30,-1.3,-2.58,40.0,-1.37,-40.0,False,01 OOS>0.5
|
| 11 |
+
S7,MeanRev,21,8,-1.37,-2.84,38.0,-0.94,-5.5,False,01 OOS>0.5
|
| 12 |
+
S16,MeanRev,19,10,0.34,-3.5,40.0,-0.93,-9.8,False,01 OOS>0.5
|
| 13 |
+
S18,MeanRev,23,9,-4.66,-3.81,33.0,-0.83,-8.7,False,01 OOS>0.5
|
| 14 |
+
S4V2,MeanRev,124,28,-1.65,-4.19,46.0,-3.36,-61.1,False,01 OOS>0.5
|
| 15 |
+
S_CTRIO,MeanRev,12,4,-0.34,-9.29,0.0,-6.09,-9.6,False,01 OOS>0.5
|
| 16 |
+
S6V2,MeanRev,15,7,-1.01,-9.58,14.0,-3.95,-20.7,False,01 OOS>0.5
|
| 17 |
+
S11,MeanRev,10,3,-2.01,-9.87,0.0,-2.81,-2.6,False,01 OOS>0.5
|
| 18 |
+
S12,Seasonal,0,158,0.0,2.16,59.0,1.17,-44.3,False,02 DD>-35
|
| 19 |
+
S_SEASONAL,Seasonal,428,282,-1.94,0.95,45.0,0.58,-67.8,False,02 DD>-35
|
| 20 |
+
S13,Seasonal,1920,0,-0.83,0.0,0.0,0.0,0.0,False,01 OOS>0.5
|
| 21 |
+
S19,Trend,17,18,-1.45,2.7,61.0,1.54,-9.0,False,03 OOS<2.5
|
| 22 |
+
SUPER,Trend,1180,463,-0.68,0.1,47.0,0.06,-97.0,False,01 OOS>0.5
|
| 23 |
+
S2,Trend,120,234,0.7,-0.01,42.0,-0.01,-70.4,False,01 OOS>0.5
|
| 24 |
+
S3,Trend,4194,1039,0.04,-0.04,48.0,-0.03,-96.7,False,01 OOS>0.5
|
| 25 |
+
S9,Trend,120,68,-0.61,-0.14,47.0,-0.09,-54.3,False,01 OOS>0.5
|
| 26 |
+
NIRA,Trend,147,275,1.46,-0.31,41.0,-0.2,-80.0,False,01 OOS>0.5
|
| 27 |
+
PED,Trend,2476,1149,-0.84,-0.74,42.0,-0.48,-100.0,False,01 OOS>0.5
|
| 28 |
+
S20,Trend,366,497,-0.34,-1.26,39.0,-0.79,-99.5,False,01 OOS>0.5
|
| 29 |
+
S14,Trend,198,51,-1.66,-2.79,41.0,-1.39,-56.2,False,01 OOS>0.5
|
| 30 |
+
S17,Volatility,70,43,-0.43,0.55,40.0,0.32,-20.4,False,04 !overfit
|
| 31 |
+
S15,Volatility,222,75,-1.7,-0.99,43.0,-0.4,-36.3,False,01 OOS>0.5
|
research/strategy_validation_funnel.py
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""research/strategy_validation_funnel.py — apply the "9,120-backtest" doc's 6-filter
|
| 3 |
+
validation funnel to THIS project's strategy signals, and answer the doc's headline claim:
|
| 4 |
+
is MEAN REVERSION really the only category that survives out-of-sample?
|
| 5 |
+
|
| 6 |
+
For each strategy signal (S1..S20, S_CTRIO, MFS, NIRA, PED, …) this builds a simple
|
| 7 |
+
long-on-signal trade series from the cached OHLCV (enter at signal close, exit `--hold`
|
| 8 |
+
trading days later, minus round-trip cost), splits it chronologically into in-sample (IS)
|
| 9 |
+
and out-of-sample (OOS), then runs the doc's six filters:
|
| 10 |
+
|
| 11 |
+
[01] OOS Sharpe > 0.5
|
| 12 |
+
[02] max drawdown better than -35%
|
| 13 |
+
[03] OOS Sharpe < 2.5 (not absurd / likely a bug)
|
| 14 |
+
[04] OOS Sharpe <= IS*1.3 + 0.5 (anti-overfit)
|
| 15 |
+
[05] >= --min-trades OOS trades
|
| 16 |
+
[06] IS Sharpe > 0
|
| 17 |
+
|
| 18 |
+
Then it aggregates SURVIVAL RATE and MEAN OOS SHARPE **by category** and prints it next to
|
| 19 |
+
the doc's own funnel so you can compare directly.
|
| 20 |
+
|
| 21 |
+
Offline only (cached tickers); never imported by the production prediction path.
|
| 22 |
+
|
| 23 |
+
Usage:
|
| 24 |
+
python research/strategy_validation_funnel.py # 300 tickers, 3-day hold
|
| 25 |
+
python research/strategy_validation_funnel.py --tickers 500 --hold 5 --min-trades 30
|
| 26 |
+
"""
|
| 27 |
+
from __future__ import annotations
|
| 28 |
+
|
| 29 |
+
import argparse
|
| 30 |
+
import os
|
| 31 |
+
import sys
|
| 32 |
+
from collections import defaultdict
|
| 33 |
+
|
| 34 |
+
import numpy as np
|
| 35 |
+
import pandas as pd
|
| 36 |
+
|
| 37 |
+
_PROJ_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 38 |
+
if _PROJ_ROOT not in sys.path:
|
| 39 |
+
sys.path.insert(0, _PROJ_ROOT)
|
| 40 |
+
|
| 41 |
+
from data_sources import cached_tickers, fetch_ohlcv # noqa: E402
|
| 42 |
+
from research.strategy_combo_swing import _gen_map, _indices # noqa: E402
|
| 43 |
+
|
| 44 |
+
ROUND_TRIP_COST_PCT = 0.30
|
| 45 |
+
TRADING_DAYS = 252
|
| 46 |
+
|
| 47 |
+
# ── Category map (mirrors the doc's taxonomy) — classified from each signal's logic ──
|
| 48 |
+
CATEGORY = {
|
| 49 |
+
# Mean reversion: oversold / dip / RSI-recovery / capitulation
|
| 50 |
+
"S1": "MeanRev", "S4": "MeanRev", "S4V2": "MeanRev", "S5": "MeanRev", "S5V2": "MeanRev",
|
| 51 |
+
"S6": "MeanRev", "S6V2": "MeanRev", "S7": "MeanRev", "S8": "MeanRev", "S10": "MeanRev",
|
| 52 |
+
"S11": "MeanRev", "S16": "MeanRev", "S18": "MeanRev", "S_CAPFLOW": "MeanRev", "S_CTRIO": "MeanRev",
|
| 53 |
+
# Trend / momentum: EMA-MACD-ADX, supertrend, breakout, gap-drift
|
| 54 |
+
"S2": "Trend", "S3": "Trend", "NIRA": "Trend", "SUPER": "Trend", "S9": "Trend",
|
| 55 |
+
"S14": "Trend", "S19": "Trend", "S20": "Trend", "PED": "Trend",
|
| 56 |
+
# Composite multi-factor
|
| 57 |
+
"MFS": "Composite",
|
| 58 |
+
# Volatility compression (squeeze / NR7)
|
| 59 |
+
"S15": "Volatility", "S17": "Volatility",
|
| 60 |
+
# Seasonal (no doc equivalent — reported separately)
|
| 61 |
+
"S_SEASONAL": "Seasonal", "S12": "Seasonal", "S13": "Seasonal",
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
# Doc's own funnel (for side-by-side comparison)
|
| 65 |
+
_DOC = {
|
| 66 |
+
"MeanRev": {"tested": 4080, "survived": 344, "rate": 8.4, "best": "Ultimate Osc 1.59"},
|
| 67 |
+
"Trend": {"tested": 3450, "survived": 108, "rate": 3.1, "best": "Turtle 1.18"},
|
| 68 |
+
"Volume": {"tested": 690, "survived": 42, "rate": 6.1, "best": "Money Flow Idx 1.02"},
|
| 69 |
+
"Composite": {"tested": 270, "survived": 11, "rate": 4.1, "best": "Triple Screen 0.97"},
|
| 70 |
+
"Volatility": {"tested": 360, "survived": 14, "rate": 3.9, "best": "Squeeze Break 0.81"},
|
| 71 |
+
"Pattern": {"tested": 240, "survived": 5, "rate": 2.1, "best": "Three Bar Rev 0.75"},
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def _sharpe(rets: list[float], hold: int) -> float:
|
| 76 |
+
"""Annualised Sharpe of a per-trade return series (each trade ≈ one `hold`-day sample)."""
|
| 77 |
+
a = np.asarray(rets, dtype=float)
|
| 78 |
+
if a.size < 2:
|
| 79 |
+
return 0.0
|
| 80 |
+
sd = a.std(ddof=1)
|
| 81 |
+
if sd <= 1e-9:
|
| 82 |
+
return 0.0
|
| 83 |
+
return float(a.mean() / sd * np.sqrt(TRADING_DAYS / max(hold, 1)))
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def _max_drawdown(rets_in_order: list[float]) -> float:
|
| 87 |
+
"""Max drawdown (%) of the equity curve from compounding trades in date order."""
|
| 88 |
+
if not rets_in_order:
|
| 89 |
+
return 0.0
|
| 90 |
+
eq = np.cumprod([1 + r / 100.0 for r in rets_in_order])
|
| 91 |
+
peak = np.maximum.accumulate(eq)
|
| 92 |
+
dd = (eq - peak) / peak
|
| 93 |
+
return float(dd.min() * 100.0)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def run(n_tickers: int, hold: int, oos_frac: float, min_trades: int, seed: int):
|
| 97 |
+
gens = _gen_map()
|
| 98 |
+
nifty_c, vix_c = _indices()
|
| 99 |
+
pool = sorted(cached_tickers("2y"))
|
| 100 |
+
if not pool:
|
| 101 |
+
raise SystemExit("no cached tickers — warm the OHLCV cache first.")
|
| 102 |
+
import random
|
| 103 |
+
random.Random(seed).shuffle(pool)
|
| 104 |
+
pool = sorted(pool[:n_tickers])
|
| 105 |
+
|
| 106 |
+
trades = defaultdict(list) # strategy -> list[(date, ret_pct_net)]
|
| 107 |
+
all_dates = []
|
| 108 |
+
print(f" Building long-on-signal trades — {len(pool)} tickers · {hold}-day hold · "
|
| 109 |
+
f"cost {ROUND_TRIP_COST_PCT}% · {len(gens)} strategies")
|
| 110 |
+
for ti, tk in enumerate(pool, 1):
|
| 111 |
+
if ti % 50 == 0 or ti == len(pool):
|
| 112 |
+
print(f" … {ti}/{len(pool)} tickers")
|
| 113 |
+
try:
|
| 114 |
+
sc, sh, sl, sv = fetch_ohlcv(tk, "2y")
|
| 115 |
+
c = sc[tk].dropna()
|
| 116 |
+
except Exception:
|
| 117 |
+
continue
|
| 118 |
+
if len(c) < 260:
|
| 119 |
+
continue
|
| 120 |
+
pos = {d: i for i, d in enumerate(c.index)}
|
| 121 |
+
cvals = c.values
|
| 122 |
+
for name, fn in gens.items():
|
| 123 |
+
try:
|
| 124 |
+
sigs = fn(sc, sh, sl, sv, nifty_c, vix_c)
|
| 125 |
+
except Exception:
|
| 126 |
+
continue
|
| 127 |
+
for d, t in sigs:
|
| 128 |
+
i = pos.get(d)
|
| 129 |
+
if i is None or i + hold >= len(cvals) or cvals[i] <= 0:
|
| 130 |
+
continue
|
| 131 |
+
ret = (cvals[i + hold] / cvals[i] - 1.0) * 100.0 - ROUND_TRIP_COST_PCT
|
| 132 |
+
trades[name].append((d, ret))
|
| 133 |
+
all_dates.append(d)
|
| 134 |
+
if not all_dates:
|
| 135 |
+
raise SystemExit("no trades generated.")
|
| 136 |
+
|
| 137 |
+
dmin, dmax = min(all_dates), max(all_dates)
|
| 138 |
+
split = dmin + (dmax - dmin) * (1 - oos_frac)
|
| 139 |
+
print(f"\n Date span {pd.Timestamp(dmin).date()} → {pd.Timestamp(dmax).date()} · "
|
| 140 |
+
f"IS < {pd.Timestamp(split).date()} ≤ OOS (OOS = last {oos_frac:.0%})")
|
| 141 |
+
|
| 142 |
+
rows = []
|
| 143 |
+
cat_oos = defaultdict(list) # category -> pooled OOS trade returns (trade-weighted)
|
| 144 |
+
for name in gens:
|
| 145 |
+
tl = sorted(trades.get(name, []), key=lambda x: x[0])
|
| 146 |
+
is_r = [r for d, r in tl if d < split]
|
| 147 |
+
oos_r = [r for d, r in tl if d >= split]
|
| 148 |
+
cat_oos[CATEGORY.get(name, "Other")].extend(oos_r)
|
| 149 |
+
is_s, oos_s = _sharpe(is_r, hold), _sharpe(oos_r, hold)
|
| 150 |
+
mdd = _max_drawdown([r for d, r in tl if d >= split])
|
| 151 |
+
n_oos = len(oos_r)
|
| 152 |
+
f = {
|
| 153 |
+
"01 OOS>0.5": oos_s > 0.5,
|
| 154 |
+
"02 DD>-35": mdd > -35.0,
|
| 155 |
+
"03 OOS<2.5": oos_s < 2.5,
|
| 156 |
+
"04 !overfit": oos_s <= is_s * 1.3 + 0.5,
|
| 157 |
+
"05 N>=min": n_oos >= min_trades,
|
| 158 |
+
"06 IS>0": is_s > 0,
|
| 159 |
+
}
|
| 160 |
+
survived = all(f.values())
|
| 161 |
+
failed = [k for k, ok in f.items() if not ok]
|
| 162 |
+
rows.append({
|
| 163 |
+
"strategy": name, "category": CATEGORY.get(name, "Other"),
|
| 164 |
+
"n_is": len(is_r), "n_oos": n_oos,
|
| 165 |
+
"is_sharpe": round(is_s, 2), "oos_sharpe": round(oos_s, 2),
|
| 166 |
+
"oos_winrate": round(100 * np.mean([r > 0 for r in oos_r]), 0) if oos_r else 0.0,
|
| 167 |
+
"oos_mean_ret": round(float(np.mean(oos_r)), 2) if oos_r else 0.0,
|
| 168 |
+
"max_dd": round(mdd, 1), "survived": survived,
|
| 169 |
+
"first_fail": failed[0] if failed else "",
|
| 170 |
+
})
|
| 171 |
+
df = pd.DataFrame(rows).sort_values(["category", "oos_sharpe"], ascending=[True, False])
|
| 172 |
+
_report(df, min_trades, cat_oos, hold)
|
| 173 |
+
out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "strategy_validation_funnel.csv")
|
| 174 |
+
df.to_csv(out, index=False)
|
| 175 |
+
print(f"\n ✓ per-strategy detail → {out}")
|
| 176 |
+
return df
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def _pooled(cat_oos: dict, hold: int):
|
| 180 |
+
"""Trade-weighted per-category stats: pool ALL OOS trades in a category into one series."""
|
| 181 |
+
out = {}
|
| 182 |
+
for cat, rets in cat_oos.items():
|
| 183 |
+
if not rets:
|
| 184 |
+
continue
|
| 185 |
+
out[cat] = {"n": len(rets), "sharpe": _sharpe(rets, hold),
|
| 186 |
+
"win": 100 * np.mean([r > 0 for r in rets]),
|
| 187 |
+
"mean": float(np.mean(rets))}
|
| 188 |
+
return out
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def _report(df: pd.DataFrame, min_trades: int, cat_oos: dict, hold: int):
|
| 192 |
+
print("\n" + "═" * 92)
|
| 193 |
+
print(" STRATEGY VALIDATION FUNNEL — doc's 6 filters applied to THIS project's signals")
|
| 194 |
+
print("═" * 92)
|
| 195 |
+
print(f" {'Strategy':<12}{'Category':<12}{'N_oos':>6}{'IS_Shp':>8}{'OOS_Shp':>9}"
|
| 196 |
+
f"{'Win%':>6}{'MeanRet':>9}{'MaxDD':>8} Verdict")
|
| 197 |
+
print(" " + "-" * 88)
|
| 198 |
+
for _, r in df.iterrows():
|
| 199 |
+
verdict = "✓ SURVIVES" if r["survived"] else f"✗ {r['first_fail']}"
|
| 200 |
+
print(f" {r['strategy']:<12}{r['category']:<12}{int(r['n_oos']):>6}{r['is_sharpe']:>8.2f}"
|
| 201 |
+
f"{r['oos_sharpe']:>9.2f}{r['oos_winrate']:>5.0f}%{r['oos_mean_ret']:>+9.2f}"
|
| 202 |
+
f"{r['max_dd']:>7.1f}% {verdict}")
|
| 203 |
+
|
| 204 |
+
print("\n" + "═" * 92)
|
| 205 |
+
print(f" SURVIVAL BY CATEGORY (our result vs the doc's 9,120-backtest funnel)")
|
| 206 |
+
print("═" * 92)
|
| 207 |
+
print(f" {'Category':<12}{'Tested':>7}{'Surv':>6}{'Rate':>7}{'MeanOOS_Shp':>13}{'BestSurvivor':>22}"
|
| 208 |
+
f" | {'DocRate':>8}{'DocBest':>20}")
|
| 209 |
+
print(" " + "-" * 108)
|
| 210 |
+
order = ["MeanRev", "Trend", "Composite", "Volatility", "Seasonal", "Other"]
|
| 211 |
+
cats = [c for c in order if c in set(df["category"])]
|
| 212 |
+
for cat in cats:
|
| 213 |
+
g = df[df["category"] == cat]
|
| 214 |
+
tested = len(g)
|
| 215 |
+
surv = int(g["survived"].sum())
|
| 216 |
+
rate = 100 * surv / tested if tested else 0
|
| 217 |
+
mean_oos = g["oos_sharpe"].mean()
|
| 218 |
+
best = g.sort_values("oos_sharpe", ascending=False).iloc[0]
|
| 219 |
+
best_lbl = f"{best['strategy']} {best['oos_sharpe']:.2f}"
|
| 220 |
+
doc = _DOC.get(cat, {})
|
| 221 |
+
doc_rate = f"{doc.get('rate', float('nan')):.1f}%" if doc else "—"
|
| 222 |
+
doc_best = doc.get("best", "—")
|
| 223 |
+
print(f" {cat:<12}{tested:>7}{surv:>6}{rate:>6.0f}%{mean_oos:>13.2f}{best_lbl:>22}"
|
| 224 |
+
f" | {doc_rate:>8}{doc_best:>20}")
|
| 225 |
+
|
| 226 |
+
# Headline comparison to the doc's claim (TRADE-WEIGHTED, robust to tiny-N strategies)
|
| 227 |
+
pooled = _pooled(cat_oos, hold)
|
| 228 |
+
print("\n ── TRADE-WEIGHTED category OOS (pool every trade in the category into one series) ──")
|
| 229 |
+
print(f" {'Category':<12}{'N_trades':>9}{'OOS_Sharpe':>12}{'Win%':>7}{'MeanRet%':>10}")
|
| 230 |
+
print(" " + "-" * 50)
|
| 231 |
+
ranked = sorted(pooled.items(), key=lambda kv: kv[1]["sharpe"], reverse=True)
|
| 232 |
+
for cat, s in ranked:
|
| 233 |
+
print(f" {cat:<12}{s['n']:>9,}{s['sharpe']:>+12.2f}{s['win']:>6.0f}%{s['mean']:>+10.2f}")
|
| 234 |
+
|
| 235 |
+
top_cat = ranked[0][0] if ranked else ""
|
| 236 |
+
mr = pooled.get("MeanRev", {}).get("sharpe", float("nan"))
|
| 237 |
+
tr = pooled.get("Trend", {}).get("sharpe", float("nan"))
|
| 238 |
+
print("\n ── VERDICT vs the doc's claim (\"mean reversion is the only category that works OOS\") ──")
|
| 239 |
+
if top_cat == "MeanRev":
|
| 240 |
+
print(f" → CONFIRMS the doc: MeanRev leads trade-weighted OOS Sharpe ({mr:+.2f} vs Trend {tr:+.2f}).")
|
| 241 |
+
else:
|
| 242 |
+
print(f" → DIFFERS from the doc: '{top_cat}' leads here; MeanRev {mr:+.2f} vs Trend {tr:+.2f}.")
|
| 243 |
+
print(" Doc tested US/crypto-style assets 2010-2025; this is NSE single-stock signals traded RAW")
|
| 244 |
+
print(" (enter@signal, exit after hold, no stop/target/ML/AI gating). Not the deployed system.")
|
| 245 |
+
print(" NOTE: point-in-time signals (no lookahead); calendar split. Low-N categories = low-confidence.")
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def main():
|
| 249 |
+
ap = argparse.ArgumentParser()
|
| 250 |
+
ap.add_argument("--tickers", type=int, default=300, help="cached tickers to sample")
|
| 251 |
+
ap.add_argument("--hold", type=int, default=3, help="holding period in trading days")
|
| 252 |
+
ap.add_argument("--oos-frac", type=float, default=0.30, help="fraction of the date span held out OOS")
|
| 253 |
+
ap.add_argument("--min-trades", type=int, default=30, help="doc filter [05]: min OOS trades to survive")
|
| 254 |
+
ap.add_argument("--seed", type=int, default=7)
|
| 255 |
+
args = ap.parse_args()
|
| 256 |
+
run(args.tickers, args.hold, args.oos_frac, args.min_trades, args.seed)
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
if __name__ == "__main__":
|
| 260 |
+
main()
|
research/watchlist_forward_eval.csv
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
ticker,date,tf,direction,confidence,conf_prob,dir_basis,ret_lo,ret_hi,target_lo,target_hi,expected_target,strategies,highest_price,highest_date,lowest_price,entered_range,median_hit,full_range_hit,direction_hit,status
|
| 2 |
+
AXISCADES.NS,2026-07-15,INTRADAY,BULLISH,HIGH,0.825,absolute,0.15,2.94,1613.12,1658.05,1624.28,S5,1652.6,2026-07-15,1595.6,1,1,0,1,DONE
|
| 3 |
+
AXISCADES.NS,2026-07-15,1D,BEARISH,LOW,0.394,vs_nifty,-4.22,0.0,1542.73,1610.7,1581.94,S5,1622.9,2026-07-16,1557.0,1,1,0,1,DONE
|
| 4 |
+
AXISCADES.NS,2026-07-15,3D,BEARISH,LOW,0.37,vs_nifty,-6.91,-0.13,1499.4,1608.61,1559.15,S5,1622.9,2026-07-16,1521.5,1,1,0,1,DONE
|
| 5 |
+
DIACABS.NS,2026-07-15,INTRADAY,NEUTRAL,LOW,0.349,absolute,-0.5,0.5,225.36,227.62,,S3|MFS|NIRA,230.5,2026-07-15,224.85,1,1,1,1,DONE
|
| 6 |
+
DIACABS.NS,2026-07-15,1D,NEUTRAL,LOW,0.388,vs_nifty,-1.0,1.0,224.23,228.75,,S3|MFS|NIRA,229.0,2026-07-16,221.01,1,1,1,1,DONE
|
| 7 |
+
DIACABS.NS,2026-07-15,3D,NEUTRAL,LOW,0.377,vs_nifty,-1.0,1.0,224.23,228.75,,S3|MFS|NIRA,244.61,2026-07-20,216.8,0,0,0,0,DONE
|
| 8 |
+
HINDZINC.NS,2026-07-15,INTRADAY,BULLISH,HIGH,0.798,absolute,0.17,2.28,528.65,539.78,531.47,,536.5,2026-07-15,527.15,1,1,0,1,DONE
|
| 9 |
+
HINDZINC.NS,2026-07-15,1D,NEUTRAL,MEDIUM,0.491,vs_nifty,-1.0,1.0,522.47,533.03,,,530.9,2026-07-16,521.1,0,0,0,0,DONE
|
| 10 |
+
HINDZINC.NS,2026-07-15,3D,NEUTRAL,MEDIUM,0.414,vs_nifty,-1.0,1.0,522.47,533.03,,,530.9,2026-07-16,514.95,1,1,1,1,DONE
|
| 11 |
+
RML.NS,2026-07-15,INTRADAY,BEARISH,MEDIUM,0.512,absolute,-3.0,-0.06,1213.76,1250.55,1242.55,S3|MFS,1270.0,2026-07-15,1230.1,1,1,0,1,DONE
|
| 12 |
+
RML.NS,2026-07-15,1D,NEUTRAL,LOW,0.405,vs_nifty,-1.0,1.0,1238.79,1263.81,,S3|MFS,1283.9,2026-07-16,1235.1,1,1,1,1,DONE
|
| 13 |
+
RML.NS,2026-07-15,3D,BEARISH,LOW,0.371,vs_nifty,-7.59,-0.07,1156.33,1250.42,1215.19,S3|MFS,1283.9,2026-07-16,1167.6,1,1,0,1,DONE
|
| 14 |
+
SCI.NS,2026-07-15,INTRADAY,BEARISH,HIGH,0.769,absolute,-2.69,-0.12,279.91,287.3,285.18,S4|S5|S5V2|S10,290.0,2026-07-15,282.9,1,1,0,1,DONE
|
| 15 |
+
SCI.NS,2026-07-15,1D,NEUTRAL,MEDIUM,0.431,vs_nifty,-1.0,1.0,284.77,290.53,,S4|S5|S5V2|S10,296.0,2026-07-16,288.5,1,1,1,1,DONE
|
| 16 |
+
SCI.NS,2026-07-15,3D,NEUTRAL,LOW,0.374,vs_nifty,-1.0,1.0,284.77,290.53,,S4|S5|S5V2|S10,296.0,2026-07-16,278.55,0,0,0,0,DONE
|
| 17 |
+
SHAILY.NS,2026-07-15,INTRADAY,BULLISH,HIGH,0.818,absolute,0.14,2.94,2697.27,2772.69,2714.85,S5|S5V2|S8,2748.5,2026-07-15,2680.0,1,1,0,1,DONE
|
| 18 |
+
SHAILY.NS,2026-07-15,1D,NEUTRAL,LOW,0.401,vs_nifty,-1.0,1.0,2666.57,2720.43,,S5|S5V2|S8,2731.8,2026-07-16,2666.3,1,1,1,1,DONE
|
| 19 |
+
SHAILY.NS,2026-07-15,3D,NEUTRAL,LOW,0.363,vs_nifty,-1.0,1.0,2666.57,2720.43,,S5|S5V2|S8,2884.6,2026-07-20,2666.3,0,0,0,0,DONE
|
| 20 |
+
STAR.NS,2026-07-15,INTRADAY,BULLISH,HIGH,0.763,absolute,0.13,2.29,1060.98,1083.86,1067.3,S5|S5V2,1077.7,2026-07-15,1053.0,1,1,0,1,DONE
|
| 21 |
+
STAR.NS,2026-07-15,1D,NEUTRAL,MEDIUM,0.468,vs_nifty,-1.0,1.0,1049.0,1070.2,,S5|S5V2,1069.0,2026-07-16,1044.0,1,1,1,1,DONE
|
| 22 |
+
STAR.NS,2026-07-15,3D,NEUTRAL,LOW,0.393,vs_nifty,-1.0,1.0,1049.0,1070.2,,S5|S5V2,1100.0,2026-07-17,1007.5,1,1,1,1,DONE
|
| 23 |
+
TATASTEEL.NS,2026-07-15,INTRADAY,BULLISH,HIGH,0.815,absolute,0.12,1.89,185.48,188.76,186.26,S5,189.33,2026-07-15,184.81,1,1,1,1,DONE
|
| 24 |
+
TATASTEEL.NS,2026-07-15,1D,NEUTRAL,MEDIUM,0.518,vs_nifty,-1.0,1.0,183.41,187.11,,S5,186.75,2026-07-16,184.91,1,1,1,1,DONE
|
| 25 |
+
TATASTEEL.NS,2026-07-15,3D,NEUTRAL,MEDIUM,0.461,vs_nifty,-1.0,1.0,183.41,187.11,,S5,187.19,2026-07-20,183.41,1,1,1,1,DONE
|
| 26 |
+
WHEELS.NS,2026-07-15,INTRADAY,BULLISH,MEDIUM,0.605,absolute,0.09,2.64,1478.93,1516.61,1487.34,S5,1510.0,2026-07-15,1466.1,1,1,0,1,DONE
|
| 27 |
+
WHEELS.NS,2026-07-15,1D,NEUTRAL,LOW,0.364,vs_nifty,-1.0,1.0,1462.82,1492.38,,S5,1495.1,2026-07-16,1459.6,1,1,1,1,DONE
|
| 28 |
+
WHEELS.NS,2026-07-15,3D,BEARISH,LOW,0.382,vs_nifty,-7.04,0.14,1373.58,1479.67,1432.34,S5,1495.1,2026-07-16,1447.1,1,0,0,1,DONE
|
research/watchlist_forward_eval.py
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""research/watchlist_forward_eval.py — PER-STOCK forward check on the watchlist.
|
| 3 |
+
|
| 4 |
+
For a single SELECTED DATE, this reconstructs the model's point-in-time prediction for every
|
| 5 |
+
watchlist ticker (no lookahead — features/strategies use only bars on/before that date), then
|
| 6 |
+
walks FORWARD over the real bars that followed and reports, PER STOCK, PER TIMEFRAME:
|
| 7 |
+
|
| 8 |
+
• the prediction made that day (direction, confidence, range, expected/median target)
|
| 9 |
+
• which STRATEGY signals (S1..S20, S_CTRIO, …) fired that day
|
| 10 |
+
• each future date and the HIGH the stock actually printed that day
|
| 11 |
+
• the single HIGHEST price the stock reached over the horizon (and the low)
|
| 12 |
+
• FULL-RANGE hit — did price reach the far (optimistic) bound of the range?
|
| 13 |
+
• MEDIAN hit — did price touch the expected/median target?
|
| 14 |
+
• ENTERED-RANGE — did price reach the near bound (enter the band at all)?
|
| 15 |
+
• DIRECTION hit — did it move the predicted way?
|
| 16 |
+
|
| 17 |
+
Nothing is aggregated into a single blended accuracy — every stock is printed on its own.
|
| 18 |
+
A short, clearly-separated STRATEGY-LIFT diagnostic at the end answers the second question
|
| 19 |
+
("which strategies can be added to raise confidence and price-hit") by comparing, over a
|
| 20 |
+
lookback window, the median-hit rate of ML-alone vs ML when a strategy also fired.
|
| 21 |
+
|
| 22 |
+
Usage:
|
| 23 |
+
python research/watchlist_forward_eval.py # auto-picks a date 6 trading days back
|
| 24 |
+
python research/watchlist_forward_eval.py --date 2026-07-15
|
| 25 |
+
python research/watchlist_forward_eval.py --tickers TATASTEEL.NS,HINDZINC.NS --date 2026-07-15
|
| 26 |
+
python research/watchlist_forward_eval.py --date 2026-07-15 --tfs 1D,3D
|
| 27 |
+
python research/watchlist_forward_eval.py --date 2026-07-15 --lift-days 40 # widen strategy-lift sample
|
| 28 |
+
python research/watchlist_forward_eval.py --no-lift # skip the strategy diagnostic
|
| 29 |
+
"""
|
| 30 |
+
from __future__ import annotations
|
| 31 |
+
|
| 32 |
+
import argparse
|
| 33 |
+
import os
|
| 34 |
+
import sqlite3
|
| 35 |
+
import sys
|
| 36 |
+
from collections import defaultdict
|
| 37 |
+
|
| 38 |
+
import numpy as np
|
| 39 |
+
import pandas as pd
|
| 40 |
+
|
| 41 |
+
_PROJ_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 42 |
+
if _PROJ_ROOT not in sys.path:
|
| 43 |
+
sys.path.insert(0, _PROJ_ROOT)
|
| 44 |
+
|
| 45 |
+
from ml_predictor.features import FEATURE_COLUMNS, TIMEFRAMES, compute_features # noqa: E402
|
| 46 |
+
from ml_predictor.infer import MLPredictor # noqa: E402
|
| 47 |
+
from predictor_core import run_strategy_signals # noqa: E402
|
| 48 |
+
|
| 49 |
+
# horizon in forward trading days per TF (0 == same-day intraday proxy: entry day's own H/L)
|
| 50 |
+
_HORIZON = {"INTRADAY": 0, "1D": 1, "3D": 3}
|
| 51 |
+
_NEUTRAL_CAP = {"INTRADAY": 0.90, "1D": 1.0, "3D": 1.0} # |close move| under which NEUTRAL "holds"
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
# ── data helpers ──────────────────────────────────────────────────────────────
|
| 55 |
+
def _watchlist() -> list[str]:
|
| 56 |
+
db = os.path.join(_PROJ_ROOT, "paper_trading.db")
|
| 57 |
+
con = sqlite3.connect("file:%s?mode=ro" % db, uri=True)
|
| 58 |
+
try:
|
| 59 |
+
return [r[0] for r in con.execute("SELECT ticker FROM watchlist ORDER BY ticker").fetchall()]
|
| 60 |
+
finally:
|
| 61 |
+
con.close()
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def _indices():
|
| 65 |
+
try:
|
| 66 |
+
import yfinance as yf
|
| 67 |
+
raw = yf.download(["^NSEI", "^INDIAVIX"], period="2y", auto_adjust=True, progress=False)
|
| 68 |
+
return raw["Close"]["^NSEI"].dropna(), raw["Close"]["^INDIAVIX"].dropna()
|
| 69 |
+
except Exception:
|
| 70 |
+
return None, None
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def _resolve_idx(index: pd.DatetimeIndex, sel: pd.Timestamp) -> int | None:
|
| 74 |
+
"""Position of the last trading bar on/before `sel`."""
|
| 75 |
+
pos = index.searchsorted(sel, side="right") - 1
|
| 76 |
+
return int(pos) if pos >= 0 else None
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def _forward(c, h, l, idx: int, horizon: int):
|
| 80 |
+
"""Return (dates, highs, lows, p0) for the forward window, or None if not enough bars.
|
| 81 |
+
horizon 0 → the entry day's own bar (INTRADAY same-day proxy)."""
|
| 82 |
+
p0 = float(c.iloc[idx])
|
| 83 |
+
if p0 <= 0:
|
| 84 |
+
return None
|
| 85 |
+
if horizon == 0:
|
| 86 |
+
return [c.index[idx]], [float(h.iloc[idx])], [float(l.iloc[idx])], p0
|
| 87 |
+
if idx + horizon >= len(c):
|
| 88 |
+
return None
|
| 89 |
+
js = range(idx + 1, idx + horizon + 1)
|
| 90 |
+
return ([c.index[j] for j in js], [float(h.iloc[j]) for j in js], [float(l.iloc[j]) for j in js], p0)
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def _hits(pred: dict, tf: str, p0: float, highs, lows, close_ret_pct: float) -> dict:
|
| 94 |
+
"""Compute entered-range / median / full-range / direction hits from actual forward H/L."""
|
| 95 |
+
d = (pred.get("direction") or "NEUTRAL").upper()
|
| 96 |
+
hi = max(highs)
|
| 97 |
+
lo = min(lows)
|
| 98 |
+
tp_lo = pred["target_price_lo"] # BULLISH: near ; BEARISH: deep(far)
|
| 99 |
+
tp_hi = pred["target_price_hi"] # BULLISH: far ; BEARISH: shallow(near)
|
| 100 |
+
exp = pred.get("expected_target_price")
|
| 101 |
+
if d == "BULLISH":
|
| 102 |
+
entered = hi >= tp_lo
|
| 103 |
+
full = hi >= tp_hi
|
| 104 |
+
median = (exp is not None) and (hi >= exp)
|
| 105 |
+
direction = hi > p0
|
| 106 |
+
elif d == "BEARISH":
|
| 107 |
+
entered = lo <= tp_hi
|
| 108 |
+
full = lo <= tp_lo
|
| 109 |
+
median = (exp is not None) and (lo <= exp)
|
| 110 |
+
direction = lo < p0
|
| 111 |
+
else: # NEUTRAL / range-bound — "hit" == it actually stayed in the band
|
| 112 |
+
cap = _NEUTRAL_CAP.get(tf, 1.0)
|
| 113 |
+
held = abs(close_ret_pct) <= cap
|
| 114 |
+
entered = held
|
| 115 |
+
full = held
|
| 116 |
+
median = held
|
| 117 |
+
direction = held
|
| 118 |
+
return {"entered": bool(entered), "median": bool(median), "full": bool(full),
|
| 119 |
+
"direction": bool(direction), "high": hi, "low": lo}
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def _strategies_on(ticker, sc, sh, sl, sv, nifty_c, vix_c, idx: int) -> list[str]:
|
| 123 |
+
"""Strategy signals that fired within the last 5 bars ending at `idx` (point-in-time)."""
|
| 124 |
+
end = idx + 1
|
| 125 |
+
try:
|
| 126 |
+
res = run_strategy_signals(ticker, sc.iloc[:end], sh.iloc[:end], sl.iloc[:end],
|
| 127 |
+
sv.iloc[:end], nifty_c, vix_c=vix_c)
|
| 128 |
+
return res.get("active", [])
|
| 129 |
+
except Exception:
|
| 130 |
+
return []
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
# ── core ──────────────────────────────────────────────────────────────────────
|
| 134 |
+
def run(tickers, sel_date: pd.Timestamp, tfs, lift_days: int, do_lift: bool, lift_only: bool = False):
|
| 135 |
+
predictor = MLPredictor()
|
| 136 |
+
if not predictor.available:
|
| 137 |
+
raise SystemExit("ml_predictor model not loaded — run `python ml_predictor/train.py` first.")
|
| 138 |
+
from data_sources import fetch_ohlcv
|
| 139 |
+
nifty_c, vix_c = _indices()
|
| 140 |
+
cutoff = predictor.manifest.get("train_cutoff")
|
| 141 |
+
|
| 142 |
+
rows = [] # CSV rows (detail at the selected date)
|
| 143 |
+
lift_rows = [] # (tf, strat_fired_set, ml_dir, ml_conf, median_hit) over the lookback window
|
| 144 |
+
|
| 145 |
+
print("\n" + "═" * 96)
|
| 146 |
+
mode = "STRATEGY-LIFT ONLY" if lift_only else "per-stock detail + lift"
|
| 147 |
+
print(f" WATCHLIST FORWARD CHECK — prediction date {sel_date.date()} · "
|
| 148 |
+
f"{len(tickers)} tickers · model cutoff {cutoff} · {mode}")
|
| 149 |
+
if not lift_only:
|
| 150 |
+
print(" (every stock shown individually — no blended accuracy number)")
|
| 151 |
+
print("═" * 96)
|
| 152 |
+
|
| 153 |
+
done_n = 0
|
| 154 |
+
for tk in tickers:
|
| 155 |
+
done_n += 1
|
| 156 |
+
if lift_only and (done_n % 10 == 0 or done_n == len(tickers)):
|
| 157 |
+
print(f" … sampled {done_n}/{len(tickers)} tickers")
|
| 158 |
+
try:
|
| 159 |
+
sc, sh, sl, sv = fetch_ohlcv(tk, "2y")
|
| 160 |
+
c, h, l, v = sc[tk].dropna(), sh[tk].dropna(), sl[tk].dropna(), sv[tk].dropna()
|
| 161 |
+
except Exception as e:
|
| 162 |
+
print(f"\n {tk:<14} ! OHLCV unavailable ({e})")
|
| 163 |
+
continue
|
| 164 |
+
if len(c) < 210:
|
| 165 |
+
print(f"\n {tk:<14} ! too little history ({len(c)} bars)")
|
| 166 |
+
continue
|
| 167 |
+
|
| 168 |
+
idx = _resolve_idx(c.index, sel_date)
|
| 169 |
+
if idx is None:
|
| 170 |
+
print(f"\n {tk:<14} ! selected date precedes available history")
|
| 171 |
+
continue
|
| 172 |
+
|
| 173 |
+
eff_date = c.index[idx]
|
| 174 |
+
price = float(c.iloc[idx])
|
| 175 |
+
feat = compute_features(c, h, l, v, nifty_c, vix_c, date=eff_date)
|
| 176 |
+
if feat is None:
|
| 177 |
+
print(f"\n {tk:<14} ! features unavailable at {eff_date.date()}")
|
| 178 |
+
continue
|
| 179 |
+
feat_row = [feat.get(k, float("nan")) for k in FEATURE_COLUMNS]
|
| 180 |
+
atr14 = (feat["atr_pct"] / 100 * price) if np.isfinite(feat.get("atr_pct", np.nan)) else None
|
| 181 |
+
active = _strategies_on(tk, sc, sh, sl, sv, nifty_c, vix_c, idx)
|
| 182 |
+
|
| 183 |
+
# ── header per stock (skipped in lift-only mode) ──
|
| 184 |
+
if not lift_only:
|
| 185 |
+
note = "" if eff_date.normalize() == sel_date.normalize() else \
|
| 186 |
+
f" (nearest trading day ≤ {sel_date.date()})"
|
| 187 |
+
print("\n" + "─" * 96)
|
| 188 |
+
print(f" {tk:<14} @ {eff_date.date()}{note} entry ₹{price:,.2f}")
|
| 189 |
+
print(f" strategies firing: {', '.join(active) if active else '(none)'}"
|
| 190 |
+
f" [{len(active)} active]")
|
| 191 |
+
|
| 192 |
+
for tf in ([] if lift_only else tfs):
|
| 193 |
+
hz = _HORIZON[tf]
|
| 194 |
+
pred = predictor._predict_tf(feat_row, tf, price, atr14, None, None, 0, anchor_close=price)
|
| 195 |
+
d = pred["direction"]
|
| 196 |
+
conf = pred["confidence"]
|
| 197 |
+
p = pred.get("confidence_prob")
|
| 198 |
+
basis = pred.get("dir_basis", "absolute")
|
| 199 |
+
band = f"₹{pred['target_price_lo']:,.2f} … ₹{pred['target_price_hi']:,.2f} " \
|
| 200 |
+
f"({pred['predicted_return_lo']:+.2f}% … {pred['predicted_return_hi']:+.2f}%)"
|
| 201 |
+
exp = pred.get("expected_target_price")
|
| 202 |
+
exp_s = f"₹{exp:,.2f}" if exp is not None else "— (range-bound)"
|
| 203 |
+
basis_tag = " vs Nifty" if basis == "vs_nifty" else ""
|
| 204 |
+
|
| 205 |
+
print(f" ── {tf} ── ML {d}{basis_tag} · conf {conf}"
|
| 206 |
+
f"{f' (p={p:.2f})' if p is not None else ''}")
|
| 207 |
+
print(f" range {band}")
|
| 208 |
+
print(f" expected/median target {exp_s}")
|
| 209 |
+
|
| 210 |
+
fwd = _forward(c, h, l, idx, hz)
|
| 211 |
+
if fwd is None:
|
| 212 |
+
print(" forward: PENDING — not enough bars after the selected date yet")
|
| 213 |
+
rows.append({"ticker": tk, "date": str(eff_date.date()), "tf": tf, "direction": d,
|
| 214 |
+
"confidence": conf, "conf_prob": p, "dir_basis": basis,
|
| 215 |
+
"ret_lo": pred["predicted_return_lo"], "ret_hi": pred["predicted_return_hi"],
|
| 216 |
+
"target_lo": pred["target_price_lo"], "target_hi": pred["target_price_hi"],
|
| 217 |
+
"expected_target": exp, "strategies": "|".join(active),
|
| 218 |
+
"status": "PENDING"})
|
| 219 |
+
continue
|
| 220 |
+
|
| 221 |
+
dates, highs, lows, p0 = fwd
|
| 222 |
+
close_ret = (float(c.iloc[idx + hz]) / p0 - 1) * 100 if hz > 0 else 0.0
|
| 223 |
+
hit = _hits(pred, tf, p0, highs, lows, close_ret)
|
| 224 |
+
hi_px, hi_i = max(zip(highs, range(len(highs))))
|
| 225 |
+
hi_date = dates[hi_i]
|
| 226 |
+
|
| 227 |
+
# per future day
|
| 228 |
+
print(" forward days (actual):")
|
| 229 |
+
for dt, hh, ll in zip(dates, highs, lows):
|
| 230 |
+
mv = (hh / p0 - 1) * 100
|
| 231 |
+
print(f" {pd.Timestamp(dt).date()} high ₹{hh:,.2f} ({mv:+.2f}%) "
|
| 232 |
+
f"low ₹{ll:,.2f} ({(ll / p0 - 1) * 100:+.2f}%)")
|
| 233 |
+
print(f" highest reached ₹{hit['high']:,.2f} ({(hit['high'] / p0 - 1) * 100:+.2f}%) "
|
| 234 |
+
f"on {pd.Timestamp(hi_date).date()} · lowest ₹{hit['low']:,.2f} "
|
| 235 |
+
f"({(hit['low'] / p0 - 1) * 100:+.2f}%)")
|
| 236 |
+
mk = lambda b: "✓" if b else "✗"
|
| 237 |
+
print(f" → entered-range {mk(hit['entered'])} median-hit {mk(hit['median'])} "
|
| 238 |
+
f"full-range {mk(hit['full'])} direction {mk(hit['direction'])}")
|
| 239 |
+
|
| 240 |
+
rows.append({"ticker": tk, "date": str(eff_date.date()), "tf": tf, "direction": d,
|
| 241 |
+
"confidence": conf, "conf_prob": p, "dir_basis": basis,
|
| 242 |
+
"ret_lo": pred["predicted_return_lo"], "ret_hi": pred["predicted_return_hi"],
|
| 243 |
+
"target_lo": pred["target_price_lo"], "target_hi": pred["target_price_hi"],
|
| 244 |
+
"expected_target": exp, "strategies": "|".join(active),
|
| 245 |
+
"highest_price": round(hit["high"], 2), "highest_date": str(pd.Timestamp(hi_date).date()),
|
| 246 |
+
"lowest_price": round(hit["low"], 2),
|
| 247 |
+
"entered_range": int(hit["entered"]), "median_hit": int(hit["median"]),
|
| 248 |
+
"full_range_hit": int(hit["full"]), "direction_hit": int(hit["direction"]),
|
| 249 |
+
"status": "DONE"})
|
| 250 |
+
|
| 251 |
+
# ── strategy-lift sampling over a lookback window (per this stock) ──
|
| 252 |
+
if do_lift:
|
| 253 |
+
start = max(210, idx - lift_days + 1)
|
| 254 |
+
for j in range(start, idx + 1):
|
| 255 |
+
dj = c.index[j]
|
| 256 |
+
fj = compute_features(c, h, l, v, nifty_c, vix_c, date=dj)
|
| 257 |
+
if fj is None:
|
| 258 |
+
continue
|
| 259 |
+
frow = [fj.get(k, float("nan")) for k in FEATURE_COLUMNS]
|
| 260 |
+
pj = float(c.iloc[j])
|
| 261 |
+
aj = (fj["atr_pct"] / 100 * pj) if np.isfinite(fj.get("atr_pct", np.nan)) else None
|
| 262 |
+
act_j = set(_strategies_on(tk, sc, sh, sl, sv, nifty_c, vix_c, j))
|
| 263 |
+
for tf in tfs:
|
| 264 |
+
hz = _HORIZON[tf]
|
| 265 |
+
fwd = _forward(c, h, l, j, hz)
|
| 266 |
+
if fwd is None:
|
| 267 |
+
continue
|
| 268 |
+
pr = predictor._predict_tf(frow, tf, pj, aj, None, None, 0, anchor_close=pj)
|
| 269 |
+
_, hh, ll, p0 = fwd
|
| 270 |
+
cret = (float(c.iloc[j + hz]) / p0 - 1) * 100 if hz > 0 else 0.0
|
| 271 |
+
hit = _hits(pr, tf, p0, hh, ll, cret)
|
| 272 |
+
lift_rows.append({"tf": tf, "strats": act_j, "ml_dir": pr["direction"],
|
| 273 |
+
"ml_conf": pr["confidence"], "median": int(hit["median"]),
|
| 274 |
+
"entered": int(hit["entered"])})
|
| 275 |
+
|
| 276 |
+
if not lift_only:
|
| 277 |
+
_write_csv(rows)
|
| 278 |
+
if do_lift and lift_rows:
|
| 279 |
+
_strategy_lift(pd.DataFrame(lift_rows), tfs)
|
| 280 |
+
return rows
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
def _write_csv(rows):
|
| 284 |
+
if not rows:
|
| 285 |
+
return
|
| 286 |
+
out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "watchlist_forward_eval.csv")
|
| 287 |
+
pd.DataFrame(rows).to_csv(out, index=False)
|
| 288 |
+
print("\n" + "─" * 96)
|
| 289 |
+
print(f" ✓ Per-stock detail written → {out}")
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
def _strategy_lift(df: pd.DataFrame, tfs):
|
| 293 |
+
"""Which strategies raise the MEDIAN-hit rate when they co-fire with the ML call?
|
| 294 |
+
|
| 295 |
+
For each TF this compares ML-alone median-hit vs ML+strategy median-hit over the sampled
|
| 296 |
+
lookback window. A positive 'lift' means: on the days that strategy fired, the model's
|
| 297 |
+
median target was reached MORE often than its own baseline — i.e. adding that strategy as a
|
| 298 |
+
confirmation gate would raise both the confidence you can place in the call and the hit rate.
|
| 299 |
+
"""
|
| 300 |
+
print("\n" + "═" * 96)
|
| 301 |
+
print(" STRATEGY-LIFT DIAGNOSTIC — which signals, added as a confirm gate, raise the median-hit rate")
|
| 302 |
+
print(" (sampled point-in-time over the lookback window; lift = strat-day hit% − ML-baseline hit%)")
|
| 303 |
+
print("═" * 96)
|
| 304 |
+
# collect the strategy universe seen
|
| 305 |
+
all_strats = sorted({s for row in df["strats"] for s in row})
|
| 306 |
+
for tf in tfs:
|
| 307 |
+
t = df[df["tf"] == tf]
|
| 308 |
+
if t.empty:
|
| 309 |
+
continue
|
| 310 |
+
base = t["median"].mean()
|
| 311 |
+
base_ent = t["entered"].mean()
|
| 312 |
+
print(f"\n ── {tf} ── ML-baseline: median-hit {base:.0%} · entered-range {base_ent:.0%} "
|
| 313 |
+
f"(N={len(t)})")
|
| 314 |
+
print(f" {'strategy':<12}{'#days':>7}{'median-hit':>13}{'lift':>9}{'entered':>10}")
|
| 315 |
+
scored = []
|
| 316 |
+
for s in all_strats:
|
| 317 |
+
m = t[t["strats"].apply(lambda st: s in st)]
|
| 318 |
+
if len(m) < 3: # too few to be meaningful
|
| 319 |
+
continue
|
| 320 |
+
mh = m["median"].mean()
|
| 321 |
+
scored.append((s, len(m), mh, mh - base, m["entered"].mean()))
|
| 322 |
+
# sort by lift desc
|
| 323 |
+
scored.sort(key=lambda r: r[3], reverse=True)
|
| 324 |
+
if not scored:
|
| 325 |
+
print(" (no strategy fired often enough over this window to measure)")
|
| 326 |
+
continue
|
| 327 |
+
for s, n, mh, lift, ent in scored:
|
| 328 |
+
flag = " ⟵ helps" if lift > 0.05 and n >= 4 else ""
|
| 329 |
+
print(f" {s:<12}{n:>7}{mh:>12.0%}{lift:>+9.0%}{ent:>10.0%}{flag}")
|
| 330 |
+
print("\n Read: a strategy with a clearly positive 'lift' and enough '#days' is a candidate to")
|
| 331 |
+
print(" gate/upgrade the ML call on (raises confidence + price-hit). Zero/negative lift = the")
|
| 332 |
+
print(" model already prices that signal in, so adding it changes nothing.")
|
| 333 |
+
|
| 334 |
+
|
| 335 |
+
def _universe_sample(n: int, seed: int = 7) -> list[str]:
|
| 336 |
+
"""A deterministic sample of the dynamic NSE universe (for broad strategy-lift validation)."""
|
| 337 |
+
from universe import get_universe
|
| 338 |
+
uni = sorted(get_universe().keys())
|
| 339 |
+
if n >= len(uni):
|
| 340 |
+
return uni
|
| 341 |
+
import random
|
| 342 |
+
random.Random(seed).shuffle(uni)
|
| 343 |
+
return sorted(uni[:n])
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
def main():
|
| 347 |
+
ap = argparse.ArgumentParser()
|
| 348 |
+
ap.add_argument("--date", default=None, help="prediction date YYYY-MM-DD (default: ~6 trading days back)")
|
| 349 |
+
ap.add_argument("--tickers", default=None, help="comma-separated override (default = DB watchlist)")
|
| 350 |
+
ap.add_argument("--universe", type=int, default=0,
|
| 351 |
+
help="validate on a deterministic N-ticker sample of the NSE universe (implies --lift-only)")
|
| 352 |
+
ap.add_argument("--tfs", default="INTRADAY,1D,3D", help="comma-separated subset of INTRADAY,1D,3D")
|
| 353 |
+
ap.add_argument("--lift-days", type=int, default=30, help="lookback trading days for strategy-lift sampling")
|
| 354 |
+
ap.add_argument("--no-lift", action="store_true", help="skip the strategy-lift diagnostic")
|
| 355 |
+
ap.add_argument("--lift-only", action="store_true",
|
| 356 |
+
help="only compute the strategy-lift diagnostic (suppress per-stock detail)")
|
| 357 |
+
args = ap.parse_args()
|
| 358 |
+
|
| 359 |
+
if args.universe > 0:
|
| 360 |
+
tickers = _universe_sample(args.universe)
|
| 361 |
+
args.lift_only = True
|
| 362 |
+
elif args.tickers:
|
| 363 |
+
tickers = [t.strip() for t in args.tickers.split(",")]
|
| 364 |
+
else:
|
| 365 |
+
tickers = _watchlist()
|
| 366 |
+
if not tickers:
|
| 367 |
+
raise SystemExit("watchlist is empty — pass --tickers or --universe N")
|
| 368 |
+
tfs = [t.strip().upper() for t in args.tfs.split(",") if t.strip().upper() in TIMEFRAMES]
|
| 369 |
+
if not tfs:
|
| 370 |
+
raise SystemExit("no valid timeframes in --tfs")
|
| 371 |
+
|
| 372 |
+
if args.date:
|
| 373 |
+
sel = pd.Timestamp(args.date)
|
| 374 |
+
else:
|
| 375 |
+
# default: 6 calendar days back (≈ leaves forward bars for 3D). Resolved per-ticker to a bar.
|
| 376 |
+
sel = pd.Timestamp.today().normalize() - pd.Timedelta(days=6)
|
| 377 |
+
print(f" (no --date given; using {sel.date()} so 1D/3D horizons have realized forward bars)")
|
| 378 |
+
|
| 379 |
+
run(tickers, sel, tfs, args.lift_days, not args.no_lift, lift_only=args.lift_only)
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
if __name__ == "__main__":
|
| 383 |
+
main()
|
static/app.js
CHANGED
|
@@ -873,8 +873,15 @@ async function _fetchAndUpdateTfCell(ticker, tf, pick, attempt = 0) {
|
|
| 873 |
if (!cell) return;
|
| 874 |
|
| 875 |
const tfLabel = tf === 'INTRADAY' ? 'Today' : tf;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 876 |
cell.className = 'tf-cell tf-cell--loading';
|
| 877 |
-
cell.innerHTML =
|
|
|
|
| 878 |
|
| 879 |
try {
|
| 880 |
const res = await fetch('/api/watchlist-pick/' + encodeURIComponent(ticker) + '/' + tf, {cache: 'no-store'});
|
|
@@ -910,7 +917,8 @@ async function _fetchAndUpdateTfCell(ticker, tf, pick, attempt = 0) {
|
|
| 910 |
// (the ML estimate is already shown), don't surface a terminal error.
|
| 911 |
if (attempt < _AI_RETRY_MAX) {
|
| 912 |
cell.className = 'tf-cell tf-cell--loading';
|
| 913 |
-
cell.innerHTML =
|
|
|
|
| 914 |
setTimeout(() => _fetchAndUpdateTfCell(ticker, tf, pick, attempt + 1), 60000);
|
| 915 |
} else {
|
| 916 |
cell.className = 'tf-cell';
|
|
@@ -1073,11 +1081,13 @@ async function _fetchAndFillMl(ticker, force = false, tfs = ['INTRADAY', '1D', '
|
|
| 1073 |
if (!ml || force || stale) {
|
| 1074 |
const res = await fetch('/api/ml-predict/' + encodeURIComponent(ticker) + '?archive=1', { cache: 'no-store' });
|
| 1075 |
ml = await res.json();
|
| 1076 |
-
//
|
| 1077 |
-
//
|
| 1078 |
-
//
|
| 1079 |
-
//
|
| 1080 |
-
|
|
|
|
|
|
|
| 1081 |
_mlCache.set(ticker, ml);
|
| 1082 |
_mlCacheTs.set(ticker, Date.now());
|
| 1083 |
}
|
|
@@ -1134,8 +1144,6 @@ function renderPickCard(pick, idx, idPrefix = 'pick', mode = 'top5') {
|
|
| 1134 |
const warning = pick.warning || '';
|
| 1135 |
|
| 1136 |
const pickPrice = pick.price || 0;
|
| 1137 |
-
const sl3d = (tfs['3D'] || {}).stop_loss || 0;
|
| 1138 |
-
const tgt3d = (tfs['3D'] || {}).expected_target_price || (tfs['3D'] || {}).min_target || 0;
|
| 1139 |
let bestTf = pick.best_tf || null;
|
| 1140 |
// Recommendation source: AI by default; if the AI produced no actionable best timeframe
|
| 1141 |
// (all AI cells N/A), fall back to the ML model's strongest directional call.
|
|
@@ -1504,6 +1512,18 @@ const _top5AiRetried = new Set(); // 'ticker|tf' cells that already have a back
|
|
| 1504 |
function _renderTop5CardsInto(cardsEl, idPrefix, picks, bannerHtml = '') {
|
| 1505 |
_lastTop5 = { cardsEl, idPrefix, picks, banner: bannerHtml };
|
| 1506 |
const ordered = _applyTop5Sort(picks);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1507 |
const sortBar = `<div class="top5-sort-bar">
|
| 1508 |
<span class="top5-sort-lbl">Rank by</span>
|
| 1509 |
${[['ai','AI'],['ml','🤖 ML'],['blend','Blend']].map(([m,l]) =>
|
|
@@ -1513,7 +1533,13 @@ function _renderTop5CardsInto(cardsEl, idPrefix, picks, bannerHtml = '') {
|
|
| 1513 |
cardsEl.innerHTML = sortBar + bannerHtml + ordered.map((p, i) => renderPickCard(p, i, idPrefix)).join('');
|
| 1514 |
ordered.forEach(p => {
|
| 1515 |
const tvId = 'tv-' + idPrefix + '-' + p.ticker.replace(/[^a-zA-Z0-9]/g, '_');
|
| 1516 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1517 |
_fetchAndFillMl(p.ticker); // instant ML row; AI row keeps its own loader
|
| 1518 |
// Top picks have no per-card ↺ Retry button, so without this any TF that came back
|
| 1519 |
// 'ai_unavailable'/'timeout' would sit on "AI loading… retrying automatically" forever.
|
|
@@ -2453,7 +2479,10 @@ function openTradeModal(ticker, name, price, stopLoss = 0, target = 0, planData
|
|
| 2453 |
// Cache hit — apply synchronously, no network round-trip needed.
|
| 2454 |
const pick = cached.pick;
|
| 2455 |
const tfs = pick.timeframes || {};
|
| 2456 |
-
|
|
|
|
|
|
|
|
|
|
| 2457 |
const tf = tfs[tfKey];
|
| 2458 |
if (tf && !_pendingTradeData.prediction_data) {
|
| 2459 |
_pendingTradeData = {
|
|
@@ -2496,7 +2525,9 @@ function openTradeModal(ticker, name, price, stopLoss = 0, target = 0, planData
|
|
| 2496 |
if (!d || !d.pick) return;
|
| 2497 |
const pick = d.pick;
|
| 2498 |
const tfs = pick.timeframes || {};
|
| 2499 |
-
|
|
|
|
|
|
|
| 2500 |
const tf = tfs[tfKey];
|
| 2501 |
if (!tf || _pendingTradeData.prediction_data) return; // already populated by the time this resolves
|
| 2502 |
_pendingTradeData = {
|
|
@@ -2812,8 +2843,24 @@ function renderValidationHistoryCard(h) {
|
|
| 2812 |
};
|
| 2813 |
const gradeInfo = h.hit_grade ? GRADE_BADGE[h.hit_grade] : null;
|
| 2814 |
const midpoint = (tpLo && tpHi) ? (tpLo + tpHi) / 2 : null;
|
| 2815 |
-
|
| 2816 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2817 |
: null;
|
| 2818 |
|
| 2819 |
const rangeStr = (tpLo && tpHi)
|
|
|
|
| 873 |
if (!cell) return;
|
| 874 |
|
| 875 |
const tfLabel = tf === 'INTRADAY' ? 'Today' : tf;
|
| 876 |
+
// Keep the ML block in the loading cell (with its slot id) so the independent ML forecast
|
| 877 |
+
// stays visible while the slow AI call is in flight — ML must never disappear behind AI.
|
| 878 |
+
const _loadingCellHtml = `<div class="tf-label">${tfLabel}</div><div class="tf-cell-spinner">⟳</div>`
|
| 879 |
+
+ `<div style="font-size:11px;color:var(--text-muted);margin-top:4px">🤖 AI loading…</div>`
|
| 880 |
+
+ `<div class="tf-ai-ml-sep"><span class="tf-ai-ml-sep-lbl">🤖 ML MODEL</span></div>`
|
| 881 |
+
+ `<div class="tf-ml-block"><div class="tf-ml-slot" id="ml-${safeId}-${tf}"><div class="tf-ml-mini-loader">🤖 ML…</div></div></div>`;
|
| 882 |
cell.className = 'tf-cell tf-cell--loading';
|
| 883 |
+
cell.innerHTML = _loadingCellHtml;
|
| 884 |
+
_fetchAndFillMl(ticker, false, [tf]); // keep ML visible during the AI fetch (cached → instant)
|
| 885 |
|
| 886 |
try {
|
| 887 |
const res = await fetch('/api/watchlist-pick/' + encodeURIComponent(ticker) + '/' + tf, {cache: 'no-store'});
|
|
|
|
| 917 |
// (the ML estimate is already shown), don't surface a terminal error.
|
| 918 |
if (attempt < _AI_RETRY_MAX) {
|
| 919 |
cell.className = 'tf-cell tf-cell--loading';
|
| 920 |
+
cell.innerHTML = _loadingCellHtml;
|
| 921 |
+
_fetchAndFillMl(ticker, false, [tf]); // keep ML visible during the retry wait
|
| 922 |
setTimeout(() => _fetchAndUpdateTfCell(ticker, tf, pick, attempt + 1), 60000);
|
| 923 |
} else {
|
| 924 |
cell.className = 'tf-cell';
|
|
|
|
| 1081 |
if (!ml || force || stale) {
|
| 1082 |
const res = await fetch('/api/ml-predict/' + encodeURIComponent(ticker) + '?archive=1', { cache: 'no-store' });
|
| 1083 |
ml = await res.json();
|
| 1084 |
+
// Always cache the payload — even when NSE is closed. The ML row (especially 1D/3D,
|
| 1085 |
+
// which don't move while the market is shut) must render instantly from cache on every
|
| 1086 |
+
// re-render, otherwise each AI-retry rebuild regenerates the '🤖 ML…' placeholder and the
|
| 1087 |
+
// ML forecast appears stuck/hung behind the slow AI call. INTRADAY freshness is preserved
|
| 1088 |
+
// by the 5-min stale window and the market-hours refresh tick, which refetch the INTRADAY
|
| 1089 |
+
// slot once the session is live again.
|
| 1090 |
+
if (ml) {
|
| 1091 |
_mlCache.set(ticker, ml);
|
| 1092 |
_mlCacheTs.set(ticker, Date.now());
|
| 1093 |
}
|
|
|
|
| 1144 |
const warning = pick.warning || '';
|
| 1145 |
|
| 1146 |
const pickPrice = pick.price || 0;
|
|
|
|
|
|
|
| 1147 |
let bestTf = pick.best_tf || null;
|
| 1148 |
// Recommendation source: AI by default; if the AI produced no actionable best timeframe
|
| 1149 |
// (all AI cells N/A), fall back to the ML model's strongest directional call.
|
|
|
|
| 1512 |
function _renderTop5CardsInto(cardsEl, idPrefix, picks, bannerHtml = '') {
|
| 1513 |
_lastTop5 = { cardsEl, idPrefix, picks, banner: bannerHtml };
|
| 1514 |
const ordered = _applyTop5Sort(picks);
|
| 1515 |
+
// Preserve already-mounted chart nodes across re-renders. Streaming polls this every ~4s and
|
| 1516 |
+
// the sort toggle re-renders too; a naive innerHTML rebuild tore down + remounted every live
|
| 1517 |
+
// chart each time, causing visible blinking. Stash each mounted chart's wrapper by ticker id
|
| 1518 |
+
// and splice it back into the fresh (empty) slot instead of remounting.
|
| 1519 |
+
const chartStash = {};
|
| 1520 |
+
ordered.forEach(p => {
|
| 1521 |
+
const tvId = 'tv-' + idPrefix + '-' + p.ticker.replace(/[^a-zA-Z0-9]/g, '_');
|
| 1522 |
+
const existing = document.getElementById(tvId);
|
| 1523 |
+
if (existing && existing.dataset.chartMounted) {
|
| 1524 |
+
chartStash[tvId] = existing.closest('.chart-wrap') || existing;
|
| 1525 |
+
}
|
| 1526 |
+
});
|
| 1527 |
const sortBar = `<div class="top5-sort-bar">
|
| 1528 |
<span class="top5-sort-lbl">Rank by</span>
|
| 1529 |
${[['ai','AI'],['ml','🤖 ML'],['blend','Blend']].map(([m,l]) =>
|
|
|
|
| 1533 |
cardsEl.innerHTML = sortBar + bannerHtml + ordered.map((p, i) => renderPickCard(p, i, idPrefix)).join('');
|
| 1534 |
ordered.forEach(p => {
|
| 1535 |
const tvId = 'tv-' + idPrefix + '-' + p.ticker.replace(/[^a-zA-Z0-9]/g, '_');
|
| 1536 |
+
const stashed = chartStash[tvId];
|
| 1537 |
+
if (stashed) {
|
| 1538 |
+
const freshSlot = document.getElementById(tvId);
|
| 1539 |
+
if (freshSlot) freshSlot.replaceWith(stashed); // reuse the live chart — no remount, no blink
|
| 1540 |
+
} else {
|
| 1541 |
+
observeTvChart(tvId, p.ticker);
|
| 1542 |
+
}
|
| 1543 |
_fetchAndFillMl(p.ticker); // instant ML row; AI row keeps its own loader
|
| 1544 |
// Top picks have no per-card ↺ Retry button, so without this any TF that came back
|
| 1545 |
// 'ai_unavailable'/'timeout' would sit on "AI loading… retrying automatically" forever.
|
|
|
|
| 2479 |
// Cache hit — apply synchronously, no network round-trip needed.
|
| 2480 |
const pick = cached.pick;
|
| 2481 |
const tfs = pick.timeframes || {};
|
| 2482 |
+
// Use the RECOMMENDED timeframe (best_tf) so the modal's target / stop / logged timeframe
|
| 2483 |
+
// match the "Recommended: <TF>" the user clicked — not a hardcoded 3D preference.
|
| 2484 |
+
const tfKey = (pick.best_tf && tfs[pick.best_tf]) ? pick.best_tf
|
| 2485 |
+
: (tfs['3D'] ? '3D' : (tfs['1D'] ? '1D' : Object.keys(tfs)[0]));
|
| 2486 |
const tf = tfs[tfKey];
|
| 2487 |
if (tf && !_pendingTradeData.prediction_data) {
|
| 2488 |
_pendingTradeData = {
|
|
|
|
| 2525 |
if (!d || !d.pick) return;
|
| 2526 |
const pick = d.pick;
|
| 2527 |
const tfs = pick.timeframes || {};
|
| 2528 |
+
// Match the recommended timeframe (best_tf), not a hardcoded 3D preference.
|
| 2529 |
+
const tfKey = (pick.best_tf && tfs[pick.best_tf]) ? pick.best_tf
|
| 2530 |
+
: (tfs['3D'] ? '3D' : (tfs['1D'] ? '1D' : Object.keys(tfs)[0]));
|
| 2531 |
const tf = tfs[tfKey];
|
| 2532 |
if (!tf || _pendingTradeData.prediction_data) return; // already populated by the time this resolves
|
| 2533 |
_pendingTradeData = {
|
|
|
|
| 2843 |
};
|
| 2844 |
const gradeInfo = h.hit_grade ? GRADE_BADGE[h.hit_grade] : null;
|
| 2845 |
const midpoint = (tpLo && tpHi) ? (tpLo + tpHi) / 2 : null;
|
| 2846 |
+
|
| 2847 |
+
// Price the stock actually reached toward the target. Prefer the stored
|
| 2848 |
+
// point_reached; otherwise derive it from the realized window so the reached
|
| 2849 |
+
// price is always shown — including on misses. Bullish → window high (best
|
| 2850 |
+
// upward point), bearish → window low (best downward point), neutral → close.
|
| 2851 |
+
const _isBull = dirKey.includes('BULL');
|
| 2852 |
+
const _isBear = dirKey.includes('BEAR');
|
| 2853 |
+
let reachedPrice = h.point_reached;
|
| 2854 |
+
if (reachedPrice == null) {
|
| 2855 |
+
if (_isBull && winHi != null) reachedPrice = winHi;
|
| 2856 |
+
else if (_isBear && winLo != null) reachedPrice = winLo;
|
| 2857 |
+
else if (h.actual_price_at_validation != null) reachedPrice = h.actual_price_at_validation;
|
| 2858 |
+
else if (winHi != null) reachedPrice = winHi;
|
| 2859 |
+
}
|
| 2860 |
+
const reachedStr = (reachedPrice != null && entry > 0)
|
| 2861 |
+
? (midpoint != null
|
| 2862 |
+
? `Midpoint ₹${num(midpoint,2)} · reached ₹${num(reachedPrice,2)} <span class="vh-pct">(${pct(reachedPrice) >= 0 ? '+' : ''}${pct(reachedPrice)}%)</span>`
|
| 2863 |
+
: `Reached ₹${num(reachedPrice,2)} <span class="vh-pct">(${pct(reachedPrice) >= 0 ? '+' : ''}${pct(reachedPrice)}%)</span>`)
|
| 2864 |
: null;
|
| 2865 |
|
| 2866 |
const rangeStr = (tpLo && tpHi)
|
templates/index.html
CHANGED
|
@@ -33,7 +33,7 @@
|
|
| 33 |
@keyframes apl-spin { to { transform: rotate(360deg); } }
|
| 34 |
.apl-hint { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 11px; color: #5a6278; }
|
| 35 |
</style>
|
| 36 |
-
<link rel="stylesheet" href="/static/style.css?v=
|
| 37 |
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
|
| 38 |
</head>
|
| 39 |
<body>
|
|
@@ -449,6 +449,6 @@
|
|
| 449 |
</a>
|
| 450 |
</nav>
|
| 451 |
|
| 452 |
-
<script src="/static/app.js?v=
|
| 453 |
</body>
|
| 454 |
</html>
|
|
|
|
| 33 |
@keyframes apl-spin { to { transform: rotate(360deg); } }
|
| 34 |
.apl-hint { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 11px; color: #5a6278; }
|
| 35 |
</style>
|
| 36 |
+
<link rel="stylesheet" href="/static/style.css?v=20260724a">
|
| 37 |
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
|
| 38 |
</head>
|
| 39 |
<body>
|
|
|
|
| 449 |
</a>
|
| 450 |
</nav>
|
| 451 |
|
| 452 |
+
<script src="/static/app.js?v=20260724a"></script>
|
| 453 |
</body>
|
| 454 |
</html>
|