mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-27 15:37:44 +00:00
refactor: rename project from Predix to NexQuant
Rename all source files, scripts, tests, documentation, and configuration from Predix/predix to NexQuant/nexquant across the entire codebase.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# Pre-commit hooks configuration for Predix
|
||||
# Pre-commit hooks configuration for NexQuant
|
||||
# See https://pre-commit.com for more information
|
||||
|
||||
repos:
|
||||
|
||||
+453
-453
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -52,7 +52,7 @@ an individual is officially representing the community in public spaces.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
nico@predix.io.
|
||||
nico@nexquant.io.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
## Attribution
|
||||
|
||||
+8
-8
@@ -1,6 +1,6 @@
|
||||
# Contributing to Predix
|
||||
# Contributing to NexQuant
|
||||
|
||||
We welcome contributions and suggestions to improve Predix. Whether it's solving an issue, addressing a bug, enhancing documentation, or even correcting a typo, every contribution is valuable and helps improve the project.
|
||||
We welcome contributions and suggestions to improve NexQuant. Whether it's solving an issue, addressing a bug, enhancing documentation, or even correcting a typo, every contribution is valuable and helps improve the project.
|
||||
|
||||
## Getting Started
|
||||
|
||||
@@ -15,11 +15,11 @@ grep -r "TODO:"
|
||||
|
||||
```bash
|
||||
# Fork the repository on GitHub, then clone your fork
|
||||
git clone https://github.com/YOUR-USERNAME/Predix.git
|
||||
cd Predix
|
||||
git clone https://github.com/YOUR-USERNAME/NexQuant.git
|
||||
cd NexQuant
|
||||
|
||||
# Add upstream remote
|
||||
git remote add upstream https://github.com/TPTBusiness/Predix.git
|
||||
git remote add upstream https://github.com/TPTBusiness/NexQuant.git
|
||||
```
|
||||
|
||||
### 2. Create a Branch
|
||||
@@ -141,7 +141,7 @@ All PRs are reviewed by maintainers. Expect:
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
Predix/
|
||||
NexQuant/
|
||||
├── rdagent/ # Core framework (open source)
|
||||
│ ├── app/ # CLI and scenario apps
|
||||
│ ├── components/ # Reusable agent components
|
||||
@@ -157,8 +157,8 @@ Predix/
|
||||
|
||||
## Need Help?
|
||||
|
||||
- **Issues**: [GitHub Issues](https://github.com/TPTBusiness/Predix/issues)
|
||||
- **Discussions**: [GitHub Discussions](https://github.com/TPTBusiness/Predix/discussions)
|
||||
- **Issues**: [GitHub Issues](https://github.com/TPTBusiness/NexQuant/issues)
|
||||
- **Discussions**: [GitHub Discussions](https://github.com/TPTBusiness/NexQuant/discussions)
|
||||
- **Documentation**: See `docs/` folder
|
||||
|
||||
## License
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Predix
|
||||
# NexQuant
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/Python-3.10%20|%203.11-blue?style=for-the-badge&logo=python" alt="Python">
|
||||
@@ -27,17 +27,17 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/TPTBusiness/Predix/actions/workflows/ci.yml">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/TPTBusiness/Predix/ci.yml?branch=master&label=CI&logo=github&style=flat-square" alt="CI Status">
|
||||
<a href="https://github.com/TPTBusiness/NexQuant/actions/workflows/ci.yml">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/TPTBusiness/NexQuant/ci.yml?branch=master&label=CI&logo=github&style=flat-square" alt="CI Status">
|
||||
</a>
|
||||
<a href="https://github.com/TPTBusiness/Predix/actions/workflows/codacy.yml">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/TPTBusiness/Predix/codacy.yml?branch=master&label=Security&logo=shield&style=flat-square" alt="Security Scan">
|
||||
<a href="https://github.com/TPTBusiness/NexQuant/actions/workflows/codacy.yml">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/TPTBusiness/NexQuant/codacy.yml?branch=master&label=Security&logo=shield&style=flat-square" alt="Security Scan">
|
||||
</a>
|
||||
<a href="https://codecov.io/gh/TPTBusiness/Predix">
|
||||
<img src="https://img.shields.io/codecov/c/github/TPTBusiness/Predix?style=flat-square&logo=codecov" alt="Coverage">
|
||||
<a href="https://codecov.io/gh/TPTBusiness/NexQuant">
|
||||
<img src="https://img.shields.io/codecov/c/github/TPTBusiness/NexQuant?style=flat-square&logo=codecov" alt="Coverage">
|
||||
</a>
|
||||
<a href="https://github.com/TPTBusiness/Predix/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/TPTBusiness/Predix?style=flat-square" alt="License">
|
||||
<a href="https://github.com/TPTBusiness/NexQuant/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/TPTBusiness/NexQuant?style=flat-square" alt="License">
|
||||
</a>
|
||||
<a href="https://www.conventionalcommits.org/">
|
||||
<img src="https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow?style=flat-square" alt="Conventional Commits">
|
||||
@@ -45,17 +45,17 @@
|
||||
<a href="https://github.com/astral-sh/ruff">
|
||||
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json&style=flat-square" alt="Ruff">
|
||||
</a>
|
||||
<a href="https://github.com/TPTBusiness/Predix/stargazers">
|
||||
<img src="https://img.shields.io/github/stars/TPTBusiness/Predix?style=flat-square" alt="Stars">
|
||||
<a href="https://github.com/TPTBusiness/NexQuant/stargazers">
|
||||
<img src="https://img.shields.io/github/stars/TPTBusiness/NexQuant?style=flat-square" alt="Stars">
|
||||
</a>
|
||||
<a href="https://github.com/TPTBusiness/Predix/forks">
|
||||
<img src="https://img.shields.io/github/forks/TPTBusiness/Predix?style=flat-square" alt="Forks">
|
||||
<a href="https://github.com/TPTBusiness/NexQuant/forks">
|
||||
<img src="https://img.shields.io/github/forks/TPTBusiness/NexQuant?style=flat-square" alt="Forks">
|
||||
</a>
|
||||
<a href="https://github.com/TPTBusiness/Predix/issues">
|
||||
<img src="https://img.shields.io/github/issues/TPTBusiness/Predix?style=flat-square" alt="Issues">
|
||||
<a href="https://github.com/TPTBusiness/NexQuant/issues">
|
||||
<img src="https://img.shields.io/github/issues/TPTBusiness/NexQuant?style=flat-square" alt="Issues">
|
||||
</a>
|
||||
<a href="https://github.com/TPTBusiness/Predix/commits/master">
|
||||
<img src="https://img.shields.io/github/last-commit/TPTBusiness/Predix?style=flat-square" alt="Last Commit">
|
||||
<a href="https://github.com/TPTBusiness/NexQuant/commits/master">
|
||||
<img src="https://img.shields.io/github/last-commit/TPTBusiness/NexQuant?style=flat-square" alt="Last Commit">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -64,18 +64,18 @@
|
||||
## 🖥️ CLI Dashboard
|
||||
|
||||
```bash
|
||||
rdagent predix
|
||||
rdagent nexquant
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
*The Predix CLI shows system status, available commands, and quick start guide.*
|
||||
*The NexQuant CLI shows system status, available commands, and quick start guide.*
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
**Predix** is an autonomous AI agent for quantitative trading strategies in the EUR/USD forex market. Built on a multi-agent framework, Predix automates the full research and development cycle:
|
||||
**NexQuant** is an autonomous AI agent for quantitative trading strategies in the EUR/USD forex market. Built on a multi-agent framework, NexQuant automates the full research and development cycle:
|
||||
|
||||
- 📊 **Factor Generation** — LLM proposes novel alpha factors; Kronos foundation model generates OHLCV-based predictions
|
||||
- 💡 **Strategy Discovery** — Autopilot generates + backtests trading strategies 24/7
|
||||
@@ -83,7 +83,7 @@ rdagent predix
|
||||
- 📈 **Backtesting** — Unified engine with 10 runtime invariants on 1-min EUR/USD data (2020–2026)
|
||||
- 🔄 **Auto-Restart** — All services run as daemons with automatic crash recovery
|
||||
|
||||
Predix is optimized for **1-minute EUR/USD FX data** (2020–2026) and supports both local LLMs (llama.cpp) and cloud backends (OpenRouter).
|
||||
NexQuant is optimized for **1-minute EUR/USD FX data** (2020–2026) and supports both local LLMs (llama.cpp) and cloud backends (OpenRouter).
|
||||
|
||||
> **Backtest Verification**: Every backtest result is automatically verified at runtime against mathematical invariants (MaxDD ∈ [-1,0], WinRate ∈ [0,1], Sharpe finite, sign consistency, etc.). 1125 collected tests with deep property-based, fuzzing, and hypothesis tests ensure metric correctness. See [Backtest Integrity](#backtest-integrity).
|
||||
|
||||
@@ -99,7 +99,7 @@ Special thanks to:
|
||||
|
||||
- **[ai-hedge-fund](https://github.com/virattt/ai-hedge-fund)** - Inspiration for macro analysis (Stanley Druckenmiller agent), risk management concepts, and market regime detection.
|
||||
|
||||
All code in Predix is originally written and implemented independently. Predix extends these frameworks with EUR/USD forex-specific features, 1-minute backtesting capabilities, comprehensive risk management, and trading dashboards.
|
||||
All code in NexQuant is originally written and implemented independently. NexQuant extends these frameworks with EUR/USD forex-specific features, 1-minute backtesting capabilities, comprehensive risk management, and trading dashboards.
|
||||
|
||||
---
|
||||
|
||||
@@ -129,12 +129,12 @@ All code in Predix is originally written and implemented independently. Predix e
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://github.com/TPTBusiness/Predix
|
||||
cd Predix
|
||||
git clone https://github.com/TPTBusiness/NexQuant
|
||||
cd NexQuant
|
||||
|
||||
# Create and activate conda environment
|
||||
conda create -n predix python=3.10 -y
|
||||
conda activate predix
|
||||
conda create -n nexquant python=3.10 -y
|
||||
conda activate nexquant
|
||||
|
||||
# Install in editable mode
|
||||
pip install -e .
|
||||
@@ -143,14 +143,14 @@ pip install -e .
|
||||
docker run --rm hello-world
|
||||
```
|
||||
|
||||
> **Important:** Predix requires a conda environment to manage dependencies properly.
|
||||
> **Important:** NexQuant requires a conda environment to manage dependencies properly.
|
||||
> Using plain Python or other environment managers may cause conflicts.
|
||||
|
||||
---
|
||||
|
||||
## Data Setup
|
||||
|
||||
Predix requires **1-minute EUR/USD OHLCV data** in HDF5 format. This is a hard prerequisite — the system cannot run without it.
|
||||
NexQuant requires **1-minute EUR/USD OHLCV data** in HDF5 format. This is a hard prerequisite — the system cannot run without it.
|
||||
|
||||
### Step 1: Get the data
|
||||
|
||||
@@ -266,7 +266,7 @@ market_context:
|
||||
|
||||
## No GPU? Use OpenRouter
|
||||
|
||||
If you don't have a CUDA-capable GPU, you can run Predix using [OpenRouter](https://openrouter.ai) for LLM inference — no local model download required.
|
||||
If you don't have a CUDA-capable GPU, you can run NexQuant using [OpenRouter](https://openrouter.ai) for LLM inference — no local model download required.
|
||||
|
||||
**1. Set up `.env` for OpenRouter:**
|
||||
|
||||
@@ -293,7 +293,7 @@ rdagent fin_quant --model openrouter
|
||||
**4. Parallel runs** (uses API concurrency instead of GPU slots):
|
||||
|
||||
```bash
|
||||
python predix_parallel.py --runs 5 --api-keys 1 -m openrouter
|
||||
python nexquant_parallel.py --runs 5 --api-keys 1 -m openrouter
|
||||
```
|
||||
|
||||
> Ollama is still required for embeddings even in the OpenRouter path. Install from [ollama.com](https://ollama.com) and run `ollama pull nomic-embed-text` once.
|
||||
@@ -318,7 +318,7 @@ curl http://localhost:8081/health
|
||||
### 1. Run Trading Loop
|
||||
|
||||
```bash
|
||||
conda activate predix
|
||||
conda activate nexquant
|
||||
rdagent fin_quant
|
||||
# or with explicit options:
|
||||
rdagent fin_quant --loop-n 5 --step-n 2
|
||||
@@ -332,7 +332,7 @@ rdagent server_ui --port 19899 --log-dir git_ignore_folder/RD-Agent_workspace/
|
||||
# then open http://127.0.0.1:19899
|
||||
|
||||
# Best strategies so far
|
||||
python predix.py best
|
||||
python nexquant.py best
|
||||
```
|
||||
|
||||
### 3. Run Continuously (Auto-Restart)
|
||||
@@ -344,7 +344,7 @@ python predix.py best
|
||||
nohup bash -c 'while true; do rdagent fin_quant --loop-n 10 --model local >> /tmp/fin_quant_daemon.log 2>&1; sleep 10; done' &
|
||||
|
||||
# Autopilot — 24/7 strategy generator (Kronos factors auto-selected)
|
||||
nohup python scripts/predix_autopilot.py >> /tmp/autopilot_daemon.log 2>&1 &
|
||||
nohup python scripts/nexquant_autopilot.py >> /tmp/autopilot_daemon.log 2>&1 &
|
||||
|
||||
# Live Trader — FTMO FIX API (requires credentials)
|
||||
nohup python git_ignore_folder/live_trading/ftmo_live_trader.py >> ftmo_live_trader.log 2>&1 &
|
||||
@@ -369,13 +369,13 @@ nohup python git_ignore_folder/live_trading/ftmo_live_trader.py >> ftmo_live_tra
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `python predix.py best` | Show top strategies by composite score |
|
||||
| `python predix.py best -n 20 -m sharpe` | Top 20 by Sharpe ratio |
|
||||
| `python predix.py best --show NAME` | Full metadata for one strategy |
|
||||
| `python predix_gen_strategies_real_bt.py 10` | Generate 10 strategies with LLM + real OHLCV backtest |
|
||||
| `python predix_gen_strategies_real_bt.py 20` | Generate 20 strategies (parallel workers) |
|
||||
| `python scripts/predix_autopilot.py` | 24/7 Auto-Pilot: endless strategy generation |
|
||||
| `python predix_continuous_strategies.py` | Continuous generation with ML training
|
||||
| `python nexquant.py best` | Show top strategies by composite score |
|
||||
| `python nexquant.py best -n 20 -m sharpe` | Top 20 by Sharpe ratio |
|
||||
| `python nexquant.py best --show NAME` | Full metadata for one strategy |
|
||||
| `python nexquant_gen_strategies_real_bt.py 10` | Generate 10 strategies with LLM + real OHLCV backtest |
|
||||
| `python nexquant_gen_strategies_real_bt.py 20` | Generate 20 strategies (parallel workers) |
|
||||
| `python scripts/nexquant_autopilot.py` | 24/7 Auto-Pilot: endless strategy generation |
|
||||
| `python nexquant_continuous_strategies.py` | Continuous generation with ML training
|
||||
|
||||
### Kronos Foundation Model
|
||||
|
||||
@@ -390,16 +390,16 @@ Kronos runs automatically — no separate command needed. Factors are regenerate
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `python predix.py evaluate --all` | Evaluate all generated factors |
|
||||
| `python predix.py top -n 20` | Show top 20 factors by IC |
|
||||
| `python predix.py portfolio-simple` | Simple portfolio optimization |
|
||||
| `python nexquant.py evaluate --all` | Evaluate all generated factors |
|
||||
| `python nexquant.py top -n 20` | Show top 20 factors by IC |
|
||||
| `python nexquant.py portfolio-simple` | Simple portfolio optimization |
|
||||
|
||||
### Parallel Execution
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `python predix_parallel.py --runs 5 --api-keys 1 -m openrouter` | Run 5 parallel factor evolutions |
|
||||
| `python predix_parallel.py --runs 20 --api-keys 2 -m openrouter` | Run 20 runs with 2 API keys |
|
||||
| `python nexquant_parallel.py --runs 5 --api-keys 1 -m openrouter` | Run 5 parallel factor evolutions |
|
||||
| `python nexquant_parallel.py --runs 20 --api-keys 2 -m openrouter` | Run 20 runs with 2 API keys |
|
||||
|
||||
### Monitoring & Debug
|
||||
|
||||
@@ -407,8 +407,8 @@ Kronos runs automatically — no separate command needed. Factors are regenerate
|
||||
|---------|-------------|
|
||||
| `rdagent server_ui --port 19899 --log-dir <path>` | Start web dashboard |
|
||||
| `rdagent health_check` | Validate environment setup |
|
||||
| `python predix_batch_backtest.py` | Batch backtest multiple factors |
|
||||
| `python predix_rebacktest_strategies.py` | Re-backtest existing strategies |
|
||||
| `python nexquant_batch_backtest.py` | Batch backtest multiple factors |
|
||||
| `python nexquant_rebacktest_strategies.py` | Re-backtest existing strategies |
|
||||
|
||||
---
|
||||
|
||||
@@ -416,7 +416,7 @@ Kronos runs automatically — no separate command needed. Factors are regenerate
|
||||
|
||||
### 🔄 Iterative Factor Evolution
|
||||
|
||||
Predix continuously proposes, implements, and validates new alpha factors:
|
||||
NexQuant continuously proposes, implements, and validates new alpha factors:
|
||||
|
||||
- Learns from backtest feedback
|
||||
- Avoids overfitting through walk-forward validation
|
||||
@@ -458,7 +458,7 @@ Real-time dashboard for monitoring:
|
||||
|
||||
### 🤖 Kronos Foundation Model Integration
|
||||
|
||||
Predix integrates Kronos — an OHLCV foundation model from the NeoQuasar team (AAAI 2026, **MIT License**) — for alpha factor generation:
|
||||
NexQuant integrates Kronos — an OHLCV foundation model from the NeoQuasar team (AAAI 2026, **MIT License**) — for alpha factor generation:
|
||||
|
||||
| Model | Params | p24 IC | Best For |
|
||||
|-------|--------|--------|----------|
|
||||
@@ -496,7 +496,7 @@ Automated quality assurance:
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
predix/
|
||||
nexquant/
|
||||
├── rdagent/ # Core agent framework
|
||||
│ ├── app/ # CLI and scenario apps
|
||||
│ │ └── qlib_rd_loop/ # Quant R&D loop (factor + model generation)
|
||||
@@ -518,12 +518,12 @@ predix/
|
||||
│ ├── scenarios/ # Domain-specific scenarios (qlib, kaggle, rl)
|
||||
│ └── utils/ # Utilities
|
||||
├── scripts/ # Daily operation scripts
|
||||
│ ├── predix_autopilot.py # 24/7 auto strategy generator
|
||||
│ ├── predix_gen_strategies_real_bt.py # Parallel strategy generation
|
||||
│ ├── predix_parallel.py # Multi-instance parallel R&D
|
||||
│ ├── predix_continuous_strategies.py # Continuous strategy generation
|
||||
│ ├── predix_fast_rebacktest.py # Fast strategy re-evaluation
|
||||
│ └── predix_rebacktest_parent.py # Parallel rebacktest orchestrator
|
||||
│ ├── nexquant_autopilot.py # 24/7 auto strategy generator
|
||||
│ ├── nexquant_gen_strategies_real_bt.py # Parallel strategy generation
|
||||
│ ├── nexquant_parallel.py # Multi-instance parallel R&D
|
||||
│ ├── nexquant_continuous_strategies.py # Continuous strategy generation
|
||||
│ ├── nexquant_fast_rebacktest.py # Fast strategy re-evaluation
|
||||
│ └── nexquant_rebacktest_parent.py # Parallel rebacktest orchestrator
|
||||
├── test/ # Test suite (1,125+ collected)
|
||||
│ ├── backtesting/ # Backtest engine deep tests
|
||||
│ ├── qlib/ # Quant loop, factor, model tests
|
||||
@@ -581,7 +581,7 @@ For major changes, please open an issue first to discuss your approach.
|
||||
|
||||
## Citation
|
||||
|
||||
If you use Predix in your research, please cite the underlying framework:
|
||||
If you use NexQuant in your research, please cite the underlying framework:
|
||||
|
||||
```bibtex
|
||||
@misc{yang2025rdagentllmagentframeworkautonomous,
|
||||
@@ -598,7 +598,7 @@ If you use Predix in your research, please cite the underlying framework:
|
||||
|
||||
## Support
|
||||
|
||||
- **Issues**: [GitHub Issues](https://github.com/TPTBusiness/Predix/issues)
|
||||
- **Issues**: [GitHub Issues](https://github.com/TPTBusiness/NexQuant/issues)
|
||||
|
||||
---
|
||||
|
||||
@@ -630,7 +630,7 @@ pytest test/backtesting/ -q # backtest engine deep tests
|
||||
|
||||
## Disclaimer
|
||||
|
||||
Predix is provided "as is" for **research and educational purposes only**. It is **not** intended for:
|
||||
NexQuant is provided "as is" for **research and educational purposes only**. It is **not** intended for:
|
||||
|
||||
- Live trading or financial advice
|
||||
- Production use without thorough testing
|
||||
|
||||
+2
-2
@@ -2,13 +2,13 @@
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We take the security of Predix seriously. If you believe you have found a security vulnerability, please report it responsibly.
|
||||
We take the security of NexQuant seriously. If you believe you have found a security vulnerability, please report it responsibly.
|
||||
|
||||
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||
|
||||
### How to Report
|
||||
|
||||
1. **Open a private security advisory** on GitHub: https://github.com/TPTBusiness/Predix/security/advisories
|
||||
1. **Open a private security advisory** on GitHub: https://github.com/TPTBusiness/NexQuant/security/advisories
|
||||
2. Provide a detailed description of the vulnerability
|
||||
3. Include steps to reproduce if possible
|
||||
4. We will respond within 48 hours
|
||||
|
||||
+3
-3
@@ -6,12 +6,12 @@ This project uses GitHub Issues to track bugs and feature requests. Please searc
|
||||
issues before filing new issues to avoid duplicates. For new issues, file your bug or
|
||||
feature request as a new Issue.
|
||||
|
||||
- **Issues**: [https://github.com/PredixAI/predix/issues](https://github.com/PredixAI/predix/issues)
|
||||
- **Issues**: [https://github.com/NexQuantAI/nexquant/issues](https://github.com/NexQuantAI/nexquant/issues)
|
||||
|
||||
For help and questions about using this project, please reach out via:
|
||||
|
||||
- **Email**: nico@predix.io
|
||||
- **GitHub Discussions**: [https://github.com/PredixAI/predix/discussions](https://github.com/PredixAI/predix/discussions)
|
||||
- **Email**: nico@nexquant.io
|
||||
- **GitHub Discussions**: [https://github.com/NexQuantAI/nexquant/discussions](https://github.com/NexQuantAI/nexquant/discussions)
|
||||
|
||||
## Community Support
|
||||
|
||||
|
||||
+8
-8
@@ -1,4 +1,4 @@
|
||||
# Predix v1.0.0 Release Notes
|
||||
# NexQuant v1.0.0 Release Notes
|
||||
|
||||
**Release Date:** 2026-04-02
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
## 🎉 Overview
|
||||
|
||||
Initial release of Predix - an autonomous AI-powered quantitative trading agent for EUR/USD forex markets.
|
||||
Initial release of NexQuant - an autonomous AI-powered quantitative trading agent for EUR/USD forex markets.
|
||||
|
||||
---
|
||||
|
||||
@@ -75,8 +75,8 @@ Initial release of Predix - an autonomous AI-powered quantitative trading agent
|
||||
|
||||
## 🔧 Changed
|
||||
|
||||
- Rebranded from RD-Agent to Predix for EUR/USD quantitative trading
|
||||
- Updated project metadata for PredixAI organization
|
||||
- Rebranded from RD-Agent to NexQuant for EUR/USD quantitative trading
|
||||
- Updated project metadata for NexQuantAI organization
|
||||
- All code comments translated to English
|
||||
- Removed 'Inspired by' comments, added comprehensive Acknowledgments
|
||||
- Enhanced .gitignore for better file management
|
||||
@@ -137,7 +137,7 @@ This release builds upon and is inspired by:
|
||||
- **TradingAgents** (Apache 2.0 License) - Multi-agent debate patterns
|
||||
- **ai-hedge-fund** - Macro analysis and risk management concepts
|
||||
|
||||
**All code in Predix v1.0.0 is originally written and independently implemented.**
|
||||
**All code in NexQuant v1.0.0 is originally written and independently implemented.**
|
||||
|
||||
---
|
||||
|
||||
@@ -149,7 +149,7 @@ This release builds upon and is inspired by:
|
||||
|
||||
If you use this code or concepts in your project, you **must**:
|
||||
1. Include the MIT License text
|
||||
2. Keep the copyright notice: "Copyright (c) 2025 Predix Team"
|
||||
2. Keep the copyright notice: "Copyright (c) 2025 NexQuant Team"
|
||||
3. Provide attribution to the original project
|
||||
|
||||
See [ATTRIBUTION.md](../ATTRIBUTION.md) for detailed guidelines.
|
||||
@@ -158,7 +158,7 @@ See [ATTRIBUTION.md](../ATTRIBUTION.md) for detailed guidelines.
|
||||
|
||||
## 🔗 Links
|
||||
|
||||
- **GitHub Release:** https://github.com/TPTBusiness/Predix/releases/tag/v1.0.0
|
||||
- **GitHub Release:** https://github.com/TPTBusiness/NexQuant/releases/tag/v1.0.0
|
||||
- **Main Changelog:** ../CHANGELOG.md
|
||||
- **Attribution Guidelines:** ../ATTRIBUTION.md
|
||||
- **Installation Guide:** ../README.md#installation
|
||||
@@ -168,7 +168,7 @@ See [ATTRIBUTION.md](../ATTRIBUTION.md) for detailed guidelines.
|
||||
|
||||
<div align="center">
|
||||
|
||||
**Made with ❤️ by Predix Team**
|
||||
**Made with ❤️ by NexQuant Team**
|
||||
|
||||
For detailed usage guidelines, see [README.md](../README.md)
|
||||
|
||||
|
||||
+6
-6
@@ -1,4 +1,4 @@
|
||||
# Predix v2.0.0 Release Notes
|
||||
# NexQuant v2.0.0 Release Notes
|
||||
|
||||
**Release Date:** 2026-04-10
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
## 🎉 Overview
|
||||
|
||||
Major update adding AI-powered strategy generation, realistic backtesting, and comprehensive CLI tooling. Predix now autonomously generates, evaluates, and optimizes trading strategies using local LLMs.
|
||||
Major update adding AI-powered strategy generation, realistic backtesting, and comprehensive CLI tooling. NexQuant now autonomously generates, evaluates, and optimizes trading strategies using local LLMs.
|
||||
|
||||
---
|
||||
|
||||
@@ -28,7 +28,7 @@ Major update adding AI-powered strategy generation, realistic backtesting, and c
|
||||
- **Proper Annualization**: sqrt(252*1440) for 1-min data
|
||||
|
||||
### CLI Commands
|
||||
- `rdagent predix` - Show beautiful welcome screen (perfect for screenshots!)
|
||||
- `rdagent nexquant` - Show beautiful welcome screen (perfect for screenshots!)
|
||||
- `rdagent start_llama` - Start llama.cpp server
|
||||
- `rdagent start_loop` - Start strategy generator loop with auto-restart
|
||||
- `rdagent generate_strategies` - Generate strategies from factors
|
||||
@@ -65,8 +65,8 @@ Major update adding AI-powered strategy generation, realistic backtesting, and c
|
||||
## 📦 Installation
|
||||
|
||||
```bash
|
||||
git clone https://github.com/TPTBusiness/Predix
|
||||
cd Predix
|
||||
git clone https://github.com/TPTBusiness/NexQuant
|
||||
cd NexQuant
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
@@ -74,7 +74,7 @@ pip install -e .
|
||||
|
||||
```bash
|
||||
# Show welcome screen
|
||||
rdagent predix
|
||||
rdagent nexquant
|
||||
|
||||
# Start LLM server
|
||||
rdagent start_llama
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Bandit Security Scanner Configuration
|
||||
# Documentation: https://bandit.readthedocs.io/
|
||||
|
||||
title: Bandit Security Scan for Predix
|
||||
title: Bandit Security Scan for NexQuant
|
||||
|
||||
# Tests to skip (known false positives or acceptable risks)
|
||||
skips:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# ============================================================
|
||||
# Predix Data Configuration
|
||||
# NexQuant Data Configuration
|
||||
# Change instrument, frequency, and time periods here
|
||||
# All other components read from this file
|
||||
# ============================================================
|
||||
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
# Attribution Guidelines
|
||||
|
||||
## Using Predix in Your Project
|
||||
## Using NexQuant in Your Project
|
||||
|
||||
If you use code, concepts, or ideas from this project, you **must**:
|
||||
|
||||
@@ -11,8 +11,8 @@ Include the full MIT License text in your project's LICENSE file or documentatio
|
||||
### 2. Include Copyright Notice
|
||||
|
||||
```
|
||||
Copyright (c) 2025 Predix Team
|
||||
Original Project: https://github.com/TPTBusiness/Predix
|
||||
Copyright (c) 2025 NexQuant Team
|
||||
Original Project: https://github.com/TPTBusiness/NexQuant
|
||||
```
|
||||
|
||||
### 3. Provide Attribution
|
||||
@@ -22,7 +22,7 @@ Add a notice in your documentation or README:
|
||||
```markdown
|
||||
## Acknowledgments
|
||||
|
||||
This project uses code/concepts from [Predix](https://github.com/TPTBusiness/Predix),
|
||||
This project uses code/concepts from [NexQuant](https://github.com/TPTBusiness/NexQuant),
|
||||
licensed under the [MIT License](https://opensource.org/licenses/MIT).
|
||||
```
|
||||
|
||||
@@ -33,7 +33,7 @@ If you modified the code:
|
||||
```markdown
|
||||
## Modifications
|
||||
|
||||
Based on Predix (original by Predix Team).
|
||||
Based on NexQuant (original by NexQuant Team).
|
||||
Modified by [Your Name/Organization] on [Date].
|
||||
Changes: [Brief description of changes]
|
||||
```
|
||||
@@ -63,13 +63,13 @@ Changes: [Brief description of changes]
|
||||
```markdown
|
||||
# My Trading Project
|
||||
|
||||
This project uses factor generation concepts from [Predix](https://github.com/TPTBusiness/Predix).
|
||||
This project uses factor generation concepts from [NexQuant](https://github.com/TPTBusiness/NexQuant).
|
||||
|
||||
## License
|
||||
MIT License - see LICENSE file for details.
|
||||
|
||||
## Credits
|
||||
- Original Predix code by Predix Team (MIT License)
|
||||
- Original NexQuant code by NexQuant Team (MIT License)
|
||||
- Modified by John Doe, 2025
|
||||
```
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to Predix will be documented in this file.
|
||||
All notable changes to NexQuant will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
+4
-4
@@ -10,9 +10,9 @@ import subprocess
|
||||
|
||||
latest_tag = subprocess.check_output(["git", "describe", "--tags", "--abbrev=0"], text=True).strip()
|
||||
|
||||
project = "Predix"
|
||||
copyright = "2025, Predix Team"
|
||||
author = "Predix Team"
|
||||
project = "NexQuant"
|
||||
copyright = "2025, NexQuant Team"
|
||||
author = "NexQuant Team"
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
@@ -66,7 +66,7 @@ html_static_path = ["_static"]
|
||||
html_favicon = "_static/favicon.ico"
|
||||
|
||||
html_theme_options = {
|
||||
"source_repository": "https://github.com/PredixAI/predix",
|
||||
"source_repository": "https://github.com/NexQuantAI/nexquant",
|
||||
"source_branch": "main",
|
||||
"source_directory": "docs/",
|
||||
}
|
||||
|
||||
+11
-11
@@ -1,4 +1,4 @@
|
||||
# Predix Parallel Run System
|
||||
# NexQuant Parallel Run System
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -10,8 +10,8 @@ The Parallel Run System enables concurrent execution of 5+ factor generation exp
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `predix.py` | Extended with `--run-id` parameter for isolated single runs |
|
||||
| `predix_parallel.py` | Parallel runner manager with Rich live dashboard |
|
||||
| `nexquant.py` | Extended with `--run-id` parameter for isolated single runs |
|
||||
| `nexquant_parallel.py` | Parallel runner manager with Rich live dashboard |
|
||||
| `factor_runner.py` | Modified to use `PARALLEL_RUN_ID` for path isolation |
|
||||
| `CoSTEER/__init__.py` | Modified to use `PARALLEL_RUN_ID` for intermediate results |
|
||||
|
||||
@@ -57,26 +57,26 @@ RD-Agent_workspace_run2/ # Parallel run #2
|
||||
|
||||
```bash
|
||||
# Run with isolated results
|
||||
predix quant --run-id 1 -m openrouter
|
||||
nexquant quant --run-id 1 -m openrouter
|
||||
```
|
||||
|
||||
### CLI - Parallel Runner (Direct)
|
||||
|
||||
```bash
|
||||
# Run 5 experiments with 2 API keys
|
||||
python predix_parallel.py --runs 5 --api-keys 2
|
||||
python nexquant_parallel.py --runs 5 --api-keys 2
|
||||
|
||||
# Run 3 experiments with local model
|
||||
python predix_parallel.py --runs 3 --model local
|
||||
python nexquant_parallel.py --runs 3 --model local
|
||||
|
||||
# Custom configuration
|
||||
python predix_parallel.py -n 10 -k 2 -m openrouter
|
||||
python nexquant_parallel.py -n 10 -k 2 -m openrouter
|
||||
```
|
||||
|
||||
### Programmatic Usage
|
||||
|
||||
```python
|
||||
from predix_parallel import main
|
||||
from nexquant_parallel import main
|
||||
|
||||
result = main(runs=5, api_keys=2, model="openrouter")
|
||||
print(f"Success: {result['success']}/{result['total']}")
|
||||
@@ -132,7 +132,7 @@ The parallel runner shows a Rich-based live dashboard:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 🔀 Predix Parallel Run Dashboard │
|
||||
│ 🔀 NexQuant Parallel Run Dashboard │
|
||||
├──────┬──────────┬──────────┬─────────┬──────────┬───────┤
|
||||
│ Run │ Status │ Elapsed │ API Key │ Model │ Exit │
|
||||
├──────┼──────────┼──────────┼─────────┼──────────┼───────┤
|
||||
@@ -222,10 +222,10 @@ if parallel_run_id != "0":
|
||||
pytest test/integration/test_all_features.py -v
|
||||
|
||||
# Test parallel runner imports
|
||||
python -c "from predix_parallel import ParallelRunner, main; print('✅ OK')"
|
||||
python -c "from nexquant_parallel import ParallelRunner, main; print('✅ OK')"
|
||||
|
||||
# Test CLI options
|
||||
predix quant --help # Should show --run-id option
|
||||
nexquant quant --help # Should show --run-id option
|
||||
```
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Security Runbook für Predix
|
||||
# Security Runbook für NexQuant
|
||||
|
||||
## Bandit Security Scanner
|
||||
|
||||
|
||||
+2
-2
@@ -127,8 +127,8 @@ jupyter notebook examples/notebooks/quickstart.ipynb
|
||||
|
||||
- **Dokumentation:** `docs/` oder [README.md](../README.md)
|
||||
- **CLI Hilfe:** `rdagent COMMAND --help`
|
||||
- **Issues:** [GitHub Issues](https://github.com/nico/Predix/issues)
|
||||
- **Community:** [Discussions](https://github.com/nico/Predix/discussions)
|
||||
- **Issues:** [GitHub Issues](https://github.com/nico/NexQuant/issues)
|
||||
- **Community:** [Discussions](https://github.com/nico/NexQuant/discussions)
|
||||
|
||||
## ⚠️ Wichtige Hinweise
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
# Predix Models
|
||||
# NexQuant Models
|
||||
|
||||
This directory contains all ML model definitions for Predix trading factors.
|
||||
This directory contains all ML model definitions for NexQuant trading factors.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+99
-99
@@ -1,12 +1,12 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Predix CLI - Wrapper for rdagent with LLM model selection.
|
||||
NexQuant CLI - Wrapper for rdagent with LLM model selection.
|
||||
|
||||
Usage:
|
||||
predix quant # Local llama.cpp (default)
|
||||
predix quant --model local # Explicit local
|
||||
predix quant --model openrouter # OpenRouter cloud model
|
||||
predix quant -d # With web dashboard
|
||||
nexquant quant # Local llama.cpp (default)
|
||||
nexquant quant --model local # Explicit local
|
||||
nexquant quant --model openrouter # OpenRouter cloud model
|
||||
nexquant quant -d # With web dashboard
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
@@ -26,7 +26,7 @@ except ImportError:
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
app = typer.Typer(help="Predix - AI Quantitative Trading Agent")
|
||||
app = typer.Typer(help="NexQuant - AI Quantitative Trading Agent")
|
||||
console = Console()
|
||||
|
||||
|
||||
@@ -178,13 +178,13 @@ def quant(
|
||||
0 = single run mode (default: 0)
|
||||
|
||||
Examples:
|
||||
$ predix quant # Local llama.cpp, single run
|
||||
$ predix quant -m openrouter # OpenRouter cloud model
|
||||
$ predix quant -d # With web dashboard on :5000
|
||||
$ predix quant -m openrouter -d # Cloud model + web dashboard
|
||||
$ predix quant --run-id 1 # Parallel run #1 (isolated)
|
||||
$ predix quant --run-id 2 --loop-n 50 # Parallel run #2, 50 loops
|
||||
$ predix quant --log-file custom.log # Custom log file path
|
||||
$ nexquant quant # Local llama.cpp, single run
|
||||
$ nexquant quant -m openrouter # OpenRouter cloud model
|
||||
$ nexquant quant -d # With web dashboard on :5000
|
||||
$ nexquant quant -m openrouter -d # Cloud model + web dashboard
|
||||
$ nexquant quant --run-id 1 # Parallel run #1 (isolated)
|
||||
$ nexquant quant --run-id 2 --loop-n 50 # Parallel run #2, 50 loops
|
||||
$ nexquant quant --log-file custom.log # Custom log file path
|
||||
|
||||
Expected Output:
|
||||
- Generated alpha factors saved to results/factors/ as JSON files
|
||||
@@ -197,9 +197,9 @@ def quant(
|
||||
Local models are faster but may have lower quality than cloud models.
|
||||
|
||||
See Also:
|
||||
predix evaluate - Evaluate existing factors with full 1min data
|
||||
predix top - Show top-performing factors by IC or Sharpe
|
||||
predix health - Check system health and configuration
|
||||
nexquant evaluate - Evaluate existing factors with full 1min data
|
||||
nexquant top - Show top-performing factors by IC or Sharpe
|
||||
nexquant health - Check system health and configuration
|
||||
"""
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -323,7 +323,7 @@ def quant(
|
||||
|
||||
if cli_dashboard:
|
||||
def start_cli_dash():
|
||||
from rdagent.log.ui.predix_dashboard import run_dashboard
|
||||
from rdagent.log.ui.nexquant_dashboard import run_dashboard
|
||||
run_dashboard(log_path="fin_quant.log", refresh_interval=3)
|
||||
|
||||
threading.Thread(target=start_cli_dash, daemon=True).start()
|
||||
@@ -408,11 +408,11 @@ def evaluate(
|
||||
when recalculating with updated methodology. (default: False)
|
||||
|
||||
Examples:
|
||||
$ predix evaluate # Evaluate 100 NEW factors
|
||||
$ predix evaluate --top 500 # Evaluate 500 NEW factors
|
||||
$ predix evaluate --all # Evaluate all remaining factors
|
||||
$ predix evaluate --force --top 50 # Re-evaluate 50 factors
|
||||
$ predix evaluate -p 8 # Use 8 parallel workers
|
||||
$ nexquant evaluate # Evaluate 100 NEW factors
|
||||
$ nexquant evaluate --top 500 # Evaluate 500 NEW factors
|
||||
$ nexquant evaluate --all # Evaluate all remaining factors
|
||||
$ nexquant evaluate --force --top 50 # Re-evaluate 50 factors
|
||||
$ nexquant evaluate -p 8 # Use 8 parallel workers
|
||||
|
||||
Expected Output:
|
||||
- Updated JSON files in results/factors/ with IC, Sharpe, Max DD, Win Rate
|
||||
@@ -424,22 +424,22 @@ def evaluate(
|
||||
With --parallel 4, expect ~30-60 seconds per factor wall-clock time.
|
||||
|
||||
See Also:
|
||||
predix top - Show top-performing factors by IC or Sharpe
|
||||
predix portfolio - Select a diversified portfolio of uncorrelated factors
|
||||
predix quant - Generate new factors via LLM trading loop
|
||||
nexquant top - Show top-performing factors by IC or Sharpe
|
||||
nexquant portfolio - Select a diversified portfolio of uncorrelated factors
|
||||
nexquant quant - Generate new factors via LLM trading loop
|
||||
"""
|
||||
from rdagent.log.daily_log import session as _daily_session
|
||||
from rich.panel import Panel
|
||||
|
||||
console.print(Panel(
|
||||
"[bold cyan]📊 Predix Factor Evaluator[/bold cyan]\n"
|
||||
"[bold cyan]📊 NexQuant Factor Evaluator[/bold cyan]\n"
|
||||
"Evaluating factors with FULL 1min data (2020-2026)\n"
|
||||
"Skips already evaluated factors automatically",
|
||||
border_style="cyan",
|
||||
))
|
||||
|
||||
# Import and run the evaluator
|
||||
from predix_full_eval import main as eval_main
|
||||
from nexquant_full_eval import main as eval_main
|
||||
|
||||
_eval_ctx = {"top": "all" if all_factors else top, "workers": parallel}
|
||||
if force:
|
||||
@@ -490,10 +490,10 @@ def top(
|
||||
(default: "ic")
|
||||
|
||||
Examples:
|
||||
$ predix top # Top 20 factors by absolute IC
|
||||
$ predix top -n 50 # Top 50 factors by absolute IC
|
||||
$ predix top -m sharpe # Top 20 factors by absolute Sharpe
|
||||
$ predix top -n 100 -m sharpe # Top 100 factors by Sharpe
|
||||
$ nexquant top # Top 20 factors by absolute IC
|
||||
$ nexquant top -n 50 # Top 50 factors by absolute IC
|
||||
$ nexquant top -m sharpe # Top 20 factors by absolute Sharpe
|
||||
$ nexquant top -n 100 -m sharpe # Top 100 factors by Sharpe
|
||||
|
||||
Expected Output:
|
||||
- Formatted table showing Factor name, IC, Sharpe, Annualized Return,
|
||||
@@ -505,9 +505,9 @@ def top(
|
||||
May take a few seconds with thousands of factor files.
|
||||
|
||||
See Also:
|
||||
predix evaluate - Evaluate factors to generate performance metrics
|
||||
predix portfolio - Select diversified portfolio from top factors
|
||||
predix build-strategies - Combine factors into trading strategies
|
||||
nexquant evaluate - Evaluate factors to generate performance metrics
|
||||
nexquant portfolio - Select diversified portfolio from top factors
|
||||
nexquant build-strategies - Combine factors into trading strategies
|
||||
"""
|
||||
import glob as glob_module
|
||||
import json
|
||||
@@ -639,10 +639,10 @@ def portfolio(
|
||||
high-IC factors. Typical range: 0.2-0.5. (default: 0.3)
|
||||
|
||||
Examples:
|
||||
$ predix portfolio # Select top 10 from top 50 candidates
|
||||
$ predix portfolio -n 100 -t 20 # Select top 20 from top 100
|
||||
$ predix portfolio -c 0.5 # Allow higher correlation (0.5)
|
||||
$ predix portfolio -n 200 -t 15 -c 0.2 # Strict diversification
|
||||
$ nexquant portfolio # Select top 10 from top 50 candidates
|
||||
$ nexquant portfolio -n 100 -t 20 # Select top 20 from top 100
|
||||
$ nexquant portfolio -c 0.5 # Allow higher correlation (0.5)
|
||||
$ nexquant portfolio -n 200 -t 15 -c 0.2 # Strict diversification
|
||||
|
||||
Expected Output:
|
||||
- Formatted table showing selected factors with IC, Sharpe, and max correlation
|
||||
@@ -654,9 +654,9 @@ def portfolio(
|
||||
Each factor must be re-evaluated to compute time-series values for correlation.
|
||||
|
||||
See Also:
|
||||
predix portfolio-simple - Faster category-based diversification
|
||||
predix top - View top factors before portfolio selection
|
||||
predix build-strategies - Build strategies from selected factors
|
||||
nexquant portfolio-simple - Faster category-based diversification
|
||||
nexquant top - View top factors before portfolio selection
|
||||
nexquant build-strategies - Build strategies from selected factors
|
||||
"""
|
||||
import glob as glob_module
|
||||
import json
|
||||
@@ -939,9 +939,9 @@ def portfolio_simple(
|
||||
the chance of finding factors in all categories. (default: 100)
|
||||
|
||||
Examples:
|
||||
$ predix portfolio-simple # Top factors from different categories
|
||||
$ predix portfolio-simple -n 200 # Consider top 200 factors
|
||||
$ predix portfolio-simple -n 50 # Quick selection from top 50
|
||||
$ nexquant portfolio-simple # Top factors from different categories
|
||||
$ nexquant portfolio-simple -n 200 # Consider top 200 factors
|
||||
$ nexquant portfolio-simple -n 50 # Quick selection from top 50
|
||||
|
||||
Expected Output:
|
||||
- Formatted table showing selected factors with their category, IC, and Sharpe
|
||||
@@ -954,9 +954,9 @@ def portfolio_simple(
|
||||
Only loads existing JSON results and performs keyword matching.
|
||||
|
||||
See Also:
|
||||
predix portfolio - Correlation-based diversification (more accurate but slower)
|
||||
predix top - View top factors before portfolio selection
|
||||
predix build-strategies - Build strategies from selected factors
|
||||
nexquant portfolio - Correlation-based diversification (more accurate but slower)
|
||||
nexquant top - View top factors before portfolio selection
|
||||
nexquant build-strategies - Build strategies from selected factors
|
||||
"""
|
||||
import glob as glob_module
|
||||
import json
|
||||
@@ -1115,10 +1115,10 @@ def build_strategies(
|
||||
strategies. (default: False)
|
||||
|
||||
Examples:
|
||||
$ predix build-strategies # Build from top 50, pairs only
|
||||
$ predix build-strategies -n 100 -c 3 # Top 100, up to triplets
|
||||
$ predix build-strategies -d # Diversified (cross-category) only
|
||||
$ predix build-strategies -n 30 -c 2 -d # Top 30, diversified pairs
|
||||
$ nexquant build-strategies # Build from top 50, pairs only
|
||||
$ nexquant build-strategies -n 100 -c 3 # Top 100, up to triplets
|
||||
$ nexquant build-strategies -d # Diversified (cross-category) only
|
||||
$ nexquant build-strategies -n 30 -c 2 -d # Top 30, diversified pairs
|
||||
|
||||
Expected Output:
|
||||
- Formatted table of top strategies ranked by Sharpe ratio
|
||||
@@ -1130,9 +1130,9 @@ def build_strategies(
|
||||
Scales with O(n^k) where n=factors, k=max_combo_size.
|
||||
|
||||
See Also:
|
||||
predix build-strategies-ai - AI-powered strategy generation via LLM
|
||||
predix portfolio - Select diversified factors before combining
|
||||
predix top - View top factors before building strategies
|
||||
nexquant build-strategies-ai - AI-powered strategy generation via LLM
|
||||
nexquant portfolio - Select diversified factors before combining
|
||||
nexquant top - View top factors before building strategies
|
||||
"""
|
||||
import numpy as np
|
||||
from rdagent.scenarios.qlib.developer.strategy_builder import StrategyBuilder
|
||||
@@ -1140,7 +1140,7 @@ def build_strategies(
|
||||
from rich.table import Table
|
||||
|
||||
console.print(Panel(
|
||||
"[bold cyan]🏗️ Predix Strategy Builder[/bold cyan]\n"
|
||||
"[bold cyan]🏗️ NexQuant Strategy Builder[/bold cyan]\n"
|
||||
"Systematically combining factors into trading strategies",
|
||||
border_style="cyan",
|
||||
))
|
||||
@@ -1271,12 +1271,12 @@ def build_strategies_ai(
|
||||
may require multiple improvement loops. (default: 1)
|
||||
|
||||
Examples:
|
||||
$ predix build-strategies-ai # Generate 1 strategy, 5 loops max
|
||||
$ predix build-strategies-ai -t 100 # Use top 100 factors as pool
|
||||
$ predix build-strategies-ai -l 10 # Allow 10 improvement loops
|
||||
$ predix build-strategies-ai --min-sharpe 2.0 # Stricter Sharpe requirement
|
||||
$ predix build-strategies-ai --max-dd -0.15 # Tighter drawdown limit
|
||||
$ predix build-strategies-ai -c 5 # Generate 5 accepted strategies
|
||||
$ nexquant build-strategies-ai # Generate 1 strategy, 5 loops max
|
||||
$ nexquant build-strategies-ai -t 100 # Use top 100 factors as pool
|
||||
$ nexquant build-strategies-ai -l 10 # Allow 10 improvement loops
|
||||
$ nexquant build-strategies-ai --min-sharpe 2.0 # Stricter Sharpe requirement
|
||||
$ nexquant build-strategies-ai --max-dd -0.15 # Tighter drawdown limit
|
||||
$ nexquant build-strategies-ai -c 5 # Generate 5 accepted strategies
|
||||
|
||||
Expected Output:
|
||||
- Formatted table of accepted strategies with Sharpe, return, drawdown,
|
||||
@@ -1289,9 +1289,9 @@ def build_strategies_ai(
|
||||
Each loop requires a full backtest execution plus LLM API calls.
|
||||
|
||||
See Also:
|
||||
predix build-strategies - Systematic (non-AI) strategy combination
|
||||
predix quant - Generate new alpha factors via LLM trading loop
|
||||
predix evaluate - Evaluate factors before strategy building
|
||||
nexquant build-strategies - Systematic (non-AI) strategy combination
|
||||
nexquant quant - Generate new alpha factors via LLM trading loop
|
||||
nexquant evaluate - Evaluate factors before strategy building
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
@@ -1345,7 +1345,7 @@ def build_strategies_ai(
|
||||
|
||||
if not factors_dir.exists():
|
||||
console.print("[bold red]❌ No factors directory found at results/factors/[/bold red]")
|
||||
console.print("[yellow]Run 'predix quant' to generate factors first.[/yellow]")
|
||||
console.print("[yellow]Run 'nexquant quant' to generate factors first.[/yellow]")
|
||||
return
|
||||
|
||||
# Load evaluated factors
|
||||
@@ -1365,7 +1365,7 @@ def build_strategies_ai(
|
||||
|
||||
if len(factors) < 10:
|
||||
console.print(f"[bold red]❌ Only {len(factors)} evaluated factors found. Need at least 10.[/bold red]")
|
||||
console.print("[yellow]Run 'predix evaluate' or 'predix quant' to generate more factors.[/yellow]")
|
||||
console.print("[yellow]Run 'nexquant evaluate' or 'nexquant quant' to generate more factors.[/yellow]")
|
||||
return
|
||||
|
||||
# Sort by IC and take top factors
|
||||
@@ -1493,17 +1493,17 @@ def generate_strategies(
|
||||
MaxDD on equity curve, WinRate on trade P&L) with runtime verification.
|
||||
|
||||
Examples:
|
||||
$ predix generate-strategies # 10 strategies, Optuna, swing
|
||||
$ predix generate-strategies -n 20 -w 4 # 20 strategies, 4 workers
|
||||
$ predix generate-strategies --min-sharpe 3.0 # Stricter acceptance
|
||||
$ predix generate-strategies -s daytrading # Day trading style
|
||||
$ predix generate-strategies --no-optuna # Skip optimization
|
||||
$ nexquant generate-strategies # 10 strategies, Optuna, swing
|
||||
$ nexquant generate-strategies -n 20 -w 4 # 20 strategies, 4 workers
|
||||
$ nexquant generate-strategies --min-sharpe 3.0 # Stricter acceptance
|
||||
$ nexquant generate-strategies -s daytrading # Day trading style
|
||||
$ nexquant generate-strategies --no-optuna # Skip optimization
|
||||
"""
|
||||
from rich.console import Console as RichConsole
|
||||
from rich.table import Table as RichTable
|
||||
|
||||
console.print(f"\n[bold cyan]{'='*60}[/bold cyan]")
|
||||
console.print("[bold cyan] Predix Strategy Generator[/bold cyan]")
|
||||
console.print("[bold cyan] NexQuant Strategy Generator[/bold cyan]")
|
||||
console.print(f"[bold cyan]{'='*60}[/bold cyan]")
|
||||
console.print(f" Strategies: [cyan]{count}[/cyan] Workers: [cyan]{workers}[/cyan] Style: [cyan]{style}[/cyan]")
|
||||
console.print(f" Optuna: {'[green]Yes[/green]' if optuna else '[yellow]No[/yellow]'} (trials={optuna_trials}) Factors: [cyan]{top_factors}[/cyan]")
|
||||
@@ -1564,8 +1564,8 @@ def health():
|
||||
helps identify setup issues before running computationally expensive operations.
|
||||
|
||||
Examples:
|
||||
$ predix health # Run full system health check
|
||||
$ predix health --verbose # Detailed output (if supported)
|
||||
$ nexquant health # Run full system health check
|
||||
$ nexquant health --verbose # Detailed output (if supported)
|
||||
|
||||
Expected Output:
|
||||
- Python version and dependency status
|
||||
@@ -1579,8 +1579,8 @@ def health():
|
||||
~5-15 seconds depending on network and database checks.
|
||||
|
||||
See Also:
|
||||
predix status - Show current trading loop status and statistics
|
||||
predix quant - Main trading loop command
|
||||
nexquant status - Show current trading loop status and statistics
|
||||
nexquant quant - Main trading loop command
|
||||
"""
|
||||
from rdagent.app.utils.health_check import health_check
|
||||
health_check()
|
||||
@@ -1597,8 +1597,8 @@ def status():
|
||||
and verifying data persistence.
|
||||
|
||||
Examples:
|
||||
$ predix status # Show current trading loop status
|
||||
$ predix status --json # JSON output (if supported)
|
||||
$ nexquant status # Show current trading loop status
|
||||
$ nexquant status --json # JSON output (if supported)
|
||||
|
||||
Expected Output:
|
||||
- Trading loop process status: RUNNING or STOPPED
|
||||
@@ -1610,9 +1610,9 @@ def status():
|
||||
Nearly instantaneous (< 1 second).
|
||||
|
||||
See Also:
|
||||
predix health - Check system health and configuration
|
||||
predix quant - Start the quantitative trading loop
|
||||
predix top - View top evaluated factors
|
||||
nexquant health - Check system health and configuration
|
||||
nexquant quant - Start the quantitative trading loop
|
||||
nexquant top - View top evaluated factors
|
||||
"""
|
||||
import sqlite3
|
||||
|
||||
@@ -1707,11 +1707,11 @@ def best(
|
||||
"""Rank backtested strategies by performance — source code is never exposed.
|
||||
|
||||
Examples:
|
||||
$ predix best # Top 10 by composite score
|
||||
$ predix best -n 20 -m sharpe # Top 20 by Sharpe
|
||||
$ predix best --no-realistic # Include numerically suspicious runs
|
||||
$ predix best --show TrendMomentumHybrid
|
||||
$ predix best -n 50 --export /tmp/top.json
|
||||
$ nexquant best # Top 10 by composite score
|
||||
$ nexquant best -n 20 -m sharpe # Top 20 by Sharpe
|
||||
$ nexquant best --no-realistic # Include numerically suspicious runs
|
||||
$ nexquant best --show TrendMomentumHybrid
|
||||
$ nexquant best -n 50 --export /tmp/top.json
|
||||
"""
|
||||
import json
|
||||
|
||||
@@ -1779,7 +1779,7 @@ def best(
|
||||
)
|
||||
console.print(table)
|
||||
console.print(f"\n[dim]{len(pool)} strategies matched filters (of {len(items)} total). "
|
||||
f"Use [bold]predix best --show NAME[/bold] for details.[/dim]")
|
||||
f"Use [bold]nexquant best --show NAME[/bold] for details.[/dim]")
|
||||
|
||||
if export:
|
||||
payload = [{k: v for k, v in s.items() if k != "code"} for s in top]
|
||||
@@ -1800,7 +1800,7 @@ def kronos_factor(
|
||||
"""Generate Kronos-mini predicted-return alpha factor (Option A).
|
||||
|
||||
Runs Kronos-mini (4.1M params OHLCV foundation model, AAAI 2026) on rolling
|
||||
windows of EUR/USD 1-min data and saves a predicted-return factor in Predix's
|
||||
windows of EUR/USD 1-min data and saves a predicted-return factor in NexQuant's
|
||||
standard MultiIndex (datetime, instrument) format.
|
||||
|
||||
Strategy: every STRIDE bars, use the previous CONTEXT bars as input and
|
||||
@@ -1813,13 +1813,13 @@ def kronos_factor(
|
||||
git_ignore_folder/factor_implementation_source_data/intraday_pv.h5
|
||||
|
||||
Examples:
|
||||
$ predix kronos-factor # Default: daily stride, GPU
|
||||
$ predix kronos-factor --pred 30 --device cpu # 30-bar horizon, CPU
|
||||
$ predix kronos-factor --context 256 --pred 48
|
||||
$ nexquant kronos-factor # Default: daily stride, GPU
|
||||
$ nexquant kronos-factor --pred 30 --device cpu # 30-bar horizon, CPU
|
||||
$ nexquant kronos-factor --context 256 --pred 48
|
||||
|
||||
See Also:
|
||||
predix kronos-eval - Evaluate Kronos as model and compute IC vs LightGBM
|
||||
predix top - Show top factors by IC
|
||||
nexquant kronos-eval - Evaluate Kronos as model and compute IC vs LightGBM
|
||||
nexquant top - Show top factors by IC
|
||||
"""
|
||||
from rdagent.components.coder.kronos_adapter import _cuda_available
|
||||
_device = device or ("cuda" if _cuda_available() else "cpu")
|
||||
@@ -1871,7 +1871,7 @@ def kronos_factor(
|
||||
console.print(f"\n[green]Factor saved:[/green] {out_path}")
|
||||
console.print(f" Shape: {factor_df.shape} | Non-NaN: {meta['n_non_nan']}")
|
||||
console.print(f" Metadata: {meta_path}")
|
||||
console.print("\n[dim]Use 'predix top' to compare with other factors.[/dim]")
|
||||
console.print("\n[dim]Use 'nexquant top' to compare with other factors.[/dim]")
|
||||
|
||||
|
||||
@app.command("kronos-eval")
|
||||
@@ -1897,13 +1897,13 @@ def kronos_eval(
|
||||
git_ignore_folder/factor_implementation_source_data/intraday_pv.h5
|
||||
|
||||
Examples:
|
||||
$ predix kronos-eval # Default: 30-bar horizon
|
||||
$ predix kronos-eval --pred 96 --device cuda # Daily horizon, GPU
|
||||
$ predix kronos-eval --context 256 --pred 15 # Shorter horizon
|
||||
$ nexquant kronos-eval # Default: 30-bar horizon
|
||||
$ nexquant kronos-eval --pred 96 --device cuda # Daily horizon, GPU
|
||||
$ nexquant kronos-eval --context 256 --pred 15 # Shorter horizon
|
||||
|
||||
See Also:
|
||||
predix kronos-factor - Generate Kronos factor for the factor pipeline
|
||||
predix best - Show top strategies
|
||||
nexquant kronos-factor - Generate Kronos factor for the factor pipeline
|
||||
nexquant best - Show top strategies
|
||||
"""
|
||||
from rdagent.components.coder.kronos_adapter import _cuda_available
|
||||
_device = device or ("cuda" if _cuda_available() else "cpu")
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
# Predix Prompts Index
|
||||
# NexQuant Prompts Index
|
||||
|
||||
Centralized location for all LLM prompts used in the Predix trading system.
|
||||
Centralized location for all LLM prompts used in the NexQuant trading system.
|
||||
|
||||
## Structure
|
||||
|
||||
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
# Predix Prompts
|
||||
# NexQuant Prompts
|
||||
|
||||
This directory contains all LLM prompts for the Predix trading agent.
|
||||
This directory contains all LLM prompts for the NexQuant trading agent.
|
||||
|
||||
---
|
||||
|
||||
@@ -174,13 +174,13 @@ prompt_v2 = load_yaml_file("prompts/local/factor_discovery_v2.yaml")
|
||||
|
||||
```bash
|
||||
# Backup to private repo
|
||||
cd ~/Predix
|
||||
cd ~/NexQuant
|
||||
git archive --format=tar prompts/local/ | gzip > ~/backups/prompts_local_$(date +%Y%m%d).tar.gz
|
||||
|
||||
# Or sync to private GitHub repo
|
||||
git clone git@github.com:TPTBusiness/predix-prompts-private.git
|
||||
cp -r prompts/local/* predix-prompts-private/
|
||||
cd predix-prompts-private && git push
|
||||
git clone git@github.com:TPTBusiness/nexquant-prompts-private.git
|
||||
cp -r prompts/local/* nexquant-prompts-private/
|
||||
cd nexquant-prompts-private && git push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
+5
-5
@@ -7,7 +7,7 @@ requires = [
|
||||
|
||||
[project]
|
||||
authors = [
|
||||
{email = "nico@predix.io", name = "Predix Team"},
|
||||
{email = "nico@nexquant.io", name = "NexQuant Team"},
|
||||
]
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
@@ -16,7 +16,7 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
]
|
||||
description = "Predix - AI-gestützter Quantitative Trading Agent für EUR/USD"
|
||||
description = "NexQuant - AI-gestützter Quantitative Trading Agent für EUR/USD"
|
||||
dynamic = [
|
||||
"dependencies",
|
||||
"optional-dependencies",
|
||||
@@ -29,7 +29,7 @@ keywords = [
|
||||
"EUR/USD",
|
||||
"Forex",
|
||||
]
|
||||
name = "predix"
|
||||
name = "nexquant"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
@@ -37,8 +37,8 @@ requires-python = ">=3.10"
|
||||
rdagent = "rdagent.app.cli:app"
|
||||
|
||||
[project.urls]
|
||||
homepage = "https://github.com/PredixAI/predix/"
|
||||
issue = "https://github.com/PredixAI/predix/issues"
|
||||
homepage = "https://github.com/NexQuantAI/nexquant/"
|
||||
issue = "https://github.com/NexQuantAI/nexquant/issues"
|
||||
|
||||
[tool.coverage.report]
|
||||
fail_under = 80
|
||||
|
||||
+13
-13
@@ -294,7 +294,7 @@ def fin_quant_cli(
|
||||
# Start CLI Dashboard wenn gewünscht
|
||||
if with_cli_dashboard:
|
||||
def start_cli_dash():
|
||||
from rdagent.log.ui.predix_dashboard import run_dashboard
|
||||
from rdagent.log.ui.nexquant_dashboard import run_dashboard
|
||||
run_dashboard(log_path="fin_quant.log", refresh_interval=3)
|
||||
|
||||
cli_thread = threading.Thread(target=start_cli_dash, daemon=True)
|
||||
@@ -1262,9 +1262,9 @@ def start_loop_cli(
|
||||
from datetime import datetime
|
||||
|
||||
script_dir = str(Path(__file__).parent.parent.parent)
|
||||
generator = [sys.executable, f"{script_dir}/scripts/predix_smart_strategy_gen.py"]
|
||||
generator = [sys.executable, f"{script_dir}/scripts/nexquant_smart_strategy_gen.py"]
|
||||
logfile = f"{script_dir}/results/logs/generator_loop.log"
|
||||
pidfile = "/tmp/predix_loop.pid" # nosec B108 — administrative PID file, single-process daemon
|
||||
pidfile = "/tmp/nexquant_loop.pid" # nosec B108 — administrative PID file, single-process daemon
|
||||
|
||||
os.makedirs(f"{script_dir}/results/logs", exist_ok=True)
|
||||
|
||||
@@ -1418,7 +1418,7 @@ def parallel_cli(
|
||||
from rdagent.log import daily_log as _dlog
|
||||
|
||||
project_root = Path(__file__).parent.parent.parent
|
||||
script = project_root / "scripts" / "predix_parallel.py"
|
||||
script = project_root / "scripts" / "nexquant_parallel.py"
|
||||
|
||||
if not script.exists():
|
||||
typer.echo(f"❌ Script not found: {script}")
|
||||
@@ -1469,7 +1469,7 @@ def eval_all_cli(
|
||||
from rdagent.log import daily_log as _dlog
|
||||
|
||||
project_root = Path(__file__).parent.parent.parent
|
||||
script = project_root / "scripts" / "predix_full_eval.py"
|
||||
script = project_root / "scripts" / "nexquant_full_eval.py"
|
||||
|
||||
if not script.exists():
|
||||
typer.echo(f"❌ Script not found: {script}")
|
||||
@@ -1522,7 +1522,7 @@ def batch_backtest_cli(
|
||||
from pathlib import Path
|
||||
|
||||
project_root = Path(__file__).parent.parent.parent
|
||||
script = project_root / "scripts" / "predix_batch_backtest.py"
|
||||
script = project_root / "scripts" / "nexquant_batch_backtest.py"
|
||||
|
||||
if not script.exists():
|
||||
typer.echo(f"❌ Script not found: {script}")
|
||||
@@ -1574,7 +1574,7 @@ def simple_eval_cli(
|
||||
from pathlib import Path
|
||||
|
||||
project_root = Path(__file__).parent.parent.parent
|
||||
script = project_root / "scripts" / "predix_simple_eval.py"
|
||||
script = project_root / "scripts" / "nexquant_simple_eval.py"
|
||||
|
||||
if not script.exists():
|
||||
typer.echo(f"❌ Script not found: {script}")
|
||||
@@ -1620,7 +1620,7 @@ def rebacktest_cli(
|
||||
from pathlib import Path
|
||||
|
||||
project_root = Path(__file__).parent.parent.parent
|
||||
script = project_root / "scripts" / "predix_rebacktest_strategies.py"
|
||||
script = project_root / "scripts" / "nexquant_rebacktest_strategies.py"
|
||||
|
||||
if not script.exists():
|
||||
typer.echo(f"❌ Script not found: {script}")
|
||||
@@ -1673,7 +1673,7 @@ def report_cli(
|
||||
from pathlib import Path
|
||||
|
||||
project_root = Path(__file__).parent.parent.parent
|
||||
script = project_root / "scripts" / "predix_strategy_report.py"
|
||||
script = project_root / "scripts" / "nexquant_strategy_report.py"
|
||||
|
||||
if not script.exists():
|
||||
typer.echo(f"❌ Script not found: {script}")
|
||||
@@ -1697,10 +1697,10 @@ def report_cli(
|
||||
|
||||
|
||||
|
||||
@app.command(name="predix")
|
||||
def predix_welcome():
|
||||
@app.command(name="nexquant")
|
||||
def nexquant_welcome():
|
||||
"""
|
||||
Show Predix welcome screen with system overview.
|
||||
Show NexQuant welcome screen with system overview.
|
||||
|
||||
This command displays a beautiful dashboard showing:
|
||||
- System status (factors, strategies, security)
|
||||
@@ -1710,7 +1710,7 @@ def predix_welcome():
|
||||
Perfect for GitHub README screenshots!
|
||||
|
||||
Examples:
|
||||
rdagent predix
|
||||
rdagent nexquant
|
||||
"""
|
||||
from rdagent.app.cli_welcome import show_welcome
|
||||
show_welcome()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Predix CLI Welcome Screen - Beautiful dashboard for GitHub README screenshot.
|
||||
NexQuant CLI Welcome Screen - Beautiful dashboard for GitHub README screenshot.
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -16,7 +16,7 @@ from datetime import datetime
|
||||
console = Console()
|
||||
|
||||
def show_welcome():
|
||||
"""Show beautiful Predix welcome screen."""
|
||||
"""Show beautiful NexQuant welcome screen."""
|
||||
|
||||
# Header
|
||||
console.print()
|
||||
@@ -89,7 +89,7 @@ def show_welcome():
|
||||
console.print()
|
||||
|
||||
# Footer
|
||||
footer = Text("📄 github.com/TPTBusiness/Predix • 🔒 MIT License • 📖 docs/", style="dim white")
|
||||
footer = Text("📄 github.com/TPTBusiness/NexQuant • 🔒 MIT License • 📖 docs/", style="dim white")
|
||||
console.print(Align.center(footer))
|
||||
console.print()
|
||||
|
||||
@@ -98,5 +98,5 @@ if __name__ == "__main__":
|
||||
|
||||
|
||||
def main():
|
||||
"""Entry point for 'predix' CLI command."""
|
||||
"""Entry point for 'nexquant' CLI command."""
|
||||
show_welcome()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Predix Backtesting Package"""
|
||||
"""NexQuant Backtesting Package"""
|
||||
from .backtest_engine import BacktestMetrics, FactorBacktester
|
||||
from .results_db import ResultsDatabase
|
||||
from .risk_management import CorrelationAnalyzer, PortfolioOptimizer, AdvancedRiskManager
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Predix Backtesting Engine - IC, Sharpe, Drawdown
|
||||
NexQuant Backtesting Engine - IC, Sharpe, Drawdown
|
||||
|
||||
Thin wrapper around the unified ``vbt_backtest.backtest_signal`` engine.
|
||||
All metric formulas live in ``vbt_backtest``; this module exists for
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Trading Protection System for Predix.
|
||||
Trading Protection System for NexQuant.
|
||||
|
||||
Prevents excessive losses by automatically pausing trading
|
||||
when risk thresholds are exceeded.
|
||||
|
||||
@@ -3,7 +3,7 @@ Trading Protection System
|
||||
|
||||
Prevents excessive losses by automatically pausing trading when risk thresholds are exceeded.
|
||||
|
||||
Inspired by common trading protection patterns, implemented from scratch for Predix.
|
||||
Inspired by common trading protection patterns, implemented from scratch for NexQuant.
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Predix Results Database - SQLite für Backtest-Ergebnisse
|
||||
NexQuant Results Database - SQLite für Backtest-Ergebnisse
|
||||
|
||||
Stores backtest metrics from Qlib/MLflow runs for querying and dashboard display.
|
||||
"""
|
||||
@@ -409,7 +409,7 @@ class ResultsDatabase:
|
||||
worst_dd_str = self._fmt_float(best['worst_drawdown'], ".4f")
|
||||
|
||||
md_lines = [
|
||||
"# Predix Results Summary",
|
||||
"# NexQuant Results Summary",
|
||||
"",
|
||||
f"**Generated:** {summary['generated_at']}",
|
||||
f"**Database:** `{summary['database_path']}`",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Predix Risk Management - Korrelation, Portfolio-Optimierung
|
||||
NexQuant Risk Management - Korrelation, Portfolio-Optimierung
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Unified, verifiable backtesting engine.
|
||||
|
||||
Single entry point (`backtest_signal`) used by:
|
||||
- scripts/predix_gen_strategies_real_bt.py
|
||||
- scripts/nexquant_gen_strategies_real_bt.py
|
||||
- rdagent/scenarios/qlib/local/strategy_orchestrator.py
|
||||
- rdagent/scenarios/qlib/local/optuna_optimizer.py
|
||||
- rdagent/components/backtesting/backtest_engine.py
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Predix Factor Auto-Fixer - Automatically patches common factor code issues.
|
||||
NexQuant Factor Auto-Fixer - Automatically patches common factor code issues.
|
||||
|
||||
This module intercepts LLM-generated factor code and automatically fixes known problems:
|
||||
1. min_periods mismatch in rolling window calculations
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Kronos Foundation Model Adapter for Predix.
|
||||
Kronos Foundation Model Adapter for NexQuant.
|
||||
|
||||
Wraps the Kronos-mini OHLCV foundation model (4.1M params, AAAI 2026, MIT)
|
||||
for use as:
|
||||
@@ -55,8 +55,8 @@ def _ensure_kronos() -> bool:
|
||||
return _KRONOS_AVAILABLE
|
||||
|
||||
|
||||
def _ohlcv_from_predix(df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""Convert Predix HDF5 format ($open/$close/...) to Kronos format (open/close/...)."""
|
||||
def _ohlcv_from_nexquant(df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""Convert NexQuant HDF5 format ($open/$close/...) to Kronos format (open/close/...)."""
|
||||
col_map = {"$open": "open", "$high": "high", "$low": "low", "$close": "close", "$volume": "volume"}
|
||||
renamed = df.rename(columns=col_map)
|
||||
cols = [c for c in ["open", "high", "low", "close", "volume"] if c in renamed.columns]
|
||||
@@ -253,7 +253,7 @@ def build_kronos_factor(
|
||||
|
||||
instrument = raw.index.get_level_values("instrument").unique()[0]
|
||||
df = raw.xs(instrument, level="instrument")
|
||||
ohlcv = _ohlcv_from_predix(df)
|
||||
ohlcv = _ohlcv_from_nexquant(df)
|
||||
|
||||
adapter = KronosAdapter(device=device, max_context=min(context_bars, 512), model_size=model_size)
|
||||
adapter.load()
|
||||
@@ -328,7 +328,7 @@ def evaluate_kronos_model(
|
||||
raw = pd.read_hdf(hdf5_path, key="data")
|
||||
instrument = raw.index.get_level_values("instrument").unique()[0]
|
||||
df = raw.xs(instrument, level="instrument")
|
||||
ohlcv = _ohlcv_from_predix(df)
|
||||
ohlcv = _ohlcv_from_nexquant(df)
|
||||
|
||||
adapter = KronosAdapter(device=device, max_context=min(context_bars, 512), model_size=model_size)
|
||||
adapter.load()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""RL Trading Agent components for Predix.
|
||||
"""RL Trading Agent components for NexQuant.
|
||||
|
||||
This package provides reinforcement learning trading capabilities.
|
||||
Works with or without stable-baselines3 (graceful fallback).
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
RL Trading Agent wrapper for Stable Baselines3.
|
||||
|
||||
Provides an easy-to-use interface for training, evaluating, and deploying
|
||||
RL trading agents within the Predix framework.
|
||||
RL trading agents within the NexQuant framework.
|
||||
|
||||
Supported algorithms:
|
||||
- PPO: Proximal Policy Optimization (most stable, recommended for production)
|
||||
|
||||
@@ -5,7 +5,7 @@ Gym-compatible environment for training RL trading agents.
|
||||
Supports single-asset (EUR/USD) trading with technical indicators
|
||||
and portfolio state as observations.
|
||||
|
||||
Inspired by common RL trading environment patterns, implemented from scratch for Predix.
|
||||
Inspired by common RL trading environment patterns, implemented from scratch for NexQuant.
|
||||
"""
|
||||
|
||||
import gymnasium as gym
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Fallback RL implementation for users without stable-baselines3.
|
||||
|
||||
Provides simple rule-based trading when RL library is not available.
|
||||
This ensures the Predix system works for all GitHub users, even
|
||||
This ensures the NexQuant system works for all GitHub users, even
|
||||
without the optional stable-baselines3 dependency.
|
||||
|
||||
The fallback implements a momentum-based strategy as a placeholder
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Predix Model Loader
|
||||
NexQuant Model Loader
|
||||
|
||||
Loads models from:
|
||||
1. models/local/*.py (your improved models - not in Git)
|
||||
@@ -23,7 +23,7 @@ from typing import Optional, Any
|
||||
|
||||
|
||||
# Base paths
|
||||
BASE_DIR = Path(__file__).parent.parent.parent # Predix/
|
||||
BASE_DIR = Path(__file__).parent.parent.parent # NexQuant/
|
||||
MODELS_DIR = BASE_DIR / "models"
|
||||
LOCAL_MODELS_DIR = MODELS_DIR / "local"
|
||||
STANDARD_MODELS_DIR = MODELS_DIR / "standard"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Predix Prompt Loader
|
||||
NexQuant Prompt Loader
|
||||
|
||||
Loads prompts from:
|
||||
1. prompts/local/*.yaml (your improved prompts - not in Git)
|
||||
@@ -22,7 +22,7 @@ from typing import Optional, Dict, Any
|
||||
|
||||
|
||||
# Base paths
|
||||
BASE_DIR = Path(__file__).parent.parent.parent # Predix/
|
||||
BASE_DIR = Path(__file__).parent.parent.parent # NexQuant/
|
||||
PROMPTS_DIR = BASE_DIR / "prompts"
|
||||
LOCAL_PROMPTS_DIR = PROMPTS_DIR / "local"
|
||||
STANDARD_PROMPTS_FILE = PROMPTS_DIR / "standard_prompts.yaml"
|
||||
|
||||
@@ -969,7 +969,7 @@ class QlibFactorRunner(CachedRunner[QlibFactorExperiment]):
|
||||
|
||||
# Run factor code on full data in a temp workspace
|
||||
import pandas as pd
|
||||
with tempfile.TemporaryDirectory(prefix="predix_fullval_") as tmp_dir:
|
||||
with tempfile.TemporaryDirectory(prefix="nexquant_fullval_") as tmp_dir:
|
||||
tmp = Path(tmp_dir)
|
||||
shutil.copy(str(factor_py), str(tmp / "factor.py"))
|
||||
shutil.copy(str(full_data), str(tmp / "intraday_pv.h5"))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Predix Strategy Builder - Systematically combine factors into trading strategies.
|
||||
NexQuant Strategy Builder - Systematically combine factors into trading strategies.
|
||||
|
||||
This module:
|
||||
1. Loads evaluated factors with time-series values
|
||||
@@ -8,9 +8,9 @@ This module:
|
||||
4. Ranks and saves best strategies
|
||||
|
||||
Usage:
|
||||
predix build-strategies # Build strategies from top factors
|
||||
predix build-strategies --top 50 # Use top 50 factors
|
||||
predix build-strategies --max-combo 3 # Allow up to 3-factor combinations
|
||||
nexquant build-strategies # Build strategies from top factors
|
||||
nexquant build-strategies --top 50 # Use top 50 factors
|
||||
nexquant build-strategies --max-combo 3 # Allow up to 3-factor combinations
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
@@ -56,7 +56,7 @@ Current Date: {current_date}
|
||||
Live Macro Data:
|
||||
{macro_data}
|
||||
|
||||
Factor Report from Predix RD-Agent:
|
||||
Factor Report from NexQuant RD-Agent:
|
||||
{factor_report}
|
||||
|
||||
Analyze the macro environment and its impact on the proposed factor:
|
||||
|
||||
@@ -47,7 +47,7 @@ Active Session: {session}
|
||||
Expected Regime: {regime}
|
||||
Session Notes: {session_note}
|
||||
|
||||
Factor Report from Predix RD-Agent:
|
||||
Factor Report from NexQuant RD-Agent:
|
||||
{factor_report}
|
||||
|
||||
Analyze whether the proposed factor is suitable for the current session regime.
|
||||
|
||||
@@ -17,7 +17,7 @@ def create_fx_trader(llm):
|
||||
|
||||
You have received reports from your team:
|
||||
|
||||
FACTOR ANALYSIS (Predix RD-Agent):
|
||||
FACTOR ANALYSIS (NexQuant RD-Agent):
|
||||
{factor_report}
|
||||
|
||||
SESSION ANALYSIS:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
FX Validator Graph — Multi-Agent Validierung für Predix Faktoren
|
||||
FX Validator Graph — Multi-Agent Validierung für NexQuant Faktoren
|
||||
|
||||
Implementiert Multi-Agenten-System für Trading-Entscheidungen:
|
||||
- Session Analyst: Analysiert aktuelle FX-Session
|
||||
@@ -88,10 +88,10 @@ def create_fx_validator(config: dict = None):
|
||||
|
||||
def validate_factor(factor_report: str, trade_date: str = None) -> dict:
|
||||
"""
|
||||
Hauptfunktion — validiert einen Predix-Faktor durch Multi-Agent Debatte
|
||||
Hauptfunktion — validiert einen NexQuant-Faktor durch Multi-Agent Debatte
|
||||
|
||||
Args:
|
||||
factor_report: Der Faktor-Report von Predix RD-Agent
|
||||
factor_report: Der Faktor-Report von NexQuant RD-Agent
|
||||
trade_date: Datum/Zeit in ISO Format (default: jetzt)
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Predix Quant Loop Factory - Selects appropriate workflow based on available components.
|
||||
NexQuant Quant Loop Factory - Selects appropriate workflow based on available components.
|
||||
|
||||
This module is the entry point for the quantitative trading loop.
|
||||
It automatically selects between:
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
# Install with: pip install -r requirements/rl.txt
|
||||
#
|
||||
# These dependencies are OPTIONAL.
|
||||
# The Predix RL trading system works without them using a simple momentum fallback.
|
||||
# The NexQuant RL trading system works without them using a simple momentum fallback.
|
||||
#
|
||||
# Only install if you want to use full PPO/A2C/SAC training.
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import numpy as np
|
||||
import pandas as pd
|
||||
from pathlib import Path
|
||||
|
||||
OHLCV_PATH = Path('/home/nico/Predix/git_ignore_folder/factor_implementation_source_data/intraday_pv.h5')
|
||||
FACTORS_DIR = Path('/home/nico/Predix/results/factors')
|
||||
OHLCV_PATH = Path('/home/nico/NexQuant/git_ignore_folder/factor_implementation_source_data/intraday_pv.h5')
|
||||
FACTORS_DIR = Path('/home/nico/NexQuant/results/factors')
|
||||
VALUES_DIR = FACTORS_DIR / 'values'
|
||||
|
||||
print("=" * 70)
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
Option A: Generate Kronos predicted-return factor from EUR/USD 1-min data.
|
||||
|
||||
Runs Kronos-mini inference in daily strides (96 bars/day) over all available
|
||||
OHLCV data and saves the resulting factor for use in Predix's factor pipeline.
|
||||
OHLCV data and saves the resulting factor for use in NexQuant's factor pipeline.
|
||||
|
||||
Usage:
|
||||
conda activate predix
|
||||
conda activate nexquant
|
||||
python scripts/kronos_factor_gen.py
|
||||
python scripts/kronos_factor_gen.py --context 512 --pred 96 --device cuda
|
||||
python scripts/kronos_factor_gen.py --device cpu # slower but no GPU needed
|
||||
@@ -71,7 +71,7 @@ def main():
|
||||
print(f"\nSample (first 5):")
|
||||
print(factor_df.head())
|
||||
|
||||
# Save metadata for predix.py top / best integration
|
||||
# Save metadata for nexquant.py top / best integration
|
||||
meta = {
|
||||
"factor_name": f"KronosPredReturn_p{args.pred}",
|
||||
"description": f"Kronos-mini predicted return, {args.pred}-bar horizon",
|
||||
|
||||
@@ -6,7 +6,7 @@ Computes IC (Information Coefficient) and hit rate for Kronos predictions
|
||||
vs actual realized returns. Results are printed for comparison with LightGBM.
|
||||
|
||||
Usage:
|
||||
conda activate predix
|
||||
conda activate nexquant
|
||||
python scripts/kronos_model_eval.py
|
||||
python scripts/kronos_model_eval.py --pred 30 --context 512 --device cuda
|
||||
"""
|
||||
|
||||
@@ -10,8 +10,8 @@ For each accepted strategy, add:
|
||||
- Generate Live Trading report
|
||||
|
||||
Usage:
|
||||
python predix_add_risk_management.py
|
||||
python predix_add_risk_management.py --live # Mark as live-ready
|
||||
python nexquant_add_risk_management.py
|
||||
python nexquant_add_risk_management.py --live # Mark as live-ready
|
||||
"""
|
||||
import os, sys, json, time
|
||||
from pathlib import Path
|
||||
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
NexQuant Auto-Pilot — vollautomatischer Strategie-Generator.
|
||||
|
||||
Läuft unbegrenzt, kein menschlicher Eingriff nötig.
|
||||
Jede Runde: Factors laden → LLM Code → Pre-Flight → Backtest → Optuna → Ensemble
|
||||
Bei Crash: auto-restart nach 30s.
|
||||
|
||||
Usage:
|
||||
python scripts/nexquant_autopilot.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json, logging, os, sys, time, traceback
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np, pandas as pd
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
# Load .env before any rdagent imports (required for pydantic-settings)
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
_env_path = Path(__file__).resolve().parent.parent / ".env"
|
||||
load_dotenv(_env_path)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||
logger = logging.getLogger("autopilot")
|
||||
|
||||
LOG_FILE = Path(__file__).resolve().parent.parent / "git_ignore_folder" / "logs" / f"autopilot_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
|
||||
LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
fh = logging.FileHandler(str(LOG_FILE))
|
||||
fh.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
|
||||
logger.addHandler(fh)
|
||||
|
||||
BATCH_SIZE = 2
|
||||
OPTUNA_TRIALS = 10
|
||||
COOLDOWN = 30
|
||||
MAX_CONSECUTIVE_FAILS = 5
|
||||
|
||||
def main_round(style: str, round_num: int) -> int:
|
||||
"""Run one round. Returns number of accepted strategies."""
|
||||
from rdagent.scenarios.qlib.local.strategy_orchestrator import StrategyOrchestrator
|
||||
|
||||
accepted_count = 0
|
||||
try:
|
||||
orch = StrategyOrchestrator(
|
||||
top_factors=20, trading_style=style,
|
||||
min_sharpe=0.1, use_optuna=True, optuna_trials=OPTUNA_TRIALS,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Orchestrator init failed: {e}")
|
||||
return 0
|
||||
|
||||
try:
|
||||
results = orch.generate_strategies(count=BATCH_SIZE, workers=1)
|
||||
except Exception as e:
|
||||
logger.error(f"generate_strategies failed: {e}")
|
||||
return 0
|
||||
|
||||
for r in results:
|
||||
status = r.get("status", "?")
|
||||
if status == "accepted":
|
||||
accepted_count += 1
|
||||
logger.info(f" ✓ {r.get('strategy_name','?')[:40]:40s} S={r.get('sharpe_ratio',0):.1f} OOS={r.get('oos_sharpe',0):.1f}")
|
||||
else:
|
||||
reason = r.get("reason", "?")[:80]
|
||||
logger.debug(f" ✗ {r.get('strategy_name','?')[:40]:40s} {reason}")
|
||||
|
||||
if accepted_count >= 2:
|
||||
try:
|
||||
ensemble = orch.build_ensemble(results)
|
||||
if ensemble and ensemble.get("status") == "success":
|
||||
logger.info(f" Ensemble: S={ensemble['sharpe_ratio']:.1f} OOS={ensemble['oos_sharpe']:.1f} ({len(ensemble['members'])} members)")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return accepted_count
|
||||
|
||||
|
||||
def main():
|
||||
print(f"\n{'='*50}")
|
||||
print(f" NexQuant Auto-Pilot")
|
||||
print(f" Log: {LOG_FILE}")
|
||||
print(f" Batch: {BATCH_SIZE} | Optuna: {OPTUNA_TRIALS} trials")
|
||||
print(f"{'='*50}\n")
|
||||
|
||||
round_num = 0
|
||||
total_accepted = 0
|
||||
consecutive_fails = 0
|
||||
start_time = datetime.now()
|
||||
styles = ["swing", "daytrading"]
|
||||
|
||||
while True:
|
||||
round_num += 1
|
||||
style = styles[round_num % 2]
|
||||
print(f"\n[Round {round_num}] {style} | {datetime.now().strftime('%H:%M:%S')}", flush=True)
|
||||
|
||||
try:
|
||||
accepted = main_round(style, round_num)
|
||||
total_accepted += accepted
|
||||
|
||||
if accepted == 0:
|
||||
consecutive_fails += 1
|
||||
else:
|
||||
consecutive_fails = 0
|
||||
|
||||
elapsed = (datetime.now() - start_time).total_seconds()
|
||||
rate = total_accepted / (elapsed / 3600) if elapsed > 0 else 0
|
||||
print(f" Accepted: {accepted} | Total: {total_accepted} | Rate: {rate:.1f}/h | Fails: {consecutive_fails}", flush=True)
|
||||
|
||||
if consecutive_fails >= MAX_CONSECUTIVE_FAILS:
|
||||
logger.warning(f"{consecutive_fails} consecutive failures — cooling down {COOLDOWN*2}s")
|
||||
time.sleep(COOLDOWN * 2)
|
||||
consecutive_fails = 0
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n\nStopped after {round_num} rounds. Total accepted: {total_accepted}")
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(f"Round {round_num} crashed: {e}\n{traceback.format_exc()[-500:]}")
|
||||
consecutive_fails += 1
|
||||
time.sleep(COOLDOWN)
|
||||
|
||||
time.sleep(COOLDOWN)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,14 +1,14 @@
|
||||
"""
|
||||
Predix Batch Backtest Script - Extract and backtest existing factors.
|
||||
NexQuant Batch Backtest Script - Extract and backtest existing factors.
|
||||
|
||||
Scans generated factor code from workspaces, runs Qlib backtests directly
|
||||
(bypassing CoSTEER), and saves results to JSON + SQLite.
|
||||
|
||||
Usage:
|
||||
python predix_batch_backtest.py --factors 100 # Backtest top 100 factors
|
||||
python predix_batch_backtest.py --all # Backtest all discovered factors
|
||||
python predix_batch_backtest.py --parallel 5 # 5 parallel backtests
|
||||
python predix_batch_backtest.py --scan-only # Only scan, don't run backtests
|
||||
python nexquant_batch_backtest.py --factors 100 # Backtest top 100 factors
|
||||
python nexquant_batch_backtest.py --all # Backtest all discovered factors
|
||||
python nexquant_batch_backtest.py --parallel 5 # 5 parallel backtests
|
||||
python nexquant_batch_backtest.py --scan-only # Only scan, don't run backtests
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -660,7 +660,7 @@ def _run_factor_directly(factor_info: FactorInfo) -> Optional[BacktestResult]:
|
||||
import tempfile
|
||||
import subprocess
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="predix_factor_") as tmp_dir:
|
||||
with tempfile.TemporaryDirectory(prefix="nexquant_factor_") as tmp_dir:
|
||||
ws = Path(tmp_dir)
|
||||
|
||||
# Write factor code
|
||||
@@ -742,7 +742,7 @@ def _run_qlib_single(factor_info: FactorInfo) -> BacktestResult:
|
||||
import tempfile
|
||||
|
||||
# Create temp workspace
|
||||
with tempfile.TemporaryDirectory(prefix="predix_bt_") as tmp_dir:
|
||||
with tempfile.TemporaryDirectory(prefix="nexquant_bt_") as tmp_dir:
|
||||
ws = Path(tmp_dir)
|
||||
|
||||
# Write factor code
|
||||
@@ -1182,7 +1182,7 @@ def main(
|
||||
Metric for ranking ('ic' or 'sharpe')
|
||||
"""
|
||||
console.print(Panel(
|
||||
"[bold cyan]Predix Batch Backtest Runner[/bold cyan]\n"
|
||||
"[bold cyan]NexQuant Batch Backtest Runner[/bold cyan]\n"
|
||||
f"Scanning workspaces for generated factors...",
|
||||
border_style="cyan",
|
||||
))
|
||||
@@ -1196,7 +1196,7 @@ def main(
|
||||
if not all_factors_list:
|
||||
console.print("\n[red]No factors found in workspaces![/red]")
|
||||
console.print(
|
||||
"[yellow]Ensure factors have been generated via `predix.py quant` first.[/yellow]"
|
||||
"[yellow]Ensure factors have been generated via `nexquant.py quant` first.[/yellow]"
|
||||
)
|
||||
return
|
||||
|
||||
@@ -1407,7 +1407,7 @@ if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Predix Batch Backtest - Extract and backtest existing factors"
|
||||
description="NexQuant Batch Backtest - Extract and backtest existing factors"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--factors", "-n",
|
||||
@@ -12,9 +12,9 @@ Features:
|
||||
- Daytrading AND swing style alternating
|
||||
|
||||
Usage:
|
||||
python scripts/predix_continuous_strategies.py
|
||||
python scripts/predix_continuous_strategies.py --style daytrading --rounds 100
|
||||
python scripts/predix_continuous_strategies.py --style both --workers 4
|
||||
python scripts/nexquant_continuous_strategies.py
|
||||
python scripts/nexquant_continuous_strategies.py --style daytrading --rounds 100
|
||||
python scripts/nexquant_continuous_strategies.py --style both --workers 4
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -105,7 +105,7 @@ def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f" Predix Continuous Strategy Generator")
|
||||
print(f" NexQuant Continuous Strategy Generator")
|
||||
print(f" Style: {args.style} | Workers: {args.workers}")
|
||||
print(f" Min Sharpe: {args.min_sharpe} | Batch: {args.batch_size}")
|
||||
print(f" ML every {args.ml_rounds} rounds")
|
||||
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python
|
||||
"""Fast rebacktest: only strategies with factor parquets, skip already-done."""
|
||||
import json, sys, pandas as pd, subprocess, tempfile, numpy as np
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
from rdagent.components.backtesting.vbt_backtest import backtest_signal
|
||||
|
||||
OHLCV = Path("git_ignore_folder/factor_implementation_source_data/intraday_pv.h5")
|
||||
FACTORS_DIR = Path("results/factors/values")
|
||||
STRAT_DIR = Path("results/strategies_new")
|
||||
|
||||
# Pre-build factor name → path map
|
||||
fmap = {p.stem: str(p) for p in FACTORS_DIR.glob("*.parquet")}
|
||||
|
||||
# Load close once
|
||||
print("Loading OHLCV...")
|
||||
ohlcv = pd.read_hdf(str(OHLCV), key="data")
|
||||
close = ohlcv["$close"].dropna()
|
||||
if isinstance(close.index, pd.MultiIndex):
|
||||
close = close.droplevel(-1)
|
||||
close = close.astype(float).sort_index()
|
||||
print(f"{len(close):,} bars")
|
||||
|
||||
# Build work list
|
||||
work = []
|
||||
for f in sorted(STRAT_DIR.glob("*.json")):
|
||||
try:
|
||||
d = json.loads(f.read_text())
|
||||
except Exception:
|
||||
continue
|
||||
if d.get("reevaluation_status") == "verified_v2":
|
||||
continue
|
||||
names = d.get("factor_names", [])
|
||||
code = d.get("code", "")
|
||||
if not names or not code:
|
||||
continue
|
||||
paths = []
|
||||
for n in names:
|
||||
p = fmap.get(n) or fmap.get(n.replace("/", "_")[:150])
|
||||
if p:
|
||||
paths.append((n, p))
|
||||
if len(paths) >= 2:
|
||||
work.append((f, d, paths))
|
||||
|
||||
print(f"{len(work)} strategies to process")
|
||||
|
||||
if not work:
|
||||
print("All done!")
|
||||
sys.exit(0)
|
||||
|
||||
ok = skip = fail = 0
|
||||
start = datetime.now()
|
||||
|
||||
for i, (f, data, factor_paths) in enumerate(work):
|
||||
name = data.get("strategy_name", f.stem)[:45]
|
||||
code = data.get("code", "")
|
||||
|
||||
# Load factor series
|
||||
series = {}
|
||||
for fn, fp in factor_paths:
|
||||
try:
|
||||
s = pd.read_parquet(fp).iloc[:, 0]
|
||||
series[fn] = s
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if len(series) < 2:
|
||||
skip += 1
|
||||
continue
|
||||
|
||||
df = pd.DataFrame(series).sort_index()
|
||||
if isinstance(df.index, pd.MultiIndex):
|
||||
df = df.droplevel(-1)
|
||||
|
||||
try:
|
||||
df_1m = df.reindex(close.index).ffill()
|
||||
except Exception:
|
||||
skip += 1
|
||||
continue
|
||||
|
||||
valid = df_1m.notna().any(axis=1)
|
||||
if valid.sum() < 1000:
|
||||
skip += 1
|
||||
continue
|
||||
|
||||
ca = close.loc[valid]
|
||||
fa = df_1m.loc[valid]
|
||||
|
||||
# Execute strategy code
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
tdp = Path(td)
|
||||
fa.to_parquet(str(tdp / "factors.parquet"))
|
||||
ca.to_pickle(str(tdp / "close.pkl"))
|
||||
|
||||
exec_script = (
|
||||
"import pandas as pd, numpy as np\n"
|
||||
"factors = pd.read_parquet('factors.parquet')\n"
|
||||
"close = pd.read_pickle('close.pkl')\n"
|
||||
"df = factors\n"
|
||||
+ code +
|
||||
"\nif 'signal' not in dir():\n"
|
||||
" raise SystemExit(1)\n"
|
||||
"pd.Series(signal).fillna(0).to_pickle('signal.pkl')\n"
|
||||
)
|
||||
(tdp / "run.py").write_text(exec_script)
|
||||
r = subprocess.run(
|
||||
["python", "run.py"],
|
||||
capture_output=True, text=True, timeout=60, cwd=str(tdp),
|
||||
)
|
||||
if r.returncode != 0:
|
||||
fail += 1
|
||||
continue
|
||||
sig = pd.read_pickle(tdp / "signal.pkl")
|
||||
except Exception:
|
||||
fail += 1
|
||||
continue
|
||||
|
||||
try:
|
||||
sig = sig.reindex(ca.index).ffill().fillna(0)
|
||||
result = backtest_signal(ca, sig, txn_cost_bps=2.14)
|
||||
except Exception:
|
||||
fail += 1
|
||||
continue
|
||||
|
||||
# Write back
|
||||
data["reevaluation_status"] = "verified_v2"
|
||||
data["sharpe_ratio"] = result.get("sharpe")
|
||||
data["max_drawdown"] = result.get("max_drawdown")
|
||||
data["win_rate"] = result.get("win_rate")
|
||||
data["total_return"] = result.get("total_return")
|
||||
data["summary"] = {
|
||||
**data.get("summary", {}),
|
||||
"sharpe": result.get("sharpe"),
|
||||
"max_drawdown": result.get("max_drawdown"),
|
||||
"win_rate": result.get("win_rate"),
|
||||
"monthly_return_pct": result.get("monthly_return_pct"),
|
||||
"real_n_trades": result.get("n_trades"),
|
||||
"total_return": result.get("total_return"),
|
||||
"annualized_return": result.get("annualized_return"),
|
||||
"engine": "verified_v2",
|
||||
"txn_cost_bps": 2.14,
|
||||
}
|
||||
f.write_text(json.dumps(data, indent=2, ensure_ascii=False))
|
||||
ok += 1
|
||||
|
||||
elapsed = (datetime.now() - start).total_seconds()
|
||||
rate = ok / elapsed * 60 if elapsed > 0 else 0
|
||||
print(f" [{ok:4d}/{len(work)}] {rate:5.0f}/min {name:45s} "
|
||||
f"S={result['sharpe']:6.1f} DD={result['max_drawdown']:7.2%} "
|
||||
f"WR={result['win_rate']:5.1%} T={result['n_trades']:4d}")
|
||||
|
||||
elapsed = (datetime.now() - start).total_seconds()
|
||||
print(f"\nDONE: ok={ok} skip={skip} fail={fail} in {elapsed:.0f}s")
|
||||
@@ -1,13 +1,13 @@
|
||||
"""
|
||||
Predix Full Data Factor Evaluator - Evaluate factors with FULL 1min data.
|
||||
NexQuant Full Data Factor Evaluator - Evaluate factors with FULL 1min data.
|
||||
|
||||
Evaluates factors using the complete intraday_pv.h5 dataset (2022-2026, ~2.26M rows)
|
||||
instead of the debug dataset (2024 only, ~371K rows).
|
||||
|
||||
Usage:
|
||||
python predix_full_eval.py --top 100 # Evaluate top 100 factors with full data
|
||||
python predix_full_eval.py --all # Evaluate all factors
|
||||
python predix_full_eval.py --parallel 4 # 4 parallel workers
|
||||
python nexquant_full_eval.py --top 100 # Evaluate top 100 factors with full data
|
||||
python nexquant_full_eval.py --all # Evaluate all factors
|
||||
python nexquant_full_eval.py --parallel 4 # 4 parallel workers
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -271,7 +271,7 @@ def evaluate_factor_full(factor: FactorInfo, full_data: pd.DataFrame,
|
||||
import tempfile
|
||||
import subprocess
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="predix_full_") as tmp_dir:
|
||||
with tempfile.TemporaryDirectory(prefix="nexquant_full_") as tmp_dir:
|
||||
ws = Path(tmp_dir)
|
||||
|
||||
try:
|
||||
@@ -628,7 +628,7 @@ def main(
|
||||
) -> None:
|
||||
"""Main entry point."""
|
||||
console.print(Panel(
|
||||
"[bold cyan]Predix Full Data Factor Evaluator[/bold cyan]\n"
|
||||
"[bold cyan]NexQuant Full Data Factor Evaluator[/bold cyan]\n"
|
||||
f"Using FULL 1min data: {FULL_DATA_FILE}",
|
||||
border_style="cyan",
|
||||
))
|
||||
@@ -679,7 +679,7 @@ if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Predix Full Data Factor Evaluator"
|
||||
description="NexQuant Full Data Factor Evaluator"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--top", "-n",
|
||||
+7
-7
@@ -7,13 +7,13 @@ each with real backtesting on OHLCV data.
|
||||
|
||||
Usage:
|
||||
# Swing trading (96-bar forward returns)
|
||||
python predix_gen_strategies_real_bt.py 10
|
||||
python nexquant_gen_strategies_real_bt.py 10
|
||||
|
||||
# Daytrading with FTMO constraints (12-bar forward returns)
|
||||
TRADING_STYLE=daytrading python predix_gen_strategies_real_bt.py 5
|
||||
TRADING_STYLE=daytrading python nexquant_gen_strategies_real_bt.py 5
|
||||
|
||||
# With parallel workers (default: CPU count)
|
||||
TRADING_STYLE=daytrading WORKERS=4 python predix_gen_strategies_real_bt.py 20
|
||||
TRADING_STYLE=daytrading WORKERS=4 python nexquant_gen_strategies_real_bt.py 20
|
||||
"""
|
||||
import os, sys, json, time, math, random, logging, warnings, subprocess
|
||||
from pathlib import Path
|
||||
@@ -42,9 +42,9 @@ except Exception:
|
||||
# ============================================================================
|
||||
# Configuration
|
||||
# ============================================================================
|
||||
OHLCV_PATH = Path('/home/nico/Predix/git_ignore_folder/factor_implementation_source_data/intraday_pv.h5')
|
||||
FACTORS_DIR = Path('/home/nico/Predix/results/factors')
|
||||
STRATEGIES_DIR = Path('/home/nico/Predix/results/strategies_new')
|
||||
OHLCV_PATH = Path('/home/nico/NexQuant/git_ignore_folder/factor_implementation_source_data/intraday_pv.h5')
|
||||
FACTORS_DIR = Path('/home/nico/NexQuant/results/factors')
|
||||
STRATEGIES_DIR = Path('/home/nico/NexQuant/results/strategies_new')
|
||||
STRATEGIES_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Trading style
|
||||
@@ -632,7 +632,7 @@ def main(target_count=10):
|
||||
|
||||
# Generate PDF report
|
||||
try:
|
||||
from predix_strategy_report import StrategyPerformanceReporter
|
||||
from nexquant_strategy_report import StrategyPerformanceReporter
|
||||
reporter = StrategyPerformanceReporter(strategy)
|
||||
reporter.generate_report()
|
||||
except:
|
||||
@@ -1,16 +1,16 @@
|
||||
"""
|
||||
Predix Parallel Runner - Run multiple factor experiments concurrently.
|
||||
NexQuant Parallel Runner - Run multiple factor experiments concurrently.
|
||||
|
||||
Spawns N subprocesses, each running `predix.py quant` with isolated config:
|
||||
Spawns N subprocesses, each running `nexquant.py quant` with isolated config:
|
||||
- Separate log files (fin_quant_run1.log, fin_quant_run2.log, etc.)
|
||||
- Separate result directories (results/runs/run1/, results/runs/run2/, etc.)
|
||||
- Separate workspace directories
|
||||
- API key distribution across multiple keys (round-robin)
|
||||
|
||||
Usage:
|
||||
python predix_parallel.py --runs 5 --api-keys 2
|
||||
python predix_parallel.py --runs 3 --model openrouter
|
||||
python predix_parallel.py --runs 5 --model local --api-keys 1
|
||||
python nexquant_parallel.py --runs 5 --api-keys 2
|
||||
python nexquant_parallel.py --runs 3 --model openrouter
|
||||
python nexquant_parallel.py --runs 5 --model local --api-keys 1
|
||||
"""
|
||||
import os
|
||||
import signal
|
||||
@@ -188,7 +188,7 @@ class ParallelRunner:
|
||||
|
||||
def _build_command(self, run_state: RunState) -> list[str]:
|
||||
"""
|
||||
Build the subprocess command to run predix quant.
|
||||
Build the subprocess command to run nexquant quant.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@@ -202,7 +202,7 @@ class ParallelRunner:
|
||||
"""
|
||||
cmd = [
|
||||
sys.executable, # Use same Python interpreter
|
||||
str(self.project_root / "predix.py"),
|
||||
str(self.project_root / "nexquant.py"),
|
||||
"quant",
|
||||
"--model", run_state.model,
|
||||
"--run-id", str(run_state.run_id),
|
||||
@@ -327,7 +327,7 @@ class ParallelRunner:
|
||||
|
||||
# Build summary table
|
||||
table = Table(
|
||||
title="🔀 Predix Parallel Run Dashboard",
|
||||
title="🔀 NexQuant Parallel Run Dashboard",
|
||||
show_header=True,
|
||||
header_style="bold cyan",
|
||||
expand=True,
|
||||
@@ -399,7 +399,7 @@ class ParallelRunner:
|
||||
signal.signal(signal.SIGTERM, self._signal_handler)
|
||||
|
||||
console.print(f"\n[bold cyan]{'=' * 60}[/bold cyan]")
|
||||
console.print("[bold cyan]🔀 Predix Parallel Runner[/bold cyan]")
|
||||
console.print("[bold cyan]🔀 NexQuant Parallel Runner[/bold cyan]")
|
||||
console.print(f"[bold cyan]{'=' * 60}[/bold cyan]")
|
||||
console.print(f" Runs: {self.num_runs}")
|
||||
console.print(f" API Keys: {self.num_api_keys} ({len(self.api_keys)} available)")
|
||||
@@ -503,7 +503,7 @@ if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Predix Parallel Runner - Run multiple factor experiments concurrently",
|
||||
description="NexQuant Parallel Runner - Run multiple factor experiments concurrently",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--runs", "-n",
|
||||
@@ -0,0 +1,467 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Quick Daytrading Strategy Generator with CORRECT factor alignment.
|
||||
|
||||
Uses forward-fill to align daily factors to 1-min frequency,
|
||||
then runs fast backtests without LLM calls.
|
||||
|
||||
Usage:
|
||||
python nexquant_quick_daytrading.py 5
|
||||
python nexquant_quick_daytrading.py 10
|
||||
"""
|
||||
import json, time, subprocess, tempfile # nosec
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from rich.console import Console
|
||||
|
||||
console = Console()
|
||||
|
||||
STRATEGIES_DIR = Path('results/strategies_new')
|
||||
STRATEGIES_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
FACTOR_FILES = Path('results/factors')
|
||||
VALUE_FILES = FACTOR_FILES / 'values'
|
||||
OHLCV_PATH = Path('git_ignore_folder/factor_implementation_source_data/intraday_pv.h5')
|
||||
|
||||
# Best daytrading strategies (12-min horizon, optimized for FTMO)
|
||||
DAYTRADING_COMBOS = [
|
||||
{
|
||||
'name': 'MomentumDivergence12min',
|
||||
'factors': ['daily_close_return_96', 'daily_session_momentum_divergence_1d'],
|
||||
'code': '''mom = factors['daily_close_return_96']
|
||||
div = factors['daily_session_momentum_divergence_1d']
|
||||
|
||||
w = 20
|
||||
mom_z = (mom - mom.rolling(w).mean()) / (mom.rolling(w).std() + 1e-8)
|
||||
div_z = (div - div.rolling(w).mean()) / (div.rolling(w).std() + 1e-8)
|
||||
|
||||
composite = (mom_z - div_z).fillna(0)
|
||||
signal = pd.Series(0, index=close.index, name='signal')
|
||||
signal[composite > 0.3] = 1
|
||||
signal[composite < -0.3] = -1
|
||||
signal = signal.fillna(0).astype(int)''',
|
||||
},
|
||||
{
|
||||
'name': 'LondonSessionScalp',
|
||||
'factors': ['london_mom', 'daily_session_momentum_divergence_1d'],
|
||||
'code': '''mom = factors['london_mom']
|
||||
div = factors['daily_session_momentum_divergence_1d']
|
||||
|
||||
w = 15
|
||||
mom_z = (mom - mom.rolling(w).mean()) / (mom.rolling(w).std() + 1e-8)
|
||||
div_z = (div - div.rolling(w).mean()) / (div.rolling(w).std() + 1e-8)
|
||||
|
||||
composite = (mom_z - div_z).fillna(0)
|
||||
signal = pd.Series(0, index=close.index, name='signal')
|
||||
signal[composite > 0.25] = 1
|
||||
signal[composite < -0.25] = -1
|
||||
signal = signal.fillna(0).astype(int)''',
|
||||
},
|
||||
{
|
||||
'name': 'TrendReversionScalp',
|
||||
'factors': ['daily_ols_slope_96', 'daily_session_momentum_divergence_1d', 'DailyTrendStrength_Raw'],
|
||||
'code': '''slope = factors['daily_ols_slope_96']
|
||||
div = factors['daily_session_momentum_divergence_1d']
|
||||
trend = factors['DailyTrendStrength_Raw']
|
||||
|
||||
w = 20
|
||||
slope_z = (slope - slope.rolling(w).mean()) / (slope.rolling(w).std() + 1e-8)
|
||||
div_z = (div - div.rolling(w).mean()) / (div.rolling(w).std() + 1e-8)
|
||||
trend_z = (trend - trend.rolling(w).mean()) / (trend.rolling(w).std() + 1e-8)
|
||||
|
||||
composite = (0.5 * slope_z - 0.3 * div_z + 0.2 * trend_z).fillna(0)
|
||||
signal = pd.Series(0, index=close.index, name='signal')
|
||||
signal[composite > 0.3] = 1
|
||||
signal[composite < -0.3] = -1
|
||||
signal = signal.fillna(0).astype(int)''',
|
||||
},
|
||||
{
|
||||
'name': 'VolAdjMomentum12',
|
||||
'factors': ['daily_ret_vol_adj_1d', 'daily_session_momentum_divergence_1d', 'DCP'],
|
||||
'code': '''vol = factors['daily_ret_vol_adj_1d']
|
||||
div = factors['daily_session_momentum_divergence_1d']
|
||||
dcp = factors['DCP']
|
||||
|
||||
w = 20
|
||||
vol_z = (vol - vol.rolling(w).mean()) / (vol.rolling(w).std() + 1e-8)
|
||||
div_z = (div - div.rolling(w).mean()) / (div.rolling(w).std() + 1e-8)
|
||||
dcp_z = (dcp - dcp.rolling(w).mean()) / (dcp.rolling(w).std() + 1e-8)
|
||||
|
||||
composite = (0.5 * vol_z - 0.3 * div_z + 0.2 * dcp_z).fillna(0)
|
||||
signal = pd.Series(0, index=close.index, name='signal')
|
||||
signal[composite > 0.35] = 1
|
||||
signal[composite < -0.35] = -1
|
||||
signal = signal.fillna(0).astype(int)''',
|
||||
},
|
||||
{
|
||||
'name': 'SessionMeanReversion',
|
||||
'factors': ['session_momentum_diff', 'daily_norm_body', 'daily_c2c_return'],
|
||||
'code': '''session = factors['session_momentum_diff']
|
||||
body = factors['daily_norm_body']
|
||||
c2c = factors['daily_c2c_return']
|
||||
|
||||
w = 15
|
||||
sess_z = (session - session.rolling(w).mean()) / (session.rolling(w).std() + 1e-8)
|
||||
body_z = (body - body.rolling(w).mean()) / (body.rolling(w).std() + 1e-8)
|
||||
c2c_z = (c2c - c2c.rolling(w).mean()) / (c2c.rolling(w).std() + 1e-8)
|
||||
|
||||
composite = (0.5 * sess_z + 0.3 * body_z + 0.2 * c2c_z).fillna(0)
|
||||
signal = pd.Series(0, index=close.index, name='signal')
|
||||
signal[composite > 0.4] = 1
|
||||
signal[composite < -0.4] = -1
|
||||
signal = signal.fillna(0).astype(int)''',
|
||||
},
|
||||
{
|
||||
'name': 'MomentumContinuation',
|
||||
'factors': ['daily_mom', 'daily_ret_1d', 'momentum_1d'],
|
||||
'code': '''mom = factors['daily_mom']
|
||||
ret = factors['daily_ret_1d']
|
||||
mom2 = factors['momentum_1d']
|
||||
|
||||
w = 12
|
||||
mom_z = (mom - mom.rolling(w).mean()) / (mom.rolling(w).std() + 1e-8)
|
||||
ret_z = (ret - ret.rolling(w).mean()) / (ret.rolling(w).std() + 1e-8)
|
||||
mom2_z = (mom2 - mom2.rolling(w).mean()) / (mom2.rolling(w).std() + 1e-8)
|
||||
|
||||
composite = (0.4 * mom_z + 0.3 * ret_z + 0.3 * mom2_z).fillna(0)
|
||||
signal = pd.Series(0, index=close.index, name='signal')
|
||||
signal[composite > 0.2] = 1
|
||||
signal[composite < -0.2] = -1
|
||||
signal = signal.fillna(0).astype(int)''',
|
||||
},
|
||||
{
|
||||
'name': 'HighFreqScalper',
|
||||
'factors': ['daily_close_return_96', 'DCP', 'london_mom'],
|
||||
'code': '''close_ret = factors['daily_close_return_96']
|
||||
dcp = factors['DCP']
|
||||
london = factors['london_mom']
|
||||
|
||||
w = 10
|
||||
cr_z = (close_ret - close_ret.rolling(w).mean()) / (close_ret.rolling(w).std() + 1e-8)
|
||||
dcp_z = (dcp - dcp.rolling(w).mean()) / (dcp.rolling(w).std() + 1e-8)
|
||||
lon_z = (london - london.rolling(w).mean()) / (london.rolling(w).std() + 1e-8)
|
||||
|
||||
composite = (0.4 * cr_z + 0.3 * dcp_z + 0.3 * lon_z).fillna(0)
|
||||
signal = pd.Series(0, index=close.index, name='signal')
|
||||
signal[composite > 0.25] = 1
|
||||
signal[composite < -0.25] = -1
|
||||
signal = signal.fillna(0).astype(int)''',
|
||||
},
|
||||
{
|
||||
'name': 'AdaptiveMomentumMR',
|
||||
'factors': ['daily_close_return_96', 'daily_session_momentum_divergence_1d', 'daily_ols_slope_96'],
|
||||
'code': '''mom = factors['daily_close_return_96']
|
||||
div = factors['daily_session_momentum_divergence_1d']
|
||||
slope = factors['daily_ols_slope_96']
|
||||
|
||||
w = 20
|
||||
mom_z = (mom - mom.rolling(w).mean()) / (mom.rolling(w).std() + 1e-8)
|
||||
div_z = (div - div.rolling(w).mean()) / (div.rolling(w).std() + 1e-8)
|
||||
slope_z = (slope - slope.rolling(w).mean()) / (slope.rolling(w).std() + 1e-8)
|
||||
|
||||
# Regime detection: high momentum = trend, low = mean reversion
|
||||
regime = (mom_z.abs() > 1.0).astype(float)
|
||||
composite = (regime * mom_z + (1 - regime) * (-div_z) + 0.3 * slope_z).fillna(0)
|
||||
signal = pd.Series(0, index=close.index, name='signal')
|
||||
signal[composite > 0.4] = 1
|
||||
signal[composite < -0.4] = -1
|
||||
signal = signal.fillna(0).astype(int)''',
|
||||
},
|
||||
{
|
||||
'name': 'TrendPullbackScalp',
|
||||
'factors': ['daily_close_return_96', 'daily_session_momentum_divergence_1d', 'daily_norm_body'],
|
||||
'code': '''mom = factors['daily_close_return_96']
|
||||
div = factors['daily_session_momentum_divergence_1d']
|
||||
body = factors['daily_norm_body']
|
||||
|
||||
w = 15
|
||||
mom_z = (mom - mom.rolling(w).mean()) / (mom.rolling(w).std() + 1e-8)
|
||||
div_z = (div - div.rolling(w).mean()) / (div.rolling(w).std() + 1e-8)
|
||||
body_z = (body - body.rolling(w).mean()) / (body.rolling(w).std() + 1e-8)
|
||||
|
||||
# Enter on pullbacks (divergence against trend)
|
||||
composite = (mom_z - 0.5 * div_z * mom_z.sign() + 0.2 * body_z).fillna(0)
|
||||
signal = pd.Series(0, index=close.index, name='signal')
|
||||
signal[composite > 0.35] = 1
|
||||
signal[composite < -0.35] = -1
|
||||
signal = signal.fillna(0).astype(int)''',
|
||||
},
|
||||
{
|
||||
'name': 'IntradayMomentumBlend',
|
||||
'factors': ['daily_close_return_96', 'london_mom', 'daily_session_momentum_divergence_1d', 'DCP'],
|
||||
'code': '''mom = factors['daily_close_return_96']
|
||||
lon = factors['london_mom']
|
||||
div = factors['daily_session_momentum_divergence_1d']
|
||||
dcp = factors['DCP']
|
||||
|
||||
w = 20
|
||||
mom_z = (mom - mom.rolling(w).mean()) / (mom.rolling(w).std() + 1e-8)
|
||||
lon_z = (lon - lon.rolling(w).mean()) / (lon.rolling(w).std() + 1e-8)
|
||||
div_z = (div - div.rolling(w).mean()) / (div.rolling(w).std() + 1e-8)
|
||||
dcp_z = (dcp - dcp.rolling(w).mean()) / (dcp.rolling(w).std() + 1e-8)
|
||||
|
||||
composite = (0.3 * mom_z + 0.3 * lon_z - 0.2 * div_z + 0.2 * dcp_z).fillna(0)
|
||||
signal = pd.Series(0, index=close.index, name='signal')
|
||||
signal[composite > 0.3] = 1
|
||||
signal[composite < -0.3] = -1
|
||||
signal = signal.fillna(0).astype(int)''',
|
||||
},
|
||||
]
|
||||
|
||||
def load_factor_series(name):
|
||||
"""Load factor parquet and return as Series with correct index."""
|
||||
safe = name.replace('/','_').replace('\\','_')[:150]
|
||||
pf = VALUE_FILES / f"{safe}.parquet"
|
||||
if not pf.exists():
|
||||
return None
|
||||
|
||||
df = pd.read_parquet(str(pf))
|
||||
|
||||
# Extract EURUSD
|
||||
if df.index.names == ['datetime', 'instrument']:
|
||||
df_reset = df.reset_index()
|
||||
if 'instrument' in df_reset.columns:
|
||||
df_eur = df_reset[df_reset['instrument'] == 'EURUSD'].copy()
|
||||
df_eur = df_eur.set_index('datetime')
|
||||
series = df_eur.iloc[:, -1] # Last column is the factor value
|
||||
series.name = name
|
||||
return series
|
||||
|
||||
# If single index, just return first column
|
||||
series = df.iloc[:, 0]
|
||||
series.name = name
|
||||
return series
|
||||
|
||||
def main(n_strategies=5):
|
||||
console.print("[bold cyan]🎯 Daytrading Strategy Generator (Quick Mode)[/bold cyan]\n")
|
||||
console.print(" Style: 12-minute forward returns")
|
||||
console.print(" Target: FTMO compliant (IC>0.02, Sharpe>0.5, Trades>20, DD>-10%)\n")
|
||||
|
||||
# Load OHLCV data
|
||||
if not OHLCV_PATH.exists():
|
||||
console.print(f"[red]✗ OHLCV data not found: {OHLCV_PATH}[/red]")
|
||||
return
|
||||
|
||||
ohlcv = pd.read_hdf(str(OHLCV_PATH), key='data')
|
||||
|
||||
# Extract close prices with datetime-only index (not MultiIndex)
|
||||
if '$close' in ohlcv.columns:
|
||||
close = ohlcv['$close'].dropna()
|
||||
elif 'close' in ohlcv.columns:
|
||||
close = ohlcv['close'].dropna()
|
||||
else:
|
||||
close = ohlcv.select_dtypes(include=[np.number]).iloc[:, 0].dropna()
|
||||
|
||||
# Extract datetime from MultiIndex if present
|
||||
if isinstance(close.index, pd.MultiIndex):
|
||||
close_dt_idx = close.index.get_level_values('datetime')
|
||||
close_series = pd.Series(close.values, index=close_dt_idx, name='close')
|
||||
else:
|
||||
close_series = close
|
||||
|
||||
close_series = close_series.dropna()
|
||||
console.print(f"[green]✓[/green] Loaded {len(close_series):,} OHLCV bars")
|
||||
|
||||
# Load all factor series and align to close index
|
||||
all_factor_series = {}
|
||||
for combo in DAYTRADING_COMBOS:
|
||||
for factor_name in combo['factors']:
|
||||
if factor_name in all_factor_series:
|
||||
continue
|
||||
|
||||
series = load_factor_series(factor_name)
|
||||
if series is not None:
|
||||
# Forward fill to match close frequency
|
||||
series_ff = series.reindex(close_series.index).ffill()
|
||||
all_factor_series[factor_name] = series_ff
|
||||
|
||||
# Create factors DataFrame
|
||||
df_factors = pd.DataFrame(all_factor_series)
|
||||
df_factors = df_factors.dropna(how='all')
|
||||
|
||||
console.print(f"[green]✓[/green] Loaded {len(df_factors.columns)} factor series")
|
||||
console.print(f"[green]✓[/green] Aligned to {len(df_factors):,} bars\n")
|
||||
|
||||
accepted = []
|
||||
|
||||
for i, combo in enumerate(DAYTRADING_COMBOS[:n_strategies]):
|
||||
console.print(f"[{i+1}/{n_strategies}] Testing {combo['name']}...")
|
||||
|
||||
# Build factor dataframe
|
||||
valid_factors = [f for f in combo['factors'] if f in df_factors.columns]
|
||||
if len(valid_factors) < 2:
|
||||
console.print(f" ✗ Not enough valid factors")
|
||||
continue
|
||||
|
||||
strat_factors = df_factors[valid_factors].dropna()
|
||||
|
||||
if len(strat_factors) < 1000:
|
||||
console.print(f" ✗ Not enough data: {len(strat_factors)} bars")
|
||||
continue
|
||||
|
||||
# Build backtest script
|
||||
forward_bars = 12
|
||||
strategy_code = combo['code']
|
||||
|
||||
script = f"""
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import json
|
||||
|
||||
close = pd.read_pickle('close.pkl') # nosec
|
||||
factors = pd.read_pickle('factors.pkl') # nosec
|
||||
|
||||
# Execute strategy
|
||||
try:
|
||||
{chr(10).join(' ' + l for l in strategy_code.split(chr(10)))}
|
||||
except Exception as e:
|
||||
print(f"ERROR: {{e}}")
|
||||
exit(1)
|
||||
|
||||
if 'signal' not in dir():
|
||||
print("ERROR: No signal generated")
|
||||
exit(1)
|
||||
|
||||
signal = signal.fillna(0)
|
||||
|
||||
# Align
|
||||
common_idx = close.index.intersection(signal.index)
|
||||
close = close.loc[common_idx]
|
||||
signal = signal.loc[common_idx]
|
||||
|
||||
# Forward returns (12-min horizon for daytrading)
|
||||
FORWARD_BARS = {forward_bars}
|
||||
returns_fwd = close.pct_change(FORWARD_BARS).shift(-FORWARD_BARS)
|
||||
signal_aligned = signal.loc[returns_fwd.dropna().index]
|
||||
fwd_returns = returns_fwd.loc[signal_aligned.index]
|
||||
|
||||
if len(signal_aligned) < 100 or len(fwd_returns) < 100:
|
||||
print("ERROR: Not enough data")
|
||||
exit(1)
|
||||
|
||||
# Metrics
|
||||
ic = signal_aligned.corr(fwd_returns)
|
||||
strategy_returns = signal_aligned * fwd_returns
|
||||
sharpe = strategy_returns.mean() / strategy_returns.std() * np.sqrt(252 * 1440 / {forward_bars}) if strategy_returns.std() > 0 else 0
|
||||
|
||||
cum = (1 + strategy_returns).cumprod()
|
||||
running_max = cum.expanding().max()
|
||||
drawdown = (cum - running_max) / running_max.replace(0, np.nan)
|
||||
max_dd = drawdown.min() if len(drawdown) > 0 else 0
|
||||
|
||||
win_rate = (strategy_returns > 0).sum() / len(strategy_returns) if len(strategy_returns) > 0 else 0
|
||||
n_trades = int((signal_aligned != signal_aligned.shift(1)).sum())
|
||||
total_return = cum.iloc[-1] - 1
|
||||
n_bars = len(strategy_returns)
|
||||
n_months = n_bars / (252 * 1440 / {forward_bars} / 12) if n_bars > 0 else 1
|
||||
monthly_return = (1 + total_return) ** (1 / n_months) - 1 if n_months > 0 and (1 + total_return) > 0 else total_return
|
||||
|
||||
result = {{
|
||||
"status": "success",
|
||||
"sharpe": float(sharpe),
|
||||
"max_drawdown": float(max_dd) if not np.isnan(max_dd) else -0.20,
|
||||
"win_rate": float(win_rate),
|
||||
"ic": float(ic) if not np.isnan(ic) else 0,
|
||||
"n_trades": n_trades,
|
||||
"total_return": float(total_return),
|
||||
"monthly_return_pct": float(monthly_return * 100),
|
||||
"n_bars": int(n_bars),
|
||||
"n_months": float(n_months),
|
||||
"signal_long": int((signal_aligned == 1).sum()),
|
||||
"signal_short": int((signal_aligned == -1).sum()),
|
||||
"signal_neutral": int((signal_aligned == 0).sum()),
|
||||
}}
|
||||
|
||||
print(json.dumps(result))
|
||||
"""
|
||||
|
||||
# Run backtest
|
||||
import tempfile
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
tdp = Path(td)
|
||||
strat_close = close_series.loc[strat_factors.index]
|
||||
strat_close.to_pickle(str(tdp / 'close.pkl')) # nosec
|
||||
strat_factors.to_pickle(str(tdp / 'factors.pkl')) # nosec
|
||||
|
||||
script_path = tdp / 'run.py'
|
||||
script_path.write_text(script)
|
||||
|
||||
try:
|
||||
result_proc = subprocess.run( # nosec B603
|
||||
[sys.executable, str(script_path)],
|
||||
capture_output=True, text=True, timeout=60,
|
||||
cwd=str(tdp)
|
||||
)
|
||||
|
||||
if result_proc.returncode != 0:
|
||||
console.print(f" ✗ Failed: {result_proc.stderr[:200]}")
|
||||
continue
|
||||
|
||||
result = None
|
||||
for line in result_proc.stdout.strip().split('\n'):
|
||||
try:
|
||||
result = json.loads(line)
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
if not result or result.get('status') != 'success':
|
||||
console.print(f" ✗ Invalid result")
|
||||
continue
|
||||
|
||||
except subprocess.TimeoutExpired: # nosec
|
||||
console.print(f" ✗ Timeout")
|
||||
continue
|
||||
except Exception as e:
|
||||
console.print(f" ✗ Error: {e}")
|
||||
continue
|
||||
|
||||
ic = result.get('ic', 0)
|
||||
sharpe = result.get('sharpe', 0)
|
||||
trades = result.get('n_trades', 0)
|
||||
dd = result.get('max_drawdown', 0)
|
||||
|
||||
# FTMO criteria
|
||||
if abs(ic) > 0.02 and sharpe > 0.5 and trades > 20 and dd > -0.10:
|
||||
strategy = {
|
||||
'strategy_name': combo['name'],
|
||||
'factor_names': combo['factors'],
|
||||
'description': f"Daytrading strategy combining {', '.join(combo['factors'])}",
|
||||
'code': combo['code'],
|
||||
'real_backtest': result,
|
||||
'metrics': result,
|
||||
'summary': {
|
||||
'sharpe': sharpe,
|
||||
'max_drawdown': dd,
|
||||
'win_rate': result.get('win_rate', 0),
|
||||
'monthly_return_pct': result.get('monthly_return_pct', 0),
|
||||
'real_ic': ic,
|
||||
'real_n_trades': trades,
|
||||
'forward_bars': 12,
|
||||
'trading_style': 'daytrading',
|
||||
}
|
||||
}
|
||||
|
||||
fname = f"{int(time.time())}_{combo['name']}.json"
|
||||
with open(STRATEGIES_DIR / fname, 'w') as f:
|
||||
json.dump(strategy, f, indent=2, ensure_ascii=False)
|
||||
|
||||
accepted.append(strategy)
|
||||
console.print(f" ✓ [green]ACCEPT[/green]: IC={ic:.4f}, Sharpe={sharpe:.2f}, Trades={trades}, DD={dd:.1%}")
|
||||
else:
|
||||
console.print(f" ✗ [red]REJECT[/red]: IC={ic:.4f}, Sharpe={sharpe:.2f}, Trades={trades}, DD={dd:.1%}")
|
||||
|
||||
console.print(f"\n[bold green]✓ {len(accepted)}/{n_strategies} strategies accepted[/bold green]\n")
|
||||
|
||||
if accepted:
|
||||
console.print("[bold]Results:[/bold]")
|
||||
for s in accepted:
|
||||
bt = s['real_backtest']
|
||||
console.print(f" • {s['strategy_name']:30s} IC={bt['ic']:.4f} Sharpe={bt['sharpe']:.2f} "
|
||||
f"Monthly={bt['monthly_return_pct']:.2f}% Trades={bt['n_trades']}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
n = int(sys.argv[1]) if len(sys.argv) > 1 else 5
|
||||
main(n)
|
||||
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python
|
||||
"""One strategy runner — standalone, called from parent script."""
|
||||
import json, sys, pandas as pd, subprocess, tempfile, numpy as np
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from rdagent.components.backtesting.vbt_backtest import backtest_signal
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python nexquant_rebacktest_one.py <strategy_json_path>")
|
||||
sys.exit(1)
|
||||
|
||||
strat_path = Path(sys.argv[1])
|
||||
data = json.loads(strat_path.read_text())
|
||||
|
||||
OHLCV = Path("git_ignore_folder/factor_implementation_source_data/intraday_pv.h5")
|
||||
FACTORS_DIR = Path("results/factors/values")
|
||||
|
||||
fmap = {p.stem: str(p) for p in FACTORS_DIR.glob("*.parquet")}
|
||||
|
||||
names = data.get("factor_names", [])
|
||||
code = data.get("code", "")
|
||||
name = data.get("strategy_name", strat_path.stem)
|
||||
|
||||
if not names or not code:
|
||||
print(json.dumps({"status": "skipped", "reason": "no factors/code"}))
|
||||
sys.exit(0)
|
||||
|
||||
# Load close
|
||||
ohlcv = pd.read_hdf(str(OHLCV), key="data")
|
||||
close = ohlcv["$close"].dropna()
|
||||
if isinstance(close.index, pd.MultiIndex):
|
||||
close = close.droplevel(-1)
|
||||
close = close.astype(float).sort_index()
|
||||
|
||||
# Load factors
|
||||
series = {}
|
||||
for fn in names:
|
||||
fp = fmap.get(fn) or fmap.get(fn.replace("/", "_")[:150])
|
||||
if fp:
|
||||
try:
|
||||
s = pd.read_parquet(fp).iloc[:, 0]
|
||||
series[fn] = s
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if len(series) < 2:
|
||||
print(json.dumps({"status": "skipped", "reason": f"only {len(series)} factors loaded"}))
|
||||
sys.exit(0)
|
||||
|
||||
df = pd.DataFrame(series).sort_index()
|
||||
if isinstance(df.index, pd.MultiIndex):
|
||||
df = df.droplevel(-1)
|
||||
|
||||
df_1m = df.reindex(close.index).ffill()
|
||||
valid = df_1m.notna().any(axis=1)
|
||||
if valid.sum() < 1000:
|
||||
print(json.dumps({"status": "skipped", "reason": f"only {valid.sum()} valid bars"}))
|
||||
sys.exit(0)
|
||||
|
||||
ca = close.loc[valid]
|
||||
fa = df_1m.loc[valid]
|
||||
|
||||
# Execute
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
tdp = Path(td)
|
||||
fa.to_parquet(str(tdp / "factors.parquet"))
|
||||
ca.to_pickle(str(tdp / "close.pkl"))
|
||||
exec_script = (
|
||||
"import sys, os\n"
|
||||
"sys.stdout = open(os.devnull, 'w')\n"
|
||||
"sys.stderr = open(os.devnull, 'w')\n"
|
||||
"import pandas as pd, numpy as np\n"
|
||||
"factors = pd.read_parquet('factors.parquet')\n"
|
||||
"close = pd.read_pickle('close.pkl')\n"
|
||||
"df = factors\n"
|
||||
+ code +
|
||||
"\nif 'signal' not in dir():\n"
|
||||
" raise SystemExit(1)\n"
|
||||
"pd.Series(signal).fillna(0).to_pickle('signal.pkl')\n"
|
||||
)
|
||||
(tdp / "run.py").write_text(exec_script)
|
||||
r = subprocess.run(
|
||||
["python", "run.py"],
|
||||
capture_output=True, text=True, timeout=60, cwd=str(tdp),
|
||||
stdin=subprocess.DEVNULL,
|
||||
)
|
||||
if r.returncode != 0:
|
||||
print(json.dumps({"status": "code_failed", "stderr": r.stderr[:500]}))
|
||||
sys.exit(1)
|
||||
sig = pd.read_pickle(tdp / "signal.pkl")
|
||||
except Exception as e:
|
||||
print(json.dumps({"status": "code_failed", "error": str(e)[:500]}))
|
||||
sys.exit(1)
|
||||
|
||||
sig = sig.reindex(ca.index).ffill().fillna(0)
|
||||
result = backtest_signal(ca, sig, txn_cost_bps=2.14)
|
||||
|
||||
# Return result as JSON
|
||||
output = {
|
||||
"status": "ok",
|
||||
"sharpe": result.get("sharpe"),
|
||||
"max_drawdown": result.get("max_drawdown"),
|
||||
"win_rate": result.get("win_rate"),
|
||||
"n_trades": result.get("n_trades"),
|
||||
"total_return": result.get("total_return"),
|
||||
"monthly_return_pct": result.get("monthly_return_pct"),
|
||||
"annualized_return": result.get("annualized_return"),
|
||||
}
|
||||
print(json.dumps(output))
|
||||
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python
|
||||
"""Parent orchestrator: calls nexquant_rebacktest_one.py for each strategy."""
|
||||
import json, subprocess, sys
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
STRAT_DIR = Path("results/strategies_new")
|
||||
|
||||
# Build work list
|
||||
work = []
|
||||
for f in sorted(STRAT_DIR.glob("*.json")):
|
||||
if "verified_v2" in f.read_text():
|
||||
continue
|
||||
try:
|
||||
d = json.loads(f.read_text())
|
||||
except Exception:
|
||||
continue
|
||||
if d.get("factor_names") and d.get("code"):
|
||||
work.append(f)
|
||||
|
||||
print(f"{len(work)} strategies to re-backtest", flush=True)
|
||||
|
||||
ok = skip = fail = 0
|
||||
start = datetime.now()
|
||||
|
||||
for i, f in enumerate(work):
|
||||
name = f.stem[:45]
|
||||
print(f"[{i+1}/{len(work)}] {name} ...", end=" ", flush=True)
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["timeout", "-s", "KILL", "90", "python", "scripts/nexquant_rebacktest_one.py", str(f)],
|
||||
capture_output=True, text=True, timeout=120,
|
||||
stdin=subprocess.DEVNULL,
|
||||
)
|
||||
result = json.loads(r.stdout.strip() or "{}")
|
||||
except subprocess.TimeoutExpired:
|
||||
print("TIMEOUT", flush=True)
|
||||
fail += 1
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"ERROR: {e}", flush=True)
|
||||
fail += 1
|
||||
continue
|
||||
|
||||
if result.get("status") == "ok":
|
||||
data = json.loads(f.read_text())
|
||||
data["reevaluation_status"] = "verified_v2"
|
||||
data["sharpe_ratio"] = result.get("sharpe")
|
||||
data["max_drawdown"] = result.get("max_drawdown")
|
||||
data["win_rate"] = result.get("win_rate")
|
||||
data["total_return"] = result.get("total_return")
|
||||
data["summary"] = {
|
||||
**data.get("summary", {}),
|
||||
"sharpe": result.get("sharpe"),
|
||||
"max_drawdown": result.get("max_drawdown"),
|
||||
"win_rate": result.get("win_rate"),
|
||||
"monthly_return_pct": result.get("monthly_return_pct"),
|
||||
"real_n_trades": result.get("n_trades"),
|
||||
"total_return": result.get("total_return"),
|
||||
"annualized_return": result.get("annualized_return"),
|
||||
"engine": "verified_v2",
|
||||
"txn_cost_bps": 2.14,
|
||||
}
|
||||
f.write_text(json.dumps(data, indent=2, ensure_ascii=False))
|
||||
ok += 1
|
||||
print(f"S={result['sharpe']:.1f} DD={result['max_drawdown']:.2%} WR={result['win_rate']:.1%} T={result['n_trades']}", flush=True)
|
||||
elif result.get("status") == "skipped":
|
||||
skip += 1
|
||||
print(f"SKIP: {result.get('reason', '?')}", flush=True)
|
||||
else:
|
||||
fail += 1
|
||||
print(f"FAIL: {result.get('stderr', result.get('error', '?'))[:100]}", flush=True)
|
||||
|
||||
elapsed = (datetime.now() - start).total_seconds()
|
||||
print(f"\nDONE: ok={ok} skip={skip} fail={fail} in {elapsed:.0f}s", flush=True)
|
||||
@@ -116,8 +116,8 @@ except Exception as e:
|
||||
"n_short":int((sig==-1).sum()), "n_neutral":int((sig==0).sum())}
|
||||
|
||||
def main(count=None):
|
||||
sdir = Path('/home/nico/Predix/results/strategies')
|
||||
vdir = Path('/home/nico/Predix/results/factors/values')
|
||||
sdir = Path('/home/nico/NexQuant/results/strategies')
|
||||
vdir = Path('/home/nico/NexQuant/results/factors/values')
|
||||
|
||||
files = []
|
||||
for f in sorted(sdir.glob('*.json'), reverse=True):
|
||||
@@ -13,9 +13,9 @@ For every strategy JSON in results/strategies_new (or a user-supplied dir):
|
||||
Does NOT mutate the strategy JSON files — read-only comparison.
|
||||
|
||||
Usage:
|
||||
python scripts/predix_rebacktest_unified.py # all strategies
|
||||
python scripts/predix_rebacktest_unified.py 50 # first 50
|
||||
python scripts/predix_rebacktest_unified.py 50 --csv report.csv
|
||||
python scripts/nexquant_rebacktest_unified.py # all strategies
|
||||
python scripts/nexquant_rebacktest_unified.py 50 # first 50
|
||||
python scripts/nexquant_rebacktest_unified.py 50 --csv report.csv
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -38,9 +38,9 @@ from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn, TimeEl
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from rdagent.components.backtesting.vbt_backtest import backtest_signal_ftmo # noqa: E402
|
||||
|
||||
OHLCV_PATH = Path("/home/nico/Predix/git_ignore_folder/factor_implementation_source_data/intraday_pv.h5")
|
||||
FACTORS_VALUES_DIR = Path("/home/nico/Predix/results/factors/values")
|
||||
STRATEGIES_DIR = Path("/home/nico/Predix/results/strategies_new")
|
||||
OHLCV_PATH = Path("/home/nico/NexQuant/git_ignore_folder/factor_implementation_source_data/intraday_pv.h5")
|
||||
FACTORS_VALUES_DIR = Path("/home/nico/NexQuant/results/factors/values")
|
||||
STRATEGIES_DIR = Path("/home/nico/NexQuant/results/strategies_new")
|
||||
|
||||
# ── Logging setup: everything printed goes to log file + stdout ───────────────
|
||||
_LOG_DIR = Path(__file__).resolve().parent.parent / "git_ignore_folder" / "logs"
|
||||
@@ -1,13 +1,13 @@
|
||||
"""
|
||||
Predix Simple Factor Evaluator - Direct IC/Sharpe computation.
|
||||
NexQuant Simple Factor Evaluator - Direct IC/Sharpe computation.
|
||||
|
||||
Evaluates existing factor results by computing IC and Sharpe directly
|
||||
from factor values and forward returns, without Qlib infrastructure.
|
||||
|
||||
Usage:
|
||||
python predix_simple_eval.py --top 100 # Evaluate top 100 factors
|
||||
python predix_simple_eval.py --all # Evaluate all
|
||||
python predix_simple_eval.py --parallel 4 # 4 parallel workers
|
||||
python nexquant_simple_eval.py --top 100 # Evaluate top 100 factors
|
||||
python nexquant_simple_eval.py --all # Evaluate all
|
||||
python nexquant_simple_eval.py --parallel 4 # 4 parallel workers
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -421,7 +421,7 @@ def main(
|
||||
) -> None:
|
||||
"""Main entry point."""
|
||||
console.print(Panel(
|
||||
"[bold cyan]Predix Simple Factor Evaluator[/bold cyan]\n"
|
||||
"[bold cyan]NexQuant Simple Factor Evaluator[/bold cyan]\n"
|
||||
f"Scanning workspaces for generated factors...",
|
||||
border_style="cyan",
|
||||
))
|
||||
@@ -467,7 +467,7 @@ if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Predix Simple Factor Evaluator - Direct IC/Sharpe computation"
|
||||
description="NexQuant Simple Factor Evaluator - Direct IC/Sharpe computation"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--top", "-n",
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Strategy Performance Report Generator for Predix.
|
||||
Strategy Performance Report Generator for NexQuant.
|
||||
|
||||
Generates detailed PDF reports with charts for each accepted strategy.
|
||||
|
||||
@@ -11,8 +11,8 @@ Features:
|
||||
- Full metrics table and strategy code
|
||||
|
||||
Usage:
|
||||
python predix_strategy_report.py # All strategies
|
||||
python predix_strategy_report.py results/strategies_new/123.json # Single strategy
|
||||
python nexquant_strategy_report.py # All strategies
|
||||
python nexquant_strategy_report.py results/strategies_new/123.json # Single strategy
|
||||
"""
|
||||
import os, sys, json, warnings
|
||||
from pathlib import Path
|
||||
@@ -39,8 +39,8 @@ from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
# Config
|
||||
OHLCV_PATH = Path('/home/nico/Predix/git_ignore_folder/factor_implementation_source_data/intraday_pv.h5')
|
||||
REPORTS_DIR = Path('/home/nico/Predix/results/strategy_reports')
|
||||
OHLCV_PATH = Path('/home/nico/NexQuant/git_ignore_folder/factor_implementation_source_data/intraday_pv.h5')
|
||||
REPORTS_DIR = Path('/home/nico/NexQuant/results/strategy_reports')
|
||||
REPORTS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Colors
|
||||
@@ -226,7 +226,7 @@ class StrategyPerformanceReporter:
|
||||
|
||||
def _gen_pdf_report(self, pdf_path):
|
||||
doc = SimpleDocTemplate(str(pdf_path), pagesize=A4,
|
||||
title=f"Predix: {self.name}", author="Predix AI",
|
||||
title=f"NexQuant: {self.name}", author="NexQuant AI",
|
||||
leftMargin=2*cm, rightMargin=2*cm, topMargin=2*cm, bottomMargin=2*cm)
|
||||
styles = getSampleStyleSheet()
|
||||
styles.add(ParagraphStyle(name='PTitle', fontName='Helvetica-Bold', fontSize=22, leading=26, alignment=TA_CENTER, textColor=colors.HexColor('#1A237E')))
|
||||
@@ -324,7 +324,7 @@ def generate_report_for_strategy(path: str) -> dict:
|
||||
|
||||
|
||||
def generate_all_reports():
|
||||
d = Path('/home/nico/Predix/results/strategies_new')
|
||||
d = Path('/home/nico/NexQuant/results/strategies_new')
|
||||
if not d.exists(): print("No strategies."); return
|
||||
for jf in sorted(d.glob('*.json')):
|
||||
try:
|
||||
@@ -14,7 +14,7 @@ FTMO 100k rules enforced:
|
||||
Out-of-sample window: 2024-01-01 onwards (never seen during factor research).
|
||||
|
||||
Usage:
|
||||
conda activate predix
|
||||
conda activate nexquant
|
||||
python scripts/realistic_backtest_all.py
|
||||
python scripts/realistic_backtest_all.py --target-monthly 4.0 --min-trades 50
|
||||
python scripts/realistic_backtest_all.py --workers 8
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# Run all Predix integration tests
|
||||
# Run all NexQuant integration tests
|
||||
# Usage:
|
||||
# ./scripts/run_all_tests.sh # Full test suite
|
||||
# ./scripts/run_all_tests.sh --quick # Skip slow tests
|
||||
@@ -12,7 +12,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
echo "========================================="
|
||||
echo "Predix Integration Test Suite"
|
||||
echo "NexQuant Integration Test Suite"
|
||||
echo "========================================="
|
||||
echo "Project: $PROJECT_ROOT"
|
||||
echo "Date: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
# Restarts automatically on crash, generates strategies continuously.
|
||||
# ============================================================================
|
||||
|
||||
SCRIPT_DIR="/home/nico/Predix"
|
||||
GENERATOR="python ${SCRIPT_DIR}/predix_smart_strategy_gen.py"
|
||||
SCRIPT_DIR="/home/nico/NexQuant"
|
||||
GENERATOR="python ${SCRIPT_DIR}/nexquant_smart_strategy_gen.py"
|
||||
TARGET_COUNT=3
|
||||
LOGFILE="${SCRIPT_DIR}/results/logs/generator_loop.log"
|
||||
PIDFILE="/tmp/predix_loop.pid"
|
||||
PIDFILE="/tmp/nexquant_loop.pid"
|
||||
|
||||
echo $$ > "$PIDFILE"
|
||||
mkdir -p "${SCRIPT_DIR}/results/logs"
|
||||
@@ -19,7 +19,7 @@ log() {
|
||||
|
||||
cleanup() {
|
||||
log "Received termination signal. Cleaning up..."
|
||||
pkill -f "predix_smart_strategy_gen.py" 2>/dev/null
|
||||
pkill -f "nexquant_smart_strategy_gen.py" 2>/dev/null
|
||||
rm -f "$PIDFILE"
|
||||
log "Cleanup complete. Exiting."
|
||||
exit 0
|
||||
@@ -53,7 +53,7 @@ while true; do
|
||||
log "📁 Existing strategies: ${STRAT_COUNT}"
|
||||
|
||||
# Kill any stale processes
|
||||
pkill -9 -f "predix_smart_strategy_gen.py" 2>/dev/null
|
||||
pkill -9 -f "nexquant_smart_strategy_gen.py" 2>/dev/null
|
||||
sleep 2
|
||||
|
||||
# Start generator
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
# Checks every 20min: is the generator running? If not, (re)start it.
|
||||
# ============================================================================
|
||||
|
||||
SCRIPT_DIR="/home/nico/Predix"
|
||||
GENERATOR="python ${SCRIPT_DIR}/predix_smart_strategy_gen.py"
|
||||
SCRIPT_DIR="/home/nico/NexQuant"
|
||||
GENERATOR="python ${SCRIPT_DIR}/nexquant_smart_strategy_gen.py"
|
||||
TARGET_COUNT=3
|
||||
LOGFILE="${SCRIPT_DIR}/results/logs/watchdog.log"
|
||||
LOCKFILE="/tmp/predix_generator.lock"
|
||||
LOCKFILE="/tmp/nexquant_generator.lock"
|
||||
MAX_ATTEMPTS=50 # Stop after this many attempts
|
||||
PIDFILE="/tmp/predix_generator_attempt.pid"
|
||||
PIDFILE="/tmp/nexquant_generator_attempt.pid"
|
||||
|
||||
mkdir -p "${SCRIPT_DIR}/results/logs"
|
||||
|
||||
@@ -51,7 +51,7 @@ check_progress() {
|
||||
|
||||
# Kill any existing generator processes
|
||||
cleanup() {
|
||||
pkill -9 -f "predix_smart_strategy_gen.py" 2>/dev/null
|
||||
pkill -9 -f "nexquant_smart_strategy_gen.py" 2>/dev/null
|
||||
rm -f "$LOCKFILE"
|
||||
log "Cleaned up old processes"
|
||||
}
|
||||
@@ -63,7 +63,7 @@ if [ "$(get_attempt_count)" -ge "$MAX_ATTEMPTS" ]; then
|
||||
fi
|
||||
|
||||
# Check if generator is running
|
||||
if pgrep -f "predix_smart_strategy_gen.py" > /dev/null 2>&1; then
|
||||
if pgrep -f "nexquant_smart_strategy_gen.py" > /dev/null 2>&1; then
|
||||
# Check if it's making progress
|
||||
if check_progress; then
|
||||
log "Generator is running and making progress. Exiting."
|
||||
|
||||
Binary file not shown.
@@ -26,7 +26,7 @@ Die Pakete sind in `requirements.txt` enthalten.
|
||||
### Alle Tests ausführen
|
||||
|
||||
```bash
|
||||
cd /home/nico/Predix
|
||||
cd /home/nico/NexQuant
|
||||
pytest test/backtesting/
|
||||
```
|
||||
|
||||
@@ -226,8 +226,8 @@ Für GitHub Actions oder andere CI/CD-Systeme:
|
||||
|
||||
```bash
|
||||
# Stelle sicher dass du im Projekt-Verzeichnis bist
|
||||
cd /home/nico/Predix
|
||||
export PYTHONPATH=/home/nico/Predix:$PYTHONPATH
|
||||
cd /home/nico/NexQuant
|
||||
export PYTHONPATH=/home/nico/NexQuant:$PYTHONPATH
|
||||
pytest test/backtesting/
|
||||
```
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
"""Predix Backtesting Test Package"""
|
||||
"""NexQuant Backtesting Test Package"""
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Predix Backtesting Test Fixtures
|
||||
NexQuant Backtesting Test Fixtures
|
||||
Wiederverwendbare Test-Daten und Fixtures für alle Backtesting-Tests
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@@ -25,8 +25,8 @@ def _make_ohlcv(n: int = 600, freq: str = "1min") -> pd.DataFrame:
|
||||
}, index=idx)
|
||||
|
||||
|
||||
def _make_predix_hdf5(tmp_path: Path, n: int = 300) -> Path:
|
||||
"""Write a minimal Predix-format HDF5 file and return its path."""
|
||||
def _make_nexquant_hdf5(tmp_path: Path, n: int = 300) -> Path:
|
||||
"""Write a minimal NexQuant-format HDF5 file and return its path."""
|
||||
idx = pd.MultiIndex.from_arrays(
|
||||
[pd.date_range("2024-01-01", periods=n, freq="1min"), ["EURUSD"] * n],
|
||||
names=["datetime", "instrument"],
|
||||
@@ -63,12 +63,12 @@ def _make_mock_adapter():
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Unit tests: _ohlcv_from_predix
|
||||
# Unit tests: _ohlcv_from_nexquant
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestOhlcvConversion:
|
||||
def test_renames_dollar_columns(self):
|
||||
from rdagent.components.coder.kronos_adapter import _ohlcv_from_predix
|
||||
from rdagent.components.coder.kronos_adapter import _ohlcv_from_nexquant
|
||||
idx = pd.MultiIndex.from_arrays(
|
||||
[pd.date_range("2024-01-01", periods=3, freq="1min"), ["EURUSD"] * 3],
|
||||
names=["datetime", "instrument"],
|
||||
@@ -78,17 +78,17 @@ class TestOhlcvConversion:
|
||||
"$low": [1.05, 1.15, 1.25], "$close": [1.12, 1.22, 1.32],
|
||||
"$volume": [100.0, 200.0, 300.0],
|
||||
}, index=idx)
|
||||
result = _ohlcv_from_predix(df)
|
||||
result = _ohlcv_from_nexquant(df)
|
||||
assert list(result.columns) == ["open", "high", "low", "close", "volume"]
|
||||
|
||||
def test_no_dollar_columns_passthrough(self):
|
||||
from rdagent.components.coder.kronos_adapter import _ohlcv_from_predix
|
||||
from rdagent.components.coder.kronos_adapter import _ohlcv_from_nexquant
|
||||
df = pd.DataFrame({"open": [1.0], "close": [1.1], "high": [1.2], "low": [0.9], "volume": [100.0]})
|
||||
result = _ohlcv_from_predix(df)
|
||||
result = _ohlcv_from_nexquant(df)
|
||||
assert "close" in result.columns
|
||||
|
||||
def test_output_is_float64(self):
|
||||
from rdagent.components.coder.kronos_adapter import _ohlcv_from_predix
|
||||
from rdagent.components.coder.kronos_adapter import _ohlcv_from_nexquant
|
||||
df = pd.DataFrame({
|
||||
"$open": np.array([1.1], dtype="float32"),
|
||||
"$close": np.array([1.1], dtype="float32"),
|
||||
@@ -96,7 +96,7 @@ class TestOhlcvConversion:
|
||||
"$low": np.array([1.1], dtype="float32"),
|
||||
"$volume": np.array([100.0], dtype="float32"),
|
||||
})
|
||||
result = _ohlcv_from_predix(df)
|
||||
result = _ohlcv_from_nexquant(df)
|
||||
assert result["close"].dtype == np.float64
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ class TestBuildKronosFactor:
|
||||
def test_output_has_correct_multiindex(self, tmp_path, monkeypatch):
|
||||
import rdagent.components.coder.kronos_adapter as mod
|
||||
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: _make_mock_adapter())
|
||||
h5 = _make_predix_hdf5(tmp_path)
|
||||
h5 = _make_nexquant_hdf5(tmp_path)
|
||||
result = mod.build_kronos_factor(h5, context_bars=100, pred_bars=20, stride_bars=20, device="cpu")
|
||||
assert result.index.names == ["datetime", "instrument"]
|
||||
assert result.index.nlevels == 2
|
||||
@@ -142,14 +142,14 @@ class TestBuildKronosFactor:
|
||||
def test_output_column_name(self, tmp_path, monkeypatch):
|
||||
import rdagent.components.coder.kronos_adapter as mod
|
||||
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: _make_mock_adapter())
|
||||
h5 = _make_predix_hdf5(tmp_path)
|
||||
h5 = _make_nexquant_hdf5(tmp_path)
|
||||
result = mod.build_kronos_factor(h5, context_bars=100, pred_bars=20, stride_bars=20, device="cpu")
|
||||
assert "KronosPredReturn" in result.columns
|
||||
|
||||
def test_output_has_non_nan_values(self, tmp_path, monkeypatch):
|
||||
import rdagent.components.coder.kronos_adapter as mod
|
||||
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: _make_mock_adapter())
|
||||
h5 = _make_predix_hdf5(tmp_path)
|
||||
h5 = _make_nexquant_hdf5(tmp_path)
|
||||
result = mod.build_kronos_factor(h5, context_bars=100, pred_bars=20, stride_bars=20, device="cpu")
|
||||
assert result["KronosPredReturn"].notna().sum() > 0
|
||||
|
||||
@@ -157,7 +157,7 @@ class TestBuildKronosFactor:
|
||||
import rdagent.components.coder.kronos_adapter as mod
|
||||
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: _make_mock_adapter())
|
||||
n = 300
|
||||
h5 = _make_predix_hdf5(tmp_path, n=n)
|
||||
h5 = _make_nexquant_hdf5(tmp_path, n=n)
|
||||
result = mod.build_kronos_factor(h5, context_bars=100, pred_bars=20, stride_bars=20, device="cpu")
|
||||
assert len(result) == n
|
||||
|
||||
@@ -165,7 +165,7 @@ class TestBuildKronosFactor:
|
||||
"""Values within a predicted window should be forward-filled, not NaN."""
|
||||
import rdagent.components.coder.kronos_adapter as mod
|
||||
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: _make_mock_adapter())
|
||||
h5 = _make_predix_hdf5(tmp_path, n=300)
|
||||
h5 = _make_nexquant_hdf5(tmp_path, n=300)
|
||||
result = mod.build_kronos_factor(h5, context_bars=100, pred_bars=20, stride_bars=20, device="cpu")
|
||||
non_nan_ratio = result["KronosPredReturn"].notna().mean()
|
||||
assert non_nan_ratio > 0.5, f"Expected >50% non-NaN, got {non_nan_ratio:.2%}"
|
||||
@@ -185,7 +185,7 @@ class TestEvaluateKronosModel:
|
||||
def test_returns_required_keys(self, tmp_path, monkeypatch):
|
||||
import rdagent.components.coder.kronos_adapter as mod
|
||||
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: _make_mock_adapter())
|
||||
h5 = _make_predix_hdf5(tmp_path, n=400)
|
||||
h5 = _make_nexquant_hdf5(tmp_path, n=400)
|
||||
metrics = mod.evaluate_kronos_model(h5, context_bars=100, pred_bars=20, stride_bars=20, device="cpu")
|
||||
for key in ["IC_mean", "IC_std", "IC_IR", "hit_rate", "n_predictions"]:
|
||||
assert key in metrics, f"Missing key: {key}"
|
||||
@@ -193,14 +193,14 @@ class TestEvaluateKronosModel:
|
||||
def test_hit_rate_in_valid_range(self, tmp_path, monkeypatch):
|
||||
import rdagent.components.coder.kronos_adapter as mod
|
||||
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: _make_mock_adapter())
|
||||
h5 = _make_predix_hdf5(tmp_path, n=400)
|
||||
h5 = _make_nexquant_hdf5(tmp_path, n=400)
|
||||
metrics = mod.evaluate_kronos_model(h5, context_bars=100, pred_bars=20, stride_bars=20, device="cpu")
|
||||
assert 0.0 <= metrics["hit_rate"] <= 1.0
|
||||
|
||||
def test_n_predictions_positive(self, tmp_path, monkeypatch):
|
||||
import rdagent.components.coder.kronos_adapter as mod
|
||||
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: _make_mock_adapter())
|
||||
h5 = _make_predix_hdf5(tmp_path, n=400)
|
||||
h5 = _make_nexquant_hdf5(tmp_path, n=400)
|
||||
metrics = mod.evaluate_kronos_model(h5, context_bars=100, pred_bars=20, stride_bars=20, device="cpu")
|
||||
assert metrics["n_predictions"] > 0
|
||||
|
||||
@@ -213,41 +213,41 @@ class TestCLICommands:
|
||||
def test_kronos_factor_missing_data_exits(self, tmp_path, monkeypatch):
|
||||
"""kronos-factor exits with code 1 when HDF5 data is missing."""
|
||||
from typer.testing import CliRunner
|
||||
import predix as predix_mod
|
||||
import nexquant as nexquant_mod
|
||||
monkeypatch.chdir(tmp_path)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(predix_mod.app, ["kronos-factor"])
|
||||
result = runner.invoke(nexquant_mod.app, ["kronos-factor"])
|
||||
assert result.exit_code == 1
|
||||
|
||||
def test_kronos_eval_missing_data_exits(self, tmp_path, monkeypatch):
|
||||
"""kronos-eval exits with code 1 when HDF5 data is missing."""
|
||||
from typer.testing import CliRunner
|
||||
import predix as predix_mod
|
||||
import nexquant as nexquant_mod
|
||||
monkeypatch.chdir(tmp_path)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(predix_mod.app, ["kronos-eval"])
|
||||
result = runner.invoke(nexquant_mod.app, ["kronos-eval"])
|
||||
assert result.exit_code == 1
|
||||
|
||||
def test_kronos_factor_runs_with_mock(self, tmp_path, monkeypatch):
|
||||
"""kronos-factor completes and saves parquet + json when adapter is mocked."""
|
||||
from typer.testing import CliRunner
|
||||
import rdagent.components.coder.kronos_adapter as mod
|
||||
import predix as predix_mod
|
||||
import nexquant as nexquant_mod
|
||||
|
||||
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: _make_mock_adapter())
|
||||
|
||||
data_dir = tmp_path / "git_ignore_folder" / "factor_implementation_source_data"
|
||||
data_dir.mkdir(parents=True)
|
||||
_make_predix_hdf5(data_dir.parent.parent, n=300)
|
||||
_make_nexquant_hdf5(data_dir.parent.parent, n=300)
|
||||
h5_src = tmp_path / "intraday_pv.h5"
|
||||
# Put HDF5 where the CLI expects it
|
||||
import shutil
|
||||
src = _make_predix_hdf5(tmp_path, n=300)
|
||||
src = _make_nexquant_hdf5(tmp_path, n=300)
|
||||
shutil.copy(src, data_dir / "intraday_pv.h5")
|
||||
|
||||
monkeypatch.chdir(tmp_path)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(predix_mod.app, [
|
||||
result = runner.invoke(nexquant_mod.app, [
|
||||
"kronos-factor", "--context", "100", "--pred", "20", "--device", "cpu"
|
||||
])
|
||||
assert result.exit_code == 0, result.output
|
||||
@@ -257,20 +257,20 @@ class TestCLICommands:
|
||||
"""kronos-eval completes and prints IC metrics when adapter is mocked."""
|
||||
from typer.testing import CliRunner
|
||||
import rdagent.components.coder.kronos_adapter as mod
|
||||
import predix as predix_mod
|
||||
import nexquant as nexquant_mod
|
||||
|
||||
monkeypatch.setattr(mod, "KronosAdapter", lambda **kw: _make_mock_adapter())
|
||||
|
||||
data_dir = tmp_path / "git_ignore_folder" / "factor_implementation_source_data"
|
||||
data_dir.mkdir(parents=True)
|
||||
_make_predix_hdf5(data_dir.parent.parent, n=400)
|
||||
src = _make_predix_hdf5(tmp_path, n=400)
|
||||
_make_nexquant_hdf5(data_dir.parent.parent, n=400)
|
||||
src = _make_nexquant_hdf5(tmp_path, n=400)
|
||||
import shutil
|
||||
shutil.copy(src, data_dir / "intraday_pv.h5")
|
||||
|
||||
monkeypatch.chdir(tmp_path)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(predix_mod.app, [
|
||||
result = runner.invoke(nexquant_mod.app, [
|
||||
"kronos-eval", "--context", "100", "--pred", "20", "--device", "cpu"
|
||||
])
|
||||
assert result.exit_code == 0, result.output
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Shared fixtures for Predix integration tests.
|
||||
Shared fixtures for NexQuant integration tests.
|
||||
Provides common test data, mock objects, and utilities.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Comprehensive Integration Test Suite for Predix
|
||||
Comprehensive Integration Test Suite for NexQuant
|
||||
Tests all 13 implemented features to ensure they work correctly.
|
||||
|
||||
Usage:
|
||||
@@ -1457,22 +1457,22 @@ class TestFinQuantCriticalIntegrations:
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CLI Model Selection Tests (predix.py, cli.py)
|
||||
# CLI Model Selection Tests (nexquant.py, cli.py)
|
||||
# =============================================================================
|
||||
|
||||
class TestCLIModelSelection:
|
||||
"""Test CLI model selection (--model/-m flag) for local vs OpenRouter."""
|
||||
|
||||
def test_predix_cli_imports(self):
|
||||
"""Test that predix.py CLI module can be imported."""
|
||||
def test_nexquant_cli_imports(self):
|
||||
"""Test that nexquant.py CLI module can be imported."""
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"predix", Path(__file__).parent.parent.parent / "predix.py"
|
||||
"nexquant", Path(__file__).parent.parent.parent / "nexquant.py"
|
||||
)
|
||||
predix = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(predix)
|
||||
assert hasattr(predix, "app")
|
||||
assert hasattr(predix, "quant")
|
||||
nexquant = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(nexquant)
|
||||
assert hasattr(nexquant, "app")
|
||||
assert hasattr(nexquant, "quant")
|
||||
|
||||
def test_fin_quant_cli_has_model_option(self):
|
||||
"""Test that fin_quant CLI has --model option."""
|
||||
@@ -1488,36 +1488,36 @@ class TestCLIModelSelection:
|
||||
# (Typer auto-generates help from function signatures)
|
||||
assert isinstance(result.output, str)
|
||||
|
||||
def test_predix_quant_has_model_option(self):
|
||||
"""Test that predix quant CLI has --model option."""
|
||||
def test_nexquant_quant_has_model_option(self):
|
||||
"""Test that nexquant quant CLI has --model option."""
|
||||
from typer.testing import CliRunner
|
||||
import importlib.util
|
||||
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"predix", Path(__file__).parent.parent.parent / "predix.py"
|
||||
"nexquant", Path(__file__).parent.parent.parent / "nexquant.py"
|
||||
)
|
||||
predix = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(predix)
|
||||
nexquant = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(nexquant)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(predix.app, ["quant", "--help"])
|
||||
result = runner.invoke(nexquant.app, ["quant", "--help"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "--model" in result.output or "-m" in result.output
|
||||
|
||||
def test_predix_quant_has_log_file_option(self):
|
||||
"""Test that predix quant CLI has --log-file option."""
|
||||
def test_nexquant_quant_has_log_file_option(self):
|
||||
"""Test that nexquant quant CLI has --log-file option."""
|
||||
from typer.testing import CliRunner
|
||||
import importlib.util
|
||||
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"predix", Path(__file__).parent.parent.parent / "predix.py"
|
||||
"nexquant", Path(__file__).parent.parent.parent / "nexquant.py"
|
||||
)
|
||||
predix = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(predix)
|
||||
nexquant = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(nexquant)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(predix.app, ["quant", "--help"])
|
||||
result = runner.invoke(nexquant.app, ["quant", "--help"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "--log-file" in result.output
|
||||
@@ -1541,12 +1541,12 @@ class TestCLIModelSelection:
|
||||
import inspect
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"predix", Path(__file__).parent.parent.parent / "predix.py"
|
||||
"nexquant", Path(__file__).parent.parent.parent / "nexquant.py"
|
||||
)
|
||||
predix = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(predix)
|
||||
nexquant = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(nexquant)
|
||||
|
||||
source = inspect.getsource(predix.quant)
|
||||
source = inspect.getsource(nexquant.quant)
|
||||
assert "OPENROUTER_API_KEY" in source
|
||||
assert "not set" in source or "not set in" in source
|
||||
finally:
|
||||
@@ -1554,50 +1554,50 @@ class TestCLIModelSelection:
|
||||
os.environ["OPENROUTER_API_KEY"] = original_key
|
||||
|
||||
def test_tee_writer_class_exists(self):
|
||||
"""Test that TeeWriter class is defined in predix.py."""
|
||||
"""Test that TeeWriter class is defined in nexquant.py."""
|
||||
import importlib.util
|
||||
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"predix", Path(__file__).parent.parent.parent / "predix.py"
|
||||
"nexquant", Path(__file__).parent.parent.parent / "nexquant.py"
|
||||
)
|
||||
predix = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(predix)
|
||||
nexquant = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(nexquant)
|
||||
|
||||
# TeeWriter is defined inside the quant function
|
||||
# Verify the function source contains TeeWriter
|
||||
import inspect
|
||||
source = inspect.getsource(predix.quant)
|
||||
source = inspect.getsource(nexquant.quant)
|
||||
assert "TeeWriter" in source
|
||||
|
||||
def test_predix_health_command(self):
|
||||
"""Test that predix health command exists."""
|
||||
def test_nexquant_health_command(self):
|
||||
"""Test that nexquant health command exists."""
|
||||
from typer.testing import CliRunner
|
||||
import importlib.util
|
||||
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"predix", Path(__file__).parent.parent.parent / "predix.py"
|
||||
"nexquant", Path(__file__).parent.parent.parent / "nexquant.py"
|
||||
)
|
||||
predix = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(predix)
|
||||
nexquant = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(nexquant)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(predix.app, ["health", "--help"])
|
||||
result = runner.invoke(nexquant.app, ["health", "--help"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
def test_predix_status_command(self):
|
||||
"""Test that predix status command exists."""
|
||||
def test_nexquant_status_command(self):
|
||||
"""Test that nexquant status command exists."""
|
||||
from typer.testing import CliRunner
|
||||
import importlib.util
|
||||
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"predix", Path(__file__).parent.parent.parent / "predix.py"
|
||||
"nexquant", Path(__file__).parent.parent.parent / "nexquant.py"
|
||||
)
|
||||
predix = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(predix)
|
||||
nexquant = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(nexquant)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(predix.app, ["status", "--help"])
|
||||
result = runner.invoke(nexquant.app, ["status", "--help"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Integration Tests for Full Predix Pipeline (P6-P9)
|
||||
Integration Tests for Full NexQuant Pipeline (P6-P9)
|
||||
|
||||
Tests the complete end-to-end pipeline including:
|
||||
- Feedback Loop Integration (P6)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Deep tests for predix_autopilot.py — property-based, mocks, edge cases.
|
||||
"""Deep tests for nexquant_autopilot.py — property-based, mocks, edge cases.
|
||||
|
||||
Tests the core logic of the 24/7 strategy generator by mocking
|
||||
the StrategyOrchestrator at the correct import path.
|
||||
@@ -39,14 +39,14 @@ class TestMainRound:
|
||||
"rdagent.scenarios.qlib.local.strategy_orchestrator.StrategyOrchestrator",
|
||||
side_effect=RuntimeError("no data"),
|
||||
):
|
||||
from scripts.predix_autopilot import main_round
|
||||
from scripts.nexquant_autopilot import main_round
|
||||
result = main_round("daytrading", 1)
|
||||
assert result == 0
|
||||
|
||||
def test_returns_zero_on_generate_failure(self, mock_orch):
|
||||
mock_cls, instance = mock_orch
|
||||
instance.generate_strategies.side_effect = RuntimeError("crash")
|
||||
from scripts.predix_autopilot import main_round
|
||||
from scripts.nexquant_autopilot import main_round
|
||||
result = main_round("daytrading", 1)
|
||||
assert result == 0
|
||||
|
||||
@@ -56,7 +56,7 @@ class TestMainRound:
|
||||
{"status": "accepted", "strategy_name": "s1", "sharpe_ratio": 0.5, "oos_sharpe": 0.3},
|
||||
{"status": "rejected", "strategy_name": "s2", "reason": "low"},
|
||||
]
|
||||
from scripts.predix_autopilot import main_round
|
||||
from scripts.nexquant_autopilot import main_round
|
||||
result = main_round("daytrading", 1)
|
||||
assert result == 1
|
||||
|
||||
@@ -70,7 +70,7 @@ class TestMainRound:
|
||||
"status": "success", "sharpe_ratio": 0.95, "oos_sharpe": 0.65,
|
||||
"members": ["a", "b"],
|
||||
}
|
||||
from scripts.predix_autopilot import main_round
|
||||
from scripts.nexquant_autopilot import main_round
|
||||
main_round("daytrading", 1)
|
||||
instance.build_ensemble.assert_called_once()
|
||||
|
||||
@@ -80,7 +80,7 @@ class TestMainRound:
|
||||
{"status": "accepted", "strategy_name": "a", "sharpe_ratio": 0.5, "oos_sharpe": 0.3},
|
||||
{"status": "rejected", "strategy_name": "b", "reason": "no"},
|
||||
]
|
||||
from scripts.predix_autopilot import main_round
|
||||
from scripts.nexquant_autopilot import main_round
|
||||
main_round("daytrading", 1)
|
||||
instance.build_ensemble.assert_not_called()
|
||||
|
||||
@@ -91,14 +91,14 @@ class TestMainRound:
|
||||
{"status": "accepted", "strategy_name": "b", "sharpe_ratio": 0.6, "oos_sharpe": 0.4},
|
||||
]
|
||||
instance.build_ensemble.side_effect = RuntimeError("boom")
|
||||
from scripts.predix_autopilot import main_round
|
||||
from scripts.nexquant_autopilot import main_round
|
||||
result = main_round("daytrading", 1)
|
||||
assert result == 2 # Still counts accepted
|
||||
|
||||
def test_empty_results_returns_zero(self, mock_orch):
|
||||
mock_cls, instance = mock_orch
|
||||
instance.generate_strategies.return_value = []
|
||||
from scripts.predix_autopilot import main_round
|
||||
from scripts.nexquant_autopilot import main_round
|
||||
result = main_round("daytrading", 1)
|
||||
assert result == 0
|
||||
|
||||
@@ -109,7 +109,7 @@ class TestMainRound:
|
||||
{"status": "accepted", "strategy_name": "b", "sharpe_ratio": 0.6, "oos_sharpe": 0.4},
|
||||
]
|
||||
instance.build_ensemble.return_value = None
|
||||
from scripts.predix_autopilot import main_round
|
||||
from scripts.nexquant_autopilot import main_round
|
||||
result = main_round("daytrading", 1)
|
||||
assert result == 2
|
||||
|
||||
@@ -130,27 +130,27 @@ class TestMainRound:
|
||||
"reason": "test"},
|
||||
]
|
||||
mock_cls.return_value = instance
|
||||
from scripts.predix_autopilot import main_round
|
||||
from scripts.nexquant_autopilot import main_round
|
||||
result = main_round("daytrading", 1)
|
||||
assert isinstance(result, int) and result >= 0
|
||||
|
||||
|
||||
class TestConfig:
|
||||
def test_batch_size_positive(self):
|
||||
from scripts import predix_autopilot
|
||||
assert predix_autopilot.BATCH_SIZE > 0
|
||||
from scripts import nexquant_autopilot
|
||||
assert nexquant_autopilot.BATCH_SIZE > 0
|
||||
|
||||
def test_optuna_trials_positive(self):
|
||||
from scripts import predix_autopilot
|
||||
assert predix_autopilot.OPTUNA_TRIALS > 0
|
||||
from scripts import nexquant_autopilot
|
||||
assert nexquant_autopilot.OPTUNA_TRIALS > 0
|
||||
|
||||
def test_cooldown_positive(self):
|
||||
from scripts import predix_autopilot
|
||||
assert predix_autopilot.COOLDOWN > 0
|
||||
from scripts import nexquant_autopilot
|
||||
assert nexquant_autopilot.COOLDOWN > 0
|
||||
|
||||
def test_max_consecutive_fails_positive(self):
|
||||
from scripts import predix_autopilot
|
||||
assert predix_autopilot.MAX_CONSECUTIVE_FAILS > 0
|
||||
from scripts import nexquant_autopilot
|
||||
assert nexquant_autopilot.MAX_CONSECUTIVE_FAILS > 0
|
||||
|
||||
|
||||
class TestStyleCycling:
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
Tests for background task infrastructure (parallel runner, CLI paths, env loading).
|
||||
|
||||
Verifies bugs that were previously present:
|
||||
- predix_parallel.py: project_root pointing to scripts/ instead of repo root
|
||||
- predix_parallel.py: .env loaded from scripts/ instead of repo root
|
||||
- predix_parallel.py: API key round-robin overwritten by comma-separated list
|
||||
- nexquant_parallel.py: project_root pointing to scripts/ instead of repo root
|
||||
- nexquant_parallel.py: .env loaded from scripts/ instead of repo root
|
||||
- nexquant_parallel.py: API key round-robin overwritten by comma-separated list
|
||||
- cli.py: project_root depth wrong (4 .parent hops instead of 3)
|
||||
- cli.py start_loop: hardcoded "python" instead of sys.executable
|
||||
- cli.py parallel: hardcoded model=local
|
||||
@@ -18,7 +18,7 @@ from unittest.mock import Mock, patch
|
||||
import pytest
|
||||
|
||||
|
||||
# ── predix_parallel.py ──────────────────────────────────────────────────
|
||||
# ── nexquant_parallel.py ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestParallelRunnerProjectRoot:
|
||||
@@ -26,36 +26,36 @@ class TestParallelRunnerProjectRoot:
|
||||
|
||||
def test_project_root_is_repo_root(self):
|
||||
"""Bug: project_root was Path(__file__).parent (= scripts/)."""
|
||||
from scripts.predix_parallel import ParallelRunner
|
||||
from scripts.nexquant_parallel import ParallelRunner
|
||||
|
||||
runner = ParallelRunner(num_runs=1, num_api_keys=1, model="local")
|
||||
root = runner.project_root
|
||||
|
||||
# Must contain predix.py (repo root), NOT be the scripts/ dir
|
||||
assert (root / "predix.py").exists(), (
|
||||
f"project_root={root} does not contain predix.py — "
|
||||
# Must contain nexquant.py (repo root), NOT be the scripts/ dir
|
||||
assert (root / "nexquant.py").exists(), (
|
||||
f"project_root={root} does not contain nexquant.py — "
|
||||
f"likely still pointing to scripts/ instead of repo root"
|
||||
)
|
||||
assert root.name != "scripts", (
|
||||
f"project_root={root} ends with 'scripts/' — should be repo root"
|
||||
)
|
||||
|
||||
def test_build_command_points_to_predix_py(self):
|
||||
"""Bug: command pointed to scripts/predix.py which doesn't exist."""
|
||||
from scripts.predix_parallel import ParallelRunner, RunState
|
||||
def test_build_command_points_to_nexquant_py(self):
|
||||
"""Bug: command pointed to scripts/nexquant.py which doesn't exist."""
|
||||
from scripts.nexquant_parallel import ParallelRunner, RunState
|
||||
|
||||
runner = ParallelRunner(num_runs=1, num_api_keys=1, model="local")
|
||||
run = RunState(run_id=1, api_key_idx=0, model="local")
|
||||
cmd = runner._build_command(run)
|
||||
|
||||
predix_path = Path(cmd[1])
|
||||
assert predix_path.exists(), (
|
||||
f"Command references {predix_path} which does not exist — "
|
||||
nexquant_path = Path(cmd[1])
|
||||
assert nexquant_path.exists(), (
|
||||
f"Command references {nexquant_path} which does not exist — "
|
||||
f"project_root likely still wrong"
|
||||
)
|
||||
assert predix_path.name == "predix.py"
|
||||
assert predix_path.parent.name != "scripts", (
|
||||
"predix.py should be in repo root, not scripts/"
|
||||
assert nexquant_path.name == "nexquant.py"
|
||||
assert nexquant_path.parent.name != "scripts", (
|
||||
"nexquant.py should be in repo root, not scripts/"
|
||||
)
|
||||
|
||||
def test_env_loading_from_repo_root(self):
|
||||
@@ -75,7 +75,7 @@ class TestParallelRunnerAPIKeys:
|
||||
|
||||
def test_single_api_key_no_overwrite(self):
|
||||
"""Bug: with num_api_keys=1, individual key was set then overwritten."""
|
||||
from scripts.predix_parallel import ParallelRunner, RunState
|
||||
from scripts.nexquant_parallel import ParallelRunner, RunState
|
||||
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
os.environ["OPENROUTER_API_KEY"] = "sk-test-key-1"
|
||||
@@ -95,7 +95,7 @@ class TestParallelRunnerAPIKeys:
|
||||
|
||||
def test_multi_api_key_comma_separated(self):
|
||||
"""With 2+ keys, all runs get comma-separated list for load balancing."""
|
||||
from scripts.predix_parallel import ParallelRunner, RunState
|
||||
from scripts.nexquant_parallel import ParallelRunner, RunState
|
||||
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
os.environ["OPENROUTER_API_KEY"] = "sk-key-a"
|
||||
@@ -111,7 +111,7 @@ class TestParallelRunnerAPIKeys:
|
||||
|
||||
def test_round_robin_api_key_index(self):
|
||||
"""Verify round-robin API key index assignment is computed correctly."""
|
||||
from scripts.predix_parallel import ParallelRunner
|
||||
from scripts.nexquant_parallel import ParallelRunner
|
||||
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
os.environ["OPENROUTER_API_KEY"] = "a"
|
||||
@@ -129,7 +129,7 @@ class TestParallelRunnerLogFileHandling:
|
||||
|
||||
def test_log_file_paths_in_repo_root(self):
|
||||
"""Bug: logs went to scripts/fin_quant_runN.log."""
|
||||
from scripts.predix_parallel import ParallelRunner
|
||||
from scripts.nexquant_parallel import ParallelRunner
|
||||
|
||||
runner = ParallelRunner(num_runs=2, num_api_keys=1, model="local")
|
||||
|
||||
@@ -170,7 +170,7 @@ class TestCLIProjectRoot:
|
||||
"4 .parent hops should NOT yield repo root "
|
||||
f"(got {buggy}, expected {self.REPO_ROOT.parent})"
|
||||
)
|
||||
assert (buggy / "Predix").exists() or buggy == self.REPO_ROOT.parent, (
|
||||
assert (buggy / "NexQuant").exists() or buggy == self.REPO_ROOT.parent, (
|
||||
f"4 .parent hops overshoots repo root: {buggy}"
|
||||
)
|
||||
|
||||
@@ -210,12 +210,12 @@ class TestCLIProjectRoot:
|
||||
|
||||
# All these commands use Path(__file__).parent.parent.parent as project_root
|
||||
commands = {
|
||||
"eval_all": "scripts/predix_full_eval.py",
|
||||
"batch_backtest": "scripts/predix_batch_backtest.py",
|
||||
"simple_eval": "scripts/predix_simple_eval.py",
|
||||
"rebacktest": "scripts/predix_rebacktest_strategies.py",
|
||||
"report": "scripts/predix_strategy_report.py",
|
||||
"parallel": "scripts/predix_parallel.py",
|
||||
"eval_all": "scripts/nexquant_full_eval.py",
|
||||
"batch_backtest": "scripts/nexquant_batch_backtest.py",
|
||||
"simple_eval": "scripts/nexquant_simple_eval.py",
|
||||
"rebacktest": "scripts/nexquant_rebacktest_strategies.py",
|
||||
"report": "scripts/nexquant_strategy_report.py",
|
||||
"parallel": "scripts/nexquant_parallel.py",
|
||||
}
|
||||
|
||||
for cmd_name, script_path in commands.items():
|
||||
@@ -231,12 +231,12 @@ class TestCLIProjectRoot:
|
||||
import inspect
|
||||
|
||||
source = inspect.getsource(start_loop_cli)
|
||||
# The generator should reference scripts/predix_smart_strategy_gen.py
|
||||
assert "predix_smart_strategy_gen.py" in source, (
|
||||
"start_loop_cli should reference predix_smart_strategy_gen.py"
|
||||
# The generator should reference scripts/nexquant_smart_strategy_gen.py
|
||||
assert "nexquant_smart_strategy_gen.py" in source, (
|
||||
"start_loop_cli should reference nexquant_smart_strategy_gen.py"
|
||||
)
|
||||
|
||||
script = self.REPO_ROOT / "scripts" / "predix_smart_strategy_gen.py"
|
||||
script = self.REPO_ROOT / "scripts" / "nexquant_smart_strategy_gen.py"
|
||||
assert script.exists(), (
|
||||
f"Generator script not found at {script}"
|
||||
)
|
||||
@@ -266,7 +266,7 @@ class TestImportsDontCrash:
|
||||
|
||||
def test_import_parallel_runner(self):
|
||||
"""ParallelRunner should import without errors."""
|
||||
from scripts.predix_parallel import ParallelRunner, RunState
|
||||
from scripts.nexquant_parallel import ParallelRunner, RunState
|
||||
runner = ParallelRunner(num_runs=1, num_api_keys=1, model="local")
|
||||
assert runner.num_runs == 1
|
||||
assert len(runner.runs) == 1
|
||||
|
||||
@@ -15,8 +15,8 @@ Verifies:
|
||||
- strategy_orchestrator.py exec() exception logged at ERROR level
|
||||
- strategy_orchestrator.py template validation warns on unreplaced {{...}}
|
||||
- factor_runner.py IC_max guard against scalar (AttributeError)
|
||||
- predix_parallel.py handle leak on Popen failure
|
||||
- predix_rebacktest_strategies.py bare except replaced with except Exception
|
||||
- nexquant_parallel.py handle leak on Popen failure
|
||||
- nexquant_rebacktest_strategies.py bare except replaced with except Exception
|
||||
"""
|
||||
|
||||
import ast
|
||||
@@ -228,32 +228,32 @@ class TestFactorRunnerICMaxGuard:
|
||||
)
|
||||
|
||||
|
||||
# ── Fix 13: predix_parallel.py handle leak ───────────────────────────────
|
||||
# ── Fix 13: nexquant_parallel.py handle leak ───────────────────────────────
|
||||
|
||||
|
||||
class TestParallelRunnerHandleLeak:
|
||||
def test_log_f_close_on_popen_failure(self):
|
||||
"""Bug: open() file handle leaked if Popen failed."""
|
||||
source = (REPO_ROOT / "scripts/predix_parallel.py").read_text()
|
||||
source = (REPO_ROOT / "scripts/nexquant_parallel.py").read_text()
|
||||
|
||||
# After fix, log_f.close() is called before re-raise
|
||||
assert "log_f.close()" in source, (
|
||||
"predix_parallel.py should close log file handle on Popen failure"
|
||||
"nexquant_parallel.py should close log file handle on Popen failure"
|
||||
)
|
||||
|
||||
|
||||
# ── Fix 14: predix_rebacktest_strategies.py bare except ──────────────────
|
||||
# ── Fix 14: nexquant_rebacktest_strategies.py bare except ──────────────────
|
||||
|
||||
|
||||
class TestRebacktestBareExcept:
|
||||
def test_not_bare_except(self):
|
||||
"""Bug: bare except: pass swallowed all errors including SystemExit."""
|
||||
source = (REPO_ROOT / "scripts/predix_rebacktest_strategies.py").read_text()
|
||||
source = (REPO_ROOT / "scripts/nexquant_rebacktest_strategies.py").read_text()
|
||||
|
||||
# After fix, should use except Exception, not bare except
|
||||
assert "except Exception:" in source
|
||||
assert "except:" not in source, (
|
||||
"predix_rebacktest_strategies.py should not use bare except:"
|
||||
"nexquant_rebacktest_strategies.py should not use bare except:"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Deep tests for predix_continuous_strategies.py — ML model building, style cycling.
|
||||
"""Deep tests for nexquant_continuous_strategies.py — ML model building, style cycling.
|
||||
|
||||
Tests the build_ml_model function and the round/style alternation logic
|
||||
without requiring real StrategyOrchestrator connections.
|
||||
@@ -45,7 +45,7 @@ def close_data():
|
||||
class TestBuildMLModel:
|
||||
def test_insufficient_data_returns_none(self, factor_data, close_data):
|
||||
"""<5000 rows should return None."""
|
||||
from scripts.predix_continuous_strategies import build_ml_model
|
||||
from scripts.nexquant_continuous_strategies import build_ml_model
|
||||
result = build_ml_model(factor_data.iloc[:100], close_data.iloc[:100], "swing")
|
||||
assert result is None
|
||||
|
||||
@@ -55,7 +55,7 @@ class TestBuildMLModel:
|
||||
"sharpe": 1.5, "max_drawdown": -0.1, "win_rate": 0.55,
|
||||
"n_trades": 200, "wf_oos_sharpe_mean": 0.8,
|
||||
}
|
||||
from scripts.predix_continuous_strategies import build_ml_model
|
||||
from scripts.nexquant_continuous_strategies import build_ml_model
|
||||
result = build_ml_model(factor_data, close_data, "daytrading")
|
||||
assert result is not None
|
||||
assert "strategy_name" in result
|
||||
@@ -69,7 +69,7 @@ class TestBuildMLModel:
|
||||
"sharpe": 1.5, "max_drawdown": -0.1, "win_rate": 0.55,
|
||||
"n_trades": 200, "wf_oos_sharpe_mean": -0.3,
|
||||
}
|
||||
from scripts.predix_continuous_strategies import build_ml_model
|
||||
from scripts.nexquant_continuous_strategies import build_ml_model
|
||||
result = build_ml_model(factor_data, close_data, "swing")
|
||||
assert result is None
|
||||
|
||||
@@ -90,7 +90,7 @@ class TestBuildMLModel:
|
||||
}, index=factor_data.index[:n])
|
||||
c = pd.Series(1.10 + rng.normal(0, 0.001, n).cumsum(), index=f.index)
|
||||
try:
|
||||
from scripts.predix_continuous_strategies import build_ml_model
|
||||
from scripts.nexquant_continuous_strategies import build_ml_model
|
||||
result = build_ml_model(f, c, "swing")
|
||||
assert result is None or isinstance(result, dict)
|
||||
except Exception as e:
|
||||
@@ -102,12 +102,12 @@ class TestBuildMLModel:
|
||||
|
||||
class TestConfig:
|
||||
def test_batch_size_is_positive(self):
|
||||
from scripts import predix_continuous_strategies
|
||||
assert predix_continuous_strategies.BATCH_SIZE > 0
|
||||
from scripts import nexquant_continuous_strategies
|
||||
assert nexquant_continuous_strategies.BATCH_SIZE > 0
|
||||
|
||||
def test_cooldown_is_positive(self):
|
||||
from scripts import predix_continuous_strategies
|
||||
assert predix_continuous_strategies.COOLDOWN_SECONDS > 0
|
||||
from scripts import nexquant_continuous_strategies
|
||||
assert nexquant_continuous_strategies.COOLDOWN_SECONDS > 0
|
||||
|
||||
|
||||
class TestStyleCycling:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Deep tests for predix_gen_strategies_real_bt.py — property-based, edge cases.
|
||||
"""Deep tests for nexquant_gen_strategies_real_bt.py — property-based, edge cases.
|
||||
|
||||
Tests factor loading, threshold rescaling, backtest runner, acceptance
|
||||
criteria, and the TeeFile logger — without requiring real OHLCV data or LLM.
|
||||
@@ -27,7 +27,7 @@ from hypothesis import strategies as st
|
||||
@pytest.fixture
|
||||
def gen_module():
|
||||
import importlib
|
||||
import scripts.predix_gen_strategies_real_bt as m
|
||||
import scripts.nexquant_gen_strategies_real_bt as m
|
||||
return m
|
||||
|
||||
|
||||
@@ -280,7 +280,7 @@ class TestConfiguration:
|
||||
"""Daytrading config uses tighter risk limits."""
|
||||
os.environ["TRADING_STYLE"] = "daytrading"
|
||||
import importlib
|
||||
import scripts.predix_gen_strategies_real_bt as m
|
||||
import scripts.nexquant_gen_strategies_real_bt as m
|
||||
importlib.reload(m)
|
||||
assert m.MIN_IC == 0.02
|
||||
assert m.MIN_SHARPE == 0.5
|
||||
@@ -289,7 +289,7 @@ class TestConfiguration:
|
||||
def test_swing_defaults(self):
|
||||
os.environ["TRADING_STYLE"] = "swing"
|
||||
import importlib
|
||||
import scripts.predix_gen_strategies_real_bt as m
|
||||
import scripts.nexquant_gen_strategies_real_bt as m
|
||||
importlib.reload(m)
|
||||
assert m.MIN_TRADES == 10
|
||||
assert m.MAX_DRAWDOWN == -0.30
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Tests for MLTrainer - ML Training Pipeline for Predix quant trading system.
|
||||
Tests for MLTrainer - ML Training Pipeline for NexQuant quant trading system.
|
||||
|
||||
Tests cover:
|
||||
- Feature matrix building
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Deep tests for predix_parallel.py — property-based, state transitions, edge cases.
|
||||
"""Deep tests for nexquant_parallel.py — property-based, state transitions, edge cases.
|
||||
|
||||
Tests RunState, ParallelRunner configuration, environment building,
|
||||
command building, and API key loading logic.
|
||||
@@ -25,7 +25,7 @@ from hypothesis import strategies as st
|
||||
|
||||
@pytest.fixture
|
||||
def runstate():
|
||||
from scripts.predix_parallel import RunState
|
||||
from scripts.nexquant_parallel import RunState
|
||||
return RunState(run_id=1, api_key_idx=0, model="local")
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ class TestRunState:
|
||||
|
||||
def test_elapsed_running(self, runstate):
|
||||
runstate.start_time = datetime(2024, 1, 1, 12, 0, 0)
|
||||
with patch("scripts.predix_parallel.datetime") as mock_dt:
|
||||
with patch("scripts.nexquant_parallel.datetime") as mock_dt:
|
||||
mock_dt.now.return_value = datetime(2024, 1, 1, 13, 30, 45)
|
||||
assert runstate.elapsed == "01:30:45"
|
||||
|
||||
@@ -94,7 +94,7 @@ class TestRunState:
|
||||
class TestParallelRunnerConfig:
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_load_api_keys_openrouter(self):
|
||||
from scripts.predix_parallel import ParallelRunner
|
||||
from scripts.nexquant_parallel import ParallelRunner
|
||||
with patch.dict(os.environ, {
|
||||
"OPENROUTER_API_KEY": "sk-key1",
|
||||
"OPENROUTER_API_KEY_2": "sk-key2",
|
||||
@@ -106,13 +106,13 @@ class TestParallelRunnerConfig:
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_load_api_keys_local(self):
|
||||
from scripts.predix_parallel import ParallelRunner
|
||||
from scripts.nexquant_parallel import ParallelRunner
|
||||
runner = ParallelRunner(num_runs=1, num_api_keys=1, model="local")
|
||||
assert runner.api_keys == ["local"]
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_load_api_keys_round_robin(self):
|
||||
from scripts.predix_parallel import ParallelRunner
|
||||
from scripts.nexquant_parallel import ParallelRunner
|
||||
runner = ParallelRunner(num_runs=5, num_api_keys=2, model="local")
|
||||
assert len(runner.runs) == 5
|
||||
idxs = [r.api_key_idx for r in runner.runs]
|
||||
@@ -120,7 +120,7 @@ class TestParallelRunnerConfig:
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_build_env_local_model(self):
|
||||
from scripts.predix_parallel import ParallelRunner, RunState
|
||||
from scripts.nexquant_parallel import ParallelRunner, RunState
|
||||
with patch.dict(os.environ, {
|
||||
"OPENAI_API_KEY": "local",
|
||||
"OPENAI_API_BASE": "http://localhost:8081/v1",
|
||||
@@ -135,7 +135,7 @@ class TestParallelRunnerConfig:
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_build_env_openrouter(self):
|
||||
from scripts.predix_parallel import ParallelRunner, RunState
|
||||
from scripts.nexquant_parallel import ParallelRunner, RunState
|
||||
with patch.dict(os.environ, {
|
||||
"OPENROUTER_API_KEY": "sk-test",
|
||||
"OPENROUTER_API_KEY_2": "sk-test2",
|
||||
@@ -147,7 +147,7 @@ class TestParallelRunnerConfig:
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_build_env_sets_workspace(self):
|
||||
from scripts.predix_parallel import ParallelRunner, RunState
|
||||
from scripts.nexquant_parallel import ParallelRunner, RunState
|
||||
runner = ParallelRunner(num_runs=1, num_api_keys=1, model="local")
|
||||
rs = RunState(run_id=42, api_key_idx=0, model="local")
|
||||
env = runner._build_env(rs)
|
||||
@@ -156,11 +156,11 @@ class TestParallelRunnerConfig:
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_build_command(self):
|
||||
from scripts.predix_parallel import ParallelRunner, RunState
|
||||
from scripts.nexquant_parallel import ParallelRunner, RunState
|
||||
runner = ParallelRunner(num_runs=1, num_api_keys=1, model="local")
|
||||
rs = RunState(run_id=7, api_key_idx=0, model="local")
|
||||
cmd = runner._build_command(rs)
|
||||
assert "predix.py" in cmd[1] or "predix" in cmd[1]
|
||||
assert "nexquant.py" in cmd[1] or "nexquant" in cmd[1]
|
||||
assert "quant" in cmd
|
||||
assert "--model" in cmd
|
||||
assert "local" in cmd
|
||||
@@ -168,7 +168,7 @@ class TestParallelRunnerConfig:
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_parallel_runner_init_counts(self):
|
||||
from scripts.predix_parallel import ParallelRunner
|
||||
from scripts.nexquant_parallel import ParallelRunner
|
||||
for n in [1, 3, 10]:
|
||||
runner = ParallelRunner(num_runs=n, num_api_keys=2, model="local")
|
||||
assert len(runner.runs) == n
|
||||
@@ -178,20 +178,20 @@ class TestParallelRunnerConfig:
|
||||
class TestParallelRunnerEdgeCases:
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_max_runs_limit(self):
|
||||
from scripts.predix_parallel import ParallelRunner
|
||||
from scripts.nexquant_parallel import ParallelRunner
|
||||
runner = ParallelRunner(num_runs=100, num_api_keys=1, model="local")
|
||||
assert len(runner.runs) == 100
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_api_keys_empty_uses_local(self):
|
||||
from scripts.predix_parallel import ParallelRunner
|
||||
from scripts.nexquant_parallel import ParallelRunner
|
||||
runner = ParallelRunner(num_runs=1, num_api_keys=2, model="openrouter")
|
||||
assert len(runner.api_keys) >= 1
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_build_env_preserves_existing_env(self, monkeypatch):
|
||||
monkeypatch.setenv("MY_CUSTOM_VAR", "custom_value")
|
||||
from scripts.predix_parallel import ParallelRunner, RunState
|
||||
from scripts.nexquant_parallel import ParallelRunner, RunState
|
||||
runner = ParallelRunner(num_runs=1, num_api_keys=1, model="local")
|
||||
rs = RunState(run_id=1, api_key_idx=0, model="local")
|
||||
env = runner._build_env(rs)
|
||||
@@ -643,14 +643,14 @@ class TestStrategySaver:
|
||||
'passed': True,
|
||||
'checks': {},
|
||||
},
|
||||
metadata={'version': '1.0', 'author': 'Predix'},
|
||||
metadata={'version': '1.0', 'author': 'NexQuant'},
|
||||
)
|
||||
|
||||
with open(filepath) as f:
|
||||
data = json.load(f)
|
||||
|
||||
assert data['metadata']['version'] == '1.0'
|
||||
assert data['metadata']['author'] == 'Predix'
|
||||
assert data['metadata']['author'] == 'NexQuant'
|
||||
|
||||
def test_save_strategy_with_llm_response(self, strategy_saver):
|
||||
"""Test saving strategy with LLM response preview."""
|
||||
|
||||
@@ -123,9 +123,9 @@ class TestWebDashboard:
|
||||
|
||||
|
||||
class TestScriptsImportable:
|
||||
def test_predix_full_eval(self):
|
||||
def test_nexquant_full_eval(self):
|
||||
import importlib
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/predix_full_eval.py")
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/nexquant_full_eval.py")
|
||||
assert spec is not None
|
||||
|
||||
def test_extract_results(self):
|
||||
@@ -153,24 +153,24 @@ class TestScriptsImportable:
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/kronos_model_eval.py")
|
||||
assert spec is not None
|
||||
|
||||
def test_predix_add_risk_management(self):
|
||||
def test_nexquant_add_risk_management(self):
|
||||
import importlib
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/predix_add_risk_management.py")
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/nexquant_add_risk_management.py")
|
||||
assert spec is not None
|
||||
|
||||
def test_predix_gen_strategies(self):
|
||||
def test_nexquant_gen_strategies(self):
|
||||
import importlib
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/predix_gen_strategies_real_bt.py")
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/nexquant_gen_strategies_real_bt.py")
|
||||
assert spec is not None
|
||||
|
||||
def test_predix_quick_daytrading(self):
|
||||
def test_nexquant_quick_daytrading(self):
|
||||
import importlib
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/predix_quick_daytrading.py")
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/nexquant_quick_daytrading.py")
|
||||
assert spec is not None
|
||||
|
||||
def test_predix_rebacktest_unified(self):
|
||||
def test_nexquant_rebacktest_unified(self):
|
||||
import importlib
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/predix_rebacktest_unified.py")
|
||||
spec = importlib.util.spec_from_file_location("m", PROJECT_ROOT / "scripts/nexquant_rebacktest_unified.py")
|
||||
assert spec is not None
|
||||
|
||||
def test_realistic_backtest_all(self):
|
||||
|
||||
@@ -196,7 +196,7 @@ class TestInfNanHandlingInsertion:
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Bug 5: scan_factors reads factor_code twice (predix_full_eval.py:174 + 195)
|
||||
# Bug 5: scan_factors reads factor_code twice (nexquant_full_eval.py:174 + 195)
|
||||
# =============================================================================
|
||||
|
||||
class TestScanFactorsDoubleRead:
|
||||
@@ -205,9 +205,9 @@ class TestScanFactorsDoubleRead:
|
||||
def test_factor_code_read_only_when_needed(self):
|
||||
"""Confirm the scan_factors double-read behavior (line 174+195)."""
|
||||
import inspect
|
||||
from scripts import predix_full_eval
|
||||
from scripts import nexquant_full_eval
|
||||
|
||||
source = inspect.getsource(predix_full_eval.scan_factors)
|
||||
source = inspect.getsource(nexquant_full_eval.scan_factors)
|
||||
|
||||
# Count occurrences of `.read_text()`
|
||||
count = source.count(".read_text()")
|
||||
|
||||
@@ -18,8 +18,8 @@ class TestContinuousGenerator:
|
||||
def test_module_imports(self):
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"predix_autopilot",
|
||||
PROJECT_ROOT / "scripts/predix_autopilot.py",
|
||||
"nexquant_autopilot",
|
||||
PROJECT_ROOT / "scripts/nexquant_autopilot.py",
|
||||
)
|
||||
assert spec is not None
|
||||
|
||||
@@ -107,6 +107,6 @@ class TestAutopilotIntegration:
|
||||
|
||||
def test_autopilot_pid_running(self):
|
||||
import os
|
||||
result = os.system("pgrep -f predix_autopilot > /dev/null 2>&1")
|
||||
result = os.system("pgrep -f nexquant_autopilot > /dev/null 2>&1")
|
||||
# 0 = running, 1 = not running — both are valid states
|
||||
assert result in (0, 1)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Tests for scripts/predix_full_eval.py pure functions and dataclasses."""
|
||||
"""Tests for scripts/nexquant_full_eval.py pure functions and dataclasses."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -13,7 +13,7 @@ sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
class TestFactorInfo:
|
||||
def test_construction(self):
|
||||
from scripts.predix_full_eval import FactorInfo
|
||||
from scripts.nexquant_full_eval import FactorInfo
|
||||
fi = FactorInfo(
|
||||
workspace_hash="abc123",
|
||||
factor_name="test_factor",
|
||||
@@ -26,7 +26,7 @@ class TestFactorInfo:
|
||||
|
||||
class TestEvalResult:
|
||||
def test_defaults(self):
|
||||
from scripts.predix_full_eval import EvalResult
|
||||
from scripts.nexquant_full_eval import EvalResult
|
||||
er = EvalResult(factor_name="f1", workspace_hash="h1")
|
||||
assert er.status == ""
|
||||
assert er.ic is None
|
||||
@@ -34,7 +34,7 @@ class TestEvalResult:
|
||||
assert er.non_null_count == 0
|
||||
|
||||
def test_failed_result(self):
|
||||
from scripts.predix_full_eval import EvalResult
|
||||
from scripts.nexquant_full_eval import EvalResult
|
||||
er = EvalResult(
|
||||
factor_name="f1", workspace_hash="h1",
|
||||
status="failed", error_message="timeout",
|
||||
@@ -43,7 +43,7 @@ class TestEvalResult:
|
||||
assert er.error_message == "timeout"
|
||||
|
||||
def test_to_dict(self):
|
||||
from scripts.predix_full_eval import EvalResult
|
||||
from scripts.nexquant_full_eval import EvalResult
|
||||
er = EvalResult(factor_name="f1", workspace_hash="h1", status="success", ic=0.05)
|
||||
d = er.to_dict()
|
||||
assert d["factor_name"] == "f1"
|
||||
@@ -53,26 +53,26 @@ class TestEvalResult:
|
||||
|
||||
class TestExtractFactorDescription:
|
||||
def test_docstring_extracted(self):
|
||||
from scripts.predix_full_eval import _extract_factor_description
|
||||
from scripts.nexquant_full_eval import _extract_factor_description
|
||||
code = '"""This is a test factor.\nComputes momentum."""\nx=1'
|
||||
desc = _extract_factor_description(code)
|
||||
assert "test factor" in desc
|
||||
|
||||
def test_comment_extraction(self):
|
||||
from scripts.predix_full_eval import _extract_factor_description
|
||||
from scripts.nexquant_full_eval import _extract_factor_description
|
||||
code = "# Momentum factor\n# Uses 20-bar window\nx=1"
|
||||
desc = _extract_factor_description(code)
|
||||
assert "Momentum factor" in desc
|
||||
assert "20-bar window" in desc
|
||||
|
||||
def test_no_docstring_or_comments(self):
|
||||
from scripts.predix_full_eval import _extract_factor_description
|
||||
from scripts.nexquant_full_eval import _extract_factor_description
|
||||
code = "x = 1\ny = 2\n"
|
||||
desc = _extract_factor_description(code)
|
||||
assert desc == "No description available"
|
||||
|
||||
def test_shebang_skipped(self):
|
||||
from scripts.predix_full_eval import _extract_factor_description
|
||||
from scripts.nexquant_full_eval import _extract_factor_description
|
||||
code = "#!/usr/bin/env python\n# Real comment\nx=1"
|
||||
desc = _extract_factor_description(code)
|
||||
assert "Real comment" in desc
|
||||
@@ -16,10 +16,10 @@ PROJECT_ROOT = Path(__file__).parent.parent.parent
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
|
||||
class TestPredixCLI:
|
||||
class TestNexQuantCLI:
|
||||
def test_cli_commands_available(self):
|
||||
import subprocess
|
||||
r = subprocess.run([sys.executable, "predix.py", "--help"], capture_output=True, text=True, timeout=10)
|
||||
r = subprocess.run([sys.executable, "nexquant.py", "--help"], capture_output=True, text=True, timeout=10)
|
||||
assert r.returncode == 0
|
||||
for cmd in ["evaluate", "top", "best", "portfolio", "build-strategies", "generate-strategies", "health"]:
|
||||
assert cmd in r.stdout.lower(), f"Missing command: {cmd}"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Predix Dashboard API
|
||||
NexQuant Dashboard API
|
||||
|
||||
Flask-Backend für das Web-Dashboard.
|
||||
Zeigt COMPLETE Progress von EURUSD Trading-Agent.
|
||||
@@ -290,7 +290,7 @@ def get_full_dashboard():
|
||||
def index():
|
||||
"""Root Endpoint - zeigt API-Info."""
|
||||
return jsonify({
|
||||
"name": "Predix Dashboard API",
|
||||
"name": "NexQuant Dashboard API",
|
||||
"version": "1.0.0",
|
||||
"description": "COMPLETE Progress Visualisierung für EURUSD Trading-Agent",
|
||||
"endpoints": {
|
||||
@@ -307,7 +307,7 @@ def index():
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("="*60)
|
||||
print("Predix Dashboard API")
|
||||
print("NexQuant Dashboard API")
|
||||
print("="*60)
|
||||
print(f"Modules available: {MODULES_AVAILABLE}")
|
||||
print(f"Starting server on http://localhost:5000")
|
||||
|
||||
Reference in New Issue
Block a user