brain-store 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- brain_store-0.1.0/LICENSE +21 -0
- brain_store-0.1.0/PKG-INFO +126 -0
- brain_store-0.1.0/README.md +88 -0
- brain_store-0.1.0/brain_store/__init__.py +18 -0
- brain_store-0.1.0/brain_store/db.py +505 -0
- brain_store-0.1.0/brain_store/py.typed +1 -0
- brain_store-0.1.0/brain_store/store.py +200 -0
- brain_store-0.1.0/brain_store.egg-info/PKG-INFO +126 -0
- brain_store-0.1.0/brain_store.egg-info/SOURCES.txt +14 -0
- brain_store-0.1.0/brain_store.egg-info/dependency_links.txt +1 -0
- brain_store-0.1.0/brain_store.egg-info/requires.txt +13 -0
- brain_store-0.1.0/brain_store.egg-info/top_level.txt +1 -0
- brain_store-0.1.0/pyproject.toml +58 -0
- brain_store-0.1.0/setup.cfg +4 -0
- brain_store-0.1.0/tests/test_db_offline.py +53 -0
- brain_store-0.1.0/tests/test_store_file_mode.py +65 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Xtley001
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: brain-store
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Dual-mode persistence layer (PostgreSQL + file-backed) and RL state store for Brain Alpha pipelines.
|
|
5
|
+
Author: Xtley001
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Xtley001/brain-alpha-pipeline
|
|
8
|
+
Project-URL: Repository, https://github.com/Xtley001/brain-alpha-pipeline
|
|
9
|
+
Project-URL: Documentation, https://github.com/Xtley001/brain-alpha-pipeline/tree/main/brain_libraries
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/Xtley001/brain-alpha-pipeline/issues
|
|
11
|
+
Keywords: worldquant,brain,alpha,quantitative-finance,postgresql,state-persistence,reinforcement-learning,storage
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
21
|
+
Classifier: Topic :: Database
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: brain-core>=0.1.0
|
|
26
|
+
Requires-Dist: psycopg[binary]>=3.1
|
|
27
|
+
Requires-Dist: psycopg_pool>=3.2
|
|
28
|
+
Requires-Dist: alembic>=1.13
|
|
29
|
+
Requires-Dist: numpy>=1.26
|
|
30
|
+
Requires-Dist: python-dotenv>=1.0
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
34
|
+
Requires-Dist: black; extra == "dev"
|
|
35
|
+
Requires-Dist: flake8; extra == "dev"
|
|
36
|
+
Requires-Dist: mypy; extra == "dev"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# brain-store
|
|
40
|
+
|
|
41
|
+
Dual-mode persistence layer (PostgreSQL connection pool + flat-file fallback) and RL state store for Brain Alpha pipelines.
|
|
42
|
+
|
|
43
|
+
[](https://github.com/Xtley001/brain-alpha-pipeline/actions)
|
|
44
|
+
[](../LICENSE)
|
|
45
|
+
[](https://pypi.org/project/brain-store/)
|
|
46
|
+
[](https://www.python.org/downloads/)
|
|
47
|
+
|
|
48
|
+
`brain_store` provides resilient, dual-mode persistence for quantitative alpha pipelines. It connects to Neon PostgreSQL with connection pooling via `psycopg_pool` while falling back automatically to local zero-config JSON/CSV file storage when `DATABASE_URL` is absent. For the complete relational entity-relationship diagram and database migration operations, see [Schema & Migrations](./docs/SCHEMA_AND_MIGRATIONS.md).
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install brain-store
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Quickstart
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from brain_store import AlphaStore
|
|
60
|
+
|
|
61
|
+
# Connects to PostgreSQL if DATABASE_URL is set, otherwise uses ./data/
|
|
62
|
+
store = AlphaStore(data_dir="./data")
|
|
63
|
+
|
|
64
|
+
# Save a qualified alpha
|
|
65
|
+
store.save_qualified_alpha({
|
|
66
|
+
"expression": "group_neutralize(rank(close), subindustry)",
|
|
67
|
+
"sharpe": 1.85,
|
|
68
|
+
"fitness": 1.42,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
# Load historical expressions with pagination to prevent OOM
|
|
72
|
+
evaluated = store.load_evaluated_expressions(limit=500, offset=0)
|
|
73
|
+
print(f"Loaded {len(evaluated)} evaluated expressions.")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
> **Note:** `OptionsStore` is preserved as a backward-compatible alias for `AlphaStore`.
|
|
77
|
+
|
|
78
|
+
## Architecture
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
brain_store/
|
|
82
|
+
├── brain_store/
|
|
83
|
+
│ ├── __init__.py # Public re-exports (AlphaStore, OptionsStore, OptionsDatabase)
|
|
84
|
+
│ ├── db.py # OptionsDatabase — connection pool, 9-table DDL, RL state
|
|
85
|
+
│ ├── store.py # AlphaStore — high-level dual-mode facade
|
|
86
|
+
│ ├── py.typed # PEP 561 typing marker
|
|
87
|
+
│ └── migrations/ # DDL migration scripts
|
|
88
|
+
├── tests/ # Offline unit tests (no PostgreSQL instance required)
|
|
89
|
+
└── docs/
|
|
90
|
+
├── API.md # Complete API specification
|
|
91
|
+
└── SCHEMA_AND_MIGRATIONS.md # Relational schema diagram & migration runbook
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Database Schema
|
|
95
|
+
|
|
96
|
+
| Table | Purpose | Indexing Strategy |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `options_alphas` | Live alpha reserve vault | Unique on `alpha_id`, Index on `status`, `category` |
|
|
99
|
+
| `options_evaluations` | Simulation backtest audit log | Index on `alpha_id`, `simulated_at` |
|
|
100
|
+
| `options_strategy_rl_state` | Strategy-level reinforcement learning operator weights | Primary key on `strategy_name` |
|
|
101
|
+
| `options_learning_memory` | Global Multi-Armed Bandit expression reward tracking | Primary key on `expression_hash` |
|
|
102
|
+
| `options_rejected_alphas` | Diagnostics for checklist / gate-failed alphas | Index on `failure_reason` |
|
|
103
|
+
| `options_correlated_alphas` | Audit log of correlation gate rejections (|rho| >= 0.70) | Index on `candidate_id` |
|
|
104
|
+
| `cluster_session_cache` | Distributed BRAIN auth session token caching (2h TTL) | Primary key on `session_key` |
|
|
105
|
+
| `cluster_run_lock` | Distributed mutex preventing duplicate simulation runs | Primary key on `lock_name` |
|
|
106
|
+
| `org_runs` | Runner heartbeat and telemetry monitoring | Primary key on `run_id` |
|
|
107
|
+
|
|
108
|
+
## Testing
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
pytest tests/ -v
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Tests run offline without requiring a live PostgreSQL instance.
|
|
115
|
+
|
|
116
|
+
## Security
|
|
117
|
+
|
|
118
|
+
Report vulnerabilities per [SECURITY.md](SECURITY.md).
|
|
119
|
+
|
|
120
|
+
## Contributing
|
|
121
|
+
|
|
122
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
Released under the [MIT License](../LICENSE). Maintained by [Xtley001](https://github.com/Xtley001).
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# brain-store
|
|
2
|
+
|
|
3
|
+
Dual-mode persistence layer (PostgreSQL connection pool + flat-file fallback) and RL state store for Brain Alpha pipelines.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/Xtley001/brain-alpha-pipeline/actions)
|
|
6
|
+
[](../LICENSE)
|
|
7
|
+
[](https://pypi.org/project/brain-store/)
|
|
8
|
+
[](https://www.python.org/downloads/)
|
|
9
|
+
|
|
10
|
+
`brain_store` provides resilient, dual-mode persistence for quantitative alpha pipelines. It connects to Neon PostgreSQL with connection pooling via `psycopg_pool` while falling back automatically to local zero-config JSON/CSV file storage when `DATABASE_URL` is absent. For the complete relational entity-relationship diagram and database migration operations, see [Schema & Migrations](./docs/SCHEMA_AND_MIGRATIONS.md).
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install brain-store
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quickstart
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from brain_store import AlphaStore
|
|
22
|
+
|
|
23
|
+
# Connects to PostgreSQL if DATABASE_URL is set, otherwise uses ./data/
|
|
24
|
+
store = AlphaStore(data_dir="./data")
|
|
25
|
+
|
|
26
|
+
# Save a qualified alpha
|
|
27
|
+
store.save_qualified_alpha({
|
|
28
|
+
"expression": "group_neutralize(rank(close), subindustry)",
|
|
29
|
+
"sharpe": 1.85,
|
|
30
|
+
"fitness": 1.42,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
# Load historical expressions with pagination to prevent OOM
|
|
34
|
+
evaluated = store.load_evaluated_expressions(limit=500, offset=0)
|
|
35
|
+
print(f"Loaded {len(evaluated)} evaluated expressions.")
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
> **Note:** `OptionsStore` is preserved as a backward-compatible alias for `AlphaStore`.
|
|
39
|
+
|
|
40
|
+
## Architecture
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
brain_store/
|
|
44
|
+
├── brain_store/
|
|
45
|
+
│ ├── __init__.py # Public re-exports (AlphaStore, OptionsStore, OptionsDatabase)
|
|
46
|
+
│ ├── db.py # OptionsDatabase — connection pool, 9-table DDL, RL state
|
|
47
|
+
│ ├── store.py # AlphaStore — high-level dual-mode facade
|
|
48
|
+
│ ├── py.typed # PEP 561 typing marker
|
|
49
|
+
│ └── migrations/ # DDL migration scripts
|
|
50
|
+
├── tests/ # Offline unit tests (no PostgreSQL instance required)
|
|
51
|
+
└── docs/
|
|
52
|
+
├── API.md # Complete API specification
|
|
53
|
+
└── SCHEMA_AND_MIGRATIONS.md # Relational schema diagram & migration runbook
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Database Schema
|
|
57
|
+
|
|
58
|
+
| Table | Purpose | Indexing Strategy |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| `options_alphas` | Live alpha reserve vault | Unique on `alpha_id`, Index on `status`, `category` |
|
|
61
|
+
| `options_evaluations` | Simulation backtest audit log | Index on `alpha_id`, `simulated_at` |
|
|
62
|
+
| `options_strategy_rl_state` | Strategy-level reinforcement learning operator weights | Primary key on `strategy_name` |
|
|
63
|
+
| `options_learning_memory` | Global Multi-Armed Bandit expression reward tracking | Primary key on `expression_hash` |
|
|
64
|
+
| `options_rejected_alphas` | Diagnostics for checklist / gate-failed alphas | Index on `failure_reason` |
|
|
65
|
+
| `options_correlated_alphas` | Audit log of correlation gate rejections (|rho| >= 0.70) | Index on `candidate_id` |
|
|
66
|
+
| `cluster_session_cache` | Distributed BRAIN auth session token caching (2h TTL) | Primary key on `session_key` |
|
|
67
|
+
| `cluster_run_lock` | Distributed mutex preventing duplicate simulation runs | Primary key on `lock_name` |
|
|
68
|
+
| `org_runs` | Runner heartbeat and telemetry monitoring | Primary key on `run_id` |
|
|
69
|
+
|
|
70
|
+
## Testing
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pytest tests/ -v
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Tests run offline without requiring a live PostgreSQL instance.
|
|
77
|
+
|
|
78
|
+
## Security
|
|
79
|
+
|
|
80
|
+
Report vulnerabilities per [SECURITY.md](SECURITY.md).
|
|
81
|
+
|
|
82
|
+
## Contributing
|
|
83
|
+
|
|
84
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
Released under the [MIT License](../LICENSE). Maintained by [Xtley001](https://github.com/Xtley001).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
brain_store — persistence layer for the Brain Alpha Engine.
|
|
3
|
+
|
|
4
|
+
Public API
|
|
5
|
+
----------
|
|
6
|
+
OptionsDatabase : PostgreSQL adapter. Owns all DDL, DML, and RL state ops.
|
|
7
|
+
AlphaStore : High-level store facade combining CSV/JSON + Postgres.
|
|
8
|
+
OptionsStore : Deprecated alias for AlphaStore (backwards-compatibility).
|
|
9
|
+
"""
|
|
10
|
+
from brain_store.db import OptionsDatabase, map_archetype_to_core
|
|
11
|
+
from brain_store.store import AlphaStore, OptionsStore
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"OptionsDatabase",
|
|
15
|
+
"map_archetype_to_core",
|
|
16
|
+
"AlphaStore",
|
|
17
|
+
"OptionsStore",
|
|
18
|
+
]
|
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PostgreSQL adapter for brain_store.
|
|
3
|
+
|
|
4
|
+
DATABASE SCHEMA — 7 Tables
|
|
5
|
+
==========================
|
|
6
|
+
1. options_alphas — Qualified alpha pool (status: QUALIFIED | SUBMITTED).
|
|
7
|
+
2. options_evaluations — Immutable audit log of every simulation run.
|
|
8
|
+
3. options_learning_memory — MAB reinforcement-learning memory (one row per expression).
|
|
9
|
+
4. options_rejected_alphas — Alphas that failed BRAIN platform checklist gates.
|
|
10
|
+
5. options_correlated_alphas — Alphas that failed self-correlation < 0.70 gate.
|
|
11
|
+
6. cluster_session_cache — Shared BRAIN session token cache across worker orgs.
|
|
12
|
+
7. cluster_run_lock — Cluster-wide mutex: prevents > 1 org simulating at once.
|
|
13
|
+
|
|
14
|
+
All tables use IF NOT EXISTS — safe to run on first boot and on upgrades.
|
|
15
|
+
|
|
16
|
+
This module is extracted from brain_options/store/db.py.
|
|
17
|
+
It owns ALL DDL and DML. No other module may issue raw SQL.
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import logging
|
|
23
|
+
import math
|
|
24
|
+
import re
|
|
25
|
+
import time
|
|
26
|
+
from typing import Any, Dict, List, Optional, Set
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
from psycopg_pool import ConnectionPool
|
|
30
|
+
except ImportError:
|
|
31
|
+
ConnectionPool = None # type: ignore[assignment,misc]
|
|
32
|
+
|
|
33
|
+
log = logging.getLogger("brain_store.db")
|
|
34
|
+
|
|
35
|
+
# ── Schema DDL ────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
SCHEMA_SQL = """
|
|
38
|
+
CREATE TABLE IF NOT EXISTS options_alphas (
|
|
39
|
+
id SERIAL PRIMARY KEY,
|
|
40
|
+
alpha_id VARCHAR(64),
|
|
41
|
+
expression TEXT NOT NULL,
|
|
42
|
+
archetype VARCHAR(128),
|
|
43
|
+
hypothesis TEXT,
|
|
44
|
+
source VARCHAR(32),
|
|
45
|
+
sharpe NUMERIC(8, 4),
|
|
46
|
+
fitness NUMERIC(8, 4),
|
|
47
|
+
turnover NUMERIC(8, 4),
|
|
48
|
+
returns NUMERIC(8, 4),
|
|
49
|
+
drawdown NUMERIC(8, 4),
|
|
50
|
+
margin NUMERIC(10, 6),
|
|
51
|
+
max_correlation NUMERIC(8, 4),
|
|
52
|
+
universe VARCHAR(32),
|
|
53
|
+
neutralization VARCHAR(32),
|
|
54
|
+
delay INTEGER,
|
|
55
|
+
decay INTEGER,
|
|
56
|
+
truncation NUMERIC(6, 4),
|
|
57
|
+
pasteurization VARCHAR(8),
|
|
58
|
+
nan_handling VARCHAR(8),
|
|
59
|
+
status VARCHAR(32) DEFAULT 'QUALIFIED',
|
|
60
|
+
submitted_at TIMESTAMPTZ,
|
|
61
|
+
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
CREATE TABLE IF NOT EXISTS options_evaluations (
|
|
65
|
+
id SERIAL PRIMARY KEY,
|
|
66
|
+
expression TEXT NOT NULL,
|
|
67
|
+
archetype VARCHAR(128),
|
|
68
|
+
source VARCHAR(32),
|
|
69
|
+
stage VARCHAR(32),
|
|
70
|
+
status VARCHAR(32),
|
|
71
|
+
sharpe NUMERIC(8, 4),
|
|
72
|
+
fitness NUMERIC(8, 4),
|
|
73
|
+
turnover NUMERIC(8, 4),
|
|
74
|
+
returns NUMERIC(8, 4),
|
|
75
|
+
drawdown NUMERIC(8, 4),
|
|
76
|
+
alpha_id VARCHAR(64),
|
|
77
|
+
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
CREATE TABLE IF NOT EXISTS options_learning_memory (
|
|
81
|
+
id SERIAL PRIMARY KEY,
|
|
82
|
+
expression TEXT UNIQUE NOT NULL,
|
|
83
|
+
reward_score NUMERIC(10, 4) DEFAULT 0,
|
|
84
|
+
sample_count INTEGER DEFAULT 0,
|
|
85
|
+
success_count INTEGER DEFAULT 0,
|
|
86
|
+
fail_count INTEGER DEFAULT 0,
|
|
87
|
+
last_updated TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
CREATE TABLE IF NOT EXISTS options_rejected_alphas (
|
|
91
|
+
id SERIAL PRIMARY KEY,
|
|
92
|
+
expression TEXT NOT NULL,
|
|
93
|
+
archetype VARCHAR(128),
|
|
94
|
+
sharpe NUMERIC(8, 4),
|
|
95
|
+
fitness NUMERIC(8, 4),
|
|
96
|
+
reason TEXT,
|
|
97
|
+
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
CREATE TABLE IF NOT EXISTS options_correlated_alphas (
|
|
101
|
+
id SERIAL PRIMARY KEY,
|
|
102
|
+
expression TEXT NOT NULL,
|
|
103
|
+
archetype VARCHAR(128),
|
|
104
|
+
sharpe NUMERIC(8, 4),
|
|
105
|
+
fitness NUMERIC(8, 4),
|
|
106
|
+
max_correlation NUMERIC(8, 4),
|
|
107
|
+
corr_partner_id VARCHAR(64),
|
|
108
|
+
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
CREATE TABLE IF NOT EXISTS cluster_session_cache (
|
|
112
|
+
org_id VARCHAR(64) PRIMARY KEY,
|
|
113
|
+
session_data TEXT NOT NULL,
|
|
114
|
+
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
CREATE TABLE IF NOT EXISTS cluster_run_lock (
|
|
118
|
+
lock_key VARCHAR(64) PRIMARY KEY,
|
|
119
|
+
holder_org VARCHAR(64),
|
|
120
|
+
acquired_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
121
|
+
);
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
# ── Database class ────────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
class OptionsDatabase:
|
|
127
|
+
"""
|
|
128
|
+
Low-level PostgreSQL adapter.
|
|
129
|
+
|
|
130
|
+
Manages a psycopg_pool ConnectionPool and exposes typed read/write
|
|
131
|
+
methods for every table. All raw SQL lives in this class.
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
database_url : PostgreSQL DSN, e.g. "postgresql://user:pass@host/db".
|
|
136
|
+
When None the pool is not created and all operations
|
|
137
|
+
are silently no-ops (file-only mode).
|
|
138
|
+
pool_min : Minimum pool connections (default 1).
|
|
139
|
+
pool_max : Maximum pool connections (default 5).
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
def __init__(
|
|
143
|
+
self,
|
|
144
|
+
database_url: Optional[str],
|
|
145
|
+
pool_min: int = 1,
|
|
146
|
+
pool_max: int = 5,
|
|
147
|
+
) -> None:
|
|
148
|
+
self._pool: Optional[Any] = None
|
|
149
|
+
if database_url and ConnectionPool is not None:
|
|
150
|
+
try:
|
|
151
|
+
self._pool = ConnectionPool(
|
|
152
|
+
database_url,
|
|
153
|
+
min_size=pool_min,
|
|
154
|
+
max_size=pool_max,
|
|
155
|
+
open=True,
|
|
156
|
+
)
|
|
157
|
+
self._init_schema()
|
|
158
|
+
log.info("brain_store: PostgreSQL pool initialised (min=%d max=%d)", pool_min, pool_max)
|
|
159
|
+
except Exception as exc:
|
|
160
|
+
log.error("brain_store: Failed to connect to PostgreSQL: %s", exc)
|
|
161
|
+
self._pool = None
|
|
162
|
+
|
|
163
|
+
def is_available(self) -> bool:
|
|
164
|
+
"""Return True if a live PostgreSQL connection pool is available."""
|
|
165
|
+
return self._pool is not None
|
|
166
|
+
|
|
167
|
+
# ── Schema init ───────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
def _init_schema(self) -> None:
|
|
170
|
+
"""Create tables if they do not already exist."""
|
|
171
|
+
if self._pool is None:
|
|
172
|
+
return
|
|
173
|
+
try:
|
|
174
|
+
with self._pool.connection() as conn:
|
|
175
|
+
conn.execute(SCHEMA_SQL)
|
|
176
|
+
conn.commit()
|
|
177
|
+
log.info("brain_store: Schema initialised.")
|
|
178
|
+
except Exception as exc:
|
|
179
|
+
log.warning("brain_store: Schema init error (may be benign on concurrent start): %s", exc)
|
|
180
|
+
|
|
181
|
+
# ── Deduplication helpers ─────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
def load_evaluated_expressions(self) -> Set[str]:
|
|
184
|
+
"""Return the set of all expressions ever evaluated (from options_evaluations)."""
|
|
185
|
+
if self._pool is None:
|
|
186
|
+
return set()
|
|
187
|
+
result: Set[str] = set()
|
|
188
|
+
try:
|
|
189
|
+
with self._pool.connection() as conn:
|
|
190
|
+
rows = conn.execute("SELECT expression FROM options_evaluations").fetchall()
|
|
191
|
+
for row in rows:
|
|
192
|
+
result.add(row[0])
|
|
193
|
+
except Exception as exc:
|
|
194
|
+
log.warning("brain_store: load_evaluated_expressions failed: %s", exc)
|
|
195
|
+
return result
|
|
196
|
+
|
|
197
|
+
# ── Alpha pool writes ─────────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
def save_qualified_alpha(self, record: Dict[str, Any]) -> None:
|
|
200
|
+
"""
|
|
201
|
+
Insert one row into options_alphas (status=QUALIFIED).
|
|
202
|
+
|
|
203
|
+
Parameters
|
|
204
|
+
----------
|
|
205
|
+
record : dict with keys matching the options_alphas columns.
|
|
206
|
+
Required: expression (str).
|
|
207
|
+
Optional: alpha_id, archetype, hypothesis, source,
|
|
208
|
+
sharpe, fitness, turnover, returns, drawdown,
|
|
209
|
+
margin, max_correlation, universe,
|
|
210
|
+
neutralization, delay, decay, truncation,
|
|
211
|
+
pasteurization, nan_handling.
|
|
212
|
+
"""
|
|
213
|
+
if self._pool is None:
|
|
214
|
+
return
|
|
215
|
+
try:
|
|
216
|
+
with self._pool.connection() as conn:
|
|
217
|
+
conn.execute(
|
|
218
|
+
"""
|
|
219
|
+
INSERT INTO options_alphas
|
|
220
|
+
(alpha_id, expression, archetype, hypothesis, source,
|
|
221
|
+
sharpe, fitness, turnover, returns, drawdown, margin,
|
|
222
|
+
max_correlation, universe, neutralization, delay, decay,
|
|
223
|
+
truncation, pasteurization, nan_handling, status)
|
|
224
|
+
VALUES
|
|
225
|
+
(%(alpha_id)s, %(expression)s, %(archetype)s,
|
|
226
|
+
%(hypothesis)s, %(source)s,
|
|
227
|
+
%(sharpe)s, %(fitness)s, %(turnover)s, %(returns)s,
|
|
228
|
+
%(drawdown)s, %(margin)s, %(max_correlation)s,
|
|
229
|
+
%(universe)s, %(neutralization)s, %(delay)s, %(decay)s,
|
|
230
|
+
%(truncation)s, %(pasteurization)s, %(nan_handling)s,
|
|
231
|
+
'QUALIFIED')
|
|
232
|
+
""",
|
|
233
|
+
record,
|
|
234
|
+
)
|
|
235
|
+
conn.commit()
|
|
236
|
+
except Exception as exc:
|
|
237
|
+
log.error("brain_store: save_qualified_alpha failed: %s", exc)
|
|
238
|
+
|
|
239
|
+
def log_evaluation(self, record: Dict[str, Any]) -> None:
|
|
240
|
+
"""
|
|
241
|
+
Append one row to options_evaluations (immutable audit log).
|
|
242
|
+
|
|
243
|
+
Required keys: expression (str), stage (str), status (str).
|
|
244
|
+
Optional keys: archetype, source, sharpe, fitness, turnover,
|
|
245
|
+
returns, drawdown, alpha_id.
|
|
246
|
+
"""
|
|
247
|
+
if self._pool is None:
|
|
248
|
+
return
|
|
249
|
+
try:
|
|
250
|
+
with self._pool.connection() as conn:
|
|
251
|
+
conn.execute(
|
|
252
|
+
"""
|
|
253
|
+
INSERT INTO options_evaluations
|
|
254
|
+
(expression, archetype, source, stage, status,
|
|
255
|
+
sharpe, fitness, turnover, returns, drawdown, alpha_id)
|
|
256
|
+
VALUES
|
|
257
|
+
(%(expression)s, %(archetype)s, %(source)s,
|
|
258
|
+
%(stage)s, %(status)s,
|
|
259
|
+
%(sharpe)s, %(fitness)s, %(turnover)s,
|
|
260
|
+
%(returns)s, %(drawdown)s, %(alpha_id)s)
|
|
261
|
+
""",
|
|
262
|
+
record,
|
|
263
|
+
)
|
|
264
|
+
conn.commit()
|
|
265
|
+
except Exception as exc:
|
|
266
|
+
log.error("brain_store: log_evaluation failed: %s", exc)
|
|
267
|
+
|
|
268
|
+
# ── MAB / RL state ────────────────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
def upsert_rl_reward(self, expression: str, delta: float) -> None:
|
|
271
|
+
"""
|
|
272
|
+
Add *delta* to the reward_score for *expression* in options_learning_memory.
|
|
273
|
+
|
|
274
|
+
Uses ON CONFLICT DO UPDATE for idempotent upsert.
|
|
275
|
+
Increments sample_count by 1 each call.
|
|
276
|
+
Increments success_count if delta > 0, fail_count if delta < 0.
|
|
277
|
+
"""
|
|
278
|
+
if self._pool is None:
|
|
279
|
+
return
|
|
280
|
+
try:
|
|
281
|
+
with self._pool.connection() as conn:
|
|
282
|
+
conn.execute(
|
|
283
|
+
"""
|
|
284
|
+
INSERT INTO options_learning_memory
|
|
285
|
+
(expression, reward_score, sample_count,
|
|
286
|
+
success_count, fail_count, last_updated)
|
|
287
|
+
VALUES
|
|
288
|
+
(%(expr)s, %(delta)s, 1,
|
|
289
|
+
CASE WHEN %(delta)s > 0 THEN 1 ELSE 0 END,
|
|
290
|
+
CASE WHEN %(delta)s < 0 THEN 1 ELSE 0 END,
|
|
291
|
+
CURRENT_TIMESTAMP)
|
|
292
|
+
ON CONFLICT (expression) DO UPDATE SET
|
|
293
|
+
reward_score = options_learning_memory.reward_score + %(delta)s,
|
|
294
|
+
sample_count = options_learning_memory.sample_count + 1,
|
|
295
|
+
success_count = options_learning_memory.success_count
|
|
296
|
+
+ CASE WHEN %(delta)s > 0 THEN 1 ELSE 0 END,
|
|
297
|
+
fail_count = options_learning_memory.fail_count
|
|
298
|
+
+ CASE WHEN %(delta)s < 0 THEN 1 ELSE 0 END,
|
|
299
|
+
last_updated = CURRENT_TIMESTAMP
|
|
300
|
+
""",
|
|
301
|
+
{"expr": expression, "delta": delta},
|
|
302
|
+
)
|
|
303
|
+
conn.commit()
|
|
304
|
+
except Exception as exc:
|
|
305
|
+
log.error("brain_store: upsert_rl_reward failed: %s", exc)
|
|
306
|
+
|
|
307
|
+
def load_top_performing_exemplars(
|
|
308
|
+
self,
|
|
309
|
+
limit: int = 5,
|
|
310
|
+
min_sharpe: float = 1.0,
|
|
311
|
+
exclude_archetypes: Optional[List[str]] = None,
|
|
312
|
+
) -> List[Dict[str, Any]]:
|
|
313
|
+
"""
|
|
314
|
+
Return up to *limit* rows from options_alphas ordered by sharpe DESC.
|
|
315
|
+
|
|
316
|
+
Filters: sharpe >= min_sharpe, status IN ('QUALIFIED', 'SUBMITTED').
|
|
317
|
+
Excludes rows whose archetype is in *exclude_archetypes* (if given).
|
|
318
|
+
"""
|
|
319
|
+
if self._pool is None:
|
|
320
|
+
return []
|
|
321
|
+
try:
|
|
322
|
+
excl = exclude_archetypes or []
|
|
323
|
+
with self._pool.connection() as conn:
|
|
324
|
+
rows = conn.execute(
|
|
325
|
+
"""
|
|
326
|
+
SELECT expression, archetype, sharpe, fitness, turnover
|
|
327
|
+
FROM options_alphas
|
|
328
|
+
WHERE sharpe >= %(min_sharpe)s
|
|
329
|
+
AND status IN ('QUALIFIED', 'SUBMITTED')
|
|
330
|
+
AND (%(excl_len)s = 0 OR archetype != ALL(%(excl)s))
|
|
331
|
+
ORDER BY sharpe DESC
|
|
332
|
+
LIMIT %(limit)s
|
|
333
|
+
""",
|
|
334
|
+
{
|
|
335
|
+
"min_sharpe": min_sharpe,
|
|
336
|
+
"excl": excl,
|
|
337
|
+
"excl_len": len(excl),
|
|
338
|
+
"limit": limit,
|
|
339
|
+
},
|
|
340
|
+
).fetchall()
|
|
341
|
+
return [
|
|
342
|
+
{"expression": r[0], "archetype": r[1],
|
|
343
|
+
"sharpe": float(r[2] or 0), "fitness": float(r[3] or 0),
|
|
344
|
+
"turnover": float(r[4] or 0)}
|
|
345
|
+
for r in rows
|
|
346
|
+
]
|
|
347
|
+
except Exception as exc:
|
|
348
|
+
log.warning("brain_store: load_top_performing_exemplars failed: %s", exc)
|
|
349
|
+
return []
|
|
350
|
+
|
|
351
|
+
def load_archetype_performance_summary(self) -> Dict[str, Any]:
|
|
352
|
+
"""
|
|
353
|
+
Return a dict mapping archetype_name -> {win_rate, avg_sharpe, total_runs}.
|
|
354
|
+
Computed from options_evaluations where status == 'PASS'.
|
|
355
|
+
"""
|
|
356
|
+
if self._pool is None:
|
|
357
|
+
return {}
|
|
358
|
+
try:
|
|
359
|
+
with self._pool.connection() as conn:
|
|
360
|
+
rows = conn.execute(
|
|
361
|
+
"""
|
|
362
|
+
SELECT
|
|
363
|
+
archetype,
|
|
364
|
+
COUNT(*) AS total_runs,
|
|
365
|
+
COUNT(*) FILTER (WHERE status = 'PASS') AS wins,
|
|
366
|
+
AVG(sharpe) FILTER (WHERE status = 'PASS') AS avg_sharpe
|
|
367
|
+
FROM options_evaluations
|
|
368
|
+
WHERE archetype IS NOT NULL
|
|
369
|
+
GROUP BY archetype
|
|
370
|
+
"""
|
|
371
|
+
).fetchall()
|
|
372
|
+
result: Dict[str, Any] = {}
|
|
373
|
+
for row in rows:
|
|
374
|
+
arch, total, wins, avg_sh = row
|
|
375
|
+
result[arch] = {
|
|
376
|
+
"total_runs": int(total),
|
|
377
|
+
"win_rate": round(int(wins) / max(int(total), 1), 4),
|
|
378
|
+
"avg_sharpe": round(float(avg_sh or 0), 4),
|
|
379
|
+
}
|
|
380
|
+
return result
|
|
381
|
+
except Exception as exc:
|
|
382
|
+
log.warning("brain_store: load_archetype_performance_summary failed: %s", exc)
|
|
383
|
+
return {}
|
|
384
|
+
|
|
385
|
+
def save_correlated_alpha(self, record: Dict[str, Any]) -> None:
|
|
386
|
+
"""Insert one row into options_correlated_alphas."""
|
|
387
|
+
if self._pool is None:
|
|
388
|
+
return
|
|
389
|
+
try:
|
|
390
|
+
with self._pool.connection() as conn:
|
|
391
|
+
conn.execute(
|
|
392
|
+
"""
|
|
393
|
+
INSERT INTO options_correlated_alphas
|
|
394
|
+
(expression, archetype, sharpe, fitness,
|
|
395
|
+
max_correlation, corr_partner_id)
|
|
396
|
+
VALUES
|
|
397
|
+
(%(expression)s, %(archetype)s, %(sharpe)s,
|
|
398
|
+
%(fitness)s, %(max_correlation)s, %(corr_partner_id)s)
|
|
399
|
+
""",
|
|
400
|
+
record,
|
|
401
|
+
)
|
|
402
|
+
conn.commit()
|
|
403
|
+
except Exception as exc:
|
|
404
|
+
log.error("brain_store: save_correlated_alpha failed: %s", exc)
|
|
405
|
+
|
|
406
|
+
def save_rejected_alpha(self, record: Dict[str, Any]) -> None:
|
|
407
|
+
"""Insert one row into options_rejected_alphas."""
|
|
408
|
+
if self._pool is None:
|
|
409
|
+
return
|
|
410
|
+
try:
|
|
411
|
+
with self._pool.connection() as conn:
|
|
412
|
+
conn.execute(
|
|
413
|
+
"""
|
|
414
|
+
INSERT INTO options_rejected_alphas
|
|
415
|
+
(expression, archetype, sharpe, fitness, reason)
|
|
416
|
+
VALUES
|
|
417
|
+
(%(expression)s, %(archetype)s, %(sharpe)s,
|
|
418
|
+
%(fitness)s, %(reason)s)
|
|
419
|
+
""",
|
|
420
|
+
record,
|
|
421
|
+
)
|
|
422
|
+
conn.commit()
|
|
423
|
+
except Exception as exc:
|
|
424
|
+
log.error("brain_store: save_rejected_alpha failed: %s", exc)
|
|
425
|
+
|
|
426
|
+
def close(self) -> None:
|
|
427
|
+
"""Close the connection pool gracefully."""
|
|
428
|
+
if self._pool is not None:
|
|
429
|
+
try:
|
|
430
|
+
self._pool.close()
|
|
431
|
+
except Exception:
|
|
432
|
+
pass
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def map_archetype_to_core(archetype_name: str) -> str:
|
|
436
|
+
"""
|
|
437
|
+
Normalises arbitrary archetype labels (from LLM output, mutation tags, etc.)
|
|
438
|
+
to the canonical 30-strategy taxonomy.
|
|
439
|
+
"""
|
|
440
|
+
if not archetype_name:
|
|
441
|
+
return "forward_basis"
|
|
442
|
+
name = archetype_name.lower().strip()
|
|
443
|
+
|
|
444
|
+
if "dynamic_short_squeeze" in name or "dynamic short squeeze" in name or "borrow fee convexity" in name:
|
|
445
|
+
return "dynamic_short_squeeze"
|
|
446
|
+
elif "days_to_cover" in name or "days-to-cover" in name or "days to cover" in name:
|
|
447
|
+
return "short_interest"
|
|
448
|
+
elif "vpin" in name or "toxicity" in name or "order_flow" in name:
|
|
449
|
+
return "order_flow_vpin"
|
|
450
|
+
elif "gamma" in name or "pinning" in name:
|
|
451
|
+
return "gamma_pinning_clustering"
|
|
452
|
+
elif "customer" in name or "cascades" in name or "customer_supplier" in name:
|
|
453
|
+
return "customer_supplier_cascades"
|
|
454
|
+
elif "rd_" in name or "r&d" in name or "spillover" in name:
|
|
455
|
+
return "rd_capitalization_spillovers"
|
|
456
|
+
elif "capex" in name or "asset_growth" in name or "asset growth" in name:
|
|
457
|
+
return "capex_asset_growth"
|
|
458
|
+
elif "peavrp" in name or "vrp compression" in name or "vrp crush" in name:
|
|
459
|
+
return "peavrp_volatility_premia"
|
|
460
|
+
elif "jump" in name or "realized_jump" in name:
|
|
461
|
+
return "realized_jump_intensity"
|
|
462
|
+
elif "default" in name or "merton" in name or "distance_to_default" in name:
|
|
463
|
+
return "distance_to_default_debt"
|
|
464
|
+
elif "macro" in name or "fomc" in name or "cpi" in name:
|
|
465
|
+
return "macro_fomc_cpi_drift"
|
|
466
|
+
elif "patent" in name or "innovation" in name:
|
|
467
|
+
return "patent_innovation_efficiency"
|
|
468
|
+
elif "peavd" in name:
|
|
469
|
+
return "peavd_earnings_vol_drift"
|
|
470
|
+
elif "insider" in name or "cluster" in name:
|
|
471
|
+
return "insider_cluster_buying"
|
|
472
|
+
elif "13f" in name or "institutional" in name or "breadth" in name:
|
|
473
|
+
return "institutional_13f_breadth"
|
|
474
|
+
elif "supply_chain" in name or "supply chain" in name:
|
|
475
|
+
return "supply_chain"
|
|
476
|
+
elif "informed_short" in name or "informed short" in name:
|
|
477
|
+
return "informed_short_demand"
|
|
478
|
+
elif "extreme_tail" in name or "tail_risk" in name or "extreme tail" in name:
|
|
479
|
+
return "extreme_tail_risk"
|
|
480
|
+
elif "iv_lead" in name or "lead_lag" in name or "lead lag" in name:
|
|
481
|
+
return "iv_lead_lag"
|
|
482
|
+
elif "network_momentum" in name or "network momentum" in name:
|
|
483
|
+
return "network_momentum"
|
|
484
|
+
elif "accruals" in name or "sloan" in name or "cashflow" in name:
|
|
485
|
+
return "accruals_cashflow"
|
|
486
|
+
elif "formulaic" in name or "101" in name or "kakushadze" in name:
|
|
487
|
+
return "formulaic_101"
|
|
488
|
+
elif "hybrid" in name or "confluence" in name or "divergence" in name:
|
|
489
|
+
return "hybrid_confluence"
|
|
490
|
+
elif "breakeven" in name:
|
|
491
|
+
return "breakeven"
|
|
492
|
+
elif "skew" in name or "smirk" in name:
|
|
493
|
+
return "skew"
|
|
494
|
+
elif "term_structure" in name or "term structure" in name or "vrp" in name or "variance" in name or "parkinson" in name:
|
|
495
|
+
return "term_structure"
|
|
496
|
+
elif "forward" in name or "basis" in name:
|
|
497
|
+
return "forward_basis"
|
|
498
|
+
elif "pcr" in name or "put-call" in name or "put_call" in name or "flow" in name:
|
|
499
|
+
return "pcr_flow"
|
|
500
|
+
elif "analyst" in name or "revision" in name or "dispersion" in name or "pead" in name or "target_price" in name or "price target" in name or "sales" in name:
|
|
501
|
+
return "analyst_revisions"
|
|
502
|
+
elif "short" in name or "borrow" in name or "days_to_cover" in name:
|
|
503
|
+
return "short_interest"
|
|
504
|
+
return name
|
|
505
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Marker file for PEP 561
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OptionsStore — high-level facade over OptionsDatabase + flat-file storage.
|
|
3
|
+
|
|
4
|
+
Combines Postgres (via OptionsDatabase) with local CSV/JSON flat files so
|
|
5
|
+
the pipeline can survive without a database connection (file-only mode).
|
|
6
|
+
|
|
7
|
+
CSV/JSON files written
|
|
8
|
+
----------------------
|
|
9
|
+
passed_options_alphas.csv — all qualified alphas (append-only)
|
|
10
|
+
passed_options_alphas.json — same data in JSON (overwritten on each save)
|
|
11
|
+
rejected_options_alphas.csv — all rejected alphas
|
|
12
|
+
correlated_options_alphas.csv — all correlated alphas
|
|
13
|
+
evaluated_candidates.csv — full evaluation history
|
|
14
|
+
pnl_series/<alpha_id>.json — cached PnL vectors for correlation checks
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import csv
|
|
19
|
+
import json
|
|
20
|
+
import logging
|
|
21
|
+
import os
|
|
22
|
+
import threading
|
|
23
|
+
from datetime import datetime, timezone
|
|
24
|
+
from typing import Any, Dict, List, Optional, Set
|
|
25
|
+
|
|
26
|
+
from brain_store.db import OptionsDatabase
|
|
27
|
+
|
|
28
|
+
log = logging.getLogger("brain_store.store")
|
|
29
|
+
|
|
30
|
+
_SENTINEL = object()
|
|
31
|
+
|
|
32
|
+
_PASSED_CSV_HEADER = [
|
|
33
|
+
"expression", "archetype", "hypothesis", "source",
|
|
34
|
+
"sharpe", "fitness", "turnover", "returns", "drawdown",
|
|
35
|
+
"margin", "universe", "neutralization", "delay", "decay",
|
|
36
|
+
"truncation", "pasteurization", "nan_handling",
|
|
37
|
+
"alpha_id", "status", "created_at",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
_HISTORY_CSV_HEADER = [
|
|
41
|
+
"expression", "archetype", "source", "stage", "status",
|
|
42
|
+
"sharpe", "fitness", "turnover", "returns", "drawdown",
|
|
43
|
+
"alpha_id", "created_at",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class AlphaStore:
|
|
48
|
+
"""
|
|
49
|
+
High-level store facade.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
data_dir : Directory for CSV/JSON files.
|
|
54
|
+
Defaults to <package_root>/data.
|
|
55
|
+
database_url : PostgreSQL DSN. Pass None to disable Postgres.
|
|
56
|
+
Defaults to DATABASE_URL env var.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
data_dir: Optional[str] = None,
|
|
62
|
+
database_url: Any = _SENTINEL,
|
|
63
|
+
) -> None:
|
|
64
|
+
if data_dir is None:
|
|
65
|
+
data_dir = os.path.join(
|
|
66
|
+
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data"
|
|
67
|
+
)
|
|
68
|
+
self.data_dir = data_dir
|
|
69
|
+
os.makedirs(self.data_dir, exist_ok=True)
|
|
70
|
+
|
|
71
|
+
self.passed_csv = os.path.join(self.data_dir, "passed_options_alphas.csv")
|
|
72
|
+
self.passed_json = os.path.join(self.data_dir, "passed_options_alphas.json")
|
|
73
|
+
self.rejected_csv = os.path.join(self.data_dir, "rejected_options_alphas.csv")
|
|
74
|
+
self.correlated_csv = os.path.join(self.data_dir, "correlated_options_alphas.csv")
|
|
75
|
+
self.history_csv = os.path.join(self.data_dir, "evaluated_candidates.csv")
|
|
76
|
+
self.pnl_cache_dir = os.path.join(self.data_dir, "pnl_series")
|
|
77
|
+
os.makedirs(self.pnl_cache_dir, exist_ok=True)
|
|
78
|
+
|
|
79
|
+
if database_url is _SENTINEL:
|
|
80
|
+
database_url = os.getenv("DATABASE_URL")
|
|
81
|
+
|
|
82
|
+
self.db = OptionsDatabase(database_url)
|
|
83
|
+
self._write_lock = threading.Lock()
|
|
84
|
+
|
|
85
|
+
def __repr__(self) -> str:
|
|
86
|
+
return f"AlphaStore(data_dir={self.data_dir!r}, postgres_enabled={self.db.is_available()})"
|
|
87
|
+
|
|
88
|
+
# ── Deduplication ─────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
def load_evaluated_expressions(
|
|
91
|
+
self, limit: Optional[int] = None, offset: int = 0
|
|
92
|
+
) -> Set[str]:
|
|
93
|
+
"""Return the set of all expressions previously evaluated, with optional limit/offset pagination."""
|
|
94
|
+
evaluated: Set[str] = set()
|
|
95
|
+
if os.path.exists(self.history_csv):
|
|
96
|
+
try:
|
|
97
|
+
with open(self.history_csv, "r", encoding="utf-8") as f:
|
|
98
|
+
count = 0
|
|
99
|
+
for idx, row in enumerate(csv.DictReader(f)):
|
|
100
|
+
if idx < offset:
|
|
101
|
+
continue
|
|
102
|
+
if limit is not None and count >= limit:
|
|
103
|
+
break
|
|
104
|
+
expr = row.get("expression")
|
|
105
|
+
if expr:
|
|
106
|
+
evaluated.add(expr.strip())
|
|
107
|
+
count += 1
|
|
108
|
+
except Exception as exc:
|
|
109
|
+
log.warning("Could not read evaluated history CSV: %s", exc)
|
|
110
|
+
try:
|
|
111
|
+
evaluated.update(self.db.load_evaluated_expressions())
|
|
112
|
+
except Exception as exc:
|
|
113
|
+
log.warning("Could not read evaluated expressions from DB: %s", exc)
|
|
114
|
+
return evaluated
|
|
115
|
+
|
|
116
|
+
# ── Qualified alpha writes ────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
def save_qualified_alpha(self, record: Dict[str, Any]) -> None:
|
|
119
|
+
"""
|
|
120
|
+
Persist a qualified alpha to both CSV/JSON and Postgres.
|
|
121
|
+
|
|
122
|
+
*record* must contain 'expression'. All other fields are optional.
|
|
123
|
+
"""
|
|
124
|
+
record.setdefault("created_at", datetime.now(timezone.utc).isoformat())
|
|
125
|
+
record.setdefault("status", "QUALIFIED")
|
|
126
|
+
with self._write_lock:
|
|
127
|
+
self._append_csv(self.passed_csv, _PASSED_CSV_HEADER, record)
|
|
128
|
+
self._rewrite_json(self.passed_csv, self.passed_json, _PASSED_CSV_HEADER)
|
|
129
|
+
self.db.save_qualified_alpha(record)
|
|
130
|
+
|
|
131
|
+
# ── Evaluation log ────────────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
def log_evaluation(self, record: Dict[str, Any]) -> None:
|
|
134
|
+
"""Append a simulation result to the evaluation history."""
|
|
135
|
+
record.setdefault("created_at", datetime.now(timezone.utc).isoformat())
|
|
136
|
+
with self._write_lock:
|
|
137
|
+
self._append_csv(self.history_csv, _HISTORY_CSV_HEADER, record)
|
|
138
|
+
self.db.log_evaluation(record)
|
|
139
|
+
|
|
140
|
+
# ── Top-performing exemplars ──────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
def load_top_performing_exemplars(
|
|
143
|
+
self,
|
|
144
|
+
limit: int = 5,
|
|
145
|
+
min_sharpe: float = 1.0,
|
|
146
|
+
exclude_archetypes: Optional[List[str]] = None,
|
|
147
|
+
) -> List[Dict[str, Any]]:
|
|
148
|
+
"""Return up to *limit* best-performing alphas from Postgres."""
|
|
149
|
+
return self.db.load_top_performing_exemplars(
|
|
150
|
+
limit=limit,
|
|
151
|
+
min_sharpe=min_sharpe,
|
|
152
|
+
exclude_archetypes=exclude_archetypes,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def load_archetype_performance_summary(self) -> Dict[str, Any]:
|
|
156
|
+
"""Return win rates and avg Sharpe per archetype from Postgres."""
|
|
157
|
+
return self.db.load_archetype_performance_summary()
|
|
158
|
+
|
|
159
|
+
# ── PnL cache ─────────────────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
def cache_pnl(self, alpha_id: str, pnl: List[float]) -> None:
|
|
162
|
+
"""Write *pnl* list to pnl_series/<alpha_id>.json."""
|
|
163
|
+
path = os.path.join(self.pnl_cache_dir, f"{alpha_id}.json")
|
|
164
|
+
with self._write_lock:
|
|
165
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
166
|
+
json.dump(pnl, f)
|
|
167
|
+
|
|
168
|
+
def load_pnl(self, alpha_id: str) -> Optional[List[float]]:
|
|
169
|
+
"""Return cached PnL list or None if not present."""
|
|
170
|
+
path = os.path.join(self.pnl_cache_dir, f"{alpha_id}.json")
|
|
171
|
+
if not os.path.exists(path):
|
|
172
|
+
return None
|
|
173
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
174
|
+
return json.load(f)
|
|
175
|
+
|
|
176
|
+
# ── Private helpers ───────────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
def _append_csv(
|
|
179
|
+
self, path: str, header: List[str], record: Dict[str, Any]
|
|
180
|
+
) -> None:
|
|
181
|
+
write_header = not os.path.exists(path)
|
|
182
|
+
with open(path, "a", newline="", encoding="utf-8") as f:
|
|
183
|
+
writer = csv.DictWriter(f, fieldnames=header, extrasaction="ignore")
|
|
184
|
+
if write_header:
|
|
185
|
+
writer.writeheader()
|
|
186
|
+
writer.writerow(record)
|
|
187
|
+
|
|
188
|
+
def _rewrite_json(
|
|
189
|
+
self, csv_path: str, json_path: str, header: List[str]
|
|
190
|
+
) -> None:
|
|
191
|
+
rows: List[Dict[str, Any]] = []
|
|
192
|
+
if os.path.exists(csv_path):
|
|
193
|
+
with open(csv_path, "r", encoding="utf-8") as f:
|
|
194
|
+
rows = list(csv.DictReader(f))
|
|
195
|
+
with open(json_path, "w", encoding="utf-8") as f:
|
|
196
|
+
json.dump(rows, f, indent=2)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# Backwards-compatibility alias (deprecated in favor of AlphaStore)
|
|
200
|
+
OptionsStore = AlphaStore
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: brain-store
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Dual-mode persistence layer (PostgreSQL + file-backed) and RL state store for Brain Alpha pipelines.
|
|
5
|
+
Author: Xtley001
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Xtley001/brain-alpha-pipeline
|
|
8
|
+
Project-URL: Repository, https://github.com/Xtley001/brain-alpha-pipeline
|
|
9
|
+
Project-URL: Documentation, https://github.com/Xtley001/brain-alpha-pipeline/tree/main/brain_libraries
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/Xtley001/brain-alpha-pipeline/issues
|
|
11
|
+
Keywords: worldquant,brain,alpha,quantitative-finance,postgresql,state-persistence,reinforcement-learning,storage
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
21
|
+
Classifier: Topic :: Database
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: brain-core>=0.1.0
|
|
26
|
+
Requires-Dist: psycopg[binary]>=3.1
|
|
27
|
+
Requires-Dist: psycopg_pool>=3.2
|
|
28
|
+
Requires-Dist: alembic>=1.13
|
|
29
|
+
Requires-Dist: numpy>=1.26
|
|
30
|
+
Requires-Dist: python-dotenv>=1.0
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
34
|
+
Requires-Dist: black; extra == "dev"
|
|
35
|
+
Requires-Dist: flake8; extra == "dev"
|
|
36
|
+
Requires-Dist: mypy; extra == "dev"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# brain-store
|
|
40
|
+
|
|
41
|
+
Dual-mode persistence layer (PostgreSQL connection pool + flat-file fallback) and RL state store for Brain Alpha pipelines.
|
|
42
|
+
|
|
43
|
+
[](https://github.com/Xtley001/brain-alpha-pipeline/actions)
|
|
44
|
+
[](../LICENSE)
|
|
45
|
+
[](https://pypi.org/project/brain-store/)
|
|
46
|
+
[](https://www.python.org/downloads/)
|
|
47
|
+
|
|
48
|
+
`brain_store` provides resilient, dual-mode persistence for quantitative alpha pipelines. It connects to Neon PostgreSQL with connection pooling via `psycopg_pool` while falling back automatically to local zero-config JSON/CSV file storage when `DATABASE_URL` is absent. For the complete relational entity-relationship diagram and database migration operations, see [Schema & Migrations](./docs/SCHEMA_AND_MIGRATIONS.md).
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install brain-store
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Quickstart
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from brain_store import AlphaStore
|
|
60
|
+
|
|
61
|
+
# Connects to PostgreSQL if DATABASE_URL is set, otherwise uses ./data/
|
|
62
|
+
store = AlphaStore(data_dir="./data")
|
|
63
|
+
|
|
64
|
+
# Save a qualified alpha
|
|
65
|
+
store.save_qualified_alpha({
|
|
66
|
+
"expression": "group_neutralize(rank(close), subindustry)",
|
|
67
|
+
"sharpe": 1.85,
|
|
68
|
+
"fitness": 1.42,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
# Load historical expressions with pagination to prevent OOM
|
|
72
|
+
evaluated = store.load_evaluated_expressions(limit=500, offset=0)
|
|
73
|
+
print(f"Loaded {len(evaluated)} evaluated expressions.")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
> **Note:** `OptionsStore` is preserved as a backward-compatible alias for `AlphaStore`.
|
|
77
|
+
|
|
78
|
+
## Architecture
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
brain_store/
|
|
82
|
+
├── brain_store/
|
|
83
|
+
│ ├── __init__.py # Public re-exports (AlphaStore, OptionsStore, OptionsDatabase)
|
|
84
|
+
│ ├── db.py # OptionsDatabase — connection pool, 9-table DDL, RL state
|
|
85
|
+
│ ├── store.py # AlphaStore — high-level dual-mode facade
|
|
86
|
+
│ ├── py.typed # PEP 561 typing marker
|
|
87
|
+
│ └── migrations/ # DDL migration scripts
|
|
88
|
+
├── tests/ # Offline unit tests (no PostgreSQL instance required)
|
|
89
|
+
└── docs/
|
|
90
|
+
├── API.md # Complete API specification
|
|
91
|
+
└── SCHEMA_AND_MIGRATIONS.md # Relational schema diagram & migration runbook
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Database Schema
|
|
95
|
+
|
|
96
|
+
| Table | Purpose | Indexing Strategy |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `options_alphas` | Live alpha reserve vault | Unique on `alpha_id`, Index on `status`, `category` |
|
|
99
|
+
| `options_evaluations` | Simulation backtest audit log | Index on `alpha_id`, `simulated_at` |
|
|
100
|
+
| `options_strategy_rl_state` | Strategy-level reinforcement learning operator weights | Primary key on `strategy_name` |
|
|
101
|
+
| `options_learning_memory` | Global Multi-Armed Bandit expression reward tracking | Primary key on `expression_hash` |
|
|
102
|
+
| `options_rejected_alphas` | Diagnostics for checklist / gate-failed alphas | Index on `failure_reason` |
|
|
103
|
+
| `options_correlated_alphas` | Audit log of correlation gate rejections (|rho| >= 0.70) | Index on `candidate_id` |
|
|
104
|
+
| `cluster_session_cache` | Distributed BRAIN auth session token caching (2h TTL) | Primary key on `session_key` |
|
|
105
|
+
| `cluster_run_lock` | Distributed mutex preventing duplicate simulation runs | Primary key on `lock_name` |
|
|
106
|
+
| `org_runs` | Runner heartbeat and telemetry monitoring | Primary key on `run_id` |
|
|
107
|
+
|
|
108
|
+
## Testing
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
pytest tests/ -v
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Tests run offline without requiring a live PostgreSQL instance.
|
|
115
|
+
|
|
116
|
+
## Security
|
|
117
|
+
|
|
118
|
+
Report vulnerabilities per [SECURITY.md](SECURITY.md).
|
|
119
|
+
|
|
120
|
+
## Contributing
|
|
121
|
+
|
|
122
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
Released under the [MIT License](../LICENSE). Maintained by [Xtley001](https://github.com/Xtley001).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
brain_store/__init__.py
|
|
5
|
+
brain_store/db.py
|
|
6
|
+
brain_store/py.typed
|
|
7
|
+
brain_store/store.py
|
|
8
|
+
brain_store.egg-info/PKG-INFO
|
|
9
|
+
brain_store.egg-info/SOURCES.txt
|
|
10
|
+
brain_store.egg-info/dependency_links.txt
|
|
11
|
+
brain_store.egg-info/requires.txt
|
|
12
|
+
brain_store.egg-info/top_level.txt
|
|
13
|
+
tests/test_db_offline.py
|
|
14
|
+
tests/test_store_file_mode.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
brain_store
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "brain-store"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Dual-mode persistence layer (PostgreSQL + file-backed) and RL state store for Brain Alpha pipelines."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "Xtley001" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"worldquant",
|
|
15
|
+
"brain",
|
|
16
|
+
"alpha",
|
|
17
|
+
"quantitative-finance",
|
|
18
|
+
"postgresql",
|
|
19
|
+
"state-persistence",
|
|
20
|
+
"reinforcement-learning",
|
|
21
|
+
"storage",
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 4 - Beta",
|
|
25
|
+
"Intended Audience :: Financial and Insurance Industry",
|
|
26
|
+
"Intended Audience :: Science/Research",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.9",
|
|
29
|
+
"Programming Language :: Python :: 3.10",
|
|
30
|
+
"Programming Language :: Python :: 3.11",
|
|
31
|
+
"Programming Language :: Python :: 3.12",
|
|
32
|
+
"Topic :: Office/Business :: Financial :: Investment",
|
|
33
|
+
"Topic :: Database",
|
|
34
|
+
]
|
|
35
|
+
dependencies = [
|
|
36
|
+
"brain-core>=0.1.0",
|
|
37
|
+
"psycopg[binary]>=3.1",
|
|
38
|
+
"psycopg_pool>=3.2",
|
|
39
|
+
"alembic>=1.13",
|
|
40
|
+
"numpy>=1.26",
|
|
41
|
+
"python-dotenv>=1.0",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[project.optional-dependencies]
|
|
45
|
+
dev = ["pytest>=8.0", "pytest-asyncio>=0.23", "black", "flake8", "mypy"]
|
|
46
|
+
|
|
47
|
+
[project.urls]
|
|
48
|
+
Homepage = "https://github.com/Xtley001/brain-alpha-pipeline"
|
|
49
|
+
Repository = "https://github.com/Xtley001/brain-alpha-pipeline"
|
|
50
|
+
Documentation = "https://github.com/Xtley001/brain-alpha-pipeline/tree/main/brain_libraries"
|
|
51
|
+
"Bug Tracker" = "https://github.com/Xtley001/brain-alpha-pipeline/issues"
|
|
52
|
+
|
|
53
|
+
[tool.setuptools.packages.find]
|
|
54
|
+
where = ["."]
|
|
55
|
+
include = ["brain_store*"]
|
|
56
|
+
|
|
57
|
+
[tool.setuptools.package-data]
|
|
58
|
+
brain_store = ["py.typed"]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for brain_store.db — offline (no PostgreSQL required).
|
|
3
|
+
|
|
4
|
+
All tests use database_url=None so no real connection is made.
|
|
5
|
+
Acceptance criteria: all methods must be no-ops and not raise when pool=None.
|
|
6
|
+
"""
|
|
7
|
+
from brain_store.db import OptionsDatabase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def make_db() -> OptionsDatabase:
|
|
11
|
+
return OptionsDatabase(database_url=None)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_load_evaluated_expressions_offline():
|
|
15
|
+
db = make_db()
|
|
16
|
+
result = db.load_evaluated_expressions()
|
|
17
|
+
assert result == set()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_save_qualified_alpha_offline():
|
|
21
|
+
db = make_db()
|
|
22
|
+
db.save_qualified_alpha({"expression": "rank(close)", "sharpe": 1.5})
|
|
23
|
+
# No exception expected
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_log_evaluation_offline():
|
|
27
|
+
db = make_db()
|
|
28
|
+
db.log_evaluation({"expression": "rank(close)", "stage": "STAGE0", "status": "PASS"})
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_upsert_rl_reward_offline():
|
|
32
|
+
db = make_db()
|
|
33
|
+
db.upsert_rl_reward("rank(close)", delta=10.0)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_load_top_performing_exemplars_offline():
|
|
37
|
+
db = make_db()
|
|
38
|
+
assert db.load_top_performing_exemplars() == []
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_load_archetype_performance_summary_offline():
|
|
42
|
+
db = make_db()
|
|
43
|
+
assert db.load_archetype_performance_summary() == {}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_save_correlated_alpha_offline():
|
|
47
|
+
db = make_db()
|
|
48
|
+
db.save_correlated_alpha({"expression": "rank(close)", "max_correlation": 0.85})
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_save_rejected_alpha_offline():
|
|
52
|
+
db = make_db()
|
|
53
|
+
db.save_rejected_alpha({"expression": "rank(close)", "reason": "low sharpe"})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for brain_store.store.OptionsStore — file-only mode (no Postgres).
|
|
3
|
+
|
|
4
|
+
Uses a tmp_path fixture so no real CSV/JSON files are created in the repo.
|
|
5
|
+
|
|
6
|
+
Acceptance criteria
|
|
7
|
+
-------------------
|
|
8
|
+
AC-1 save_qualified_alpha() creates passed_options_alphas.csv.
|
|
9
|
+
AC-2 log_evaluation() creates evaluated_candidates.csv.
|
|
10
|
+
AC-3 load_evaluated_expressions() returns expressions from the CSV.
|
|
11
|
+
AC-4 cache_pnl/load_pnl round-trip works correctly.
|
|
12
|
+
"""
|
|
13
|
+
import os
|
|
14
|
+
import pytest
|
|
15
|
+
from brain_store.store import OptionsStore
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.fixture
|
|
19
|
+
def store(tmp_path):
|
|
20
|
+
return OptionsStore(data_dir=str(tmp_path), database_url=None)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_ac1_save_qualified_alpha(store, tmp_path):
|
|
24
|
+
store.save_qualified_alpha({"expression": "rank(close)", "sharpe": 1.5, "fitness": 1.1})
|
|
25
|
+
assert os.path.exists(os.path.join(str(tmp_path), "passed_options_alphas.csv"))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_ac2_log_evaluation(store, tmp_path):
|
|
29
|
+
store.log_evaluation({"expression": "rank(close)", "stage": "STAGE0", "status": "PASS"})
|
|
30
|
+
assert os.path.exists(os.path.join(str(tmp_path), "evaluated_candidates.csv"))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_ac3_load_evaluated_expressions(store):
|
|
34
|
+
store.log_evaluation({"expression": "rank(close)", "stage": "STAGE0", "status": "PASS"})
|
|
35
|
+
store.log_evaluation({"expression": "rank(volume)", "stage": "STAGE0", "status": "FAIL"})
|
|
36
|
+
exprs = store.load_evaluated_expressions()
|
|
37
|
+
assert "rank(close)" in exprs
|
|
38
|
+
assert "rank(volume)" in exprs
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_ac4_pnl_cache_round_trip(store):
|
|
42
|
+
pnl = [0.01, -0.02, 0.03]
|
|
43
|
+
store.cache_pnl("test_alpha_001", pnl)
|
|
44
|
+
loaded = store.load_pnl("test_alpha_001")
|
|
45
|
+
assert loaded == pnl
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_load_pnl_missing_returns_none(store):
|
|
49
|
+
assert store.load_pnl("nonexistent_id") is None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_alphastore_alias_and_repr(tmp_path):
|
|
53
|
+
from brain_store import AlphaStore, OptionsStore
|
|
54
|
+
assert AlphaStore is OptionsStore
|
|
55
|
+
s = AlphaStore(data_dir=str(tmp_path), database_url=None)
|
|
56
|
+
assert repr(s).startswith("AlphaStore(")
|
|
57
|
+
assert s.db.is_available() is False
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_load_evaluated_expressions_pagination(store):
|
|
61
|
+
store.log_evaluation({"expression": "expr_1", "stage": "STAGE0", "status": "PASS"})
|
|
62
|
+
store.log_evaluation({"expression": "expr_2", "stage": "STAGE0", "status": "PASS"})
|
|
63
|
+
store.log_evaluation({"expression": "expr_3", "stage": "STAGE0", "status": "PASS"})
|
|
64
|
+
page = store.load_evaluated_expressions(limit=2, offset=0)
|
|
65
|
+
assert len(page) == 2
|