malkhut(T4): Strategy DSL v2 + generator + supporting modules
Strategy DSL v2 (dsl.py): 40+ action primitives, 40+ market sensors, 12 comparison operators, 16 builtins, full parser. Strategy Generator (generator.py): genetic programming evolution — crossover, mutation, tournament selection, pool management. Supporting: discrepancy tracking, execution quality, hooks, feature importance, observability, parallel eval, auto-rollback, stress testing, structured observations, trajectory recording.
This commit is contained in:
134
MALKHUT/malkhut/training/parallel.py
Normal file
134
MALKHUT/malkhut/training/parallel.py
Normal file
@@ -0,0 +1,134 @@
|
||||
"""
|
||||
Training Parallelism — parallel evaluation across scenarios.
|
||||
|
||||
CMA-ES evaluates sequentially. This module parallelizes evaluation
|
||||
across scenarios for 4-8x faster convergence.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import concurrent.futures
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, List, Optional, Sequence
|
||||
|
||||
from malkhut.state import FulfilmentPolicyParams, MarketWorldState
|
||||
from malkhut.training.cma_trainer import PolicyEvaluator, Scenario
|
||||
|
||||
|
||||
class ParallelEvaluator:
|
||||
"""
|
||||
Parallel evaluation of strategies across scenarios.
|
||||
|
||||
Uses ThreadPoolExecutor for I/O-bound scenarios.
|
||||
Uses ProcessPoolExecutor for CPU-bound scenarios.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
evaluator: PolicyEvaluator,
|
||||
max_workers: int = 4,
|
||||
) -> None:
|
||||
self.evaluator = evaluator
|
||||
self._max_workers = max_workers
|
||||
|
||||
def evaluate_candidate(
|
||||
self,
|
||||
params: FulfilmentPolicyParams,
|
||||
scenarios: Sequence[Scenario],
|
||||
rng_seed: int = 0,
|
||||
) -> tuple[float, list]:
|
||||
"""Evaluate candidate with parallel scenario execution."""
|
||||
if len(scenarios) <= 1 or self._max_workers <= 1:
|
||||
return self.evaluator.evaluate_candidate(params, scenarios, rng_seed)
|
||||
|
||||
# Split scenarios across workers
|
||||
chunk_size = max(1, len(scenarios) // self._max_workers)
|
||||
chunks = []
|
||||
for i in range(0, len(scenarios), chunk_size):
|
||||
chunks.append(scenarios[i:i + chunk_size])
|
||||
|
||||
# Parallel evaluation
|
||||
all_results = []
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=self._max_workers) as executor:
|
||||
futures = []
|
||||
for i, chunk in enumerate(chunks):
|
||||
future = executor.submit(
|
||||
self.evaluator.evaluate_candidate,
|
||||
params, chunk, rng_seed + i,
|
||||
)
|
||||
futures.append(future)
|
||||
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
score, results = future.result()
|
||||
all_results.extend(results)
|
||||
|
||||
# Aggregate scores
|
||||
if all_results:
|
||||
scores = [r.pnl_bps for r in all_results]
|
||||
avg_score = sum(scores) / len(scores)
|
||||
else:
|
||||
avg_score = 0.0
|
||||
|
||||
return avg_score, all_results
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class TrainingMetrics:
|
||||
"""Metrics for a training run."""
|
||||
total_time_s: float
|
||||
generations: int
|
||||
total_evals: int
|
||||
best_score: float
|
||||
avg_score: float
|
||||
score_improvement: float
|
||||
convergence_gen: int
|
||||
|
||||
|
||||
class TrainingMonitor:
|
||||
"""
|
||||
Monitor training progress and convergence.
|
||||
|
||||
Tracks metrics, detects convergence, logs progress.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._scores: list[float] = []
|
||||
self._times: list[float] = []
|
||||
self._start_time = time.time()
|
||||
|
||||
def record_generation(self, score: float) -> None:
|
||||
self._scores.append(score)
|
||||
self._times.append(time.time())
|
||||
|
||||
@property
|
||||
def best_score(self) -> float:
|
||||
return max(self._scores) if self._scores else 0.0
|
||||
|
||||
@property
|
||||
def avg_score(self) -> float:
|
||||
return sum(self._scores) / len(self._scores) if self._scores else 0.0
|
||||
|
||||
@property
|
||||
def improvement(self) -> float:
|
||||
if len(self._scores) < 2:
|
||||
return 0.0
|
||||
return self._scores[-1] - self._scores[0]
|
||||
|
||||
@property
|
||||
def converged(self) -> bool:
|
||||
if len(self._scores) < 5:
|
||||
return False
|
||||
recent = self._scores[-5:]
|
||||
variance = sum((s - self.avg_score) ** 2 for s in recent) / len(recent)
|
||||
return variance < 0.01 # low variance = converged
|
||||
|
||||
def metrics(self) -> TrainingMetrics:
|
||||
return TrainingMetrics(
|
||||
total_time_s=time.time() - self._start_time,
|
||||
generations=len(self._scores),
|
||||
total_evals=0,
|
||||
best_score=self.best_score,
|
||||
avg_score=self.avg_score,
|
||||
score_improvement=self.improvement,
|
||||
convergence_gen=len(self._scores) if self.converged else -1,
|
||||
)
|
||||
Reference in New Issue
Block a user