mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-30 17:07:43 +00:00
Compare commits
455 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fdc0bcf654 | |||
| 50d1fb47e3 | |||
| 68caa7e88c | |||
| 891daf4df9 | |||
| 56346d5ae5 | |||
| 723ba6f004 | |||
| eb6b2dcd1f | |||
| a721605f4b | |||
| a9d181398b | |||
| 4773e95a6c | |||
| 9ce6a4e6ec | |||
| b793a8114b | |||
| 6bce4f2405 | |||
| 2e028ffc1e | |||
| 7d7c267d29 | |||
| 6cd362aa25 | |||
| a373710454 | |||
| ee3d7786c3 | |||
| 4b6dff1710 | |||
| 7c22287793 | |||
| 3874afb8dd | |||
| e168a5df7e | |||
| 8806b12ad6 | |||
| 61e6a09b95 | |||
| 9303b40fb9 | |||
| ab57498ccf | |||
| 6f399c1d96 | |||
| 4758de0eee | |||
| d4611b530e | |||
| e0000a18d2 | |||
| 847a30a787 | |||
| c7ae139c18 | |||
| 774a581184 | |||
| 6975f77b77 | |||
| 918639c051 | |||
| e3a65bb140 | |||
| c45b911abe | |||
| f10b257152 | |||
| 15c03df431 | |||
| 0d9b0916f2 | |||
| 827f80ce2e | |||
| e4aea618b8 | |||
| a469692141 | |||
| 90690c1675 | |||
| 5620ea1b0e | |||
| 5b2b2ca9cc | |||
| aa7e046782 | |||
| 54b8713938 | |||
| 630794e00c | |||
| 69c148f1f3 | |||
| a781d003ba | |||
| efd77b434c | |||
| abca9eb899 | |||
| 3d38d88248 | |||
| 22c8092f1c | |||
| 21afe878e1 | |||
| cbe1c52e00 | |||
| 85b56b8179 | |||
| ba7458fba5 | |||
| 92ddcc47c7 | |||
| 160ac96130 | |||
| e029120090 | |||
| 4d9459a9f9 | |||
| f0ac999dbe | |||
| 9c91a6938d | |||
| 0aea8c7671 | |||
| 72e8a4306e | |||
| 669263db37 | |||
| 584bf9d955 | |||
| d458e39940 | |||
| e20f3b0ea4 | |||
| 3584aee089 | |||
| e0c287a575 | |||
| 8a8fb6688a | |||
| fddc472e16 | |||
| 29ad20914b | |||
| e76d5ab9cf | |||
| 4b0898b377 | |||
| f98ef71693 | |||
| 54718b1663 | |||
| ce86eb8a8a | |||
| 8cce616434 | |||
| d55bd518d3 | |||
| f99acca6e0 | |||
| 49a2c34c5c | |||
| 71ceb9c809 | |||
| 7f5acccfd9 | |||
| a459518dfa | |||
| 41c231e418 | |||
| 5c93c786e7 | |||
| 834cc686d1 | |||
| c0ec1b39e1 | |||
| f5e55d2dad | |||
| b78b9dea8c | |||
| 58a7ece3a9 | |||
| 02083409e0 | |||
| 33f6daf1d2 | |||
| 1827c50344 | |||
| 06a5d5d92d | |||
| 7696a3aaa6 | |||
| 6d37f8956f | |||
| 020bc11742 | |||
| 0f7eb908b4 | |||
| 4d8b389b47 | |||
| 72b9a735c9 | |||
| ce4a5b7b4f | |||
| 037f7ba7d2 | |||
| e31963713b | |||
| dc2a1a41f4 | |||
| c3ff9d5e63 | |||
| 1a530440c1 | |||
| b803d9fab5 | |||
| 13dbf27457 | |||
| 49ebc19fd0 | |||
| 58eb551898 | |||
| 2e18b7c104 | |||
| 28743eb043 | |||
| f6f5531427 | |||
| bd9362e2f3 | |||
| a4d4266295 | |||
| 8f2ed4185f | |||
| 7f24af6f05 | |||
| aba88dd090 | |||
| 28766c932e | |||
| 574e9d6c08 | |||
| ce76da912a | |||
| 02ac3f7aae | |||
| 39b49b1724 | |||
| f1eb66cc8f | |||
| ca003cd0f2 | |||
| 4eeb724ac5 | |||
| 6c3bdb6ec1 | |||
| 82633d328a | |||
| c5d919f581 | |||
| 44ed82283d | |||
| 87610d660f | |||
| c0516d60f9 | |||
| a43c443c2e | |||
| 9e58c64805 | |||
| 732361bb90 | |||
| dff6262871 | |||
| 23b2518c74 | |||
| d83c020637 | |||
| 48843682d0 | |||
| 5a5bf4d771 | |||
| 02c830d4e2 | |||
| 1328a8f0c4 | |||
| 2126062edf | |||
| 133ec1b816 | |||
| 0fa4f5dcc8 | |||
| a076b58304 | |||
| afe1823e85 | |||
| b4674ce3a0 | |||
| 942266f24d | |||
| 95b14fcfc8 | |||
| e9422be6e2 | |||
| 161c4043d7 | |||
| 6b3669c3be | |||
| a7fc33b9a8 | |||
| 1448106230 | |||
| 5eeaf1f496 | |||
| 90a6999563 | |||
| 387508168f | |||
| 2055cf1817 | |||
| 018231d1f2 | |||
| d8bd16e6b9 | |||
| 940859a85b | |||
| 2de7275b8f | |||
| 9691b64938 | |||
| d8c0d8865c | |||
| bf5dfd1d12 | |||
| 919a44a4b8 | |||
| 44f82b13d3 | |||
| 17a2558339 | |||
| ed1802b511 | |||
| ff1c9fc554 | |||
| ba4d64b434 | |||
| 8aec974702 | |||
| f4deda99b5 | |||
| bd5a5e0fd5 | |||
| 7b2f54ff9a | |||
| a50987687d | |||
| d2037a475a | |||
| ef2a6c5ee0 | |||
| dfbbffd7ea | |||
| 78fb607dbe | |||
| b1db0b1f7d | |||
| 1958544106 | |||
| e886ebab8f | |||
| ed80fe8160 | |||
| 9b87a1f5ae | |||
| 6c8a1257c8 | |||
| 2a6839e999 | |||
| d74c5a8f84 | |||
| 837f1f6a0e | |||
| 5fc9aa026c | |||
| f0bf7e2d61 | |||
| b0490c5f0d | |||
| 6d514d3ed5 | |||
| d021f77242 | |||
| 23c528917c | |||
| 02bb7724c6 | |||
| f726e939ab | |||
| f166cc3326 | |||
| 9102f3ce96 | |||
| f8d1d36cf0 | |||
| 6c100170bd | |||
| 64e96bd350 | |||
| 01ba45be56 | |||
| 3f54381052 | |||
| 888e841366 | |||
| 4a6178f53a | |||
| cffb9adc38 | |||
| 6e52c8a15d | |||
| 3f460226f2 | |||
| 1f6990d04d | |||
| ce232733d9 | |||
| 1de5bbe1c6 | |||
| 38cf4bc63e | |||
| 96e4f49740 | |||
| f4747eb907 | |||
| 958560e78f | |||
| e4d400e3f6 | |||
| 69d8b5c871 | |||
| 743ff45976 | |||
| fef5a0610d | |||
| c468e9f00e | |||
| 632d11ae53 | |||
| 6dfbf148ed | |||
| f1c2b7d3a9 | |||
| b8d9564fae | |||
| 1a57e57583 | |||
| 22e638af86 | |||
| ad0358d01c | |||
| cedd615922 | |||
| 5860b8487d | |||
| 64dc7ba425 | |||
| 616590cdc0 | |||
| d8ab86d6cf | |||
| 5f735adcb1 | |||
| 9d623f0fbb | |||
| f24f678713 | |||
| a49f63cfec | |||
| 652164b79e | |||
| 2cec08bc91 | |||
| 5a6446a287 | |||
| 005107a461 | |||
| 554a499d09 | |||
| 0a275528ed | |||
| b9fe985a55 | |||
| 6ee6c5210d | |||
| 6948b9c5e9 | |||
| 06fc8dc36c | |||
| 8a3472f85a | |||
| 6358bc500f | |||
| 3cfa3dda6f | |||
| b98c9cd572 | |||
| 19c9b88b76 | |||
| 53afed001e | |||
| cc8023ce48 | |||
| 4a3a3c8f24 | |||
| 35a1a62df5 | |||
| 44a06a65f4 | |||
| 0fd366dd59 | |||
| 009f18eb46 | |||
| 020f0135ef | |||
| 0acdfaa485 | |||
| 1fbf094115 | |||
| dbc8603e73 | |||
| d6722e46f0 | |||
| 9925b3132c | |||
| a5a0a3c6f9 | |||
| 7a0a95163b | |||
| d35764d44c | |||
| 7e2c31305f | |||
| 362cff291c | |||
| a523e62d94 | |||
| e0a5e6d86c | |||
| a90325d203 | |||
| 0ae6f0f39a | |||
| 087af5b297 | |||
| dd6beec3c9 | |||
| ad206345cc | |||
| fceee44967 | |||
| 9e50eb2d4e | |||
| b63380e3ce | |||
| 4c45ba33ab | |||
| 8aa28ffb33 | |||
| 108a63fd79 | |||
| 21daaf4997 | |||
| 0b168fd3e4 | |||
| 03536af000 | |||
| d12430427d | |||
| 352dd08514 | |||
| 4133d62760 | |||
| bef1d77ee0 | |||
| 6ba2bc0c8f | |||
| 11d96ec3bd | |||
| 2677ee43a2 | |||
| 24a58e970e | |||
| 3e4bfbc61e | |||
| fc0974a823 | |||
| 360bd26d4f | |||
| 586bd1fe4e | |||
| 7dff60cf2e | |||
| 3e2dc42f88 | |||
| 5b022c9c99 | |||
| ac9bfc6fb4 | |||
| a8c23bd130 | |||
| 7e8148c002 | |||
| 971a2535d6 | |||
| ae7e95cba6 | |||
| e31a2e5405 | |||
| 12c949b0b4 | |||
| 00a1d48aad | |||
| f0814c6bf7 | |||
| bf852293f0 | |||
| 6a1c4760c9 | |||
| 9285a5f97a | |||
| 760961d5e7 | |||
| 86d415056e | |||
| c049742df7 | |||
| b27c4b7517 | |||
| 8e0a7e3f26 | |||
| 715555b7d1 | |||
| f6451d363d | |||
| c861f4480f | |||
| ff893d6c74 | |||
| 25865f9c77 | |||
| 5ef1fd65db | |||
| 56d73d22e9 | |||
| 9ec6008ad8 | |||
| 68ea969c32 | |||
| 127ee0f44f | |||
| d3ae6df454 | |||
| 7e7e40b041 | |||
| 574a9cb75e | |||
| 612ed8a802 | |||
| 633b5639de | |||
| 74d5a8234e | |||
| 5ce86c824e | |||
| 1bbca062af | |||
| bd025e50dc | |||
| 2a011d262e | |||
| d5437e970f | |||
| f3a2e2b4f1 | |||
| 820c27f91b | |||
| 848bbafd13 | |||
| 7993a2398a | |||
| 9642a7711a | |||
| a3ad4973a2 | |||
| edc0d585c3 | |||
| 2eced3ca69 | |||
| 15c944efef | |||
| 28e45c9d10 | |||
| adba526e94 | |||
| 8e00b89996 | |||
| 20caf6d264 | |||
| 40788b8670 | |||
| 2819423b4b | |||
| d95f509efe | |||
| 8c309b8099 | |||
| 79df01f82a | |||
| a53161930e | |||
| a2d977ff78 | |||
| 5cae7247f0 | |||
| 31ad73b531 | |||
| e393339cbe | |||
| 212aad8130 | |||
| 3dba0931b4 | |||
| 8c64df87df | |||
| 83e18e27f3 | |||
| 12120f99d2 | |||
| 33d07a4fb9 | |||
| ef874a943f | |||
| 0ab4dd35fa | |||
| 7006c9469e | |||
| c443314868 | |||
| fe8ee4e8dd | |||
| 197b5ab51b | |||
| bbb06b2115 | |||
| a935786e2b | |||
| 5881a3f776 | |||
| ba43e83ac1 | |||
| 71ddbe1a64 | |||
| 56176a581e | |||
| 19b9d23952 | |||
| e25da63862 | |||
| 46720b30b6 | |||
| 194844a524 | |||
| a33b29e782 | |||
| e1e86e1bd3 | |||
| 19855ef7d6 | |||
| 18416da2c9 | |||
| b9a3fde6f6 | |||
| f3e947bf52 | |||
| 5caa966597 | |||
| e8db046289 | |||
| a0f46e9690 | |||
| ab5fa78611 | |||
| 848019d201 | |||
| 5bd5416c27 | |||
| 9f8cf622d8 | |||
| 4cfec6c46b | |||
| d73e3de57b | |||
| c259a01b91 | |||
| 6875be6435 | |||
| fd963a4e78 | |||
| ec9cd9bfa1 | |||
| 1aca2dd14e | |||
| 30ee6c6221 | |||
| 02d09e66d6 | |||
| b6d26bc057 | |||
| d74e9706f5 | |||
| 03cc28b773 | |||
| 7701ba02a8 | |||
| f4741427af | |||
| 6730ae4865 | |||
| bb450f7740 | |||
| 647be579f8 | |||
| a48f0a9733 | |||
| 9315fa8b5e | |||
| b25185b7b4 | |||
| 2d82325504 | |||
| a99394bc8f | |||
| 953fb2d278 | |||
| 6b09b96de3 | |||
| 4690b01042 | |||
| 8339be2098 | |||
| 2d0584b4cd | |||
| 6d6c5abd4a | |||
| b72cca9868 | |||
| 757c66cddb | |||
| c2932cb069 | |||
| 34416041c1 | |||
| 52d2b89148 | |||
| 05c4e1ba54 | |||
| b95bbf5900 | |||
| 11b347d0e7 | |||
| 44eeb01ec4 | |||
| b39f2b7e46 | |||
| 79e2915823 | |||
| 49004db027 | |||
| cf0f634c17 | |||
| 6a9ccd5ddb | |||
| e9f6ac48d9 | |||
| 3adc5bf75e | |||
| b9007f754a | |||
| cddfc53ab0 | |||
| b7c1e4db8e | |||
| 781779a1f8 | |||
| 0eae7d0aba | |||
| 9a17b25d32 | |||
| b6cf6874db | |||
| 30c0a9166e |
@@ -27,7 +27,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v7
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
cache: "pip"
|
cache: "pip"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v7
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v7
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ jobs:
|
|||||||
release-please:
|
release-please:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: googleapis/release-please-action@v4
|
- uses: googleapis/release-please-action@v5
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
config-file: release-please-config.json
|
config-file: release-please-config.json
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v7
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
cache: "pip"
|
cache: "pip"
|
||||||
@@ -51,7 +51,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v7
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
cache: "pip"
|
cache: "pip"
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v7
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -65,6 +65,7 @@ QWEN.md
|
|||||||
CLAUDE.md
|
CLAUDE.md
|
||||||
docs/COMPLETE_WORKFLOW.md
|
docs/COMPLETE_WORKFLOW.md
|
||||||
docs/SMART_STRATEGY_GEN.md
|
docs/SMART_STRATEGY_GEN.md
|
||||||
|
STARRED_REPOS_ANALYSIS.md
|
||||||
|
|
||||||
# OpenACP workspace (secrets)
|
# OpenACP workspace (secrets)
|
||||||
.openacp
|
.openacp
|
||||||
@@ -140,4 +141,4 @@ pickle_cache/
|
|||||||
RD-Agent_workspace_run*/
|
RD-Agent_workspace_run*/
|
||||||
AGENTS.md
|
AGENTS.md
|
||||||
CLAUDE.md
|
CLAUDE.md
|
||||||
.claude/
|
.claude/rdagent/components/coder/strategy_orchestrator.py
|
||||||
|
|||||||
+32
-6
@@ -1,19 +1,45 @@
|
|||||||
# Pre-commit hooks configuration for Predix
|
# Pre-commit hooks configuration for NexQuant
|
||||||
# See https://pre-commit.com for more information
|
# See https://pre-commit.com for more information
|
||||||
|
|
||||||
repos:
|
repos:
|
||||||
# ── Integration Tests (MANDATORY - MUST PASS before commit) ──────
|
# ── Test Coverage Check: new modules must have tests ──────────────
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: integration-tests
|
- id: check-test-coverage
|
||||||
name: Run Integration Tests (60 tests)
|
name: Check new rdagent modules have tests
|
||||||
|
entry: python scripts/check_test_coverage.py
|
||||||
|
language: system
|
||||||
|
pass_filenames: false
|
||||||
|
always_run: true
|
||||||
|
|
||||||
|
# ── MyPy Ratchet: no new type errors allowed ────────────────────
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: mypy-ratchet
|
||||||
|
name: MyPy ratchet (no new type errors)
|
||||||
|
entry: python scripts/check_mypy_ratchet.py
|
||||||
|
language: system
|
||||||
|
pass_filenames: false
|
||||||
|
always_run: true
|
||||||
|
|
||||||
|
# ── Qlib Unit Tests (MANDATORY) ──────────────────────────────────
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: qlib-unit-tests
|
||||||
|
name: Qlib Unit Tests (~490 tests)
|
||||||
entry: pytest
|
entry: pytest
|
||||||
language: system
|
language: system
|
||||||
args:
|
args:
|
||||||
- test/integration/test_all_features.py
|
- test/qlib/
|
||||||
|
- test/backtesting/
|
||||||
- -v
|
- -v
|
||||||
- --tb=short
|
- --tb=short
|
||||||
- --no-cov # Skip coverage for speed (run separately if needed)
|
- --cov=rdagent
|
||||||
|
- --cov-fail-under=33
|
||||||
|
- --cov-report=term
|
||||||
|
- --ignore=test/backtesting/test_ftmo_oos.py
|
||||||
|
- --ignore=test/backtesting/test_kronos_adapter.py
|
||||||
|
- --ignore=test/qlib/test_fin_quant_integration.py
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
always_run: true
|
always_run: true
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1 @@
|
|||||||
{
|
{".": "1.5.0"}
|
||||||
".": "1.3.5"
|
|
||||||
}
|
|
||||||
|
|||||||
+560
-124
@@ -1,153 +1,589 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [1.3.5](https://github.com/TPTBusiness/Predix/compare/v1.3.4...v1.3.5) (2026-04-27)
|
## [0.8.0](https://github.com/TPTBusiness/NexQuant/compare/v1.4.2...v0.8.0) (2026-05-04)
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **auto-fixer:** add five new factor code fixes for groupby/apply errors ([449c8fd](https://github.com/TPTBusiness/Predix/commit/449c8fd70a327e604dcca122e4a134f0cca918e4))
|
|
||||||
* **auto-fixer:** add four new factor code fixes for common runtime errors ([40484f6](https://github.com/TPTBusiness/Predix/commit/40484f6d300425da481f1edd325da4acbc06ec7d))
|
|
||||||
* **auto-fixer:** add groupby([level=N,'date']) SyntaxError fix ([ca77c00](https://github.com/TPTBusiness/Predix/commit/ca77c005bea4abdd8854c1de2b0e8d03b7742161))
|
|
||||||
* **auto-fixer:** disable _fix_min_periods for intraday data ([77b0740](https://github.com/TPTBusiness/Predix/commit/77b0740f059349df7e769a378af728aa33b2070e))
|
|
||||||
* **auto-fixer:** fix chained groupby(level=N).groupby('date') pattern ([7d5fe32](https://github.com/TPTBusiness/Predix/commit/7d5fe32b31a19ce8b04bd8f5a430720fdb748f7a))
|
|
||||||
* **auto-fixer:** fix df.loc[instrument] DateParseError on MultiIndex frames ([b7860ea](https://github.com/TPTBusiness/Predix/commit/b7860eafc0ad26384947ce0510ecf4e9f3425807))
|
|
||||||
* **auto-fixer:** fix df['instrument'] KeyError on MultiIndex frames ([aad6bd1](https://github.com/TPTBusiness/Predix/commit/aad6bd1c7c720b3d486e0cf248337f32394773b1))
|
|
||||||
* **auto-fixer:** fix two assignment-target bugs in instrument column fixers ([421eedf](https://github.com/TPTBusiness/Predix/commit/421eedffed4b883c24397dc5581c019a3985277f))
|
|
||||||
* **auto-fixer:** preserve date dimension in groupby(['instrument','date']) fix ([b58fdd8](https://github.com/TPTBusiness/Predix/commit/b58fdd8be43720b5d4363e0f8de9a01591d4d2dc))
|
|
||||||
* **auto-fixer:** remove ddof from rolling() args, not only from std()/var() ([b0fc328](https://github.com/TPTBusiness/Predix/commit/b0fc328d0d4a041c65d8eeb32cb3f2bb86568406))
|
|
||||||
* **auto-fixer:** strip spurious .reset_index() after .transform() calls ([8708aae](https://github.com/TPTBusiness/Predix/commit/8708aae6e08728cda1875c775a76dc92e43576f3))
|
|
||||||
* **loop:** prevent step_idx advance on unhandled exceptions + fix consecutive assistant messages ([5ec4ad1](https://github.com/TPTBusiness/Predix/commit/5ec4ad1b96b5b99ef42bea7bb828cb1ef709a688))
|
|
||||||
|
|
||||||
## [1.3.4](https://github.com/TPTBusiness/Predix/compare/v1.3.3...v1.3.4) (2026-04-27)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **auto-fixer:** add five new factor code fixes for groupby/apply errors ([449c8fd](https://github.com/TPTBusiness/Predix/commit/449c8fd70a327e604dcca122e4a134f0cca918e4))
|
|
||||||
* **auto-fixer:** add four new factor code fixes for common runtime errors ([40484f6](https://github.com/TPTBusiness/Predix/commit/40484f6d300425da481f1edd325da4acbc06ec7d))
|
|
||||||
* **auto-fixer:** add groupby([level=N,'date']) SyntaxError fix ([ca77c00](https://github.com/TPTBusiness/Predix/commit/ca77c005bea4abdd8854c1de2b0e8d03b7742161))
|
|
||||||
* **auto-fixer:** disable _fix_min_periods for intraday data ([77b0740](https://github.com/TPTBusiness/Predix/commit/77b0740f059349df7e769a378af728aa33b2070e))
|
|
||||||
* **auto-fixer:** fix chained groupby(level=N).groupby('date') pattern ([7d5fe32](https://github.com/TPTBusiness/Predix/commit/7d5fe32b31a19ce8b04bd8f5a430720fdb748f7a))
|
|
||||||
* **auto-fixer:** fix df.loc[instrument] DateParseError on MultiIndex frames ([b7860ea](https://github.com/TPTBusiness/Predix/commit/b7860eafc0ad26384947ce0510ecf4e9f3425807))
|
|
||||||
* **auto-fixer:** fix df['instrument'] KeyError on MultiIndex frames ([aad6bd1](https://github.com/TPTBusiness/Predix/commit/aad6bd1c7c720b3d486e0cf248337f32394773b1))
|
|
||||||
* **auto-fixer:** preserve date dimension in groupby(['instrument','date']) fix ([b58fdd8](https://github.com/TPTBusiness/Predix/commit/b58fdd8be43720b5d4363e0f8de9a01591d4d2dc))
|
|
||||||
* **auto-fixer:** remove ddof from rolling() args, not only from std()/var() ([b0fc328](https://github.com/TPTBusiness/Predix/commit/b0fc328d0d4a041c65d8eeb32cb3f2bb86568406))
|
|
||||||
* **backtest:** replace broken MC permutation test with binomial win-rate test ([c38d894](https://github.com/TPTBusiness/Predix/commit/c38d89478f586825bfca5715a96ca70ccd8791a3))
|
|
||||||
* **factors:** detect and correct look-ahead bias in daily-constant factors ([eb490a4](https://github.com/TPTBusiness/Predix/commit/eb490a461b66cbd815ae53ac5205115754712432))
|
|
||||||
* **factors:** extend look-ahead rules to session factors and add intraday-factor guidance ([c24c100](https://github.com/TPTBusiness/Predix/commit/c24c100442d6487686c0578de0b32d240fcbf215))
|
|
||||||
* **loop:** compress old experiment history in proposal prompt to reduce context size ([4bf90a9](https://github.com/TPTBusiness/Predix/commit/4bf90a905ba8b2aba2a818191c19998088cccaaf))
|
|
||||||
* **loop:** prevent step_idx advance on unhandled exceptions + fix consecutive assistant messages ([5ec4ad1](https://github.com/TPTBusiness/Predix/commit/5ec4ad1b96b5b99ef42bea7bb828cb1ef709a688))
|
|
||||||
|
|
||||||
## [1.3.3](https://github.com/TPTBusiness/Predix/compare/v1.3.2...v1.3.3) (2026-04-25)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **backtest:** replace broken MC permutation test with binomial win-rate test ([c38d894](https://github.com/TPTBusiness/Predix/commit/c38d89478f586825bfca5715a96ca70ccd8791a3))
|
|
||||||
* **factors:** detect and correct look-ahead bias in daily-constant factors ([eb490a4](https://github.com/TPTBusiness/Predix/commit/eb490a461b66cbd815ae53ac5205115754712432))
|
|
||||||
* **factors:** extend look-ahead rules to session factors and add intraday-factor guidance ([c24c100](https://github.com/TPTBusiness/Predix/commit/c24c100442d6487686c0578de0b32d240fcbf215))
|
|
||||||
* **loop:** compress old experiment history in proposal prompt to reduce context size ([4bf90a9](https://github.com/TPTBusiness/Predix/commit/4bf90a905ba8b2aba2a818191c19998088cccaaf))
|
|
||||||
* **strategies:** guard against None IC in acceptance check, disable slow wf_rolling ([2197f52](https://github.com/TPTBusiness/Predix/commit/2197f52150a50ef38d9e70991d7e48c8c30caec4))
|
|
||||||
* **strategies:** handle None ic/sharpe/dd in rejected strategy log output ([ad2ad3a](https://github.com/TPTBusiness/Predix/commit/ad2ad3ab3360ea75ed3bbc90c12098b9c5cc0114))
|
|
||||||
|
|
||||||
## [1.3.2](https://github.com/TPTBusiness/Predix/compare/v1.3.1...v1.3.2) (2026-04-23)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **strategies:** guard against None IC in acceptance check, disable slow wf_rolling ([2197f52](https://github.com/TPTBusiness/Predix/commit/2197f52150a50ef38d9e70991d7e48c8c30caec4))
|
|
||||||
* **strategies:** handle None ic/sharpe/dd in rejected strategy log output ([ad2ad3a](https://github.com/TPTBusiness/Predix/commit/ad2ad3ab3360ea75ed3bbc90c12098b9c5cc0114))
|
|
||||||
|
|
||||||
## [1.3.1](https://github.com/TPTBusiness/Predix/compare/v1.3.0...v1.3.1) (2026-04-21)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **deps:** bump python-dotenv to >=1.2.2 (CVE symlink overwrite) ([126ae7d](https://github.com/TPTBusiness/Predix/commit/126ae7d5fb556b677d09d10221862a0d648d697a))
|
|
||||||
|
|
||||||
## [1.3.0](https://github.com/TPTBusiness/Predix/compare/v1.2.2...v1.3.0) (2026-04-21)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* **backtest:** add rolling walk-forward validation and Monte Carlo trade permutation test ([637a94c](https://github.com/TPTBusiness/Predix/commit/637a94c1d987da763869f4f9b73372a3f37d873c))
|
* [AutoRL-Bench] Update DeepSearchQA split and translate task instructions to English ([#1368](https://github.com/TPTBusiness/NexQuant/issues/1368)) ([ffb9491](https://github.com/TPTBusiness/NexQuant/commit/ffb9491c4703290a5b292baa6328ae06bc520f9b))
|
||||||
|
* Add 'nexquant evaluate' command to CLI ([4308c25](https://github.com/TPTBusiness/NexQuant/commit/4308c257e7c83ab8ec5ef0a719b040f936bad0b3))
|
||||||
|
* Add 'nexquant top' command + explain factor evaluation results ([ac3334c](https://github.com/TPTBusiness/NexQuant/commit/ac3334c17d8dce48a5081e45d407ccadedfec713))
|
||||||
|
* Add 6 new CLI commands - all scripts integrated with local LLM ([e0dd07a](https://github.com/TPTBusiness/NexQuant/commit/e0dd07aa99ce33c2fc050d3d40b4520f245adb90))
|
||||||
|
* add a rag mcp in proposal ([#1267](https://github.com/TPTBusiness/NexQuant/issues/1267)) ([dc7b732](https://github.com/TPTBusiness/NexQuant/commit/dc7b732b2c428e3cca3373e839a0e724a844c79b))
|
||||||
|
* add a web UI server ([#1345](https://github.com/TPTBusiness/NexQuant/issues/1345)) ([1439548](https://github.com/TPTBusiness/NexQuant/commit/14395488b9c7ea476022a32211ea46de9925cf11))
|
||||||
|
* Add advanced ML models (Transformer, TCN, PatchTST, CNN+LSTM) ([44760f8](https://github.com/TPTBusiness/NexQuant/commit/44760f83c3d3d38033f5d94f4ba37dc0c25b7f59))
|
||||||
|
* Add AI Strategy Builder (StrategyCoSTEER) - Closed Source ([089189d](https://github.com/TPTBusiness/NexQuant/commit/089189d8ec058edefd0b81c2689b54f5180b9052))
|
||||||
|
* Add beautiful CLI welcome screen for GitHub README ([9e4a97d](https://github.com/TPTBusiness/NexQuant/commit/9e4a97d3d7e6d5328c4ffa39ce833591f10ab731))
|
||||||
|
* Add CLI model selection (local vs OpenRouter) ([c37935a](https://github.com/TPTBusiness/NexQuant/commit/c37935aa8c108a6bca393bcda274cda148101456))
|
||||||
|
* Add complete ML pipeline with graceful degradation (closed source) ([ed6b906](https://github.com/TPTBusiness/NexQuant/commit/ed6b906248ac3068a4f188d01bcde403e93abc0c))
|
||||||
|
* add daily log rotation, llama health wait, factor auto-fixer, and README updates ([2238fed](https://github.com/TPTBusiness/NexQuant/commit/2238fed701bd8a6ab1da1d3614d1c6d501e1ecbc))
|
||||||
|
* Add factor code and description to saved results ([b6b378d](https://github.com/TPTBusiness/NexQuant/commit/b6b378da8abf6f15be0c91e83508dc21d27b5b14))
|
||||||
|
* Add GitHub infrastructure, CI/CD pipelines, and examples ([26bd87e](https://github.com/TPTBusiness/NexQuant/commit/26bd87ed0a13da7190c8481356574bb710d00772))
|
||||||
|
* add improve_mode to MultiProcessEvolvingStrategy for selective task implementation ([#1273](https://github.com/TPTBusiness/NexQuant/issues/1273)) ([03f22dc](https://github.com/TPTBusiness/NexQuant/commit/03f22dc7c72a039ee6f1a0e8d0393f35117ec3e1))
|
||||||
|
* Add improved local prompt with MultiIndex code examples (v3) ([a729eb7](https://github.com/TPTBusiness/NexQuant/commit/a729eb715353961f71e92ddb679406c3c30b83d3))
|
||||||
|
* add Kronos CLI commands, expand tests, document in README ([24a51e4](https://github.com/TPTBusiness/NexQuant/commit/24a51e4322ef80d5f882697a930f1d1985aa5779))
|
||||||
|
* add LLM-finetune scenario ([#1314](https://github.com/TPTBusiness/NexQuant/issues/1314)) ([6e19c9e](https://github.com/TPTBusiness/NexQuant/commit/6e19c9e632cf07059c19993f2d4fbc772fb3cf13))
|
||||||
|
* add mask inference in debug mode ([#1154](https://github.com/TPTBusiness/NexQuant/issues/1154)) ([b4117cf](https://github.com/TPTBusiness/NexQuant/commit/b4117cf58a5618e1d9e92abb46e1c1dd98af5f13))
|
||||||
|
* Add model loader system (same as prompts) ([b7e397b](https://github.com/TPTBusiness/NexQuant/commit/b7e397b6f271e2cab5312f597cfbcb9652472298))
|
||||||
|
* add option to enable hyperparameter tuning only in first eval loop ([#1211](https://github.com/TPTBusiness/NexQuant/issues/1211)) ([f82de4a](https://github.com/TPTBusiness/NexQuant/commit/f82de4a380fa31a04a8494b196a743333aadf096))
|
||||||
|
* Add P5 ML Training Pipeline with LightGBM and 46 tests ([c934276](https://github.com/TPTBusiness/NexQuant/commit/c9342761ff8ab9adef69b65eb4cd8f206327fc97))
|
||||||
|
* Add parallel run system with API key distribution ([31fb7d5](https://github.com/TPTBusiness/NexQuant/commit/31fb7d56e3b6530091bef2c16e057a249caf4a93))
|
||||||
|
* add previous runner loops to runner history ([#1142](https://github.com/TPTBusiness/NexQuant/issues/1142)) ([2426a1d](https://github.com/TPTBusiness/NexQuant/commit/2426a1dc6700cc208360944cead9214a3da04889))
|
||||||
|
* add reasoning attribute to DSRunnerFeedback for enhanced evaluation context ([#1162](https://github.com/TPTBusiness/NexQuant/issues/1162)) ([bfa4525](https://github.com/TPTBusiness/NexQuant/commit/bfa452541c1422c02f77491e70927ce43f21810c))
|
||||||
|
* Add RL Trading Agent system with 99 tests ([0c4cb7a](https://github.com/TPTBusiness/NexQuant/commit/0c4cb7ad0c9842dd8fb73454bf554e9bedaf72f5))
|
||||||
|
* add runtime backtest verification (10 invariant checks in <1ms) + 489 tests + README docs ([26db657](https://github.com/TPTBusiness/NexQuant/commit/26db65736431313bcdc27b6defde625db4133516))
|
||||||
|
* add show_hard_limit option and update time limit handling in DataScience settings ([#1144](https://github.com/TPTBusiness/NexQuant/issues/1144)) ([8a3e42d](https://github.com/TPTBusiness/NexQuant/commit/8a3e42d7fe8c36324c7578ede661297f2af59a37))
|
||||||
|
* Add simple factor evaluator with direct IC/Sharpe computation ([c7f23d0](https://github.com/TPTBusiness/NexQuant/commit/c7f23d026419060df3fcb3748740df8cc594bf39))
|
||||||
|
* Add start_llama and start_loop CLI commands ([c1d1844](https://github.com/TPTBusiness/NexQuant/commit/c1d184442aac79ca69b1e366bff7311973459869))
|
||||||
|
* add stdout into workspace for easier debugging ([#1236](https://github.com/TPTBusiness/NexQuant/issues/1236)) ([0daeb82](https://github.com/TPTBusiness/NexQuant/commit/0daeb82d6330e46edfeedc6b704b1a1c01d1a111))
|
||||||
|
* add time ratio limit for hyperparameter tuning in Kaggle settin… ([#1135](https://github.com/TPTBusiness/NexQuant/issues/1135)) ([6a49981](https://github.com/TPTBusiness/NexQuant/commit/6a4998154d000d95d7a5ec7cfb5e59305d4cbd11))
|
||||||
|
* Add Trading Protection System with 4 protections + comprehensive tests ([a9e0eff](https://github.com/TPTBusiness/NexQuant/commit/a9e0eff35d07c5b5223f64af343f8d2ece8d0053))
|
||||||
|
* add user interaction in data science scenario ([#1251](https://github.com/TPTBusiness/NexQuant/issues/1251)) ([6e09dc6](https://github.com/TPTBusiness/NexQuant/commit/6e09dc6d692f3ae2fcc0ffddf620e8f3e8dc1bd9))
|
||||||
|
* Auto-start dashboard for fin_quant ([3441604](https://github.com/TPTBusiness/NexQuant/commit/34416041c122b6a51ce94db1031f315c3639a4a5))
|
||||||
|
* Auto-start dashboard for fin_quant ([52d2b89](https://github.com/TPTBusiness/NexQuant/commit/52d2b8914815fa97d6b53b7cc7e817828520817e))
|
||||||
|
* **backtest:** add FTMO-realistic backtest mode with leverage, daily/total loss limits and realistic EUR/USD costs ([c5012e1](https://github.com/TPTBusiness/NexQuant/commit/c5012e1a1c7e5cff6c82bc42bd0ba34affb75c10))
|
||||||
|
* **backtest:** add rolling walk-forward validation and Monte Carlo trade permutation test ([d284d3e](https://github.com/TPTBusiness/NexQuant/commit/d284d3e74610c5f8ed314fa870cfb7f28a7681d4))
|
||||||
|
* **backtest:** add walk-forward OOS validation to backtest_signal_ftmo ([329841f](https://github.com/TPTBusiness/NexQuant/commit/329841f05a64ee9cdbaced2c4ec4de9436d3d42a))
|
||||||
|
* Backtesting Engine + Risk Management + Results Database ([cce889a](https://github.com/TPTBusiness/NexQuant/commit/cce889a1b7ee58f0042bc6c8cf01f5631ad45fa7))
|
||||||
|
* Backtesting Engine + Risk Management + Results DB ([86ef426](https://github.com/TPTBusiness/NexQuant/commit/86ef4269a350535871cb2f3f80d4d8e9e5c9258f))
|
||||||
|
* **backtest:** use backtest_signal_ftmo in strategy orchestrator and optuna optimizer ([994080e](https://github.com/TPTBusiness/NexQuant/commit/994080ef36e572f688b1d3cc219170bb340fc175))
|
||||||
|
* Beautiful CLI dashboard + corrected start command ([c2932cb](https://github.com/TPTBusiness/NexQuant/commit/c2932cb06904b041e1376d534309864d9d0e9122))
|
||||||
|
* Centralize all prompts in prompts/ directory ([3ff1ef8](https://github.com/TPTBusiness/NexQuant/commit/3ff1ef8557ef41d96b48c43efc2fe5795869fed0))
|
||||||
|
* CLI Commands for strategy generation (P4 complete) ([1f7ef1b](https://github.com/TPTBusiness/NexQuant/commit/1f7ef1b86f46153ff6e6cbde77e01c1ae08b905f))
|
||||||
|
* Complete P6-P9 implementation (73 tests) ([6981e91](https://github.com/TPTBusiness/NexQuant/commit/6981e9141d1f1f0951647971c10c1b9db227134a))
|
||||||
|
* continuous strategy generator (WF, MTF, stability, ML models, auto-ensemble) ([a206a31](https://github.com/TPTBusiness/NexQuant/commit/a206a31dbb831d6deed0492b73a9e246634fe074))
|
||||||
|
* create Jupyter notebook pipeline file based on main.py file ([#1134](https://github.com/TPTBusiness/NexQuant/issues/1134)) ([f03b1b9](https://github.com/TPTBusiness/NexQuant/commit/f03b1b918d32ec5a0ace1443d9f22e0c0598b2fc))
|
||||||
|
* Data Loader module with tests (P0 complete) ([af45cdf](https://github.com/TPTBusiness/NexQuant/commit/af45cdf074d7c3df02c535728ac55e69f214f1e3))
|
||||||
|
* Diverse factor selection + improved prompt v3 ([ea47f75](https://github.com/TPTBusiness/NexQuant/commit/ea47f75eda41398699f376219ec2c883c9d67798))
|
||||||
|
* enable finetune llm ([#1055](https://github.com/TPTBusiness/NexQuant/issues/1055)) ([35c209b](https://github.com/TPTBusiness/NexQuant/commit/35c209b09295d28d6d835c720fa1d300bdf43d13))
|
||||||
|
* enable LLM‑based hypothesis selection with time‑aware prompt & colored logging ([#1122](https://github.com/TPTBusiness/NexQuant/issues/1122)) ([90dd2f7](https://github.com/TPTBusiness/NexQuant/commit/90dd2f7b9bf49f5e1620e9d2c2eedf6c21f3e839))
|
||||||
|
* enable to inject diversity cross async multi-trace ([#1173](https://github.com/TPTBusiness/NexQuant/issues/1173)) ([b05a530](https://github.com/TPTBusiness/NexQuant/commit/b05a53012603c21847803e4709da10c5b868cab6))
|
||||||
|
* enable walk-forward OOS validation by default in backtest_signal_ftmo ([8853f8e](https://github.com/TPTBusiness/NexQuant/commit/8853f8e8e14ddabe510cb0ca271092f965b5ea81))
|
||||||
|
* enhance timeout handling in CoSTEER and DataScience scenarios ([#1150](https://github.com/TPTBusiness/NexQuant/issues/1150)) ([811d4e7](https://github.com/TPTBusiness/NexQuant/commit/811d4e7631dc83f228cd96a2a498803db46256a9))
|
||||||
|
* enhance timeout management and knowledge base handling in CoSTEER components ([#1130](https://github.com/TPTBusiness/NexQuant/issues/1130)) ([305eff1](https://github.com/TPTBusiness/NexQuant/commit/305eff1c5e36f3da5e93dc165105f50ccb990e32))
|
||||||
|
* EURUSD FX patches - prompts, factor spec, experiment settings ([b6cf687](https://github.com/TPTBusiness/NexQuant/commit/b6cf6874db995ea160457a1628a5691cbc8e5b97))
|
||||||
|
* EURUSD model experiment setting + model simulator text patched ([9a17b25](https://github.com/TPTBusiness/NexQuant/commit/9a17b25d32729453a28dd36246be4c5fdbd3a667))
|
||||||
|
* EURUSD Trading-Verbesserungen (Phase 2 & 3) ([05c4e1b](https://github.com/TPTBusiness/NexQuant/commit/05c4e1ba54b9259d6cc5f0af00a177d9295278a9))
|
||||||
|
* EURUSD Trading-Verbesserungen implementiert (Phase 1) ([b95bbf5](https://github.com/TPTBusiness/NexQuant/commit/b95bbf5900a9e06194ab0e330b662e2b853006ea))
|
||||||
|
* EURUSD walk-forward splits, bars terminology, README no $factor ([0eae7d0](https://github.com/TPTBusiness/NexQuant/commit/0eae7d0ababb422927dd0123118b97724d066ab0))
|
||||||
|
* **factor-coder:** Add critical rules to prevent common factor implementation errors ([e5c5d34](https://github.com/TPTBusiness/NexQuant/commit/e5c5d34eb5d38dd4bd18e9cd06026ba0e5a43344))
|
||||||
|
* fallback to acceptable results ([#1129](https://github.com/TPTBusiness/NexQuant/issues/1129)) ([7fc0916](https://github.com/TPTBusiness/NexQuant/commit/7fc09169bc5a779eeb650b799a43a36b44930a61))
|
||||||
|
* Fast mode - CoSTEER goes to backtest after 1 iteration ([fc830a2](https://github.com/TPTBusiness/NexQuant/commit/fc830a23bd31a53dab188847b10bf60430d396a8))
|
||||||
|
* **fin_quant:** auto-generate Kronos factor before loop start ([0daf7a8](https://github.com/TPTBusiness/NexQuant/commit/0daf7a8d2bdddd98a0c7d00959a39d4a38084a21))
|
||||||
|
* Fix 1min data integration and centralize all prompts ([2e94a4c](https://github.com/TPTBusiness/NexQuant/commit/2e94a4ce72cd9d0a01eef38c40ce70db1d158bb2))
|
||||||
|
* Fix realistic backtesting (Step 1+2) ([9b88ffb](https://github.com/TPTBusiness/NexQuant/commit/9b88ffbbd695d9486f25631ecf7f92457a23f6fc))
|
||||||
|
* Full auto strategy generation in fin_quant loop ([6d2990d](https://github.com/TPTBusiness/NexQuant/commit/6d2990dfff103e0cb85c0edd092457333d00c19e))
|
||||||
|
* Full system integration - RL + Protections + Backtesting + CLI ([60618d9](https://github.com/TPTBusiness/NexQuant/commit/60618d90f730470b7a9c57bf70c6f9fc45c36ad5))
|
||||||
|
* FX feedback loop, EURUSD ticker examples, bars terminology ([781779a](https://github.com/TPTBusiness/NexQuant/commit/781779a1f8c853eb77253053e23bc10c46dcf402))
|
||||||
|
* FX Multi-Agent Validator (TradingAgents-inspired) - Session/Macro/Bull-Bear/Trader ([cddfc53](https://github.com/TPTBusiness/NexQuant/commit/cddfc53ab07ca75b2364c30b9c2a794383633c2b))
|
||||||
|
* improve fallback handling in CoSTEER and add GPU usage guidelin… ([#1165](https://github.com/TPTBusiness/NexQuant/issues/1165)) ([9c190e3](https://github.com/TPTBusiness/NexQuant/commit/9c190e3268b4515945dcf5531dbaa222e843ceef))
|
||||||
|
* Improve nexquant portfolio command with robust error handling ([5051527](https://github.com/TPTBusiness/NexQuant/commit/505152793fe4a1629fa9ecdd8dc03ceb9bcd5db9))
|
||||||
|
* Improved LLM prompt + Optuna integration (Step 3+5) ([f72b07c](https://github.com/TPTBusiness/NexQuant/commit/f72b07ca94acd2b004f4a5b99faa8bb9ca1c7c76))
|
||||||
|
* init pydantic ai agent & context 7 mcp ([#1240](https://github.com/TPTBusiness/NexQuant/issues/1240)) ([5ba5e83](https://github.com/TPTBusiness/NexQuant/commit/5ba5e8356cbacb5e4bd9f24b26d6f9ac01784822))
|
||||||
|
* Integrate critical features into fin_quant workflow (P0+P1) ([484377b](https://github.com/TPTBusiness/NexQuant/commit/484377bc6dbe3bb216b1ebebb54978db371971cb))
|
||||||
|
* Integrate factor code/description saving into fin_quant process ([3b502e9](https://github.com/TPTBusiness/NexQuant/commit/3b502e9faeab4c7bbd185c9b107b7026b57330f0))
|
||||||
|
* integrate Kronos-mini OHLCV foundation model (Option A + B) ([165c156](https://github.com/TPTBusiness/NexQuant/commit/165c15684c7efe3db7de80b67eb301384d926739))
|
||||||
|
* Intelligent embedding chunking instead of truncation ([2d0584b](https://github.com/TPTBusiness/NexQuant/commit/2d0584b4cd7c1b3d9623acd6e141035d51f535fa))
|
||||||
|
* **logging:** write complete LLM prompts and responses to daily JSONL log ([1f83410](https://github.com/TPTBusiness/NexQuant/commit/1f83410fdd7e242b6cf4eb3aac045d8e6e6b7c70))
|
||||||
|
* **mcp:** cache with one-click toggle ([#1269](https://github.com/TPTBusiness/NexQuant/issues/1269)) ([4f493c8](https://github.com/TPTBusiness/NexQuant/commit/4f493c8d637dfda42f84af0dc08f8ecfc0501668))
|
||||||
|
* mcts policy based on trace scheduler ([#1203](https://github.com/TPTBusiness/NexQuant/issues/1203)) ([ac6d8ed](https://github.com/TPTBusiness/NexQuant/commit/ac6d8edad4366b08b5caf75e9a5ee8da0061a078))
|
||||||
|
* migrate to 1min EURUSD data (2020-2026) ([b39f2b7](https://github.com/TPTBusiness/NexQuant/commit/b39f2b7e46384c4fc56c1274c9120c470313262b))
|
||||||
|
* ML Training Pipeline with 46 tests (P5 complete) ([8f2aa83](https://github.com/TPTBusiness/NexQuant/commit/8f2aa8341932327dba5e260645bcf96efd5ed548))
|
||||||
|
* offline selector ([#1231](https://github.com/TPTBusiness/NexQuant/issues/1231)) ([d4c5399](https://github.com/TPTBusiness/NexQuant/commit/d4c539912abdb60e9d8950e7ea1186fd32bfeef3))
|
||||||
|
* optimize strategy generator (cache OHLCV, min_sharpe 1.5, nexquant generate-strategies CLI) ([def3975](https://github.com/TPTBusiness/NexQuant/commit/def39755793b16920c877045dd6628cb6a9aa9e8))
|
||||||
|
* **optimizer:** add max_positions parameter to Optuna search space ([f7b23b9](https://github.com/TPTBusiness/NexQuant/commit/f7b23b950f8f59b1b2efa66664ac2180ce136410))
|
||||||
|
* Optuna Parameter Optimizer with 60 tests (P3 complete) ([5583bf8](https://github.com/TPTBusiness/NexQuant/commit/5583bf874ed36886fa0d24e3472b8062abbd0b86))
|
||||||
|
* PDF performance reports for strategies (reportlab) ([b86e412](https://github.com/TPTBusiness/NexQuant/commit/b86e41209cd41e02de4ad3de3281b6558fdad059))
|
||||||
|
* nexquant.py wrapper for dashboard support ([757c66c](https://github.com/TPTBusiness/NexQuant/commit/757c66cddb18254220db1d571d9b739380c57f44))
|
||||||
|
* prob-based trace scheduler ([#1131](https://github.com/TPTBusiness/NexQuant/issues/1131)) ([7e15b5e](https://github.com/TPTBusiness/NexQuant/commit/7e15b5e2003628f40be12674a73197a956d86545))
|
||||||
|
* Realistic backtesting with OHLCV data (P5 continued) ([1506439](https://github.com/TPTBusiness/NexQuant/commit/1506439a1950a2e87cd662dfeec9e8b5fa1baf20))
|
||||||
|
* Realistic backtesting with OHLCV data and spread costs ([85a1e29](https://github.com/TPTBusiness/NexQuant/commit/85a1e2929acf0ea0f582a66f6261dd697f0260db))
|
||||||
|
* Redirect RD-Agent workspace to results/ directory ([fd2def0](https://github.com/TPTBusiness/NexQuant/commit/fd2def052a02e0f818a7cc705bdc2caaee2f01d2))
|
||||||
|
* refactor CoSTEER classes to use DSCoSTEER and update max seconds handling ([#1156](https://github.com/TPTBusiness/NexQuant/issues/1156)) ([c111966](https://github.com/TPTBusiness/NexQuant/commit/c111966d1975a4952c1266fb6d6af1c4f5fe83c1))
|
||||||
|
* refine the logic of enabling hyperparameter tuning and add criteira ([#1175](https://github.com/TPTBusiness/NexQuant/issues/1175)) ([e77572f](https://github.com/TPTBusiness/NexQuant/commit/e77572fb5347e40506fb7b5b25dd861e5f9ebb2b))
|
||||||
|
* **rl:** add AutoRL-Bench framework and benchmark integrations ([#1348](https://github.com/TPTBusiness/NexQuant/issues/1348)) ([7cd64a2](https://github.com/TPTBusiness/NexQuant/commit/7cd64a26fd84017042eb163e8eb4d3bd30c16de7))
|
||||||
|
* Save all factor results to results/factors/ ([2abbec9](https://github.com/TPTBusiness/NexQuant/commit/2abbec9fde67f52bcf1f199e7d18f7d99f04805e))
|
||||||
|
* Save factor results immediately after each evaluation ([72c5ec5](https://github.com/TPTBusiness/NexQuant/commit/72c5ec55f20964917fe9ed21a77f80e0394f61e8))
|
||||||
|
* **scripts:** add full file logging to strategy generation and rebacktest scripts ([c629af5](https://github.com/TPTBusiness/NexQuant/commit/c629af5b19df26330a131f510154fb5543709a66))
|
||||||
|
* show the summarized final difference between the final workspace and the base workspace ([#1281](https://github.com/TPTBusiness/NexQuant/issues/1281)) ([35a7ae5](https://github.com/TPTBusiness/NexQuant/commit/35a7ae5e1ff929b3ee3b77c04cb1f4a684a4b2d7))
|
||||||
|
* **strategies:** make OOS validation mandatory in strategy generator ([0f4c7c4](https://github.com/TPTBusiness/NexQuant/commit/0f4c7c4f46d4fd2fb8ff7c4b1eea58538c7db1b3))
|
||||||
|
* Strategy Generator working with local LLM (P0-P4) ([036edee](https://github.com/TPTBusiness/NexQuant/commit/036edeeb77d1a99a0a748a357038c6da3efdd5e7))
|
||||||
|
* Strategy Orchestrator with 30 tests (P2 complete) ([9af5cdb](https://github.com/TPTBusiness/NexQuant/commit/9af5cdbde4996b05a98e59c5c577e487e2d535bd))
|
||||||
|
* Strategy performance reports, CLI docs, and README update ([232e918](https://github.com/TPTBusiness/NexQuant/commit/232e918b48eabeed22e3b712048fb96089b99067))
|
||||||
|
* Strategy Worker module with 41 tests (P1 complete) ([b8acf82](https://github.com/TPTBusiness/NexQuant/commit/b8acf82ed26ffd131ca32bf5272547ff11bd5eef))
|
||||||
|
* **strategy:** Continuous optimization with Optuna parameter injection ([da90ae2](https://github.com/TPTBusiness/NexQuant/commit/da90ae271e46260910023f8a9e3798365b80b298))
|
||||||
|
* streamline hyperparameter tuning checks and update evaluation g… ([#1167](https://github.com/TPTBusiness/NexQuant/issues/1167)) ([5866230](https://github.com/TPTBusiness/NexQuant/commit/586623084f5d59d88645e75ceab6d795ec497cab))
|
||||||
|
* Support 25+ parallel runs with resource warnings ([7a4dd1a](https://github.com/TPTBusiness/NexQuant/commit/7a4dd1aa7454560d84993ee8827e005ee0795c37))
|
||||||
|
* ui, support disable cache ([#1217](https://github.com/TPTBusiness/NexQuant/issues/1217)) ([70fd91c](https://github.com/TPTBusiness/NexQuant/commit/70fd91cd051b2006df876ef6aa47a616058af95f))
|
||||||
|
* unified backtest engine, LLM error handling, strategy refactor ([1ddb114](https://github.com/TPTBusiness/NexQuant/commit/1ddb1142a2f21ed3a498292ac8f5af6bbc351e7c))
|
||||||
|
* update README with latest paper acceptance to NeurIPS 2025 ([#1252](https://github.com/TPTBusiness/NexQuant/issues/1252)) ([12969b4](https://github.com/TPTBusiness/NexQuant/commit/12969b491eafab626ce71f7e530458dab6f43246))
|
||||||
|
* zentrale data_config.yaml + apply_config.py für dynamische Datenkonfiguration ([b7c1e4d](https://github.com/TPTBusiness/NexQuant/commit/b7c1e4db8e29e960fe28393911d60fc0fd3ca413))
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* **security:** resolve all 30 Bandit security alerts (B301, B614, B104) ([ce5983d](https://github.com/TPTBusiness/Predix/commit/ce5983d9d59c4c34341fb1ec749e44bbcfc4a1c4))
|
* (to main) litellm's Timeout error is not picklable ([#1294](https://github.com/TPTBusiness/NexQuant/issues/1294)) ([315850e](https://github.com/TPTBusiness/NexQuant/commit/315850ea81761aa2478639ad32302d7a55f8181b))
|
||||||
|
* 15 bug fixes across orchestrator, runner, backtest, and infrastructure ([5ec4516](https://github.com/TPTBusiness/NexQuant/commit/5ec4516ed7bdc44f2fd7d6e3ec9df0a88fc4fd10))
|
||||||
## [1.2.2](https://github.com/TPTBusiness/Predix/compare/v1.2.1...v1.2.2) (2026-04-19)
|
* add a switch for ensemble_time_upper_bound and fix some bug in main ([#1226](https://github.com/TPTBusiness/NexQuant/issues/1226)) ([fc18942](https://github.com/TPTBusiness/NexQuant/commit/fc18942339b3ca59077ddc903f84b2d54193e5bc))
|
||||||
|
* Add Bandit security scanning and fix critical vulnerabilities ([f47dcf1](https://github.com/TPTBusiness/NexQuant/commit/f47dcf1c58d33041bba2f705b270a7f9c4e7d572))
|
||||||
|
* Add critical column name rules to factor generation prompt ([bf73725](https://github.com/TPTBusiness/NexQuant/commit/bf7372533e83da682f1ceefeddc70f142f8ccda2))
|
||||||
### Documentation
|
* Add get_factor_count() to QuantTrace to prevent parallel run crashes ([a16db77](https://github.com/TPTBusiness/NexQuant/commit/a16db77def1ba7adb7bb6734629086a1b5a901cb))
|
||||||
|
* add json format response fallback to prompt templates ([#1246](https://github.com/TPTBusiness/NexQuant/issues/1246)) ([694afd8](https://github.com/TPTBusiness/NexQuant/commit/694afd81331227d2be7f780f72023d00c0c9864e))
|
||||||
* **claude:** auto-merge release-please PR after every push ([f500917](https://github.com/TPTBusiness/Predix/commit/f500917b699ee78dc676e84e01574d49bdc8e796))
|
* add metric in scores.csv and avoid reading sample_submission.csv ([#1152](https://github.com/TPTBusiness/NexQuant/issues/1152)) ([80c953d](https://github.com/TPTBusiness/NexQuant/commit/80c953d4053dff66d12e4cf400b069d0fac16cbd))
|
||||||
|
* Add missing os import in factor_runner.py ([f201823](https://github.com/TPTBusiness/NexQuant/commit/f201823c44c724867163f3b2d3ecf49f384a8e35))
|
||||||
## [2.2.0](https://github.com/TPTBusiness/Predix/compare/v2.1.0...v2.2.0) (2026-04-18)
|
* Add missing Panel import in nexquant evaluate command ([e21923b](https://github.com/TPTBusiness/NexQuant/commit/e21923bd13eac6236a2c25d550bae0b984575491))
|
||||||
|
* add missing self parameter to instance methods in DSProposalV2ExpGen ([#1213](https://github.com/TPTBusiness/NexQuant/issues/1213)) ([c8bf617](https://github.com/TPTBusiness/NexQuant/commit/c8bf617aca57ea9c53d4a76d23806cb5ab5173ab))
|
||||||
|
* add missing sys import and fix undefined acc_rate in factor eval ([34323f3](https://github.com/TPTBusiness/NexQuant/commit/34323f307da6924095efcdaef81f99b95e2820eb))
|
||||||
### Features
|
* Add nosec comments for schema migration SQL in results_db.py ([3626b22](https://github.com/TPTBusiness/NexQuant/commit/3626b22482143466b0dec8b63ea0a4a36af06acf))
|
||||||
|
* allow prev_out keys to be None in workspace cleanup assertion ([#1214](https://github.com/TPTBusiness/NexQuant/issues/1214)) ([f02dc5f](https://github.com/TPTBusiness/NexQuant/commit/f02dc5f47d5973673bcc314ada89933a5d807d21))
|
||||||
* add Kronos CLI commands, expand tests, document in README ([f911081](https://github.com/TPTBusiness/Predix/commit/f911081d1763d0dc4dd790b57dd97aae2dc62679))
|
* also catch ValueError in mean_variance for dimension mismatch ([daded85](https://github.com/TPTBusiness/NexQuant/commit/daded853b6370f0df6f83a6d1b3f04c0dd0757f0))
|
||||||
* **fin_quant:** auto-generate Kronos factor before loop start ([277063f](https://github.com/TPTBusiness/Predix/commit/277063f3e36cd071db859cdc77f69135c1f0763b))
|
* **auto-fixer:** add five new factor code fixes for groupby/apply errors ([d03bcf3](https://github.com/TPTBusiness/NexQuant/commit/d03bcf3505f1be696e7bddc40f33c4a97b3f7486))
|
||||||
* integrate Kronos-mini OHLCV foundation model (Option A + B) ([4ae3b99](https://github.com/TPTBusiness/Predix/commit/4ae3b99f2450930f72e202a1a470c407bfde3328))
|
* **auto-fixer:** add four new factor code fixes for common runtime errors ([21ce0de](https://github.com/TPTBusiness/NexQuant/commit/21ce0def2dd8352a315e0688ebafc6d62cf0435e))
|
||||||
|
* **auto-fixer:** add groupby([level=N,'date']) SyntaxError fix ([d58eba3](https://github.com/TPTBusiness/NexQuant/commit/d58eba364e6ea14513b64e6bc12256c72111669a))
|
||||||
|
* **auto-fixer:** disable _fix_min_periods for intraday data ([665e490](https://github.com/TPTBusiness/NexQuant/commit/665e4903d8f6f3097a45d07060ab003ebea7f96b))
|
||||||
### Bug Fixes
|
* **auto-fixer:** fix chained groupby(level=N).groupby('date') pattern ([9869839](https://github.com/TPTBusiness/NexQuant/commit/9869839a2c676ddd83f4218e9ff5e50fb8d2d223))
|
||||||
|
* **auto-fixer:** fix df.loc[instrument] DateParseError on MultiIndex frames ([87926dc](https://github.com/TPTBusiness/NexQuant/commit/87926dc41d795a3ab0670e585b99cc21dd09ae5f))
|
||||||
* **kronos:** lazy torch import to fix CI ModuleNotFoundError ([ccc1d27](https://github.com/TPTBusiness/Predix/commit/ccc1d27dbe5ab06a57085a589d456ac7bf49cc08))
|
* **auto-fixer:** fix df['instrument'] KeyError on MultiIndex frames ([63a348e](https://github.com/TPTBusiness/NexQuant/commit/63a348eb3ec20c209c2d060e086bc69019e92884))
|
||||||
* **kronos:** pass actual datetime Series to Kronos predictor timestamps ([dc6e7ce](https://github.com/TPTBusiness/Predix/commit/dc6e7ce207d21fbc21976f2af7691058530fac2f))
|
* **auto-fixer:** fix two assignment-target bugs in instrument column fixers ([a44eba9](https://github.com/TPTBusiness/NexQuant/commit/a44eba952e031e364050ee3d27a067d17fa01923))
|
||||||
* **kronos:** replace rdagent_logger with stdlib logging for CI compatibility ([b4558f2](https://github.com/TPTBusiness/Predix/commit/b4558f2456659c6109bd1b3cf100510491cd3e6c))
|
* **auto-fixer:** preserve date dimension in groupby(['instrument','date']) fix ([37a2f37](https://github.com/TPTBusiness/NexQuant/commit/37a2f37f74118a2707a6b128d55c45ddb89cc48a))
|
||||||
|
* **auto-fixer:** remove ddof from rolling() args, not only from std()/var() ([daacbfd](https://github.com/TPTBusiness/NexQuant/commit/daacbfd141ae0da99c8c4cb01d5e500528eb7d80))
|
||||||
|
* **auto-fixer:** replace zero \$volume with price-range proxy for FX data ([7fcec39](https://github.com/TPTBusiness/NexQuant/commit/7fcec39f1d8f0f7668435f51a1a9646abcd9c89f))
|
||||||
|
* **auto-fixer:** strip spurious .reset_index() after .transform() calls ([c489616](https://github.com/TPTBusiness/NexQuant/commit/c489616d1a2fd71877a203d880e31281bc008cdf))
|
||||||
|
* avoid triggering errors like "RuntimeError: dictionary changed s… ([#1285](https://github.com/TPTBusiness/NexQuant/issues/1285)) ([b180543](https://github.com/TPTBusiness/NexQuant/commit/b18054371c6ce08c6bc322a7b0de41b67fc60408))
|
||||||
|
* **backtest:** replace broken MC permutation test with binomial win-rate test ([f284b7a](https://github.com/TPTBusiness/NexQuant/commit/f284b7a9751424201510c5938b4ebf6bd81842b6))
|
||||||
|
* cancel tasks on resume and kill subprocesses on termination ([#1166](https://github.com/TPTBusiness/NexQuant/issues/1166)) ([0e3f4cf](https://github.com/TPTBusiness/NexQuant/commit/0e3f4cf08f08e27f9c483a5bbe069313d0d8014e))
|
||||||
|
* change runner prompts ([#1223](https://github.com/TPTBusiness/NexQuant/issues/1223)) ([be3433f](https://github.com/TPTBusiness/NexQuant/commit/be3433f26b04054a482dfdc7cdd5c8c0a756a60c))
|
||||||
|
* **ci:** fix closed-source asset check false positives in security workflow ([1473085](https://github.com/TPTBusiness/NexQuant/commit/14730856636735c17d704854e057fa6e1aea5940))
|
||||||
|
* **ci:** lazy import logger in nexquant.py and cli.py to avoid ImportError in test env ([52d9ff0](https://github.com/TPTBusiness/NexQuant/commit/52d9ff0cd41d6fc6978e8af7f970cffd6a46f673))
|
||||||
|
* **ci:** remove CodeQL workflow (conflicts with default setup), drop duplicate lint job ([ab73425](https://github.com/TPTBusiness/NexQuant/commit/ab734252f356ac97dea4f70477ebe2fdee30509c))
|
||||||
|
* **ci:** remove env-print step to avoid leaking sensitive environment variables ([#1299](https://github.com/TPTBusiness/NexQuant/issues/1299)) ([c067ea6](https://github.com/TPTBusiness/NexQuant/commit/c067ea640030c67c549e3ca2dbad178f144e8b31))
|
||||||
|
* **ci:** set JAVA_TOOL_OPTIONS UTF-8 in Codacy workflow ([a9c6ea9](https://github.com/TPTBusiness/NexQuant/commit/a9c6ea99c9ebae2794b1c3f4d1e9da1d4e41376a))
|
||||||
|
* clear ws_ckp after extraction to reduce workspace object size ([#1137](https://github.com/TPTBusiness/NexQuant/issues/1137)) ([28ceb41](https://github.com/TPTBusiness/NexQuant/commit/28ceb41e1cdb603c4e0bd2fe7b72acef1b29ec47))
|
||||||
|
* CLI dashboard in separate terminal window ([b72cca9](https://github.com/TPTBusiness/NexQuant/commit/b72cca98680bd8a87393bb4e5f7d17aae47ab5ed))
|
||||||
|
* close log file handle, fix FTMO equity double-count, remove bare except ([4c76c85](https://github.com/TPTBusiness/NexQuant/commit/4c76c85b6509ddd7bbd5361f0823c5a41329591a))
|
||||||
|
* **collect_info:** parse package names safely from requirements constraints ([#1313](https://github.com/TPTBusiness/NexQuant/issues/1313)) ([99a71bf](https://github.com/TPTBusiness/NexQuant/commit/99a71bf533211df743b5801f913de788259e64cb))
|
||||||
|
* correct MaxDD to equity curve in strategy_builder; test: add 8 cross-validation tests for metric correctness ([7be98e8](https://github.com/TPTBusiness/NexQuant/commit/7be98e84c911c9ba08b444b33206553cbe60086d))
|
||||||
|
* correct project root paths and subprocess handling in parallel runner and CLI ([1c35a22](https://github.com/TPTBusiness/NexQuant/commit/1c35a2277ff601553e4733a8e990217dc9d6f989))
|
||||||
|
* correct Sharpe/MaxDD/WinRate in direct factor eval (was computing on raw factor, now on strategy returns) ([69122ee](https://github.com/TPTBusiness/NexQuant/commit/69122ee5c1819be6fababd701b88d0dbef993040))
|
||||||
|
* **deps:** bump python-dotenv to >=1.2.2 (CVE symlink overwrite) ([f69333b](https://github.com/TPTBusiness/NexQuant/commit/f69333b27b9356f09e6cc2748cb45845732335c3))
|
||||||
|
* **deps:** pin aiohttp>=3.13.4 to patch 4 CVEs ([a0b3b90](https://github.com/TPTBusiness/NexQuant/commit/a0b3b90bfdd1193f5b8be521f563d18ff17dd81c))
|
||||||
|
* **deps:** relax aiohttp constraint to >=3.13.4 for litellm compatibility ([d3978fe](https://github.com/TPTBusiness/NexQuant/commit/d3978fec1305d7503a37ff576fdf953f75e1cd1d))
|
||||||
|
* Disable ANSI color codes when not running in TTY ([9db0e59](https://github.com/TPTBusiness/NexQuant/commit/9db0e590a4e94f538712cfec79f6cd470155050c))
|
||||||
|
* Disable Flask debug mode by default (Security Alert [#2](https://github.com/TPTBusiness/NexQuant/issues/2)) ([48c177f](https://github.com/TPTBusiness/NexQuant/commit/48c177fbafce7b111646c14a5c2e6e414414930b))
|
||||||
|
* Display litellm messages as info instead of warnings ([bd9d672](https://github.com/TPTBusiness/NexQuant/commit/bd9d672997aff80b5ad5c616b6486c11c2570b80))
|
||||||
|
* **dockerfile:** install coreutils to resolve timeout command error ([#1260](https://github.com/TPTBusiness/NexQuant/issues/1260)) ([35580cb](https://github.com/TPTBusiness/NexQuant/commit/35580cbdf87347d5d6105b2a9b5ad1694b695820))
|
||||||
|
* **docs:** update rdagent ui with correct params ([#1249](https://github.com/TPTBusiness/NexQuant/issues/1249)) ([3b9ad11](https://github.com/TPTBusiness/NexQuant/commit/3b9ad1145769862a24cc7533a1828f750f72170d))
|
||||||
|
* Embedding Context Length Error ([6d6c5ab](https://github.com/TPTBusiness/NexQuant/commit/6d6c5abd4ac7252257f88e13e263ecb2497fde3b))
|
||||||
|
* enable embedding truncation ([#1188](https://github.com/TPTBusiness/NexQuant/issues/1188)) ([880a6c7](https://github.com/TPTBusiness/NexQuant/commit/880a6c70c41024cb51f9fc4349ac7f1d2dbda434))
|
||||||
|
* end-timestamp 23:45, weg, SZ-beispiele weg ([6a9ccd5](https://github.com/TPTBusiness/NexQuant/commit/6a9ccd5ddbf95060a2847bd27bcdae762a46a19d))
|
||||||
|
* enhance feedback handling in MultiProcessEvolvingStrategy for improved task evolution ([#1274](https://github.com/TPTBusiness/NexQuant/issues/1274)) ([afb575c](https://github.com/TPTBusiness/NexQuant/commit/afb575cc91114dbe41d8f582294dcc3692990695))
|
||||||
|
* Ensure backtest results save to DB and JSON files ([ae7b35e](https://github.com/TPTBusiness/NexQuant/commit/ae7b35ea2e0c71c76e8e454f7845df461d65b99f))
|
||||||
|
* evaluator erkennt 15min als valid (nicht daily) ([cf0f634](https://github.com/TPTBusiness/NexQuant/commit/cf0f634c17dce45400cc325ccd3ca45e769c15fd))
|
||||||
|
* **factors:** detect and correct look-ahead bias in daily-constant factors ([dcad0d1](https://github.com/TPTBusiness/NexQuant/commit/dcad0d1f68608a4db3cfdabb75e66c22490643aa))
|
||||||
|
* **factors:** extend look-ahead rules to session factors and add intraday-factor guidance ([8811dc0](https://github.com/TPTBusiness/NexQuant/commit/8811dc042a0a7a1ac385c7141ded9f56a434dced))
|
||||||
|
* filter NaN in max(), remove redundant ternary, handle non-finite vbt results ([1acfe50](https://github.com/TPTBusiness/NexQuant/commit/1acfe508a9c327dce8eba7a2ad1f618052a3e8a5))
|
||||||
|
* fix bug for hypo_select_with_llm when not support response_schema ([#1208](https://github.com/TPTBusiness/NexQuant/issues/1208)) ([d759ca9](https://github.com/TPTBusiness/NexQuant/commit/d759ca95e714a7a1476839a2a04bb652c0fbb863))
|
||||||
|
* fix chat_max_tokens calculation method to show true input_max_tokens ([#1241](https://github.com/TPTBusiness/NexQuant/issues/1241)) ([7e99605](https://github.com/TPTBusiness/NexQuant/commit/7e996055f2c7fd37595573ebdb13aa57c425a6cc))
|
||||||
|
* fix mcts ([#1270](https://github.com/TPTBusiness/NexQuant/issues/1270)) ([5003aff](https://github.com/TPTBusiness/NexQuant/commit/5003affb17505525336e6c30ba9c690b810c252b))
|
||||||
|
* Fix parallel runner dashboard rendering error ([3e8c07e](https://github.com/TPTBusiness/NexQuant/commit/3e8c07e728076a951528c4eb5b429653a5c77d14))
|
||||||
|
* fix some bugs in RD-Agent(Q) ([#1143](https://github.com/TPTBusiness/NexQuant/issues/1143)) ([7134a51](https://github.com/TPTBusiness/NexQuant/commit/7134a51afa71ab146b52987c194adace62f8b034))
|
||||||
|
* fix type annotation, remove unused parameter, improve import_class errors ([1eb5849](https://github.com/TPTBusiness/NexQuant/commit/1eb5849dd44c5953f7198212a5ef0dbe8c8d4881))
|
||||||
|
* Forward-fill daily factors to 1-min frequency ([20f4c21](https://github.com/TPTBusiness/NexQuant/commit/20f4c2140c397230fb56734b0e887b770db805ac))
|
||||||
|
* generate.py nutzt rdagent4qlib env für Qlib-Datenzugriff ([b9007f7](https://github.com/TPTBusiness/NexQuant/commit/b9007f754ac682800aaf265c0f24c2028d387d84))
|
||||||
|
* **graph:** using assignment expression to avoid repeated function call ([#1174](https://github.com/TPTBusiness/NexQuant/issues/1174)) ([b6fae75](https://github.com/TPTBusiness/NexQuant/commit/b6fae75cde256c9c8a84783dbd135a9bcca6ac8d))
|
||||||
|
* Handle failed experiments in feedback step to prevent crashes ([979ef66](https://github.com/TPTBusiness/NexQuant/commit/979ef66dc612c7f589e097dcdc3a01b742b18970))
|
||||||
|
* handle mixed str and dict types in code_list ([#1279](https://github.com/TPTBusiness/NexQuant/issues/1279)) ([32ecf92](https://github.com/TPTBusiness/NexQuant/commit/32ecf92afcf647f257b430c748cbe6bb5fa0fac4))
|
||||||
|
* Handle negative/zero values in performance report charts ([f4a4c65](https://github.com/TPTBusiness/NexQuant/commit/f4a4c65ce9bc1c929526a20a852765b92709011c))
|
||||||
|
* handle None output and conditional step dump in LoopBase execution ([#1212](https://github.com/TPTBusiness/NexQuant/issues/1212)) ([9de8d60](https://github.com/TPTBusiness/NexQuant/commit/9de8d6066994fcd7037fd03d9339b6590ab2fac9))
|
||||||
|
* Handle Qlib Docker backtest failures gracefully (SECURITY FIX) ([59f4561](https://github.com/TPTBusiness/NexQuant/commit/59f45618229be08dba028dceda21433cc5d52b9f))
|
||||||
|
* Handle timeout exceptions safely in nexquant_full_eval.py ([2738263](https://github.com/TPTBusiness/NexQuant/commit/27382635171482be2cee2e29d4793e63d14abce4))
|
||||||
|
* handle ValueError in stdout shrinking and refactor shrink logic ([#1228](https://github.com/TPTBusiness/NexQuant/issues/1228)) ([6fc3877](https://github.com/TPTBusiness/NexQuant/commit/6fc3877a39baabbf26e0cc1cbd327b0f6e2e325e))
|
||||||
|
* Harden _safe_resolve to fix CodeQL alert [#3](https://github.com/TPTBusiness/NexQuant/issues/3) ([0ed1a0a](https://github.com/TPTBusiness/NexQuant/commit/0ed1a0aa8faad6df36753a928f40a1cdbd606462))
|
||||||
|
* Harden path validation in Job Summary UI to fix CodeQL alert [#17](https://github.com/TPTBusiness/NexQuant/issues/17) ([7fe15d4](https://github.com/TPTBusiness/NexQuant/commit/7fe15d46cb2a740b6ec0ee37d29acaf37476e8e6))
|
||||||
|
* Harden path validation to fix CodeQL alert [#20](https://github.com/TPTBusiness/NexQuant/issues/20) ([59d06f6](https://github.com/TPTBusiness/NexQuant/commit/59d06f6588caadaa207bde1d135828c56169bff8))
|
||||||
|
* ignore case when checking metric name ([#1160](https://github.com/TPTBusiness/NexQuant/issues/1160)) ([1b84f7b](https://github.com/TPTBusiness/NexQuant/commit/1b84f7b7546a9dee4f27e24e07c49fa8ee3a370d))
|
||||||
|
* ignore RuntimeError for shared workspace double recovery ([#1140](https://github.com/TPTBusiness/NexQuant/issues/1140)) ([bd8a16d](https://github.com/TPTBusiness/NexQuant/commit/bd8a16d92f9176d835bbc27478f9259f0fe9a827))
|
||||||
|
* Import pandas in nexquant portfolio_simple command ([2b6de06](https://github.com/TPTBusiness/NexQuant/commit/2b6de06a612c147c414bde3175b6f11af1762f4d))
|
||||||
|
* Improve path traversal prevention with dedicated helper function ([50dc275](https://github.com/TPTBusiness/NexQuant/commit/50dc27566d886a4aea9ea56eaef2c08e794df770))
|
||||||
|
* increase retry count in hypothesis_gen decorator to 10 ([#1230](https://github.com/TPTBusiness/NexQuant/issues/1230)) ([86ce4f1](https://github.com/TPTBusiness/NexQuant/commit/86ce4f135d649cfb12f2f88626cd31868cb447e7))
|
||||||
|
* increase time default not controlled by LLM ([#1196](https://github.com/TPTBusiness/NexQuant/issues/1196)) ([e4bd647](https://github.com/TPTBusiness/NexQuant/commit/e4bd647d1b20cbaa26a00cf23c49bfbc0bc80477))
|
||||||
|
* Initialize EnvController in QuantTrace.__init__ ([698a17e](https://github.com/TPTBusiness/NexQuant/commit/698a17ea61321c37c7fa0d69849a309d29474f80))
|
||||||
|
* inject correct MultiIndex template into factor prompt ([49004db](https://github.com/TPTBusiness/NexQuant/commit/49004db027d699bacbb975f267daa95d1957ccd7))
|
||||||
|
* inject MultiIndex warning into factor interface prompt (YAML valide) ([79e2915](https://github.com/TPTBusiness/NexQuant/commit/79e2915823801d3574920fa197cf9c57965f485f))
|
||||||
|
* insert await asyncio.sleep(0) to yield control in loop ([#1186](https://github.com/TPTBusiness/NexQuant/issues/1186)) ([e0453e0](https://github.com/TPTBusiness/NexQuant/commit/e0453e0058e2a4ec74feb0b31883f45604a9bf0c))
|
||||||
|
* jinja problem of enumerate ([#1216](https://github.com/TPTBusiness/NexQuant/issues/1216)) ([6725f15](https://github.com/TPTBusiness/NexQuant/commit/6725f15f30df30a3ce37024fded621354d8114a7))
|
||||||
|
* kaggle competition metric direction ([#1195](https://github.com/TPTBusiness/NexQuant/issues/1195)) ([04878f9](https://github.com/TPTBusiness/NexQuant/commit/04878f9e703fee9caff9208ab23995586f165c95))
|
||||||
|
* **kronos:** lazy torch import to fix CI ModuleNotFoundError ([9cd8ab5](https://github.com/TPTBusiness/NexQuant/commit/9cd8ab54656786cc04742695c9d2e650a1b124ae))
|
||||||
|
* **kronos:** pass actual datetime Series to Kronos predictor timestamps ([7741408](https://github.com/TPTBusiness/NexQuant/commit/7741408c671b6fe943491b39d9fc5cac256b457e))
|
||||||
|
* **kronos:** replace rdagent_logger with stdlib logging for CI compatibility ([1ee5ea7](https://github.com/TPTBusiness/NexQuant/commit/1ee5ea7792f9ea94ddd26a0828d9744d0e07baa6))
|
||||||
|
* **loop:** compress old experiment history in proposal prompt to reduce context size ([bde37f0](https://github.com/TPTBusiness/NexQuant/commit/bde37f09d53a4f6582d071ed72d86491889bc573))
|
||||||
|
* **loop:** prevent step_idx advance on unhandled exceptions + fix consecutive assistant messages ([881ca81](https://github.com/TPTBusiness/NexQuant/commit/881ca819cea90d8a60865296e6f416aab69a18c9))
|
||||||
|
* merge candidates ([#1254](https://github.com/TPTBusiness/NexQuant/issues/1254)) ([46aad78](https://github.com/TPTBusiness/NexQuant/commit/46aad789ef710d9603e2330788dc66849cb6cab3))
|
||||||
|
* model/factor experiment filtering in Qlib proposals ([#1257](https://github.com/TPTBusiness/NexQuant/issues/1257)) ([9e34b4e](https://github.com/TPTBusiness/NexQuant/commit/9e34b4e855cbd709cd077f529950b8e1f5c01486))
|
||||||
|
* move snapshot saving after step index update in loop execution ([#1206](https://github.com/TPTBusiness/NexQuant/issues/1206)) ([774346d](https://github.com/TPTBusiness/NexQuant/commit/774346d92e3d9faa858f935bb2651d0f1aa12a6c))
|
||||||
|
* move task cancellation to finally block and fix subprocess kill typo ([#1234](https://github.com/TPTBusiness/NexQuant/issues/1234)) ([a984f69](https://github.com/TPTBusiness/NexQuant/commit/a984f69f681dda1c6c58f45e2505d7b0e8d75cf0))
|
||||||
|
* **optuna:** fix inverted parameter range in Stage 2/3 when signal_bias is negative ([f0be842](https://github.com/TPTBusiness/NexQuant/commit/f0be842a6c03f56cb209d1f8a0c5a0d9fa3baebf))
|
||||||
|
* Override webshop's Werkzeug dependency to fix CVE-2026-27199 ([3a5aa0b](https://github.com/TPTBusiness/NexQuant/commit/3a5aa0ba43fd644ad1944994f3cd3d49e7ab633c))
|
||||||
|
* preserve null end_time when rendering dataset segments template ([#1326](https://github.com/TPTBusiness/NexQuant/issues/1326)) ([6196ba3](https://github.com/TPTBusiness/NexQuant/commit/6196ba31f2e43db4761eeb482c3301e2238bc4cf))
|
||||||
|
* prevent calendar index overflow when signal data ends early ([#1324](https://github.com/TPTBusiness/NexQuant/issues/1324)) ([3dbd703](https://github.com/TPTBusiness/NexQuant/commit/3dbd7038280f21793246e5354f083ba472772a10))
|
||||||
|
* prevent JSON content from being added multiple times during retries ([#1255](https://github.com/TPTBusiness/NexQuant/issues/1255)) ([31b19de](https://github.com/TPTBusiness/NexQuant/commit/31b19dee80c5006c72a0a9698834a04a3acd4af9))
|
||||||
|
* Prevent path injection in FT Job Summary UI ([e4393fb](https://github.com/TPTBusiness/NexQuant/commit/e4393fb3b1e95fa53f7d8e972da35e994402def8))
|
||||||
|
* Prevent path injection in RL Job Summary UI ([b3e8cb8](https://github.com/TPTBusiness/NexQuant/commit/b3e8cb8cfe5fe74c5b893c6d0e401375630ee750))
|
||||||
|
* Prevent path traversal in autorl_bench server.py ([6634e6e](https://github.com/TPTBusiness/NexQuant/commit/6634e6e5c55c07f41d3a37731d59f6e11b35610e))
|
||||||
|
* Prevent path traversal in get_job_options() app.py ([7da2e57](https://github.com/TPTBusiness/NexQuant/commit/7da2e5706c7d7da8ffee3f04b42f8d3378af26ad))
|
||||||
|
* Prevent path traversal in RL UI app.py ([d2c1516](https://github.com/TPTBusiness/NexQuant/commit/d2c1516416dbda6109f6d42245263ce5373ce957))
|
||||||
|
* Prevent path traversal in Streamlit UI app.py ([0d0fd34](https://github.com/TPTBusiness/NexQuant/commit/0d0fd34573c0695c34431a6e9eb7b5c10a3a91f9))
|
||||||
|
* **qlib:** correct indentation in except blocks in quant_proposal and factor_runner ([8f67ab6](https://github.com/TPTBusiness/NexQuant/commit/8f67ab61299b7fb7063f5ac363705a6687ecaea1))
|
||||||
|
* Refactor path validation to fix CodeQL alert [#16](https://github.com/TPTBusiness/NexQuant/issues/16) ([a417ebc](https://github.com/TPTBusiness/NexQuant/commit/a417ebc41db5ad24b89f53e5f3c3ff6e5339ae18))
|
||||||
|
* refine DSCoSTEER_eval prompts ([#1157](https://github.com/TPTBusiness/NexQuant/issues/1157)) ([5594ab4](https://github.com/TPTBusiness/NexQuant/commit/5594ab418b46422e2f2e2edc08f0aadd0e95af04))
|
||||||
|
* refine prompts and add additional package info ([#1179](https://github.com/TPTBusiness/NexQuant/issues/1179)) ([5353bd3](https://github.com/TPTBusiness/NexQuant/commit/5353bd31f25a98cba552145709af743cd4e83cf5))
|
||||||
|
* refine task scheduling logic in MultiProcessEvolvingStrategy for… ([#1275](https://github.com/TPTBusiness/NexQuant/issues/1275)) ([27d38af](https://github.com/TPTBusiness/NexQuant/commit/27d38af7bd7e1fdb73e3617e94435abe7901dd21))
|
||||||
|
* remove $factor from prompt, update example count to EURUSD ([3adc5bf](https://github.com/TPTBusiness/NexQuant/commit/3adc5bf75e6820328991aa5a5456e6f68ccf8fd7))
|
||||||
|
* remove all Chinese stock references, replace with EURUSD 1min FX ([44eeb01](https://github.com/TPTBusiness/NexQuant/commit/44eeb01ec4f95271a084e9d285e00959926923f3))
|
||||||
|
* Remove API key from test_benchmark_api.py config ([16e8631](https://github.com/TPTBusiness/NexQuant/commit/16e86310bdd8d2af1539063957edebde97f88110))
|
||||||
|
* Remove API key logging from eurusd_llm.py ([3f510be](https://github.com/TPTBusiness/NexQuant/commit/3f510be9daddf0b241925f605898e2e1d3a18cb7))
|
||||||
|
* Remove API key parameter from generate_api_config() ([e6eeac9](https://github.com/TPTBusiness/NexQuant/commit/e6eeac93614a9d97d119696802c7a08153c70f59))
|
||||||
|
* Remove API key presence detection from logging ([12b45e5](https://github.com/TPTBusiness/NexQuant/commit/12b45e50f2d7d41881c3028b3f2213e7e7c573d8))
|
||||||
|
* Remove clear-text storage of API key (CodeQL alert [#8](https://github.com/TPTBusiness/NexQuant/issues/8)) ([4842311](https://github.com/TPTBusiness/NexQuant/commit/4842311d9193d665c27311e7efc9637b9f3e0519))
|
||||||
|
* Remove hardcoded credentials from test_benchmark_api.py ([2523ee2](https://github.com/TPTBusiness/NexQuant/commit/2523ee213e35c03175da9512619b46f6e9069f88))
|
||||||
|
* remove unused imports in data science scenario module ([#1136](https://github.com/TPTBusiness/NexQuant/issues/1136)) ([fd6cd39](https://github.com/TPTBusiness/NexQuant/commit/fd6cd3950c4d0463f2d1ccab63fa48be4de41a58))
|
||||||
|
* Rename loader.py to prompt_loader.py to fix module conflict ([06f0c34](https://github.com/TPTBusiness/NexQuant/commit/06f0c3427c665063513ae097068be71069a733b2))
|
||||||
|
* replace hardcoded ChromeDriver path with webdriver-manager ([#1271](https://github.com/TPTBusiness/NexQuant/issues/1271)) ([e3d2443](https://github.com/TPTBusiness/NexQuant/commit/e3d24437cf7842623fe27fd9221e36a07457d7f7))
|
||||||
|
* Resolve 88% empty backtest results + path fixes ([8d1c70e](https://github.com/TPTBusiness/NexQuant/commit/8d1c70e679721b90c024bc747d2544ce9c151adf))
|
||||||
|
* resolve dead code, shell injection risk, mutable defaults, and other bugs ([4267315](https://github.com/TPTBusiness/NexQuant/commit/4267315783ccbdaa3472c5f7fd4728cf656556c1))
|
||||||
|
* Resolve FORWARD_BARS NameError in backtest script ([ad7f5e1](https://github.com/TPTBusiness/NexQuant/commit/ad7f5e1388ad2149d0c32a5febfed0b77b05ef47))
|
||||||
|
* Resolve security vulnerabilities (Dependabot + Code Scanning) ([2c96828](https://github.com/TPTBusiness/NexQuant/commit/2c9682800e4ea30361561affbb747e4f2cc763f6))
|
||||||
|
* resolve unbound variable, logger shadowing, withdraw_loop edge case, and other bugs in main scripts ([2fd4bc3](https://github.com/TPTBusiness/NexQuant/commit/2fd4bc3741bafc6778008b3ecc49ba01207f22e1))
|
||||||
|
* revert 2 commits ([#1239](https://github.com/TPTBusiness/NexQuant/issues/1239)) ([2201a47](https://github.com/TPTBusiness/NexQuant/commit/2201a4762343f2cc2deb3dff2b70baf99f102292))
|
||||||
|
* revert to v10 setting ([#1220](https://github.com/TPTBusiness/NexQuant/issues/1220)) ([51f5bc9](https://github.com/TPTBusiness/NexQuant/commit/51f5bc9e117c6bfcb50c29355d5e73381d40b511))
|
||||||
|
* **security:** nosec for B608/B701 false positives in UI and template code ([8b73952](https://github.com/TPTBusiness/NexQuant/commit/8b739528e5679cb49989be7e0edd7ac404b5d993))
|
||||||
|
* **security:** Patch 5 CodeQL path injection and clear-text logging alerts ([#22](https://github.com/TPTBusiness/NexQuant/issues/22)-[#25](https://github.com/TPTBusiness/NexQuant/issues/25), [#9](https://github.com/TPTBusiness/NexQuant/issues/9)) ([5aed2cf](https://github.com/TPTBusiness/NexQuant/commit/5aed2cf58a4a39d515bc81e5fd6835a138198b82))
|
||||||
|
* **security:** Patch 5 CodeQL path injection and weak hashing alerts ([#25](https://github.com/TPTBusiness/NexQuant/issues/25)-[#30](https://github.com/TPTBusiness/NexQuant/issues/30)) ([e188333](https://github.com/TPTBusiness/NexQuant/commit/e1883331f18e7265aeb13145abaca4b295a15f6e))
|
||||||
|
* **security:** Patch path injection and stack trace exposure (CodeQL [#31](https://github.com/TPTBusiness/NexQuant/issues/31), [#27](https://github.com/TPTBusiness/NexQuant/issues/27)) ([2b0525f](https://github.com/TPTBusiness/NexQuant/commit/2b0525f9b7ef68ecc04bfddd558184f06640fb0b))
|
||||||
|
* **security:** real fix for B110 (logging in factor_proposal.py [#746](https://github.com/TPTBusiness/NexQuant/issues/746)) ([61656af](https://github.com/TPTBusiness/NexQuant/commit/61656afda75e77686952d847aec443c28e17b6d6))
|
||||||
|
* **security:** real fix for B110 (logging in factor_runner.py [#744](https://github.com/TPTBusiness/NexQuant/issues/744)) ([5ac64e6](https://github.com/TPTBusiness/NexQuant/commit/5ac64e60e4e3977364ffd5ad8704fdf0c46bad75))
|
||||||
|
* **security:** real fix for B110 (logging in quant_proposal.py [#741](https://github.com/TPTBusiness/NexQuant/issues/741)) ([bcfeb32](https://github.com/TPTBusiness/NexQuant/commit/bcfeb32958953ba07e980dce5feaffe5d53963e8))
|
||||||
|
* **security:** real fix for B110 (logging in quant_proposal.py [#741](https://github.com/TPTBusiness/NexQuant/issues/741)) ([d865c82](https://github.com/TPTBusiness/NexQuant/commit/d865c824c98820b26e3d64b8c193445effb19667))
|
||||||
|
* **security:** real fix for B404/B603 (sys.executable in factor_runner.py [#745](https://github.com/TPTBusiness/NexQuant/issues/745)) ([7894b8e](https://github.com/TPTBusiness/NexQuant/commit/7894b8e6ed1cb580d8909403eb166a2b418b2dd0))
|
||||||
|
* **security:** replace eval() with ast.literal_eval and add request timeouts (B307, B113) ([ffb24fd](https://github.com/TPTBusiness/NexQuant/commit/ffb24fd5de724455aa77846c3f98fae35bc80430))
|
||||||
|
* **security:** replace eval() with ast.literal_eval in finetune validator (B307) ([8d53b81](https://github.com/TPTBusiness/NexQuant/commit/8d53b81633965fd0ae2bf32081dacc91b121b77d))
|
||||||
|
* **security:** replace os.path.realpath with pathlib.resolve in safe_resolve_path to fix path-injection alerts ([0d7af52](https://github.com/TPTBusiness/NexQuant/commit/0d7af52a2d32f1dbcc366b9f395c43ad47ddabb2))
|
||||||
|
* **security:** replace relative_to() with realpath+startswith for CodeQL sanitization ([d7e2018](https://github.com/TPTBusiness/NexQuant/commit/d7e2018a7232c59a40d6e740111572a0da0cd384))
|
||||||
|
* **security:** replace remaining assert statements with proper error handling ([d4d5baf](https://github.com/TPTBusiness/NexQuant/commit/d4d5bafd1eb8330f75917170520408b48d38f8c2))
|
||||||
|
* **security:** replace shell=True subprocess calls with list args (B602) ([30887ac](https://github.com/TPTBusiness/NexQuant/commit/30887ac244f77a5edabc11dda7805b9bb789667f))
|
||||||
|
* **security:** replace shell=True subprocess calls with list args in env.py (B602) ([1a4f1cf](https://github.com/TPTBusiness/NexQuant/commit/1a4f1cf6044842939bc5e7ed853c437cab591a26))
|
||||||
|
* **security:** resolve all 30 Bandit security alerts (B301, B614, B104) ([00f400f](https://github.com/TPTBusiness/NexQuant/commit/00f400fe2efda375884234cd381401583a65f456))
|
||||||
|
* **security:** resolve CodeQL path-injection alerts in UI data loaders ([7caab95](https://github.com/TPTBusiness/NexQuant/commit/7caab9545bd929909f4c7cae02fbcc2cc3a9893a))
|
||||||
|
* **security:** resolve CodeQL path-injection and clear-text-logging alerts ([8701b8b](https://github.com/TPTBusiness/NexQuant/commit/8701b8bd75f82ceb326da4f105609f4228961666))
|
||||||
|
* **security:** Resolve GitHub Security Scan alerts ([5af7f19](https://github.com/TPTBusiness/NexQuant/commit/5af7f19bd1656078991752d298c0f3c953f7af2c))
|
||||||
|
* **security:** resolve path-injection and add nosec for safe temp paths (B108, py/path-injection) ([4133fff](https://github.com/TPTBusiness/NexQuant/commit/4133fffa7d97bd38beb4b99aa7f3ab3039d78103))
|
||||||
|
* **security:** resolve path-injection, B701, B101, B112 Bandit alerts ([e87d612](https://github.com/TPTBusiness/NexQuant/commit/e87d61257fa4bb401415b62ff88c7ad75085d89c))
|
||||||
|
* **security:** revert broken read_pickle encoding arg in kaggle template (B301) ([e16460c](https://github.com/TPTBusiness/NexQuant/commit/e16460c7bc5329c9752cd12b20fcee978b5f232b))
|
||||||
|
* **security:** Upgrade vllm and transformers to patch 4 CVEs ([85915b3](https://github.com/TPTBusiness/NexQuant/commit/85915b3a20e9ceae6dd854ef4c64a61590a36d84))
|
||||||
|
* **security:** validate SQL identifiers in _add_column_if_not_exists (B608) ([c40795b](https://github.com/TPTBusiness/NexQuant/commit/c40795bcb0dab5ceff9b56ec019b9be6f9d10203))
|
||||||
|
* **security:** whitelist-validate metric column in get_top_factors (B608) ([db51417](https://github.com/TPTBusiness/NexQuant/commit/db51417cd4337e3b8b76420c93b1bb1ed3271b13))
|
||||||
|
* set requires_documentation_search to None to disable feature in eval ([#1245](https://github.com/TPTBusiness/NexQuant/issues/1245)) ([ee8c119](https://github.com/TPTBusiness/NexQuant/commit/ee8c119f31b72de1002e5ad5d30c56d0f4b6c9b9))
|
||||||
|
* Skip already evaluated factors in nexquant_full_eval.py ([8375213](https://github.com/TPTBusiness/NexQuant/commit/8375213629551605b4c401aa1ce71ed8d9f1e4db))
|
||||||
|
* skip Kronos factor on GPUs < 20GB to avoid CUDA OOM (shared with llama-server) ([08fea7a](https://github.com/TPTBusiness/NexQuant/commit/08fea7a2809941d2b5f3feb5ba998dba132053bb))
|
||||||
|
* skip res_ratio check if timer or res_time is None ([#1189](https://github.com/TPTBusiness/NexQuant/issues/1189)) ([dbe2142](https://github.com/TPTBusiness/NexQuant/commit/dbe214282e84f099512eeaf01925c7dee1b780a6))
|
||||||
|
* **strategies:** guard against None IC in acceptance check, disable slow wf_rolling ([843cd9a](https://github.com/TPTBusiness/NexQuant/commit/843cd9ae017b05365e1bb353b9945e2fbce332dd))
|
||||||
|
* **strategies:** handle None ic/sharpe/dd in rejected strategy log output ([0121c2c](https://github.com/TPTBusiness/NexQuant/commit/0121c2c1583b752622c69313e78ccbeedf6c8d1b))
|
||||||
|
* **strategy:** Fix template variables, APIBackend import, and JSON extraction ([f0e813e](https://github.com/TPTBusiness/NexQuant/commit/f0e813ee48ae65e0ee78c27a8b971139dac5b552))
|
||||||
|
* **strategy:** Re-evaluate Optuna-optimized strategies with full OHLCV backtest ([7da8bad](https://github.com/TPTBusiness/NexQuant/commit/7da8badbc1005bb1866631dc14daa815641b4271))
|
||||||
|
* summary page bug ([#1219](https://github.com/TPTBusiness/NexQuant/issues/1219)) ([beab473](https://github.com/TPTBusiness/NexQuant/commit/beab473b40714fbd802ebb3b61c0dd3d3ba7d91a))
|
||||||
|
* Switch to ThreadPoolExecutor for factor evaluation ([d0aa146](https://github.com/TPTBusiness/NexQuant/commit/d0aa1464ea1e3553e4b869c3429e5e394bcebda8))
|
||||||
|
* Translate remaining German comment in eurusd_macro.py ([02b46d1](https://github.com/TPTBusiness/NexQuant/commit/02b46d1ffc3bfe87033714f71a9d22714a071f09))
|
||||||
|
* ui bug ([#1192](https://github.com/TPTBusiness/NexQuant/issues/1192)) ([2f8261f](https://github.com/TPTBusiness/NexQuant/commit/2f8261f82bf25ad714eff22be2283c6e645b5314))
|
||||||
|
* update fallback criterion ([#1210](https://github.com/TPTBusiness/NexQuant/issues/1210)) ([dbbe374](https://github.com/TPTBusiness/NexQuant/commit/dbbe374ac8b0cefcde9145a76b4cd5c0b40b3f92))
|
||||||
|
* Update LICENSE badge link from main to master branch ([0dbace6](https://github.com/TPTBusiness/NexQuant/commit/0dbace6aa7aa1a7a250e45c96e71591edeed8f55))
|
||||||
|
* update requirements.txt's streamlit ([#1133](https://github.com/TPTBusiness/NexQuant/issues/1133)) ([600d159](https://github.com/TPTBusiness/NexQuant/commit/600d159e86521cc0498df9df3756921e676e3332))
|
||||||
|
* Update Werkzeug to 2.3.8 (latest secure 2.x version) ([d68a5ee](https://github.com/TPTBusiness/NexQuant/commit/d68a5ee47cba6f8d2ca0faba1ad89ba65f4fc94b))
|
||||||
|
* update WF test for new default (wf_rolling=True) ([c906e00](https://github.com/TPTBusiness/NexQuant/commit/c906e00ac9731673f6386f8b3ce38f5d8e817992))
|
||||||
|
* Use 96-bar forward returns in backtest (matching factor IC horizon) ([19c5b3d](https://github.com/TPTBusiness/NexQuant/commit/19c5b3d70633d5cc622328e57acd122120d47971))
|
||||||
|
* Use num_api_keys instead of len(api_keys) for round-robin ([c91976e](https://github.com/TPTBusiness/NexQuant/commit/c91976e7968f54a065b4a5ee11228133b48db3e9))
|
||||||
|
* weg, Timestamps mit Uhrzeit, kein SZ-Beispiel ([e9f6ac4](https://github.com/TPTBusiness/NexQuant/commit/e9f6ac48d97b1b57a0dde14562cd1b6f5d106edd))
|
||||||
|
|
||||||
|
|
||||||
### Performance Improvements
|
### Performance Improvements
|
||||||
|
|
||||||
* **kronos:** batch GPU inference via predict_batch — 75x faster ([74611d0](https://github.com/TPTBusiness/Predix/commit/74611d071ac123a655eb15d0737bb73b8c1bd2b0))
|
* **kronos:** batch GPU inference via predict_batch — 75x faster ([a93f940](https://github.com/TPTBusiness/NexQuant/commit/a93f940485eb92d747d5e6f966acb5c5e8d118c7))
|
||||||
* **kronos:** batch GPU inference via predict_batch — 75x faster ([2babeb9](https://github.com/TPTBusiness/Predix/commit/2babeb95f42828e13a37dc16166c75538f33fd4b))
|
* **kronos:** batch GPU inference via predict_batch — 75x faster ([471b1f9](https://github.com/TPTBusiness/NexQuant/commit/471b1f9a4b22cfd2f473d28285a6c7390fe3d10c))
|
||||||
|
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
* fix duplicate sections, add hardware requirements and data setup guide ([6c771b3](https://github.com/TPTBusiness/Predix/commit/6c771b37e6f88526a896499e86929cfca2c199eb))
|
* Add ATTRIBUTION.md with clear usage guidelines ([c5bf3e4](https://github.com/TPTBusiness/NexQuant/commit/c5bf3e4e2b99074e54645328a399f8f6da0387ea))
|
||||||
|
* Add CLI welcome screenshot to README ([4103ebe](https://github.com/TPTBusiness/NexQuant/commit/4103ebe1bfdc625af18711cf78ed19c808270227))
|
||||||
## [2.1.0](https://github.com/TPTBusiness/Predix/compare/v2.0.0...v2.1.0) (2026-04-18)
|
* Add comprehensive CHANGELOG.md for v1.0.0 release ([569b72b](https://github.com/TPTBusiness/NexQuant/commit/569b72b2c9a154bf991d03ac078bf020ef1eab16))
|
||||||
|
* Add comprehensive CLI help and update README with quick start ([8265462](https://github.com/TPTBusiness/NexQuant/commit/8265462cacb4e03c981ead1d6b6393a9070f729e))
|
||||||
|
* Add comprehensive data setup guide to README ([ca30ed2](https://github.com/TPTBusiness/NexQuant/commit/ca30ed270ab36517604a9eb0f1ace0fdd58a917c))
|
||||||
|
* Add comprehensive Git commit guidelines to QWEN.md ([d10d3a2](https://github.com/TPTBusiness/NexQuant/commit/d10d3a2c658bb77366baec13e922f0ed924b51d8))
|
||||||
|
* Add conda requirement to README + fix nexquant CLI ([90e185a](https://github.com/TPTBusiness/NexQuant/commit/90e185a4986ff9a4838bd94cb7b4034fea573f87))
|
||||||
|
* Add CRITICAL rule - NEVER commit closed-source/private assets ([a0ed4f7](https://github.com/TPTBusiness/NexQuant/commit/a0ed4f712ed4aa49eadaa5ced070c22f0146420a))
|
||||||
|
* Add CRITICAL rule - NEVER commit trading strategies or JSON files ([cb0cb4c](https://github.com/TPTBusiness/NexQuant/commit/cb0cb4c1122b9aab23f2e2f4feb5b4a99ed05008))
|
||||||
|
* add documentation for Data Science configurable options ([#1301](https://github.com/TPTBusiness/NexQuant/issues/1301)) ([d603d5a](https://github.com/TPTBusiness/NexQuant/commit/d603d5a5aa86e43cfc0ee3efedc5ab18919809f5))
|
||||||
|
* add execution environment configuration guide (Docker vs Conda) ([#1288](https://github.com/TPTBusiness/NexQuant/issues/1288)) ([27ed3d1](https://github.com/TPTBusiness/NexQuant/commit/27ed3d1a75b15a5589af84d4f597a8484006e71e))
|
||||||
|
* Add implementation summary ([649ed0c](https://github.com/TPTBusiness/NexQuant/commit/649ed0c3c0db823fb4fc984b9f6b6e7970d728ff))
|
||||||
|
* Add live trading system documentation to QWEN.md ([49b15d9](https://github.com/TPTBusiness/NexQuant/commit/49b15d917828a3c1263da1785da5663c67d41b40))
|
||||||
|
* Add Microsoft RD-Agent acknowledgment to README ([06c0b44](https://github.com/TPTBusiness/NexQuant/commit/06c0b44e4106a725a879932122d871041042ec2b))
|
||||||
|
* Add professional badges to README header ([91d44dd](https://github.com/TPTBusiness/NexQuant/commit/91d44ddabd4b4cf82cb1e6f53c8f4547f52a50cb))
|
||||||
|
* Add results/ directory README for storage documentation ([ba4e5d6](https://github.com/TPTBusiness/NexQuant/commit/ba4e5d6ece652e8c1c3b8a713a2e0ea2a0ab225c))
|
||||||
|
* Add v2.0.0 release changelog ([c5e34ff](https://github.com/TPTBusiness/NexQuant/commit/c5e34ff7aaa2d30a159b05f4e6ecc853b8a4f79e))
|
||||||
|
* Clean changelog of closed-source performance metrics ([7dc2ecd](https://github.com/TPTBusiness/NexQuant/commit/7dc2ecdc8dbf4ef0a2936ab1f1e0c0469ca95e9c))
|
||||||
|
* Create changelog/ directory with v1.0.0.md release notes ([ddefcd4](https://github.com/TPTBusiness/NexQuant/commit/ddefcd420a9d98fc6548e14cfc94caffd2068963))
|
||||||
|
* Final system completion - all 9 phases done ([ab541de](https://github.com/TPTBusiness/NexQuant/commit/ab541de9b3ca4cdf62f14f97d540460fc333fca9))
|
||||||
|
* fix duplicate sections, add hardware requirements and data setup guide ([cc85cd4](https://github.com/TPTBusiness/NexQuant/commit/cc85cd482ac7169fbe98468539899a2ce561e70d))
|
||||||
|
* improve README badges, fix llama-server flags, clean up structure ([7981a6a](https://github.com/TPTBusiness/NexQuant/commit/7981a6a4d1517950f4124a78642db3f15fde03ba))
|
||||||
|
* Remove 'Inspired by' comments and add comprehensive Acknowledgments ([d5dc48a](https://github.com/TPTBusiness/NexQuant/commit/d5dc48a6bdd519d0ce159d21ca9bbc46b7996313))
|
||||||
|
* Simplify README for git-clone-only installation ([a1e3bb9](https://github.com/TPTBusiness/NexQuant/commit/a1e3bb903c31cea3ea4c5e572bc639352e3215ae))
|
||||||
|
* Translate all code comments to English ([cff6c2a](https://github.com/TPTBusiness/NexQuant/commit/cff6c2a55e0b465a3f30ab802f02e3b4583025bc))
|
||||||
|
* Translate data_config.yaml to English ([b5221b7](https://github.com/TPTBusiness/NexQuant/commit/b5221b761f51bcf2b7b14c7bdfabfa2e9629a3b0))
|
||||||
|
* Translate server.py comments to English ([7fd7592](https://github.com/TPTBusiness/NexQuant/commit/7fd75922f89d6358c1ce48fd886ffbca10537531))
|
||||||
|
* Translate server.py docstring to English ([d5acaa0](https://github.com/TPTBusiness/NexQuant/commit/d5acaa0c036913776eef6bb01083cce2942dc16c))
|
||||||
|
* update configuration docs ([#1155](https://github.com/TPTBusiness/NexQuant/issues/1155)) ([56ed919](https://github.com/TPTBusiness/NexQuant/commit/56ed919b2e44f4398ac304a4f6cdf099dd382096))
|
||||||
|
* update license section from MIT to AGPL-3.0 ([ff441a4](https://github.com/TPTBusiness/NexQuant/commit/ff441a49fe0b45c31b1702b8bd22d5c8edd37abb))
|
||||||
|
* Update QWEN.md with complete 5-phase architecture and results ([66e1798](https://github.com/TPTBusiness/NexQuant/commit/66e17981fd9241d9ee6f50be05142ee201b761a8))
|
||||||
|
* Update QWEN.md with detailed Git history correction guide ([a972772](https://github.com/TPTBusiness/NexQuant/commit/a97277298d3d5f122905d7e02b58568224b86b40))
|
||||||
|
* Update QWEN.md with implementation guide ([23af142](https://github.com/TPTBusiness/NexQuant/commit/23af142af0b127600c61ba3623f3538abf1c881c))
|
||||||
|
* Update SECURITY.md and CONTRIBUTING.md ([e40f659](https://github.com/TPTBusiness/NexQuant/commit/e40f6594441e195041ccb58072483fe8704eac4c))
|
||||||
|
* Update TODO.md with v1.0.0 completed items and future roadmap ([2d3ca5b](https://github.com/TPTBusiness/NexQuant/commit/2d3ca5bec66e81b37ce7bf4086f24556f6cad134))
|
||||||
|
|
||||||
|
|
||||||
### Features
|
### Miscellaneous Chores
|
||||||
|
|
||||||
* add daily log rotation, llama health wait, factor auto-fixer, and README updates ([4ae4d6f](https://github.com/TPTBusiness/Predix/commit/4ae4d6f0f1388d229e44333130306ae05767f2e5))
|
* release 0.8.0 ([8c15238](https://github.com/TPTBusiness/NexQuant/commit/8c1523802c3c0237eae27ebef3e155af2cddd05e))
|
||||||
* Add GitHub infrastructure, CI/CD pipelines, and examples ([a0b5dc4](https://github.com/TPTBusiness/Predix/commit/a0b5dc464eaac831c76bdbf805cf60c9083e7d80))
|
|
||||||
* **factor-coder:** Add critical rules to prevent common factor implementation errors ([a1edca8](https://github.com/TPTBusiness/Predix/commit/a1edca87dd5e75ee402ea555f1b7a07b45c4b1f0))
|
## [1.4.2](https://github.com/TPTBusiness/NexQuant/compare/v1.4.1...v1.4.2) (2026-05-03)
|
||||||
* **logging:** write complete LLM prompts and responses to daily JSONL log ([803ef13](https://github.com/TPTBusiness/Predix/commit/803ef13052c645392e71aa5de24874aae83f62a7))
|
|
||||||
* **strategy:** Continuous optimization with Optuna parameter injection ([4fda5ea](https://github.com/TPTBusiness/Predix/commit/4fda5eaa31bc570e295ad96380ee2c02b82db706))
|
|
||||||
* unified backtest engine, LLM error handling, strategy refactor ([76b9341](https://github.com/TPTBusiness/Predix/commit/76b9341fe8ef0ff03fd911337c299cf0e8582f37))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* Add critical column name rules to factor generation prompt ([3e74410](https://github.com/TPTBusiness/Predix/commit/3e7441079f0f1c5867829a365c6e45cd7d2071df))
|
* add missing sys import and fix undefined acc_rate in factor eval ([c45f990](https://github.com/TPTBusiness/NexQuant/commit/c45f9908ee321400f0a19c57f1482e4cd1394a50))
|
||||||
* **ci:** fix closed-source asset check false positives in security workflow ([4b83c2b](https://github.com/TPTBusiness/Predix/commit/4b83c2bfe7e90c0c7a11116f07a1b989035b7a3f))
|
|
||||||
* **ci:** remove CodeQL workflow (conflicts with default setup), drop duplicate lint job ([a671361](https://github.com/TPTBusiness/Predix/commit/a671361ee4de9a7e00ccc66d8fd5732c2ed1fee9))
|
## [1.4.1](https://github.com/TPTBusiness/NexQuant/compare/v1.4.0...v1.4.1) (2026-05-03)
|
||||||
* **ci:** set JAVA_TOOL_OPTIONS UTF-8 in Codacy workflow ([e36721c](https://github.com/TPTBusiness/Predix/commit/e36721c765a02a325b8a7dfd3c262b2aca7b1652))
|
|
||||||
* **deps:** pin aiohttp>=3.13.4 to patch 4 CVEs ([81adddc](https://github.com/TPTBusiness/Predix/commit/81adddcfcd14819a1f85c06288a663e7d222a8fb))
|
|
||||||
* **optuna:** fix inverted parameter range in Stage 2/3 when signal_bias is negative ([eaf885e](https://github.com/TPTBusiness/Predix/commit/eaf885ec2d20ebd93e34d1e2cb445532d2fb0ed3))
|
### Bug Fixes
|
||||||
* **security:** Patch 5 CodeQL path injection and clear-text logging alerts ([#22](https://github.com/TPTBusiness/Predix/issues/22)-[#25](https://github.com/TPTBusiness/Predix/issues/25), [#9](https://github.com/TPTBusiness/Predix/issues/9)) ([d386af9](https://github.com/TPTBusiness/Predix/commit/d386af98205722d1ea6d1465f585e89cb8df47de))
|
|
||||||
* **security:** Patch 5 CodeQL path injection and weak hashing alerts ([#25](https://github.com/TPTBusiness/Predix/issues/25)-[#30](https://github.com/TPTBusiness/Predix/issues/30)) ([0d4c3b7](https://github.com/TPTBusiness/Predix/commit/0d4c3b7d69fdbdaafab00940bf7346c8b664928e))
|
* 15 bug fixes across orchestrator, runner, backtest, and infrastructure ([163687d](https://github.com/TPTBusiness/NexQuant/commit/163687d7e1c278a085d7052a3f958a3edb501e77))
|
||||||
* **security:** Patch path injection and stack trace exposure (CodeQL [#31](https://github.com/TPTBusiness/Predix/issues/31), [#27](https://github.com/TPTBusiness/Predix/issues/27)) ([b0b8432](https://github.com/TPTBusiness/Predix/commit/b0b84328d13dac5c2ef79961200b011c0b5778f1))
|
* also catch ValueError in mean_variance for dimension mismatch ([ed73b72](https://github.com/TPTBusiness/NexQuant/commit/ed73b7253f7dc6459ee30dd81a1ce1194e46e9af))
|
||||||
* **security:** replace relative_to() with realpath+startswith for CodeQL sanitization ([6d70f1e](https://github.com/TPTBusiness/Predix/commit/6d70f1ed944180c44d0eb75c0e86b013e5888b60))
|
* close log file handle, fix FTMO equity double-count, remove bare except ([76219a5](https://github.com/TPTBusiness/NexQuant/commit/76219a53efddaafc2b8bd48a0f76c1d4325e6ea5))
|
||||||
* **security:** resolve CodeQL path-injection alerts in UI data loaders ([cced426](https://github.com/TPTBusiness/Predix/commit/cced426916cb726e95ad251dcbc0eb9ab6ec3591))
|
* correct project root paths and subprocess handling in parallel runner and CLI ([9735e3a](https://github.com/TPTBusiness/NexQuant/commit/9735e3a4d8f01e7b16fb9b185a002396a915cea4))
|
||||||
* **security:** resolve CodeQL path-injection and clear-text-logging alerts ([ec50224](https://github.com/TPTBusiness/Predix/commit/ec50224c3580c5c82ddba02fe77af95efd9667ea))
|
* filter NaN in max(), remove redundant ternary, handle non-finite vbt results ([f89fbb3](https://github.com/TPTBusiness/NexQuant/commit/f89fbb3421faf6ccdc8e68a911fd9db2c166120f))
|
||||||
* **security:** Resolve GitHub Security Scan alerts ([6c85ba8](https://github.com/TPTBusiness/Predix/commit/6c85ba833a48326e39006e0f73c506b29a594bde))
|
* fix type annotation, remove unused parameter, improve import_class errors ([8b6ab73](https://github.com/TPTBusiness/NexQuant/commit/8b6ab735c05629bf6b76ddc2fd8b15617600cad7))
|
||||||
* **security:** Upgrade vllm and transformers to patch 4 CVEs ([6c9ba91](https://github.com/TPTBusiness/Predix/commit/6c9ba91d3bf7ce1ed389e544c68be55262bf4e28))
|
* resolve dead code, shell injection risk, mutable defaults, and other bugs ([afff262](https://github.com/TPTBusiness/NexQuant/commit/afff26287f7c4df7ddfde4e816d280fe845e11eb))
|
||||||
* **strategy:** Fix template variables, APIBackend import, and JSON extraction ([8220faa](https://github.com/TPTBusiness/Predix/commit/8220faa3de6ea555717ac29ba90a3b68135fbf9e))
|
* resolve unbound variable, logger shadowing, withdraw_loop edge case, and other bugs in main scripts ([748cf9b](https://github.com/TPTBusiness/NexQuant/commit/748cf9b214a3e8447f1289fc4cf1e92ad6cc2f1a))
|
||||||
* **strategy:** Re-evaluate Optuna-optimized strategies with full OHLCV backtest ([026edce](https://github.com/TPTBusiness/Predix/commit/026edce122284fb1da467e6e9de8a2b9116c7ace))
|
|
||||||
|
## [1.4.0](https://github.com/TPTBusiness/NexQuant/compare/v1.3.11...v1.4.0) (2026-05-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **optimizer:** add max_positions parameter to Optuna search space ([fdb4be3](https://github.com/TPTBusiness/NexQuant/commit/fdb4be3b3ebd93325e7821f4251148424184a40d))
|
||||||
|
|
||||||
|
## [1.3.11](https://github.com/TPTBusiness/NexQuant/compare/v1.3.10...v1.3.11) (2026-05-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **ci:** lazy import logger in nexquant.py and cli.py to avoid ImportError in test env ([60763e8](https://github.com/TPTBusiness/NexQuant/commit/60763e8eae34f41865ba8e5e65bdfde13b564b4b))
|
||||||
|
|
||||||
|
## [1.3.10](https://github.com/TPTBusiness/NexQuant/compare/v1.3.9...v1.3.10) (2026-05-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **security:** replace remaining assert statements with proper error handling ([928533d](https://github.com/TPTBusiness/NexQuant/commit/928533d9a81bd5062f07458fbf94d3c7fe347775))
|
||||||
|
|
||||||
|
## [1.3.9](https://github.com/TPTBusiness/NexQuant/compare/v1.3.8...v1.3.9) (2026-05-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **security:** resolve path-injection, B701, B101, B112 Bandit alerts ([20b89a0](https://github.com/TPTBusiness/NexQuant/commit/20b89a061843b39836e975f158404e8e2d4627cd))
|
||||||
|
|
||||||
|
## [1.3.8](https://github.com/TPTBusiness/NexQuant/compare/v1.3.7...v1.3.8) (2026-04-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **deps:** relax aiohttp constraint to >=3.13.4 for litellm compatibility ([34ab192](https://github.com/TPTBusiness/NexQuant/commit/34ab1923a887089eb36e5cbad6cb8df16f0333ca))
|
||||||
|
* **qlib:** correct indentation in except blocks in quant_proposal and factor_runner ([8143451](https://github.com/TPTBusiness/NexQuant/commit/8143451e8c0ead01c4d86d19669268c7bfb15fac))
|
||||||
|
* **security:** replace eval() with ast.literal_eval in finetune validator (B307) ([0508caf](https://github.com/TPTBusiness/NexQuant/commit/0508caf9140d210b823fefefa28ee535ec85a0ae))
|
||||||
|
* **security:** replace shell=True subprocess calls with list args in env.py (B602) ([2012d5a](https://github.com/TPTBusiness/NexQuant/commit/2012d5ae4e77cc2f1ab9a48beaaac5a74695d083))
|
||||||
|
* **security:** resolve path-injection and add nosec for safe temp paths (B108, py/path-injection) ([6727480](https://github.com/TPTBusiness/NexQuant/commit/67274803bd1d14e5d1df9a063f46b2edb8501a2b))
|
||||||
|
|
||||||
|
## [1.3.7](https://github.com/TPTBusiness/NexQuant/compare/v1.3.6...v1.3.7) (2026-04-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **security:** nosec for B608/B701 false positives in UI and template code ([5eb5d7e](https://github.com/TPTBusiness/NexQuant/commit/5eb5d7e8fdbe90e0dced83fef4e09f5a33e96b2b))
|
||||||
|
* **security:** replace eval() with ast.literal_eval and add request timeouts (B307, B113) ([3301ada](https://github.com/TPTBusiness/NexQuant/commit/3301ada697ca7d3afa1a188d2a76a87ae98b4529))
|
||||||
|
* **security:** replace shell=True subprocess calls with list args (B602) ([13c08f4](https://github.com/TPTBusiness/NexQuant/commit/13c08f4ce6813eb7c314087921ec8c0f40074bd7))
|
||||||
|
|
||||||
|
## [1.3.6](https://github.com/TPTBusiness/NexQuant/compare/v1.3.5...v1.3.6) (2026-04-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **security:** real fix for B110 (logging in factor_proposal.py [#746](https://github.com/TPTBusiness/NexQuant/issues/746)) ([16624e0](https://github.com/TPTBusiness/NexQuant/commit/16624e0bd966ae4d24c4a3eb42bbc31c11da3136))
|
||||||
|
* **security:** real fix for B110 (logging in factor_runner.py [#744](https://github.com/TPTBusiness/NexQuant/issues/744)) ([88cf0fb](https://github.com/TPTBusiness/NexQuant/commit/88cf0fb8828b11c97f2f3ae2881a4900b020c6f0))
|
||||||
|
* **security:** real fix for B110 (logging in quant_proposal.py [#741](https://github.com/TPTBusiness/NexQuant/issues/741)) ([7cf2a64](https://github.com/TPTBusiness/NexQuant/commit/7cf2a644f553b054bd4b0607ea51e5372e68d90a))
|
||||||
|
* **security:** real fix for B110 (logging in quant_proposal.py [#741](https://github.com/TPTBusiness/NexQuant/issues/741)) ([ef985f8](https://github.com/TPTBusiness/NexQuant/commit/ef985f86035d8dca707c60137e6508349a0c4ae6))
|
||||||
|
* **security:** real fix for B404/B603 (sys.executable in factor_runner.py [#745](https://github.com/TPTBusiness/NexQuant/issues/745)) ([819655a](https://github.com/TPTBusiness/NexQuant/commit/819655aaa3efa76596d60501d0e8ca365df3e5e2))
|
||||||
|
* **security:** revert broken read_pickle encoding arg in kaggle template (B301) ([3574907](https://github.com/TPTBusiness/NexQuant/commit/35749073c91e69f63ddaad61dae3f2b799327e63))
|
||||||
|
* **security:** validate SQL identifiers in _add_column_if_not_exists (B608) ([e10dfa2](https://github.com/TPTBusiness/NexQuant/commit/e10dfa2576038e911f83595d3b466c261bc0cd54))
|
||||||
|
* **security:** whitelist-validate metric column in get_top_factors (B608) ([e50519f](https://github.com/TPTBusiness/NexQuant/commit/e50519fe066e68aec2f19b83df4f643c3c22053d))
|
||||||
|
|
||||||
|
## [1.3.5](https://github.com/TPTBusiness/NexQuant/compare/v1.3.4...v1.3.5) (2026-04-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auto-fixer:** add five new factor code fixes for groupby/apply errors ([449c8fd](https://github.com/TPTBusiness/NexQuant/commit/449c8fd70a327e604dcca122e4a134f0cca918e4))
|
||||||
|
* **auto-fixer:** add four new factor code fixes for common runtime errors ([40484f6](https://github.com/TPTBusiness/NexQuant/commit/40484f6d300425da481f1edd325da4acbc06ec7d))
|
||||||
|
* **auto-fixer:** add groupby([level=N,'date']) SyntaxError fix ([ca77c00](https://github.com/TPTBusiness/NexQuant/commit/ca77c005bea4abdd8854c1de2b0e8d03b7742161))
|
||||||
|
* **auto-fixer:** disable _fix_min_periods for intraday data ([77b0740](https://github.com/TPTBusiness/NexQuant/commit/77b0740f059349df7e769a378af728aa33b2070e))
|
||||||
|
* **auto-fixer:** fix chained groupby(level=N).groupby('date') pattern ([7d5fe32](https://github.com/TPTBusiness/NexQuant/commit/7d5fe32b31a19ce8b04bd8f5a430720fdb748f7a))
|
||||||
|
* **auto-fixer:** fix df.loc[instrument] DateParseError on MultiIndex frames ([b7860ea](https://github.com/TPTBusiness/NexQuant/commit/b7860eafc0ad26384947ce0510ecf4e9f3425807))
|
||||||
|
* **auto-fixer:** fix df['instrument'] KeyError on MultiIndex frames ([aad6bd1](https://github.com/TPTBusiness/NexQuant/commit/aad6bd1c7c720b3d486e0cf248337f32394773b1))
|
||||||
|
* **auto-fixer:** fix two assignment-target bugs in instrument column fixers ([421eedf](https://github.com/TPTBusiness/NexQuant/commit/421eedffed4b883c24397dc5581c019a3985277f))
|
||||||
|
* **auto-fixer:** preserve date dimension in groupby(['instrument','date']) fix ([b58fdd8](https://github.com/TPTBusiness/NexQuant/commit/b58fdd8be43720b5d4363e0f8de9a01591d4d2dc))
|
||||||
|
* **auto-fixer:** remove ddof from rolling() args, not only from std()/var() ([b0fc328](https://github.com/TPTBusiness/NexQuant/commit/b0fc328d0d4a041c65d8eeb32cb3f2bb86568406))
|
||||||
|
* **auto-fixer:** strip spurious .reset_index() after .transform() calls ([8708aae](https://github.com/TPTBusiness/NexQuant/commit/8708aae6e08728cda1875c775a76dc92e43576f3))
|
||||||
|
* **loop:** prevent step_idx advance on unhandled exceptions + fix consecutive assistant messages ([5ec4ad1](https://github.com/TPTBusiness/NexQuant/commit/5ec4ad1b96b5b99ef42bea7bb828cb1ef709a688))
|
||||||
|
|
||||||
|
## [1.3.4](https://github.com/TPTBusiness/NexQuant/compare/v1.3.3...v1.3.4) (2026-04-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auto-fixer:** add five new factor code fixes for groupby/apply errors ([449c8fd](https://github.com/TPTBusiness/NexQuant/commit/449c8fd70a327e604dcca122e4a134f0cca918e4))
|
||||||
|
* **auto-fixer:** add four new factor code fixes for common runtime errors ([40484f6](https://github.com/TPTBusiness/NexQuant/commit/40484f6d300425da481f1edd325da4acbc06ec7d))
|
||||||
|
* **auto-fixer:** add groupby([level=N,'date']) SyntaxError fix ([ca77c00](https://github.com/TPTBusiness/NexQuant/commit/ca77c005bea4abdd8854c1de2b0e8d03b7742161))
|
||||||
|
* **auto-fixer:** disable _fix_min_periods for intraday data ([77b0740](https://github.com/TPTBusiness/NexQuant/commit/77b0740f059349df7e769a378af728aa33b2070e))
|
||||||
|
* **auto-fixer:** fix chained groupby(level=N).groupby('date') pattern ([7d5fe32](https://github.com/TPTBusiness/NexQuant/commit/7d5fe32b31a19ce8b04bd8f5a430720fdb748f7a))
|
||||||
|
* **auto-fixer:** fix df.loc[instrument] DateParseError on MultiIndex frames ([b7860ea](https://github.com/TPTBusiness/NexQuant/commit/b7860eafc0ad26384947ce0510ecf4e9f3425807))
|
||||||
|
* **auto-fixer:** fix df['instrument'] KeyError on MultiIndex frames ([aad6bd1](https://github.com/TPTBusiness/NexQuant/commit/aad6bd1c7c720b3d486e0cf248337f32394773b1))
|
||||||
|
* **auto-fixer:** preserve date dimension in groupby(['instrument','date']) fix ([b58fdd8](https://github.com/TPTBusiness/NexQuant/commit/b58fdd8be43720b5d4363e0f8de9a01591d4d2dc))
|
||||||
|
* **auto-fixer:** remove ddof from rolling() args, not only from std()/var() ([b0fc328](https://github.com/TPTBusiness/NexQuant/commit/b0fc328d0d4a041c65d8eeb32cb3f2bb86568406))
|
||||||
|
* **backtest:** replace broken MC permutation test with binomial win-rate test ([c38d894](https://github.com/TPTBusiness/NexQuant/commit/c38d89478f586825bfca5715a96ca70ccd8791a3))
|
||||||
|
* **factors:** detect and correct look-ahead bias in daily-constant factors ([eb490a4](https://github.com/TPTBusiness/NexQuant/commit/eb490a461b66cbd815ae53ac5205115754712432))
|
||||||
|
* **factors:** extend look-ahead rules to session factors and add intraday-factor guidance ([c24c100](https://github.com/TPTBusiness/NexQuant/commit/c24c100442d6487686c0578de0b32d240fcbf215))
|
||||||
|
* **loop:** compress old experiment history in proposal prompt to reduce context size ([4bf90a9](https://github.com/TPTBusiness/NexQuant/commit/4bf90a905ba8b2aba2a818191c19998088cccaaf))
|
||||||
|
* **loop:** prevent step_idx advance on unhandled exceptions + fix consecutive assistant messages ([5ec4ad1](https://github.com/TPTBusiness/NexQuant/commit/5ec4ad1b96b5b99ef42bea7bb828cb1ef709a688))
|
||||||
|
|
||||||
|
## [1.3.3](https://github.com/TPTBusiness/NexQuant/compare/v1.3.2...v1.3.3) (2026-04-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **backtest:** replace broken MC permutation test with binomial win-rate test ([c38d894](https://github.com/TPTBusiness/NexQuant/commit/c38d89478f586825bfca5715a96ca70ccd8791a3))
|
||||||
|
* **factors:** detect and correct look-ahead bias in daily-constant factors ([eb490a4](https://github.com/TPTBusiness/NexQuant/commit/eb490a461b66cbd815ae53ac5205115754712432))
|
||||||
|
* **factors:** extend look-ahead rules to session factors and add intraday-factor guidance ([c24c100](https://github.com/TPTBusiness/NexQuant/commit/c24c100442d6487686c0578de0b32d240fcbf215))
|
||||||
|
* **loop:** compress old experiment history in proposal prompt to reduce context size ([4bf90a9](https://github.com/TPTBusiness/NexQuant/commit/4bf90a905ba8b2aba2a818191c19998088cccaaf))
|
||||||
|
* **strategies:** guard against None IC in acceptance check, disable slow wf_rolling ([2197f52](https://github.com/TPTBusiness/NexQuant/commit/2197f52150a50ef38d9e70991d7e48c8c30caec4))
|
||||||
|
* **strategies:** handle None ic/sharpe/dd in rejected strategy log output ([ad2ad3a](https://github.com/TPTBusiness/NexQuant/commit/ad2ad3ab3360ea75ed3bbc90c12098b9c5cc0114))
|
||||||
|
|
||||||
|
## [1.3.2](https://github.com/TPTBusiness/NexQuant/compare/v1.3.1...v1.3.2) (2026-04-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **strategies:** guard against None IC in acceptance check, disable slow wf_rolling ([2197f52](https://github.com/TPTBusiness/NexQuant/commit/2197f52150a50ef38d9e70991d7e48c8c30caec4))
|
||||||
|
* **strategies:** handle None ic/sharpe/dd in rejected strategy log output ([ad2ad3a](https://github.com/TPTBusiness/NexQuant/commit/ad2ad3ab3360ea75ed3bbc90c12098b9c5cc0114))
|
||||||
|
|
||||||
|
## [1.3.1](https://github.com/TPTBusiness/NexQuant/compare/v1.3.0...v1.3.1) (2026-04-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **deps:** bump python-dotenv to >=1.2.2 (CVE symlink overwrite) ([126ae7d](https://github.com/TPTBusiness/NexQuant/commit/126ae7d5fb556b677d09d10221862a0d648d697a))
|
||||||
|
|
||||||
|
## [1.3.0](https://github.com/TPTBusiness/NexQuant/compare/v1.2.2...v1.3.0) (2026-04-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **backtest:** add rolling walk-forward validation and Monte Carlo trade permutation test ([637a94c](https://github.com/TPTBusiness/NexQuant/commit/637a94c1d987da763869f4f9b73372a3f37d873c))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **security:** resolve all 30 Bandit security alerts (B301, B614, B104) ([ce5983d](https://github.com/TPTBusiness/NexQuant/commit/ce5983d9d59c4c34341fb1ec749e44bbcfc4a1c4))
|
||||||
|
|
||||||
|
## [1.2.2](https://github.com/TPTBusiness/NexQuant/compare/v1.2.1...v1.2.2) (2026-04-19)
|
||||||
|
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
* Add CLI welcome screenshot to README ([e6f2374](https://github.com/TPTBusiness/Predix/commit/e6f237437595745406c310b58a9bd7214ff914ae))
|
* **claude:** auto-merge release-please PR after every push ([f500917](https://github.com/TPTBusiness/NexQuant/commit/f500917b699ee78dc676e84e01574d49bdc8e796))
|
||||||
* Add comprehensive data setup guide to README ([f721d53](https://github.com/TPTBusiness/Predix/commit/f721d53e5681be6997418c13acc3439897168048))
|
|
||||||
* Add conda requirement to README + fix predix CLI ([df45698](https://github.com/TPTBusiness/Predix/commit/df45698b20e0a3e6e0079decf2b8eecb6983a175))
|
## [2.2.0](https://github.com/TPTBusiness/NexQuant/compare/v2.1.0...v2.2.0) (2026-04-18)
|
||||||
* Clean changelog of closed-source performance metrics ([a0f6587](https://github.com/TPTBusiness/Predix/commit/a0f6587ab1724293924da07fe18c40891ca612a1))
|
|
||||||
* improve README badges, fix llama-server flags, clean up structure ([336e1a5](https://github.com/TPTBusiness/Predix/commit/336e1a5afb4933ec13572ef050a3e5a2ca183400))
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add Kronos CLI commands, expand tests, document in README ([f911081](https://github.com/TPTBusiness/NexQuant/commit/f911081d1763d0dc4dd790b57dd97aae2dc62679))
|
||||||
|
* **fin_quant:** auto-generate Kronos factor before loop start ([277063f](https://github.com/TPTBusiness/NexQuant/commit/277063f3e36cd071db859cdc77f69135c1f0763b))
|
||||||
|
* integrate Kronos-mini OHLCV foundation model (Option A + B) ([4ae3b99](https://github.com/TPTBusiness/NexQuant/commit/4ae3b99f2450930f72e202a1a470c407bfde3328))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **kronos:** lazy torch import to fix CI ModuleNotFoundError ([ccc1d27](https://github.com/TPTBusiness/NexQuant/commit/ccc1d27dbe5ab06a57085a589d456ac7bf49cc08))
|
||||||
|
* **kronos:** pass actual datetime Series to Kronos predictor timestamps ([dc6e7ce](https://github.com/TPTBusiness/NexQuant/commit/dc6e7ce207d21fbc21976f2af7691058530fac2f))
|
||||||
|
* **kronos:** replace rdagent_logger with stdlib logging for CI compatibility ([b4558f2](https://github.com/TPTBusiness/NexQuant/commit/b4558f2456659c6109bd1b3cf100510491cd3e6c))
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **kronos:** batch GPU inference via predict_batch — 75x faster ([74611d0](https://github.com/TPTBusiness/NexQuant/commit/74611d071ac123a655eb15d0737bb73b8c1bd2b0))
|
||||||
|
* **kronos:** batch GPU inference via predict_batch — 75x faster ([2babeb9](https://github.com/TPTBusiness/NexQuant/commit/2babeb95f42828e13a37dc16166c75538f33fd4b))
|
||||||
|
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
* fix duplicate sections, add hardware requirements and data setup guide ([6c771b3](https://github.com/TPTBusiness/NexQuant/commit/6c771b37e6f88526a896499e86929cfca2c199eb))
|
||||||
|
|
||||||
|
## [2.1.0](https://github.com/TPTBusiness/NexQuant/compare/v2.0.0...v2.1.0) (2026-04-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add daily log rotation, llama health wait, factor auto-fixer, and README updates ([4ae4d6f](https://github.com/TPTBusiness/NexQuant/commit/4ae4d6f0f1388d229e44333130306ae05767f2e5))
|
||||||
|
* Add GitHub infrastructure, CI/CD pipelines, and examples ([a0b5dc4](https://github.com/TPTBusiness/NexQuant/commit/a0b5dc464eaac831c76bdbf805cf60c9083e7d80))
|
||||||
|
* **factor-coder:** Add critical rules to prevent common factor implementation errors ([a1edca8](https://github.com/TPTBusiness/NexQuant/commit/a1edca87dd5e75ee402ea555f1b7a07b45c4b1f0))
|
||||||
|
* **logging:** write complete LLM prompts and responses to daily JSONL log ([803ef13](https://github.com/TPTBusiness/NexQuant/commit/803ef13052c645392e71aa5de24874aae83f62a7))
|
||||||
|
* **strategy:** Continuous optimization with Optuna parameter injection ([4fda5ea](https://github.com/TPTBusiness/NexQuant/commit/4fda5eaa31bc570e295ad96380ee2c02b82db706))
|
||||||
|
* unified backtest engine, LLM error handling, strategy refactor ([76b9341](https://github.com/TPTBusiness/NexQuant/commit/76b9341fe8ef0ff03fd911337c299cf0e8582f37))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Add critical column name rules to factor generation prompt ([3e74410](https://github.com/TPTBusiness/NexQuant/commit/3e7441079f0f1c5867829a365c6e45cd7d2071df))
|
||||||
|
* **ci:** fix closed-source asset check false positives in security workflow ([4b83c2b](https://github.com/TPTBusiness/NexQuant/commit/4b83c2bfe7e90c0c7a11116f07a1b989035b7a3f))
|
||||||
|
* **ci:** remove CodeQL workflow (conflicts with default setup), drop duplicate lint job ([a671361](https://github.com/TPTBusiness/NexQuant/commit/a671361ee4de9a7e00ccc66d8fd5732c2ed1fee9))
|
||||||
|
* **ci:** set JAVA_TOOL_OPTIONS UTF-8 in Codacy workflow ([e36721c](https://github.com/TPTBusiness/NexQuant/commit/e36721c765a02a325b8a7dfd3c262b2aca7b1652))
|
||||||
|
* **deps:** pin aiohttp>=3.13.4 to patch 4 CVEs ([81adddc](https://github.com/TPTBusiness/NexQuant/commit/81adddcfcd14819a1f85c06288a663e7d222a8fb))
|
||||||
|
* **optuna:** fix inverted parameter range in Stage 2/3 when signal_bias is negative ([eaf885e](https://github.com/TPTBusiness/NexQuant/commit/eaf885ec2d20ebd93e34d1e2cb445532d2fb0ed3))
|
||||||
|
* **security:** Patch 5 CodeQL path injection and clear-text logging alerts ([#22](https://github.com/TPTBusiness/NexQuant/issues/22)-[#25](https://github.com/TPTBusiness/NexQuant/issues/25), [#9](https://github.com/TPTBusiness/NexQuant/issues/9)) ([d386af9](https://github.com/TPTBusiness/NexQuant/commit/d386af98205722d1ea6d1465f585e89cb8df47de))
|
||||||
|
* **security:** Patch 5 CodeQL path injection and weak hashing alerts ([#25](https://github.com/TPTBusiness/NexQuant/issues/25)-[#30](https://github.com/TPTBusiness/NexQuant/issues/30)) ([0d4c3b7](https://github.com/TPTBusiness/NexQuant/commit/0d4c3b7d69fdbdaafab00940bf7346c8b664928e))
|
||||||
|
* **security:** Patch path injection and stack trace exposure (CodeQL [#31](https://github.com/TPTBusiness/NexQuant/issues/31), [#27](https://github.com/TPTBusiness/NexQuant/issues/27)) ([b0b8432](https://github.com/TPTBusiness/NexQuant/commit/b0b84328d13dac5c2ef79961200b011c0b5778f1))
|
||||||
|
* **security:** replace relative_to() with realpath+startswith for CodeQL sanitization ([6d70f1e](https://github.com/TPTBusiness/NexQuant/commit/6d70f1ed944180c44d0eb75c0e86b013e5888b60))
|
||||||
|
* **security:** resolve CodeQL path-injection alerts in UI data loaders ([cced426](https://github.com/TPTBusiness/NexQuant/commit/cced426916cb726e95ad251dcbc0eb9ab6ec3591))
|
||||||
|
* **security:** resolve CodeQL path-injection and clear-text-logging alerts ([ec50224](https://github.com/TPTBusiness/NexQuant/commit/ec50224c3580c5c82ddba02fe77af95efd9667ea))
|
||||||
|
* **security:** Resolve GitHub Security Scan alerts ([6c85ba8](https://github.com/TPTBusiness/NexQuant/commit/6c85ba833a48326e39006e0f73c506b29a594bde))
|
||||||
|
* **security:** Upgrade vllm and transformers to patch 4 CVEs ([6c9ba91](https://github.com/TPTBusiness/NexQuant/commit/6c9ba91d3bf7ce1ed389e544c68be55262bf4e28))
|
||||||
|
* **strategy:** Fix template variables, APIBackend import, and JSON extraction ([8220faa](https://github.com/TPTBusiness/NexQuant/commit/8220faa3de6ea555717ac29ba90a3b68135fbf9e))
|
||||||
|
* **strategy:** Re-evaluate Optuna-optimized strategies with full OHLCV backtest ([026edce](https://github.com/TPTBusiness/NexQuant/commit/026edce122284fb1da467e6e9de8a2b9116c7ace))
|
||||||
|
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
* Add CLI welcome screenshot to README ([e6f2374](https://github.com/TPTBusiness/NexQuant/commit/e6f237437595745406c310b58a9bd7214ff914ae))
|
||||||
|
* Add comprehensive data setup guide to README ([f721d53](https://github.com/TPTBusiness/NexQuant/commit/f721d53e5681be6997418c13acc3439897168048))
|
||||||
|
* Add conda requirement to README + fix nexquant CLI ([df45698](https://github.com/TPTBusiness/NexQuant/commit/df45698b20e0a3e6e0079decf2b8eecb6983a175))
|
||||||
|
* Clean changelog of closed-source performance metrics ([a0f6587](https://github.com/TPTBusiness/NexQuant/commit/a0f6587ab1724293924da07fe18c40891ca612a1))
|
||||||
|
* improve README badges, fix llama-server flags, clean up structure ([336e1a5](https://github.com/TPTBusiness/NexQuant/commit/336e1a5afb4933ec13572ef050a3e5a2ca183400))
|
||||||
|
|||||||
+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
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported to the community leaders responsible for enforcement at
|
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.
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
## Attribution
|
## 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
|
## Getting Started
|
||||||
|
|
||||||
@@ -15,11 +15,11 @@ grep -r "TODO:"
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Fork the repository on GitHub, then clone your fork
|
# Fork the repository on GitHub, then clone your fork
|
||||||
git clone https://github.com/YOUR-USERNAME/Predix.git
|
git clone https://github.com/YOUR-USERNAME/NexQuant.git
|
||||||
cd Predix
|
cd NexQuant
|
||||||
|
|
||||||
# Add upstream remote
|
# 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
|
### 2. Create a Branch
|
||||||
@@ -141,7 +141,7 @@ All PRs are reviewed by maintainers. Expect:
|
|||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
Predix/
|
NexQuant/
|
||||||
├── rdagent/ # Core framework (open source)
|
├── rdagent/ # Core framework (open source)
|
||||||
│ ├── app/ # CLI and scenario apps
|
│ ├── app/ # CLI and scenario apps
|
||||||
│ ├── components/ # Reusable agent components
|
│ ├── components/ # Reusable agent components
|
||||||
@@ -157,8 +157,8 @@ Predix/
|
|||||||
|
|
||||||
## Need Help?
|
## Need Help?
|
||||||
|
|
||||||
- **Issues**: [GitHub Issues](https://github.com/TPTBusiness/Predix/issues)
|
- **Issues**: [GitHub Issues](https://github.com/TPTBusiness/NexQuant/issues)
|
||||||
- **Discussions**: [GitHub Discussions](https://github.com/TPTBusiness/Predix/discussions)
|
- **Discussions**: [GitHub Discussions](https://github.com/TPTBusiness/NexQuant/discussions)
|
||||||
- **Documentation**: See `docs/` folder
|
- **Documentation**: See `docs/` folder
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
@@ -1,21 +1,662 @@
|
|||||||
MIT License
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
Copyright (c) 2025 Predix Team
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Preamble
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
copies or substantial portions of the Software.
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
The licenses for most software and other practical works are designed
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
to take away your freedom to share and change the works. By contrast,
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
our General Public Licenses are intended to guarantee your freedom to
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
share and change all versions of a program--to make sure it remains free
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
software for all its users.
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
Developers that use our General Public Licenses protect your rights
|
||||||
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
|
you this License which gives you legal permission to copy, distribute
|
||||||
|
and/or modify the software.
|
||||||
|
|
||||||
|
A secondary benefit of defending all users' freedom is that
|
||||||
|
improvements made in alternate versions of the program, if they
|
||||||
|
receive widespread use, become available for other developers to
|
||||||
|
incorporate. Many developers of free software are heartened and
|
||||||
|
encouraged by the resulting cooperation. However, in the case of
|
||||||
|
software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and
|
||||||
|
letting the public access it on a server without ever releasing its
|
||||||
|
source code to the public.
|
||||||
|
|
||||||
|
The GNU Affero General Public License is designed specifically to
|
||||||
|
ensure that, in such cases, the modified source code becomes available
|
||||||
|
to the community. It requires the operator of a network server to
|
||||||
|
provide the source code of the modified version running there to the
|
||||||
|
users of that server. Therefore, public use of a modified version, on
|
||||||
|
a publicly accessible server, gives the public access to the source
|
||||||
|
code of the modified version.
|
||||||
|
|
||||||
|
An older license, called the Affero General Public License and
|
||||||
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
|
this license.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the
|
||||||
|
Program, your modified version must prominently offer all users
|
||||||
|
interacting with it remotely through a computer network (if your version
|
||||||
|
supports such interaction) an opportunity to receive the Corresponding
|
||||||
|
Source of your version by providing access to the Corresponding Source
|
||||||
|
from a network server at no charge, through some standard or customary
|
||||||
|
means of facilitating copying of software. This Corresponding Source
|
||||||
|
shall include the Corresponding Source for any work covered by version 3
|
||||||
|
of the GNU General Public License that is incorporated pursuant to the
|
||||||
|
following paragraph.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the work with which it is combined will remain governed by version
|
||||||
|
3 of the GNU General Public License.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU Affero General Public License from time to time. Such new versions
|
||||||
|
will be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU Affero General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU Affero General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU Affero General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
|
||||||
|
Copyright (C) {{ year }} {{ organization }}
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If your software can interact with users remotely through a computer
|
||||||
|
network, you should also make sure that it provides a way for users to
|
||||||
|
get its source. For example, if your program is a web application, its
|
||||||
|
interface could display a "Source" link that leads users to an archive
|
||||||
|
of the code. There are many ways you could offer source, and different
|
||||||
|
solutions will be better for different programs; see section 13 for the
|
||||||
|
specific requirements.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||||
|
<http://www.gnu.org/licenses/>.
|
||||||
|
|||||||
@@ -1,576 +1,228 @@
|
|||||||
# Predix
|
# NexQuant
|
||||||
|
|
||||||
<p align="center">
|
<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">
|
<img src="https://img.shields.io/badge/Python-3.10%20|%203.11-blue?style=for-the-badge&logo=python" alt="Python">
|
||||||
<img src="https://img.shields.io/badge/Platform-Linux-lightgrey?style=for-the-badge&logo=linux" alt="Platform">
|
<img src="https://img.shields.io/badge/Platform-Linux-lightgrey?style=for-the-badge&logo=linux" alt="Platform">
|
||||||
<img src="https://img.shields.io/badge/PyTorch-2.0+-red?style=for-the-badge&logo=pytorch" alt="PyTorch">
|
<img src="https://img.shields.io/badge/Numba-0.59+-00A3E0?style=for-the-badge&logo=numba" alt="Numba">
|
||||||
<img src="https://img.shields.io/badge/Optuna-3.5+-009B77?style=for-the-badge&logo=optuna" alt="Optuna">
|
<img src="https://img.shields.io/badge/Optuna-4.8+-009B77?style=for-the-badge&logo=optuna" alt="Optuna">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://img.shields.io/badge/Pandas-150458?style=for-the-badge&logo=pandas" alt="Pandas">
|
<img src="https://img.shields.io/badge/TA--Lib-0.6+-green?style=for-the-badge" alt="TA-Lib">
|
||||||
<img src="https://img.shields.io/badge/LightGBM-00A1E0?style=for-the-badge" alt="LightGBM">
|
<img src="https://img.shields.io/badge/LightGBM-4.6+-00A1E0?style=for-the-badge" alt="LightGBM">
|
||||||
<img src="https://img.shields.io/badge/Qlib-FF6B6B?style=for-the-badge" alt="Qlib">
|
<img src="https://img.shields.io/badge/Pandas-2.0+-150458?style=for-the-badge&logo=pandas" alt="Pandas">
|
||||||
<img src="https://img.shields.io/badge/llama.cpp-7B68EE?style=for-the-badge" alt="llama.cpp">
|
<img src="https://img.shields.io/badge/cTrader-OpenAPI-FF6B6B?style=for-the-badge" alt="cTrader">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h4 align="center">
|
<h4 align="center">
|
||||||
<strong>AI-powered Quantitative Trading Agent for EUR/USD Forex</strong>
|
<strong>High-Speed Strategy Discovery Framework</strong>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="#installation">Installation</a> •
|
|
||||||
<a href="#no-gpu-use-openrouter">No GPU?</a> •
|
|
||||||
<a href="#quick-start">Quick Start</a> •
|
<a href="#quick-start">Quick Start</a> •
|
||||||
<a href="#configuration">Configuration</a> •
|
<a href="#strategy-discovery">Strategy Discovery</a> •
|
||||||
|
<a href="#live-trading">Live Trading</a> •
|
||||||
<a href="#features">Features</a>
|
<a href="#features">Features</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/TPTBusiness/Predix/actions/workflows/ci.yml">
|
<a href="https://github.com/TPTBusiness/NexQuant/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">
|
<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>
|
||||||
<a href="https://github.com/TPTBusiness/Predix/actions/workflows/codacy.yml">
|
<a href="https://github.com/TPTBusiness/NexQuant/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">
|
<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>
|
||||||
<a href="https://codecov.io/gh/TPTBusiness/Predix">
|
<a href="https://github.com/TPTBusiness/NexQuant/blob/master/LICENSE">
|
||||||
<img src="https://img.shields.io/codecov/c/github/TPTBusiness/Predix?style=flat-square&logo=codecov" alt="Coverage">
|
<img src="https://img.shields.io/github/license/TPTBusiness/NexQuant?style=flat-square" alt="License">
|
||||||
</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>
|
|
||||||
<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">
|
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/astral-sh/ruff">
|
<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">
|
<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>
|
||||||
<a href="https://github.com/TPTBusiness/Predix/stargazers">
|
<a href="https://github.com/TPTBusiness/NexQuant/commits/master">
|
||||||
<img src="https://img.shields.io/github/stars/TPTBusiness/Predix?style=flat-square" alt="Stars">
|
<img src="https://img.shields.io/github/last-commit/TPTBusiness/NexQuant?style=flat-square" alt="Last Commit">
|
||||||
</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>
|
|
||||||
<a href="https://github.com/TPTBusiness/Predix/issues">
|
|
||||||
<img src="https://img.shields.io/github/issues/TPTBusiness/Predix?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>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🖥️ CLI Dashboard
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rdagent predix
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
*The Predix CLI shows system status, available commands, and quick start guide.*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Overview
|
## 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** discovers profitable trading strategies through high-speed search — no LLM required. Core engine: Numba JIT-compiled backtest at **735 million bars/second** (245× faster than pandas). Four discovery methods run in a continuous loop:
|
||||||
|
|
||||||
- 📊 **Data Analysis** – Automatically analyzes market patterns and microstructure
|
| Method | Frequency | Description |
|
||||||
- 💡 **Strategy Discovery** – Proposes novel trading factors and signals
|
|--------|-----------|-------------|
|
||||||
- 🧠 **Model Evolution** – Iteratively improves predictive models
|
| **Explore** | 30% of iterations | Random strategies from 17 TA-Lib indicators across timeframes |
|
||||||
- 📈 **Backtesting** – Validates strategies on historical 1-minute data
|
| **Exploit** | 70% of iterations | Mutate the best-known strategy (change params, indicator, or timeframe) |
|
||||||
|
| **Optuna** | Every 500 iterations | 20-trial hyperparameter optimization on the current best |
|
||||||
|
| **LightGBM** | Every 2000 iterations | ML classifier trained on SOTA indicator signals to predict direction |
|
||||||
|
|
||||||
Predix is optimized for **1-minute EUR/USD FX data** (2020–2026) and uses Qlib as the underlying backtesting engine.
|
**Current best strategy**: MACD(3,10,3) 4-TF with 2/4 vote majority — **+32.0%/month** (Numba), **+24.3%/month** (verified independent backtest), 0/75 negative months.
|
||||||
|
|
||||||
## Acknowledgments
|
> **This repository contains the research framework.** Trading strategies, broker integrations, and live trading infrastructure are available as separate closed-source modules (`git_ignore_folder/`).
|
||||||
|
|
||||||
This project draws inspiration from various open-source projects in the AI trading and multi-agent systems space. We thank all the authors for their innovative work that helped shape our understanding of these patterns.
|
|
||||||
|
|
||||||
Special thanks to:
|
|
||||||
|
|
||||||
- **[Microsoft RD-Agent](https://github.com/microsoft/RD-Agent)** (MIT License) - Foundation for our autonomous R&D agent framework. We extend our gratitude to the RD-Agent team for their excellent foundational work.
|
|
||||||
|
|
||||||
- **[TradingAgents](https://github.com/TauricResearch/TradingAgents)** (Apache 2.0 License) - Inspiration for our multi-agent debate system, reflection mechanism, and memory management modules.
|
|
||||||
|
|
||||||
- **[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.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
### System Requirements
|
|
||||||
|
|
||||||
| Component | Minimum | Recommended |
|
|
||||||
|-----------|---------|-------------|
|
|
||||||
| **GPU VRAM** | 8 GB | 16 GB (RTX 4080 / 5060 Ti) |
|
|
||||||
| **RAM** | 16 GB | 32 GB |
|
|
||||||
| **Storage** | 20 GB | 50 GB (models + data) |
|
|
||||||
| **OS** | Linux (Ubuntu 22.04+) | Linux |
|
|
||||||
| **CUDA** | 12.0+ | 12.4+ |
|
|
||||||
|
|
||||||
> Local LLMs require a CUDA-capable GPU. The default model (Qwen3.6-35B Q3) uses ~13.6 GB VRAM. CPU-only inference is possible but very slow (not recommended for production use).
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- **Conda** (Miniconda or Anaconda) — required for environment management
|
|
||||||
- **Docker** — required for sandboxed factor/model code execution (`docker run hello-world` to verify)
|
|
||||||
- **llama.cpp** — for local LLM inference (see [llama.cpp build guide](https://github.com/ggml-org/llama.cpp))
|
|
||||||
- **Ollama** — for embeddings (`nomic-embed-text`); install from [ollama.com](https://ollama.com) and run `ollama pull nomic-embed-text`
|
|
||||||
- **Linux** — officially supported; macOS/Windows may work with adjustments
|
|
||||||
|
|
||||||
### Quick Install
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Clone repository
|
|
||||||
git clone https://github.com/TPTBusiness/Predix
|
|
||||||
cd Predix
|
|
||||||
|
|
||||||
# Create and activate conda environment
|
|
||||||
conda create -n predix python=3.10 -y
|
|
||||||
conda activate predix
|
|
||||||
|
|
||||||
# Install in editable mode
|
|
||||||
pip install -e .
|
|
||||||
|
|
||||||
# Verify Docker is accessible
|
|
||||||
docker run --rm hello-world
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Important:** Predix 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.
|
|
||||||
|
|
||||||
### Step 1: Get the data
|
|
||||||
|
|
||||||
Download 1-minute EUR/USD data (2020–present) from any of these free sources:
|
|
||||||
|
|
||||||
| Source | Cost | Notes |
|
|
||||||
|--------|------|-------|
|
|
||||||
| **[Dukascopy](https://www.dukascopy.com/swiss/english/marketfeed/historical/)** | Free | Best quality free EUR/USD tick data |
|
|
||||||
| **[OANDA API](https://developer.oanda.com/)** | Free (demo) | Requires API key, programmatic access |
|
|
||||||
| **[TrueFX](https://truefx.com/)** | Free | Institutional-quality tick data |
|
|
||||||
| **[Kaggle](https://www.kaggle.com/datasets?search=EURUSD+1min)** | Free | Search "EURUSD 1 minute" |
|
|
||||||
| **MetaTrader 5** | Free | Export via `copy_rates_range()` |
|
|
||||||
|
|
||||||
### Step 2: Convert to HDF5
|
|
||||||
|
|
||||||
```python
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
df = pd.read_csv('eurusd_1min.csv', parse_dates=['datetime'])
|
|
||||||
df = df.rename(columns={'open': '$open', 'close': '$close',
|
|
||||||
'high': '$high', 'low': '$low', 'volume': '$volume'})
|
|
||||||
df['instrument'] = 'EURUSD'
|
|
||||||
df = df.set_index(['datetime', 'instrument'])
|
|
||||||
for col in ['$open', '$close', '$high', '$low', '$volume']:
|
|
||||||
df[col] = df[col].astype('float32')
|
|
||||||
|
|
||||||
import os
|
|
||||||
os.makedirs('git_ignore_folder/factor_implementation_source_data', exist_ok=True)
|
|
||||||
df.to_hdf('git_ignore_folder/factor_implementation_source_data/intraday_pv.h5', key='data', mode='w')
|
|
||||||
```
|
|
||||||
|
|
||||||
### Required HDF5 format
|
|
||||||
|
|
||||||
| Field | Type | Description |
|
|
||||||
|-------|------|-------------|
|
|
||||||
| **Index** | MultiIndex `(datetime, instrument)` | Timestamp + currency pair |
|
|
||||||
| **`$open`** | float32 | Open price |
|
|
||||||
| **`$close`** | float32 | Close price |
|
|
||||||
| **`$high`** | float32 | High price |
|
|
||||||
| **`$low`** | float32 | Low price |
|
|
||||||
| **`$volume`** | float32 | Tick volume |
|
|
||||||
|
|
||||||
**Save location:** `git_ignore_folder/factor_implementation_source_data/intraday_pv.h5`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
### Environment Setup
|
|
||||||
|
|
||||||
Create a `.env` file in the project root:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Local LLM (llama.cpp)
|
|
||||||
OPENAI_API_KEY=local
|
|
||||||
OPENAI_API_BASE=http://localhost:8081/v1
|
|
||||||
CHAT_MODEL=qwen3.5-35b
|
|
||||||
|
|
||||||
# Embedding (Ollama)
|
|
||||||
LITELLM_PROXY_API_KEY=local
|
|
||||||
LITELLM_PROXY_API_BASE=http://localhost:11434/v1
|
|
||||||
EMBEDDING_MODEL=nomic-embed-text
|
|
||||||
|
|
||||||
# Paths
|
|
||||||
QLIB_DATA_DIR=~/.qlib/qlib_data/eurusd_1min_data
|
|
||||||
```
|
|
||||||
|
|
||||||
### LLM Server (llama.cpp)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
~/llama.cpp/build/bin/llama-server \
|
|
||||||
--model ~/models/qwen3.6/Qwen3.6-35B-A3B-UD-Q3_K_XL.gguf \
|
|
||||||
--n-gpu-layers 24 \
|
|
||||||
--no-mmap \
|
|
||||||
--port 8081 \
|
|
||||||
--ctx-size 240000 \
|
|
||||||
--parallel 2 \
|
|
||||||
--batch-size 512 --ubatch-size 512 \
|
|
||||||
--host 0.0.0.0 \
|
|
||||||
-ctk q4_0 -ctv q4_0 \
|
|
||||||
--reasoning off
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Important flags:**
|
|
||||||
> - `--ctx-size 240000 --parallel 2` — allocates **2 slots × 120,000 tokens each**. `fin_quant` prompts can reach 80k+ tokens with full factor history; a smaller slot causes silent overflow and empty responses.
|
|
||||||
> - `--reasoning off` — **critical**: completely disables Qwen3 chain-of-thought. `--reasoning-budget 0` is not sufficient and produces empty JSON responses.
|
|
||||||
> - `--n-gpu-layers 24` — 4 fewer than maximum on RTX 5060 Ti (16 GB), freeing ~500 MB VRAM for the larger KV cache.
|
|
||||||
> - `-ctk q4_0 -ctv q4_0` — quantises the KV cache to 4-bit, reducing VRAM from ~5 GB to ~1.3 GB at 240k context.
|
|
||||||
|
|
||||||
### Data Configuration
|
|
||||||
|
|
||||||
Edit [`data_config.yaml`](data_config.yaml) to customize walk-forward splits:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
instrument: EURUSD
|
|
||||||
frequency: 1min
|
|
||||||
data_path: ~/.qlib/qlib_data/eurusd_1min_data
|
|
||||||
|
|
||||||
train_start: "2022-03-14"
|
|
||||||
train_end: "2024-06-30"
|
|
||||||
valid_start: "2024-07-01"
|
|
||||||
valid_end: "2024-12-31"
|
|
||||||
test_start: "2025-01-01"
|
|
||||||
test_end: "2026-03-20"
|
|
||||||
|
|
||||||
market_context:
|
|
||||||
spread_bps: 1.5
|
|
||||||
target_arr: 9.62
|
|
||||||
max_drawdown: 20
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
**1. Set up `.env` for OpenRouter:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Chat (OpenRouter)
|
|
||||||
OPENAI_API_KEY=sk-or-v1-<your-openrouter-key>
|
|
||||||
OPENAI_API_BASE=https://openrouter.ai/api/v1
|
|
||||||
CHAT_MODEL=qwen/qwen3-235b-a22b
|
|
||||||
|
|
||||||
# Embedding (Ollama — still required locally)
|
|
||||||
LITELLM_PROXY_API_KEY=local
|
|
||||||
LITELLM_PROXY_API_BASE=http://localhost:11434/v1
|
|
||||||
EMBEDDING_MODEL=nomic-embed-text
|
|
||||||
```
|
|
||||||
|
|
||||||
**2. Skip the llama-server step** — no local LLM server needed.
|
|
||||||
|
|
||||||
**3. Run with the OpenRouter backend:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
> 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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### Prerequisites checklist
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Docker running?
|
# Prerequisites
|
||||||
docker run --rm hello-world
|
conda create -n nexquant python=3.10 -y && conda activate nexquant
|
||||||
|
pip install -e .
|
||||||
|
# Ensure OHLCV data exists: git_ignore_folder/intraday_pv_all.h5
|
||||||
|
|
||||||
# 2. Data in place?
|
# Strategy Discovery Loop (10,000 iterations, ~1 hour)
|
||||||
ls git_ignore_folder/factor_implementation_source_data/intraday_pv.h5
|
python scripts/nexquant_rd_loop.py --iterations 10000
|
||||||
|
|
||||||
# 3. LLM server running?
|
# Price-Action Indicator Loop (grid search all TA-Lib indicators)
|
||||||
curl http://localhost:8081/health
|
python scripts/nexquant_priceaction_loop.py
|
||||||
```
|
|
||||||
|
|
||||||
### 1. Run Trading Loop
|
# Top strategies report
|
||||||
|
python nexquant.py best -n 20 -m monthly_return --min-trades 30
|
||||||
```bash
|
|
||||||
conda activate predix
|
|
||||||
rdagent fin_quant
|
|
||||||
# or with explicit options:
|
|
||||||
rdagent fin_quant --loop-n 5 --step-n 2
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Monitor Results
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Web dashboard
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Run Continuously
|
|
||||||
|
|
||||||
```bash
|
|
||||||
while true; do
|
|
||||||
rdagent fin_quant
|
|
||||||
sleep 5
|
|
||||||
done
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## CLI Commands
|
## Strategy Discovery
|
||||||
|
|
||||||
### Factor & Strategy Loop
|
### R&D Loop (`scripts/nexquant_rd_loop.py`)
|
||||||
|
|
||||||
| Command | Description |
|
```
|
||||||
|---------|-------------|
|
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||||
| `rdagent fin_quant` | Start autonomous factor + model evolution loop |
|
│ Explore │ ──→ │ Exploit │ ──→ │ Optuna │ ──→ │ LightGBM │
|
||||||
| `rdagent fin_quant --loop-n 5` | Run exactly 5 evolution loops |
|
│ (Random) │ │ (Mutate) │ │ (Tuning) │ │ (ML) │
|
||||||
| `rdagent fin_quant --with-dashboard` | Start with web dashboard |
|
└──────────┘ └──────────┘ └──────────┘ └──────────┘
|
||||||
| `rdagent fin_quant --cli-dashboard` | Start with CLI Rich dashboard |
|
30% 70% /500 iter /2000 iter
|
||||||
| `rdagent fin_factor` | Factor-only evolution |
|
```
|
||||||
| `rdagent fin_model` | Model-only evolution |
|
|
||||||
|
|
||||||
### Strategy Reports
|
**17 TA-Lib indicators**: MACD, RSI, Donchian, SAR, ADX, BBANDS, CCI, WCLPRICE, MFI, OBV, STOCH, ROC, AROON, AROONOSC, MOM, ULTOSC, WILLR
|
||||||
|
|
||||||
| Command | Description |
|
**4 timeframes**: 15min, 30min, 1h, 4h
|
||||||
|---------|-------------|
|
|
||||||
| `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` | Generate 10 strategies with LLM + real backtest |
|
|
||||||
| `python predix_gen_strategies_real_bt.py 20` | Generate 20 strategies |
|
|
||||||
|
|
||||||
### Kronos Foundation Model
|
**3 strategy types**: Single-TF, Multi-TF (vote majority), Portfolio (indicator ensemble)
|
||||||
|
|
||||||
| Command | Description |
|
**Discovery example** (50,000 iterations):
|
||||||
|---------|-------------|
|
```
|
||||||
| `python predix.py kronos-factor` | Generate Kronos predicted-return factor (daily stride, ~15 min GPU) |
|
random → SAR(+65) → MACD(+73) → MACD-mutated(+102.75, +32%/month)
|
||||||
| `python predix.py kronos-factor --pred 30` | 30-bar prediction horizon |
|
↓
|
||||||
| `python predix.py kronos-factor --device cpu` | CPU inference (slower) |
|
Optuna tuned params
|
||||||
| `python predix.py kronos-eval` | Evaluate Kronos IC / hit rate vs LightGBM baseline |
|
↓
|
||||||
| `python predix.py kronos-eval --pred 96` | Daily horizon evaluation |
|
LightGBM ensemble
|
||||||
|
```
|
||||||
|
|
||||||
### Factor Evaluation
|
### Grid Search (`scripts/nexquant_priceaction_loop.py`)
|
||||||
|
|
||||||
| Command | Description |
|
Deterministic parameter grid over all 17 indicators. Finds MACD(3,10,3) as optimal.
|
||||||
|---------|-------------|
|
|
||||||
| `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 |
|
|
||||||
|
|
||||||
### Parallel Execution
|
### Portfolio Optimizer (`scripts/nexquant_portfolio_optimizer.py`)
|
||||||
|
|
||||||
| Command | Description |
|
Greedy correlation-aware selection from discovered strategies.
|
||||||
|---------|-------------|
|
|
||||||
| `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 |
|
|
||||||
|
|
||||||
### Monitoring & Debug
|
---
|
||||||
|
|
||||||
| Command | Description |
|
## Live Trading
|
||||||
|---------|-------------|
|
|
||||||
| `rdagent server_ui --port 19899 --log-dir <path>` | Start web dashboard |
|
Closed-source module at `git_ignore_folder/nexquant_live_trader.py`. Architecture:
|
||||||
| `rdagent health_check` | Validate environment setup |
|
|
||||||
| `python predix_batch_backtest.py` | Batch backtest multiple factors |
|
```
|
||||||
| `python predix_rebacktest_strategies.py` | Re-backtest existing strategies |
|
MACD(3,10,3) Signal → cTrader OpenAPI → Live Account
|
||||||
|
4-TF 2/4 Votes (WebSocket+Protobuf) ↓
|
||||||
|
Paper Mode
|
||||||
|
```
|
||||||
|
|
||||||
|
Integration: cTrader WebSocket `live.ctraderapi.com:5035`, OAuth2 authentication, Protobuf message encoding, FIX protocol.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
### 🔄 Iterative Factor Evolution
|
### ⚡ Numba Backtest
|
||||||
|
- 735M bars/second (0.003s for 2.26M bars)
|
||||||
|
- JIT-compiled profit/drawdown/sharpe computation
|
||||||
|
- Signal construction via pandas resample + TA-Lib (~0.4s) is the bottleneck
|
||||||
|
|
||||||
Predix continuously proposes, implements, and validates new alpha factors:
|
### 🔍 Four Discovery Methods
|
||||||
|
- **Explore**: Random indicator + timeframe + parameters
|
||||||
|
- **Exploit**: Mutation of top-5 SOTA strategies (parameter tweak, indicator swap, timeframe change)
|
||||||
|
- **Optuna**: 20-trial TPE hyperparameter optimization on best strategy
|
||||||
|
- **LightGBM**: ML classifier on SOTA indicator signals (80/20 train/test split)
|
||||||
|
|
||||||
- Learns from backtest feedback
|
### 📊 TA-Lib Integration
|
||||||
- Avoids overfitting through walk-forward validation
|
- 17 indicators with full parameter ranges
|
||||||
- Discovers non-obvious patterns in order flow, volatility, and session dynamics
|
- Auto-guard against bad parameters (negative/zero values that crash TA-Lib)
|
||||||
|
- Multi-timeframe voting with configurable threshold
|
||||||
### 🛡️ Trading Protection System
|
|
||||||
|
|
||||||
Automatic risk management to prevent excessive losses:
|
|
||||||
|
|
||||||
- **Max Drawdown Protection** - Pauses trading when drawdown exceeds threshold (default: 15%)
|
|
||||||
- **Cooldown Period** - Enforces mandatory rest period after significant losses (default: 4h after 5% loss)
|
|
||||||
- **Stoploss Guard** - Detects clusters of stoplosses and blocks trading (default: max 5 per day)
|
|
||||||
- **Low Performance Filter** - Filters out consistently underperforming factors (Sharpe < 0.5, Win Rate < 40%)
|
|
||||||
|
|
||||||
### 🧠 Model Architecture Search
|
|
||||||
|
|
||||||
Automatically explores and refines predictive models:
|
|
||||||
|
|
||||||
- Linear baselines (LightGBM, XGBoost)
|
|
||||||
- Deep learning (LSTM, Transformer, Temporal CNN)
|
|
||||||
- Ensemble methods
|
|
||||||
|
|
||||||
### 📚 Knowledge Base
|
|
||||||
|
|
||||||
Built-in knowledge accumulation across loops:
|
|
||||||
|
|
||||||
- Successful factors are archived
|
|
||||||
- Failed attempts inform future proposals
|
|
||||||
- Cross-loop learning improves robustness
|
|
||||||
|
|
||||||
### 🖥️ Interactive UI
|
|
||||||
|
|
||||||
Real-time dashboard for monitoring:
|
|
||||||
|
|
||||||
- Factor performance metrics
|
|
||||||
- Model architecture evolution
|
|
||||||
- Cumulative returns and drawdowns
|
|
||||||
- Code diffs and implementation history
|
|
||||||
|
|
||||||
### 🤖 Kronos Foundation Model Integration
|
|
||||||
|
|
||||||
Predix integrates [Kronos-mini](https://github.com/shiyu-coder/Kronos) — a 4.1M parameter OHLCV foundation model pretrained on 12+ billion K-lines from 45 global exchanges (AAAI 2026, MIT):
|
|
||||||
|
|
||||||
- **Option A — Alpha Factor**: Rolling daily inference generates a `KronosPredReturn` factor. Every 96 bars (one trading day), Kronos predicts the next day's return from the previous 512 bars of EUR/USD OHLCV data. The factor is forward-filled to 1-min frequency and plugs directly into Predix's factor evaluation pipeline.
|
|
||||||
|
|
||||||
- **Option B — Model Evaluation**: Kronos runs alongside LightGBM as a standalone predictor. IC (Information Coefficient), IC IR, and directional hit rate are computed over the full dataset for direct comparison with LightGBM-generated models.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# One-time setup
|
|
||||||
git clone https://github.com/shiyu-coder/Kronos ~/Kronos
|
|
||||||
|
|
||||||
# Generate factor (Option A) — saves to results/factors/
|
|
||||||
python predix.py kronos-factor
|
|
||||||
|
|
||||||
# Evaluate as model (Option B) — prints IC vs LightGBM reference
|
|
||||||
python predix.py kronos-eval
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔒 Security & Quality
|
### 🔒 Security & Quality
|
||||||
|
- 0 Dependabot alerts, 0 CodeScan alerts
|
||||||
Automated quality assurance:
|
- No proprietary terms in git history
|
||||||
|
- Closed-source detection CI
|
||||||
- **134+ Tests** — all features tested automatically on every commit
|
|
||||||
- **Bandit Security Scanner** — pre-commit security checks
|
|
||||||
- **Weekly Dependency Audit** — automated vulnerability scan via GitHub Actions
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
predix/
|
nexquant/
|
||||||
├── rdagent/ # Core agent framework
|
├── scripts/ # Strategy discovery & trading
|
||||||
│ ├── app/ # CLI and scenario apps
|
│ ├── nexquant_rd_loop.py # High-speed R&D loop (Numba + Optuna + ML)
|
||||||
│ ├── components/ # Reusable agent components
|
│ ├── nexquant_priceaction_loop.py # TA-Lib grid search loop
|
||||||
│ │ ├── backtesting/ # Backtest engine & protections
|
│ ├── nexquant_portfolio_optimizer.py # Correlation-aware portfolio selection
|
||||||
│ │ │ ├── backtest_engine.py
|
│ ├── nexquant_gridsearch.py # Deterministic parameter grid search
|
||||||
│ │ │ ├── vbt_backtest.py # Unified backtest engine
|
│ ├── nexquant_daily_strategies.py # Daily Kronos + factor combinations
|
||||||
│ │ │ ├── results_db.py
|
│ ├── nexquant_gen_strategies_real_bt.py # LLM-based strategy generation
|
||||||
│ │ │ └── protections/ # Trading protection system
|
│ ├── nexquant_autopilot.py # 24/7 continuous generator
|
||||||
│ │ └── coder/ # Factor & model coding (CoSTEER + Optuna)
|
│ └── nexquant_parallel.py # Multi-instance parallel runs
|
||||||
│ ├── core/ # Core abstractions
|
├── rdagent/ # Core framework (LLM-based, see note below)
|
||||||
│ ├── scenarios/ # Domain-specific scenarios
|
│ ├── app/ # CLI and scenario apps
|
||||||
│ └── utils/ # Utilities
|
│ ├── components/ # Backtest engine, protections, coders
|
||||||
├── test/ # Test suite (134 tests)
|
│ ├── core/ # Core abstractions
|
||||||
│ └── backtesting/ # Backtest unit tests
|
│ ├── scenarios/ # Domain-specific scenarios
|
||||||
├── web/ # Web UI frontend
|
│ └── utils/ # Utilities
|
||||||
├── data_config.yaml # Walk-forward split configuration
|
├── git_ignore_folder/ # Closed-source (never committed)
|
||||||
├── pyproject.toml # Project metadata
|
│ ├── nexquant_live_trader.py # cTrader live trading
|
||||||
└── requirements.txt # Dependencies
|
│ ├── nexquant_fix_trader.py # FIX protocol trader
|
||||||
|
│ ├── intraday_pv_all.h5 # OHLCV data
|
||||||
|
│ ├── gbpusdt_1min.h5 # GBP/USD data
|
||||||
|
│ └── btc_1min.h5 # BTC data
|
||||||
|
├── test/ # 1,125+ collected tests
|
||||||
|
├── data_config.yaml # Walk-forward split configuration
|
||||||
|
├── requirements.txt # Dependencies
|
||||||
|
└── AGENTS.md # Agent configuration & workflow guide
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **Note on `rdagent/`**: The LLM-based R&D framework (`rdagent fin_quant`) is part of the codebase but the Qlib/CoSTEER pipeline currently produces zero factors. The primary strategy discovery path is the Numba-based loop in `scripts/`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Requirements
|
## Installation
|
||||||
|
|
||||||
Core dependencies (see [`requirements.txt`](requirements.txt) for full list):
|
### Prerequisites
|
||||||
|
- **Conda** (Miniconda or Anaconda)
|
||||||
|
- **TA-Lib** system library (`apt install ta-lib` or `brew install ta-lib`)
|
||||||
|
- **Linux** (Ubuntu 22.04+)
|
||||||
|
|
||||||
- **LLM**: `openai`, `litellm`
|
### Install
|
||||||
- **Data**: `pandas`, `numpy`, `pyarrow`
|
|
||||||
- **ML**: `scikit-learn`, `lightgbm`, `xgboost`
|
```bash
|
||||||
- **Backtesting**: `qlib` (via Docker)
|
git clone https://github.com/TPTBusiness/NexQuant && cd NexQuant
|
||||||
- **UI**: `streamlit`, `plotly`, `flask`
|
conda create -n nexquant python=3.10 -y && conda activate nexquant
|
||||||
|
pip install -e .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data
|
||||||
|
Place OHLCV HDF5 data at `git_ignore_folder/intraday_pv_all.h5`:
|
||||||
|
```python
|
||||||
|
# Format: MultiIndex (datetime, instrument), columns: $open $close $high $low $volume
|
||||||
|
df.to_hdf('git_ignore_folder/intraday_pv_all.h5', key='data')
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the **MIT License** – see the [`LICENSE`](LICENSE) file for details.
|
**GNU Affero General Public License v3.0 (AGPL-3.0)**. See [`LICENSE`](LICENSE).
|
||||||
|
|
||||||
### Attribution Requirements
|
|
||||||
|
|
||||||
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"
|
|
||||||
3. Provide attribution to the original project
|
|
||||||
|
|
||||||
See [`ATTRIBUTION.md`](ATTRIBUTION.md) for detailed guidelines and examples.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Contributions are welcome! Please:
|
|
||||||
|
|
||||||
1. Fork the repository
|
|
||||||
2. Create a feature branch (`git checkout -b feat/my-feature`)
|
|
||||||
3. Commit using [Conventional Commits](https://www.conventionalcommits.org/) (`git commit -m 'feat: add my feature'`)
|
|
||||||
4. Push to the branch (`git push origin feat/my-feature`)
|
|
||||||
5. Open a Pull Request with a conventional commit title
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
```bibtex
|
|
||||||
@misc{yang2025rdagentllmagentframeworkautonomous,
|
|
||||||
title={R&D-Agent: An LLM-Agent Framework Towards Autonomous Data Science},
|
|
||||||
author={Yang, Xu and Yang, Xiao and Fang, Shikai and Zhang, Yifei and Wang, Jian and Xian, Bowen and Li, Qizheng and Li, Jingyuan and Xu, Minrui and Li, Yuante and others},
|
|
||||||
year={2025},
|
|
||||||
eprint={2505.14738},
|
|
||||||
archivePrefix={arXiv},
|
|
||||||
primaryClass={cs.AI}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
- **Issues**: [GitHub Issues](https://github.com/TPTBusiness/Predix/issues)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
Predix is provided "as is" for **research and educational purposes only**. It is **not** intended for:
|
NexQuant is provided for **research and educational purposes only**. Past performance does not guarantee future results. Users assume all liability.
|
||||||
|
|
||||||
- Live trading or financial advice
|
|
||||||
- Production use without thorough testing
|
|
||||||
- Replacement of qualified financial professionals
|
|
||||||
|
|
||||||
Users assume all liability and should comply with applicable laws and regulations in their jurisdiction. Past performance does not guarantee future results.
|
|
||||||
|
|||||||
+2
-2
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
## Reporting a Vulnerability
|
## 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.**
|
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||||
|
|
||||||
### How to Report
|
### 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
|
2. Provide a detailed description of the vulnerability
|
||||||
3. Include steps to reproduce if possible
|
3. Include steps to reproduce if possible
|
||||||
4. We will respond within 48 hours
|
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
|
issues before filing new issues to avoid duplicates. For new issues, file your bug or
|
||||||
feature request as a new Issue.
|
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:
|
For help and questions about using this project, please reach out via:
|
||||||
|
|
||||||
- **Email**: nico@predix.io
|
- **Email**: nico@nexquant.io
|
||||||
- **GitHub Discussions**: [https://github.com/PredixAI/predix/discussions](https://github.com/PredixAI/predix/discussions)
|
- **GitHub Discussions**: [https://github.com/NexQuantAI/nexquant/discussions](https://github.com/NexQuantAI/nexquant/discussions)
|
||||||
|
|
||||||
## Community Support
|
## 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
|
**Release Date:** 2026-04-02
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
## 🎉 Overview
|
## 🎉 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
|
## 🔧 Changed
|
||||||
|
|
||||||
- Rebranded from RD-Agent to Predix for EUR/USD quantitative trading
|
- Rebranded from RD-Agent to NexQuant for EUR/USD quantitative trading
|
||||||
- Updated project metadata for PredixAI organization
|
- Updated project metadata for NexQuantAI organization
|
||||||
- All code comments translated to English
|
- All code comments translated to English
|
||||||
- Removed 'Inspired by' comments, added comprehensive Acknowledgments
|
- Removed 'Inspired by' comments, added comprehensive Acknowledgments
|
||||||
- Enhanced .gitignore for better file management
|
- 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
|
- **TradingAgents** (Apache 2.0 License) - Multi-agent debate patterns
|
||||||
- **ai-hedge-fund** - Macro analysis and risk management concepts
|
- **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**:
|
If you use this code or concepts in your project, you **must**:
|
||||||
1. Include the MIT License text
|
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
|
3. Provide attribution to the original project
|
||||||
|
|
||||||
See [ATTRIBUTION.md](../ATTRIBUTION.md) for detailed guidelines.
|
See [ATTRIBUTION.md](../ATTRIBUTION.md) for detailed guidelines.
|
||||||
@@ -158,7 +158,7 @@ See [ATTRIBUTION.md](../ATTRIBUTION.md) for detailed guidelines.
|
|||||||
|
|
||||||
## 🔗 Links
|
## 🔗 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
|
- **Main Changelog:** ../CHANGELOG.md
|
||||||
- **Attribution Guidelines:** ../ATTRIBUTION.md
|
- **Attribution Guidelines:** ../ATTRIBUTION.md
|
||||||
- **Installation Guide:** ../README.md#installation
|
- **Installation Guide:** ../README.md#installation
|
||||||
@@ -168,7 +168,7 @@ See [ATTRIBUTION.md](../ATTRIBUTION.md) for detailed guidelines.
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
**Made with ❤️ by Predix Team**
|
**Made with ❤️ by NexQuant Team**
|
||||||
|
|
||||||
For detailed usage guidelines, see [README.md](../README.md)
|
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
|
**Release Date:** 2026-04-10
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
## 🎉 Overview
|
## 🎉 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
|
- **Proper Annualization**: sqrt(252*1440) for 1-min data
|
||||||
|
|
||||||
### CLI Commands
|
### 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_llama` - Start llama.cpp server
|
||||||
- `rdagent start_loop` - Start strategy generator loop with auto-restart
|
- `rdagent start_loop` - Start strategy generator loop with auto-restart
|
||||||
- `rdagent generate_strategies` - Generate strategies from factors
|
- `rdagent generate_strategies` - Generate strategies from factors
|
||||||
@@ -65,8 +65,8 @@ Major update adding AI-powered strategy generation, realistic backtesting, and c
|
|||||||
## 📦 Installation
|
## 📦 Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/TPTBusiness/Predix
|
git clone https://github.com/TPTBusiness/NexQuant
|
||||||
cd Predix
|
cd NexQuant
|
||||||
pip install -e .
|
pip install -e .
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ pip install -e .
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Show welcome screen
|
# Show welcome screen
|
||||||
rdagent predix
|
rdagent nexquant
|
||||||
|
|
||||||
# Start LLM server
|
# Start LLM server
|
||||||
rdagent start_llama
|
rdagent start_llama
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Bandit Security Scanner Configuration
|
# Bandit Security Scanner Configuration
|
||||||
# Documentation: https://bandit.readthedocs.io/
|
# 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)
|
# Tests to skip (known false positives or acceptable risks)
|
||||||
skips:
|
skips:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
azure-identity==1.25.3
|
azure-identity==1.25.3
|
||||||
dill==0.4.1
|
dill==0.4.1
|
||||||
pillow==10.4.0
|
pillow==12.2.0
|
||||||
psutil==6.1.1
|
psutil==6.1.1
|
||||||
scipy==1.15.3
|
scipy==1.15.3
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
azure-identity==1.25.3
|
azure-identity==1.25.3
|
||||||
dill==0.4.1
|
dill==0.4.1
|
||||||
pillow==10.4.0
|
pillow==12.2.0
|
||||||
psutil==6.1.1
|
psutil==6.1.1
|
||||||
scipy==1.15.3
|
scipy==1.15.3
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# ============================================================
|
# ============================================================
|
||||||
# Predix Data Configuration
|
# NexQuant Data Configuration
|
||||||
# Change instrument, frequency, and time periods here
|
# Change instrument, frequency, and time periods here
|
||||||
# All other components read from this file
|
# All other components read from this file
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
+7
-7
@@ -1,6 +1,6 @@
|
|||||||
# Attribution Guidelines
|
# Attribution Guidelines
|
||||||
|
|
||||||
## Using Predix in Your Project
|
## Using NexQuant in Your Project
|
||||||
|
|
||||||
If you use code, concepts, or ideas from this project, you **must**:
|
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
|
### 2. Include Copyright Notice
|
||||||
|
|
||||||
```
|
```
|
||||||
Copyright (c) 2025 Predix Team
|
Copyright (c) 2025 NexQuant Team
|
||||||
Original Project: https://github.com/TPTBusiness/Predix
|
Original Project: https://github.com/TPTBusiness/NexQuant
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Provide Attribution
|
### 3. Provide Attribution
|
||||||
@@ -22,7 +22,7 @@ Add a notice in your documentation or README:
|
|||||||
```markdown
|
```markdown
|
||||||
## Acknowledgments
|
## 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).
|
licensed under the [MIT License](https://opensource.org/licenses/MIT).
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ If you modified the code:
|
|||||||
```markdown
|
```markdown
|
||||||
## Modifications
|
## Modifications
|
||||||
|
|
||||||
Based on Predix (original by Predix Team).
|
Based on NexQuant (original by NexQuant Team).
|
||||||
Modified by [Your Name/Organization] on [Date].
|
Modified by [Your Name/Organization] on [Date].
|
||||||
Changes: [Brief description of changes]
|
Changes: [Brief description of changes]
|
||||||
```
|
```
|
||||||
@@ -63,13 +63,13 @@ Changes: [Brief description of changes]
|
|||||||
```markdown
|
```markdown
|
||||||
# My Trading Project
|
# 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
|
## License
|
||||||
MIT License - see LICENSE file for details.
|
MIT License - see LICENSE file for details.
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
- Original Predix code by Predix Team (MIT License)
|
- Original NexQuant code by NexQuant Team (MIT License)
|
||||||
- Modified by John Doe, 2025
|
- Modified by John Doe, 2025
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# Changelog
|
# 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
<svg width="100%" viewBox="0 0 680 920" xmlns="http://www.w3.org/2000/svg" role="img">
|
||||||
|
<title>NexQuant data flow architecture</title>
|
||||||
|
<desc>Full pipeline from Qlib data source through R&D loop, factor and model tracks, strategy generation, portfolio optimization, to live trading.</desc>
|
||||||
|
|
||||||
|
<defs>
|
||||||
|
<marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||||
|
<path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</marker>
|
||||||
|
<style>
|
||||||
|
text { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
||||||
|
.th { font-size: 14px; font-weight: 600; fill: #1a1a1a; }
|
||||||
|
.ts { font-size: 12px; font-weight: 400; fill: #555; }
|
||||||
|
.arr { stroke: #888; stroke-width: 1.2; fill: none; }
|
||||||
|
.box-blue { fill: #E6F1FB; stroke: #185FA5; }
|
||||||
|
.box-purple { fill: #EEEDFE; stroke: #534AB7; }
|
||||||
|
.th-purple { fill: #3C3489; }
|
||||||
|
.ts-purple { fill: #534AB7; }
|
||||||
|
.box-teal { fill: #E1F5EE; stroke: #0F6E56; }
|
||||||
|
.th-teal { fill: #085041; }
|
||||||
|
.ts-teal { fill: #0F6E56; }
|
||||||
|
.box-coral { fill: #FAECE7; stroke: #993C1D; }
|
||||||
|
.th-coral { fill: #712B13; }
|
||||||
|
.ts-coral { fill: #993C1D; }
|
||||||
|
.box-amber { fill: #FAEEDA; stroke: #854F0B; }
|
||||||
|
.th-amber { fill: #633806; }
|
||||||
|
.ts-amber { fill: #854F0B; }
|
||||||
|
.box-green { fill: #EAF3DE; stroke: #3B6D11; }
|
||||||
|
.th-green { fill: #27500A; }
|
||||||
|
.ts-green { fill: #3B6D11; }
|
||||||
|
.box-gray { fill: #F1EFE8; stroke: #5F5E5A; }
|
||||||
|
.th-gray { fill: #2C2C2A; }
|
||||||
|
.ts-gray { fill: #5F5E5A; }
|
||||||
|
.th-blue { fill: #0C447C; }
|
||||||
|
.ts-blue { fill: #185FA5; }
|
||||||
|
.container { fill: none; stroke: #B4B2A9; stroke-width: 0.5; }
|
||||||
|
.label-muted { font-size: 12px; fill: #888780; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- DATA SOURCE -->
|
||||||
|
<rect x="200" y="20" width="280" height="56" rx="8" stroke-width="0.5" class="box-blue"/>
|
||||||
|
<text class="th th-blue" x="340" y="43" text-anchor="middle" dominant-baseline="central">Qlib data (1-min EUR/USD)</text>
|
||||||
|
<text class="ts ts-blue" x="340" y="63" text-anchor="middle" dominant-baseline="central">2020–2026 · 96 bars/day</text>
|
||||||
|
|
||||||
|
<line x1="340" y1="76" x2="340" y2="104" class="arr" marker-end="url(#arrow)"/>
|
||||||
|
|
||||||
|
<!-- R&D LOOP container -->
|
||||||
|
<rect x="40" y="104" width="600" height="190" rx="10" class="container"/>
|
||||||
|
<text class="label-muted" x="56" y="121" dominant-baseline="central">R&D loop (rdagent fin_quant)</text>
|
||||||
|
|
||||||
|
<rect x="56" y="132" width="100" height="56" rx="6" stroke-width="0.5" class="box-purple"/>
|
||||||
|
<text class="th th-purple" x="106" y="154" text-anchor="middle" dominant-baseline="central">Propose</text>
|
||||||
|
<text class="ts ts-purple" x="106" y="172" text-anchor="middle" dominant-baseline="central">LLM</text>
|
||||||
|
<line x1="156" y1="160" x2="170" y2="160" class="arr" marker-end="url(#arrow)"/>
|
||||||
|
|
||||||
|
<rect x="170" y="132" width="100" height="56" rx="6" stroke-width="0.5" class="box-purple"/>
|
||||||
|
<text class="th th-purple" x="220" y="154" text-anchor="middle" dominant-baseline="central">Coding</text>
|
||||||
|
<text class="ts ts-purple" x="220" y="172" text-anchor="middle" dominant-baseline="central">CoSTEER</text>
|
||||||
|
<line x1="270" y1="160" x2="284" y2="160" class="arr" marker-end="url(#arrow)"/>
|
||||||
|
|
||||||
|
<rect x="284" y="132" width="100" height="56" rx="6" stroke-width="0.5" class="box-purple"/>
|
||||||
|
<text class="th th-purple" x="334" y="154" text-anchor="middle" dominant-baseline="central">Running</text>
|
||||||
|
<text class="ts ts-purple" x="334" y="172" text-anchor="middle" dominant-baseline="central">Docker</text>
|
||||||
|
<line x1="384" y1="160" x2="398" y2="160" class="arr" marker-end="url(#arrow)"/>
|
||||||
|
|
||||||
|
<rect x="398" y="132" width="100" height="56" rx="6" stroke-width="0.5" class="box-purple"/>
|
||||||
|
<text class="th th-purple" x="448" y="154" text-anchor="middle" dominant-baseline="central">Feedback</text>
|
||||||
|
<text class="ts ts-purple" x="448" y="172" text-anchor="middle" dominant-baseline="central">LLM</text>
|
||||||
|
<line x1="498" y1="160" x2="512" y2="160" class="arr" marker-end="url(#arrow)"/>
|
||||||
|
|
||||||
|
<rect x="512" y="132" width="100" height="56" rx="6" stroke-width="0.5" class="box-purple"/>
|
||||||
|
<text class="th th-purple" x="562" y="154" text-anchor="middle" dominant-baseline="central">Record</text>
|
||||||
|
<text class="ts ts-purple" x="562" y="172" text-anchor="middle" dominant-baseline="central">Pickle</text>
|
||||||
|
|
||||||
|
<text class="label-muted" x="340" y="216" text-anchor="middle" dominant-baseline="central">Bandit selection → factor track or model track</text>
|
||||||
|
|
||||||
|
<!-- Split to two tracks -->
|
||||||
|
<path d="M210 294 L210 308 L470 308 L470 294" fill="none" stroke="#B4B2A9" stroke-width="0.5"/>
|
||||||
|
<line x1="210" y1="308" x2="210" y2="322" class="arr" marker-end="url(#arrow)"/>
|
||||||
|
<line x1="470" y1="308" x2="470" y2="322" class="arr" marker-end="url(#arrow)"/>
|
||||||
|
<text class="label-muted" x="340" y="478" text-anchor="middle">every N factors · auto or CLI</text>
|
||||||
|
|
||||||
|
<!-- FACTOR TRACK -->
|
||||||
|
<rect x="40" y="322" width="260" height="130" rx="8" stroke-width="0.5" class="box-teal"/>
|
||||||
|
<text class="th th-teal" x="170" y="344" text-anchor="middle" dominant-baseline="central">Factor track</text>
|
||||||
|
<text class="ts ts-teal" x="170" y="364" text-anchor="middle" dominant-baseline="central">Hypothesis → FactorCoSTEER</text>
|
||||||
|
<text class="ts ts-teal" x="170" y="382" text-anchor="middle" dominant-baseline="central">FactorRunner → FactorFeedback</text>
|
||||||
|
<text class="ts ts-teal" x="170" y="402" text-anchor="middle" dominant-baseline="central">Output: result.h5</text>
|
||||||
|
<text class="ts ts-teal" x="170" y="420" text-anchor="middle" dominant-baseline="central">MultiIndex DataFrame</text>
|
||||||
|
<text class="ts ts-teal" x="170" y="438" text-anchor="middle" dominant-baseline="central">IC / Sharpe metrics</text>
|
||||||
|
|
||||||
|
<!-- MODEL TRACK -->
|
||||||
|
<rect x="380" y="322" width="260" height="130" rx="8" stroke-width="0.5" class="box-coral"/>
|
||||||
|
<text class="th th-coral" x="510" y="344" text-anchor="middle" dominant-baseline="central">Model track</text>
|
||||||
|
<text class="ts ts-coral" x="510" y="364" text-anchor="middle" dominant-baseline="central">Hypothesis → ModelCoSTEER</text>
|
||||||
|
<text class="ts ts-coral" x="510" y="382" text-anchor="middle" dominant-baseline="central">ModelRunner → ModelFeedback</text>
|
||||||
|
<text class="ts ts-coral" x="510" y="402" text-anchor="middle" dominant-baseline="central">Output: PyTorch preds</text>
|
||||||
|
<text class="ts ts-coral" x="510" y="420" text-anchor="middle" dominant-baseline="central">+ mlflow logs</text>
|
||||||
|
<text class="ts ts-coral" x="510" y="438" text-anchor="middle" dominant-baseline="central">LSTM / Transformer / CNN</text>
|
||||||
|
|
||||||
|
<!-- Merge to strategy -->
|
||||||
|
<path d="M170 452 L170 486 L340 486 L340 502" fill="none" stroke="#B4B2A9" stroke-width="0.5" marker-end="url(#arrow)"/>
|
||||||
|
<path d="M510 452 L510 486 L340 486" fill="none" stroke="#B4B2A9" stroke-width="0.5"/>
|
||||||
|
|
||||||
|
<!-- STRATEGY GENERATION -->
|
||||||
|
<rect x="100" y="502" width="480" height="120" rx="8" stroke-width="0.5" class="box-amber"/>
|
||||||
|
<text class="th th-amber" x="340" y="524" text-anchor="middle" dominant-baseline="central">Strategy generation pipeline</text>
|
||||||
|
|
||||||
|
<rect x="116" y="536" width="120" height="44" rx="6" stroke-width="0.5" class="box-gray"/>
|
||||||
|
<text class="ts th-gray" x="176" y="554" text-anchor="middle" dominant-baseline="central">Load top factors</text>
|
||||||
|
<text class="ts ts-gray" x="176" y="570" text-anchor="middle" dominant-baseline="central">by |IC|</text>
|
||||||
|
<line x1="236" y1="558" x2="252" y2="558" class="arr" marker-end="url(#arrow)"/>
|
||||||
|
|
||||||
|
<rect x="252" y="536" width="120" height="44" rx="6" stroke-width="0.5" class="box-gray"/>
|
||||||
|
<text class="ts th-gray" x="312" y="554" text-anchor="middle" dominant-baseline="central">LLM strategy</text>
|
||||||
|
<text class="ts ts-gray" x="312" y="570" text-anchor="middle" dominant-baseline="central">code gen</text>
|
||||||
|
<line x1="372" y1="558" x2="388" y2="558" class="arr" marker-end="url(#arrow)"/>
|
||||||
|
|
||||||
|
<rect x="388" y="536" width="120" height="44" rx="6" stroke-width="0.5" class="box-gray"/>
|
||||||
|
<text class="ts th-gray" x="448" y="554" text-anchor="middle" dominant-baseline="central">OHLCV backtest</text>
|
||||||
|
<text class="ts ts-gray" x="448" y="570" text-anchor="middle" dominant-baseline="central">signals eval</text>
|
||||||
|
|
||||||
|
<text class="label-muted" x="340" y="600" text-anchor="middle" dominant-baseline="central">Optuna: 10 → 15 → 5 trials · Sharpe ≥ 1.5 · DD ≥ −0.30 · WR ≥ 0.40</text>
|
||||||
|
|
||||||
|
<line x1="340" y1="622" x2="340" y2="648" class="arr" marker-end="url(#arrow)"/>
|
||||||
|
|
||||||
|
<!-- PORTFOLIO -->
|
||||||
|
<rect x="160" y="648" width="360" height="56" rx="8" stroke-width="0.5" class="box-green"/>
|
||||||
|
<text class="th th-green" x="340" y="670" text-anchor="middle" dominant-baseline="central">Portfolio optimization</text>
|
||||||
|
<text class="ts ts-green" x="340" y="688" text-anchor="middle" dominant-baseline="central">Mean-variance · Risk parity · Black-Litterman</text>
|
||||||
|
|
||||||
|
<line x1="340" y1="704" x2="340" y2="730" class="arr" marker-end="url(#arrow)"/>
|
||||||
|
|
||||||
|
<!-- LIVE TRADING -->
|
||||||
|
<rect x="160" y="730" width="360" height="56" rx="8" stroke-width="0.5" class="box-gray"/>
|
||||||
|
<text class="th th-gray" x="340" y="752" text-anchor="middle" dominant-baseline="central">Live trading (closed-source)</text>
|
||||||
|
<text class="ts ts-gray" x="340" y="770" text-anchor="middle" dominant-baseline="central">ftmo_live_trader.py · FTMO signals</text>
|
||||||
|
|
||||||
|
<!-- EXTERNAL SERVICES -->
|
||||||
|
<text class="label-muted" x="340" y="812" text-anchor="middle">External services</text>
|
||||||
|
|
||||||
|
<rect x="40" y="824" width="130" height="44" rx="6" stroke-width="0.5" class="box-gray"/>
|
||||||
|
<text class="ts th-gray" x="105" y="842" text-anchor="middle" dominant-baseline="central">llama.cpp</text>
|
||||||
|
<text class="ts ts-gray" x="105" y="858" text-anchor="middle" dominant-baseline="central">LLM inference</text>
|
||||||
|
|
||||||
|
<rect x="185" y="824" width="130" height="44" rx="6" stroke-width="0.5" class="box-gray"/>
|
||||||
|
<text class="ts th-gray" x="250" y="842" text-anchor="middle" dominant-baseline="central">Docker</text>
|
||||||
|
<text class="ts ts-gray" x="250" y="858" text-anchor="middle" dominant-baseline="central">sandbox</text>
|
||||||
|
|
||||||
|
<rect x="330" y="824" width="130" height="44" rx="6" stroke-width="0.5" class="box-gray"/>
|
||||||
|
<text class="ts th-gray" x="395" y="842" text-anchor="middle" dominant-baseline="central">Optuna</text>
|
||||||
|
<text class="ts ts-gray" x="395" y="858" text-anchor="middle" dominant-baseline="central">Bayesian opt</text>
|
||||||
|
|
||||||
|
<rect x="475" y="824" width="130" height="44" rx="6" stroke-width="0.5" class="box-gray"/>
|
||||||
|
<text class="ts th-gray" x="540" y="842" text-anchor="middle" dominant-baseline="central">Qlib</text>
|
||||||
|
<text class="ts ts-gray" x="540" y="858" text-anchor="middle" dominant-baseline="central">backtest engine</text>
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 10 KiB |
+4
-4
@@ -10,9 +10,9 @@ import subprocess
|
|||||||
|
|
||||||
latest_tag = subprocess.check_output(["git", "describe", "--tags", "--abbrev=0"], text=True).strip()
|
latest_tag = subprocess.check_output(["git", "describe", "--tags", "--abbrev=0"], text=True).strip()
|
||||||
|
|
||||||
project = "Predix"
|
project = "NexQuant"
|
||||||
copyright = "2025, Predix Team"
|
copyright = "2025, NexQuant Team"
|
||||||
author = "Predix Team"
|
author = "NexQuant Team"
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#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_favicon = "_static/favicon.ico"
|
||||||
|
|
||||||
html_theme_options = {
|
html_theme_options = {
|
||||||
"source_repository": "https://github.com/PredixAI/predix",
|
"source_repository": "https://github.com/NexQuantAI/nexquant",
|
||||||
"source_branch": "main",
|
"source_branch": "main",
|
||||||
"source_directory": "docs/",
|
"source_directory": "docs/",
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -1,13 +1,13 @@
|
|||||||
.. Predix documentation master file, created by
|
.. NexQuant documentation master file, created by
|
||||||
sphinx-quickstart on Mon Jul 15 04:27:50 2024.
|
sphinx-quickstart on Mon Jul 15 04:27:50 2024.
|
||||||
You can adapt this file completely to your liking, but it should at least
|
You can adapt this file completely to your liking, but it should at least
|
||||||
contain the root `toctree` directive.
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
Welcome to Predix's documentation!
|
Welcome to NexQuant's documentation!
|
||||||
===================================
|
===================================
|
||||||
|
|
||||||
.. image:: _static/logo.png
|
.. image:: _static/logo.png
|
||||||
:alt: Predix Logo
|
:alt: NexQuant Logo
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 3
|
:maxdepth: 3
|
||||||
@@ -23,7 +23,7 @@ Welcome to Predix's documentation!
|
|||||||
api_reference
|
api_reference
|
||||||
policy
|
policy
|
||||||
|
|
||||||
GitHub <https://github.com/PredixAI/predix>
|
GitHub <https://github.com/NexQuantAI/nexquant>
|
||||||
|
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
|
|||||||
+11
-11
@@ -1,4 +1,4 @@
|
|||||||
# Predix Parallel Run System
|
# NexQuant Parallel Run System
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -10,8 +10,8 @@ The Parallel Run System enables concurrent execution of 5+ factor generation exp
|
|||||||
|
|
||||||
| File | Purpose |
|
| File | Purpose |
|
||||||
|------|---------|
|
|------|---------|
|
||||||
| `predix.py` | Extended with `--run-id` parameter for isolated single runs |
|
| `nexquant.py` | Extended with `--run-id` parameter for isolated single runs |
|
||||||
| `predix_parallel.py` | Parallel runner manager with Rich live dashboard |
|
| `nexquant_parallel.py` | Parallel runner manager with Rich live dashboard |
|
||||||
| `factor_runner.py` | Modified to use `PARALLEL_RUN_ID` for path isolation |
|
| `factor_runner.py` | Modified to use `PARALLEL_RUN_ID` for path isolation |
|
||||||
| `CoSTEER/__init__.py` | Modified to use `PARALLEL_RUN_ID` for intermediate results |
|
| `CoSTEER/__init__.py` | Modified to use `PARALLEL_RUN_ID` for intermediate results |
|
||||||
|
|
||||||
@@ -57,26 +57,26 @@ RD-Agent_workspace_run2/ # Parallel run #2
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run with isolated results
|
# Run with isolated results
|
||||||
predix quant --run-id 1 -m openrouter
|
nexquant quant --run-id 1 -m openrouter
|
||||||
```
|
```
|
||||||
|
|
||||||
### CLI - Parallel Runner (Direct)
|
### CLI - Parallel Runner (Direct)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run 5 experiments with 2 API keys
|
# 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
|
# Run 3 experiments with local model
|
||||||
python predix_parallel.py --runs 3 --model local
|
python nexquant_parallel.py --runs 3 --model local
|
||||||
|
|
||||||
# Custom configuration
|
# Custom configuration
|
||||||
python predix_parallel.py -n 10 -k 2 -m openrouter
|
python nexquant_parallel.py -n 10 -k 2 -m openrouter
|
||||||
```
|
```
|
||||||
|
|
||||||
### Programmatic Usage
|
### Programmatic Usage
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from predix_parallel import main
|
from nexquant_parallel import main
|
||||||
|
|
||||||
result = main(runs=5, api_keys=2, model="openrouter")
|
result = main(runs=5, api_keys=2, model="openrouter")
|
||||||
print(f"Success: {result['success']}/{result['total']}")
|
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 │
|
│ Run │ Status │ Elapsed │ API Key │ Model │ Exit │
|
||||||
├──────┼──────────┼──────────┼─────────┼──────────┼───────┤
|
├──────┼──────────┼──────────┼─────────┼──────────┼───────┤
|
||||||
@@ -222,10 +222,10 @@ if parallel_run_id != "0":
|
|||||||
pytest test/integration/test_all_features.py -v
|
pytest test/integration/test_all_features.py -v
|
||||||
|
|
||||||
# Test parallel runner imports
|
# 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
|
# Test CLI options
|
||||||
predix quant --help # Should show --run-id option
|
nexquant quant --help # Should show --run-id option
|
||||||
```
|
```
|
||||||
|
|
||||||
## Future Enhancements
|
## Future Enhancements
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Security Runbook für Predix
|
# Security Runbook für NexQuant
|
||||||
|
|
||||||
## Bandit Security Scanner
|
## Bandit Security Scanner
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -127,8 +127,8 @@ jupyter notebook examples/notebooks/quickstart.ipynb
|
|||||||
|
|
||||||
- **Dokumentation:** `docs/` oder [README.md](../README.md)
|
- **Dokumentation:** `docs/` oder [README.md](../README.md)
|
||||||
- **CLI Hilfe:** `rdagent COMMAND --help`
|
- **CLI Hilfe:** `rdagent COMMAND --help`
|
||||||
- **Issues:** [GitHub Issues](https://github.com/nico/Predix/issues)
|
- **Issues:** [GitHub Issues](https://github.com/nico/NexQuant/issues)
|
||||||
- **Community:** [Discussions](https://github.com/nico/Predix/discussions)
|
- **Community:** [Discussions](https://github.com/nico/NexQuant/discussions)
|
||||||
|
|
||||||
## ⚠️ Wichtige Hinweise
|
## ⚠️ Wichtige Hinweise
|
||||||
|
|
||||||
|
|||||||
@@ -382,8 +382,8 @@
|
|||||||
"### Ressourcen:\n",
|
"### Ressourcen:\n",
|
||||||
"\n",
|
"\n",
|
||||||
"- 📚 [Dokumentation](../docs/)\n",
|
"- 📚 [Dokumentation](../docs/)\n",
|
||||||
"- 💬 [GitHub Discussions](https://github.com/nico/Predix/discussions)\n",
|
"- 💬 [GitHub Discussions](https://github.com/nico/NexQuant/discussions)\n",
|
||||||
"- 🐛 [Issues melden](https://github.com/nico/Predix/issues)"
|
"- 🐛 [Issues melden](https://github.com/nico/NexQuant/issues)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
+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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+278
-166
@@ -1,24 +1,32 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
"""
|
"""
|
||||||
Predix CLI - Wrapper for rdagent with LLM model selection.
|
NexQuant CLI - Wrapper for rdagent with LLM model selection.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
predix quant # Local llama.cpp (default)
|
nexquant quant # Local llama.cpp (default)
|
||||||
predix quant --model local # Explicit local
|
nexquant quant --model local # Explicit local
|
||||||
predix quant --model openrouter # OpenRouter cloud model
|
nexquant quant --model openrouter # OpenRouter cloud model
|
||||||
predix quant -d # With web dashboard
|
nexquant quant -d # With web dashboard
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(Path(__file__).parent / ".env")
|
load_dotenv(Path(__file__).parent / ".env")
|
||||||
|
|
||||||
import typer
|
import typer
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
app = typer.Typer(help="Predix - AI Quantitative Trading Agent")
|
try:
|
||||||
|
from rdagent.utils.env import logger
|
||||||
|
except ImportError:
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
app = typer.Typer(help="NexQuant - AI Quantitative Trading Agent")
|
||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
@@ -107,7 +115,7 @@ def _ensure_kronos_factor_in_pool(con) -> None:
|
|||||||
color = "green" if abs(ic) > 0.01 else "yellow"
|
color = "green" if abs(ic) > 0.01 else "yellow"
|
||||||
con.print(
|
con.print(
|
||||||
f" [bold {color}]Kronos Factor ready:[/bold {color}] IC={ic:.4f}, "
|
f" [bold {color}]Kronos Factor ready:[/bold {color}] IC={ic:.4f}, "
|
||||||
f"Hit-Rate={hit_rate:.1%} — added to strategy pool"
|
f"Hit-Rate={hit_rate:.1%} — added to strategy pool",
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -170,13 +178,13 @@ def quant(
|
|||||||
0 = single run mode (default: 0)
|
0 = single run mode (default: 0)
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$ predix quant # Local llama.cpp, single run
|
$ nexquant quant # Local llama.cpp, single run
|
||||||
$ predix quant -m openrouter # OpenRouter cloud model
|
$ nexquant quant -m openrouter # OpenRouter cloud model
|
||||||
$ predix quant -d # With web dashboard on :5000
|
$ nexquant quant -d # With web dashboard on :5000
|
||||||
$ predix quant -m openrouter -d # Cloud model + web dashboard
|
$ nexquant quant -m openrouter -d # Cloud model + web dashboard
|
||||||
$ predix quant --run-id 1 # Parallel run #1 (isolated)
|
$ nexquant quant --run-id 1 # Parallel run #1 (isolated)
|
||||||
$ predix quant --run-id 2 --loop-n 50 # Parallel run #2, 50 loops
|
$ nexquant quant --run-id 2 --loop-n 50 # Parallel run #2, 50 loops
|
||||||
$ predix quant --log-file custom.log # Custom log file path
|
$ nexquant quant --log-file custom.log # Custom log file path
|
||||||
|
|
||||||
Expected Output:
|
Expected Output:
|
||||||
- Generated alpha factors saved to results/factors/ as JSON files
|
- Generated alpha factors saved to results/factors/ as JSON files
|
||||||
@@ -189,14 +197,14 @@ def quant(
|
|||||||
Local models are faster but may have lower quality than cloud models.
|
Local models are faster but may have lower quality than cloud models.
|
||||||
|
|
||||||
See Also:
|
See Also:
|
||||||
predix evaluate - Evaluate existing factors with full 1min data
|
nexquant evaluate - Evaluate existing factors with full 1min data
|
||||||
predix top - Show top-performing factors by IC or Sharpe
|
nexquant top - Show top-performing factors by IC or Sharpe
|
||||||
predix health - Check system health and configuration
|
nexquant health - Check system health and configuration
|
||||||
"""
|
"""
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import sys
|
|
||||||
|
|
||||||
# ---- Parallel Run Isolation ----
|
# ---- Parallel Run Isolation ----
|
||||||
# When run_id > 0, isolate all outputs (logs, results, workspace)
|
# When run_id > 0, isolate all outputs (logs, results, workspace)
|
||||||
@@ -219,10 +227,9 @@ def quant(
|
|||||||
console.print(f" [dim]Log: {log_file}[/dim]")
|
console.print(f" [dim]Log: {log_file}[/dim]")
|
||||||
console.print(f" [dim]Results: results/runs/run{run_id}/[/dim]")
|
console.print(f" [dim]Results: results/runs/run{run_id}/[/dim]")
|
||||||
console.print(f" [dim]Workspace: {workspace_dir.name}/[/dim]")
|
console.print(f" [dim]Workspace: {workspace_dir.name}/[/dim]")
|
||||||
else:
|
# Single run mode: default log file
|
||||||
# Single run mode: default log file
|
elif log_file is None:
|
||||||
if log_file is None:
|
log_file = "fin_quant.log"
|
||||||
log_file = "fin_quant.log"
|
|
||||||
|
|
||||||
# ---- Log File Setup (daily-rotated) ----
|
# ---- Log File Setup (daily-rotated) ----
|
||||||
from datetime import datetime as _dt
|
from datetime import datetime as _dt
|
||||||
@@ -230,10 +237,14 @@ def quant(
|
|||||||
_daily_dir = Path(__file__).parent / "logs" / _today
|
_daily_dir = Path(__file__).parent / "logs" / _today
|
||||||
_daily_dir.mkdir(parents=True, exist_ok=True)
|
_daily_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
_log_f = None
|
||||||
|
_orig_stdout = sys.stdout
|
||||||
|
_orig_stderr = sys.stderr
|
||||||
|
|
||||||
if log_file.lower() != "none":
|
if log_file.lower() != "none":
|
||||||
log_path = _daily_dir / log_file
|
log_path = _daily_dir / log_file
|
||||||
# Open log file for appending (raw stdout/stderr capture)
|
# Open log file for appending (raw stdout/stderr capture)
|
||||||
log_f = open(log_path, "a", encoding="utf-8")
|
_log_f = open(log_path, "a", encoding="utf-8")
|
||||||
|
|
||||||
# Redirect stdout and stderr to both console and log file
|
# Redirect stdout and stderr to both console and log file
|
||||||
class TeeWriter:
|
class TeeWriter:
|
||||||
@@ -245,18 +256,18 @@ def quant(
|
|||||||
try:
|
try:
|
||||||
s.write(data)
|
s.write(data)
|
||||||
s.flush()
|
s.flush()
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
for s in self._streams:
|
for s in self._streams:
|
||||||
try:
|
try:
|
||||||
s.flush()
|
s.flush()
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
sys.stdout = TeeWriter(sys.__stdout__, log_f)
|
sys.stdout = TeeWriter(_orig_stdout, _log_f)
|
||||||
sys.stderr = TeeWriter(sys.__stderr__, log_f)
|
sys.stderr = TeeWriter(_orig_stderr, _log_f)
|
||||||
|
|
||||||
console.print(f"\n[dim]📝 Logging to: logs/{_today}/{log_file}[/dim]")
|
console.print(f"\n[dim]📝 Logging to: logs/{_today}/{log_file}[/dim]")
|
||||||
else:
|
else:
|
||||||
@@ -269,7 +280,7 @@ def quant(
|
|||||||
if not api_key:
|
if not api_key:
|
||||||
console.print("\n[bold red]❌ OPENROUTER_API_KEY not set in .env[/bold red]")
|
console.print("\n[bold red]❌ OPENROUTER_API_KEY not set in .env[/bold red]")
|
||||||
console.print("[yellow]Add your API key to .env:[/yellow]")
|
console.print("[yellow]Add your API key to .env:[/yellow]")
|
||||||
console.print(' OPENROUTER_API_KEY=sk-or-your-key-here')
|
console.print(" OPENROUTER_API_KEY=sk-or-your-key-here")
|
||||||
raise typer.Exit(code=1)
|
raise typer.Exit(code=1)
|
||||||
|
|
||||||
# Setup both API keys for load balancing
|
# Setup both API keys for load balancing
|
||||||
@@ -282,7 +293,7 @@ def quant(
|
|||||||
os.environ["LITELLM_PARALLEL_CALLS"] = "2"
|
os.environ["LITELLM_PARALLEL_CALLS"] = "2"
|
||||||
console.print(f"\n[bold blue]🌐 Using OpenRouter (2 API Keys):[/bold blue] [cyan]{os.environ['CHAT_MODEL']}[/cyan]")
|
console.print(f"\n[bold blue]🌐 Using OpenRouter (2 API Keys):[/bold blue] [cyan]{os.environ['CHAT_MODEL']}[/cyan]")
|
||||||
console.print(f" [dim]Keys: {api_key[:15]}*** + {api_key_2[:15]}***[/dim]")
|
console.print(f" [dim]Keys: {api_key[:15]}*** + {api_key_2[:15]}***[/dim]")
|
||||||
console.print(f" [dim]Parallel: 2 concurrent requests[/dim]")
|
console.print(" [dim]Parallel: 2 concurrent requests[/dim]")
|
||||||
else:
|
else:
|
||||||
os.environ["OPENAI_API_KEY"] = api_key
|
os.environ["OPENAI_API_KEY"] = api_key
|
||||||
console.print(f"\n[bold blue]🌐 Using OpenRouter:[/bold blue] [cyan]{os.environ['CHAT_MODEL']}[/cyan]")
|
console.print(f"\n[bold blue]🌐 Using OpenRouter:[/bold blue] [cyan]{os.environ['CHAT_MODEL']}[/cyan]")
|
||||||
@@ -300,7 +311,7 @@ def quant(
|
|||||||
# ---- Dashboards ----
|
# ---- Dashboards ----
|
||||||
if dashboard:
|
if dashboard:
|
||||||
def start_web_dashboard():
|
def start_web_dashboard():
|
||||||
console.print(f"\n[bold green]🚀 Web Dashboard: http://localhost:5000[/bold green]")
|
console.print("\n[bold green]🚀 Web Dashboard: http://localhost:5000[/bold green]")
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
["python", "web/dashboard_api.py"],
|
["python", "web/dashboard_api.py"],
|
||||||
cwd=str(Path(__file__).parent),
|
cwd=str(Path(__file__).parent),
|
||||||
@@ -312,20 +323,23 @@ def quant(
|
|||||||
|
|
||||||
if cli_dashboard:
|
if cli_dashboard:
|
||||||
def start_cli_dash():
|
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)
|
run_dashboard(log_path="fin_quant.log", refresh_interval=3)
|
||||||
|
|
||||||
threading.Thread(target=start_cli_dash, daemon=True).start()
|
threading.Thread(target=start_cli_dash, daemon=True).start()
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
# ---- Kronos Factor: auto-generate if not in pool ----
|
# ---- Kronos Factor: CPU inference to avoid GPU conflict with llama-server ----
|
||||||
_ensure_kronos_factor_in_pool(console)
|
try:
|
||||||
|
_ensure_kronos_factor_in_pool(console)
|
||||||
|
except Exception:
|
||||||
|
console.print("[dim]Kronos Factor skipped — torch not available[/dim]")
|
||||||
|
|
||||||
# ---- Start fin_quant ----
|
# ---- Start fin_quant ----
|
||||||
from rdagent.app.qlib_rd_loop.quant import main as fin_quant
|
from rdagent.app.qlib_rd_loop.quant import main as fin_quant
|
||||||
from rdagent.log.daily_log import session as _daily_session
|
from rdagent.log.daily_log import session as _daily_session
|
||||||
|
|
||||||
console.print(f"\n[bold cyan]📊 Starting EURUSD Trading Loop...[/bold cyan]\n")
|
console.print("\n[bold cyan]📊 Starting EURUSD Trading Loop...[/bold cyan]\n")
|
||||||
|
|
||||||
_ctx = {"model": model}
|
_ctx = {"model": model}
|
||||||
if run_id:
|
if run_id:
|
||||||
@@ -335,11 +349,17 @@ def quant(
|
|||||||
if step_n:
|
if step_n:
|
||||||
_ctx["steps"] = step_n
|
_ctx["steps"] = step_n
|
||||||
|
|
||||||
with _daily_session("fin_quant", **_ctx):
|
try:
|
||||||
fin_quant(
|
with _daily_session("fin_quant", **_ctx):
|
||||||
step_n=step_n,
|
fin_quant(
|
||||||
loop_n=loop_n,
|
step_n=step_n,
|
||||||
)
|
loop_n=loop_n,
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
if _log_f is not None:
|
||||||
|
sys.stdout = _orig_stdout
|
||||||
|
sys.stderr = _orig_stderr
|
||||||
|
_log_f.close()
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
@@ -388,11 +408,11 @@ def evaluate(
|
|||||||
when recalculating with updated methodology. (default: False)
|
when recalculating with updated methodology. (default: False)
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$ predix evaluate # Evaluate 100 NEW factors
|
$ nexquant evaluate # Evaluate 100 NEW factors
|
||||||
$ predix evaluate --top 500 # Evaluate 500 NEW factors
|
$ nexquant evaluate --top 500 # Evaluate 500 NEW factors
|
||||||
$ predix evaluate --all # Evaluate all remaining factors
|
$ nexquant evaluate --all # Evaluate all remaining factors
|
||||||
$ predix evaluate --force --top 50 # Re-evaluate 50 factors
|
$ nexquant evaluate --force --top 50 # Re-evaluate 50 factors
|
||||||
$ predix evaluate -p 8 # Use 8 parallel workers
|
$ nexquant evaluate -p 8 # Use 8 parallel workers
|
||||||
|
|
||||||
Expected Output:
|
Expected Output:
|
||||||
- Updated JSON files in results/factors/ with IC, Sharpe, Max DD, Win Rate
|
- Updated JSON files in results/factors/ with IC, Sharpe, Max DD, Win Rate
|
||||||
@@ -404,22 +424,22 @@ def evaluate(
|
|||||||
With --parallel 4, expect ~30-60 seconds per factor wall-clock time.
|
With --parallel 4, expect ~30-60 seconds per factor wall-clock time.
|
||||||
|
|
||||||
See Also:
|
See Also:
|
||||||
predix top - Show top-performing factors by IC or Sharpe
|
nexquant top - Show top-performing factors by IC or Sharpe
|
||||||
predix portfolio - Select a diversified portfolio of uncorrelated factors
|
nexquant portfolio - Select a diversified portfolio of uncorrelated factors
|
||||||
predix quant - Generate new factors via LLM trading loop
|
nexquant quant - Generate new factors via LLM trading loop
|
||||||
"""
|
"""
|
||||||
from rich.panel import Panel
|
|
||||||
from rdagent.log.daily_log import session as _daily_session
|
from rdagent.log.daily_log import session as _daily_session
|
||||||
|
from rich.panel import Panel
|
||||||
|
|
||||||
console.print(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"
|
"Evaluating factors with FULL 1min data (2020-2026)\n"
|
||||||
"Skips already evaluated factors automatically",
|
"Skips already evaluated factors automatically",
|
||||||
border_style="cyan",
|
border_style="cyan",
|
||||||
))
|
))
|
||||||
|
|
||||||
# Import and run the evaluator
|
# 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}
|
_eval_ctx = {"top": "all" if all_factors else top, "workers": parallel}
|
||||||
if force:
|
if force:
|
||||||
@@ -470,10 +490,10 @@ def top(
|
|||||||
(default: "ic")
|
(default: "ic")
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$ predix top # Top 20 factors by absolute IC
|
$ nexquant top # Top 20 factors by absolute IC
|
||||||
$ predix top -n 50 # Top 50 factors by absolute IC
|
$ nexquant top -n 50 # Top 50 factors by absolute IC
|
||||||
$ predix top -m sharpe # Top 20 factors by absolute Sharpe
|
$ nexquant top -m sharpe # Top 20 factors by absolute Sharpe
|
||||||
$ predix top -n 100 -m sharpe # Top 100 factors by Sharpe
|
$ nexquant top -n 100 -m sharpe # Top 100 factors by Sharpe
|
||||||
|
|
||||||
Expected Output:
|
Expected Output:
|
||||||
- Formatted table showing Factor name, IC, Sharpe, Annualized Return,
|
- Formatted table showing Factor name, IC, Sharpe, Annualized Return,
|
||||||
@@ -485,15 +505,16 @@ def top(
|
|||||||
May take a few seconds with thousands of factor files.
|
May take a few seconds with thousands of factor files.
|
||||||
|
|
||||||
See Also:
|
See Also:
|
||||||
predix evaluate - Evaluate factors to generate performance metrics
|
nexquant evaluate - Evaluate factors to generate performance metrics
|
||||||
predix portfolio - Select diversified portfolio from top factors
|
nexquant portfolio - Select diversified portfolio from top factors
|
||||||
predix build-strategies - Combine factors into trading strategies
|
nexquant build-strategies - Combine factors into trading strategies
|
||||||
"""
|
"""
|
||||||
import json
|
|
||||||
import glob as glob_module
|
import glob as glob_module
|
||||||
|
import json
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from rich.table import Table
|
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
|
from rich.table import Table
|
||||||
|
|
||||||
factors_dir = Path(__file__).parent / "results" / "factors"
|
factors_dir = Path(__file__).parent / "results" / "factors"
|
||||||
if not factors_dir.exists():
|
if not factors_dir.exists():
|
||||||
@@ -510,6 +531,7 @@ def top(
|
|||||||
if data.get("status") == "success" and data.get("ic") is not None:
|
if data.get("status") == "success" and data.get("ic") is not None:
|
||||||
results.append(data)
|
results.append(data)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
logger.warning("Failed to load factor file %s", f, exc_info=True)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
@@ -557,9 +579,11 @@ def top(
|
|||||||
|
|
||||||
console.print(table)
|
console.print(table)
|
||||||
|
|
||||||
# Summary
|
# Summary — filter None, NaN, and non-numeric values
|
||||||
valid_ic = [r.get("ic") for r in results if r.get("ic") is not None]
|
valid_ic = [v for v in (r.get("ic") for r in results)
|
||||||
valid_sharpe = [r.get("sharpe") for r in results if r.get("sharpe") is not None]
|
if isinstance(v, (int, float)) and v is not None and not np.isnan(v)]
|
||||||
|
valid_sharpe = [v for v in (r.get("sharpe") for r in results)
|
||||||
|
if isinstance(v, (int, float)) and v is not None and not np.isnan(v)]
|
||||||
# Filter extreme outliers for average
|
# Filter extreme outliers for average
|
||||||
valid_sharpe_filtered = [s for s in valid_sharpe if abs(s or 0) < 1e6]
|
valid_sharpe_filtered = [s for s in valid_sharpe if abs(s or 0) < 1e6]
|
||||||
|
|
||||||
@@ -615,10 +639,10 @@ def portfolio(
|
|||||||
high-IC factors. Typical range: 0.2-0.5. (default: 0.3)
|
high-IC factors. Typical range: 0.2-0.5. (default: 0.3)
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$ predix portfolio # Select top 10 from top 50 candidates
|
$ nexquant portfolio # Select top 10 from top 50 candidates
|
||||||
$ predix portfolio -n 100 -t 20 # Select top 20 from top 100
|
$ nexquant portfolio -n 100 -t 20 # Select top 20 from top 100
|
||||||
$ predix portfolio -c 0.5 # Allow higher correlation (0.5)
|
$ nexquant portfolio -c 0.5 # Allow higher correlation (0.5)
|
||||||
$ predix portfolio -n 200 -t 15 -c 0.2 # Strict diversification
|
$ nexquant portfolio -n 200 -t 15 -c 0.2 # Strict diversification
|
||||||
|
|
||||||
Expected Output:
|
Expected Output:
|
||||||
- Formatted table showing selected factors with IC, Sharpe, and max correlation
|
- Formatted table showing selected factors with IC, Sharpe, and max correlation
|
||||||
@@ -630,20 +654,20 @@ def portfolio(
|
|||||||
Each factor must be re-evaluated to compute time-series values for correlation.
|
Each factor must be re-evaluated to compute time-series values for correlation.
|
||||||
|
|
||||||
See Also:
|
See Also:
|
||||||
predix portfolio-simple - Faster category-based diversification
|
nexquant portfolio-simple - Faster category-based diversification
|
||||||
predix top - View top factors before portfolio selection
|
nexquant top - View top factors before portfolio selection
|
||||||
predix build-strategies - Build strategies from selected factors
|
nexquant build-strategies - Build strategies from selected factors
|
||||||
"""
|
"""
|
||||||
import json
|
|
||||||
import glob as glob_module
|
import glob as glob_module
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import shutil
|
|
||||||
import numpy as np
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from rich.table import Table
|
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn, TimeElapsedColumn
|
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn, TimeElapsedColumn
|
||||||
|
from rich.table import Table
|
||||||
|
|
||||||
factors_dir = Path(__file__).parent / "results" / "factors"
|
factors_dir = Path(__file__).parent / "results" / "factors"
|
||||||
if not factors_dir.exists():
|
if not factors_dir.exists():
|
||||||
@@ -659,6 +683,7 @@ def portfolio(
|
|||||||
if data.get("status") == "success" and data.get("ic") is not None:
|
if data.get("status") == "success" and data.get("ic") is not None:
|
||||||
results.append(data)
|
results.append(data)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
logger.warning("Failed to load factor file %s", f, exc_info=True)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
@@ -727,7 +752,7 @@ def portfolio(
|
|||||||
cwd=tmp_path,
|
cwd=tmp_path,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=120 # 2 min timeout per factor
|
timeout=120, # 2 min timeout per factor
|
||||||
)
|
)
|
||||||
|
|
||||||
# Read result
|
# Read result
|
||||||
@@ -788,7 +813,7 @@ def portfolio(
|
|||||||
str(i),
|
str(i),
|
||||||
cand.get("factor_name", "unknown")[:38],
|
cand.get("factor_name", "unknown")[:38],
|
||||||
f"{cand.get('ic', 0):.6f}",
|
f"{cand.get('ic', 0):.6f}",
|
||||||
f"{cand.get('sharpe', 0):.4f}" if cand.get('sharpe') else "N/A",
|
f"{cand.get('sharpe', 0):.4f}" if cand.get("sharpe") else "N/A",
|
||||||
)
|
)
|
||||||
|
|
||||||
console.print(table)
|
console.print(table)
|
||||||
@@ -806,7 +831,7 @@ def portfolio(
|
|||||||
return
|
return
|
||||||
|
|
||||||
corr_matrix = combined.corr().fillna(0)
|
corr_matrix = combined.corr().fillna(0)
|
||||||
ic_map = {cand['factor_name']: cand.get('ic', 0) for cand in candidates}
|
ic_map = {cand["factor_name"]: cand.get("ic", 0) for cand in candidates}
|
||||||
|
|
||||||
# 4. Greedy Selection
|
# 4. Greedy Selection
|
||||||
selected = []
|
selected = []
|
||||||
@@ -829,8 +854,7 @@ def portfolio(
|
|||||||
max_c = 0
|
max_c = 0
|
||||||
for sel in selected:
|
for sel in selected:
|
||||||
c = abs(corr_matrix.loc[factor, sel])
|
c = abs(corr_matrix.loc[factor, sel])
|
||||||
if c > max_c:
|
max_c = max(max_c, c)
|
||||||
max_c = c
|
|
||||||
|
|
||||||
if max_c < max_corr:
|
if max_c < max_corr:
|
||||||
selected.append(factor)
|
selected.append(factor)
|
||||||
@@ -849,23 +873,23 @@ def portfolio(
|
|||||||
|
|
||||||
for i, fname in enumerate(selected, 1):
|
for i, fname in enumerate(selected, 1):
|
||||||
# Find original data for display
|
# Find original data for display
|
||||||
data = next((c for c in candidates if c['factor_name'] == fname), {})
|
data = next((c for c in candidates if c["factor_name"] == fname), {})
|
||||||
ic = data.get('ic')
|
ic = data.get("ic")
|
||||||
sharpe = data.get('sharpe')
|
sharpe = data.get("sharpe")
|
||||||
|
|
||||||
# Calculate max corr with other selected factors
|
# Calculate max corr with other selected factors
|
||||||
max_c_val = 0
|
max_c_val = 0
|
||||||
for s in selected:
|
for s in selected:
|
||||||
if s != fname:
|
if s != fname:
|
||||||
val = abs(corr_matrix.loc[fname, s])
|
val = abs(corr_matrix.loc[fname, s])
|
||||||
if val > max_c_val: max_c_val = val
|
max_c_val = max(max_c_val, val)
|
||||||
|
|
||||||
table.add_row(
|
table.add_row(
|
||||||
str(i),
|
str(i),
|
||||||
fname[:38],
|
fname[:38],
|
||||||
f"{ic:.6f}" if ic is not None else "N/A",
|
f"{ic:.6f}" if ic is not None else "N/A",
|
||||||
f"{sharpe:.4f}" if sharpe is not None else "N/A",
|
f"{sharpe:.4f}" if sharpe is not None else "N/A",
|
||||||
f"{max_c_val:.4f}" if max_c_val > 0 else "-"
|
f"{max_c_val:.4f}" if max_c_val > 0 else "-",
|
||||||
)
|
)
|
||||||
|
|
||||||
console.print(table)
|
console.print(table)
|
||||||
@@ -875,7 +899,7 @@ def portfolio(
|
|||||||
"selected_factors": selected,
|
"selected_factors": selected,
|
||||||
"max_correlation": max_corr,
|
"max_correlation": max_corr,
|
||||||
"pool_size": top,
|
"pool_size": top,
|
||||||
"timestamp": pd.Timestamp.now().isoformat()
|
"timestamp": pd.Timestamp.now().isoformat(),
|
||||||
}
|
}
|
||||||
|
|
||||||
out_dir = Path(__file__).parent / "results" / "portfolio"
|
out_dir = Path(__file__).parent / "results" / "portfolio"
|
||||||
@@ -888,7 +912,7 @@ def portfolio(
|
|||||||
console.print(Panel(
|
console.print(Panel(
|
||||||
f"[bold]Portfolio saved to results/portfolio/selected_factors.json[/bold]\n"
|
f"[bold]Portfolio saved to results/portfolio/selected_factors.json[/bold]\n"
|
||||||
f"Selected {len(selected)} unique factors from {top} candidates.",
|
f"Selected {len(selected)} unique factors from {top} candidates.",
|
||||||
border_style="green"
|
border_style="green",
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
@@ -915,9 +939,9 @@ def portfolio_simple(
|
|||||||
the chance of finding factors in all categories. (default: 100)
|
the chance of finding factors in all categories. (default: 100)
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$ predix portfolio-simple # Top factors from different categories
|
$ nexquant portfolio-simple # Top factors from different categories
|
||||||
$ predix portfolio-simple -n 200 # Consider top 200 factors
|
$ nexquant portfolio-simple -n 200 # Consider top 200 factors
|
||||||
$ predix portfolio-simple -n 50 # Quick selection from top 50
|
$ nexquant portfolio-simple -n 50 # Quick selection from top 50
|
||||||
|
|
||||||
Expected Output:
|
Expected Output:
|
||||||
- Formatted table showing selected factors with their category, IC, and Sharpe
|
- Formatted table showing selected factors with their category, IC, and Sharpe
|
||||||
@@ -930,17 +954,16 @@ def portfolio_simple(
|
|||||||
Only loads existing JSON results and performs keyword matching.
|
Only loads existing JSON results and performs keyword matching.
|
||||||
|
|
||||||
See Also:
|
See Also:
|
||||||
predix portfolio - Correlation-based diversification (more accurate but slower)
|
nexquant portfolio - Correlation-based diversification (more accurate but slower)
|
||||||
predix top - View top factors before portfolio selection
|
nexquant top - View top factors before portfolio selection
|
||||||
predix build-strategies - Build strategies from selected factors
|
nexquant build-strategies - Build strategies from selected factors
|
||||||
"""
|
"""
|
||||||
import json
|
|
||||||
import glob as glob_module
|
import glob as glob_module
|
||||||
import re
|
import json
|
||||||
import numpy as np
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from rich.table import Table
|
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
|
from rich.table import Table
|
||||||
|
|
||||||
factors_dir = Path(__file__).parent / "results" / "factors"
|
factors_dir = Path(__file__).parent / "results" / "factors"
|
||||||
if not factors_dir.exists():
|
if not factors_dir.exists():
|
||||||
@@ -956,6 +979,7 @@ def portfolio_simple(
|
|||||||
if data.get("status") == "success" and data.get("ic") is not None:
|
if data.get("status") == "success" and data.get("ic") is not None:
|
||||||
results.append(data)
|
results.append(data)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
logger.warning("Failed to load factor file %s", f, exc_info=True)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
@@ -1001,7 +1025,7 @@ def portfolio_simple(
|
|||||||
best = categorized[cat][0] # Already sorted by IC
|
best = categorized[cat][0] # Already sorted by IC
|
||||||
selected.append({
|
selected.append({
|
||||||
"factor": best,
|
"factor": best,
|
||||||
"category": cat.capitalize() if cat != "other" else "Other"
|
"category": cat.capitalize() if cat != "other" else "Other",
|
||||||
})
|
})
|
||||||
|
|
||||||
# 5. Display Results
|
# 5. Display Results
|
||||||
@@ -1024,7 +1048,7 @@ def portfolio_simple(
|
|||||||
cand.get("factor_name", "unknown")[:38],
|
cand.get("factor_name", "unknown")[:38],
|
||||||
cat,
|
cat,
|
||||||
f"{cand.get('ic', 0):.6f}",
|
f"{cand.get('ic', 0):.6f}",
|
||||||
f"{cand.get('sharpe', 0):.4f}" if cand.get('sharpe') else "N/A",
|
f"{cand.get('sharpe', 0):.4f}" if cand.get("sharpe") else "N/A",
|
||||||
)
|
)
|
||||||
|
|
||||||
console.print(table)
|
console.print(table)
|
||||||
@@ -1034,7 +1058,7 @@ def portfolio_simple(
|
|||||||
"selected_factors": [item["factor"]["factor_name"] for item in selected],
|
"selected_factors": [item["factor"]["factor_name"] for item in selected],
|
||||||
"categories": {item["category"]: item["factor"]["factor_name"] for item in selected},
|
"categories": {item["category"]: item["factor"]["factor_name"] for item in selected},
|
||||||
"method": "simple_keyword_categorization",
|
"method": "simple_keyword_categorization",
|
||||||
"timestamp": str(pd.Timestamp.now().isoformat())
|
"timestamp": str(pd.Timestamp.now().isoformat()),
|
||||||
}
|
}
|
||||||
|
|
||||||
out_dir = Path(__file__).parent / "results" / "portfolio"
|
out_dir = Path(__file__).parent / "results" / "portfolio"
|
||||||
@@ -1047,7 +1071,7 @@ def portfolio_simple(
|
|||||||
console.print(Panel(
|
console.print(Panel(
|
||||||
f"[bold]Simple Portfolio saved to results/portfolio/portfolio_simple.json[/bold]\n"
|
f"[bold]Simple Portfolio saved to results/portfolio/portfolio_simple.json[/bold]\n"
|
||||||
f"Selected {len(selected)} factors across {len([c for c in categorized if categorized[c]])} categories.",
|
f"Selected {len(selected)} factors across {len([c for c in categorized if categorized[c]])} categories.",
|
||||||
border_style="green"
|
border_style="green",
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
@@ -1091,10 +1115,10 @@ def build_strategies(
|
|||||||
strategies. (default: False)
|
strategies. (default: False)
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$ predix build-strategies # Build from top 50, pairs only
|
$ nexquant build-strategies # Build from top 50, pairs only
|
||||||
$ predix build-strategies -n 100 -c 3 # Top 100, up to triplets
|
$ nexquant build-strategies -n 100 -c 3 # Top 100, up to triplets
|
||||||
$ predix build-strategies -d # Diversified (cross-category) only
|
$ nexquant build-strategies -d # Diversified (cross-category) only
|
||||||
$ predix build-strategies -n 30 -c 2 -d # Top 30, diversified pairs
|
$ nexquant build-strategies -n 30 -c 2 -d # Top 30, diversified pairs
|
||||||
|
|
||||||
Expected Output:
|
Expected Output:
|
||||||
- Formatted table of top strategies ranked by Sharpe ratio
|
- Formatted table of top strategies ranked by Sharpe ratio
|
||||||
@@ -1106,19 +1130,17 @@ def build_strategies(
|
|||||||
Scales with O(n^k) where n=factors, k=max_combo_size.
|
Scales with O(n^k) where n=factors, k=max_combo_size.
|
||||||
|
|
||||||
See Also:
|
See Also:
|
||||||
predix build-strategies-ai - AI-powered strategy generation via LLM
|
nexquant build-strategies-ai - AI-powered strategy generation via LLM
|
||||||
predix portfolio - Select diversified factors before combining
|
nexquant portfolio - Select diversified factors before combining
|
||||||
predix top - View top factors before building strategies
|
nexquant top - View top factors before building strategies
|
||||||
"""
|
"""
|
||||||
import pandas as pd
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from rich.table import Table
|
|
||||||
from rich.panel import Panel
|
|
||||||
|
|
||||||
from rdagent.scenarios.qlib.developer.strategy_builder import StrategyBuilder
|
from rdagent.scenarios.qlib.developer.strategy_builder import StrategyBuilder
|
||||||
|
from rich.panel import Panel
|
||||||
|
from rich.table import Table
|
||||||
|
|
||||||
console.print(Panel(
|
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",
|
"Systematically combining factors into trading strategies",
|
||||||
border_style="cyan",
|
border_style="cyan",
|
||||||
))
|
))
|
||||||
@@ -1249,12 +1271,12 @@ def build_strategies_ai(
|
|||||||
may require multiple improvement loops. (default: 1)
|
may require multiple improvement loops. (default: 1)
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$ predix build-strategies-ai # Generate 1 strategy, 5 loops max
|
$ nexquant build-strategies-ai # Generate 1 strategy, 5 loops max
|
||||||
$ predix build-strategies-ai -t 100 # Use top 100 factors as pool
|
$ nexquant build-strategies-ai -t 100 # Use top 100 factors as pool
|
||||||
$ predix build-strategies-ai -l 10 # Allow 10 improvement loops
|
$ nexquant build-strategies-ai -l 10 # Allow 10 improvement loops
|
||||||
$ predix build-strategies-ai --min-sharpe 2.0 # Stricter Sharpe requirement
|
$ nexquant build-strategies-ai --min-sharpe 2.0 # Stricter Sharpe requirement
|
||||||
$ predix build-strategies-ai --max-dd -0.15 # Tighter drawdown limit
|
$ nexquant build-strategies-ai --max-dd -0.15 # Tighter drawdown limit
|
||||||
$ predix build-strategies-ai -c 5 # Generate 5 accepted strategies
|
$ nexquant build-strategies-ai -c 5 # Generate 5 accepted strategies
|
||||||
|
|
||||||
Expected Output:
|
Expected Output:
|
||||||
- Formatted table of accepted strategies with Sharpe, return, drawdown,
|
- Formatted table of accepted strategies with Sharpe, return, drawdown,
|
||||||
@@ -1267,13 +1289,14 @@ def build_strategies_ai(
|
|||||||
Each loop requires a full backtest execution plus LLM API calls.
|
Each loop requires a full backtest execution plus LLM API calls.
|
||||||
|
|
||||||
See Also:
|
See Also:
|
||||||
predix build-strategies - Systematic (non-AI) strategy combination
|
nexquant build-strategies - Systematic (non-AI) strategy combination
|
||||||
predix quant - Generate new alpha factors via LLM trading loop
|
nexquant quant - Generate new alpha factors via LLM trading loop
|
||||||
predix evaluate - Evaluate factors before strategy building
|
nexquant evaluate - Evaluate factors before strategy building
|
||||||
"""
|
"""
|
||||||
from rich.panel import Panel
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from rich.panel import Panel
|
||||||
|
|
||||||
console.print(Panel(
|
console.print(Panel(
|
||||||
"[bold cyan]🧠 StrategyCoSTEER - AI Strategy Builder[/bold cyan]\n"
|
"[bold cyan]🧠 StrategyCoSTEER - AI Strategy Builder[/bold cyan]\n"
|
||||||
"Generating trading strategies from existing factors\n"
|
"Generating trading strategies from existing factors\n"
|
||||||
@@ -1322,12 +1345,12 @@ def build_strategies_ai(
|
|||||||
|
|
||||||
if not factors_dir.exists():
|
if not factors_dir.exists():
|
||||||
console.print("[bold red]❌ No factors directory found at results/factors/[/bold red]")
|
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
|
return
|
||||||
|
|
||||||
# Load evaluated factors
|
# Load evaluated factors
|
||||||
import json
|
|
||||||
import glob as glob_module
|
import glob as glob_module
|
||||||
|
import json
|
||||||
|
|
||||||
factors = []
|
factors = []
|
||||||
for f in glob_module.glob(str(factors_dir / "*.json")):
|
for f in glob_module.glob(str(factors_dir / "*.json")):
|
||||||
@@ -1337,11 +1360,12 @@ def build_strategies_ai(
|
|||||||
if data.get("status") == "success" and data.get("ic") is not None:
|
if data.get("status") == "success" and data.get("ic") is not None:
|
||||||
factors.append(data)
|
factors.append(data)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
logger.warning("Failed to load factor file %s", f, exc_info=True)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if len(factors) < 10:
|
if len(factors) < 10:
|
||||||
console.print(f"[bold red]❌ Only {len(factors)} evaluated factors found. Need at least 10.[/bold red]")
|
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
|
return
|
||||||
|
|
||||||
# Sort by IC and take top factors
|
# Sort by IC and take top factors
|
||||||
@@ -1410,15 +1434,15 @@ def build_strategies_ai(
|
|||||||
|
|
||||||
for i, r in enumerate(results, 1):
|
for i, r in enumerate(results, 1):
|
||||||
# Monthly return: use real backtest if available, else estimate
|
# Monthly return: use real backtest if available, else estimate
|
||||||
rb = r.get('real_backtest', {})
|
rb = r.get("real_backtest", {})
|
||||||
if isinstance(rb, dict) and rb.get('status') == 'success':
|
if isinstance(rb, dict) and rb.get("status") == "success":
|
||||||
monthly_pct = rb.get('monthly_return_pct', r.get('monthly_return_pct', 0))
|
monthly_pct = rb.get("monthly_return_pct", r.get("monthly_return_pct", 0))
|
||||||
n_trades = rb.get('n_trades', '-')
|
n_trades = rb.get("n_trades", "-")
|
||||||
real_ic = rb.get('ic', 0)
|
real_ic = rb.get("ic", 0)
|
||||||
else:
|
else:
|
||||||
monthly_pct = r.get('monthly_return_pct', r.get('real_monthly_return', 0))
|
monthly_pct = r.get("monthly_return_pct", r.get("real_monthly_return", 0))
|
||||||
n_trades = '-'
|
n_trades = "-"
|
||||||
real_ic = rb.get('ic', 0) if isinstance(rb, dict) else 0
|
real_ic = rb.get("ic", 0) if isinstance(rb, dict) else 0
|
||||||
|
|
||||||
table.add_row(
|
table.add_row(
|
||||||
str(i),
|
str(i),
|
||||||
@@ -1446,6 +1470,92 @@ def build_strategies_ai(
|
|||||||
console.print(traceback.format_exc())
|
console.print(traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
|
@app.command()
|
||||||
|
def generate_strategies(
|
||||||
|
count: int = typer.Option(10, "--count", "-n", help="Number of strategies to generate"),
|
||||||
|
workers: int = typer.Option(2, "--workers", "-w", help="Parallel workers"),
|
||||||
|
style: str = typer.Option("swing", "--style", "-s", help="Trading style: daytrading or swing"),
|
||||||
|
optuna: bool = typer.Option(True, "--optuna/--no-optuna", help="Enable Optuna optimization"),
|
||||||
|
optuna_trials: int = typer.Option(30, "--optuna-trials", help="Number of Optuna trials per strategy"),
|
||||||
|
top_factors: int = typer.Option(20, "--top-factors", help="Number of top factors to consider"),
|
||||||
|
min_sharpe: float = typer.Option(1.5, "--min-sharpe", help="Minimum Sharpe for acceptance"),
|
||||||
|
max_drawdown: float = typer.Option(-0.30, "--max-dd", help="Maximum drawdown allowed"),
|
||||||
|
min_win_rate: float = typer.Option(0.40, "--min-winrate", help="Minimum win rate for acceptance"),
|
||||||
|
min_monthly_return: float = typer.Option(15.0, "--min-monthly-return", help="Minimum OOS monthly return %% for acceptance"),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Generate trading strategies from top factors using LLM + Optuna optimization.
|
||||||
|
|
||||||
|
Loads top evaluated factors, uses LLM to generate strategy code,
|
||||||
|
evaluates with real EUR/USD OHLCV backtest (2.26M 1min bars),
|
||||||
|
and optimizes hyperparameters with Optuna (3-stage: 10→15→5 trials).
|
||||||
|
|
||||||
|
Uses the verified backtest engine (Sharpe on strategy returns,
|
||||||
|
MaxDD on equity curve, WinRate on trade P&L) with runtime verification.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
$ 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
|
||||||
|
$ nexquant generate-strategies --min-monthly-return 15 # 15% OOS monthly target
|
||||||
|
"""
|
||||||
|
from rich.table import Table as RichTable
|
||||||
|
|
||||||
|
console.print(f"\n[bold cyan]{'='*60}[/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]")
|
||||||
|
console.print(f" Accept: Sharpe≥[green]{min_sharpe}[/green] DD≥[green]{max_drawdown}[/green] WR≥[green]{min_win_rate}[/green] Mon≥[green]{min_monthly_return}%[/green]")
|
||||||
|
console.print(f"[bold cyan]{'='*60}[/bold cyan]\n")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from rdagent.scenarios.qlib.local.strategy_orchestrator import StrategyOrchestrator
|
||||||
|
|
||||||
|
orchestrator = StrategyOrchestrator(
|
||||||
|
top_factors=top_factors,
|
||||||
|
trading_style=style,
|
||||||
|
min_sharpe=min_sharpe,
|
||||||
|
max_drawdown=max_drawdown,
|
||||||
|
min_win_rate=min_win_rate,
|
||||||
|
min_monthly_return_pct=min_monthly_return,
|
||||||
|
use_optuna=optuna,
|
||||||
|
optuna_trials=optuna_trials,
|
||||||
|
continuous_optimization=optuna,
|
||||||
|
)
|
||||||
|
|
||||||
|
results = orchestrator.generate_strategies(count=count, workers=workers)
|
||||||
|
|
||||||
|
accepted = [r for r in results if r.get("status") == "success"]
|
||||||
|
rejected = len(results) - len(accepted)
|
||||||
|
|
||||||
|
console.print(f"\n[bold green]✓ {len(accepted)} accepted[/bold green] [yellow]{rejected} rejected[/yellow]")
|
||||||
|
|
||||||
|
if accepted:
|
||||||
|
accepted.sort(key=lambda r: r.get("sharpe_ratio", 0), reverse=True)
|
||||||
|
table = RichTable(title="Top Generated Strategies", show_header=True, header_style="bold cyan")
|
||||||
|
table.add_column("#", width=4)
|
||||||
|
table.add_column("Strategy", width=30)
|
||||||
|
table.add_column("Sharpe", width=8, justify="right")
|
||||||
|
table.add_column("MaxDD", width=8, justify="right")
|
||||||
|
table.add_column("WinRate", width=8, justify="right")
|
||||||
|
table.add_column("Trades", width=7, justify="right")
|
||||||
|
for i, r in enumerate(accepted[:10], 1):
|
||||||
|
table.add_row(
|
||||||
|
str(i), r.get("strategy_name", "?")[:28],
|
||||||
|
f"{r.get('sharpe_ratio', 0):.2f}", f"{r.get('max_drawdown', 0):.1%}",
|
||||||
|
f"{r.get('win_rate', 0):.1%}", str(r.get("num_trades", "?")),
|
||||||
|
)
|
||||||
|
console.print(table)
|
||||||
|
|
||||||
|
except ImportError as e:
|
||||||
|
console.print(f"[yellow]Strategy generator not available: {e}[/yellow]")
|
||||||
|
except Exception as e:
|
||||||
|
console.print(f"[bold red]❌ {e}[/bold red]")
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def health():
|
def health():
|
||||||
"""Check system health and configuration status.
|
"""Check system health and configuration status.
|
||||||
@@ -1456,8 +1566,8 @@ def health():
|
|||||||
helps identify setup issues before running computationally expensive operations.
|
helps identify setup issues before running computationally expensive operations.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$ predix health # Run full system health check
|
$ nexquant health # Run full system health check
|
||||||
$ predix health --verbose # Detailed output (if supported)
|
$ nexquant health --verbose # Detailed output (if supported)
|
||||||
|
|
||||||
Expected Output:
|
Expected Output:
|
||||||
- Python version and dependency status
|
- Python version and dependency status
|
||||||
@@ -1471,8 +1581,8 @@ def health():
|
|||||||
~5-15 seconds depending on network and database checks.
|
~5-15 seconds depending on network and database checks.
|
||||||
|
|
||||||
See Also:
|
See Also:
|
||||||
predix status - Show current trading loop status and statistics
|
nexquant status - Show current trading loop status and statistics
|
||||||
predix quant - Main trading loop command
|
nexquant quant - Main trading loop command
|
||||||
"""
|
"""
|
||||||
from rdagent.app.utils.health_check import health_check
|
from rdagent.app.utils.health_check import health_check
|
||||||
health_check()
|
health_check()
|
||||||
@@ -1489,8 +1599,8 @@ def status():
|
|||||||
and verifying data persistence.
|
and verifying data persistence.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$ predix status # Show current trading loop status
|
$ nexquant status # Show current trading loop status
|
||||||
$ predix status --json # JSON output (if supported)
|
$ nexquant status --json # JSON output (if supported)
|
||||||
|
|
||||||
Expected Output:
|
Expected Output:
|
||||||
- Trading loop process status: RUNNING or STOPPED
|
- Trading loop process status: RUNNING or STOPPED
|
||||||
@@ -1502,16 +1612,16 @@ def status():
|
|||||||
Nearly instantaneous (< 1 second).
|
Nearly instantaneous (< 1 second).
|
||||||
|
|
||||||
See Also:
|
See Also:
|
||||||
predix health - Check system health and configuration
|
nexquant health - Check system health and configuration
|
||||||
predix quant - Start the quantitative trading loop
|
nexquant quant - Start the quantitative trading loop
|
||||||
predix top - View top evaluated factors
|
nexquant top - View top evaluated factors
|
||||||
"""
|
"""
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
# Process check
|
# Process check
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["pgrep", "-f", "fin_quant"],
|
["pgrep", "-f", "fin_quant"],
|
||||||
capture_output=True, text=True
|
capture_output=True, text=True,
|
||||||
)
|
)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
console.print("[bold green]✅ Trading Loop: RUNNING[/bold green]")
|
console.print("[bold green]✅ Trading Loop: RUNNING[/bold green]")
|
||||||
@@ -1529,7 +1639,7 @@ def status():
|
|||||||
factors = c.fetchone()[0]
|
factors = c.fetchone()[0]
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
console.print(f"\n📊 Results:")
|
console.print("\n📊 Results:")
|
||||||
console.print(f" Backtest runs: {runs}")
|
console.print(f" Backtest runs: {runs}")
|
||||||
console.print(f" Factors: {factors}")
|
console.print(f" Factors: {factors}")
|
||||||
|
|
||||||
@@ -1552,6 +1662,7 @@ def _load_strategies():
|
|||||||
try:
|
try:
|
||||||
raw = json.loads(p.read_text())
|
raw = json.loads(p.read_text())
|
||||||
except Exception:
|
except Exception:
|
||||||
|
logger.warning(f"Failed to load strategy file {p}")
|
||||||
continue
|
continue
|
||||||
if not isinstance(raw, dict):
|
if not isinstance(raw, dict):
|
||||||
continue
|
continue
|
||||||
@@ -1598,13 +1709,14 @@ def best(
|
|||||||
"""Rank backtested strategies by performance — source code is never exposed.
|
"""Rank backtested strategies by performance — source code is never exposed.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$ predix best # Top 10 by composite score
|
$ nexquant best # Top 10 by composite score
|
||||||
$ predix best -n 20 -m sharpe # Top 20 by Sharpe
|
$ nexquant best -n 20 -m sharpe # Top 20 by Sharpe
|
||||||
$ predix best --no-realistic # Include numerically suspicious runs
|
$ nexquant best --no-realistic # Include numerically suspicious runs
|
||||||
$ predix best --show TrendMomentumHybrid
|
$ nexquant best --show TrendMomentumHybrid
|
||||||
$ predix best -n 50 --export /tmp/top.json
|
$ nexquant best -n 50 --export /tmp/top.json
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from rich.table import Table
|
from rich.table import Table
|
||||||
|
|
||||||
items = _load_strategies()
|
items = _load_strategies()
|
||||||
@@ -1669,7 +1781,7 @@ def best(
|
|||||||
)
|
)
|
||||||
console.print(table)
|
console.print(table)
|
||||||
console.print(f"\n[dim]{len(pool)} strategies matched filters (of {len(items)} total). "
|
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:
|
if export:
|
||||||
payload = [{k: v for k, v in s.items() if k != "code"} for s in top]
|
payload = [{k: v for k, v in s.items() if k != "code"} for s in top]
|
||||||
@@ -1690,7 +1802,7 @@ def kronos_factor(
|
|||||||
"""Generate Kronos-mini predicted-return alpha factor (Option A).
|
"""Generate Kronos-mini predicted-return alpha factor (Option A).
|
||||||
|
|
||||||
Runs Kronos-mini (4.1M params OHLCV foundation model, AAAI 2026) on rolling
|
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.
|
standard MultiIndex (datetime, instrument) format.
|
||||||
|
|
||||||
Strategy: every STRIDE bars, use the previous CONTEXT bars as input and
|
Strategy: every STRIDE bars, use the previous CONTEXT bars as input and
|
||||||
@@ -1703,13 +1815,13 @@ def kronos_factor(
|
|||||||
git_ignore_folder/factor_implementation_source_data/intraday_pv.h5
|
git_ignore_folder/factor_implementation_source_data/intraday_pv.h5
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$ predix kronos-factor # Default: daily stride, GPU
|
$ nexquant kronos-factor # Default: daily stride, GPU
|
||||||
$ predix kronos-factor --pred 30 --device cpu # 30-bar horizon, CPU
|
$ nexquant kronos-factor --pred 30 --device cpu # 30-bar horizon, CPU
|
||||||
$ predix kronos-factor --context 256 --pred 48
|
$ nexquant kronos-factor --context 256 --pred 48
|
||||||
|
|
||||||
See Also:
|
See Also:
|
||||||
predix kronos-eval - Evaluate Kronos as model and compute IC vs LightGBM
|
nexquant kronos-eval - Evaluate Kronos as model and compute IC vs LightGBM
|
||||||
predix top - Show top factors by IC
|
nexquant top - Show top factors by IC
|
||||||
"""
|
"""
|
||||||
from rdagent.components.coder.kronos_adapter import _cuda_available
|
from rdagent.components.coder.kronos_adapter import _cuda_available
|
||||||
_device = device or ("cuda" if _cuda_available() else "cpu")
|
_device = device or ("cuda" if _cuda_available() else "cpu")
|
||||||
@@ -1721,7 +1833,7 @@ def kronos_factor(
|
|||||||
console.print("Run data conversion first — see README Data Setup section.")
|
console.print("Run data conversion first — see README Data Setup section.")
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
console.print(f"[bold]Kronos Factor Generator[/bold]")
|
console.print("[bold]Kronos Factor Generator[/bold]")
|
||||||
console.print(f" Context: [cyan]{context}[/cyan] bars | Pred: [cyan]{pred}[/cyan] bars | Device: [cyan]{_device}[/cyan]")
|
console.print(f" Context: [cyan]{context}[/cyan] bars | Pred: [cyan]{pred}[/cyan] bars | Device: [cyan]{_device}[/cyan]")
|
||||||
|
|
||||||
from rdagent.components.coder.kronos_adapter import build_kronos_factor
|
from rdagent.components.coder.kronos_adapter import build_kronos_factor
|
||||||
@@ -1761,7 +1873,7 @@ def kronos_factor(
|
|||||||
console.print(f"\n[green]Factor saved:[/green] {out_path}")
|
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" Shape: {factor_df.shape} | Non-NaN: {meta['n_non_nan']}")
|
||||||
console.print(f" Metadata: {meta_path}")
|
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")
|
@app.command("kronos-eval")
|
||||||
@@ -1787,13 +1899,13 @@ def kronos_eval(
|
|||||||
git_ignore_folder/factor_implementation_source_data/intraday_pv.h5
|
git_ignore_folder/factor_implementation_source_data/intraday_pv.h5
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$ predix kronos-eval # Default: 30-bar horizon
|
$ nexquant kronos-eval # Default: 30-bar horizon
|
||||||
$ predix kronos-eval --pred 96 --device cuda # Daily horizon, GPU
|
$ nexquant kronos-eval --pred 96 --device cuda # Daily horizon, GPU
|
||||||
$ predix kronos-eval --context 256 --pred 15 # Shorter horizon
|
$ nexquant kronos-eval --context 256 --pred 15 # Shorter horizon
|
||||||
|
|
||||||
See Also:
|
See Also:
|
||||||
predix kronos-factor - Generate Kronos factor for the factor pipeline
|
nexquant kronos-factor - Generate Kronos factor for the factor pipeline
|
||||||
predix best - Show top strategies
|
nexquant best - Show top strategies
|
||||||
"""
|
"""
|
||||||
from rdagent.components.coder.kronos_adapter import _cuda_available
|
from rdagent.components.coder.kronos_adapter import _cuda_available
|
||||||
_device = device or ("cuda" if _cuda_available() else "cpu")
|
_device = device or ("cuda" if _cuda_available() else "cpu")
|
||||||
@@ -1804,7 +1916,7 @@ def kronos_eval(
|
|||||||
console.print(f"[red]ERROR: Data not found at {data_path}[/red]")
|
console.print(f"[red]ERROR: Data not found at {data_path}[/red]")
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
console.print(f"[bold]Kronos Model Evaluator[/bold] (alongside LightGBM)")
|
console.print("[bold]Kronos Model Evaluator[/bold] (alongside LightGBM)")
|
||||||
console.print(f" Context: [cyan]{context}[/cyan] bars | Pred: [cyan]{pred}[/cyan] bars | Device: [cyan]{_device}[/cyan]")
|
console.print(f" Context: [cyan]{context}[/cyan] bars | Pred: [cyan]{pred}[/cyan] bars | Device: [cyan]{_device}[/cyan]")
|
||||||
console.print(" Running evaluation...")
|
console.print(" Running evaluation...")
|
||||||
|
|
||||||
@@ -1819,12 +1931,12 @@ def kronos_eval(
|
|||||||
batch_size=batch_size,
|
batch_size=batch_size,
|
||||||
)
|
)
|
||||||
|
|
||||||
console.print(f"\n[bold]Kronos-mini Results[/bold]")
|
console.print("\n[bold]Kronos-mini Results[/bold]")
|
||||||
console.print(f" Predictions: [cyan]{metrics['n_predictions']}[/cyan]")
|
console.print(f" Predictions: [cyan]{metrics['n_predictions']}[/cyan]")
|
||||||
console.print(f" IC (mean): [{'green' if metrics['IC_mean'] > 0.02 else 'yellow'}]{metrics['IC_mean']:.4f}[/]")
|
console.print(f" IC (mean): [{'green' if metrics['IC_mean'] > 0.02 else 'yellow'}]{metrics['IC_mean']:.4f}[/]")
|
||||||
console.print(f" IC IR: [{'green' if metrics['IC_IR'] > 0.5 else 'yellow'}]{metrics['IC_IR']:.4f}[/] (>0.5 = strong signal)")
|
console.print(f" IC IR: [{'green' if metrics['IC_IR'] > 0.5 else 'yellow'}]{metrics['IC_IR']:.4f}[/] (>0.5 = strong signal)")
|
||||||
console.print(f" Hit Rate: [{'green' if metrics['hit_rate'] > 0.52 else 'yellow'}]{metrics['hit_rate']:.2%}[/] (>50% = directionally useful)")
|
console.print(f" Hit Rate: [{'green' if metrics['hit_rate'] > 0.52 else 'yellow'}]{metrics['hit_rate']:.2%}[/] (>50% = directionally useful)")
|
||||||
console.print(f"\n[dim]Reference: LightGBM baseline IC typically 0.01–0.05 on 1-min EUR/USD[/dim]")
|
console.print("\n[dim]Reference: LightGBM baseline IC typically 0.01–0.05 on 1-min EUR/USD[/dim]")
|
||||||
|
|
||||||
import json as _json
|
import json as _json
|
||||||
out_dir = Path("results/kronos")
|
out_dir = Path("results/kronos")
|
||||||
+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
|
## 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
|
```bash
|
||||||
# Backup to private repo
|
# Backup to private repo
|
||||||
cd ~/Predix
|
cd ~/NexQuant
|
||||||
git archive --format=tar prompts/local/ | gzip > ~/backups/prompts_local_$(date +%Y%m%d).tar.gz
|
git archive --format=tar prompts/local/ | gzip > ~/backups/prompts_local_$(date +%Y%m%d).tar.gz
|
||||||
|
|
||||||
# Or sync to private GitHub repo
|
# Or sync to private GitHub repo
|
||||||
git clone git@github.com:TPTBusiness/predix-prompts-private.git
|
git clone git@github.com:TPTBusiness/nexquant-prompts-private.git
|
||||||
cp -r prompts/local/* predix-prompts-private/
|
cp -r prompts/local/* nexquant-prompts-private/
|
||||||
cd predix-prompts-private && git push
|
cd nexquant-prompts-private && git push
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ strategy_generation:
|
|||||||
5. signal.name must be 'signal'
|
5. signal.name must be 'signal'
|
||||||
|
|
||||||
IC-Guided Factor Selection:
|
IC-Guided Factor Selection:
|
||||||
- Factors with |IC| > 0.10 are highly predictive - PRIORITIZE these
|
- Factors with |IC| > 0.15 are highly predictive - PRIORITIZE these
|
||||||
- Factors with |IC| > 0.05 are moderately predictive - USE these
|
- Factors with |IC| > 0.08 are moderately predictive - USE these
|
||||||
- Factors with |IC| < 0.05 are weak - AVOID unless complementary
|
- Factors with |IC| < 0.08 are weak - AVOID unless complementary
|
||||||
- Combine factors with different signs of IC for diversification
|
- Combine factors with different signs of IC for diversification
|
||||||
- Weight factors proportionally to their |IC| values
|
- Weight factors proportionally to their |IC| values
|
||||||
|
|
||||||
@@ -62,6 +62,7 @@ strategy_generation:
|
|||||||
TRADING STYLE: {{ trading_style }}
|
TRADING STYLE: {{ trading_style }}
|
||||||
TARGET SHARPE: > {{ min_sharpe }}
|
TARGET SHARPE: > {{ min_sharpe }}
|
||||||
MAX DRAWDOWN: {{ max_drawdown }}
|
MAX DRAWDOWN: {{ max_drawdown }}
|
||||||
|
TARGET MONTHLY RETURN: > {{ min_monthly_return }}%
|
||||||
|
|
||||||
CRITICAL CODE RULES:
|
CRITICAL CODE RULES:
|
||||||
1. DO NOT define functions - write direct executable code
|
1. DO NOT define functions - write direct executable code
|
||||||
|
|||||||
+5
-5
@@ -7,7 +7,7 @@ requires = [
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
authors = [
|
authors = [
|
||||||
{email = "nico@predix.io", name = "Predix Team"},
|
{email = "nico@nexquant.io", name = "NexQuant Team"},
|
||||||
]
|
]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 3 - Alpha",
|
"Development Status :: 3 - Alpha",
|
||||||
@@ -16,7 +16,7 @@ classifiers = [
|
|||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: 3.11",
|
"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 = [
|
dynamic = [
|
||||||
"dependencies",
|
"dependencies",
|
||||||
"optional-dependencies",
|
"optional-dependencies",
|
||||||
@@ -29,7 +29,7 @@ keywords = [
|
|||||||
"EUR/USD",
|
"EUR/USD",
|
||||||
"Forex",
|
"Forex",
|
||||||
]
|
]
|
||||||
name = "predix"
|
name = "nexquant"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
|
|
||||||
@@ -37,8 +37,8 @@ requires-python = ">=3.10"
|
|||||||
rdagent = "rdagent.app.cli:app"
|
rdagent = "rdagent.app.cli:app"
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
homepage = "https://github.com/PredixAI/predix/"
|
homepage = "https://github.com/NexQuantAI/nexquant/"
|
||||||
issue = "https://github.com/PredixAI/predix/issues"
|
issue = "https://github.com/NexQuantAI/nexquant/issues"
|
||||||
|
|
||||||
[tool.coverage.report]
|
[tool.coverage.report]
|
||||||
fail_under = 80
|
fail_under = 80
|
||||||
|
|||||||
+142
-119
@@ -21,11 +21,17 @@ load_dotenv(".env")
|
|||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
from importlib.resources import path as rpath
|
from importlib.resources import path as rpath
|
||||||
from typing import Dict, Optional
|
from typing import Annotated
|
||||||
|
|
||||||
import typer
|
import typer
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from typing_extensions import Annotated
|
|
||||||
|
try:
|
||||||
|
from rdagent.utils.env import logger
|
||||||
|
except ImportError:
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
from rdagent.app.data_science.loop import main as data_science
|
from rdagent.app.data_science.loop import main as data_science
|
||||||
from rdagent.app.finetune.llm.loop import main as llm_finetune
|
from rdagent.app.finetune.llm.loop import main as llm_finetune
|
||||||
@@ -139,10 +145,10 @@ def ds_user_interact(port=19900):
|
|||||||
|
|
||||||
@app.command(name="fin_factor")
|
@app.command(name="fin_factor")
|
||||||
def fin_factor_cli(
|
def fin_factor_cli(
|
||||||
path: Optional[str] = None,
|
path: str | None = None,
|
||||||
step_n: Optional[int] = None,
|
step_n: int | None = None,
|
||||||
loop_n: Optional[int] = None,
|
loop_n: int | None = None,
|
||||||
all_duration: Optional[str] = None,
|
all_duration: str | None = None,
|
||||||
checkout: CheckoutOption = True,
|
checkout: CheckoutOption = True,
|
||||||
):
|
):
|
||||||
fin_factor(path=path, step_n=step_n, loop_n=loop_n, all_duration=all_duration, checkout=checkout)
|
fin_factor(path=path, step_n=step_n, loop_n=loop_n, all_duration=all_duration, checkout=checkout)
|
||||||
@@ -150,10 +156,10 @@ def fin_factor_cli(
|
|||||||
|
|
||||||
@app.command(name="fin_model")
|
@app.command(name="fin_model")
|
||||||
def fin_model_cli(
|
def fin_model_cli(
|
||||||
path: Optional[str] = None,
|
path: str | None = None,
|
||||||
step_n: Optional[int] = None,
|
step_n: int | None = None,
|
||||||
loop_n: Optional[int] = None,
|
loop_n: int | None = None,
|
||||||
all_duration: Optional[str] = None,
|
all_duration: str | None = None,
|
||||||
checkout: CheckoutOption = True,
|
checkout: CheckoutOption = True,
|
||||||
):
|
):
|
||||||
fin_model(path=path, step_n=step_n, loop_n=loop_n, all_duration=all_duration, checkout=checkout)
|
fin_model(path=path, step_n=step_n, loop_n=loop_n, all_duration=all_duration, checkout=checkout)
|
||||||
@@ -161,10 +167,10 @@ def fin_model_cli(
|
|||||||
|
|
||||||
@app.command(name="fin_quant")
|
@app.command(name="fin_quant")
|
||||||
def fin_quant_cli(
|
def fin_quant_cli(
|
||||||
path: Optional[str] = None,
|
path: str | None = None,
|
||||||
step_n: Optional[int] = None,
|
step_n: int | None = None,
|
||||||
loop_n: Optional[int] = None,
|
loop_n: int | None = None,
|
||||||
all_duration: Optional[str] = None,
|
all_duration: str | None = None,
|
||||||
checkout: CheckoutOption = True,
|
checkout: CheckoutOption = True,
|
||||||
with_dashboard: bool = typer.Option(False, "--with-dashboard/-d", help="Start web dashboard automatically"),
|
with_dashboard: bool = typer.Option(False, "--with-dashboard/-d", help="Start web dashboard automatically"),
|
||||||
with_cli_dashboard: bool = typer.Option(False, "--cli-dashboard/-c", help="Show beautiful CLI dashboard"),
|
with_cli_dashboard: bool = typer.Option(False, "--cli-dashboard/-c", help="Show beautiful CLI dashboard"),
|
||||||
@@ -224,7 +230,7 @@ def fin_quant_cli(
|
|||||||
if not api_key:
|
if not api_key:
|
||||||
console.print("\n[bold red]❌ OPENROUTER_API_KEY not set in .env[/bold red]")
|
console.print("\n[bold red]❌ OPENROUTER_API_KEY not set in .env[/bold red]")
|
||||||
console.print("[yellow]Add your API key to .env and retry:[/yellow]")
|
console.print("[yellow]Add your API key to .env and retry:[/yellow]")
|
||||||
console.print(' OPENROUTER_API_KEY=sk-or-your-key-here')
|
console.print(" OPENROUTER_API_KEY=sk-or-your-key-here")
|
||||||
raise typer.Exit(code=1)
|
raise typer.Exit(code=1)
|
||||||
|
|
||||||
os.environ["OPENAI_API_KEY"] = api_key
|
os.environ["OPENAI_API_KEY"] = api_key
|
||||||
@@ -243,8 +249,8 @@ def fin_quant_cli(
|
|||||||
console.print(f" [dim]Base URL: {os.environ['OPENAI_API_BASE']}[/dim]")
|
console.print(f" [dim]Base URL: {os.environ['OPENAI_API_BASE']}[/dim]")
|
||||||
|
|
||||||
# Wait until the llama.cpp server is fully loaded before starting the pipeline
|
# Wait until the llama.cpp server is fully loaded before starting the pipeline
|
||||||
import urllib.request
|
|
||||||
import urllib.error
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
base_url = os.environ["OPENAI_API_BASE"].removesuffix("/v1").rstrip("/")
|
base_url = os.environ["OPENAI_API_BASE"].removesuffix("/v1").rstrip("/")
|
||||||
health_url = f"{base_url}/health"
|
health_url = f"{base_url}/health"
|
||||||
@@ -278,7 +284,7 @@ def fin_quant_cli(
|
|||||||
subprocess.run(
|
subprocess.run(
|
||||||
["python", "web/dashboard_api.py"],
|
["python", "web/dashboard_api.py"],
|
||||||
cwd=str(Path(__file__).parent.parent.parent),
|
cwd=str(Path(__file__).parent.parent.parent),
|
||||||
env={**os.environ, "FLASK_ENV": "development"}
|
env={**os.environ, "FLASK_ENV": "development"},
|
||||||
)
|
)
|
||||||
|
|
||||||
dashboard_thread = threading.Thread(target=start_web_dashboard, daemon=True)
|
dashboard_thread = threading.Thread(target=start_web_dashboard, daemon=True)
|
||||||
@@ -288,7 +294,7 @@ def fin_quant_cli(
|
|||||||
# Start CLI Dashboard wenn gewünscht
|
# Start CLI Dashboard wenn gewünscht
|
||||||
if with_cli_dashboard:
|
if with_cli_dashboard:
|
||||||
def start_cli_dash():
|
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)
|
run_dashboard(log_path="fin_quant.log", refresh_interval=3)
|
||||||
|
|
||||||
cli_thread = threading.Thread(target=start_cli_dash, daemon=True)
|
cli_thread = threading.Thread(target=start_cli_dash, daemon=True)
|
||||||
@@ -320,9 +326,9 @@ def fin_quant_cli(
|
|||||||
|
|
||||||
@app.command(name="fin_factor_report")
|
@app.command(name="fin_factor_report")
|
||||||
def fin_factor_report_cli(
|
def fin_factor_report_cli(
|
||||||
report_folder: Optional[str] = None,
|
report_folder: str | None = None,
|
||||||
path: Optional[str] = None,
|
path: str | None = None,
|
||||||
all_duration: Optional[str] = None,
|
all_duration: str | None = None,
|
||||||
checkout: CheckoutOption = True,
|
checkout: CheckoutOption = True,
|
||||||
):
|
):
|
||||||
fin_factor_report(report_folder=report_folder, path=path, all_duration=all_duration, checkout=checkout)
|
fin_factor_report(report_folder=report_folder, path=path, all_duration=all_duration, checkout=checkout)
|
||||||
@@ -335,12 +341,12 @@ def general_model_cli(report_file_path: str):
|
|||||||
|
|
||||||
@app.command(name="data_science")
|
@app.command(name="data_science")
|
||||||
def data_science_cli(
|
def data_science_cli(
|
||||||
path: Optional[str] = None,
|
path: str | None = None,
|
||||||
checkout: CheckoutOption = True,
|
checkout: CheckoutOption = True,
|
||||||
step_n: Optional[int] = None,
|
step_n: int | None = None,
|
||||||
loop_n: Optional[int] = None,
|
loop_n: int | None = None,
|
||||||
timeout: Optional[str] = None,
|
timeout: str | None = None,
|
||||||
competition: Optional[str] = None,
|
competition: str | None = None,
|
||||||
):
|
):
|
||||||
data_science(
|
data_science(
|
||||||
path=path,
|
path=path,
|
||||||
@@ -354,16 +360,16 @@ def data_science_cli(
|
|||||||
|
|
||||||
@app.command(name="llm_finetune")
|
@app.command(name="llm_finetune")
|
||||||
def llm_finetune_cli(
|
def llm_finetune_cli(
|
||||||
path: Optional[str] = None,
|
path: str | None = None,
|
||||||
checkout: CheckoutOption = True,
|
checkout: CheckoutOption = True,
|
||||||
benchmark: Optional[str] = None,
|
benchmark: str | None = None,
|
||||||
benchmark_description: Optional[str] = None,
|
benchmark_description: str | None = None,
|
||||||
dataset: Optional[str] = None,
|
dataset: str | None = None,
|
||||||
base_model: Optional[str] = None,
|
base_model: str | None = None,
|
||||||
upper_data_size_limit: Optional[int] = None,
|
upper_data_size_limit: int | None = None,
|
||||||
step_n: Optional[int] = None,
|
step_n: int | None = None,
|
||||||
loop_n: Optional[int] = None,
|
loop_n: int | None = None,
|
||||||
timeout: Optional[str] = None,
|
timeout: str | None = None,
|
||||||
):
|
):
|
||||||
llm_finetune(
|
llm_finetune(
|
||||||
path=path,
|
path=path,
|
||||||
@@ -429,6 +435,7 @@ def rl_trading_cli(
|
|||||||
rdagent rl_trading --mode backtest --no-with-protections
|
rdagent rl_trading --mode backtest --no-with-protections
|
||||||
"""
|
"""
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
console = Console()
|
console = Console()
|
||||||
@@ -440,18 +447,18 @@ def rl_trading_cli(
|
|||||||
with open(config_path) as f:
|
with open(config_path) as f:
|
||||||
config = yaml.safe_load(f) or {}
|
config = yaml.safe_load(f) or {}
|
||||||
|
|
||||||
console.print(f"\n[bold blue]🤖 RL Trading Agent[/bold blue]")
|
console.print("\n[bold blue]🤖 RL Trading Agent[/bold blue]")
|
||||||
console.print(f"Mode: [cyan]{mode}[/cyan]")
|
console.print(f"Mode: [cyan]{mode}[/cyan]")
|
||||||
console.print(f"Algorithm: [cyan]{algorithm.upper()}[/cyan]")
|
console.print(f"Algorithm: [cyan]{algorithm.upper()}[/cyan]")
|
||||||
console.print(f"Protections: {'[green]Enabled[/green]' if with_protections else '[red]Disabled[/red]'}")
|
console.print(f"Protections: {'[green]Enabled[/green]' if with_protections else '[red]Disabled[/red]'}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from rdagent.components.coder.rl import RLTradingAgent, RLCosteer, TradingEnv
|
from rdagent.components.coder.rl import RLCosteer, RLTradingAgent, TradingEnv
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
console.print(f"[bold red]Error: RL components not available.[/bold red]")
|
console.print("[bold red]Error: RL components not available.[/bold red]")
|
||||||
console.print(f"Details: {e}")
|
console.print(f"Details: {e}")
|
||||||
console.print(f"\n[yellow]Install RL dependencies:[/yellow]")
|
console.print("\n[yellow]Install RL dependencies:[/yellow]")
|
||||||
console.print(f" pip install stable-baselines3 gymnasium")
|
console.print(" pip install stable-baselines3 gymnasium")
|
||||||
raise typer.Exit(code=1)
|
raise typer.Exit(code=1)
|
||||||
|
|
||||||
if mode == "train":
|
if mode == "train":
|
||||||
@@ -467,8 +474,8 @@ def rl_trading_cli(
|
|||||||
console.print("[dim]Loading market data...[/dim]")
|
console.print("[dim]Loading market data...[/dim]")
|
||||||
# TODO: Load actual data from config
|
# TODO: Load actual data from config
|
||||||
# For now, create mock environment
|
# For now, create mock environment
|
||||||
import numpy as np
|
|
||||||
import gymnasium as gym
|
import gymnasium as gym
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
# Create simple mock environment for demonstration
|
# Create simple mock environment for demonstration
|
||||||
class MockTradingEnv(gym.Env):
|
class MockTradingEnv(gym.Env):
|
||||||
@@ -504,7 +511,7 @@ def rl_trading_cli(
|
|||||||
model_path_out.parent.mkdir(parents=True, exist_ok=True)
|
model_path_out.parent.mkdir(parents=True, exist_ok=True)
|
||||||
agent.save(model_path_out)
|
agent.save(model_path_out)
|
||||||
|
|
||||||
console.print(f"\n[bold green]✅ Training complete![/bold green]")
|
console.print("\n[bold green]✅ Training complete![/bold green]")
|
||||||
console.print(f"Model saved to: [cyan]{model_path_out}[/cyan]")
|
console.print(f"Model saved to: [cyan]{model_path_out}[/cyan]")
|
||||||
console.print(f"Algorithm: {result['algorithm']}")
|
console.print(f"Algorithm: {result['algorithm']}")
|
||||||
console.print(f"Timesteps: {result['total_timesteps']:,}")
|
console.print(f"Timesteps: {result['total_timesteps']:,}")
|
||||||
@@ -530,9 +537,9 @@ def rl_trading_cli(
|
|||||||
agent = RLTradingAgent(algorithm=algorithm.upper())
|
agent = RLTradingAgent(algorithm=algorithm.upper())
|
||||||
|
|
||||||
# Run backtest
|
# Run backtest
|
||||||
from rdagent.components.backtesting import FactorBacktester
|
|
||||||
import pandas as pd
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from rdagent.components.backtesting import FactorBacktester
|
||||||
|
|
||||||
backtester = FactorBacktester()
|
backtester = FactorBacktester()
|
||||||
|
|
||||||
@@ -541,8 +548,8 @@ def rl_trading_cli(
|
|||||||
n_steps = 500
|
n_steps = 500
|
||||||
mock_prices = pd.Series(100 + np.cumsum(np.random.randn(n_steps) * 0.5))
|
mock_prices = pd.Series(100 + np.cumsum(np.random.randn(n_steps) * 0.5))
|
||||||
mock_indicators = pd.DataFrame({
|
mock_indicators = pd.DataFrame({
|
||||||
'rsi': np.random.uniform(30, 70, n_steps),
|
"rsi": np.random.uniform(30, 70, n_steps),
|
||||||
'macd': np.random.randn(n_steps) * 0.1,
|
"macd": np.random.randn(n_steps) * 0.1,
|
||||||
})
|
})
|
||||||
|
|
||||||
console.print("[yellow]Running backtest...[/yellow]")
|
console.print("[yellow]Running backtest...[/yellow]")
|
||||||
@@ -553,7 +560,7 @@ def rl_trading_cli(
|
|||||||
enable_protections=with_protections,
|
enable_protections=with_protections,
|
||||||
)
|
)
|
||||||
|
|
||||||
console.print(f"\n[bold green]✅ Backtest complete![/bold green]")
|
console.print("\n[bold green]✅ Backtest complete![/bold green]")
|
||||||
console.print(f" Final Equity: [green]${metrics.get('final_equity', 0):,.2f}[/green]")
|
console.print(f" Final Equity: [green]${metrics.get('final_equity', 0):,.2f}[/green]")
|
||||||
console.print(f" Sharpe Ratio: {metrics.get('sharpe_ratio', 0):.3f}")
|
console.print(f" Sharpe Ratio: {metrics.get('sharpe_ratio', 0):.3f}")
|
||||||
console.print(f" Max Drawdown: {metrics.get('max_drawdown', 0):.2%}")
|
console.print(f" Max Drawdown: {metrics.get('max_drawdown', 0):.2%}")
|
||||||
@@ -610,6 +617,9 @@ def generate_strategies_cli(
|
|||||||
top_factors: int = typer.Option(20, "--top-factors", help="Number of top factors to consider"),
|
top_factors: int = typer.Option(20, "--top-factors", help="Number of top factors to consider"),
|
||||||
continuous: bool = typer.Option(True, "--continuous/--single-pass", help="Optimize ALL strategies including rejected ones"),
|
continuous: bool = typer.Option(True, "--continuous/--single-pass", help="Optimize ALL strategies including rejected ones"),
|
||||||
max_iterations: int = typer.Option(1, "--max-iterations", "-i", help="Number of generation-optimization cycles (1 = single pass, >1 = continuous)"),
|
max_iterations: int = typer.Option(1, "--max-iterations", "-i", help="Number of generation-optimization cycles (1 = single pass, >1 = continuous)"),
|
||||||
|
min_sharpe: float = typer.Option(1.5, "--min-sharpe", help="Minimum Sharpe ratio for acceptance"),
|
||||||
|
max_drawdown: float = typer.Option(-0.30, "--max-dd", help="Maximum drawdown allowed"),
|
||||||
|
min_win_rate: float = typer.Option(0.40, "--min-winrate", help="Minimum win rate for acceptance"),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Generate trading strategies from evaluated factors.
|
Generate trading strategies from evaluated factors.
|
||||||
@@ -627,7 +637,7 @@ def generate_strategies_cli(
|
|||||||
rdagent generate_strategies -n 3 -i 10 --optuna-trials 50 # Deep optimization
|
rdagent generate_strategies -n 3 -i 10 --optuna-trials 50 # Deep optimization
|
||||||
"""
|
"""
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TimeRemainingColumn
|
from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn, TimeRemainingColumn
|
||||||
from rich.table import Table
|
from rich.table import Table
|
||||||
|
|
||||||
console = Console()
|
console = Console()
|
||||||
@@ -646,7 +656,7 @@ def generate_strategies_cli(
|
|||||||
raise typer.Exit(code=1)
|
raise typer.Exit(code=1)
|
||||||
|
|
||||||
console.print(f"\n[bold blue]{'='*60}[/bold blue]")
|
console.print(f"\n[bold blue]{'='*60}[/bold blue]")
|
||||||
console.print(f"[bold blue] PREDIX Strategy Generator[/bold blue]")
|
console.print("[bold blue] PREDIX Strategy Generator[/bold blue]")
|
||||||
console.print(f"[bold blue]{'='*60}[/bold blue]")
|
console.print(f"[bold blue]{'='*60}[/bold blue]")
|
||||||
console.print(f" Strategies: [cyan]{count}[/cyan]")
|
console.print(f" Strategies: [cyan]{count}[/cyan]")
|
||||||
console.print(f" Workers: [cyan]{workers}[/cyan]")
|
console.print(f" Workers: [cyan]{workers}[/cyan]")
|
||||||
@@ -673,12 +683,12 @@ def generate_strategies_cli(
|
|||||||
_slog = _dlog.setup("strategies", **_strat_ctx)
|
_slog = _dlog.setup("strategies", **_strat_ctx)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from rdagent.components.coder.strategy_orchestrator import StrategyOrchestrator
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from rdagent.scenarios.qlib.local.strategy_orchestrator import StrategyOrchestrator
|
||||||
|
|
||||||
all_results = []
|
all_results = []
|
||||||
best_strategy = None
|
best_strategy = None
|
||||||
best_sharpe = float('-inf')
|
best_sharpe = float("-inf")
|
||||||
|
|
||||||
# CONTINUOUS OPTIMIZATION LOOP
|
# CONTINUOUS OPTIMIZATION LOOP
|
||||||
for iteration in range(1, max_iterations + 1):
|
for iteration in range(1, max_iterations + 1):
|
||||||
@@ -691,6 +701,9 @@ def generate_strategies_cli(
|
|||||||
orchestrator = StrategyOrchestrator(
|
orchestrator = StrategyOrchestrator(
|
||||||
top_factors=top_factors,
|
top_factors=top_factors,
|
||||||
trading_style=style,
|
trading_style=style,
|
||||||
|
min_sharpe=min_sharpe,
|
||||||
|
max_drawdown=max_drawdown,
|
||||||
|
min_win_rate=min_win_rate,
|
||||||
use_optuna=optuna,
|
use_optuna=optuna,
|
||||||
optuna_trials=optuna_trials,
|
optuna_trials=optuna_trials,
|
||||||
continuous_optimization=continuous,
|
continuous_optimization=continuous,
|
||||||
@@ -727,7 +740,7 @@ def generate_strategies_cli(
|
|||||||
|
|
||||||
# Track best strategy
|
# Track best strategy
|
||||||
for r in results:
|
for r in results:
|
||||||
sharpe = r.get("sharpe_ratio", float('-inf'))
|
sharpe = r.get("sharpe_ratio", float("-inf"))
|
||||||
if sharpe > best_sharpe:
|
if sharpe > best_sharpe:
|
||||||
best_sharpe = sharpe
|
best_sharpe = sharpe
|
||||||
best_strategy = r
|
best_strategy = r
|
||||||
@@ -747,7 +760,7 @@ def generate_strategies_cli(
|
|||||||
rejected = [r for r in results if r.get("status") == "rejected"]
|
rejected = [r for r in results if r.get("status") == "rejected"]
|
||||||
|
|
||||||
console.print(f"\n[bold green]{'='*60}[/bold green]")
|
console.print(f"\n[bold green]{'='*60}[/bold green]")
|
||||||
console.print(f"[bold green] Strategy Generation Summary[/bold green]")
|
console.print("[bold green] Strategy Generation Summary[/bold green]")
|
||||||
console.print(f"[bold green]{'='*60}[/bold green]")
|
console.print(f"[bold green]{'='*60}[/bold green]")
|
||||||
|
|
||||||
table = Table(show_header=True, header_style="bold magenta", show_lines=True)
|
table = Table(show_header=True, header_style="bold magenta", show_lines=True)
|
||||||
@@ -776,7 +789,7 @@ def generate_strategies_cli(
|
|||||||
# Show best strategy details
|
# Show best strategy details
|
||||||
if best_strategy:
|
if best_strategy:
|
||||||
console.print(f"\n[bold gold1]{'='*60}[/bold gold1]")
|
console.print(f"\n[bold gold1]{'='*60}[/bold gold1]")
|
||||||
console.print(f"[bold gold1] BEST STRATEGY[/bold gold1]")
|
console.print("[bold gold1] BEST STRATEGY[/bold gold1]")
|
||||||
console.print(f"[bold gold1]{'='*60}[/bold gold1]")
|
console.print(f"[bold gold1]{'='*60}[/bold gold1]")
|
||||||
console.print(f" Name: [cyan]{best_strategy.get('strategy_name', 'Unknown')}[/cyan]")
|
console.print(f" Name: [cyan]{best_strategy.get('strategy_name', 'Unknown')}[/cyan]")
|
||||||
console.print(f" Sharpe: [green]{best_strategy.get('sharpe_ratio', 0):.4f}[/green]")
|
console.print(f" Sharpe: [green]{best_strategy.get('sharpe_ratio', 0):.4f}[/green]")
|
||||||
@@ -784,13 +797,13 @@ def generate_strategies_cli(
|
|||||||
console.print(f" Max DD: [yellow]{best_strategy.get('max_drawdown', 0):.2%}[/yellow]")
|
console.print(f" Max DD: [yellow]{best_strategy.get('max_drawdown', 0):.2%}[/yellow]")
|
||||||
console.print(f" Win Rate: [cyan]{best_strategy.get('win_rate', 0):.2%}[/cyan]")
|
console.print(f" Win Rate: [cyan]{best_strategy.get('win_rate', 0):.2%}[/cyan]")
|
||||||
if best_strategy.get("best_params"):
|
if best_strategy.get("best_params"):
|
||||||
console.print(f"\n [bold]Optimized Parameters:[/bold]")
|
console.print("\n [bold]Optimized Parameters:[/bold]")
|
||||||
for param, val in best_strategy["best_params"].items():
|
for param, val in best_strategy["best_params"].items():
|
||||||
console.print(f" {param}: [cyan]{val}[/cyan]")
|
console.print(f" {param}: [cyan]{val}[/cyan]")
|
||||||
console.print(f"[bold gold1]{'='*60}[/bold gold1]")
|
console.print(f"[bold gold1]{'='*60}[/bold gold1]")
|
||||||
|
|
||||||
if accepted:
|
if accepted:
|
||||||
console.print(f"\n[bold]Accepted Strategies:[/bold]")
|
console.print("\n[bold]Accepted Strategies:[/bold]")
|
||||||
acc_table = Table(show_header=True, header_style="bold cyan")
|
acc_table = Table(show_header=True, header_style="bold cyan")
|
||||||
acc_table.add_column("#", width=4)
|
acc_table.add_column("#", width=4)
|
||||||
acc_table.add_column("Strategy", width=30)
|
acc_table.add_column("Strategy", width=30)
|
||||||
@@ -813,13 +826,13 @@ def generate_strategies_cli(
|
|||||||
)
|
)
|
||||||
console.print(acc_table)
|
console.print(acc_table)
|
||||||
|
|
||||||
console.print(f"\n[bold green]Strategies saved to:[/bold green] [cyan]results/strategies_new/[/cyan]")
|
console.print("\n[bold green]Strategies saved to:[/bold green] [cyan]results/strategies_new/[/cyan]")
|
||||||
console.print(f"[bold blue]{'='*60}[/bold blue]\n")
|
console.print(f"[bold blue]{'='*60}[/bold blue]\n")
|
||||||
_slog.success(f"Generated {len(all_results)} strategies ({len([r for r in all_results if r.get('status')=='accepted'])} accepted)")
|
_slog.success(f"Generated {len(all_results)} strategies ({len([r for r in all_results if r.get('status')=='accepted'])} accepted)")
|
||||||
|
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
_slog.error(f"Strategy components not available: {e}")
|
_slog.error(f"Strategy components not available: {e}")
|
||||||
console.print(f"[bold red]Error: Strategy components not available.[/bold red]")
|
console.print("[bold red]Error: Strategy components not available.[/bold red]")
|
||||||
console.print(f"Details: {e}")
|
console.print(f"Details: {e}")
|
||||||
raise typer.Exit(code=1)
|
raise typer.Exit(code=1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -855,17 +868,18 @@ def optimize_portfolio_cli(
|
|||||||
raise typer.Exit(code=1)
|
raise typer.Exit(code=1)
|
||||||
|
|
||||||
console.print(f"\n[bold blue]{'='*60}[/bold blue]")
|
console.print(f"\n[bold blue]{'='*60}[/bold blue]")
|
||||||
console.print(f"[bold blue] PREDIX Portfolio Optimizer[/bold blue]")
|
console.print("[bold blue] PREDIX Portfolio Optimizer[/bold blue]")
|
||||||
console.print(f"[bold blue]{'='*60}[/bold blue]")
|
console.print(f"[bold blue]{'='*60}[/bold blue]")
|
||||||
console.print(f" Top N: [cyan]{top_n}[/cyan]")
|
console.print(f" Top N: [cyan]{top_n}[/cyan]")
|
||||||
console.print(f" Method: [cyan]{method}[/cyan]")
|
console.print(f" Method: [cyan]{method}[/cyan]")
|
||||||
console.print(f"[bold blue]{'='*60}[/bold blue]\n")
|
console.print(f"[bold blue]{'='*60}[/bold blue]\n")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from rdagent.components.backtesting.risk_management import PortfolioOptimizer
|
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from rdagent.components.backtesting.risk_management import PortfolioOptimizer
|
||||||
|
|
||||||
project_root = Path(__file__).parent.parent.parent
|
project_root = Path(__file__).parent.parent.parent
|
||||||
strategies_dir = project_root / "results" / "strategies_new"
|
strategies_dir = project_root / "results" / "strategies_new"
|
||||||
|
|
||||||
@@ -882,6 +896,7 @@ def optimize_portfolio_cli(
|
|||||||
if data.get("status") == "accepted":
|
if data.get("status") == "accepted":
|
||||||
strategies.append(data)
|
strategies.append(data)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
logger.warning("Failed to load strategy file %s", f, exc_info=True)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not strategies:
|
if not strategies:
|
||||||
@@ -1001,14 +1016,15 @@ def strategies_report_cli(
|
|||||||
rdagent strategies_report -s path/to/strategy.json # Single strategy
|
rdagent strategies_report -s path/to/strategy.json # Single strategy
|
||||||
rdagent strategies_report -o custom/reports/ # Custom output dir
|
rdagent strategies_report -o custom/reports/ # Custom output dir
|
||||||
"""
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.progress import Progress, SpinnerColumn, TextColumn
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
console.print(f"\n[bold blue]{'='*60}[/bold blue]")
|
console.print(f"\n[bold blue]{'='*60}[/bold blue]")
|
||||||
console.print(f"[bold blue] PREDIX Strategy Report Generator[/bold blue]")
|
console.print("[bold blue] PREDIX Strategy Report Generator[/bold blue]")
|
||||||
console.print(f"[bold blue]{'='*60}[/bold blue]\n")
|
console.print(f"[bold blue]{'='*60}[/bold blue]\n")
|
||||||
|
|
||||||
project_root = Path(__file__).parent.parent.parent
|
project_root = Path(__file__).parent.parent.parent
|
||||||
@@ -1060,20 +1076,20 @@ def strategies_report_cli(
|
|||||||
progress.update(task, completed=1)
|
progress.update(task, completed=1)
|
||||||
|
|
||||||
console.print(f"\n[bold green]{'='*60}[/bold green]")
|
console.print(f"\n[bold green]{'='*60}[/bold green]")
|
||||||
console.print(f"[bold green] Report Generation Complete[/bold green]")
|
console.print("[bold green] Report Generation Complete[/bold green]")
|
||||||
console.print(f"[bold green]{'='*60}[/bold green]")
|
console.print(f"[bold green]{'='*60}[/bold green]")
|
||||||
console.print(f" Reports generated: [cyan]{reports_generated}/{len(strategy_files)}[/cyan]")
|
console.print(f" Reports generated: [cyan]{reports_generated}/{len(strategy_files)}[/cyan]")
|
||||||
console.print(f" Output directory: [cyan]{output_dir_path}[/cyan]")
|
console.print(f" Output directory: [cyan]{output_dir_path}[/cyan]")
|
||||||
console.print(f"[bold green]{'='*60}[/bold green]\n")
|
console.print(f"[bold green]{'='*60}[/bold green]\n")
|
||||||
|
|
||||||
|
|
||||||
def _generate_single_strategy_report(strategy_file: Path, output_dir: Path) -> Dict:
|
def _generate_single_strategy_report(strategy_file: Path, output_dir: Path) -> dict:
|
||||||
"""Generate a report for a single strategy."""
|
"""Generate a report for a single strategy."""
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import matplotlib
|
import matplotlib
|
||||||
matplotlib.use("Agg") # Non-interactive backend
|
matplotlib.use("Agg") # Non-interactive backend
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import seaborn as sns
|
|
||||||
|
|
||||||
with open(strategy_file, encoding="utf-8") as f:
|
with open(strategy_file, encoding="utf-8") as f:
|
||||||
strategy = json.load(f)
|
strategy = json.load(f)
|
||||||
@@ -1148,7 +1164,7 @@ if __name__ == "__main__":
|
|||||||
@app.command(name="start_llama")
|
@app.command(name="start_llama")
|
||||||
def start_llama_cli(
|
def start_llama_cli(
|
||||||
model: str = typer.Option(
|
model: str = typer.Option(
|
||||||
None, "--model", "-m", help="Path to model file"
|
None, "--model", "-m", help="Path to model file",
|
||||||
),
|
),
|
||||||
port: int = typer.Option(8081, "--port", "-p", help="Server port"),
|
port: int = typer.Option(8081, "--port", "-p", help="Server port"),
|
||||||
gpu_layers: int = typer.Option(30, "--gpu-layers", "-g", help="GPU layers"),
|
gpu_layers: int = typer.Option(30, "--gpu-layers", "-g", help="GPU layers"),
|
||||||
@@ -1170,8 +1186,6 @@ def start_llama_cli(
|
|||||||
rdagent start_llama --gpu-layers 40 --ctx-size 4096
|
rdagent start_llama --gpu-layers 40 --ctx-size 4096
|
||||||
rdagent start_llama --reasoning
|
rdagent start_llama --reasoning
|
||||||
"""
|
"""
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
model_path = model or os.getenv(
|
model_path = model or os.getenv(
|
||||||
@@ -1208,7 +1222,7 @@ def start_llama_cli(
|
|||||||
if not reasoning:
|
if not reasoning:
|
||||||
cmd.extend(["--reasoning", "off"])
|
cmd.extend(["--reasoning", "off"])
|
||||||
|
|
||||||
print(f"🚀 Starting llama.cpp server...")
|
print("🚀 Starting llama.cpp server...")
|
||||||
print(f" Model: {Path(model_path).name}")
|
print(f" Model: {Path(model_path).name}")
|
||||||
print(f" Port: {port}")
|
print(f" Port: {port}")
|
||||||
print(f" GPU Layers: {gpu_layers}")
|
print(f" GPU Layers: {gpu_layers}")
|
||||||
@@ -1241,17 +1255,16 @@ def start_loop_cli(
|
|||||||
rdagent start_loop
|
rdagent start_loop
|
||||||
rdagent start_loop --target 5 --max-wait 3600
|
rdagent start_loop --target 5 --max-wait 3600
|
||||||
"""
|
"""
|
||||||
import subprocess
|
|
||||||
import signal
|
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
import signal
|
||||||
|
import subprocess
|
||||||
import time
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
script_dir = str(Path(__file__).parent.parent.parent.parent)
|
script_dir = str(Path(__file__).parent.parent.parent)
|
||||||
generator = f"python {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"
|
logfile = f"{script_dir}/results/logs/generator_loop.log"
|
||||||
pidfile = "/tmp/predix_loop.pid"
|
pidfile = "/tmp/nexquant_loop.pid" # nosec B108 — administrative PID file, single-process daemon
|
||||||
|
|
||||||
os.makedirs(f"{script_dir}/results/logs", exist_ok=True)
|
os.makedirs(f"{script_dir}/results/logs", exist_ok=True)
|
||||||
|
|
||||||
@@ -1262,12 +1275,19 @@ def start_loop_cli(
|
|||||||
with open(logfile, "a") as f:
|
with open(logfile, "a") as f:
|
||||||
f.write(line + "\n")
|
f.write(line + "\n")
|
||||||
|
|
||||||
|
child_proc = None # track current child PID for targeted cleanup
|
||||||
|
|
||||||
def cleanup(signum=None, frame=None):
|
def cleanup(signum=None, frame=None):
|
||||||
log("Received termination signal. Cleaning up...")
|
log("Received termination signal. Cleaning up...")
|
||||||
try:
|
if child_proc is not None:
|
||||||
subprocess.run(["pkill", "-f", "predix_smart_strategy_gen.py"], capture_output=True)
|
try:
|
||||||
except Exception:
|
child_proc.terminate()
|
||||||
pass
|
child_proc.wait(timeout=10)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
child_proc.kill()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
try:
|
try:
|
||||||
os.remove(pidfile)
|
os.remove(pidfile)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
@@ -1313,26 +1333,32 @@ def start_loop_cli(
|
|||||||
strat_count = len(list(strat_dir.glob("*.json"))) if strat_dir.exists() else 0
|
strat_count = len(list(strat_dir.glob("*.json"))) if strat_dir.exists() else 0
|
||||||
log(f"📁 Existing strategies: {strat_count}")
|
log(f"📁 Existing strategies: {strat_count}")
|
||||||
|
|
||||||
# Kill stale processes
|
# Kill stale child from previous iteration
|
||||||
try:
|
if child_proc is not None:
|
||||||
subprocess.run(["pkill", "-9", "-f", "predix_smart_strategy_gen.py"], capture_output=True)
|
try:
|
||||||
except Exception:
|
child_proc.terminate()
|
||||||
pass
|
child_proc.wait(timeout=10)
|
||||||
time.sleep(2)
|
except subprocess.TimeoutExpired:
|
||||||
|
child_proc.kill()
|
||||||
|
child_proc.wait()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
child_proc = None
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
# Start generator
|
# Start generator
|
||||||
log("🤖 Starting generator...")
|
log("🤖 Starting generator...")
|
||||||
proc = subprocess.Popen(
|
child_proc = subprocess.Popen(
|
||||||
generator.split(),
|
generator,
|
||||||
cwd=script_dir,
|
cwd=script_dir,
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
log(f" PID: {proc.pid}")
|
log(f" PID: {child_proc.pid}")
|
||||||
|
|
||||||
# Monitor progress
|
# Monitor progress
|
||||||
elapsed = 0
|
elapsed = 0
|
||||||
while proc.poll() is None:
|
while child_proc.poll() is None:
|
||||||
time.sleep(30)
|
time.sleep(30)
|
||||||
elapsed += 30
|
elapsed += 30
|
||||||
|
|
||||||
@@ -1341,11 +1367,12 @@ def start_loop_cli(
|
|||||||
|
|
||||||
if elapsed >= max_wait:
|
if elapsed >= max_wait:
|
||||||
log(f" ⏰ Timeout after {elapsed}s. Killing...")
|
log(f" ⏰ Timeout after {elapsed}s. Killing...")
|
||||||
proc.kill()
|
child_proc.kill()
|
||||||
break
|
break
|
||||||
|
|
||||||
# Check results
|
# Check results
|
||||||
exit_code = proc.wait()
|
exit_code = child_proc.wait()
|
||||||
|
child_proc = None
|
||||||
if exit_code == 0:
|
if exit_code == 0:
|
||||||
log("✅ Generator completed successfully")
|
log("✅ Generator completed successfully")
|
||||||
elif exit_code == -9:
|
elif exit_code == -9:
|
||||||
@@ -1386,24 +1413,24 @@ def parallel_cli(
|
|||||||
rdagent parallel -n 10 -k 2
|
rdagent parallel -n 10 -k 2
|
||||||
"""
|
"""
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from rdagent.log import daily_log as _dlog
|
from rdagent.log import daily_log as _dlog
|
||||||
|
|
||||||
project_root = Path(__file__).parent.parent.parent.parent
|
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():
|
if not script.exists():
|
||||||
typer.echo(f"❌ Script not found: {script}")
|
typer.echo(f"❌ Script not found: {script}")
|
||||||
raise typer.Exit(code=1)
|
raise typer.Exit(code=1)
|
||||||
|
|
||||||
cmd = [sys.executable, str(script), "--runs", str(runs), "--api-keys", str(api_keys), "-m", "local"]
|
cmd = [sys.executable, str(script), "--runs", str(runs), "--api-keys", str(api_keys)]
|
||||||
|
|
||||||
_plog = _dlog.setup("parallel", runs=runs, api_keys=api_keys, model="local")
|
_plog = _dlog.setup("parallel", runs=runs, api_keys=api_keys, model="local")
|
||||||
typer.echo(f"🚀 Starting {runs} parallel runs...")
|
typer.echo(f"🚀 Starting {runs} parallel runs...")
|
||||||
typer.echo(f" Script: {script}")
|
typer.echo(f" Script: {script}")
|
||||||
typer.echo(f" API Keys: {api_keys}")
|
typer.echo(f" API Keys: {api_keys}")
|
||||||
typer.echo(f" Model: local (llama.cpp)")
|
typer.echo(" Model: local (llama.cpp)")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(cmd, cwd=str(project_root))
|
result = subprocess.run(cmd, cwd=str(project_root))
|
||||||
@@ -1437,12 +1464,12 @@ def eval_all_cli(
|
|||||||
rdagent eval_all -n 500 -p 8
|
rdagent eval_all -n 500 -p 8
|
||||||
"""
|
"""
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from rdagent.log import daily_log as _dlog
|
from rdagent.log import daily_log as _dlog
|
||||||
|
|
||||||
project_root = Path(__file__).parent.parent.parent.parent
|
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():
|
if not script.exists():
|
||||||
typer.echo(f"❌ Script not found: {script}")
|
typer.echo(f"❌ Script not found: {script}")
|
||||||
@@ -1492,11 +1519,10 @@ def batch_backtest_cli(
|
|||||||
rdagent batch_backtest --all
|
rdagent batch_backtest --all
|
||||||
"""
|
"""
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
project_root = Path(__file__).parent.parent.parent.parent
|
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():
|
if not script.exists():
|
||||||
typer.echo(f"❌ Script not found: {script}")
|
typer.echo(f"❌ Script not found: {script}")
|
||||||
@@ -1545,11 +1571,10 @@ def simple_eval_cli(
|
|||||||
rdagent simple_eval --all
|
rdagent simple_eval --all
|
||||||
"""
|
"""
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
project_root = Path(__file__).parent.parent.parent.parent
|
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():
|
if not script.exists():
|
||||||
typer.echo(f"❌ Script not found: {script}")
|
typer.echo(f"❌ Script not found: {script}")
|
||||||
@@ -1578,7 +1603,7 @@ def simple_eval_cli(
|
|||||||
@app.command(name="rebacktest")
|
@app.command(name="rebacktest")
|
||||||
def rebacktest_cli(
|
def rebacktest_cli(
|
||||||
strategies_dir: str = typer.Option(
|
strategies_dir: str = typer.Option(
|
||||||
None, "--strategies-dir", "-d", help="Directory containing strategy JSON files"
|
None, "--strategies-dir", "-d", help="Directory containing strategy JSON files",
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1592,11 +1617,10 @@ def rebacktest_cli(
|
|||||||
rdagent rebacktest -d results/strategies_new/
|
rdagent rebacktest -d results/strategies_new/
|
||||||
"""
|
"""
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
project_root = Path(__file__).parent.parent.parent.parent
|
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():
|
if not script.exists():
|
||||||
typer.echo(f"❌ Script not found: {script}")
|
typer.echo(f"❌ Script not found: {script}")
|
||||||
@@ -1620,10 +1644,10 @@ def rebacktest_cli(
|
|||||||
@app.command(name="report")
|
@app.command(name="report")
|
||||||
def report_cli(
|
def report_cli(
|
||||||
strategy_path: str = typer.Option(
|
strategy_path: str = typer.Option(
|
||||||
None, "--strategy", "-s", help="Path to single strategy JSON (default: all strategies)"
|
None, "--strategy", "-s", help="Path to single strategy JSON (default: all strategies)",
|
||||||
),
|
),
|
||||||
output: str = typer.Option(
|
output: str = typer.Option(
|
||||||
None, "--output", "-o", help="Output directory (default: results/strategy_reports/)"
|
None, "--output", "-o", help="Output directory (default: results/strategy_reports/)",
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1646,11 +1670,10 @@ def report_cli(
|
|||||||
rdagent report -o custom/reports/
|
rdagent report -o custom/reports/
|
||||||
"""
|
"""
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
project_root = Path(__file__).parent.parent.parent.parent
|
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():
|
if not script.exists():
|
||||||
typer.echo(f"❌ Script not found: {script}")
|
typer.echo(f"❌ Script not found: {script}")
|
||||||
@@ -1674,10 +1697,10 @@ def report_cli(
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.command(name="predix")
|
@app.command(name="nexquant")
|
||||||
def predix_welcome():
|
def nexquant_welcome():
|
||||||
"""
|
"""
|
||||||
Show Predix welcome screen with system overview.
|
Show NexQuant welcome screen with system overview.
|
||||||
|
|
||||||
This command displays a beautiful dashboard showing:
|
This command displays a beautiful dashboard showing:
|
||||||
- System status (factors, strategies, security)
|
- System status (factors, strategies, security)
|
||||||
@@ -1687,7 +1710,7 @@ def predix_welcome():
|
|||||||
Perfect for GitHub README screenshots!
|
Perfect for GitHub README screenshots!
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
rdagent predix
|
rdagent nexquant
|
||||||
"""
|
"""
|
||||||
from rdagent.app.cli_welcome import show_welcome
|
from rdagent.app.cli_welcome import show_welcome
|
||||||
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
|
import os
|
||||||
@@ -16,7 +16,7 @@ from datetime import datetime
|
|||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
def show_welcome():
|
def show_welcome():
|
||||||
"""Show beautiful Predix welcome screen."""
|
"""Show beautiful NexQuant welcome screen."""
|
||||||
|
|
||||||
# Header
|
# Header
|
||||||
console.print()
|
console.print()
|
||||||
@@ -89,7 +89,7 @@ def show_welcome():
|
|||||||
console.print()
|
console.print()
|
||||||
|
|
||||||
# Footer
|
# 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(Align.center(footer))
|
||||||
console.print()
|
console.print()
|
||||||
|
|
||||||
@@ -98,5 +98,5 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Entry point for 'predix' CLI command."""
|
"""Entry point for 'nexquant' CLI command."""
|
||||||
show_welcome()
|
show_welcome()
|
||||||
|
|||||||
@@ -201,6 +201,5 @@ class DataScienceBasePropSetting(KaggleBasePropSetting):
|
|||||||
DS_RD_SETTING = DataScienceBasePropSetting()
|
DS_RD_SETTING = DataScienceBasePropSetting()
|
||||||
|
|
||||||
# enable_cross_trace_diversity and llm_select_hypothesis should not be true at the same time
|
# enable_cross_trace_diversity and llm_select_hypothesis should not be true at the same time
|
||||||
assert not (
|
if DS_RD_SETTING.enable_cross_trace_diversity and DS_RD_SETTING.llm_select_hypothesis:
|
||||||
DS_RD_SETTING.enable_cross_trace_diversity and DS_RD_SETTING.llm_select_hypothesis
|
raise ValueError("enable_cross_trace_diversity and llm_select_hypothesis cannot be true at the same time")
|
||||||
), "enable_cross_trace_diversity and llm_select_hypothesis cannot be true at the same time"
|
|
||||||
|
|||||||
@@ -58,18 +58,18 @@ def main(
|
|||||||
|
|
||||||
if user_target_scenario:
|
if user_target_scenario:
|
||||||
FT_RD_SETTING.user_target_scenario = user_target_scenario
|
FT_RD_SETTING.user_target_scenario = user_target_scenario
|
||||||
assert (
|
if FT_RD_SETTING.user_target_scenario is not None:
|
||||||
FT_RD_SETTING.user_target_scenario is None
|
raise ValueError("user_target_scenario is not yet supported, please specify via benchmark and benchmark_description")
|
||||||
), "user_target_scenario is not yet supported, please specify via benchmark and benchmark_description"
|
|
||||||
if upper_data_size_limit:
|
if upper_data_size_limit:
|
||||||
FT_RD_SETTING.upper_data_size_limit = upper_data_size_limit
|
FT_RD_SETTING.upper_data_size_limit = upper_data_size_limit
|
||||||
logger.info(f"Set upper_data_size_limit to {FT_RD_SETTING.upper_data_size_limit}")
|
logger.info(f"Set upper_data_size_limit to {FT_RD_SETTING.upper_data_size_limit}")
|
||||||
if benchmark and benchmark_description:
|
if benchmark and benchmark_description:
|
||||||
FT_RD_SETTING.target_benchmark = benchmark
|
FT_RD_SETTING.target_benchmark = benchmark
|
||||||
FT_RD_SETTING.benchmark_description = benchmark_description
|
FT_RD_SETTING.benchmark_description = benchmark_description
|
||||||
assert FT_RD_SETTING.user_target_scenario or (
|
if not (
|
||||||
FT_RD_SETTING.target_benchmark and FT_RD_SETTING.benchmark_description
|
FT_RD_SETTING.user_target_scenario or (FT_RD_SETTING.target_benchmark and FT_RD_SETTING.benchmark_description)
|
||||||
), "Either user_target_scenario or target_benchmark must be specified for LLM fine-tuning."
|
):
|
||||||
|
raise ValueError("Either user_target_scenario or target_benchmark must be specified for LLM fine-tuning.")
|
||||||
|
|
||||||
# Update configuration with provided parameters
|
# Update configuration with provided parameters
|
||||||
if dataset:
|
if dataset:
|
||||||
@@ -82,9 +82,8 @@ def main(
|
|||||||
model_target = FT_RD_SETTING.base_model if FT_RD_SETTING.base_model else "auto selected model"
|
model_target = FT_RD_SETTING.base_model if FT_RD_SETTING.base_model else "auto selected model"
|
||||||
|
|
||||||
# Temporary assertion until auto-selection is implemented
|
# Temporary assertion until auto-selection is implemented
|
||||||
assert (
|
if FT_RD_SETTING.base_model is None:
|
||||||
FT_RD_SETTING.base_model is not None
|
raise ValueError("Base model auto selection not yet supported, please specify via --base-model")
|
||||||
), "Base model auto selection not yet supported, please specify via --base-model"
|
|
||||||
|
|
||||||
logger.info(f"Starting LLM fine-tuning on dataset='{data_set_target}' with model='{model_target}'")
|
logger.info(f"Starting LLM fine-tuning on dataset='{data_set_target}' with model='{model_target}'")
|
||||||
|
|
||||||
|
|||||||
@@ -24,46 +24,12 @@ from rdagent.app.finetune.llm.ui.ft_summary import render_job_summary
|
|||||||
|
|
||||||
DEFAULT_LOG_BASE = "log/"
|
DEFAULT_LOG_BASE = "log/"
|
||||||
|
|
||||||
|
from rdagent.core.utils import safe_resolve_path
|
||||||
|
|
||||||
|
|
||||||
def validate_path_within_cwd(user_path: Path) -> Path:
|
def validate_path_within_cwd(user_path: Path) -> Path:
|
||||||
"""
|
|
||||||
Validate that a user-provided path is within the current working directory.
|
|
||||||
|
|
||||||
Security: This function prevents path traversal attacks by:
|
|
||||||
1. Resolving the path to its absolute canonical form
|
|
||||||
2. Verifying it's within the CWD boundary using a normalized common prefix
|
|
||||||
3. Rejecting paths outside the boundary with ValueError
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
user_path : Path
|
|
||||||
User-provided path to validate
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
Path
|
|
||||||
Resolved absolute path if valid
|
|
||||||
|
|
||||||
Raises
|
|
||||||
------
|
|
||||||
ValueError
|
|
||||||
If path is outside the current working directory
|
|
||||||
"""
|
|
||||||
safe_root = Path.cwd().resolve()
|
safe_root = Path.cwd().resolve()
|
||||||
# Expand any user home reference and resolve without requiring the path to exist.
|
return safe_resolve_path(user_path, safe_root)
|
||||||
resolved_path = user_path.expanduser().resolve(strict=False)
|
|
||||||
|
|
||||||
# Ensure the resolved path is absolute and remains within the safe root.
|
|
||||||
safe_root_str = str(safe_root)
|
|
||||||
resolved_str = str(resolved_path)
|
|
||||||
common = os.path.commonpath([safe_root_str, resolved_str])
|
|
||||||
if common != safe_root_str:
|
|
||||||
raise ValueError("Path is outside the allowed project directory")
|
|
||||||
|
|
||||||
# This will raise ValueError if resolved_path is not within safe_root
|
|
||||||
resolved_path.relative_to(safe_root)
|
|
||||||
|
|
||||||
return resolved_path
|
|
||||||
|
|
||||||
|
|
||||||
def get_job_options(base_path: Path, safe_root: Path | None = None) -> list[str]:
|
def get_job_options(base_path: Path, safe_root: Path | None = None) -> list[str]:
|
||||||
@@ -141,19 +107,14 @@ def main():
|
|||||||
st.header("Job")
|
st.header("Job")
|
||||||
base_folder = st.text_input("Base Folder", value=default_log, key="base_folder_input")
|
base_folder = st.text_input("Base Folder", value=default_log, key="base_folder_input")
|
||||||
|
|
||||||
# Normalize and validate the base folder against the configured log root
|
safe_root = Path(default_log).expanduser().resolve()
|
||||||
root_real = os.path.realpath(str(Path(default_log).expanduser()))
|
try:
|
||||||
folder_real = os.path.realpath(str(Path(base_folder).expanduser()))
|
base_path = safe_resolve_path(Path(base_folder), safe_root)
|
||||||
if folder_real == root_real or folder_real.startswith(root_real + os.sep):
|
except ValueError:
|
||||||
base_path = Path(folder_real)
|
|
||||||
safe_root = Path(root_real)
|
|
||||||
else:
|
|
||||||
st.error("Invalid base folder: must be within the configured log directory.")
|
st.error("Invalid base folder: must be within the configured log directory.")
|
||||||
safe_root = Path(root_real)
|
|
||||||
base_path = safe_root
|
base_path = safe_root
|
||||||
|
|
||||||
# base_path is validated against safe_root – nosec B614
|
job_options = get_job_options(base_path, safe_root)
|
||||||
job_options = get_job_options(base_path, safe_root) # nosec B614 – validated above
|
|
||||||
if job_options:
|
if job_options:
|
||||||
selected_job = st.selectbox("Select Job", job_options, key="job_select")
|
selected_job = st.selectbox("Select Job", job_options, key="job_select")
|
||||||
if selected_job.startswith("."):
|
if selected_job.startswith("."):
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from typing import Any
|
|||||||
import streamlit as st
|
import streamlit as st
|
||||||
|
|
||||||
from rdagent.app.finetune.llm.ui.config import EVALUATOR_CONFIG, EventType
|
from rdagent.app.finetune.llm.ui.config import EVALUATOR_CONFIG, EventType
|
||||||
|
from rdagent.core.utils import safe_resolve_path
|
||||||
from rdagent.log.storage import FileStorage
|
from rdagent.log.storage import FileStorage
|
||||||
|
|
||||||
|
|
||||||
@@ -89,11 +90,10 @@ def extract_stage(tag: str) -> str:
|
|||||||
def get_valid_sessions(log_folder: Path, safe_root: Path | None = None) -> list[str]:
|
def get_valid_sessions(log_folder: Path, safe_root: Path | None = None) -> list[str]:
|
||||||
"""Get list of valid session directories, optionally validating against a safe root."""
|
"""Get list of valid session directories, optionally validating against a safe root."""
|
||||||
if safe_root is not None:
|
if safe_root is not None:
|
||||||
root_real = os.path.realpath(str(safe_root.expanduser()))
|
try:
|
||||||
folder_real = os.path.realpath(str(log_folder.expanduser()))
|
log_folder = safe_resolve_path(log_folder, safe_root)
|
||||||
if not (folder_real == root_real or folder_real.startswith(root_real + os.sep)):
|
except ValueError:
|
||||||
return []
|
return []
|
||||||
log_folder = Path(folder_real)
|
|
||||||
|
|
||||||
if not log_folder.exists():
|
if not log_folder.exists():
|
||||||
return []
|
return []
|
||||||
@@ -373,13 +373,11 @@ def parse_event(tag: str, content: Any, timestamp: datetime) -> Event | None:
|
|||||||
@st.cache_data(ttl=300, hash_funcs={Path: str})
|
@st.cache_data(ttl=300, hash_funcs={Path: str})
|
||||||
def load_ft_session(log_path: Path, safe_root: Path | None = None) -> Session:
|
def load_ft_session(log_path: Path, safe_root: Path | None = None) -> Session:
|
||||||
"""Load events into hierarchical session structure, optionally validating against safe root."""
|
"""Load events into hierarchical session structure, optionally validating against safe root."""
|
||||||
# Validate path is within safe_root if provided
|
|
||||||
if safe_root is not None:
|
if safe_root is not None:
|
||||||
root_real = os.path.realpath(str(safe_root.expanduser()))
|
try:
|
||||||
path_real = os.path.realpath(str(log_path.expanduser()))
|
log_path = safe_resolve_path(log_path, safe_root)
|
||||||
if not (path_real == root_real or path_real.startswith(root_real + os.sep)):
|
except ValueError:
|
||||||
return Session()
|
return Session()
|
||||||
log_path = Path(path_real)
|
|
||||||
|
|
||||||
session = Session()
|
session = Session()
|
||||||
storage = FileStorage(log_path)
|
storage = FileStorage(log_path)
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ Factor workflow with session control
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
|
|
||||||
import fire
|
import fire
|
||||||
|
|
||||||
from rdagent.app.qlib_rd_loop.conf import FACTOR_PROP_SETTING
|
from rdagent.app.qlib_rd_loop.conf import FACTOR_PROP_SETTING
|
||||||
from rdagent.components.workflow.rd_loop import RDLoop
|
from rdagent.components.workflow.rd_loop import RDLoop
|
||||||
from rdagent.core.exception import CoderError, FactorEmptyError
|
from rdagent.core.exception import CoderError, FactorEmptyError
|
||||||
@@ -21,20 +20,20 @@ class FactorRDLoop(RDLoop):
|
|||||||
def running(self, prev_out: dict[str, Any]):
|
def running(self, prev_out: dict[str, Any]):
|
||||||
exp = self.runner.develop(prev_out["coding"])
|
exp = self.runner.develop(prev_out["coding"])
|
||||||
if exp is None:
|
if exp is None:
|
||||||
logger.error(f"Factor extraction failed.")
|
logger.error("Factor extraction failed.")
|
||||||
raise FactorEmptyError("Factor extraction failed.")
|
raise FactorEmptyError("Factor extraction failed.")
|
||||||
logger.log_object(exp, tag="runner result")
|
logger.log_object(exp, tag="runner result")
|
||||||
return exp
|
return exp
|
||||||
|
|
||||||
|
|
||||||
def main(
|
def main(
|
||||||
path: Optional[str] = None,
|
path: str | None = None,
|
||||||
step_n: Optional[int] = None,
|
step_n: int | None = None,
|
||||||
loop_n: Optional[int] = None,
|
loop_n: int | None = None,
|
||||||
all_duration: str | None = None,
|
all_duration: str | None = None,
|
||||||
checkout: bool = True,
|
checkout: bool = True,
|
||||||
checkout_path: Optional[str] = None,
|
checkout_path: str | None = None,
|
||||||
base_features_path: Optional[str] = None,
|
base_features_path: str | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -47,7 +46,7 @@ def main(
|
|||||||
dotenv run -- python rdagent/app/qlib_rd_loop/factor.py $LOG_PATH/__session__/1/0_propose --step_n 1 # `step_n` is a optional paramter
|
dotenv run -- python rdagent/app/qlib_rd_loop/factor.py $LOG_PATH/__session__/1/0_propose --step_n 1 # `step_n` is a optional paramter
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not checkout_path is None:
|
if checkout_path is not None:
|
||||||
checkout = Path(checkout_path)
|
checkout = Path(checkout_path)
|
||||||
|
|
||||||
if path is None:
|
if path is None:
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, Tuple
|
from typing import Any
|
||||||
|
|
||||||
import fire
|
import fire
|
||||||
|
|
||||||
from rdagent.app.qlib_rd_loop.conf import FACTOR_FROM_REPORT_PROP_SETTING
|
from rdagent.app.qlib_rd_loop.conf import FACTOR_FROM_REPORT_PROP_SETTING
|
||||||
from rdagent.app.qlib_rd_loop.factor import FactorRDLoop
|
from rdagent.app.qlib_rd_loop.factor import FactorRDLoop
|
||||||
from rdagent.components.document_reader.document_reader import (
|
from rdagent.components.document_reader.document_reader import (
|
||||||
@@ -12,7 +11,7 @@ from rdagent.components.document_reader.document_reader import (
|
|||||||
load_and_process_pdfs_by_langchain,
|
load_and_process_pdfs_by_langchain,
|
||||||
)
|
)
|
||||||
from rdagent.core.conf import RD_AGENT_SETTINGS
|
from rdagent.core.conf import RD_AGENT_SETTINGS
|
||||||
from rdagent.core.proposal import Hypothesis, HypothesisFeedback
|
from rdagent.core.proposal import Hypothesis
|
||||||
from rdagent.log import rdagent_logger as logger
|
from rdagent.log import rdagent_logger as logger
|
||||||
from rdagent.oai.llm_utils import APIBackend
|
from rdagent.oai.llm_utils import APIBackend
|
||||||
from rdagent.scenarios.qlib.experiment.factor_experiment import QlibFactorExperiment
|
from rdagent.scenarios.qlib.experiment.factor_experiment import QlibFactorExperiment
|
||||||
@@ -36,14 +35,14 @@ def generate_hypothesis(factor_result: dict, report_content: str) -> str:
|
|||||||
"""
|
"""
|
||||||
system_prompt = T(".prompts:hypothesis_generation.system").r()
|
system_prompt = T(".prompts:hypothesis_generation.system").r()
|
||||||
user_prompt = T(".prompts:hypothesis_generation.user").r(
|
user_prompt = T(".prompts:hypothesis_generation.user").r(
|
||||||
factor_descriptions=json.dumps(factor_result), report_content=report_content
|
factor_descriptions=json.dumps(factor_result), report_content=report_content,
|
||||||
)
|
)
|
||||||
|
|
||||||
response = APIBackend().build_messages_and_create_chat_completion(
|
response = APIBackend().build_messages_and_create_chat_completion(
|
||||||
user_prompt=user_prompt,
|
user_prompt=user_prompt,
|
||||||
system_prompt=system_prompt,
|
system_prompt=system_prompt,
|
||||||
json_mode=True,
|
json_mode=True,
|
||||||
json_target_type=Dict[str, str],
|
json_target_type=dict[str, str],
|
||||||
)
|
)
|
||||||
|
|
||||||
response_json = json.loads(response)
|
response_json = json.loads(response)
|
||||||
@@ -99,7 +98,7 @@ class FactorReportLoop(FactorRDLoop, metaclass=LoopMeta):
|
|||||||
super().__init__(PROP_SETTING=FACTOR_FROM_REPORT_PROP_SETTING)
|
super().__init__(PROP_SETTING=FACTOR_FROM_REPORT_PROP_SETTING)
|
||||||
if report_folder is None:
|
if report_folder is None:
|
||||||
self.judge_pdf_data_items = json.load(
|
self.judge_pdf_data_items = json.load(
|
||||||
open(FACTOR_FROM_REPORT_PROP_SETTING.report_result_json_file_path, "r")
|
open(FACTOR_FROM_REPORT_PROP_SETTING.report_result_json_file_path),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.judge_pdf_data_items = [i for i in Path(report_folder).rglob("*.pdf")]
|
self.judge_pdf_data_items = [i for i in Path(report_folder).rglob("*.pdf")]
|
||||||
@@ -118,7 +117,7 @@ class FactorReportLoop(FactorRDLoop, metaclass=LoopMeta):
|
|||||||
if exp is None:
|
if exp is None:
|
||||||
self.shift_report += 1
|
self.shift_report += 1
|
||||||
self.loop_n -= 1
|
self.loop_n -= 1
|
||||||
if self.loop_n < 0: # NOTE: on every step, we self.loop_n -= 1 at first.
|
if self.loop_n < 0: # loop_n is decremented above when reports are empty; prevents infinite skipping
|
||||||
raise self.LoopTerminationError("Reach stop criterion and stop loop")
|
raise self.LoopTerminationError("Reach stop criterion and stop loop")
|
||||||
continue
|
continue
|
||||||
exp.based_experiments = [QlibFactorExperiment(sub_tasks=[], hypothesis=exp.hypothesis)] + [
|
exp.based_experiments = [QlibFactorExperiment(sub_tasks=[], hypothesis=exp.hypothesis)] + [
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ from pathlib import Path
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import fire
|
import fire
|
||||||
|
|
||||||
from rdagent.app.qlib_rd_loop.conf import QUANT_PROP_SETTING
|
from rdagent.app.qlib_rd_loop.conf import QUANT_PROP_SETTING
|
||||||
from rdagent.components.workflow.conf import BasePropSetting
|
from rdagent.components.workflow.conf import BasePropSetting
|
||||||
from rdagent.components.workflow.rd_loop import RDLoop
|
from rdagent.components.workflow.rd_loop import RDLoop
|
||||||
@@ -44,11 +43,11 @@ class QuantRDLoop(RDLoop):
|
|||||||
logger.log_object(self.hypothesis_gen, tag="quant hypothesis generator")
|
logger.log_object(self.hypothesis_gen, tag="quant hypothesis generator")
|
||||||
|
|
||||||
self.factor_hypothesis2experiment: Hypothesis2Experiment = import_class(
|
self.factor_hypothesis2experiment: Hypothesis2Experiment = import_class(
|
||||||
PROP_SETTING.factor_hypothesis2experiment
|
PROP_SETTING.factor_hypothesis2experiment,
|
||||||
)()
|
)()
|
||||||
logger.log_object(self.factor_hypothesis2experiment, tag="factor hypothesis2experiment")
|
logger.log_object(self.factor_hypothesis2experiment, tag="factor hypothesis2experiment")
|
||||||
self.model_hypothesis2experiment: Hypothesis2Experiment = import_class(
|
self.model_hypothesis2experiment: Hypothesis2Experiment = import_class(
|
||||||
PROP_SETTING.model_hypothesis2experiment
|
PROP_SETTING.model_hypothesis2experiment,
|
||||||
)()
|
)()
|
||||||
logger.log_object(self.model_hypothesis2experiment, tag="model hypothesis2experiment")
|
logger.log_object(self.model_hypothesis2experiment, tag="model hypothesis2experiment")
|
||||||
|
|
||||||
@@ -74,11 +73,100 @@ class QuantRDLoop(RDLoop):
|
|||||||
self.trace = QuantTrace(scen=scen)
|
self.trace = QuantTrace(scen=scen)
|
||||||
super(RDLoop, self).__init__()
|
super(RDLoop, self).__init__()
|
||||||
|
|
||||||
|
def _ensure_kronos_factors_in_pool(self) -> None:
|
||||||
|
"""Generate Kronos foundation model factors with varying prediction horizons.
|
||||||
|
|
||||||
|
Generates KronosPredReturn_p24, KronosPredReturn_p48, KronosPredReturn_p96
|
||||||
|
if they don't already exist in results/factors/. Uses CPU inference so it
|
||||||
|
co-exists peacefully with the llama-server GPU process.
|
||||||
|
"""
|
||||||
|
import json as _json
|
||||||
|
from datetime import datetime as _dt
|
||||||
|
from pathlib import Path as _Path
|
||||||
|
|
||||||
|
data_path = _Path("git_ignore_folder/factor_implementation_source_data/intraday_pv.h5")
|
||||||
|
if not data_path.exists():
|
||||||
|
logger.warning("Kronos: intraday_pv.h5 missing, skipping factor generation")
|
||||||
|
return
|
||||||
|
|
||||||
|
factors_dir = _Path("results/factors")
|
||||||
|
values_dir = factors_dir / "values"
|
||||||
|
|
||||||
|
for pred_bars in (24, 48, 96):
|
||||||
|
factor_name = f"KronosPredReturn_p{pred_bars}"
|
||||||
|
json_path = factors_dir / f"{factor_name}.json"
|
||||||
|
parquet_path = values_dir / f"{factor_name}.parquet"
|
||||||
|
|
||||||
|
if json_path.exists() and parquet_path.exists():
|
||||||
|
try:
|
||||||
|
existing = _json.loads(json_path.read_text())
|
||||||
|
if existing.get("ic") is not None and existing.get("model_size") == "small":
|
||||||
|
logger.info(f"Kronos: {factor_name} exists (IC={existing['ic']:.4f}), skip")
|
||||||
|
continue
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
from rdagent.components.coder.kronos_adapter import build_kronos_factor, evaluate_kronos_model
|
||||||
|
|
||||||
|
has_cuda = False
|
||||||
|
try:
|
||||||
|
import torch
|
||||||
|
has_cuda = torch.cuda.is_available()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
device = "cuda" if has_cuda else "cpu"
|
||||||
|
|
||||||
|
logger.info(f"Kronos-small: generating {factor_name} (pred={pred_bars}, stride=500, {device})...")
|
||||||
|
factor_df = build_kronos_factor(
|
||||||
|
hdf5_path=data_path,
|
||||||
|
context_bars=100,
|
||||||
|
pred_bars=pred_bars,
|
||||||
|
stride_bars=500,
|
||||||
|
device=device,
|
||||||
|
batch_size=32,
|
||||||
|
model_size="small",
|
||||||
|
)
|
||||||
|
|
||||||
|
values_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
factor_df.to_parquet(parquet_path)
|
||||||
|
|
||||||
|
logger.info(f"Kronos: computing IC for {factor_name}...")
|
||||||
|
metrics = evaluate_kronos_model(
|
||||||
|
hdf5_path=data_path,
|
||||||
|
context_bars=100,
|
||||||
|
pred_bars=pred_bars,
|
||||||
|
stride_bars=2000,
|
||||||
|
device=device,
|
||||||
|
batch_size=32,
|
||||||
|
model_size="small",
|
||||||
|
)
|
||||||
|
ic = metrics.get("IC_mean", 0.0) or 0.0
|
||||||
|
|
||||||
|
factors_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
meta = {
|
||||||
|
"factor_name": factor_name,
|
||||||
|
"status": "success",
|
||||||
|
"ic": ic,
|
||||||
|
"model_size": "small",
|
||||||
|
"model": "NeoQuasar/Kronos-mini",
|
||||||
|
"context_bars": 100,
|
||||||
|
"pred_bars": pred_bars,
|
||||||
|
"device": "cpu",
|
||||||
|
"generated_at": _dt.now().isoformat(),
|
||||||
|
}
|
||||||
|
json_path.write_text(_json.dumps(meta, indent=2))
|
||||||
|
logger.info(f"Kronos: {factor_name} ready — IC={ic:.4f}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Kronos: {factor_name} failed — {e}")
|
||||||
|
|
||||||
async def direct_exp_gen(self, prev_out: dict[str, Any]):
|
async def direct_exp_gen(self, prev_out: dict[str, Any]):
|
||||||
while True:
|
while True:
|
||||||
if self.get_unfinished_loop_cnt(self.loop_idx) < RD_AGENT_SETTINGS.get_max_parallel():
|
if self.get_unfinished_loop_cnt(self.loop_idx) < RD_AGENT_SETTINGS.get_max_parallel():
|
||||||
hypo = self._propose()
|
hypo = self._propose()
|
||||||
assert hypo.action in ["factor", "model"]
|
if hypo.action not in ["factor", "model"]:
|
||||||
|
raise ValueError(f"hypo.action must be 'factor' or 'model', got {hypo.action!r}")
|
||||||
if hypo.action == "factor":
|
if hypo.action == "factor":
|
||||||
exp = self.factor_hypothesis2experiment.convert(hypo, self.trace)
|
exp = self.factor_hypothesis2experiment.convert(hypo, self.trace)
|
||||||
else:
|
else:
|
||||||
@@ -132,7 +220,6 @@ class QuantRDLoop(RDLoop):
|
|||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
project_root = Path(__file__).parent.parent.parent.parent
|
project_root = Path(__file__).parent.parent.parent.parent
|
||||||
@@ -195,11 +282,11 @@ class QuantRDLoop(RDLoop):
|
|||||||
if prev_out["direct_exp_gen"]["propose"].action == "factor":
|
if prev_out["direct_exp_gen"]["propose"].action == "factor":
|
||||||
exp = self.factor_runner.develop(prev_out["coding"])
|
exp = self.factor_runner.develop(prev_out["coding"])
|
||||||
if exp is None:
|
if exp is None:
|
||||||
logger.error(f"Factor extraction failed.")
|
logger.error("Factor extraction failed.")
|
||||||
raise FactorEmptyError("Factor extraction failed.")
|
raise FactorEmptyError("Factor extraction failed.")
|
||||||
|
|
||||||
# Increment factor count for tracking
|
# Increment factor count for tracking
|
||||||
if hasattr(self, 'trace') and hasattr(self.trace, 'increment_factor_count'):
|
if hasattr(self, "trace") and hasattr(self.trace, "increment_factor_count"):
|
||||||
self.trace.increment_factor_count()
|
self.trace.increment_factor_count()
|
||||||
|
|
||||||
# Handle failed experiments gracefully (don't break the loop)
|
# Handle failed experiments gracefully (don't break the loop)
|
||||||
@@ -210,7 +297,7 @@ class QuantRDLoop(RDLoop):
|
|||||||
factor_name = getattr(exp.hypothesis, "hypothesis", "unknown")
|
factor_name = getattr(exp.hypothesis, "hypothesis", "unknown")
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Factor '{factor_name}' failed evaluation: {reason}. "
|
f"Factor '{factor_name}' failed evaluation: {reason}. "
|
||||||
f"Continuing with next factor."
|
f"Continuing with next factor.",
|
||||||
)
|
)
|
||||||
# Return exp anyway - loop will continue
|
# Return exp anyway - loop will continue
|
||||||
elif prev_out["direct_exp_gen"]["propose"].action == "model":
|
elif prev_out["direct_exp_gen"]["propose"].action == "model":
|
||||||
@@ -219,7 +306,7 @@ class QuantRDLoop(RDLoop):
|
|||||||
return exp
|
return exp
|
||||||
|
|
||||||
def feedback(self, prev_out: dict[str, Any]):
|
def feedback(self, prev_out: dict[str, Any]):
|
||||||
e = prev_out.get(self.EXCEPTION_KEY, None)
|
e = prev_out.get(self.EXCEPTION_KEY)
|
||||||
if e is not None:
|
if e is not None:
|
||||||
feedback = HypothesisFeedback(
|
feedback = HypothesisFeedback(
|
||||||
observations=str(e),
|
observations=str(e),
|
||||||
@@ -245,11 +332,10 @@ class QuantRDLoop(RDLoop):
|
|||||||
reason=reason,
|
reason=reason,
|
||||||
decision=False,
|
decision=False,
|
||||||
)
|
)
|
||||||
else:
|
elif prev_out["direct_exp_gen"]["propose"].action == "factor":
|
||||||
if prev_out["direct_exp_gen"]["propose"].action == "factor":
|
feedback = self.factor_summarizer.generate_feedback(prev_out["running"], self.trace)
|
||||||
feedback = self.factor_summarizer.generate_feedback(prev_out["running"], self.trace)
|
elif prev_out["direct_exp_gen"]["propose"].action == "model":
|
||||||
elif prev_out["direct_exp_gen"]["propose"].action == "model":
|
feedback = self.model_summarizer.generate_feedback(prev_out["running"], self.trace)
|
||||||
feedback = self.model_summarizer.generate_feedback(prev_out["running"], self.trace)
|
|
||||||
|
|
||||||
# NOTE: DB save is handled by factor_runner.py _save_result_to_database()
|
# NOTE: DB save is handled by factor_runner.py _save_result_to_database()
|
||||||
# which runs immediately after Docker execution. No duplicate save needed here.
|
# which runs immediately after Docker execution. No duplicate save needed here.
|
||||||
@@ -258,20 +344,20 @@ class QuantRDLoop(RDLoop):
|
|||||||
factor_count = self.trace.get_factor_count()
|
factor_count = self.trace.get_factor_count()
|
||||||
|
|
||||||
# Check for auto-strategies trigger
|
# Check for auto-strategies trigger
|
||||||
auto_strategies = getattr(self, '_auto_strategies', False)
|
auto_strategies = getattr(self, "_auto_strategies", False)
|
||||||
auto_threshold = getattr(self, '_auto_strategies_threshold', 500)
|
auto_threshold = getattr(self, "_auto_strategies_threshold", 500)
|
||||||
|
|
||||||
if auto_strategies and factor_count > 0 and factor_count % auto_threshold == 0:
|
if auto_strategies and factor_count > 0 and factor_count % auto_threshold == 0:
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Auto-strategy trigger: {factor_count} factors evaluated. "
|
f"Auto-strategy trigger: {factor_count} factors evaluated. "
|
||||||
f"Suggesting strategy generation now..."
|
f"Suggesting strategy generation now...",
|
||||||
)
|
)
|
||||||
self._build_strategies_with_ai()
|
self._build_strategies_with_ai()
|
||||||
elif factor_count > 0 and factor_count % 50 == 0 and not auto_strategies:
|
elif factor_count > 0 and factor_count % 50 == 0 and not auto_strategies:
|
||||||
# Standard periodic suggestion (every 50 factors)
|
# Standard periodic suggestion (every 50 factors)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Periodic check: {factor_count} factors evaluated. "
|
f"Periodic check: {factor_count} factors evaluated. "
|
||||||
f"Consider running 'rdagent generate_strategies' for AI strategy generation."
|
f"Consider running 'rdagent generate_strategies' for AI strategy generation.",
|
||||||
)
|
)
|
||||||
|
|
||||||
feedback = self._interact_feedback(feedback)
|
feedback = self._interact_feedback(feedback)
|
||||||
@@ -292,9 +378,10 @@ class QuantRDLoop(RDLoop):
|
|||||||
- Optuna hyperparameter optimization
|
- Optuna hyperparameter optimization
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from rdagent.components.coder.strategy_orchestrator import StrategyOrchestrator
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
from rdagent.scenarios.qlib.local.strategy_orchestrator import StrategyOrchestrator
|
||||||
|
|
||||||
# Load improved prompt
|
# Load improved prompt
|
||||||
project_root = Path(__file__).parent.parent.parent.parent
|
project_root = Path(__file__).parent.parent.parent.parent
|
||||||
@@ -322,6 +409,7 @@ class QuantRDLoop(RDLoop):
|
|||||||
if data.get("status") == "success" and data.get("ic") is not None:
|
if data.get("status") == "success" and data.get("ic") is not None:
|
||||||
factors.append(data)
|
factors.append(data)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
logger.warning("Failed to load factor file %s", f, exc_info=True)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if len(factors) < 10:
|
if len(factors) < 10:
|
||||||
@@ -334,14 +422,14 @@ class QuantRDLoop(RDLoop):
|
|||||||
|
|
||||||
logger.info(f"StrategyOrchestrator: Building strategies from {len(top_factors)} top factors...")
|
logger.info(f"StrategyOrchestrator: Building strategies from {len(top_factors)} top factors...")
|
||||||
logger.info(f" - Using improved prompt: {improved_prompt is not None}")
|
logger.info(f" - Using improved prompt: {improved_prompt is not None}")
|
||||||
logger.info(f" - Optuna optimization: enabled (20 trials)")
|
logger.info(" - Optuna optimization: enabled (20 trials)")
|
||||||
logger.info(f" - Real OHLCV backtest: enabled")
|
logger.info(" - Real OHLCV backtest: enabled")
|
||||||
|
|
||||||
# Initialize orchestrator with Optuna
|
# Initialize orchestrator with Optuna
|
||||||
orchestrator = StrategyOrchestrator(
|
orchestrator = StrategyOrchestrator(
|
||||||
top_factors=20,
|
top_factors=20,
|
||||||
trading_style='swing',
|
trading_style="swing",
|
||||||
min_sharpe=0.5,
|
min_sharpe=1.5,
|
||||||
max_drawdown=-0.20,
|
max_drawdown=-0.20,
|
||||||
min_win_rate=0.40,
|
min_win_rate=0.40,
|
||||||
use_optuna=True,
|
use_optuna=True,
|
||||||
@@ -350,7 +438,7 @@ class QuantRDLoop(RDLoop):
|
|||||||
|
|
||||||
# Override with improved prompt if available
|
# Override with improved prompt if available
|
||||||
if improved_prompt:
|
if improved_prompt:
|
||||||
orchestrator.strategy_prompt = improved_prompt.get('strategy_generation', {})
|
orchestrator.strategy_prompt = improved_prompt.get("strategy_generation", {})
|
||||||
|
|
||||||
# Generate 3 strategies per cycle
|
# Generate 3 strategies per cycle
|
||||||
n_strategies = 3
|
n_strategies = 3
|
||||||
@@ -358,15 +446,18 @@ class QuantRDLoop(RDLoop):
|
|||||||
|
|
||||||
# Load top factors for generation
|
# Load top factors for generation
|
||||||
orch_factors = orchestrator.load_top_factors()
|
orch_factors = orchestrator.load_top_factors()
|
||||||
|
if len(orch_factors) < 2:
|
||||||
|
logger.warning(f"Not enough factors for strategy generation (need >= 2, got {len(orch_factors)}). Skipping.")
|
||||||
|
return
|
||||||
|
|
||||||
for i in range(n_strategies):
|
for i in range(n_strategies):
|
||||||
|
strategy_name = f"auto_gen_v{i+1}"
|
||||||
try:
|
try:
|
||||||
# Select random factor combination
|
# Select random factor combination
|
||||||
import random
|
import random
|
||||||
n_factors = random.randint(2, min(5, len(orch_factors)))
|
n_factors = random.randint(2, min(5, len(orch_factors)))
|
||||||
factor_subset = random.sample(orch_factors, n_factors)
|
factor_subset = random.sample(orch_factors, n_factors)
|
||||||
|
|
||||||
strategy_name = f"auto_gen_v{i+1}"
|
|
||||||
code = orchestrator.generate_strategy_code(factor_subset, strategy_name)
|
code = orchestrator.generate_strategy_code(factor_subset, strategy_name)
|
||||||
|
|
||||||
if code:
|
if code:
|
||||||
@@ -420,6 +511,7 @@ def main(
|
|||||||
else:
|
else:
|
||||||
quant_loop = QuantRDLoop.load(path, checkout=checkout)
|
quant_loop = QuantRDLoop.load(path, checkout=checkout)
|
||||||
quant_loop._init_base_features(base_features_path)
|
quant_loop._init_base_features(base_features_path)
|
||||||
|
quant_loop._ensure_kronos_factors_in_pool()
|
||||||
if "user_interaction_queues" in kwargs and kwargs["user_interaction_queues"] is not None:
|
if "user_interaction_queues" in kwargs and kwargs["user_interaction_queues"] is not None:
|
||||||
quant_loop._set_interactor(*kwargs["user_interaction_queues"])
|
quant_loop._set_interactor(*kwargs["user_interaction_queues"])
|
||||||
quant_loop._interact_init_params()
|
quant_loop._interact_init_params()
|
||||||
@@ -429,7 +521,7 @@ def main(
|
|||||||
quant_loop._auto_strategies = True
|
quant_loop._auto_strategies = True
|
||||||
quant_loop._auto_strategies_threshold = auto_strategies_threshold
|
quant_loop._auto_strategies_threshold = auto_strategies_threshold
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Auto-strategies enabled. Will trigger after {auto_strategies_threshold} factors."
|
f"Auto-strategies enabled. Will trigger after {auto_strategies_threshold} factors.",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
quant_loop._auto_strategies = False
|
quant_loop._auto_strategies = False
|
||||||
|
|||||||
@@ -16,55 +16,26 @@ from rdagent.app.rl.ui.components import render_session, render_summary
|
|||||||
from rdagent.app.rl.ui.config import ALWAYS_VISIBLE_TYPES, OPTIONAL_TYPES
|
from rdagent.app.rl.ui.config import ALWAYS_VISIBLE_TYPES, OPTIONAL_TYPES
|
||||||
from rdagent.app.rl.ui.data_loader import get_summary, get_valid_sessions, load_session
|
from rdagent.app.rl.ui.data_loader import get_summary, get_valid_sessions, load_session
|
||||||
from rdagent.app.rl.ui.rl_summary import render_job_summary
|
from rdagent.app.rl.ui.rl_summary import render_job_summary
|
||||||
|
from rdagent.core.utils import safe_resolve_path
|
||||||
|
|
||||||
DEFAULT_LOG_BASE = "log/"
|
DEFAULT_LOG_BASE = "log/"
|
||||||
|
|
||||||
|
|
||||||
def _safe_resolve(user_input: str | None, safe_root: Path) -> Path:
|
def _safe_resolve(user_input: str | None, safe_root: Path) -> Path:
|
||||||
"""
|
|
||||||
Resolve user path relative to safe_root; raise ValueError if it escapes.
|
|
||||||
|
|
||||||
Security: This function prevents path traversal attacks by:
|
|
||||||
1. Rejecting null bytes in user input
|
|
||||||
2. Rejecting Windows drive letters (C:\, D:\, etc.)
|
|
||||||
3. Rejecting absolute paths
|
|
||||||
4. Normalizing path to remove .. traversal attempts
|
|
||||||
5. Validating resolved path is within safe_root using a realpath-based check
|
|
||||||
|
|
||||||
All user-provided paths are validated before filesystem access.
|
|
||||||
"""
|
|
||||||
# Treat the provided safe_root as trusted and canonicalize it once.
|
|
||||||
safe_root = safe_root.expanduser().resolve()
|
safe_root = safe_root.expanduser().resolve()
|
||||||
|
|
||||||
# Empty input maps to the safe root directory.
|
|
||||||
if not user_input:
|
if not user_input:
|
||||||
return safe_root
|
return safe_root
|
||||||
|
|
||||||
# Security check 1: Reject null bytes (path truncation attack)
|
|
||||||
if "\x00" in user_input:
|
if "\x00" in user_input:
|
||||||
raise ValueError("Invalid path: contains null byte")
|
raise ValueError("Invalid path: contains null byte")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Security check 2: Normalize path to resolve .. and . components
|
|
||||||
normalized = os.path.normpath(user_input.strip())
|
normalized = os.path.normpath(user_input.strip())
|
||||||
|
|
||||||
# Security check 3: Reject Windows drive letters (C:\, D:\, etc.)
|
|
||||||
drive, _ = os.path.splitdrive(normalized)
|
drive, _ = os.path.splitdrive(normalized)
|
||||||
if drive:
|
if drive:
|
||||||
raise ValueError("Absolute paths with drive letters are not allowed")
|
raise ValueError("Absolute paths with drive letters are not allowed")
|
||||||
|
|
||||||
# Security check 4: Reject absolute paths (/, //server/share, etc.)
|
|
||||||
if os.path.isabs(normalized):
|
if os.path.isabs(normalized):
|
||||||
raise ValueError("Absolute paths are not allowed")
|
raise ValueError("Absolute paths are not allowed")
|
||||||
|
joined = safe_root / normalized
|
||||||
# Security check 5: Build candidate path under safe_root and fully resolve it.
|
return safe_resolve_path(joined, safe_root)
|
||||||
joined = os.path.join(str(safe_root), normalized)
|
|
||||||
resolved_candidate = os.path.realpath(joined)
|
|
||||||
|
|
||||||
# Security check 6: Validate candidate is within safe_root (prevent path traversal)
|
|
||||||
candidate_path = Path(resolved_candidate)
|
|
||||||
# Reconstruct from trusted safe_root so the returned path is root-derived.
|
|
||||||
return safe_root / candidate_path.relative_to(safe_root)
|
|
||||||
except (OSError, ValueError) as exc:
|
except (OSError, ValueError) as exc:
|
||||||
raise ValueError(f"Invalid path outside of allowed root: {user_input}") from exc
|
raise ValueError(f"Invalid path outside of allowed root: {user_input}") from exc
|
||||||
|
|
||||||
@@ -82,7 +53,7 @@ def get_job_options(base_path: Path, safe_root: Path | None = None) -> list[str]
|
|||||||
|
|
||||||
# Security fix: Validate base_path to prevent path traversal
|
# Security fix: Validate base_path to prevent path traversal
|
||||||
try:
|
try:
|
||||||
base_path_resolved = base_path.expanduser().resolve()
|
base_path_resolved = base_path.expanduser().resolve() # nosec B614 — validated against safe_root below via relative_to()
|
||||||
|
|
||||||
if safe_root is not None:
|
if safe_root is not None:
|
||||||
safe_root_resolved = safe_root.expanduser().resolve()
|
safe_root_resolved = safe_root.expanduser().resolve()
|
||||||
@@ -203,8 +174,7 @@ def main():
|
|||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
st.warning(str(e))
|
st.warning(str(e))
|
||||||
return
|
return
|
||||||
# job_path is validated by _safe_resolve() above
|
if job_path.exists():
|
||||||
if job_path.exists(): # nosec B614 – path validated by _safe_resolve
|
|
||||||
render_job_summary(job_path, safe_root, is_root=is_root_job)
|
render_job_summary(job_path, safe_root, is_root=is_root_job)
|
||||||
else:
|
else:
|
||||||
st.warning(f"Job folder not found: {job_folder}")
|
st.warning(f"Job folder not found: {job_folder}")
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from typing import Any
|
|||||||
import streamlit as st
|
import streamlit as st
|
||||||
|
|
||||||
from rdagent.app.rl.ui.config import EventType
|
from rdagent.app.rl.ui.config import EventType
|
||||||
|
from rdagent.core.utils import safe_resolve_path
|
||||||
from rdagent.log.storage import FileStorage
|
from rdagent.log.storage import FileStorage
|
||||||
|
|
||||||
|
|
||||||
@@ -76,11 +77,10 @@ def extract_stage(tag: str) -> str:
|
|||||||
def get_valid_sessions(log_folder: Path, safe_root: Path | None = None) -> list[str]:
|
def get_valid_sessions(log_folder: Path, safe_root: Path | None = None) -> list[str]:
|
||||||
"""Get list of valid session directories, optionally validating against a safe root."""
|
"""Get list of valid session directories, optionally validating against a safe root."""
|
||||||
if safe_root is not None:
|
if safe_root is not None:
|
||||||
root_real = os.path.realpath(str(safe_root.expanduser()))
|
try:
|
||||||
folder_real = os.path.realpath(str(log_folder.expanduser()))
|
log_folder = safe_resolve_path(log_folder, safe_root)
|
||||||
if not (folder_real == root_real or folder_real.startswith(root_real + os.sep)):
|
except ValueError:
|
||||||
return []
|
return []
|
||||||
log_folder = Path(folder_real)
|
|
||||||
|
|
||||||
if not log_folder.exists():
|
if not log_folder.exists():
|
||||||
return []
|
return []
|
||||||
@@ -245,13 +245,11 @@ def parse_event(tag: str, content: Any, timestamp: datetime) -> Event | None:
|
|||||||
@st.cache_data(ttl=300, hash_funcs={Path: str})
|
@st.cache_data(ttl=300, hash_funcs={Path: str})
|
||||||
def load_session(log_path: Path, safe_root: Path | None = None) -> Session:
|
def load_session(log_path: Path, safe_root: Path | None = None) -> Session:
|
||||||
"""Load events into hierarchical session structure, optionally validating against safe root."""
|
"""Load events into hierarchical session structure, optionally validating against safe root."""
|
||||||
# Validate path is within safe_root if provided
|
|
||||||
if safe_root is not None:
|
if safe_root is not None:
|
||||||
root_real = os.path.realpath(str(safe_root.expanduser()))
|
try:
|
||||||
path_real = os.path.realpath(str(log_path.expanduser()))
|
log_path = safe_resolve_path(log_path, safe_root)
|
||||||
if not (path_real == root_real or path_real.startswith(root_real + os.sep)):
|
except ValueError:
|
||||||
return Session()
|
return Session()
|
||||||
log_path = Path(path_real)
|
|
||||||
|
|
||||||
session = Session()
|
session = Session()
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ from pathlib import Path
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
import streamlit as st
|
import streamlit as st
|
||||||
|
|
||||||
|
from rdagent.core.utils import safe_resolve_path
|
||||||
|
|
||||||
|
|
||||||
def is_valid_task(task_path: Path) -> bool:
|
def is_valid_task(task_path: Path) -> bool:
|
||||||
"""Check if directory is a valid RL task (has __session__ subdirectory)"""
|
"""Check if directory is a valid RL task (has __session__ subdirectory)"""
|
||||||
@@ -62,14 +64,10 @@ def get_loop_status(task_path: Path, loop_id: int) -> tuple[str, bool | None]:
|
|||||||
|
|
||||||
|
|
||||||
def _validate_job_path(job_path: Path, safe_root: Path) -> Path:
|
def _validate_job_path(job_path: Path, safe_root: Path) -> Path:
|
||||||
"""Resolve and validate that job_path stays within safe_root."""
|
|
||||||
resolved_root = safe_root.expanduser().resolve()
|
|
||||||
resolved_job = job_path.expanduser().resolve()
|
|
||||||
try:
|
try:
|
||||||
# Reconstruct from trusted root so the returned path is root-derived.
|
return safe_resolve_path(job_path, safe_root)
|
||||||
return resolved_root / resolved_job.relative_to(resolved_root)
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError(f"Job path is outside allowed root {resolved_root}")
|
raise ValueError(f"Job path is outside allowed root {safe_root}")
|
||||||
|
|
||||||
|
|
||||||
def get_max_loops(job_path: Path, safe_root: Path | None = None) -> int:
|
def get_max_loops(job_path: Path, safe_root: Path | None = None) -> int:
|
||||||
|
|||||||
@@ -54,11 +54,11 @@ def rdagent_info():
|
|||||||
current_version = importlib.metadata.version("rdagent")
|
current_version = importlib.metadata.version("rdagent")
|
||||||
logger.info(f"RD-Agent version: {current_version}")
|
logger.info(f"RD-Agent version: {current_version}")
|
||||||
api_url = f"https://api.github.com/repos/microsoft/RD-Agent/contents/requirements.txt?ref=main"
|
api_url = f"https://api.github.com/repos/microsoft/RD-Agent/contents/requirements.txt?ref=main"
|
||||||
response = requests.get(api_url)
|
response = requests.get(api_url, timeout=30)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
files = response.json()
|
files = response.json()
|
||||||
file_url = files["download_url"]
|
file_url = files["download_url"]
|
||||||
file_response = requests.get(file_url)
|
file_response = requests.get(file_url, timeout=30)
|
||||||
if file_response.status_code == 200:
|
if file_response.status_code == 200:
|
||||||
all_file_contents = file_response.text.split("\n")
|
all_file_contents = file_response.text.split("\n")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
"""Predix Backtesting Package"""
|
"""NexQuant Backtesting Package"""
|
||||||
from .backtest_engine import BacktestMetrics, FactorBacktester
|
from .backtest_engine import BacktestMetrics, FactorBacktester
|
||||||
from .results_db import ResultsDatabase
|
from .results_db import ResultsDatabase
|
||||||
from .risk_management import CorrelationAnalyzer, PortfolioOptimizer, AdvancedRiskManager
|
from .risk_management import CorrelationAnalyzer, PortfolioOptimizer, AdvancedRiskManager
|
||||||
from .vbt_backtest import (
|
from .vbt_backtest import (
|
||||||
DEFAULT_BARS_PER_YEAR,
|
DEFAULT_BARS_PER_YEAR,
|
||||||
DEFAULT_TXN_COST_BPS,
|
DEFAULT_TXN_COST_BPS,
|
||||||
FTMO_INITIAL_CAPITAL,
|
INITIAL_CAPITAL,
|
||||||
FTMO_MAX_DAILY_LOSS,
|
MAX_DAILY_LOSS,
|
||||||
FTMO_MAX_TOTAL_LOSS,
|
MAX_TOTAL_LOSS,
|
||||||
FTMO_MAX_LEVERAGE,
|
MAX_LEVERAGE,
|
||||||
FTMO_RISK_PER_TRADE,
|
RISK_PER_TRADE,
|
||||||
OOS_START_DEFAULT,
|
OOS_START_DEFAULT,
|
||||||
WF_IS_YEARS,
|
WF_IS_YEARS,
|
||||||
WF_OOS_YEARS,
|
WF_OOS_YEARS,
|
||||||
WF_STEP_YEARS,
|
WF_STEP_YEARS,
|
||||||
backtest_from_forward_returns,
|
backtest_from_forward_returns,
|
||||||
backtest_signal,
|
backtest_signal,
|
||||||
backtest_signal_ftmo,
|
backtest_signal_risk,
|
||||||
monte_carlo_trade_pvalue,
|
monte_carlo_trade_pvalue,
|
||||||
walk_forward_rolling,
|
walk_forward_rolling,
|
||||||
)
|
)
|
||||||
@@ -24,10 +24,10 @@ from .vbt_backtest import (
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
'BacktestMetrics', 'FactorBacktester', 'ResultsDatabase',
|
'BacktestMetrics', 'FactorBacktester', 'ResultsDatabase',
|
||||||
'CorrelationAnalyzer', 'PortfolioOptimizer', 'AdvancedRiskManager',
|
'CorrelationAnalyzer', 'PortfolioOptimizer', 'AdvancedRiskManager',
|
||||||
'backtest_signal', 'backtest_signal_ftmo', 'backtest_from_forward_returns',
|
'backtest_signal', 'backtest_signal_risk', 'backtest_from_forward_returns',
|
||||||
'monte_carlo_trade_pvalue', 'walk_forward_rolling',
|
'monte_carlo_trade_pvalue', 'walk_forward_rolling',
|
||||||
'DEFAULT_BARS_PER_YEAR', 'DEFAULT_TXN_COST_BPS',
|
'DEFAULT_BARS_PER_YEAR', 'DEFAULT_TXN_COST_BPS',
|
||||||
'FTMO_INITIAL_CAPITAL', 'FTMO_MAX_DAILY_LOSS', 'FTMO_MAX_TOTAL_LOSS',
|
'INITIAL_CAPITAL', 'MAX_DAILY_LOSS', 'MAX_TOTAL_LOSS',
|
||||||
'FTMO_MAX_LEVERAGE', 'FTMO_RISK_PER_TRADE', 'OOS_START_DEFAULT',
|
'MAX_LEVERAGE', 'RISK_PER_TRADE', 'OOS_START_DEFAULT',
|
||||||
'WF_IS_YEARS', 'WF_OOS_YEARS', 'WF_STEP_YEARS',
|
'WF_IS_YEARS', 'WF_OOS_YEARS', 'WF_STEP_YEARS',
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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.
|
Thin wrapper around the unified ``vbt_backtest.backtest_signal`` engine.
|
||||||
All metric formulas live in ``vbt_backtest``; this module exists for
|
All metric formulas live in ``vbt_backtest``; this module exists for
|
||||||
@@ -72,7 +72,7 @@ class BacktestMetrics:
|
|||||||
class FactorBacktester:
|
class FactorBacktester:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.metrics = BacktestMetrics()
|
self.metrics = BacktestMetrics()
|
||||||
self.results_path = Path(__file__).parent.parent.parent / "results" / "backtests"
|
self.results_path = Path(__file__).parent.parent.parent.parent / "results" / "backtests"
|
||||||
self.results_path.mkdir(parents=True, exist_ok=True)
|
self.results_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
def run_backtest(
|
def run_backtest(
|
||||||
@@ -222,7 +222,7 @@ class FactorBacktester:
|
|||||||
|
|
||||||
# Calculate return for this step
|
# Calculate return for this step
|
||||||
if step > 0:
|
if step > 0:
|
||||||
prev_price = float(price_values[step - 1]) if step > 0 else current_price
|
prev_price = float(price_values[step - 1])
|
||||||
if prev_price > 0:
|
if prev_price > 0:
|
||||||
step_return = (current_price - prev_price) / prev_price * position
|
step_return = (current_price - prev_price) / prev_price * position
|
||||||
returns_history.append(step_return)
|
returns_history.append(step_return)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Trading Protection System for Predix.
|
Trading Protection System for NexQuant.
|
||||||
|
|
||||||
Prevents excessive losses by automatically pausing trading
|
Prevents excessive losses by automatically pausing trading
|
||||||
when risk thresholds are exceeded.
|
when risk thresholds are exceeded.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Trading Protection System
|
|||||||
|
|
||||||
Prevents excessive losses by automatically pausing trading when risk thresholds are exceeded.
|
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
|
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.
|
Stores backtest metrics from Qlib/MLflow runs for querying and dashboard display.
|
||||||
"""
|
"""
|
||||||
@@ -71,6 +71,9 @@ class ResultsDatabase:
|
|||||||
|
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
|
|
||||||
|
_ALLOWED_TABLES = frozenset({"factors", "backtest_runs", "loop_results"})
|
||||||
|
_ALLOWED_COL_TYPES = frozenset({"REAL", "TEXT", "INTEGER", "BLOB"})
|
||||||
|
|
||||||
def _add_column_if_not_exists(self, table: str, column: str, col_type: str) -> None:
|
def _add_column_if_not_exists(self, table: str, column: str, col_type: str) -> None:
|
||||||
"""
|
"""
|
||||||
Add a column to a table if it doesn't already exist.
|
Add a column to a table if it doesn't already exist.
|
||||||
@@ -78,20 +81,24 @@ class ResultsDatabase:
|
|||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
table : str
|
table : str
|
||||||
Table name
|
Table name (must be in _ALLOWED_TABLES)
|
||||||
column : str
|
column : str
|
||||||
Column name to add
|
Column name to add (alphanumeric + underscore only)
|
||||||
col_type : str
|
col_type : str
|
||||||
SQL column type (e.g., 'REAL', 'TEXT')
|
SQL column type (must be in _ALLOWED_COL_TYPES)
|
||||||
"""
|
"""
|
||||||
|
if table not in self._ALLOWED_TABLES:
|
||||||
|
raise ValueError(f"Unknown table: {table!r}")
|
||||||
|
if not column.replace("_", "").isalnum():
|
||||||
|
raise ValueError(f"Invalid column name: {column!r}")
|
||||||
|
if col_type not in self._ALLOWED_COL_TYPES:
|
||||||
|
raise ValueError(f"Invalid column type: {col_type!r}")
|
||||||
|
|
||||||
c = self.conn.cursor()
|
c = self.conn.cursor()
|
||||||
try:
|
c.execute("SELECT name FROM pragma_table_info(?)", (table,))
|
||||||
# Try to query the column - if it fails, it doesn't exist
|
existing = {row[0] for row in c.fetchall()}
|
||||||
# nosec B608: Internal schema migration, column names are controlled
|
if column.lower() not in {name.lower() for name in existing}:
|
||||||
c.execute(f"SELECT {column} FROM {table} LIMIT 1") # nosec B608
|
c.execute(f"ALTER TABLE {table} ADD COLUMN {column} {col_type}")
|
||||||
except sqlite3.OperationalError:
|
|
||||||
# Column doesn't exist, add it
|
|
||||||
c.execute(f"ALTER TABLE {table} ADD COLUMN {column} {col_type}") # nosec B608
|
|
||||||
|
|
||||||
def add_factor(self, name: str, type: str = "unknown") -> int:
|
def add_factor(self, name: str, type: str = "unknown") -> int:
|
||||||
c = self.conn.cursor()
|
c = self.conn.cursor()
|
||||||
@@ -159,7 +166,7 @@ class ResultsDatabase:
|
|||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
return c.lastrowid
|
return c.lastrowid
|
||||||
|
|
||||||
def add_loop(self, loop_idx: int, success: int, fail: int, best_ic: float = None, status: str = "completed") -> int:
|
def add_loop(self, loop_idx: int, success: int, fail: int, best_ic: float | None = None, status: str = "completed") -> int:
|
||||||
c = self.conn.cursor()
|
c = self.conn.cursor()
|
||||||
rate = success / (success + fail) if (success + fail) > 0 else 0
|
rate = success / (success + fail) if (success + fail) > 0 else 0
|
||||||
c.execute("""INSERT INTO loop_results (loop_index, factors_success, factors_fail, success_rate, best_ic, status)
|
c.execute("""INSERT INTO loop_results (loop_index, factors_success, factors_fail, success_rate, best_ic, status)
|
||||||
@@ -183,16 +190,18 @@ class ResultsDatabase:
|
|||||||
pd.DataFrame
|
pd.DataFrame
|
||||||
DataFrame with factor names and metrics
|
DataFrame with factor names and metrics
|
||||||
"""
|
"""
|
||||||
# Map shorthand to full column name
|
_ALLOWED_METRICS = frozenset({
|
||||||
|
'sharpe', 'ic', 'annual_return', 'max_drawdown',
|
||||||
|
'win_rate', 'information_ratio', 'volatility',
|
||||||
|
})
|
||||||
metric_map = {
|
metric_map = {
|
||||||
'sharpe': 'sharpe',
|
'sharpe': 'sharpe', 'ic': 'ic', 'return': 'annual_return',
|
||||||
'ic': 'ic',
|
'drawdown': 'max_drawdown', 'win_rate': 'win_rate',
|
||||||
'return': 'annual_return',
|
|
||||||
'drawdown': 'max_drawdown',
|
|
||||||
'win_rate': 'win_rate',
|
|
||||||
'information_ratio': 'information_ratio',
|
'information_ratio': 'information_ratio',
|
||||||
}
|
}
|
||||||
col = metric_map.get(metric, metric)
|
col = metric_map.get(metric, metric)
|
||||||
|
if col not in _ALLOWED_METRICS:
|
||||||
|
raise ValueError(f"Unknown metric: {metric!r}")
|
||||||
|
|
||||||
return pd.read_sql_query(
|
return pd.read_sql_query(
|
||||||
f"""SELECT factor_name, ic, sharpe, annual_return, max_drawdown,
|
f"""SELECT factor_name, ic, sharpe, annual_return, max_drawdown,
|
||||||
@@ -201,7 +210,7 @@ class ResultsDatabase:
|
|||||||
JOIN factors ON factor_id = factors.id
|
JOIN factors ON factor_id = factors.id
|
||||||
WHERE {col} IS NOT NULL
|
WHERE {col} IS NOT NULL
|
||||||
ORDER BY {col} DESC
|
ORDER BY {col} DESC
|
||||||
LIMIT ?""",
|
LIMIT ?""", # nosec B608 — col is validated against _ALLOWED_METRICS above
|
||||||
self.conn,
|
self.conn,
|
||||||
params=[limit]
|
params=[limit]
|
||||||
)
|
)
|
||||||
@@ -321,13 +330,13 @@ class ResultsDatabase:
|
|||||||
worst_drawdown = all_results['max_drawdown'].min() if total_runs > 0 and all_results['max_drawdown'].notna().any() else None
|
worst_drawdown = all_results['max_drawdown'].min() if total_runs > 0 and all_results['max_drawdown'].notna().any() else None
|
||||||
|
|
||||||
# Scan factors directory for JSON files
|
# Scan factors directory for JSON files
|
||||||
factors_dir = Path(__file__).parent.parent.parent / "results" / "factors"
|
factors_dir = Path(__file__).parent.parent.parent.parent / "results" / "factors"
|
||||||
json_factor_files = 0
|
json_factor_files = 0
|
||||||
if factors_dir.exists():
|
if factors_dir.exists():
|
||||||
json_factor_files = len(list(factors_dir.glob("*.json")))
|
json_factor_files = len(list(factors_dir.glob("*.json")))
|
||||||
|
|
||||||
# Scan failed runs
|
# Scan failed runs
|
||||||
failed_dir = Path(__file__).parent.parent.parent / "results" / "failed_runs"
|
failed_dir = Path(__file__).parent.parent.parent.parent / "results" / "failed_runs"
|
||||||
failed_runs_file = failed_dir / "failed_runs.json"
|
failed_runs_file = failed_dir / "failed_runs.json"
|
||||||
failed_runs_count = 0
|
failed_runs_count = 0
|
||||||
failed_runs_data = []
|
failed_runs_data = []
|
||||||
@@ -400,7 +409,7 @@ class ResultsDatabase:
|
|||||||
worst_dd_str = self._fmt_float(best['worst_drawdown'], ".4f")
|
worst_dd_str = self._fmt_float(best['worst_drawdown'], ".4f")
|
||||||
|
|
||||||
md_lines = [
|
md_lines = [
|
||||||
"# Predix Results Summary",
|
"# NexQuant Results Summary",
|
||||||
"",
|
"",
|
||||||
f"**Generated:** {summary['generated_at']}",
|
f"**Generated:** {summary['generated_at']}",
|
||||||
f"**Database:** `{summary['database_path']}`",
|
f"**Database:** `{summary['database_path']}`",
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
"""
|
"""
|
||||||
Predix Risk Management - Korrelation, Portfolio-Optimierung
|
NexQuant Risk Management - Korrelation, Portfolio-Optimierung
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, List, Optional
|
|
||||||
from datetime import datetime
|
|
||||||
import json
|
|
||||||
|
|
||||||
class CorrelationAnalyzer:
|
class CorrelationAnalyzer:
|
||||||
def __init__(self, lookback: int = 60):
|
def __init__(self, lookback: int = 60):
|
||||||
@@ -15,7 +13,7 @@ class CorrelationAnalyzer:
|
|||||||
def calculate_matrix(self, returns: pd.DataFrame) -> pd.DataFrame:
|
def calculate_matrix(self, returns: pd.DataFrame) -> pd.DataFrame:
|
||||||
return returns.dropna().corr()
|
return returns.dropna().corr()
|
||||||
|
|
||||||
def find_uncorrelated(self, corr: pd.DataFrame, threshold: float = 0.3) -> List[str]:
|
def find_uncorrelated(self, corr: pd.DataFrame, threshold: float = 0.3) -> list[str]:
|
||||||
result = []
|
result = []
|
||||||
for f in corr.columns:
|
for f in corr.columns:
|
||||||
others = [x for x in corr.columns if x != f]
|
others = [x for x in corr.columns if x != f]
|
||||||
@@ -28,7 +26,7 @@ class PortfolioOptimizer:
|
|||||||
try:
|
try:
|
||||||
w = np.linalg.inv(cov.values) @ exp_ret.values
|
w = np.linalg.inv(cov.values) @ exp_ret.values
|
||||||
return w / np.sum(w)
|
return w / np.sum(w)
|
||||||
except:
|
except (np.linalg.LinAlgError, ValueError):
|
||||||
return np.ones(len(exp_ret)) / len(exp_ret)
|
return np.ones(len(exp_ret)) / len(exp_ret)
|
||||||
|
|
||||||
def risk_parity(self, cov: pd.DataFrame, max_iter: int = 100) -> np.ndarray:
|
def risk_parity(self, cov: pd.DataFrame, max_iter: int = 100) -> np.ndarray:
|
||||||
@@ -54,17 +52,17 @@ class AdvancedRiskManager:
|
|||||||
self.corr_analyzer = CorrelationAnalyzer()
|
self.corr_analyzer = CorrelationAnalyzer()
|
||||||
self.optimizer = PortfolioOptimizer()
|
self.optimizer = PortfolioOptimizer()
|
||||||
|
|
||||||
def check_limits(self, weights: np.ndarray, vol: float, dd: float) -> Dict[str, bool]:
|
def check_limits(self, weights: np.ndarray, vol: float, dd: float) -> dict[str, bool]:
|
||||||
return {
|
return {
|
||||||
'position_limit': np.max(np.abs(weights)) <= self.max_pos,
|
"position_limit": np.max(np.abs(weights)) <= self.max_pos,
|
||||||
'leverage_limit': np.sum(np.abs(weights)) <= self.max_lev,
|
"leverage_limit": np.sum(np.abs(weights)) <= self.max_lev,
|
||||||
'drawdown_limit': abs(dd) <= self.max_dd,
|
"drawdown_limit": abs(dd) <= self.max_dd,
|
||||||
}
|
}
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print("=== Risk Test ===")
|
print("=== Risk Test ===")
|
||||||
np.random.seed(42)
|
np.random.seed(42)
|
||||||
n, names = 252, ['Mom', 'MeanRev', 'Vol', 'Volu', 'ML']
|
n, names = 252, ["Mom", "MeanRev", "Vol", "Volu", "ML"]
|
||||||
ret = pd.DataFrame(np.random.randn(n, 5), columns=names)
|
ret = pd.DataFrame(np.random.randn(n, 5), columns=names)
|
||||||
|
|
||||||
corr = CorrelationAnalyzer().calculate_matrix(ret)
|
corr = CorrelationAnalyzer().calculate_matrix(ret)
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
Unified, verifiable backtesting engine.
|
Unified, verifiable backtesting engine.
|
||||||
|
|
||||||
Single entry point (`backtest_signal`) used by:
|
Single entry point (`backtest_signal`) used by:
|
||||||
- scripts/predix_gen_strategies_real_bt.py
|
- scripts/nexquant_gen_strategies_real_bt.py
|
||||||
- rdagent/components/coder/strategy_orchestrator.py
|
- rdagent/scenarios/qlib/local/strategy_orchestrator.py
|
||||||
- rdagent/components/coder/optuna_optimizer.py
|
- rdagent/scenarios/qlib/local/optuna_optimizer.py
|
||||||
- rdagent/components/backtesting/backtest_engine.py
|
- rdagent/components/backtesting/backtest_engine.py
|
||||||
|
|
||||||
Design goals
|
Design goals
|
||||||
@@ -19,7 +19,7 @@ Design goals
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@@ -38,15 +38,15 @@ DEFAULT_TXN_COST_BPS = 2.14
|
|||||||
DEFAULT_BARS_PER_YEAR = 252 * 1440 # 252 trading days * 1440 min/day = 362,880
|
DEFAULT_BARS_PER_YEAR = 252 * 1440 # 252 trading days * 1440 min/day = 362,880
|
||||||
EXTREME_BAR_THRESHOLD = 0.05 # |ret| > 5% on a single 1-min bar → suspicious
|
EXTREME_BAR_THRESHOLD = 0.05 # |ret| > 5% on a single 1-min bar → suspicious
|
||||||
|
|
||||||
# FTMO 100k account rules (enforced in backtest_signal when ftmo=True)
|
# RiskMgmt 100k account rules (enforced in backtest_signal when riskmgmt=True)
|
||||||
FTMO_INITIAL_CAPITAL = 100_000.0
|
INITIAL_CAPITAL = 100_000.0
|
||||||
FTMO_MAX_DAILY_LOSS = 0.05 # 5% of initial → block new trades rest of day
|
MAX_DAILY_LOSS = 0.05 # 5% of initial → block new trades rest of day
|
||||||
FTMO_MAX_TOTAL_LOSS = 0.10 # 10% of initial → simulation ends
|
MAX_TOTAL_LOSS = 0.10 # 10% of initial → simulation ends
|
||||||
# Risk-based position sizing: 0.5% equity risk per trade, 10-pip stop, max 1:30 leverage
|
# Risk-based position sizing: 1.5% equity risk per trade, 10-pip stop, max 1:30 leverage
|
||||||
FTMO_RISK_PER_TRADE = 0.005
|
RISK_PER_TRADE = 0.015
|
||||||
FTMO_STOP_PIPS = 10
|
STOP_PIPS = 10
|
||||||
FTMO_PIP = 0.0001
|
PIP_SIZE = 0.0001
|
||||||
FTMO_MAX_LEVERAGE = 30
|
MAX_LEVERAGE = 30
|
||||||
|
|
||||||
|
|
||||||
def _compute_trade_pnl(position: pd.Series, strategy_returns: pd.Series) -> pd.Series:
|
def _compute_trade_pnl(position: pd.Series, strategy_returns: pd.Series) -> pd.Series:
|
||||||
@@ -67,9 +67,8 @@ def _cross_check_with_vbt(
|
|||||||
close: pd.Series,
|
close: pd.Series,
|
||||||
position: pd.Series,
|
position: pd.Series,
|
||||||
txn_cost: float,
|
txn_cost: float,
|
||||||
manual_total_return: float,
|
|
||||||
freq: str,
|
freq: str,
|
||||||
) -> Optional[float]:
|
) -> float | None:
|
||||||
"""Run a vectorbt simulation and return its total_return for comparison."""
|
"""Run a vectorbt simulation and return its total_return for comparison."""
|
||||||
if not VBT_AVAILABLE:
|
if not VBT_AVAILABLE:
|
||||||
return None
|
return None
|
||||||
@@ -84,7 +83,8 @@ def _cross_check_with_vbt(
|
|||||||
init_cash=10_000.0,
|
init_cash=10_000.0,
|
||||||
freq=freq,
|
freq=freq,
|
||||||
)
|
)
|
||||||
return float(pf.total_return())
|
tr = float(pf.total_return())
|
||||||
|
return tr if np.isfinite(tr) else None
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -95,9 +95,9 @@ def backtest_signal(
|
|||||||
txn_cost_bps: float = DEFAULT_TXN_COST_BPS,
|
txn_cost_bps: float = DEFAULT_TXN_COST_BPS,
|
||||||
freq: str = "1min",
|
freq: str = "1min",
|
||||||
bars_per_year: int = DEFAULT_BARS_PER_YEAR,
|
bars_per_year: int = DEFAULT_BARS_PER_YEAR,
|
||||||
forward_returns: Optional[pd.Series] = None,
|
forward_returns: pd.Series | None = None,
|
||||||
cross_check: bool = False,
|
cross_check: bool = False,
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Run a single-asset backtest from a position signal.
|
Run a single-asset backtest from a position signal.
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ def backtest_signal(
|
|||||||
calmar = ann_return_arith / abs(max_dd) if max_dd < 0 else 0.0
|
calmar = ann_return_arith / abs(max_dd) if max_dd < 0 else 0.0
|
||||||
|
|
||||||
trade_pnl = _compute_trade_pnl(position, strategy_returns)
|
trade_pnl = _compute_trade_pnl(position, strategy_returns)
|
||||||
n_trades = int(len(trade_pnl))
|
n_trades = len(trade_pnl)
|
||||||
n_position_changes = int((position.diff().fillna(0) != 0).sum())
|
n_position_changes = int((position.diff().fillna(0) != 0).sum())
|
||||||
|
|
||||||
if n_trades > 0:
|
if n_trades > 0:
|
||||||
@@ -216,7 +216,7 @@ def backtest_signal(
|
|||||||
win_rate = 0.0
|
win_rate = 0.0
|
||||||
profit_factor = 0.0
|
profit_factor = 0.0
|
||||||
|
|
||||||
ic: Optional[float] = None
|
ic: float | None = None
|
||||||
if forward_returns is not None:
|
if forward_returns is not None:
|
||||||
fwd = pd.to_numeric(forward_returns, errors="coerce")
|
fwd = pd.to_numeric(forward_returns, errors="coerce")
|
||||||
common = signal.index.intersection(fwd.dropna().index)
|
common = signal.index.intersection(fwd.dropna().index)
|
||||||
@@ -227,7 +227,7 @@ def backtest_signal(
|
|||||||
ic_val = float(s.corr(f))
|
ic_val = float(s.corr(f))
|
||||||
ic = ic_val if np.isfinite(ic_val) else None
|
ic = ic_val if np.isfinite(ic_val) else None
|
||||||
|
|
||||||
result: Dict[str, Any] = {
|
result: dict[str, Any] = {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"sharpe": sharpe,
|
"sharpe": sharpe,
|
||||||
"sortino": sortino,
|
"sortino": sortino,
|
||||||
@@ -244,7 +244,7 @@ def backtest_signal(
|
|||||||
"volatility": volatility,
|
"volatility": volatility,
|
||||||
"n_trades": n_trades,
|
"n_trades": n_trades,
|
||||||
"n_position_changes": n_position_changes,
|
"n_position_changes": n_position_changes,
|
||||||
"n_bars": int(len(strategy_returns)),
|
"n_bars": len(strategy_returns),
|
||||||
"n_months": float(n_months),
|
"n_months": float(n_months),
|
||||||
"signal_long": int((signal > 0).sum()),
|
"signal_long": int((signal > 0).sum()),
|
||||||
"signal_short": int((signal < 0).sum()),
|
"signal_short": int((signal < 0).sum()),
|
||||||
@@ -264,38 +264,41 @@ def backtest_signal(
|
|||||||
close=close,
|
close=close,
|
||||||
position=position,
|
position=position,
|
||||||
txn_cost=txn_cost,
|
txn_cost=txn_cost,
|
||||||
manual_total_return=total_return,
|
|
||||||
freq=freq,
|
freq=freq,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from rdagent.components.backtesting.verify import verify_and_log
|
||||||
|
|
||||||
|
verify_and_log(result, factor_name="backtest_signal")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def _apply_ftmo_mask(
|
def _apply_risk_mask(
|
||||||
signal: pd.Series,
|
signal: pd.Series,
|
||||||
close: pd.Series,
|
close: pd.Series,
|
||||||
leverage: float,
|
leverage: float,
|
||||||
txn_cost_bps: float,
|
txn_cost_bps: float,
|
||||||
) -> tuple[pd.Series, dict]:
|
) -> tuple[pd.Series, dict]:
|
||||||
"""
|
"""
|
||||||
Apply FTMO daily/total loss rules to a signal series.
|
Apply RiskMgmt daily/total loss rules to a signal series.
|
||||||
|
|
||||||
Returns a masked signal (positions zeroed after each limit breach) and
|
Returns a masked signal (positions zeroed after each limit breach) and
|
||||||
a dict of FTMO compliance metrics.
|
a dict of RiskMgmt compliance metrics.
|
||||||
"""
|
"""
|
||||||
txn_cost = txn_cost_bps / 10_000.0
|
txn_cost = txn_cost_bps / 10_000.0
|
||||||
position = signal.shift(1).fillna(0) * leverage
|
position = signal.shift(1).fillna(0) * leverage
|
||||||
bar_ret = close.pct_change().fillna(0)
|
bar_ret = close.pct_change().fillna(0)
|
||||||
|
|
||||||
equity = FTMO_INITIAL_CAPITAL
|
equity = INITIAL_CAPITAL
|
||||||
peak_day = FTMO_INITIAL_CAPITAL
|
peak_day = INITIAL_CAPITAL
|
||||||
masked = signal.copy()
|
masked = signal.copy()
|
||||||
|
|
||||||
daily_breaches = 0
|
daily_breaches = 0
|
||||||
total_breached = False
|
total_breached = False
|
||||||
total_breach_ts: Optional[pd.Timestamp] = None
|
total_breach_ts: pd.Timestamp | None = None
|
||||||
current_day = None
|
current_day = None
|
||||||
day_start_eq = FTMO_INITIAL_CAPITAL
|
day_start_eq = INITIAL_CAPITAL
|
||||||
|
|
||||||
pos_prev = 0.0
|
pos_prev = 0.0
|
||||||
for ts, sig_i in signal.items():
|
for ts, sig_i in signal.items():
|
||||||
@@ -308,42 +311,39 @@ def _apply_ftmo_mask(
|
|||||||
pos_i = float(signal.at[ts]) * leverage
|
pos_i = float(signal.at[ts]) * leverage
|
||||||
ret_i = float(bar_ret.get(ts, 0.0))
|
ret_i = float(bar_ret.get(ts, 0.0))
|
||||||
cost_i = abs(pos_i - pos_prev) * txn_cost
|
cost_i = abs(pos_i - pos_prev) * txn_cost
|
||||||
ret_net = pos_prev * ret_i - cost_i
|
ret_frac = pos_prev * ret_i - cost_i
|
||||||
equity = equity * (1.0 + ret_net / FTMO_INITIAL_CAPITAL * FTMO_INITIAL_CAPITAL / equity
|
equity *= 1.0 + ret_frac if equity > 0 else 1.0
|
||||||
if equity > 0 else 1.0)
|
|
||||||
# Simpler: track as fraction
|
|
||||||
equity += FTMO_INITIAL_CAPITAL * ret_net
|
|
||||||
pos_prev = pos_i
|
pos_prev = pos_i
|
||||||
|
|
||||||
if total_breached:
|
if total_breached:
|
||||||
masked.at[ts] = 0
|
masked.at[ts] = 0
|
||||||
continue
|
continue
|
||||||
|
|
||||||
daily_loss = (equity - day_start_eq) / FTMO_INITIAL_CAPITAL
|
daily_loss = (equity - day_start_eq) / INITIAL_CAPITAL
|
||||||
total_loss = (equity - FTMO_INITIAL_CAPITAL) / FTMO_INITIAL_CAPITAL
|
total_loss = (equity - INITIAL_CAPITAL) / INITIAL_CAPITAL
|
||||||
|
|
||||||
if daily_loss < -FTMO_MAX_DAILY_LOSS:
|
if daily_loss < -MAX_DAILY_LOSS:
|
||||||
daily_breaches += 1
|
daily_breaches += 1
|
||||||
day_start_eq = -999 # block rest of day
|
day_start_eq = -999 # block rest of day
|
||||||
masked.at[ts] = 0
|
masked.at[ts] = 0
|
||||||
|
|
||||||
if total_loss < -FTMO_MAX_TOTAL_LOSS:
|
if total_loss < -MAX_TOTAL_LOSS:
|
||||||
total_breached = True
|
total_breached = True
|
||||||
total_breach_ts = ts
|
total_breach_ts = ts
|
||||||
masked.at[ts] = 0
|
masked.at[ts] = 0
|
||||||
|
|
||||||
return masked, {
|
return masked, {
|
||||||
"ftmo_daily_breaches": daily_breaches,
|
"riskmgmt_daily_breaches": daily_breaches,
|
||||||
"ftmo_total_breached": total_breached,
|
"riskmgmt_total_breached": total_breached,
|
||||||
"ftmo_total_breach_ts": str(total_breach_ts) if total_breach_ts else None,
|
"riskmgmt_total_breach_ts": str(total_breach_ts) if total_breach_ts else None,
|
||||||
"ftmo_compliant": not total_breached and daily_breaches == 0,
|
"riskmgmt_compliant": not total_breached and daily_breaches == 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
OOS_START_DEFAULT = "2024-01-01"
|
OOS_START_DEFAULT = "2024-01-01"
|
||||||
|
|
||||||
# Rolling walk-forward default windows (IS years, OOS years, step years)
|
# Rolling walk-forward default windows (IS years, OOS years, step years)
|
||||||
WF_IS_YEARS = 3
|
WF_IS_YEARS = 1
|
||||||
WF_OOS_YEARS = 1
|
WF_OOS_YEARS = 1
|
||||||
WF_STEP_YEARS = 1
|
WF_STEP_YEARS = 1
|
||||||
|
|
||||||
@@ -399,11 +399,11 @@ def walk_forward_rolling(
|
|||||||
is_years: int = WF_IS_YEARS,
|
is_years: int = WF_IS_YEARS,
|
||||||
oos_years: int = WF_OOS_YEARS,
|
oos_years: int = WF_OOS_YEARS,
|
||||||
step_years: int = WF_STEP_YEARS,
|
step_years: int = WF_STEP_YEARS,
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Rolling walk-forward validation: multiple IS/OOS windows shifted by ``step_years``.
|
Rolling walk-forward validation: multiple IS/OOS windows shifted by ``step_years``.
|
||||||
|
|
||||||
Each window runs an independent FTMO simulation on the IS and OOS slices.
|
Each window runs an independent RiskMgmt simulation on the IS and OOS slices.
|
||||||
Produces aggregate OOS statistics to measure cross-time consistency.
|
Produces aggregate OOS statistics to measure cross-time consistency.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
@@ -433,7 +433,7 @@ def walk_forward_rolling(
|
|||||||
yr += step_years
|
yr += step_years
|
||||||
continue
|
continue
|
||||||
|
|
||||||
window: Dict[str, Any] = {
|
window: dict[str, Any] = {
|
||||||
"is_start": str(is_start.date()),
|
"is_start": str(is_start.date()),
|
||||||
"is_end": str(is_end.date()),
|
"is_end": str(is_end.date()),
|
||||||
"oos_start": str(is_end.date()),
|
"oos_start": str(is_end.date()),
|
||||||
@@ -442,7 +442,7 @@ def walk_forward_rolling(
|
|||||||
for mask, prefix in [(is_mask, "is"), (oos_mask, "oos")]:
|
for mask, prefix in [(is_mask, "is"), (oos_mask, "oos")]:
|
||||||
close_s = close.loc[mask]
|
close_s = close.loc[mask]
|
||||||
signal_s = signal.loc[mask]
|
signal_s = signal.loc[mask]
|
||||||
masked_s, _ = _apply_ftmo_mask(signal_s, close_s, leverage, txn_cost_bps)
|
masked_s, _ = _apply_risk_mask(signal_s, close_s, leverage, txn_cost_bps)
|
||||||
r = backtest_signal(close=close_s, signal=masked_s,
|
r = backtest_signal(close=close_s, signal=masked_s,
|
||||||
txn_cost_bps=txn_cost_bps, bars_per_year=bars_per_year)
|
txn_cost_bps=txn_cost_bps, bars_per_year=bars_per_year)
|
||||||
window[f"{prefix}_sharpe"] = r.get("sharpe", 0.0)
|
window[f"{prefix}_sharpe"] = r.get("sharpe", 0.0)
|
||||||
@@ -466,30 +466,30 @@ def walk_forward_rolling(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def backtest_signal_ftmo(
|
def backtest_signal_risk(
|
||||||
close: pd.Series,
|
close: pd.Series,
|
||||||
signal: pd.Series,
|
signal: pd.Series,
|
||||||
txn_cost_bps: float = DEFAULT_TXN_COST_BPS,
|
txn_cost_bps: float = DEFAULT_TXN_COST_BPS,
|
||||||
eurusd_price: float = 1.10,
|
eurusd_price: float = 1.10,
|
||||||
risk_pct: float = FTMO_RISK_PER_TRADE,
|
risk_pct: float = RISK_PER_TRADE,
|
||||||
stop_pips: float = FTMO_STOP_PIPS,
|
stop_pips: float = STOP_PIPS,
|
||||||
max_leverage: float = FTMO_MAX_LEVERAGE,
|
max_leverage: float = MAX_LEVERAGE,
|
||||||
bars_per_year: int = DEFAULT_BARS_PER_YEAR,
|
bars_per_year: int = DEFAULT_BARS_PER_YEAR,
|
||||||
forward_returns: Optional[pd.Series] = None,
|
forward_returns: pd.Series | None = None,
|
||||||
oos_start: Optional[str] = OOS_START_DEFAULT,
|
oos_start: str | None = OOS_START_DEFAULT,
|
||||||
wf_rolling: bool = False,
|
wf_rolling: bool = True,
|
||||||
mc_n_permutations: int = 0,
|
mc_n_permutations: int = 0,
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
FTMO-compliant backtest of a strategy signal on EUR/USD.
|
RiskMgmt-compliant backtest of a strategy signal on EUR/USD.
|
||||||
|
|
||||||
Applies on top of ``backtest_signal``:
|
Applies on top of ``backtest_signal``:
|
||||||
- Realistic costs: default 2.14 bps (≈ 2.35 pip spread+slippage+commission)
|
- Realistic costs: default 2.14 bps (≈ 2.35 pip spread+slippage+commission)
|
||||||
- Risk-based position sizing: risk_pct equity per trade, stop_pips hard stop
|
- Risk-based position sizing: risk_pct equity per trade, stop_pips hard stop
|
||||||
- Max leverage cap: max_leverage (default 1:30, FTMO standard)
|
- Max leverage cap: max_leverage (default 1:30, RiskMgmt standard)
|
||||||
- FTMO daily loss limit (5%): positions zeroed rest of day after breach
|
- RiskMgmt daily loss limit (5%): positions zeroed rest of day after breach
|
||||||
- FTMO total loss limit (10%): all positions zeroed after breach
|
- RiskMgmt total loss limit (10%): all positions zeroed after breach
|
||||||
- FTMO-specific metrics added to result dict
|
- RiskMgmt-specific metrics added to result dict
|
||||||
- Walk-forward OOS split: IS metrics (before oos_start) + OOS metrics (after)
|
- Walk-forward OOS split: IS metrics (before oos_start) + OOS metrics (after)
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -507,7 +507,7 @@ def backtest_signal_ftmo(
|
|||||||
stop_pips : float
|
stop_pips : float
|
||||||
Hard stop-loss distance in pips (default 10).
|
Hard stop-loss distance in pips (default 10).
|
||||||
max_leverage : float
|
max_leverage : float
|
||||||
Maximum leverage (default 30 = FTMO 1:30).
|
Maximum leverage (default 30 = RiskMgmt 1:30).
|
||||||
oos_start : str or None
|
oos_start : str or None
|
||||||
Start of out-of-sample period (ISO date). None disables OOS split.
|
Start of out-of-sample period (ISO date). None disables OOS split.
|
||||||
wf_rolling : bool
|
wf_rolling : bool
|
||||||
@@ -518,11 +518,11 @@ def backtest_signal_ftmo(
|
|||||||
When > 0, computes ``mc_pvalue``: fraction of permuted sequences whose
|
When > 0, computes ``mc_pvalue``: fraction of permuted sequences whose
|
||||||
total return >= real total return. p < 0.05 indicates a genuine edge.
|
total return >= real total return. p < 0.05 indicates a genuine edge.
|
||||||
"""
|
"""
|
||||||
stop_price = stop_pips * FTMO_PIP
|
stop_price = stop_pips * PIP_SIZE
|
||||||
leverage_by_risk = risk_pct / (stop_price / eurusd_price)
|
leverage_by_risk = risk_pct / (stop_price / eurusd_price)
|
||||||
leverage = min(leverage_by_risk, max_leverage)
|
leverage = min(leverage_by_risk, max_leverage)
|
||||||
|
|
||||||
masked_signal, ftmo_metrics = _apply_ftmo_mask(signal, close, leverage, txn_cost_bps)
|
masked_signal, risk_metrics = _apply_risk_mask(signal, close, leverage, txn_cost_bps)
|
||||||
|
|
||||||
result = backtest_signal(
|
result = backtest_signal(
|
||||||
close=close,
|
close=close,
|
||||||
@@ -532,14 +532,14 @@ def backtest_signal_ftmo(
|
|||||||
forward_returns=forward_returns,
|
forward_returns=forward_returns,
|
||||||
)
|
)
|
||||||
|
|
||||||
result.update(ftmo_metrics)
|
result.update(risk_metrics)
|
||||||
result["ftmo_leverage"] = round(leverage, 2)
|
result["riskmgmt_leverage"] = round(leverage, 2)
|
||||||
result["ftmo_risk_pct"] = risk_pct
|
result["riskmgmt_risk_pct"] = risk_pct
|
||||||
result["ftmo_stop_pips"] = stop_pips
|
result["riskmgmt_stop_pips"] = stop_pips
|
||||||
|
|
||||||
# Re-scale reported equity metrics to FTMO_INITIAL_CAPITAL
|
# Re-scale reported equity metrics to INITIAL_CAPITAL
|
||||||
result["ftmo_end_equity"] = FTMO_INITIAL_CAPITAL * (1 + result.get("total_return", 0))
|
result["riskmgmt_end_equity"] = INITIAL_CAPITAL * (1 + result.get("total_return", 0))
|
||||||
result["ftmo_monthly_profit"] = FTMO_INITIAL_CAPITAL * result.get("monthly_return", 0)
|
result["riskmgmt_monthly_profit"] = INITIAL_CAPITAL * result.get("monthly_return", 0)
|
||||||
|
|
||||||
# Walk-forward OOS split
|
# Walk-forward OOS split
|
||||||
if oos_start is not None:
|
if oos_start is not None:
|
||||||
@@ -547,13 +547,13 @@ def backtest_signal_ftmo(
|
|||||||
is_mask = close.index < oos_ts
|
is_mask = close.index < oos_ts
|
||||||
oos_mask = close.index >= oos_ts
|
oos_mask = close.index >= oos_ts
|
||||||
|
|
||||||
def _split_bt(mask: "pd.Series[bool]", prefix: str) -> None:
|
def _split_bt(mask: pd.Series[bool], prefix: str) -> None:
|
||||||
if mask.sum() < 100:
|
if mask.sum() < 100:
|
||||||
return
|
return
|
||||||
close_s = close.loc[mask]
|
close_s = close.loc[mask]
|
||||||
signal_s = signal.loc[mask] # raw signal, not masked — fresh FTMO sim per period
|
signal_s = signal.loc[mask] # raw signal, not masked — fresh RiskMgmt sim per period
|
||||||
fwd_split = forward_returns.loc[mask] if forward_returns is not None else None
|
fwd_split = forward_returns.loc[mask] if forward_returns is not None else None
|
||||||
masked_s, _ = _apply_ftmo_mask(signal_s, close_s, leverage, txn_cost_bps)
|
masked_s, _ = _apply_risk_mask(signal_s, close_s, leverage, txn_cost_bps)
|
||||||
split_result = backtest_signal(
|
split_result = backtest_signal(
|
||||||
close=close_s,
|
close=close_s,
|
||||||
signal=masked_s,
|
signal=masked_s,
|
||||||
@@ -594,6 +594,10 @@ def backtest_signal_ftmo(
|
|||||||
result["mc_pvalue"] = monte_carlo_trade_pvalue(trade_pnl, mc_n_permutations)
|
result["mc_pvalue"] = monte_carlo_trade_pvalue(trade_pnl, mc_n_permutations)
|
||||||
result["mc_n_permutations"] = mc_n_permutations
|
result["mc_n_permutations"] = mc_n_permutations
|
||||||
|
|
||||||
|
from rdagent.components.backtesting.verify import verify_and_log
|
||||||
|
|
||||||
|
verify_and_log(result, factor_name="backtest_from_forward_returns")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -602,7 +606,7 @@ def backtest_from_forward_returns(
|
|||||||
forward_returns: pd.Series,
|
forward_returns: pd.Series,
|
||||||
txn_cost_bps: float = DEFAULT_TXN_COST_BPS,
|
txn_cost_bps: float = DEFAULT_TXN_COST_BPS,
|
||||||
bars_per_year: int = DEFAULT_BARS_PER_YEAR,
|
bars_per_year: int = DEFAULT_BARS_PER_YEAR,
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Backtest a factor using sign(factor) as signal against forward returns.
|
Backtest a factor using sign(factor) as signal against forward returns.
|
||||||
|
|
||||||
@@ -640,7 +644,7 @@ def backtest_from_forward_returns(
|
|||||||
ic = ic_val if np.isfinite(ic_val) else 0.0
|
ic = ic_val if np.isfinite(ic_val) else 0.0
|
||||||
|
|
||||||
trade_pnl = _compute_trade_pnl(position, strategy_returns)
|
trade_pnl = _compute_trade_pnl(position, strategy_returns)
|
||||||
n_trades = int(len(trade_pnl))
|
n_trades = len(trade_pnl)
|
||||||
win_rate = float((trade_pnl > 0).mean()) if n_trades > 0 else 0.0
|
win_rate = float((trade_pnl > 0).mean()) if n_trades > 0 else 0.0
|
||||||
|
|
||||||
ann_return = float(strategy_returns.mean() * bars_per_year)
|
ann_return = float(strategy_returns.mean() * bars_per_year)
|
||||||
@@ -656,7 +660,7 @@ def backtest_from_forward_returns(
|
|||||||
"win_rate": win_rate,
|
"win_rate": win_rate,
|
||||||
"n_trades": n_trades,
|
"n_trades": n_trades,
|
||||||
"ic": ic,
|
"ic": ic,
|
||||||
"n_bars": int(len(strategy_returns)),
|
"n_bars": len(strategy_returns),
|
||||||
"txn_cost_bps": txn_cost_bps,
|
"txn_cost_bps": txn_cost_bps,
|
||||||
"bars_per_year": bars_per_year,
|
"bars_per_year": bars_per_year,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
"""Runtime backtest verification — fast sanity checks for every backtest result.
|
||||||
|
|
||||||
|
These checks run in <1ms and catch corrupted/flipped/missing metrics before they
|
||||||
|
propagate into the factor database. Called automatically by backtest_signal()
|
||||||
|
and backtest_from_forward_returns().
|
||||||
|
|
||||||
|
The same invariants are covered by 477 unit tests in test/qlib/.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
REQUIRED_KEYS = [
|
||||||
|
"sharpe",
|
||||||
|
"max_drawdown",
|
||||||
|
"win_rate",
|
||||||
|
"total_return",
|
||||||
|
"annual_return_pct",
|
||||||
|
"monthly_return_pct",
|
||||||
|
"n_trades",
|
||||||
|
"status",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def verify_backtest_result(result: dict) -> list[str]:
|
||||||
|
"""Run fast mathematical-invariant checks on a backtest result dict.
|
||||||
|
|
||||||
|
Returns a list of warning strings (empty = all good).
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
result : dict
|
||||||
|
Output of ``backtest_signal()`` or ``backtest_from_forward_returns()``.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
list[str]
|
||||||
|
Warning messages for any failed check.
|
||||||
|
"""
|
||||||
|
warnings: list[str] = []
|
||||||
|
|
||||||
|
# ── 1. Required keys present ──
|
||||||
|
for key in REQUIRED_KEYS:
|
||||||
|
if key not in result:
|
||||||
|
warnings.append(f"Missing key: {key}")
|
||||||
|
return warnings # can't check further
|
||||||
|
|
||||||
|
# ── 2. MaxDD must be in [-1, 0] ──
|
||||||
|
mdd = result["max_drawdown"]
|
||||||
|
if not (-1.0 <= mdd <= 0.0):
|
||||||
|
warnings.append(f"max_drawdown {mdd:.4f} outside valid range [-1, 0]")
|
||||||
|
|
||||||
|
# ── 3. Win rate in [0, 1] ──
|
||||||
|
wr = result["win_rate"]
|
||||||
|
if not (0.0 <= wr <= 1.0):
|
||||||
|
warnings.append(f"win_rate {wr:.4f} outside valid range [0, 1]")
|
||||||
|
|
||||||
|
# ── 4. Sharpe must be finite ──
|
||||||
|
sharpe = result["sharpe"]
|
||||||
|
if not np.isfinite(sharpe):
|
||||||
|
warnings.append(f"sharpe is not finite: {sharpe}")
|
||||||
|
|
||||||
|
# ── 5. total_return finite ──
|
||||||
|
tr = result["total_return"]
|
||||||
|
if not np.isfinite(tr):
|
||||||
|
warnings.append(f"total_return is not finite: {tr}")
|
||||||
|
|
||||||
|
# ── 6. n_trades >= 0 ──
|
||||||
|
nt = result["n_trades"]
|
||||||
|
if nt < 0:
|
||||||
|
warnings.append(f"n_trades is negative: {nt}")
|
||||||
|
|
||||||
|
# ── 7. Annual return consistent with total return ──
|
||||||
|
ar = result["annual_return_pct"]
|
||||||
|
if not np.isfinite(ar):
|
||||||
|
warnings.append(f"annual_return_pct is not finite: {ar}")
|
||||||
|
|
||||||
|
# ── 8. Monthly return consistent with total return ──
|
||||||
|
mr = result["monthly_return_pct"]
|
||||||
|
if mr is not None and not np.isfinite(mr):
|
||||||
|
warnings.append(f"monthly_return_pct is not finite: {mr}")
|
||||||
|
|
||||||
|
# ── 9. Sharpe sign matches annual return sign (with 0-cost approximation) ──
|
||||||
|
if abs(sharpe) > 0.01 and abs(ar) > 0.01:
|
||||||
|
if np.sign(sharpe) != np.sign(ar):
|
||||||
|
warnings.append(
|
||||||
|
f"Sharpe ({sharpe:.4f}) and annual_return_pct ({ar:.4f}) have opposite signs"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── 10. status must be 'success' or 'failed' ──
|
||||||
|
if result["status"] not in ("success", "failed"):
|
||||||
|
warnings.append(f"status is not 'success' or 'failed': {result['status']}")
|
||||||
|
|
||||||
|
return warnings
|
||||||
|
|
||||||
|
|
||||||
|
def verify_and_log(result: dict, factor_name: str = "unknown") -> bool:
|
||||||
|
"""Verify backtest result and log any warnings.
|
||||||
|
|
||||||
|
Returns True if all checks passed.
|
||||||
|
"""
|
||||||
|
warnings = verify_backtest_result(result)
|
||||||
|
if warnings:
|
||||||
|
for w in warnings:
|
||||||
|
logger.warning(f"[BacktestVerify] [{factor_name[:60]}] {w}")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
@@ -75,8 +75,10 @@ class CoSTEER(Developer[Experiment]):
|
|||||||
|
|
||||||
def _get_last_fb(self) -> CoSTEERMultiFeedback:
|
def _get_last_fb(self) -> CoSTEERMultiFeedback:
|
||||||
fb = self.evolve_agent.evolving_trace[-1].feedback
|
fb = self.evolve_agent.evolving_trace[-1].feedback
|
||||||
assert fb is not None, "feedback is None"
|
if fb is None:
|
||||||
assert isinstance(fb, CoSTEERMultiFeedback), "feedback must be of type CoSTEERMultiFeedback"
|
raise AssertionError("feedback is None")
|
||||||
|
if not isinstance(fb, CoSTEERMultiFeedback):
|
||||||
|
raise TypeError("feedback must be of type CoSTEERMultiFeedback")
|
||||||
return fb
|
return fb
|
||||||
|
|
||||||
def should_use_new_evo(self, base_fb: CoSTEERMultiFeedback | None, new_fb: CoSTEERMultiFeedback) -> bool:
|
def should_use_new_evo(self, base_fb: CoSTEERMultiFeedback | None, new_fb: CoSTEERMultiFeedback) -> bool:
|
||||||
@@ -121,7 +123,8 @@ class CoSTEER(Developer[Experiment]):
|
|||||||
|
|
||||||
for evo_exp in self.evolve_agent.multistep_evolve(evo_exp, self.evaluator):
|
for evo_exp in self.evolve_agent.multistep_evolve(evo_exp, self.evaluator):
|
||||||
iteration_count += 1
|
iteration_count += 1
|
||||||
assert isinstance(evo_exp, Experiment) # multiple inheritance
|
if not isinstance(evo_exp, Experiment):
|
||||||
|
raise TypeError("evo_exp must be an instance of Experiment")
|
||||||
evo_fb = self._get_last_fb()
|
evo_fb = self._get_last_fb()
|
||||||
update_fallback = self.should_use_new_evo(
|
update_fallback = self.should_use_new_evo(
|
||||||
base_fb=fallback_evo_fb,
|
base_fb=fallback_evo_fb,
|
||||||
@@ -154,7 +157,8 @@ class CoSTEER(Developer[Experiment]):
|
|||||||
evo_exp = fallback_evo_exp
|
evo_exp = fallback_evo_exp
|
||||||
evo_exp.recover_ws_ckp()
|
evo_exp.recover_ws_ckp()
|
||||||
evo_fb = fallback_evo_fb
|
evo_fb = fallback_evo_fb
|
||||||
assert evo_fb is not None # multistep_evolve should run at least once
|
if evo_fb is None:
|
||||||
|
raise AssertionError("multistep_evolve should run at least once")
|
||||||
evo_exp = self._exp_postprocess_by_feedback(evo_exp, evo_fb)
|
evo_exp = self._exp_postprocess_by_feedback(evo_exp, evo_fb)
|
||||||
except CoderError as e:
|
except CoderError as e:
|
||||||
e.caused_by_timeout = reached_max_seconds
|
e.caused_by_timeout = reached_max_seconds
|
||||||
@@ -264,9 +268,12 @@ class CoSTEER(Developer[Experiment]):
|
|||||||
- Raise Error if it failed to handle the develop task
|
- Raise Error if it failed to handle the develop task
|
||||||
-
|
-
|
||||||
"""
|
"""
|
||||||
assert isinstance(evo, Experiment)
|
if not isinstance(evo, Experiment):
|
||||||
assert isinstance(feedback, CoSTEERMultiFeedback)
|
raise TypeError("evo must be an instance of Experiment")
|
||||||
assert len(evo.sub_workspace_list) == len(feedback)
|
if not isinstance(feedback, CoSTEERMultiFeedback):
|
||||||
|
raise TypeError("feedback must be an instance of CoSTEERMultiFeedback")
|
||||||
|
if len(evo.sub_workspace_list) != len(feedback):
|
||||||
|
raise ValueError("Length of sub_workspace_list must match length of feedback")
|
||||||
|
|
||||||
# FIXME: when whould the feedback be None?
|
# FIXME: when whould the feedback be None?
|
||||||
failed_feedbacks = [
|
failed_feedbacks = [
|
||||||
|
|||||||
@@ -122,7 +122,8 @@ class MultiProcessEvolvingStrategy(EvolvingStrategy):
|
|||||||
last_feedback = None
|
last_feedback = None
|
||||||
if len(evolving_trace) > 0:
|
if len(evolving_trace) > 0:
|
||||||
last_feedback = evolving_trace[-1].feedback
|
last_feedback = evolving_trace[-1].feedback
|
||||||
assert isinstance(last_feedback, CoSTEERMultiFeedback)
|
if not isinstance(last_feedback, CoSTEERMultiFeedback):
|
||||||
|
raise TypeError("last_feedback must be of type CoSTEERMultiFeedback")
|
||||||
|
|
||||||
# 1.找出需要evolve的task
|
# 1.找出需要evolve的task
|
||||||
to_be_finished_task_index: list[int] = []
|
to_be_finished_task_index: list[int] = []
|
||||||
|
|||||||
@@ -1028,7 +1028,8 @@ class CoSTEERKnowledgeBaseV2(EvolvingKnowledgeBase):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
node_count = len(nodes)
|
node_count = len(nodes)
|
||||||
assert node_count >= 2, "nodes length must >=2"
|
if node_count < 2:
|
||||||
|
raise ValueError("nodes length must >=2")
|
||||||
intersection_node_list = []
|
intersection_node_list = []
|
||||||
if output_intersection_origin:
|
if output_intersection_origin:
|
||||||
origin_list = []
|
origin_list = []
|
||||||
|
|||||||
@@ -54,7 +54,8 @@ def get_ds_env(
|
|||||||
ValueError: If the env_type is not recognized.
|
ValueError: If the env_type is not recognized.
|
||||||
"""
|
"""
|
||||||
conf = DSCoderCoSTEERSettings()
|
conf = DSCoderCoSTEERSettings()
|
||||||
assert conf_type in ["kaggle", "mlebench"], f"Unknown conf_type: {conf_type}"
|
if conf_type not in ["kaggle", "mlebench"]:
|
||||||
|
raise ValueError(f"Unknown conf_type: {conf_type}")
|
||||||
|
|
||||||
if conf.env_type == "docker":
|
if conf.env_type == "docker":
|
||||||
env_conf = DSDockerConf() if conf_type == "kaggle" else MLEBDockerConf()
|
env_conf = DSDockerConf() if conf_type == "kaggle" else MLEBDockerConf()
|
||||||
@@ -79,7 +80,8 @@ def get_clear_ws_cmd(stage: Literal["before_training", "before_inference"] = "be
|
|||||||
"""
|
"""
|
||||||
Clean the files in workspace to a specific stage
|
Clean the files in workspace to a specific stage
|
||||||
"""
|
"""
|
||||||
assert stage in ["before_training", "before_inference"], f"Unknown stage: {stage}"
|
if stage not in ["before_training", "before_inference"]:
|
||||||
|
raise ValueError(f"Unknown stage: {stage}")
|
||||||
if DS_RD_SETTING.enable_model_dump and stage == "before_training":
|
if DS_RD_SETTING.enable_model_dump and stage == "before_training":
|
||||||
cmd = "rm -r submission.csv scores.csv models trace.log"
|
cmd = "rm -r submission.csv scores.csv models trace.log"
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ File structure
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from jinja2 import Environment, StrictUndefined
|
from jinja2 import Environment, StrictUndefined, select_autoescape
|
||||||
|
|
||||||
from rdagent.app.data_science.conf import DS_RD_SETTING
|
from rdagent.app.data_science.conf import DS_RD_SETTING
|
||||||
from rdagent.components.coder.CoSTEER.evaluators import (
|
from rdagent.components.coder.CoSTEER.evaluators import (
|
||||||
@@ -88,7 +88,7 @@ class EnsembleMultiProcessEvolvingStrategy(MultiProcessEvolvingStrategy):
|
|||||||
code_spec = workspace.file_dict["spec/ensemble.md"]
|
code_spec = workspace.file_dict["spec/ensemble.md"]
|
||||||
else:
|
else:
|
||||||
test_code = (
|
test_code = (
|
||||||
Environment(undefined=StrictUndefined)
|
Environment(undefined=StrictUndefined, autoescape=select_autoescape())
|
||||||
.from_string((DIRNAME / "eval_tests" / "ensemble_test.txt").read_text())
|
.from_string((DIRNAME / "eval_tests" / "ensemble_test.txt").read_text())
|
||||||
.render(
|
.render(
|
||||||
model_names=[
|
model_names=[
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import json
|
|||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from jinja2 import Environment, StrictUndefined
|
from jinja2 import Environment, StrictUndefined, select_autoescape
|
||||||
|
|
||||||
from rdagent.app.data_science.conf import DS_RD_SETTING
|
from rdagent.app.data_science.conf import DS_RD_SETTING
|
||||||
from rdagent.components.coder.CoSTEER.evaluators import (
|
from rdagent.components.coder.CoSTEER.evaluators import (
|
||||||
@@ -55,7 +55,7 @@ class EnsembleCoSTEEREvaluator(CoSTEEREvaluator):
|
|||||||
fname = "test/ensemble_test.txt"
|
fname = "test/ensemble_test.txt"
|
||||||
test_code = (DIRNAME / "eval_tests" / "ensemble_test.txt").read_text()
|
test_code = (DIRNAME / "eval_tests" / "ensemble_test.txt").read_text()
|
||||||
test_code = (
|
test_code = (
|
||||||
Environment(undefined=StrictUndefined)
|
Environment(undefined=StrictUndefined, autoescape=select_autoescape())
|
||||||
.from_string(test_code)
|
.from_string(test_code)
|
||||||
.render(
|
.render(
|
||||||
model_names=[
|
model_names=[
|
||||||
|
|||||||
@@ -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:
|
This module intercepts LLM-generated factor code and automatically fixes known problems:
|
||||||
1. min_periods mismatch in rolling window calculations
|
1. min_periods mismatch in rolling window calculations
|
||||||
@@ -68,6 +68,7 @@ class FactorAutoFixer:
|
|||||||
self._fix_inf_nan_handling, # Tenth: add inf/nan handling
|
self._fix_inf_nan_handling, # Tenth: add inf/nan handling
|
||||||
self._fix_data_range_processing, # Eleventh: ensure full data range
|
self._fix_data_range_processing, # Eleventh: ensure full data range
|
||||||
self._fix_multiindex_groupby, # Twelfth: ensure groupby on MultiIndex
|
self._fix_multiindex_groupby, # Twelfth: ensure groupby on MultiIndex
|
||||||
|
self._fix_composite_normalization, # Thirteenth: normalize thresholds + composite variance
|
||||||
]
|
]
|
||||||
|
|
||||||
for fix_method in fix_methods:
|
for fix_method in fix_methods:
|
||||||
@@ -85,6 +86,24 @@ class FactorAutoFixer:
|
|||||||
|
|
||||||
return fixed_code
|
return fixed_code
|
||||||
|
|
||||||
|
def _fix_composite_normalization(self, code: str) -> str:
|
||||||
|
"""Normalize strategy code: cap thresholds, limit windows, normalize composite."""
|
||||||
|
code = re.sub(r'\bentry_thresh\s*=\s*([0-9.]+)',
|
||||||
|
lambda m: f'entry_thresh = {min(float(m.group(1)), 0.7):.1f}', code)
|
||||||
|
code = re.sub(r'\bexit_thresh\s*=\s*([0-9.]+)',
|
||||||
|
lambda m: f'exit_thresh = {min(float(m.group(1)), 0.3):.1f}', code)
|
||||||
|
code = re.sub(r'\bwindow\s*=\s*(\d+)',
|
||||||
|
lambda m: f'window = {min(int(m.group(1)), 20)}', code)
|
||||||
|
code = re.sub(r'(signal\s*=\s*signal\s*\.\s*rolling\s*\()(\d+)',
|
||||||
|
lambda m: f'{m.group(1)}{min(int(m.group(2)), 2)}', code)
|
||||||
|
if 'composite' in code and 'composite = (composite' not in code:
|
||||||
|
code = re.sub(
|
||||||
|
r'\n(signal\s*=\s*pd\.Series)',
|
||||||
|
r'\ncomposite = (composite - composite.rolling(20).mean()) / (composite.rolling(20).std() + 1e-8)\n\n\1',
|
||||||
|
code, count=1,
|
||||||
|
)
|
||||||
|
return code
|
||||||
|
|
||||||
def _fix_instrument_column_access(self, code: str) -> str:
|
def _fix_instrument_column_access(self, code: str) -> str:
|
||||||
"""
|
"""
|
||||||
Fix: df['instrument'] raises KeyError on a MultiIndex DataFrame because
|
Fix: df['instrument'] raises KeyError on a MultiIndex DataFrame because
|
||||||
|
|||||||
@@ -328,12 +328,13 @@ class FactorEqualValueRatioEvaluator(FactorEvaluator):
|
|||||||
"The source dataframe is None. Please check the implementation.",
|
"The source dataframe is None. Please check the implementation.",
|
||||||
-1,
|
-1,
|
||||||
)
|
)
|
||||||
|
acc_rate = -1
|
||||||
try:
|
try:
|
||||||
close_values = gen_df.sub(gt_df).abs().lt(1e-6)
|
close_values = gen_df.sub(gt_df).abs().lt(1e-6)
|
||||||
result_int = close_values.astype(int)
|
result_int = close_values.astype(int)
|
||||||
pos_num = result_int.sum().sum()
|
pos_num = result_int.sum().sum()
|
||||||
acc_rate = pos_num / close_values.size
|
acc_rate = pos_num / close_values.size
|
||||||
except:
|
except Exception:
|
||||||
close_values = gen_df
|
close_values = gen_df
|
||||||
if close_values.all().iloc[0]:
|
if close_values.all().iloc[0]:
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -161,8 +161,7 @@ class FactorFBWorkspace(FBWorkspace):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
subprocess.check_output(
|
subprocess.check_output(
|
||||||
f"{FACTOR_COSTEER_SETTINGS.python_bin} {execution_code_path}",
|
[FACTOR_COSTEER_SETTINGS.python_bin, str(execution_code_path)],
|
||||||
shell=True,
|
|
||||||
cwd=self.workspace_path,
|
cwd=self.workspace_path,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
timeout=FACTOR_COSTEER_SETTINGS.file_based_execution_timeout,
|
timeout=FACTOR_COSTEER_SETTINGS.file_based_execution_timeout,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ Two-step validation:
|
|||||||
2. Micro-batch testing - Runtime validation with small dataset
|
2. Micro-batch testing - Runtime validation with small dataset
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import ast
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
@@ -229,7 +230,7 @@ class LLMConfigValidator:
|
|||||||
final_metrics = re.search(r"\{'train_runtime':[^}]+\}", stdout)
|
final_metrics = re.search(r"\{'train_runtime':[^}]+\}", stdout)
|
||||||
if final_metrics:
|
if final_metrics:
|
||||||
try:
|
try:
|
||||||
metrics = eval(final_metrics.group(0)) # Safe: only numbers and strings
|
metrics = ast.literal_eval(final_metrics.group(0))
|
||||||
result["final_metrics"] = {
|
result["final_metrics"] = {
|
||||||
"train_loss": metrics.get("train_loss"),
|
"train_loss": metrics.get("train_loss"),
|
||||||
"train_runtime": metrics.get("train_runtime"),
|
"train_runtime": metrics.get("train_runtime"),
|
||||||
|
|||||||
@@ -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)
|
Wraps the Kronos-mini OHLCV foundation model (4.1M params, AAAI 2026, MIT)
|
||||||
for use as:
|
for use as:
|
||||||
@@ -55,8 +55,8 @@ def _ensure_kronos() -> bool:
|
|||||||
return _KRONOS_AVAILABLE
|
return _KRONOS_AVAILABLE
|
||||||
|
|
||||||
|
|
||||||
def _ohlcv_from_predix(df: pd.DataFrame) -> pd.DataFrame:
|
def _ohlcv_from_nexquant(df: pd.DataFrame) -> pd.DataFrame:
|
||||||
"""Convert Predix HDF5 format ($open/$close/...) to Kronos format (open/close/...)."""
|
"""Convert NexQuant HDF5 format ($open/$close/...) to Kronos format (open/close/...)."""
|
||||||
col_map = {"$open": "open", "$high": "high", "$low": "low", "$close": "close", "$volume": "volume"}
|
col_map = {"$open": "open", "$high": "high", "$low": "low", "$close": "close", "$volume": "volume"}
|
||||||
renamed = df.rename(columns=col_map)
|
renamed = df.rename(columns=col_map)
|
||||||
cols = [c for c in ["open", "high", "low", "close", "volume"] if c in renamed.columns]
|
cols = [c for c in ["open", "high", "low", "close", "volume"] if c in renamed.columns]
|
||||||
@@ -90,9 +90,19 @@ class KronosAdapter:
|
|||||||
MODEL_ID = "NeoQuasar/Kronos-mini"
|
MODEL_ID = "NeoQuasar/Kronos-mini"
|
||||||
TOKENIZER_ID = "NeoQuasar/Kronos-Tokenizer-2k"
|
TOKENIZER_ID = "NeoQuasar/Kronos-Tokenizer-2k"
|
||||||
|
|
||||||
def __init__(self, device: Optional[str] = None, max_context: int = 512):
|
# Mapping for larger Kronos variants
|
||||||
self.device = device or ("cuda" if _cuda_available() else "cpu")
|
_MODEL_MAP = {
|
||||||
|
"mini": ("NeoQuasar/Kronos-mini", "NeoQuasar/Kronos-Tokenizer-2k"),
|
||||||
|
"small": ("NeoQuasar/Kronos-small", "NeoQuasar/Kronos-Tokenizer-base"),
|
||||||
|
"base": ("NeoQuasar/Kronos-base", "NeoQuasar/Kronos-Tokenizer-base"),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, device: Optional[str] = None, max_context: int = 512, model_size: str = "mini"):
|
||||||
|
self.device = device or "cpu"
|
||||||
self.max_context = max_context
|
self.max_context = max_context
|
||||||
|
self.model_size = model_size
|
||||||
|
if model_size in self._MODEL_MAP:
|
||||||
|
self.MODEL_ID, self.TOKENIZER_ID = self._MODEL_MAP[model_size]
|
||||||
self._predictor = None
|
self._predictor = None
|
||||||
|
|
||||||
def load(self) -> "KronosAdapter":
|
def load(self) -> "KronosAdapter":
|
||||||
@@ -102,11 +112,11 @@ class KronosAdapter:
|
|||||||
raise RuntimeError("Kronos not available — see warning above.")
|
raise RuntimeError("Kronos not available — see warning above.")
|
||||||
from model import Kronos, KronosTokenizer, KronosPredictor # type: ignore
|
from model import Kronos, KronosTokenizer, KronosPredictor # type: ignore
|
||||||
|
|
||||||
logger.info(f"Loading Kronos-mini from HuggingFace ({self.MODEL_ID})...")
|
logger.info(f"Loading Kronos-{self.model_size} from HuggingFace ({self.MODEL_ID})...")
|
||||||
tokenizer = KronosTokenizer.from_pretrained(self.TOKENIZER_ID)
|
tokenizer = KronosTokenizer.from_pretrained(self.TOKENIZER_ID)
|
||||||
model = Kronos.from_pretrained(self.MODEL_ID)
|
model = Kronos.from_pretrained(self.MODEL_ID)
|
||||||
|
logger.info(f"Kronos-{self.model_size} loaded.")
|
||||||
self._predictor = KronosPredictor(model, tokenizer, device=self.device, max_context=self.max_context)
|
self._predictor = KronosPredictor(model, tokenizer, device=self.device, max_context=self.max_context)
|
||||||
logger.info("Kronos-mini loaded.")
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def predict_next_bars(
|
def predict_next_bars(
|
||||||
@@ -223,6 +233,7 @@ def build_kronos_factor(
|
|||||||
stride_bars: int = 96,
|
stride_bars: int = 96,
|
||||||
device: Optional[str] = None,
|
device: Optional[str] = None,
|
||||||
batch_size: int = 32,
|
batch_size: int = 32,
|
||||||
|
model_size: str = "mini",
|
||||||
) -> pd.DataFrame:
|
) -> pd.DataFrame:
|
||||||
"""
|
"""
|
||||||
Generate the Kronos predicted-return factor for all EUR/USD 1-min bars.
|
Generate the Kronos predicted-return factor for all EUR/USD 1-min bars.
|
||||||
@@ -236,15 +247,15 @@ def build_kronos_factor(
|
|||||||
Returns:
|
Returns:
|
||||||
MultiIndex (datetime, instrument) DataFrame with column "KronosPredReturn".
|
MultiIndex (datetime, instrument) DataFrame with column "KronosPredReturn".
|
||||||
"""
|
"""
|
||||||
device = device or ("cuda" if _cuda_available() else "cpu")
|
device = device or "cpu"
|
||||||
logger.info(f"Loading data from {hdf5_path}...")
|
logger.info(f"Loading data from {hdf5_path}...")
|
||||||
raw = pd.read_hdf(hdf5_path, key="data")
|
raw = pd.read_hdf(hdf5_path, key="data")
|
||||||
|
|
||||||
instrument = raw.index.get_level_values("instrument").unique()[0]
|
instrument = raw.index.get_level_values("instrument").unique()[0]
|
||||||
df = raw.xs(instrument, level="instrument")
|
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))
|
adapter = KronosAdapter(device=device, max_context=min(context_bars, 512), model_size=model_size)
|
||||||
adapter.load()
|
adapter.load()
|
||||||
|
|
||||||
bar_indices = list(range(context_bars, len(ohlcv), stride_bars))
|
bar_indices = list(range(context_bars, len(ohlcv), stride_bars))
|
||||||
@@ -302,6 +313,7 @@ def evaluate_kronos_model(
|
|||||||
stride_bars: int = 30,
|
stride_bars: int = 30,
|
||||||
device: Optional[str] = None,
|
device: Optional[str] = None,
|
||||||
batch_size: int = 32,
|
batch_size: int = 32,
|
||||||
|
model_size: str = "mini",
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Evaluate Kronos as a standalone model (Option B, alongside LightGBM).
|
Evaluate Kronos as a standalone model (Option B, alongside LightGBM).
|
||||||
@@ -312,13 +324,13 @@ def evaluate_kronos_model(
|
|||||||
Returns:
|
Returns:
|
||||||
dict with keys: IC_mean, IC_std, IC_IR (IC / std), hit_rate, n_predictions
|
dict with keys: IC_mean, IC_std, IC_IR (IC / std), hit_rate, n_predictions
|
||||||
"""
|
"""
|
||||||
device = device or ("cuda" if _cuda_available() else "cpu")
|
device = device or "cpu"
|
||||||
raw = pd.read_hdf(hdf5_path, key="data")
|
raw = pd.read_hdf(hdf5_path, key="data")
|
||||||
instrument = raw.index.get_level_values("instrument").unique()[0]
|
instrument = raw.index.get_level_values("instrument").unique()[0]
|
||||||
df = raw.xs(instrument, level="instrument")
|
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))
|
adapter = KronosAdapter(device=device, max_context=min(context_bars, 512), model_size=model_size)
|
||||||
adapter.load()
|
adapter.load()
|
||||||
|
|
||||||
n = len(ohlcv)
|
n = len(ohlcv)
|
||||||
|
|||||||
@@ -58,10 +58,12 @@ class ModelCodeEvaluator(CoSTEEREvaluator):
|
|||||||
model_execution_feedback: str = "",
|
model_execution_feedback: str = "",
|
||||||
model_value_feedback: str = "",
|
model_value_feedback: str = "",
|
||||||
):
|
):
|
||||||
assert isinstance(target_task, ModelTask)
|
if not isinstance(target_task, ModelTask):
|
||||||
assert isinstance(implementation, ModelFBWorkspace)
|
raise TypeError("target_task must be of type ModelTask")
|
||||||
if gt_implementation is not None:
|
if not isinstance(implementation, ModelFBWorkspace):
|
||||||
assert isinstance(gt_implementation, ModelFBWorkspace)
|
raise TypeError("implementation must be of type ModelFBWorkspace")
|
||||||
|
if gt_implementation is not None and not isinstance(gt_implementation, ModelFBWorkspace):
|
||||||
|
raise TypeError("gt_implementation must be of type ModelFBWorkspace")
|
||||||
|
|
||||||
model_task_information = target_task.get_task_information()
|
model_task_information = target_task.get_task_information()
|
||||||
code = implementation.all_codes
|
code = implementation.all_codes
|
||||||
@@ -113,10 +115,12 @@ class ModelFinalEvaluator(CoSTEEREvaluator):
|
|||||||
model_value_feedback: str,
|
model_value_feedback: str,
|
||||||
model_code_feedback: str,
|
model_code_feedback: str,
|
||||||
):
|
):
|
||||||
assert isinstance(target_task, ModelTask)
|
if not isinstance(target_task, ModelTask):
|
||||||
assert isinstance(implementation, ModelFBWorkspace)
|
raise TypeError("target_task must be of type ModelTask")
|
||||||
if gt_implementation is not None:
|
if not isinstance(implementation, ModelFBWorkspace):
|
||||||
assert isinstance(gt_implementation, ModelFBWorkspace)
|
raise TypeError("implementation must be of type ModelFBWorkspace")
|
||||||
|
if gt_implementation is not None and not isinstance(gt_implementation, ModelFBWorkspace):
|
||||||
|
raise TypeError("gt_implementation must be of type ModelFBWorkspace")
|
||||||
|
|
||||||
system_prompt = T(".prompts:evaluator_final_feedback.system").r(
|
system_prompt = T(".prompts:evaluator_final_feedback.system").r(
|
||||||
scenario=(
|
scenario=(
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ class ModelCoSTEEREvaluator(CoSTEEREvaluator):
|
|||||||
final_feedback="This task has failed too many times, skip implementation.",
|
final_feedback="This task has failed too many times, skip implementation.",
|
||||||
final_decision=False,
|
final_decision=False,
|
||||||
)
|
)
|
||||||
assert isinstance(target_task, ModelTask)
|
if not isinstance(target_task, ModelTask):
|
||||||
|
raise TypeError(f"Expected ModelTask, got {type(target_task)}")
|
||||||
|
|
||||||
# NOTE: Use fixed input to test the model to avoid randomness
|
# NOTE: Use fixed input to test the model to avoid randomness
|
||||||
batch_size = 8
|
batch_size = 8
|
||||||
@@ -50,7 +51,8 @@ class ModelCoSTEEREvaluator(CoSTEEREvaluator):
|
|||||||
input_value = 0.4
|
input_value = 0.4
|
||||||
param_init_value = 0.6
|
param_init_value = 0.6
|
||||||
|
|
||||||
assert isinstance(implementation, ModelFBWorkspace)
|
if not isinstance(implementation, ModelFBWorkspace):
|
||||||
|
raise TypeError(f"Expected ModelFBWorkspace, got {type(implementation)}")
|
||||||
model_execution_feedback, gen_np_array = implementation.execute(
|
model_execution_feedback, gen_np_array = implementation.execute(
|
||||||
batch_size=batch_size,
|
batch_size=batch_size,
|
||||||
num_features=num_features,
|
num_features=num_features,
|
||||||
@@ -59,7 +61,8 @@ class ModelCoSTEEREvaluator(CoSTEEREvaluator):
|
|||||||
param_init_value=param_init_value,
|
param_init_value=param_init_value,
|
||||||
)
|
)
|
||||||
if gt_implementation is not None:
|
if gt_implementation is not None:
|
||||||
assert isinstance(gt_implementation, ModelFBWorkspace)
|
if not isinstance(gt_implementation, ModelFBWorkspace):
|
||||||
|
raise TypeError(f"Expected ModelFBWorkspace, got {type(gt_implementation)}")
|
||||||
_, gt_np_array = gt_implementation.execute(
|
_, gt_np_array = gt_implementation.execute(
|
||||||
batch_size=batch_size,
|
batch_size=batch_size,
|
||||||
num_features=num_features,
|
num_features=num_features,
|
||||||
|
|||||||
@@ -1,699 +0,0 @@
|
|||||||
"""
|
|
||||||
Predix Optuna Optimizer - Hyperparameter optimization for trading strategies.
|
|
||||||
|
|
||||||
This module:
|
|
||||||
1. Takes generated strategies and optimizes their parameters using Optuna
|
|
||||||
2. Searches for optimal entry/exit thresholds, position sizing, etc.
|
|
||||||
3. Validates optimized strategies to prevent overfitting
|
|
||||||
4. Returns improved strategy metrics
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
optimizer = OptunaOptimizer(n_trials=30)
|
|
||||||
optimized = optimizer.optimize_strategy(strategy_result, factor_values)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import time
|
|
||||||
from datetime import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
from rdagent.log import rdagent_logger as logger
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
try:
|
|
||||||
import optuna
|
|
||||||
OPTUNA_AVAILABLE = True
|
|
||||||
except ImportError:
|
|
||||||
OPTUNA_AVAILABLE = False
|
|
||||||
logger.warning("Optuna not installed. Install with: pip install optuna")
|
|
||||||
|
|
||||||
|
|
||||||
class OptunaOptimizer:
|
|
||||||
"""
|
|
||||||
Optimizes strategy hyperparameters using Optuna Bayesian optimization.
|
|
||||||
|
|
||||||
Optimizes:
|
|
||||||
- Entry/exit signal thresholds
|
|
||||||
- Position sizing parameters
|
|
||||||
- Rolling window sizes
|
|
||||||
- Risk management parameters
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
n_trials: int = 30,
|
|
||||||
timeout: Optional[int] = None,
|
|
||||||
n_jobs: int = 1,
|
|
||||||
optimization_metric: str = "sharpe",
|
|
||||||
results_dir: Optional[str] = None,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
n_trials : int
|
|
||||||
Number of Optuna trials for optimization
|
|
||||||
timeout : int, optional
|
|
||||||
Maximum optimization time in seconds
|
|
||||||
n_jobs : int
|
|
||||||
Number of parallel jobs (-1 = all cores)
|
|
||||||
optimization_metric : str
|
|
||||||
Metric to optimize: 'sharpe', 'sortino', 'calmar', 'omega'
|
|
||||||
results_dir : str, optional
|
|
||||||
Path to save optimization results
|
|
||||||
"""
|
|
||||||
if not OPTUNA_AVAILABLE:
|
|
||||||
raise ImportError("Optuna is required. Install with: pip install optuna")
|
|
||||||
|
|
||||||
self.n_trials = n_trials
|
|
||||||
self.timeout = timeout
|
|
||||||
self.n_jobs = n_jobs
|
|
||||||
self.optimization_metric = optimization_metric
|
|
||||||
|
|
||||||
if results_dir is None:
|
|
||||||
project_root = Path(__file__).parent.parent.parent.parent
|
|
||||||
self.results_dir = project_root / "results"
|
|
||||||
else:
|
|
||||||
self.results_dir = Path(results_dir)
|
|
||||||
|
|
||||||
self.optimization_dir = self.results_dir / "optimization"
|
|
||||||
self.optimization_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f"OptunaOptimizer initialized: trials={n_trials}, metric={optimization_metric}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def optimize_strategy(
|
|
||||||
self,
|
|
||||||
strategy_result: Dict[str, Any],
|
|
||||||
factor_values: pd.DataFrame,
|
|
||||||
forward_returns: Optional[pd.Series] = None,
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Optimiere eine einzelne Strategie mit mehrstufiger Suche (grob → fein).
|
|
||||||
|
|
||||||
STAGE 1: Grobe Suche mit weiten Bereichen (10 Trials)
|
|
||||||
STAGE 2: Feine Suche um die besten Stage-1-Parameter (15 Trials)
|
|
||||||
STAGE 3: Sehr feine lokale Suche (5 Trials)
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
strategy_result : Dict[str, Any]
|
|
||||||
Strategy result from StrategyOrchestrator
|
|
||||||
factor_values : pd.DataFrame
|
|
||||||
DataFrame with factor values over time
|
|
||||||
forward_returns : pd.Series, optional
|
|
||||||
Forward returns for evaluation
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
Dict[str, Any]
|
|
||||||
Optimized strategy result with best parameters
|
|
||||||
"""
|
|
||||||
strategy_name = strategy_result.get("strategy_name", "Unknown")
|
|
||||||
logger.info(f"Starting multi-stage optimization for strategy: {strategy_name}")
|
|
||||||
|
|
||||||
# Speichere Referenzen für Objective-Methoden
|
|
||||||
self._current_strategy = strategy_result
|
|
||||||
self._current_factors = factor_values
|
|
||||||
self._current_forward_returns = forward_returns
|
|
||||||
|
|
||||||
# STAGE 1: Grobe Suche mit weiten Bereichen (10 Trials)
|
|
||||||
logger.info(f"Stage 1: Coarse search for {strategy_name}")
|
|
||||||
stage1_study = optuna.create_study(
|
|
||||||
direction="maximize",
|
|
||||||
sampler=optuna.samplers.TPESampler(seed=42),
|
|
||||||
pruner=optuna.pruners.MedianPruner(n_startup_trials=3, n_warmup_steps=5),
|
|
||||||
)
|
|
||||||
stage1_study.optimize(self._objective_coarse, n_trials=10, gc_after_trial=True)
|
|
||||||
|
|
||||||
best_stage1 = stage1_study.best_trial.params
|
|
||||||
best_stage1_value = stage1_study.best_trial.value
|
|
||||||
logger.info(
|
|
||||||
f"Stage 1 complete: best_value={best_stage1_value:.4f}, "
|
|
||||||
f"params={best_stage1}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# STAGE 2: Feine Suche um die besten Stage-1-Parameter (15 Trials)
|
|
||||||
logger.info(f"Stage 2: Fine search around best params")
|
|
||||||
stage2_study = optuna.create_study(
|
|
||||||
direction="maximize",
|
|
||||||
sampler=optuna.samplers.TPESampler(seed=43),
|
|
||||||
pruner=optuna.pruners.MedianPruner(n_startup_trials=5, n_warmup_steps=5),
|
|
||||||
)
|
|
||||||
# Verwende beste Stage-1-Parameter als Zentrum für feine Suche
|
|
||||||
self._fine_search_center = best_stage1
|
|
||||||
stage2_study.optimize(self._objective_fine, n_trials=15, gc_after_trial=True)
|
|
||||||
|
|
||||||
best_stage2 = stage2_study.best_trial.params
|
|
||||||
best_stage2_value = stage2_study.best_trial.value
|
|
||||||
logger.info(
|
|
||||||
f"Stage 2 complete: best_value={best_stage2_value:.4f}, "
|
|
||||||
f"params={best_stage2}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# STAGE 3: Sehr feine lokale Suche (5 Trials) - nur wenn Stage 2 besser war
|
|
||||||
if best_stage2_value > best_stage1_value:
|
|
||||||
logger.info(f"Stage 3: Very fine local search")
|
|
||||||
stage3_study = optuna.create_study(
|
|
||||||
direction="maximize",
|
|
||||||
sampler=optuna.samplers.TPESampler(seed=44),
|
|
||||||
)
|
|
||||||
self._very_fine_center = best_stage2
|
|
||||||
stage3_study.optimize(self._objective_very_fine, n_trials=5, gc_after_trial=True)
|
|
||||||
|
|
||||||
best_stage3_value = stage3_study.best_trial.value
|
|
||||||
logger.info(f"Stage 3 complete: best_value={best_stage3_value:.4f}")
|
|
||||||
|
|
||||||
# Bestes Trial über alle Stufen wählen
|
|
||||||
if best_stage3_value > best_stage2_value:
|
|
||||||
best_trial = stage3_study.best_trial
|
|
||||||
else:
|
|
||||||
best_trial = stage2_study.best_trial
|
|
||||||
else:
|
|
||||||
best_trial = stage1_study.best_trial
|
|
||||||
|
|
||||||
# Re-evaluate with best params
|
|
||||||
best_params = best_trial.params
|
|
||||||
best_metrics = self._evaluate_with_params(
|
|
||||||
strategy_result, factor_values, best_params, forward_returns
|
|
||||||
)
|
|
||||||
|
|
||||||
# Baue optimiertes Ergebnis
|
|
||||||
optimized_result = {
|
|
||||||
**strategy_result,
|
|
||||||
"status": "accepted" if self._is_acceptable(best_metrics) else "rejected",
|
|
||||||
"sharpe_ratio": best_metrics.get("sharpe_ratio", 0),
|
|
||||||
"annualized_return": best_metrics.get("annualized_return", 0),
|
|
||||||
"max_drawdown": best_metrics.get("max_drawdown", 0),
|
|
||||||
"win_rate": best_metrics.get("win_rate", 0),
|
|
||||||
"optimization_status": "success",
|
|
||||||
"best_params": best_params,
|
|
||||||
"optimization_stages": {
|
|
||||||
"stage1_best": best_stage1_value,
|
|
||||||
"stage2_best": best_stage2_value,
|
|
||||||
"stage3_best": best_stage3_value if best_stage2_value > best_stage1_value else None,
|
|
||||||
},
|
|
||||||
"optimization_trials": len(stage1_study.trials) + len(stage2_study.trials) + (
|
|
||||||
len(stage3_study.trials) if best_stage2_value > best_stage1_value else 0
|
|
||||||
),
|
|
||||||
"optimization_history": {
|
|
||||||
"stage1": [t.value for t in stage1_study.trials if t.value is not None],
|
|
||||||
"stage2": [t.value for t in stage2_study.trials if t.value is not None],
|
|
||||||
"stage3": (
|
|
||||||
[t.value for t in stage3_study.trials if t.value is not None]
|
|
||||||
if best_stage2_value > best_stage1_value else []
|
|
||||||
),
|
|
||||||
},
|
|
||||||
"optimized_at": datetime.now().isoformat(),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Speichere Optimierungsergebnisse
|
|
||||||
self._save_optimization_results(optimized_result, strategy_name)
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f"Multi-stage optimization complete for {strategy_name}: "
|
|
||||||
f"best_metric={best_trial.value:.4f}, status={optimized_result['status']}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return optimized_result
|
|
||||||
|
|
||||||
def optimize_batch(
|
|
||||||
self,
|
|
||||||
strategies: List[Dict[str, Any]],
|
|
||||||
factor_values: pd.DataFrame,
|
|
||||||
forward_returns: Optional[pd.Series] = None,
|
|
||||||
progress_callback=None,
|
|
||||||
) -> List[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
Optimize multiple strategies in batch.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
strategies : List[Dict[str, Any]]
|
|
||||||
List of strategy results to optimize
|
|
||||||
factor_values : pd.DataFrame
|
|
||||||
Factor values for all strategies
|
|
||||||
forward_returns : pd.Series, optional
|
|
||||||
Forward returns for evaluation
|
|
||||||
progress_callback : callable, optional
|
|
||||||
Callback(current, total, result) for progress updates
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
List[Dict[str, Any]]
|
|
||||||
List of optimized strategy results
|
|
||||||
"""
|
|
||||||
optimized = []
|
|
||||||
|
|
||||||
for i, strategy in enumerate(strategies):
|
|
||||||
if progress_callback:
|
|
||||||
progress_callback(i, len(strategies), strategy)
|
|
||||||
|
|
||||||
try:
|
|
||||||
opt_result = self.optimize_strategy(strategy, factor_values, forward_returns)
|
|
||||||
optimized.append(opt_result)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to optimize strategy {strategy.get('strategy_name', i)}: {e}")
|
|
||||||
optimized.append({
|
|
||||||
**strategy,
|
|
||||||
"optimization_status": "failed",
|
|
||||||
"error": str(e),
|
|
||||||
})
|
|
||||||
|
|
||||||
return optimized
|
|
||||||
|
|
||||||
def _sample_coarse_params(self, trial: optuna.Trial) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Weite Bereiche für initiale Exploration (Stage 1).
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
trial : optuna.Trial
|
|
||||||
Current Optuna trial
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
Dict[str, Any]
|
|
||||||
Sampled hyperparameters with wide ranges
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
"entry_threshold": trial.suggest_float("entry_threshold", 0.1, 3.0, step=0.1),
|
|
||||||
"exit_threshold": trial.suggest_float("exit_threshold", 0.0, 1.5, step=0.1),
|
|
||||||
"zscore_window": trial.suggest_int("zscore_window", 5, 500, step=5),
|
|
||||||
"signal_window": trial.suggest_int("signal_window", 1, 30, step=1),
|
|
||||||
"position_size_pct": trial.suggest_float("position_size_pct", 0.05, 1.0, step=0.05),
|
|
||||||
"stop_loss_mult": trial.suggest_float("stop_loss_mult", 0.5, 15.0, step=0.5),
|
|
||||||
"take_profit_mult": trial.suggest_float("take_profit_mult", 1.0, 20.0, step=0.5),
|
|
||||||
"volatility_lookback": trial.suggest_int("volatility_lookback", 5, 500, step=5),
|
|
||||||
"signal_bias": trial.suggest_float("signal_bias", -1.0, 1.0, step=0.05),
|
|
||||||
"max_hold_bars": trial.suggest_int("max_hold_bars", 5, 1000, step=5),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Parameters that are allowed to be negative (not clamped to 0).
|
|
||||||
_SIGNED_PARAMS = {"signal_bias"}
|
|
||||||
# Absolute lower bounds per parameter (applied after the center-half_width calc).
|
|
||||||
_PARAM_FLOOR: Dict[str, float] = {
|
|
||||||
"entry_threshold": 0.0,
|
|
||||||
"exit_threshold": 0.0,
|
|
||||||
"zscore_window": 1.0,
|
|
||||||
"signal_window": 1.0,
|
|
||||||
"position_size_pct": 0.01,
|
|
||||||
"stop_loss_mult": 0.1,
|
|
||||||
"take_profit_mult": 0.1,
|
|
||||||
"volatility_lookback": 1.0,
|
|
||||||
"signal_bias": -1.0,
|
|
||||||
"max_hold_bars": 1.0,
|
|
||||||
}
|
|
||||||
|
|
||||||
def _suggest_bounded(
|
|
||||||
self,
|
|
||||||
trial: optuna.Trial,
|
|
||||||
key: str,
|
|
||||||
center_val: float,
|
|
||||||
half_width: float,
|
|
||||||
) -> Any:
|
|
||||||
"""Suggest a parameter value with safe bounds that never invert."""
|
|
||||||
floor = self._PARAM_FLOOR.get(key, -float("inf"))
|
|
||||||
is_int = "window" in key or "lookback" in key or "bars" in key
|
|
||||||
if is_int:
|
|
||||||
low = max(int(floor), int(center_val - half_width))
|
|
||||||
high = max(low + 1, int(center_val + half_width))
|
|
||||||
return trial.suggest_int(key, low, high)
|
|
||||||
else:
|
|
||||||
low = max(floor, center_val - half_width)
|
|
||||||
high = center_val + half_width
|
|
||||||
if high <= low:
|
|
||||||
high = low + max(1e-4, half_width * 0.1)
|
|
||||||
return trial.suggest_float(key, low, high)
|
|
||||||
|
|
||||||
def _sample_fine_params(self, trial: optuna.Trial) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Enge Bereiche zentriert um die besten Stage-1-Parameter (Stage 2).
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
trial : optuna.Trial
|
|
||||||
Current Optuna trial
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
Dict[str, Any]
|
|
||||||
Sampled hyperparameters with narrow ranges around Stage 1 best
|
|
||||||
"""
|
|
||||||
center = getattr(self, "_fine_search_center", {})
|
|
||||||
ranges: Dict[str, Tuple[float, float]] = {
|
|
||||||
"entry_threshold": (center.get("entry_threshold", 1.0), 0.3),
|
|
||||||
"exit_threshold": (center.get("exit_threshold", 0.3), 0.2),
|
|
||||||
"zscore_window": (center.get("zscore_window", 50), 20),
|
|
||||||
"signal_window": (center.get("signal_window", 3), 5),
|
|
||||||
"position_size_pct": (center.get("position_size_pct", 0.5), 0.15),
|
|
||||||
"stop_loss_mult": (center.get("stop_loss_mult", 5.0), 2.0),
|
|
||||||
"take_profit_mult": (center.get("take_profit_mult", 5.0), 2.0),
|
|
||||||
"volatility_lookback": (center.get("volatility_lookback", 100), 30),
|
|
||||||
"signal_bias": (center.get("signal_bias", 0.0), 0.2),
|
|
||||||
"max_hold_bars": (center.get("max_hold_bars", 100), 50),
|
|
||||||
}
|
|
||||||
return {key: self._suggest_bounded(trial, key, c, hw) for key, (c, hw) in ranges.items()}
|
|
||||||
|
|
||||||
def _sample_very_fine_params(self, trial: optuna.Trial) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Sehr enge Bereiche für finale Verfeinerung (Stage 3).
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
trial : optuna.Trial
|
|
||||||
Current Optuna trial
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
Dict[str, Any]
|
|
||||||
Sampled hyperparameters with very narrow ranges around Stage 2 best
|
|
||||||
"""
|
|
||||||
center = getattr(
|
|
||||||
self, "_very_fine_center", getattr(self, "_fine_search_center", {})
|
|
||||||
)
|
|
||||||
ranges: Dict[str, Tuple[float, float]] = {
|
|
||||||
"entry_threshold": (center.get("entry_threshold", 1.0), 0.1),
|
|
||||||
"exit_threshold": (center.get("exit_threshold", 0.3), 0.07),
|
|
||||||
"zscore_window": (center.get("zscore_window", 50), 7),
|
|
||||||
"signal_window": (center.get("signal_window", 3), 2),
|
|
||||||
"position_size_pct": (center.get("position_size_pct", 0.5), 0.05),
|
|
||||||
"stop_loss_mult": (center.get("stop_loss_mult", 5.0), 0.7),
|
|
||||||
"take_profit_mult": (center.get("take_profit_mult", 5.0), 0.7),
|
|
||||||
"volatility_lookback": (center.get("volatility_lookback", 100), 10),
|
|
||||||
"signal_bias": (center.get("signal_bias", 0.0), 0.07),
|
|
||||||
"max_hold_bars": (center.get("max_hold_bars", 100), 17),
|
|
||||||
}
|
|
||||||
return {key: self._suggest_bounded(trial, key, c, hw) for key, (c, hw) in ranges.items()}
|
|
||||||
|
|
||||||
def _objective_coarse(self, trial: optuna.Trial) -> float:
|
|
||||||
"""Objective-Funktion für Stage 1 (grobe Suche)."""
|
|
||||||
try:
|
|
||||||
params = self._sample_coarse_params(trial)
|
|
||||||
metrics = self._evaluate_with_params(
|
|
||||||
self._current_strategy, self._current_factors, params, self._current_forward_returns
|
|
||||||
)
|
|
||||||
return self._extract_metric(metrics, self.optimization_metric)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Stage 1 trial {trial.number} failed: {e}")
|
|
||||||
return float("-inf")
|
|
||||||
|
|
||||||
def _objective_fine(self, trial: optuna.Trial) -> float:
|
|
||||||
"""Objective-Funktion für Stage 2 (feine Suche)."""
|
|
||||||
try:
|
|
||||||
params = self._sample_fine_params(trial)
|
|
||||||
metrics = self._evaluate_with_params(
|
|
||||||
self._current_strategy, self._current_factors, params, self._current_forward_returns
|
|
||||||
)
|
|
||||||
return self._extract_metric(metrics, self.optimization_metric)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Stage 2 trial {trial.number} failed: {e}")
|
|
||||||
return float("-inf")
|
|
||||||
|
|
||||||
def _objective_very_fine(self, trial: optuna.Trial) -> float:
|
|
||||||
"""Objective-Funktion für Stage 3 (sehr feine Suche)."""
|
|
||||||
try:
|
|
||||||
params = self._sample_very_fine_params(trial)
|
|
||||||
metrics = self._evaluate_with_params(
|
|
||||||
self._current_strategy, self._current_factors, params, self._current_forward_returns
|
|
||||||
)
|
|
||||||
return self._extract_metric(metrics, self.optimization_metric)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Stage 3 trial {trial.number} failed: {e}")
|
|
||||||
return float("-inf")
|
|
||||||
|
|
||||||
def _sample_hyperparameters(self, trial: optuna.Trial) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Sample hyperparameters for a trial.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
trial : optuna.Trial
|
|
||||||
Current Optuna trial
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
Dict[str, Any]
|
|
||||||
Sampled hyperparameters
|
|
||||||
"""
|
|
||||||
params = {
|
|
||||||
# Entry/exit thresholds (wider range for better optimization)
|
|
||||||
"entry_threshold": trial.suggest_float("entry_threshold", 0.3, 2.0, step=0.1),
|
|
||||||
"exit_threshold": trial.suggest_float("exit_threshold", 0.0, 1.0, step=0.1),
|
|
||||||
|
|
||||||
# Rolling window for z-score normalization
|
|
||||||
"zscore_window": trial.suggest_int("zscore_window", 10, 200, step=10),
|
|
||||||
|
|
||||||
# Rolling window for signal smoothing
|
|
||||||
"signal_window": trial.suggest_int("signal_window", 1, 15, step=1),
|
|
||||||
|
|
||||||
# Position sizing
|
|
||||||
"position_size_pct": trial.suggest_float("position_size_pct", 0.1, 1.0, step=0.1),
|
|
||||||
|
|
||||||
# Stop loss / take profit (in terms of factor std)
|
|
||||||
"stop_loss_mult": trial.suggest_float("stop_loss_mult", 1.0, 10.0, step=0.5),
|
|
||||||
"take_profit_mult": trial.suggest_float("take_profit_mult", 1.5, 15.0, step=0.5),
|
|
||||||
|
|
||||||
# Volatility adjustment
|
|
||||||
"volatility_lookback": trial.suggest_int("volatility_lookback", 10, 200, step=10),
|
|
||||||
|
|
||||||
# Signal bias (shifts thresholds)
|
|
||||||
"signal_bias": trial.suggest_float("signal_bias", -0.5, 0.5, step=0.1),
|
|
||||||
|
|
||||||
# Max holding periods (in bars)
|
|
||||||
"max_hold_bars": trial.suggest_int("max_hold_bars", 10, 500, step=10),
|
|
||||||
}
|
|
||||||
|
|
||||||
return params
|
|
||||||
|
|
||||||
def _evaluate_with_params(
|
|
||||||
self,
|
|
||||||
strategy_result: Dict[str, Any],
|
|
||||||
factor_values: pd.DataFrame,
|
|
||||||
params: Dict[str, Any],
|
|
||||||
forward_returns: Optional[pd.Series] = None,
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Evaluate strategy with specific hyperparameters.
|
|
||||||
|
|
||||||
This method:
|
|
||||||
1. Uses the ORIGINAL strategy code from the LLM
|
|
||||||
2. Overrides key parameters (thresholds, windows) via exec
|
|
||||||
3. Evaluates the resulting signals
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
strategy_result : Dict[str, Any]
|
|
||||||
Original strategy result with 'code' field
|
|
||||||
factor_values : pd.DataFrame
|
|
||||||
Factor values over time
|
|
||||||
params : Dict[str, Any]
|
|
||||||
Hyperparameters to evaluate
|
|
||||||
forward_returns : pd.Series, optional
|
|
||||||
Forward returns
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
Dict[str, Any]
|
|
||||||
Evaluation metrics
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Get original strategy code
|
|
||||||
original_code = strategy_result.get("code", "")
|
|
||||||
|
|
||||||
# Get factor weights if available
|
|
||||||
factors_used = strategy_result.get("factors_used", list(factor_values.columns))
|
|
||||||
available_factors = [f for f in factors_used if f in factor_values.columns]
|
|
||||||
|
|
||||||
if not available_factors:
|
|
||||||
return self._default_metrics()
|
|
||||||
|
|
||||||
df_factors = factor_values[available_factors]
|
|
||||||
|
|
||||||
if len(df_factors) < 100:
|
|
||||||
return self._default_metrics()
|
|
||||||
|
|
||||||
# Extract Optuna parameters
|
|
||||||
entry_thresh = params["entry_threshold"]
|
|
||||||
exit_thresh = params["exit_threshold"]
|
|
||||||
zscore_window = params["zscore_window"]
|
|
||||||
signal_window = params["signal_window"]
|
|
||||||
signal_bias = params.get("signal_bias", 0.0)
|
|
||||||
|
|
||||||
# Build parameter-override prefix that INJECTS Optuna params into code scope
|
|
||||||
# This replaces hardcoded thresholds/windows in the LLM code
|
|
||||||
|
|
||||||
# If no original code, build strategy from scratch using factor IC weights
|
|
||||||
if not original_code or len(original_code.strip()) < 20:
|
|
||||||
df_norm = (df_factors - df_factors.rolling(zscore_window).mean()) / (df_factors.rolling(zscore_window).std() + 1e-8)
|
|
||||||
|
|
||||||
ic_weights = strategy_result.get("ic_weights", [])
|
|
||||||
if len(ic_weights) == len(available_factors):
|
|
||||||
weighted_sum = sum(
|
|
||||||
w * df_norm[col] for col, w in zip(available_factors, ic_weights)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
weighted_sum = df_norm.mean(axis=1)
|
|
||||||
|
|
||||||
signal = pd.Series(0.0, index=df_factors.index)
|
|
||||||
signal[weighted_sum > entry_thresh] = 1
|
|
||||||
signal[weighted_sum < -entry_thresh] = -1
|
|
||||||
signal[abs(weighted_sum) < exit_thresh] = 0
|
|
||||||
signal = signal.rolling(window=signal_window, min_periods=1).mean().round().astype(int)
|
|
||||||
else:
|
|
||||||
# Patch the LLM code: replace hardcoded parameter assignments with Optuna values
|
|
||||||
import re
|
|
||||||
patched_code = original_code
|
|
||||||
|
|
||||||
# Replace parameter assignments: entry_thresh = 0.8 → entry_thresh = 1.2
|
|
||||||
param_patterns = [
|
|
||||||
(r'entry_thresh\s*=\s*[\d.]+', f'entry_thresh = {entry_thresh}'),
|
|
||||||
(r'exit_thresh\s*=\s*[\d.]+', f'exit_thresh = {exit_thresh}'),
|
|
||||||
(r'window\s*=\s*\d+', f'window = {zscore_window}'),
|
|
||||||
(r'signal_window\s*=\s*\d+', f'signal_window = {signal_window}'),
|
|
||||||
]
|
|
||||||
for pattern, replacement in param_patterns:
|
|
||||||
patched_code = re.sub(pattern, replacement, patched_code)
|
|
||||||
|
|
||||||
# Also handle inline .rolling(N) calls → use zscore_window
|
|
||||||
# Only replace if the number is a common window size (20, 50, 100, etc.)
|
|
||||||
rolling_pattern = r'\.rolling\((\d+)\)'
|
|
||||||
def replace_rolling(match):
|
|
||||||
val = int(match.group(1))
|
|
||||||
if val in (20, 30, 50, 100, 200):
|
|
||||||
return f'.rolling({zscore_window})'
|
|
||||||
return match.group(0)
|
|
||||||
patched_code = re.sub(rolling_pattern, replace_rolling, patched_code)
|
|
||||||
|
|
||||||
# Execute patched code
|
|
||||||
local_vars = {"factors": df_factors}
|
|
||||||
try:
|
|
||||||
exec(patched_code, {"np": np, "pd": pd, "numpy": np}, local_vars) # nosec B102: exec is required for sandboxed strategy code evaluation
|
|
||||||
except Exception:
|
|
||||||
# Fallback: build simple IC-weighted strategy
|
|
||||||
df_norm = (df_factors - df_factors.rolling(zscore_window).mean()) / (df_factors.rolling(zscore_window).std() + 1e-8)
|
|
||||||
combined = df_norm.mean(axis=1)
|
|
||||||
signal = pd.Series(0, index=combined.index)
|
|
||||||
signal[combined > entry_thresh] = 1
|
|
||||||
signal[combined < -entry_thresh] = -1
|
|
||||||
signal[abs(combined) < exit_thresh] = 0
|
|
||||||
signal = signal.rolling(window=signal_window, min_periods=1).mean().round().astype(int)
|
|
||||||
local_vars["signal"] = signal
|
|
||||||
|
|
||||||
signal = local_vars.get("signal")
|
|
||||||
|
|
||||||
if signal is None or len(signal) < 10:
|
|
||||||
return self._default_metrics()
|
|
||||||
|
|
||||||
# Ensure signal is aligned
|
|
||||||
signal = signal.reindex(df_factors.index).fillna(0).astype(int)
|
|
||||||
|
|
||||||
# Apply signal bias (shifts signal values before thresholding)
|
|
||||||
if signal_bias != 0.0:
|
|
||||||
signal = (signal.astype(float) + signal_bias).round().astype(int).clip(-1, 1)
|
|
||||||
|
|
||||||
# Build a synthetic close from the factor-mean so we can route
|
|
||||||
# through the same unified engine as every other backtest path.
|
|
||||||
# Backtest formulas must match the orchestrator's real-OHLCV path.
|
|
||||||
combined = df_factors.mean(axis=1)
|
|
||||||
combined_ret = combined.pct_change().fillna(0)
|
|
||||||
synthetic_close = (1 + combined_ret).cumprod() * 100.0
|
|
||||||
|
|
||||||
from rdagent.components.backtesting.vbt_backtest import (
|
|
||||||
backtest_signal_ftmo,
|
|
||||||
DEFAULT_TXN_COST_BPS,
|
|
||||||
)
|
|
||||||
import os as _os
|
|
||||||
|
|
||||||
bt = backtest_signal_ftmo(
|
|
||||||
close=synthetic_close,
|
|
||||||
signal=signal,
|
|
||||||
txn_cost_bps=float(_os.getenv("TXN_COST_BPS", DEFAULT_TXN_COST_BPS)),
|
|
||||||
)
|
|
||||||
if bt.get("status") != "success":
|
|
||||||
return self._default_metrics()
|
|
||||||
|
|
||||||
return {
|
|
||||||
"sharpe_ratio": bt["sharpe"],
|
|
||||||
"annualized_return": bt["annualized_return"],
|
|
||||||
"max_drawdown": bt["max_drawdown"],
|
|
||||||
"win_rate": bt["win_rate"],
|
|
||||||
"volatility": bt["volatility"],
|
|
||||||
"total_return": bt["total_return"],
|
|
||||||
"num_trades": bt["n_trades"],
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"Evaluation failed with params {params}: {e}")
|
|
||||||
return self._default_metrics()
|
|
||||||
|
|
||||||
def _default_metrics(self) -> Dict[str, float]:
|
|
||||||
"""Return default/failure metrics."""
|
|
||||||
return {
|
|
||||||
"sharpe_ratio": float("-inf"),
|
|
||||||
"annualized_return": 0.0,
|
|
||||||
"max_drawdown": 0.0,
|
|
||||||
"win_rate": 0.0,
|
|
||||||
"volatility": 0.0,
|
|
||||||
"total_return": 0.0,
|
|
||||||
"num_trades": 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
def _extract_metric(self, metrics: Dict[str, Any], metric_name: str) -> float:
|
|
||||||
"""Extract specific metric from metrics dict."""
|
|
||||||
metric_map = {
|
|
||||||
"sharpe": metrics.get("sharpe_ratio", float("-inf")),
|
|
||||||
"sortino": self._calculate_sortino(metrics),
|
|
||||||
"calmar": self._calculate_calmar(metrics),
|
|
||||||
"omega": self._calculate_omega(metrics),
|
|
||||||
}
|
|
||||||
return metric_map.get(metric_name, metrics.get("sharpe_ratio", float("-inf")))
|
|
||||||
|
|
||||||
def _calculate_sortino(self, metrics: Dict[str, Any]) -> float:
|
|
||||||
"""Calculate Sortino ratio (simplified)."""
|
|
||||||
sharpe = metrics.get("sharpe_ratio", 0)
|
|
||||||
# Sortino is typically higher than Sharpe (only penalizes downside)
|
|
||||||
return sharpe * 1.2 if sharpe > 0 else sharpe
|
|
||||||
|
|
||||||
def _calculate_calmar(self, metrics: Dict[str, Any]) -> float:
|
|
||||||
"""Calculate Calmar ratio."""
|
|
||||||
ann_return = metrics.get("annualized_return", 0)
|
|
||||||
max_dd = abs(metrics.get("max_drawdown", 0.01))
|
|
||||||
return ann_return / max_dd if max_dd > 0 else 0.0
|
|
||||||
|
|
||||||
def _calculate_omega(self, metrics: Dict[str, Any]) -> float:
|
|
||||||
"""Calculate Omega ratio (simplified)."""
|
|
||||||
win_rate = metrics.get("win_rate", 0.5)
|
|
||||||
return win_rate / (1 - win_rate) if win_rate < 1 else float("inf")
|
|
||||||
|
|
||||||
def _is_acceptable(self, metrics: Dict[str, Any]) -> bool:
|
|
||||||
"""Check if optimized strategy is acceptable."""
|
|
||||||
sharpe = metrics.get("sharpe_ratio", 0)
|
|
||||||
max_dd = metrics.get("max_drawdown", 0)
|
|
||||||
win_rate = metrics.get("win_rate", 0)
|
|
||||||
|
|
||||||
return sharpe >= 0.3 and max_dd >= -0.30 and win_rate >= 0.40
|
|
||||||
|
|
||||||
def _save_optimization_results(
|
|
||||||
self, optimized_result: Dict[str, Any], strategy_name: str
|
|
||||||
) -> None:
|
|
||||||
"""Save optimization results to file."""
|
|
||||||
import json
|
|
||||||
|
|
||||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
||||||
safe_name = strategy_name.replace("/", "_").replace(" ", "_")[:60]
|
|
||||||
filename = f"opt_{safe_name}_{timestamp}.json"
|
|
||||||
filepath = self.optimization_dir / filename
|
|
||||||
|
|
||||||
# Remove non-serializable fields
|
|
||||||
save_data = {k: v for k, v in optimized_result.items() if k != "code"}
|
|
||||||
|
|
||||||
with open(filepath, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(save_data, f, indent=2, default=str, ensure_ascii=False)
|
|
||||||
|
|
||||||
logger.debug(f"Saved optimization results to {filepath}")
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""RL Trading Agent components for Predix.
|
"""RL Trading Agent components for NexQuant.
|
||||||
|
|
||||||
This package provides reinforcement learning trading capabilities.
|
This package provides reinforcement learning trading capabilities.
|
||||||
Works with or without stable-baselines3 (graceful fallback).
|
Works with or without stable-baselines3 (graceful fallback).
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
RL Trading Agent wrapper for Stable Baselines3.
|
RL Trading Agent wrapper for Stable Baselines3.
|
||||||
|
|
||||||
Provides an easy-to-use interface for training, evaluating, and deploying
|
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:
|
Supported algorithms:
|
||||||
- PPO: Proximal Policy Optimization (most stable, recommended for production)
|
- 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
|
Supports single-asset (EUR/USD) trading with technical indicators
|
||||||
and portfolio state as observations.
|
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
|
import gymnasium as gym
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
Fallback RL implementation for users without stable-baselines3.
|
Fallback RL implementation for users without stable-baselines3.
|
||||||
|
|
||||||
Provides simple rule-based trading when RL library is not available.
|
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.
|
without the optional stable-baselines3 dependency.
|
||||||
|
|
||||||
The fallback implements a momentum-based strategy as a placeholder
|
The fallback implements a momentum-based strategy as a placeholder
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -85,13 +85,16 @@ def load_and_process_one_pdf_by_azure_document_intelligence(
|
|||||||
|
|
||||||
|
|
||||||
def load_and_process_pdfs_by_azure_document_intelligence(path: Path) -> dict[str, str]:
|
def load_and_process_pdfs_by_azure_document_intelligence(path: Path) -> dict[str, str]:
|
||||||
assert RD_AGENT_SETTINGS.azure_document_intelligence_key is not None
|
if RD_AGENT_SETTINGS.azure_document_intelligence_key is None:
|
||||||
assert RD_AGENT_SETTINGS.azure_document_intelligence_endpoint is not None
|
raise AssertionError("azure_document_intelligence_key must be set")
|
||||||
|
if RD_AGENT_SETTINGS.azure_document_intelligence_endpoint is None:
|
||||||
|
raise AssertionError("azure_document_intelligence_endpoint must be set")
|
||||||
|
|
||||||
content_dict = {}
|
content_dict = {}
|
||||||
ab_path = path.resolve()
|
ab_path = path.resolve()
|
||||||
if ab_path.is_file():
|
if ab_path.is_file():
|
||||||
assert ".pdf" in ab_path.suffixes, "The file must be a PDF file."
|
if ".pdf" not in ab_path.suffixes:
|
||||||
|
raise ValueError("The file must be a PDF file.")
|
||||||
proc = load_and_process_one_pdf_by_azure_document_intelligence
|
proc = load_and_process_one_pdf_by_azure_document_intelligence
|
||||||
content_dict[str(ab_path)] = proc(
|
content_dict[str(ab_path)] = proc(
|
||||||
ab_path,
|
ab_path,
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ class UndirectedNode(Node):
|
|||||||
super().__init__(content, label, embedding)
|
super().__init__(content, label, embedding)
|
||||||
self.neighbors: set[UndirectedNode] = set()
|
self.neighbors: set[UndirectedNode] = set()
|
||||||
self.appendix = appendix # appendix stores any additional information
|
self.appendix = appendix # appendix stores any additional information
|
||||||
assert isinstance(content, str), "content must be a string"
|
if not isinstance(content, str):
|
||||||
|
raise TypeError("content must be a string")
|
||||||
|
|
||||||
def add_neighbor(self, node: UndirectedNode) -> None:
|
def add_neighbor(self, node: UndirectedNode) -> None:
|
||||||
self.neighbors.add(node)
|
self.neighbors.add(node)
|
||||||
@@ -96,7 +97,8 @@ class Graph(KnowledgeBase):
|
|||||||
APIBackend().create_embedding(input_content=contents[i : i + size]),
|
APIBackend().create_embedding(input_content=contents[i : i + size]),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(nodes) == len(embeddings), "nodes' length must equals embeddings' length"
|
if len(nodes) != len(embeddings):
|
||||||
|
raise ValueError("nodes' length must equal embeddings' length")
|
||||||
for node, embedding in zip(nodes, embeddings):
|
for node, embedding in zip(nodes, embeddings):
|
||||||
node.embedding = embedding
|
node.embedding = embedding
|
||||||
return nodes
|
return nodes
|
||||||
@@ -252,7 +254,8 @@ class UndirectedGraph(Graph):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
min_nodes_count = 2
|
min_nodes_count = 2
|
||||||
assert len(nodes) >= min_nodes_count, "nodes length must >=2"
|
if len(nodes) < min_nodes_count:
|
||||||
|
raise ValueError("nodes length must >=2")
|
||||||
intersection = None
|
intersection = None
|
||||||
|
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
|
|||||||
@@ -87,7 +87,8 @@ class ModelWsLoader(WsLoader[ModelTask, ModelFBWorkspace]):
|
|||||||
self.path = Path(path)
|
self.path = Path(path)
|
||||||
|
|
||||||
def load(self, task: ModelTask) -> ModelFBWorkspace:
|
def load(self, task: ModelTask) -> ModelFBWorkspace:
|
||||||
assert task.name is not None
|
if task.name is None:
|
||||||
|
raise AssertionError("task.name should not be None")
|
||||||
mti = ModelFBWorkspace(task)
|
mti = ModelFBWorkspace(task)
|
||||||
mti.prepare()
|
mti.prepare()
|
||||||
with open(self.path / f"{task.name}.py", "r") as f:
|
with open(self.path / f"{task.name}.py", "r") as f:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Predix Model Loader
|
NexQuant Model Loader
|
||||||
|
|
||||||
Loads models from:
|
Loads models from:
|
||||||
1. models/local/*.py (your improved models - not in Git)
|
1. models/local/*.py (your improved models - not in Git)
|
||||||
@@ -23,7 +23,7 @@ from typing import Optional, Any
|
|||||||
|
|
||||||
|
|
||||||
# Base paths
|
# Base paths
|
||||||
BASE_DIR = Path(__file__).parent.parent.parent # Predix/
|
BASE_DIR = Path(__file__).parent.parent.parent # NexQuant/
|
||||||
MODELS_DIR = BASE_DIR / "models"
|
MODELS_DIR = BASE_DIR / "models"
|
||||||
LOCAL_MODELS_DIR = MODELS_DIR / "local"
|
LOCAL_MODELS_DIR = MODELS_DIR / "local"
|
||||||
STANDARD_MODELS_DIR = MODELS_DIR / "standard"
|
STANDARD_MODELS_DIR = MODELS_DIR / "standard"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Predix Prompt Loader
|
NexQuant Prompt Loader
|
||||||
|
|
||||||
Loads prompts from:
|
Loads prompts from:
|
||||||
1. prompts/local/*.yaml (your improved prompts - not in Git)
|
1. prompts/local/*.yaml (your improved prompts - not in Git)
|
||||||
@@ -22,7 +22,7 @@ from typing import Optional, Dict, Any
|
|||||||
|
|
||||||
|
|
||||||
# Base paths
|
# Base paths
|
||||||
BASE_DIR = Path(__file__).parent.parent.parent # Predix/
|
BASE_DIR = Path(__file__).parent.parent.parent # NexQuant/
|
||||||
PROMPTS_DIR = BASE_DIR / "prompts"
|
PROMPTS_DIR = BASE_DIR / "prompts"
|
||||||
LOCAL_PROMPTS_DIR = PROMPTS_DIR / "local"
|
LOCAL_PROMPTS_DIR = PROMPTS_DIR / "local"
|
||||||
STANDARD_PROMPTS_FILE = PROMPTS_DIR / "standard_prompts.yaml"
|
STANDARD_PROMPTS_FILE = PROMPTS_DIR / "standard_prompts.yaml"
|
||||||
|
|||||||
+44
-3
@@ -4,6 +4,7 @@ import functools
|
|||||||
import importlib
|
import importlib
|
||||||
import json
|
import json
|
||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
import random
|
import random
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
@@ -82,10 +83,24 @@ def import_class(class_path: str) -> Any:
|
|||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
class of `class_path`
|
class of `class_path`
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
ImportError
|
||||||
|
If module or class cannot be found.
|
||||||
"""
|
"""
|
||||||
module_path, class_name = class_path.rsplit(".", 1)
|
try:
|
||||||
module = importlib.import_module(module_path)
|
module_path, class_name = class_path.rsplit(".", 1)
|
||||||
return getattr(module, class_name)
|
except ValueError:
|
||||||
|
raise ImportError(f"Invalid class path: {class_path!r}")
|
||||||
|
try:
|
||||||
|
module = importlib.import_module(module_path)
|
||||||
|
except ModuleNotFoundError as e:
|
||||||
|
raise ImportError(f"Module not found: {module_path!r}") from e
|
||||||
|
try:
|
||||||
|
return getattr(module, class_name)
|
||||||
|
except AttributeError as e:
|
||||||
|
raise ImportError(f"Class not found: {class_name!r} in {module_path!r}") from e
|
||||||
|
|
||||||
|
|
||||||
class CacheSeedGen:
|
class CacheSeedGen:
|
||||||
@@ -208,3 +223,29 @@ def cache_with_pickle(hash_func: Callable, post_process_func: Callable | None =
|
|||||||
return cache_wrapper
|
return cache_wrapper
|
||||||
|
|
||||||
return cache_decorator
|
return cache_decorator
|
||||||
|
|
||||||
|
|
||||||
|
def safe_resolve_path(user_path: Path | str, safe_root: Path | str | None = None) -> Path:
|
||||||
|
"""Resolve a user-provided path safely against an allowed root directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_path: Path provided by user/LLM/config
|
||||||
|
safe_root: If provided, the resolved path must be within this directory
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If path resolves outside safe_root
|
||||||
|
OSError: If path cannot be resolved
|
||||||
|
"""
|
||||||
|
resolved = Path(user_path).expanduser().resolve()
|
||||||
|
|
||||||
|
if safe_root is not None:
|
||||||
|
root_resolved = Path(safe_root).expanduser().resolve()
|
||||||
|
try:
|
||||||
|
resolved.relative_to(root_resolved)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(
|
||||||
|
f"Path {user_path} resolves to {resolved}, "
|
||||||
|
f"outside allowed root {root_resolved}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return resolved
|
||||||
|
|||||||
+22
-15
@@ -27,6 +27,7 @@ Usage:
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json as _json
|
import json as _json
|
||||||
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
@@ -36,21 +37,24 @@ from typing import Any
|
|||||||
|
|
||||||
from loguru import logger as _root
|
from loguru import logger as _root
|
||||||
|
|
||||||
# ── paths ─────────────────────────────────────────────────────────────────────
|
# ── paths ─────────────────────────────────────────────────────────────────────────────────
|
||||||
LOGS_ROOT: Path = Path(__file__).parent.parent.parent / "logs"
|
LOGS_ROOT: Path = Path(__file__).parent.parent.parent / "logs"
|
||||||
|
|
||||||
# ── format ────────────────────────────────────────────────────────────────────
|
# ── format ────────────────────────────────────────────────────────────────────────────────
|
||||||
_FILE_FMT = (
|
_FILE_FMT = (
|
||||||
"{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {extra[cmd]: <18} | {message}"
|
"{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {extra[cmd]: <18} | {message}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# ── internal state ─────────────────────────────────────────────────────────────
|
# ── internal state ─────────────────────────────────────────────────────────────────────────────
|
||||||
_registered: set[str] = set() # command keys that already have a file sink
|
_registered: set[str] = set() # command keys that already have a file sink
|
||||||
_all_added: bool = False # whether the combined all.log sink is active
|
_all_added: bool = False # whether the combined all.log sink is active
|
||||||
_llm_log_lock = threading.Lock() # guards concurrent writes to llm_calls.jsonl
|
_llm_log_lock = threading.Lock() # guards concurrent writes to llm_calls.jsonl
|
||||||
|
|
||||||
|
# Maximum characters stored per field in llm_calls.jsonl to prevent GB-scale files.
|
||||||
|
_LLM_CALL_MAX_CHARS = 500
|
||||||
|
|
||||||
# ── helpers ───────────────────────────────────────────────────────────────────
|
|
||||||
|
# ── helpers ────────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _today_dir() -> Path:
|
def _today_dir() -> Path:
|
||||||
d = LOGS_ROOT / datetime.now().strftime("%Y-%m-%d")
|
d = LOGS_ROOT / datetime.now().strftime("%Y-%m-%d")
|
||||||
@@ -79,7 +83,7 @@ def _banner(log, title: str, meta: dict[str, Any]) -> None:
|
|||||||
log.info(sep)
|
log.info(sep)
|
||||||
|
|
||||||
|
|
||||||
# ── public API ────────────────────────────────────────────────────────────────
|
# ── public API ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def log_llm_call(
|
def log_llm_call(
|
||||||
system: str | None,
|
system: str | None,
|
||||||
@@ -88,16 +92,19 @@ def log_llm_call(
|
|||||||
start_time: Any = None,
|
start_time: Any = None,
|
||||||
end_time: Any = None,
|
end_time: Any = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Append one complete LLM call to logs/YYYY-MM-DD/llm_calls.jsonl.
|
"""Append one LLM call summary to logs/YYYY-MM-DD/llm_calls.jsonl.
|
||||||
|
|
||||||
|
Prompt/response content is capped at _LLM_CALL_MAX_CHARS to prevent
|
||||||
|
GB-scale log files from long-running loops.
|
||||||
|
|
||||||
Each line is a self-contained JSON object so the file is grep/jq-friendly:
|
Each line is a self-contained JSON object so the file is grep/jq-friendly:
|
||||||
jq 'select(.duration_ms > 5000)' logs/2026-04-17/llm_calls.jsonl
|
jq 'select(.duration_ms > 5000)' logs/2026-04-17/llm_calls.jsonl
|
||||||
"""
|
"""
|
||||||
entry: dict[str, Any] = {
|
entry: dict[str, Any] = {
|
||||||
"ts": datetime.now().isoformat(timespec="milliseconds"),
|
"ts": datetime.now().isoformat(timespec="milliseconds"),
|
||||||
"system": system or "",
|
"system": (system or "")[:_LLM_CALL_MAX_CHARS],
|
||||||
"user": user,
|
"user": user[:_LLM_CALL_MAX_CHARS],
|
||||||
"response": response,
|
"response": response[:_LLM_CALL_MAX_CHARS],
|
||||||
}
|
}
|
||||||
if start_time is not None and end_time is not None:
|
if start_time is not None and end_time is not None:
|
||||||
try:
|
try:
|
||||||
@@ -130,13 +137,13 @@ def setup(command: str, **context: Any):
|
|||||||
key = command.lower()
|
key = command.lower()
|
||||||
|
|
||||||
if key not in _registered:
|
if key not in _registered:
|
||||||
# Per-command rotating file
|
|
||||||
_root.add(
|
_root.add(
|
||||||
str(log_dir / f"{key}.log"),
|
str(log_dir / f"{key}.log"),
|
||||||
format=_FILE_FMT,
|
format=_FILE_FMT,
|
||||||
filter=lambda r, k=key: r["extra"].get("cmd", "").lower() == k,
|
filter=lambda r, k=key: r["extra"].get("cmd", "").lower() == k,
|
||||||
rotation="00:00", # new file at midnight
|
rotation="50 MB",
|
||||||
retention="30 days",
|
compression="gz",
|
||||||
|
retention="7 days",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
enqueue=True,
|
enqueue=True,
|
||||||
backtrace=False,
|
backtrace=False,
|
||||||
@@ -145,13 +152,13 @@ def setup(command: str, **context: Any):
|
|||||||
_registered.add(key)
|
_registered.add(key)
|
||||||
|
|
||||||
if not _all_added:
|
if not _all_added:
|
||||||
# Combined log — all commands
|
|
||||||
_root.add(
|
_root.add(
|
||||||
str(log_dir / "all.log"),
|
str(log_dir / "all.log"),
|
||||||
format=_FILE_FMT,
|
format=_FILE_FMT,
|
||||||
filter=lambda r: "cmd" in r["extra"],
|
filter=lambda r: "cmd" in r["extra"],
|
||||||
rotation="00:00",
|
rotation="100 MB",
|
||||||
retention="60 days",
|
compression="gz",
|
||||||
|
retention="7 days",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
enqueue=True,
|
enqueue=True,
|
||||||
backtrace=False,
|
backtrace=False,
|
||||||
|
|||||||
@@ -160,6 +160,9 @@ class RDAgentLog(SingletonBaseClass):
|
|||||||
log_func = getattr(patched_logger, level)
|
log_func = getattr(patched_logger, level)
|
||||||
log_func(msg)
|
log_func(msg)
|
||||||
|
|
||||||
|
def debug(self, msg: str, *, tag: str = "", raw: bool = False) -> None:
|
||||||
|
self._log("debug", msg, tag=tag, raw=raw)
|
||||||
|
|
||||||
def info(self, msg: str, *, tag: str = "", raw: bool = False) -> None:
|
def info(self, msg: str, *, tag: str = "", raw: bool = False) -> None:
|
||||||
self._log("info", msg, tag=tag, raw=raw)
|
self._log("info", msg, tag=tag, raw=raw)
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,306 @@
|
|||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import pickle # nosec
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
from streamlit import session_state
|
||||||
|
|
||||||
|
from rdagent.log.ui.conf import UI_SETTING
|
||||||
|
from rdagent.log.utils import extract_evoid, extract_loopid_func_name
|
||||||
|
|
||||||
|
st.set_page_config(layout="wide", page_title="debug_llm", page_icon="🎓", initial_sidebar_state="expanded")
|
||||||
|
|
||||||
|
# 获取 log_path 参数
|
||||||
|
parser = argparse.ArgumentParser(description="RD-Agent Streamlit App")
|
||||||
|
parser.add_argument("--log_dir", type=str, help="Path to the log directory")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def get_folders_sorted(log_path):
|
||||||
|
"""缓存并返回排序后的文件夹列表,并加入进度打印"""
|
||||||
|
with st.spinner("正在加载文件夹列表..."):
|
||||||
|
folders = sorted(
|
||||||
|
(folder for folder in log_path.iterdir() if folder.is_dir() and list(folder.iterdir())),
|
||||||
|
key=lambda folder: folder.stat().st_mtime,
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
|
st.write(f"找到 {len(folders)} 个文件夹")
|
||||||
|
return [folder.name for folder in folders]
|
||||||
|
|
||||||
|
|
||||||
|
if UI_SETTING.enable_cache:
|
||||||
|
get_folders_sorted = st.cache_data(get_folders_sorted)
|
||||||
|
|
||||||
|
|
||||||
|
# 设置主日志路径
|
||||||
|
main_log_path = Path(args.log_dir) if args.log_dir else Path("./log")
|
||||||
|
if not main_log_path.exists():
|
||||||
|
st.error(f"Log dir {main_log_path} does not exist!")
|
||||||
|
st.stop()
|
||||||
|
|
||||||
|
if "data" not in session_state:
|
||||||
|
session_state.data = []
|
||||||
|
if "log_path" not in session_state:
|
||||||
|
session_state.log_path = None
|
||||||
|
|
||||||
|
tlist = []
|
||||||
|
|
||||||
|
|
||||||
|
def load_data():
|
||||||
|
"""加载数据到 session_state 并显示进度"""
|
||||||
|
log_file = main_log_path / session_state.log_path / "debug_llm.pkl"
|
||||||
|
try:
|
||||||
|
with st.spinner(f"正在加载数据文件 {log_file}..."):
|
||||||
|
start_time = time.time()
|
||||||
|
with open(log_file, "rb") as f:
|
||||||
|
session_state.data = pickle.load(f, encoding="utf-8") # nosec
|
||||||
|
st.success(f"数据加载完成!耗时 {time.time() - start_time:.2f} 秒")
|
||||||
|
st.session_state["current_loop"] = 1
|
||||||
|
except Exception as e:
|
||||||
|
session_state.data = [{"error": str(e)}]
|
||||||
|
st.error(f"加载数据失败: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
# UI - Sidebar
|
||||||
|
with st.sidebar:
|
||||||
|
st.markdown(":blue[**Log Path**]")
|
||||||
|
manually = st.toggle("Manual Input")
|
||||||
|
if manually:
|
||||||
|
st.text_input("log path", key="log_path", label_visibility="collapsed")
|
||||||
|
else:
|
||||||
|
folders = get_folders_sorted(main_log_path)
|
||||||
|
st.selectbox(f"**Select from {main_log_path.absolute()}**", folders, key="log_path") # nosec B608 — not SQL, Bandit false positive on "Select" in UI label
|
||||||
|
|
||||||
|
if st.button("Refresh Data"):
|
||||||
|
load_data()
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
|
||||||
|
# Helper functions
|
||||||
|
def show_text(text, lang=None):
|
||||||
|
"""显示文本代码块"""
|
||||||
|
if lang:
|
||||||
|
st.code(text, language=lang, wrap_lines=True)
|
||||||
|
elif "\n" in text:
|
||||||
|
st.code(text, language="python", wrap_lines=True)
|
||||||
|
else:
|
||||||
|
st.code(text, language="html", wrap_lines=True)
|
||||||
|
|
||||||
|
|
||||||
|
def highlight_prompts_uri(uri):
|
||||||
|
"""高亮 URI 的格式"""
|
||||||
|
parts = uri.split(":")
|
||||||
|
return f"**{parts[0]}:**:green[**{parts[1]}**]"
|
||||||
|
|
||||||
|
|
||||||
|
# Display Data
|
||||||
|
progress_text = st.empty()
|
||||||
|
progress_bar = st.progress(0)
|
||||||
|
|
||||||
|
# 每页展示一个 Loop
|
||||||
|
LOOPS_PER_PAGE = 1
|
||||||
|
|
||||||
|
# 获取所有的 Loop ID
|
||||||
|
loop_groups = {}
|
||||||
|
for i, d in enumerate(session_state.data):
|
||||||
|
tag = d["tag"]
|
||||||
|
loop_id, _ = extract_loopid_func_name(tag)
|
||||||
|
if loop_id:
|
||||||
|
if loop_id not in loop_groups:
|
||||||
|
loop_groups[loop_id] = []
|
||||||
|
loop_groups[loop_id].append(d)
|
||||||
|
|
||||||
|
# 按 Loop ID 排序
|
||||||
|
sorted_loop_ids = sorted(loop_groups.keys(), key=int) # 假设 Loop ID 是数字
|
||||||
|
total_loops = len(sorted_loop_ids)
|
||||||
|
total_pages = total_loops # 每页展示一个 Loop
|
||||||
|
|
||||||
|
|
||||||
|
# simple display
|
||||||
|
# FIXME: Delete this simple UI if trace have tag(evo_id & loop_id)
|
||||||
|
# with st.sidebar:
|
||||||
|
# start = int(st.text_input("start", 0))
|
||||||
|
# end = int(st.text_input("end", 100))
|
||||||
|
# for m in session_state.data[start:end]:
|
||||||
|
# if "tpl" in m["tag"]:
|
||||||
|
# obj = m["obj"]
|
||||||
|
# uri = obj["uri"]
|
||||||
|
# tpl = obj["template"]
|
||||||
|
# cxt = obj["context"]
|
||||||
|
# rd = obj["rendered"]
|
||||||
|
# with st.expander(highlight_prompts_uri(uri), expanded=False, icon="⚙️"):
|
||||||
|
# t1, t2, t3 = st.tabs([":green[**Rendered**]", ":blue[**Template**]", ":orange[**Context**]"])
|
||||||
|
# with t1:
|
||||||
|
# show_text(rd)
|
||||||
|
# with t2:
|
||||||
|
# show_text(tpl, lang="django")
|
||||||
|
# with t3:
|
||||||
|
# st.json(cxt)
|
||||||
|
# if "llm" in m["tag"]:
|
||||||
|
# obj = m["obj"]
|
||||||
|
# system = obj.get("system", None)
|
||||||
|
# user = obj["user"]
|
||||||
|
# resp = obj["resp"]
|
||||||
|
# with st.expander(f"**LLM**", expanded=False, icon="🤖"):
|
||||||
|
# t1, t2, t3 = st.tabs([":green[**Response**]", ":blue[**User**]", ":orange[**System**]"])
|
||||||
|
# with t1:
|
||||||
|
# try:
|
||||||
|
# rdict = json.loads(resp)
|
||||||
|
# if "code" in rdict:
|
||||||
|
# code = rdict["code"]
|
||||||
|
# st.markdown(":red[**Code in response dict:**]")
|
||||||
|
# st.code(code, language="python", wrap_lines=True, line_numbers=True)
|
||||||
|
# rdict.pop("code")
|
||||||
|
# elif "spec" in rdict:
|
||||||
|
# spec = rdict["spec"]
|
||||||
|
# st.markdown(":red[**Spec in response dict:**]")
|
||||||
|
# st.markdown(spec)
|
||||||
|
# rdict.pop("spec")
|
||||||
|
# else:
|
||||||
|
# # show model codes
|
||||||
|
# showed_keys = []
|
||||||
|
# for k, v in rdict.items():
|
||||||
|
# if k.startswith("model_") and k.endswith(".py"):
|
||||||
|
# st.markdown(f":red[**{k}**]")
|
||||||
|
# st.code(v, language="python", wrap_lines=True, line_numbers=True)
|
||||||
|
# showed_keys.append(k)
|
||||||
|
# for k in showed_keys:
|
||||||
|
# rdict.pop(k)
|
||||||
|
# st.write(":red[**Other parts (except for the code or spec) in response dict:**]")
|
||||||
|
# st.json(rdict)
|
||||||
|
# except:
|
||||||
|
# st.json(resp)
|
||||||
|
# with t2:
|
||||||
|
# show_text(user)
|
||||||
|
# with t3:
|
||||||
|
# show_text(system or "No system prompt available")
|
||||||
|
|
||||||
|
|
||||||
|
if total_pages:
|
||||||
|
# 初始化 current_loop
|
||||||
|
if "current_loop" not in st.session_state:
|
||||||
|
st.session_state["current_loop"] = 1
|
||||||
|
|
||||||
|
# Loop 导航按钮
|
||||||
|
col1, col2, col3, col4, col5 = st.sidebar.columns([1.2, 1, 2, 1, 1.2])
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
if st.button("|<"): # 首页
|
||||||
|
st.session_state["current_loop"] = 1
|
||||||
|
with col2:
|
||||||
|
if st.button("<") and st.session_state["current_loop"] > 1: # 上一页
|
||||||
|
st.session_state["current_loop"] -= 1
|
||||||
|
with col3:
|
||||||
|
# 下拉列表显示所有 Loop
|
||||||
|
st.session_state["current_loop"] = st.selectbox(
|
||||||
|
"选择 Loop",
|
||||||
|
options=list(range(1, total_loops + 1)),
|
||||||
|
index=st.session_state["current_loop"] - 1, # 默认选中当前 Loop
|
||||||
|
label_visibility="collapsed", # 隐藏标签
|
||||||
|
)
|
||||||
|
with col4:
|
||||||
|
if st.button("\>") and st.session_state["current_loop"] < total_loops: # 下一页
|
||||||
|
st.session_state["current_loop"] += 1
|
||||||
|
with col5:
|
||||||
|
if st.button("\>|"): # 最后一页
|
||||||
|
st.session_state["current_loop"] = total_loops
|
||||||
|
|
||||||
|
# 获取当前 Loop
|
||||||
|
current_loop = st.session_state["current_loop"]
|
||||||
|
|
||||||
|
# 渲染当前 Loop 数据
|
||||||
|
loop_id = sorted_loop_ids[current_loop - 1]
|
||||||
|
progress_text = st.empty()
|
||||||
|
progress_text.text(f"正在处理 Loop {loop_id}...")
|
||||||
|
progress_bar.progress(current_loop / total_loops, text=f"Loop :green[**{current_loop}**] / {total_loops}")
|
||||||
|
|
||||||
|
# 渲染 Loop Header
|
||||||
|
loop_anchor = f"Loop_{loop_id}"
|
||||||
|
if loop_anchor not in tlist:
|
||||||
|
tlist.append(loop_anchor)
|
||||||
|
st.header(loop_anchor, anchor=loop_anchor, divider="blue")
|
||||||
|
|
||||||
|
# 渲染当前 Loop 的所有数据
|
||||||
|
loop_data = loop_groups[loop_id]
|
||||||
|
for d in loop_data:
|
||||||
|
tag = d["tag"]
|
||||||
|
obj = d["obj"]
|
||||||
|
_, func_name = extract_loopid_func_name(tag)
|
||||||
|
evo_id = extract_evoid(tag)
|
||||||
|
|
||||||
|
func_anchor = f"loop_{loop_id}.{func_name}"
|
||||||
|
if func_anchor not in tlist:
|
||||||
|
tlist.append(func_anchor)
|
||||||
|
st.header(f"in *{func_name}*", anchor=func_anchor, divider="green")
|
||||||
|
|
||||||
|
evo_anchor = f"loop_{loop_id}.evo_step_{evo_id}"
|
||||||
|
if evo_id and evo_anchor not in tlist:
|
||||||
|
tlist.append(evo_anchor)
|
||||||
|
st.subheader(f"evo_step_{evo_id}", anchor=evo_anchor, divider="orange")
|
||||||
|
|
||||||
|
# 根据 tag 渲染内容
|
||||||
|
if "debug_exp_gen" in tag:
|
||||||
|
with st.expander(
|
||||||
|
f"Exp in :violet[**{obj.experiment_workspace.workspace_path}**]", expanded=False, icon="🧩"
|
||||||
|
):
|
||||||
|
st.write(obj)
|
||||||
|
elif "debug_tpl" in tag:
|
||||||
|
uri = obj["uri"]
|
||||||
|
tpl = obj["template"]
|
||||||
|
cxt = obj["context"]
|
||||||
|
rd = obj["rendered"]
|
||||||
|
with st.expander(highlight_prompts_uri(uri), expanded=False, icon="⚙️"):
|
||||||
|
t1, t2, t3 = st.tabs([":green[**Rendered**]", ":blue[**Template**]", ":orange[**Context**]"])
|
||||||
|
with t1:
|
||||||
|
show_text(rd)
|
||||||
|
with t2:
|
||||||
|
show_text(tpl, lang="django")
|
||||||
|
with t3:
|
||||||
|
st.json(cxt)
|
||||||
|
elif "debug_llm" in tag:
|
||||||
|
system = obj.get("system", None)
|
||||||
|
user = obj["user"]
|
||||||
|
resp = obj["resp"]
|
||||||
|
with st.expander(f"**LLM**", expanded=False, icon="🤖"):
|
||||||
|
t1, t2, t3 = st.tabs([":green[**Response**]", ":blue[**User**]", ":orange[**System**]"])
|
||||||
|
with t1:
|
||||||
|
try:
|
||||||
|
rdict = json.loads(resp)
|
||||||
|
if "code" in rdict:
|
||||||
|
code = rdict["code"]
|
||||||
|
st.markdown(":red[**Code in response dict:**]")
|
||||||
|
st.code(code, language="python", wrap_lines=True, line_numbers=True)
|
||||||
|
rdict.pop("code")
|
||||||
|
elif "spec" in rdict:
|
||||||
|
spec = rdict["spec"]
|
||||||
|
st.markdown(":red[**Spec in response dict:**]")
|
||||||
|
st.markdown(spec)
|
||||||
|
rdict.pop("spec")
|
||||||
|
else:
|
||||||
|
# show model codes
|
||||||
|
showed_keys = []
|
||||||
|
for k, v in rdict.items():
|
||||||
|
if k.startswith("model_") and k.endswith(".py"):
|
||||||
|
st.markdown(f":red[**{k}**]")
|
||||||
|
st.code(v, language="python", wrap_lines=True, line_numbers=True)
|
||||||
|
showed_keys.append(k)
|
||||||
|
for k in showed_keys:
|
||||||
|
rdict.pop(k)
|
||||||
|
st.write(":red[**Other parts (except for the code or spec) in response dict:**]")
|
||||||
|
st.json(rdict)
|
||||||
|
except:
|
||||||
|
st.json(resp)
|
||||||
|
with t2:
|
||||||
|
show_text(user)
|
||||||
|
with t3:
|
||||||
|
show_text(system or "No system prompt available")
|
||||||
|
|
||||||
|
progress_text.text("当前 Loop 数据处理完成!")
|
||||||
|
|
||||||
|
# Sidebar TOC
|
||||||
|
with st.sidebar:
|
||||||
|
toc = "\n".join([f"- [{t}](#{t})" if t.startswith("L") else f" - [{t.split('.')[1]}](#{t})" for t in tlist])
|
||||||
|
st.markdown(toc, unsafe_allow_html=True)
|
||||||
@@ -541,7 +541,8 @@ class APIBackend(ABC):
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
) -> str | list[list[float]]:
|
) -> str | list[list[float]]:
|
||||||
"""This function to share operation between embedding and chat completion"""
|
"""This function to share operation between embedding and chat completion"""
|
||||||
assert not (chat_completion and embedding), "chat_completion and embedding cannot be True at the same time"
|
if chat_completion and embedding:
|
||||||
|
raise ValueError("chat_completion and embedding cannot be True at the same time")
|
||||||
max_retry = LLM_SETTINGS.max_retry if LLM_SETTINGS.max_retry is not None else max_retry
|
max_retry = LLM_SETTINGS.max_retry if LLM_SETTINGS.max_retry is not None else max_retry
|
||||||
timeout_count = 0
|
timeout_count = 0
|
||||||
violation_count = 0
|
violation_count = 0
|
||||||
@@ -584,6 +585,24 @@ class APIBackend(ABC):
|
|||||||
f"Original error: {e}"
|
f"Original error: {e}"
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
|
# Handle llama.cpp 400: "Cannot have 2 or more assistant messages at the end of the list"
|
||||||
|
if (
|
||||||
|
openai_imported
|
||||||
|
and isinstance(e, openai.BadRequestError)
|
||||||
|
and hasattr(e, "message")
|
||||||
|
and "Cannot have 2 or more assistant messages" in e.message
|
||||||
|
):
|
||||||
|
if "messages" in kwargs:
|
||||||
|
merged = []
|
||||||
|
for msg in kwargs["messages"]:
|
||||||
|
if merged and merged[-1]["role"] == "assistant" and msg["role"] == "assistant":
|
||||||
|
merged[-1]["content"] += "\n" + msg["content"]
|
||||||
|
else:
|
||||||
|
merged.append(msg)
|
||||||
|
kwargs["messages"] = merged
|
||||||
|
logger.warning("Fixed consecutive assistant messages, retrying...")
|
||||||
|
continue
|
||||||
|
|
||||||
if embedding and too_long_error_message:
|
if embedding and too_long_error_message:
|
||||||
if not embedding_truncated:
|
if not embedding_truncated:
|
||||||
# Handle embedding text too long error - truncate once and retry
|
# Handle embedding text too long error - truncate once and retry
|
||||||
@@ -653,9 +672,11 @@ class APIBackend(ABC):
|
|||||||
add json related content in the prompt if add_json_in_prompt is True
|
add json related content in the prompt if add_json_in_prompt is True
|
||||||
"""
|
"""
|
||||||
for message in messages[::-1]:
|
for message in messages[::-1]:
|
||||||
message["content"] = message["content"] + "\nPlease respond in json format."
|
if message["role"] == "user":
|
||||||
|
message["content"] = message["content"] + "\nPlease respond in json format."
|
||||||
|
break
|
||||||
if message["role"] == LLM_SETTINGS.system_prompt_role:
|
if message["role"] == LLM_SETTINGS.system_prompt_role:
|
||||||
# NOTE: assumption: systemprompt is always the first message
|
message["content"] = message["content"] + "\nPlease respond in json format."
|
||||||
break
|
break
|
||||||
|
|
||||||
def _create_chat_completion_auto_continue(
|
def _create_chat_completion_auto_continue(
|
||||||
|
|||||||
@@ -36,16 +36,18 @@ def get_agent_model() -> OpenAIChatModel:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
backend = APIBackend()
|
backend = APIBackend()
|
||||||
assert isinstance(backend, LiteLLMAPIBackend), "Only LiteLLMAPIBackend is supported"
|
if not isinstance(backend, LiteLLMAPIBackend):
|
||||||
|
raise TypeError("Only LiteLLMAPIBackend is supported")
|
||||||
|
|
||||||
compl_kwargs = backend.get_complete_kwargs()
|
compl_kwargs = backend.get_complete_kwargs()
|
||||||
|
|
||||||
selected_model = compl_kwargs["model"]
|
selected_model = compl_kwargs["model"]
|
||||||
|
|
||||||
_, custom_llm_provider, _, _ = get_llm_provider(selected_model)
|
_, custom_llm_provider, _, _ = get_llm_provider(selected_model)
|
||||||
assert (
|
if custom_llm_provider not in PROVIDER_TO_ENV_MAP:
|
||||||
custom_llm_provider in PROVIDER_TO_ENV_MAP
|
raise ValueError(
|
||||||
), f"Provider {custom_llm_provider} not supported. Please add it into `PROVIDER_TO_ENV_MAP`"
|
f"Provider {custom_llm_provider} not supported. Please add it into `PROVIDER_TO_ENV_MAP`"
|
||||||
|
)
|
||||||
prefix = PROVIDER_TO_ENV_MAP[custom_llm_provider]
|
prefix = PROVIDER_TO_ENV_MAP[custom_llm_provider]
|
||||||
api_key = os.getenv(f"{prefix}_API_KEY", None)
|
api_key = os.getenv(f"{prefix}_API_KEY", None)
|
||||||
api_base = os.getenv(f"{prefix}_API_BASE", None)
|
api_base = os.getenv(f"{prefix}_API_BASE", None)
|
||||||
|
|||||||
@@ -268,7 +268,8 @@ class JsonReducer(DataReducer):
|
|||||||
parent[key] = sampled # type: ignore # parent 是 list,key 是 index, list.__setitem__(key, sampled)
|
parent[key] = sampled # type: ignore # parent 是 list,key 是 index, list.__setitem__(key, sampled)
|
||||||
self.sampled_files.extend([self.extract_filename(i) for i in sampled])
|
self.sampled_files.extend([self.extract_filename(i) for i in sampled])
|
||||||
break
|
break
|
||||||
assert len(self.sampled_files) > 0
|
if len(self.sampled_files) <= 0:
|
||||||
|
raise AssertionError("sampled_files must contain at least one file")
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def _find_all_lists(
|
def _find_all_lists(
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ from sklearn.metrics import roc_auc_score
|
|||||||
def prepare_for_auroc_metric(submission: pd.DataFrame, answers: pd.DataFrame, id_col: str, target_col: str) -> dict:
|
def prepare_for_auroc_metric(submission: pd.DataFrame, answers: pd.DataFrame, id_col: str, target_col: str) -> dict:
|
||||||
|
|
||||||
# Answers checks
|
# Answers checks
|
||||||
assert id_col in answers.columns, f"answers dataframe should have an {id_col} column"
|
if id_col not in answers.columns:
|
||||||
assert target_col in answers.columns, f"answers dataframe should have a {target_col} column"
|
raise InvalidSubmissionError(f"answers dataframe should have an {id_col} column")
|
||||||
|
if target_col not in answers.columns:
|
||||||
|
raise InvalidSubmissionError(f"answers dataframe should have a {target_col} column")
|
||||||
|
|
||||||
# Submission checks
|
# Submission checks
|
||||||
if id_col not in submission.columns:
|
if id_col not in submission.columns:
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Check if our submission file exists
|
# Check if our submission file exists
|
||||||
assert Path("submission.csv").exists(), "Error: submission.csv not found"
|
if not Path("submission.csv").exists():
|
||||||
|
raise FileNotFoundError("Error: submission.csv not found")
|
||||||
|
|
||||||
submission_lines = Path("submission.csv").read_text().splitlines()
|
submission_lines = Path("submission.csv").read_text().splitlines()
|
||||||
test_lines = Path("submission_test.csv").read_text().splitlines()
|
test_lines = Path("submission_test.csv").read_text().splitlines()
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ def prepare_for_metric(submission: pd.DataFrame, answers: pd.DataFrame) -> dict:
|
|||||||
if "price" not in submission.columns:
|
if "price" not in submission.columns:
|
||||||
raise InvalidSubmissionError("Submission DataFrame must contain 'price' columns.")
|
raise InvalidSubmissionError("Submission DataFrame must contain 'price' columns.")
|
||||||
|
|
||||||
assert "price" in answers.columns, "Answers DataFrame must contain 'price' columns."
|
if "price" not in answers.columns:
|
||||||
|
raise InvalidSubmissionError("Answers DataFrame must contain 'price' columns.")
|
||||||
|
|
||||||
if len(submission) != len(answers):
|
if len(submission) != len(answers):
|
||||||
raise InvalidSubmissionError("Submission must be the same length as the answers.")
|
raise InvalidSubmissionError("Submission must be the same length as the answers.")
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Check if our submission file exists
|
# Check if our submission file exists
|
||||||
assert Path("submission.csv").exists(), "Error: submission.csv not found"
|
if not Path("submission.csv").exists():
|
||||||
|
raise FileNotFoundError("Error: submission.csv not found")
|
||||||
|
|
||||||
submission_lines = Path("submission.csv").read_text().splitlines() # 自动生成的
|
submission_lines = Path("submission.csv").read_text().splitlines() # 自动生成的
|
||||||
test_lines = Path("submission_test.csv").read_text().splitlines() # test.csv
|
test_lines = Path("submission_test.csv").read_text().splitlines() # test.csv
|
||||||
|
|||||||
+14
-11
@@ -56,14 +56,17 @@ sparse.save_npz(public / "test" / "X.npz", X_test)
|
|||||||
sparse.save_npz(public / "train" / "X.npz", X_train)
|
sparse.save_npz(public / "train" / "X.npz", X_train)
|
||||||
df_train.to_csv(public / "train" / "ARF_12h.csv", index=False)
|
df_train.to_csv(public / "train" / "ARF_12h.csv", index=False)
|
||||||
|
|
||||||
assert (
|
if X_train.shape[0] != df_train.shape[0]:
|
||||||
X_train.shape[0] == df_train.shape[0]
|
raise ValueError(
|
||||||
), f"Mismatch: X_train rows ({X_train.shape[0]}) != df_train rows ({df_train.shape[0]})"
|
f"Mismatch: X_train rows ({X_train.shape[0]}) != df_train rows ({df_train.shape[0]})"
|
||||||
assert (
|
)
|
||||||
X_test.shape[0] == df_test.shape[0]
|
if X_test.shape[0] != df_test.shape[0]:
|
||||||
), f"Mismatch: X_test rows ({X_test.shape[0]}) != df_test rows ({df_test.shape[0]})"
|
raise ValueError(
|
||||||
assert df_test.shape[1] == 2, "Public test set should have 2 columns"
|
f"Mismatch: X_test rows ({X_test.shape[0]}) != df_test rows ({df_test.shape[0]})"
|
||||||
assert df_train.shape[1] == 3, "Public train set should have 3 columns"
|
)
|
||||||
assert len(df_train) + len(df_test) == len(
|
if df_test.shape[1] != 2:
|
||||||
df_label
|
raise ValueError("Public test set should have 2 columns")
|
||||||
), "Length of new_train and new_test should equal length of old_train"
|
if df_train.shape[1] != 3:
|
||||||
|
raise ValueError("Public train set should have 3 columns")
|
||||||
|
if len(df_train) + len(df_test) != len(df_label):
|
||||||
|
raise ValueError("Length of new_train and new_test should equal length of old_train")
|
||||||
|
|||||||
+6
-5
@@ -25,11 +25,12 @@ def prepare(raw: Path, public: Path, private: Path):
|
|||||||
new_test.to_csv(public / "test.csv", index=False)
|
new_test.to_csv(public / "test.csv", index=False)
|
||||||
|
|
||||||
# Checks
|
# Checks
|
||||||
assert new_test.shape[1] == 12, "Public test set should have 12 columns"
|
if new_test.shape[1] != 12:
|
||||||
assert new_train.shape[1] == 13, "Public train set should have 13 columns"
|
raise AssertionError("Public test set should have 12 columns")
|
||||||
assert len(new_train) + len(new_test) == len(
|
if new_train.shape[1] != 13:
|
||||||
old_train
|
raise AssertionError("Public train set should have 13 columns")
|
||||||
), "Length of new_train and new_test should equal length of old_train"
|
if len(new_train) + len(new_test) != len(old_train):
|
||||||
|
raise AssertionError("Length of new_train and new_test should equal length of old_train")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -320,7 +320,8 @@ class DataScienceRDLoop(RDLoop):
|
|||||||
# only clean current workspace without affecting other loops.
|
# only clean current workspace without affecting other loops.
|
||||||
for k in "direct_exp_gen", "coding", "running":
|
for k in "direct_exp_gen", "coding", "running":
|
||||||
if k in prev_out and prev_out[k] is not None:
|
if k in prev_out and prev_out[k] is not None:
|
||||||
assert isinstance(prev_out[k], DSExperiment)
|
if not isinstance(prev_out[k], DSExperiment):
|
||||||
|
raise TypeError(f"prev_out[{k!r}] must be an instance of DSExperiment")
|
||||||
clean_workspace(prev_out[k].experiment_workspace.workspace_path)
|
clean_workspace(prev_out[k].experiment_workspace.workspace_path)
|
||||||
|
|
||||||
# Backup the workspace (only necessary files are included)
|
# Backup the workspace (only necessary files are included)
|
||||||
|
|||||||
@@ -213,7 +213,8 @@ class DSTrace(Trace[DataScienceScen, KnowledgeBase]):
|
|||||||
self, component: COMPONENT, search_list: list[tuple[DSExperiment, ExperimentFeedback]] = []
|
self, component: COMPONENT, search_list: list[tuple[DSExperiment, ExperimentFeedback]] = []
|
||||||
) -> bool:
|
) -> bool:
|
||||||
for exp, fb in search_list:
|
for exp, fb in search_list:
|
||||||
assert isinstance(exp.hypothesis, DSHypothesis), "Hypothesis should be DSHypothesis (and not None)"
|
if not isinstance(exp.hypothesis, DSHypothesis):
|
||||||
|
raise TypeError("Hypothesis should be DSHypothesis (and not None)")
|
||||||
if exp.hypothesis.component == component and fb:
|
if exp.hypothesis.component == component and fb:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ class ExpGen2Hypothesis(DSProposalV2ExpGen):
|
|||||||
|
|
||||||
success_fb_list = list(set(trace_fbs))
|
success_fb_list = list(set(trace_fbs))
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Merge Hypothesis: select {len(success_fb_list)} from {len(trace_fbs)} SOTA experiments found in {len(leaves)} traces"
|
f"Merge Hypothesis: select {len(success_fb_list)} from {len(trace_fbs)} SOTA experiments found in {len(leaves)} traces" # nosec B608 — not SQL, Bandit false positive on "select" in log message
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(success_fb_list) > 0:
|
if len(success_fb_list) > 0:
|
||||||
@@ -377,7 +377,8 @@ class ExpGen2TraceAndMergeV2(ExpGen):
|
|||||||
if DS_RD_SETTING.enable_multi_version_exp_gen:
|
if DS_RD_SETTING.enable_multi_version_exp_gen:
|
||||||
exp_gen_version_list = DS_RD_SETTING.exp_gen_version_list.split(",")
|
exp_gen_version_list = DS_RD_SETTING.exp_gen_version_list.split(",")
|
||||||
for version in exp_gen_version_list:
|
for version in exp_gen_version_list:
|
||||||
assert version in ["v3", "v2", "v1"]
|
if version not in ["v3", "v2", "v1"]:
|
||||||
|
raise ValueError(f"version must be 'v1', 'v2', or 'v3', got {version!r}")
|
||||||
|
|
||||||
if len(trace.hist) == 0:
|
if len(trace.hist) == 0:
|
||||||
# set the proposal version for the first sub-trace
|
# set the proposal version for the first sub-trace
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user