from __future__ import annotations from django.db import models from control_plane.common import TimestampedModel class TradingProjectStatus(models.TextChoices): ARCHAEOLOGY = "ARCHAEOLOGY" DATA_VALIDATION = "DATA_VALIDATION" BACKTEST_AUDIT = "BACKTEST_AUDIT" OFFLINE_ONLY = "OFFLINE_ONLY" SHADOW_READY = "SHADOW_READY" PAUSED = "PAUSED" class DataKind(models.TextChoices): OHLCV = "OHLCV" TRADES = "TRADES" TICKS = "TICKS" ORDER_BOOK = "ORDER_BOOK" FUNDING = "FUNDING" OPEN_INTEREST = "OPEN_INTEREST" LIQUIDATIONS = "LIQUIDATIONS" MARK_PRICE = "MARK_PRICE" INDEX_PRICE = "INDEX_PRICE" FILL_HISTORY = "FILL_HISTORY" OTHER = "OTHER" class EvidenceStatus(models.TextChoices): CONFIRMED = "CONFIRMED" INFERRED = "INFERRED" UNKNOWN = "UNKNOWN" SAFE = "SAFE" SUSPICIOUS = "SUSPICIOUS" BLOCKED = "BLOCKED" class SplitKind(models.TextChoices): DISCOVERY = "DISCOVERY" TRAIN = "TRAIN" VALIDATION = "VALIDATION" HOLDOUT = "HOLDOUT" FORWARD = "FORWARD" LIVE = "LIVE" class StrategyStage(models.TextChoices): HYPOTHESIS = "HYPOTHESIS" BACKTEST = "BACKTEST" WALK_FORWARD = "WALK_FORWARD" ROBUSTNESS = "ROBUSTNESS" HOLDOUT = "HOLDOUT" SHADOW = "SHADOW" MICRO_LIVE = "MICRO_LIVE" PROVEN = "PROVEN" CHAMPION = "CHAMPION" KILLED = "KILLED" class ExperimentStatus(models.TextChoices): PROPOSED = "PROPOSED" RUNNING = "RUNNING" SUCCEEDED = "SUCCEEDED" REJECTED = "REJECTED" FAILED = "FAILED" class RunStatus(models.TextChoices): QUEUED = "QUEUED" RUNNING = "RUNNING" SUCCEEDED = "SUCCEEDED" FAILED = "FAILED" INVALID = "INVALID" class Decision(models.TextChoices): KILL = "KILL" REVISE = "REVISE" ADVANCE = "ADVANCE" PROMOTE = "PROMOTE" DEMOTE = "DEMOTE" REQUIRE_MORE_EVIDENCE = "REQUIRE_MORE_EVIDENCE" class FailureType(models.TextChoices): NO_ALPHA = "NO_ALPHA" OVERFIT = "OVERFIT" LEAKAGE = "LEAKAGE" FEE_DESTROYED = "FEE_DESTROYED" SLIPPAGE_DESTROYED = "SLIPPAGE_DESTROYED" LATENCY_DESTROYED = "LATENCY_DESTROYED" REGIME_FRAGILE = "REGIME_FRAGILE" PARAMETER_FRAGILE = "PARAMETER_FRAGILE" INSUFFICIENT_TRADES = "INSUFFICIENT_TRADES" RISK_TOO_HIGH = "RISK_TOO_HIGH" TAIL_RISK = "TAIL_RISK" EXECUTION_FAILURE = "EXECUTION_FAILURE" DATA_FAILURE = "DATA_FAILURE" LIVE_DIVERGENCE = "LIVE_DIVERGENCE" CAPACITY_LIMIT = "CAPACITY_LIMIT" UNKNOWN = "UNKNOWN" class AllocationTier(models.TextChoices): RESEARCH = "RESEARCH" SHADOW = "SHADOW" MICRO = "MICRO" VALIDATED = "VALIDATED" PROVEN = "PROVEN" CHAMPION = "CHAMPION" class TradingProject(TimestampedModel): project = models.ForeignKey("projects.Project", on_delete=models.PROTECT, related_name="trading_projects") name = models.CharField(max_length=200) slug = models.SlugField(max_length=120, unique=True) goal = models.TextField() repository_path = models.TextField(blank=True) working_directory = models.TextField(blank=True) profile_name = models.CharField(max_length=120, default="crypto_hyperliquid") existing_system_profile = models.CharField(max_length=120, default="hyperscalper") status = models.CharField(max_length=32, choices=TradingProjectStatus.choices, default=TradingProjectStatus.ARCHAEOLOGY) current_champion = models.ForeignKey("StrategyVersion", on_delete=models.SET_NULL, null=True, blank=True, related_name="champion_for_projects") metadata = models.JSONField(default=dict, blank=True) class MarketDataset(TimestampedModel): trading_project = models.ForeignKey(TradingProject, on_delete=models.CASCADE, related_name="market_datasets") name = models.CharField(max_length=200) kind = models.CharField(max_length=32, choices=DataKind.choices) venue = models.CharField(max_length=120, blank=True) symbol = models.CharField(max_length=120, blank=True) market_type = models.CharField(max_length=80, blank=True) metadata = models.JSONField(default=dict, blank=True) class Meta: constraints = [models.UniqueConstraint(fields=["trading_project", "name"], name="unique_trading_market_dataset")] class MarketDatasetVersion(TimestampedModel): dataset = models.ForeignKey(MarketDataset, on_delete=models.CASCADE, related_name="versions") version = models.CharField(max_length=120) reference = models.TextField() content_hash = models.CharField(max_length=128) processing_version = models.CharField(max_length=120, blank=True) start_at = models.DateTimeField(null=True, blank=True) end_at = models.DateTimeField(null=True, blank=True) resolution = models.CharField(max_length=80, blank=True) record_count = models.BigIntegerField(null=True, blank=True) fields = models.JSONField(default=list, blank=True) quality = models.JSONField(default=dict, blank=True) lookahead_risk_status = models.CharField(max_length=16, choices=EvidenceStatus.choices, default=EvidenceStatus.UNKNOWN) quality_status = models.CharField(max_length=16, choices=EvidenceStatus.choices, default=EvidenceStatus.UNKNOWN) temporal_splits = models.JSONField(default=dict, blank=True) immutable = models.BooleanField(default=False) class Meta: constraints = [models.UniqueConstraint(fields=["dataset", "version"], name="unique_trading_market_dataset_version")] def save(self, *args, **kwargs): if self.pk and self.immutable: original = type(self).objects.get(pk=self.pk) fields = ["version", "reference", "content_hash", "processing_version", "start_at", "end_at", "resolution", "record_count", "fields", "quality", "lookahead_risk_status", "temporal_splits"] if any(getattr(self, field) != getattr(original, field) for field in fields): raise ValueError("MarketDatasetVersion is immutable after research use.") super().save(*args, **kwargs) class FeatureDefinition(TimestampedModel): trading_project = models.ForeignKey(TradingProject, on_delete=models.CASCADE, related_name="features") name = models.CharField(max_length=160) family = models.CharField(max_length=120, blank=True) implementation_reference = models.TextField() code_hash = models.CharField(max_length=128, blank=True) lookback = models.JSONField(default=dict, blank=True) dependencies = models.JSONField(default=list, blank=True) normalization = models.JSONField(default=dict, blank=True) timeframe = models.CharField(max_length=80, blank=True) leakage_status = models.CharField(max_length=16, choices=EvidenceStatus.choices, default=EvidenceStatus.UNKNOWN) evidence = models.JSONField(default=dict, blank=True) class Meta: constraints = [models.UniqueConstraint(fields=["trading_project", "name", "code_hash"], name="unique_trading_feature_definition")] class FeatureSetVersion(TimestampedModel): trading_project = models.ForeignKey(TradingProject, on_delete=models.CASCADE, related_name="feature_sets") name = models.CharField(max_length=160) version = models.CharField(max_length=120) feature_definitions = models.ManyToManyField(FeatureDefinition, related_name="feature_sets") content_hash = models.CharField(max_length=128) immutable = models.BooleanField(default=False) class Meta: constraints = [models.UniqueConstraint(fields=["trading_project", "name", "version"], name="unique_trading_feature_set_version")] class Strategy(TimestampedModel): trading_project = models.ForeignKey(TradingProject, on_delete=models.CASCADE, related_name="strategies") name = models.CharField(max_length=200) strategy_class = models.CharField(max_length=80, blank=True) description = models.TextField(blank=True) metadata = models.JSONField(default=dict, blank=True) class StrategyVersion(TimestampedModel): strategy = models.ForeignKey(Strategy, on_delete=models.CASCADE, related_name="versions") version = models.CharField(max_length=120) parent_version = models.ForeignKey("self", on_delete=models.SET_NULL, null=True, blank=True, related_name="children") feature_set = models.ForeignKey(FeatureSetVersion, on_delete=models.PROTECT, null=True, blank=True, related_name="strategy_versions") genome = models.JSONField(default=dict) fingerprint = models.CharField(max_length=128) code_reference = models.TextField(blank=True) stage = models.CharField(max_length=32, choices=StrategyStage.choices, default=StrategyStage.HYPOTHESIS) holdout_exposure_count = models.PositiveIntegerField(default=0) immutable = models.BooleanField(default=False) class Meta: constraints = [models.UniqueConstraint(fields=["strategy", "version"], name="unique_trading_strategy_version"), models.UniqueConstraint(fields=["strategy", "fingerprint"], name="unique_trading_strategy_fingerprint")] def save(self, *args, **kwargs): if self.pk and self.immutable: original = type(self).objects.get(pk=self.pk) if any(getattr(self, field) != getattr(original, field) for field in ["version", "parent_version_id", "feature_set_id", "genome", "fingerprint", "code_reference"]): raise ValueError("Live or evaluated StrategyVersion is immutable; create a child version.") super().save(*args, **kwargs) class TradingCohort(TimestampedModel): trading_project = models.ForeignKey(TradingProject, on_delete=models.CASCADE, related_name="cohorts") name = models.CharField(max_length=200) status = models.CharField(max_length=32, choices=StrategyStage.choices, default=StrategyStage.HYPOTHESIS) policy_snapshot = models.JSONField(default=dict) dataset_version = models.ForeignKey(MarketDatasetVersion, on_delete=models.PROTECT, related_name="cohorts") report = models.ForeignKey("TradingResearchReport", on_delete=models.SET_NULL, null=True, blank=True, related_name="cohorts") class CohortMembership(TimestampedModel): """An ordered, immutable strategy membership imported from a frozen cohort spec.""" cohort = models.ForeignKey(TradingCohort, on_delete=models.CASCADE, related_name="memberships") strategy_version = models.ForeignKey(StrategyVersion, on_delete=models.PROTECT, related_name="cohort_memberships") ordinal = models.PositiveIntegerField() member_id = models.UUIDField(unique=True) provenance = models.CharField(max_length=80) metadata = models.JSONField(default=dict) class Meta: constraints = [models.UniqueConstraint(fields=["cohort", "ordinal"], name="unique_cohort_member_ordinal")] def save(self, *args, **kwargs): if self.pk and type(self).objects.filter(pk=self.pk).exists(): original = type(self).objects.get(pk=self.pk) fields = ["cohort_id", "strategy_version_id", "ordinal", "member_id", "provenance", "metadata"] if any(getattr(self, field) != getattr(original, field) for field in fields): raise ValueError("CohortMembership is immutable after frozen-spec materialization.") super().save(*args, **kwargs) class StrategyExperiment(TimestampedModel): cohort = models.ForeignKey(TradingCohort, on_delete=models.CASCADE, related_name="experiments") strategy_version = models.ForeignKey(StrategyVersion, on_delete=models.PROTECT, related_name="experiments") hypothesis = models.TextField() market_rationale = models.TextField() expected_regime = models.JSONField(default=dict) controls = models.JSONField(default=dict) success_criteria = models.JSONField(default=dict) rejection_criteria = models.JSONField(default=dict) risk_assumptions = models.JSONField(default=dict) execution_assumptions = models.JSONField(default=dict) estimated_evaluation_cost = models.JSONField(default=dict) status = models.CharField(max_length=32, choices=ExperimentStatus.choices, default=ExperimentStatus.PROPOSED) conclusion = models.CharField(max_length=32, choices=Decision.choices, blank=True) failure_type = models.CharField(max_length=32, choices=FailureType.choices, blank=True) class BacktestRun(TimestampedModel): experiment = models.ForeignKey(StrategyExperiment, on_delete=models.PROTECT, related_name="backtests") dataset_version = models.ForeignKey(MarketDatasetVersion, on_delete=models.PROTECT, related_name="backtest_runs") split = models.CharField(max_length=32, choices=SplitKind.choices) execution_model_version = models.CharField(max_length=120) configuration = models.JSONField(default=dict) status = models.CharField(max_length=16, choices=RunStatus.choices, default=RunStatus.QUEUED) metrics = models.JSONField(default=dict) artifact_reference = models.TextField(blank=True) failure_details = models.TextField(blank=True) class QualificationReplayRun(TimestampedModel): """Persisted CANARY_ONLY qualification replay and its immutable inputs.""" strategy_version = models.ForeignKey(StrategyVersion, on_delete=models.PROTECT, related_name="qualification_runs") dataset_version = models.ForeignKey(MarketDatasetVersion, on_delete=models.PROTECT, related_name="qualification_runs") fold = models.CharField(max_length=120) scenario_name = models.CharField(max_length=160) runner_name = models.CharField(max_length=160) replay_mode = models.CharField(max_length=32, default="CANARY_ONLY") input_hash = models.CharField(max_length=128) calibration = models.JSONField(default=dict) configuration = models.JSONField(default=dict) summary = models.JSONField(default=dict) class Meta: constraints = [models.UniqueConstraint( fields=["strategy_version", "dataset_version", "fold", "scenario_name", "runner_name", "input_hash"], name="unique_qualification_replay_identity", )] class QualificationReplayLedger(TimestampedModel): """Append-only replay result. Payload preserves every directly emitted ledger field.""" qualification_run = models.ForeignKey(QualificationReplayRun, on_delete=models.CASCADE, related_name="ledger_rows") sequence = models.PositiveIntegerField() identity = models.CharField(max_length=512) result = models.JSONField(default=dict) class Meta: constraints = [ models.UniqueConstraint(fields=["qualification_run", "sequence"], name="unique_qualification_ledger_sequence"), models.UniqueConstraint(fields=["qualification_run", "identity"], name="unique_qualification_ledger_identity"), ] class WalkForwardRun(TimestampedModel): experiment = models.ForeignKey(StrategyExperiment, on_delete=models.PROTECT, related_name="walk_forwards") dataset_version = models.ForeignKey(MarketDatasetVersion, on_delete=models.PROTECT, related_name="walk_forward_runs") folds = models.JSONField(default=list) embargo_bars = models.PositiveIntegerField(default=0) status = models.CharField(max_length=16, choices=RunStatus.choices, default=RunStatus.QUEUED) metrics = models.JSONField(default=dict) class ShadowRun(TimestampedModel): strategy_version = models.ForeignKey(StrategyVersion, on_delete=models.PROTECT, related_name="shadow_runs") status = models.CharField(max_length=16, choices=RunStatus.choices, default=RunStatus.QUEUED) evidence = models.JSONField(default=dict) class LiveStrategyRun(TimestampedModel): strategy_version = models.ForeignKey(StrategyVersion, on_delete=models.PROTECT, related_name="live_runs") status = models.CharField(max_length=16, choices=RunStatus.choices, default=RunStatus.QUEUED) allocated_capital = models.DecimalField(max_digits=12, decimal_places=2, default=0) safety_policy = models.JSONField(default=dict) reconciliation_status = models.CharField(max_length=32, default="NOT_STARTED") enabled = models.BooleanField(default=False) class TradeRecord(TimestampedModel): strategy_version = models.ForeignKey(StrategyVersion, on_delete=models.PROTECT, related_name="trade_records") live_run = models.ForeignKey(LiveStrategyRun, on_delete=models.SET_NULL, null=True, blank=True, related_name="trades") source = models.CharField(max_length=32, default="IMPORTED") lifecycle = models.JSONField(default=dict) gross_pnl = models.FloatField(default=0) fees = models.FloatField(default=0) funding = models.FloatField(default=0) slippage = models.FloatField(default=0) other_execution_cost = models.FloatField(default=0) net_pnl = models.FloatField(default=0) class StrategyEvaluation(TimestampedModel): strategy_version = models.ForeignKey(StrategyVersion, on_delete=models.PROTECT, related_name="evaluations") experiment = models.ForeignKey(StrategyExperiment, on_delete=models.PROTECT, related_name="evaluations") stage = models.CharField(max_length=32, choices=StrategyStage.choices) verdict = models.CharField(max_length=32, choices=Decision.choices) metrics = models.JSONField(default=dict) evidence = models.JSONField(default=dict) failure_type = models.CharField(max_length=32, choices=FailureType.choices, blank=True) class StrategyPromotionDecision(TimestampedModel): trading_project = models.ForeignKey(TradingProject, on_delete=models.CASCADE, related_name="promotion_decisions") candidate = models.ForeignKey(StrategyVersion, on_delete=models.PROTECT, related_name="promotion_decisions") champion = models.ForeignKey(StrategyVersion, on_delete=models.SET_NULL, null=True, blank=True, related_name="challenged_by") decision = models.CharField(max_length=32, choices=Decision.choices) policy_snapshot = models.JSONField(default=dict) evidence = models.JSONField(default=dict) reason = models.TextField() class StrategyCapitalAllocation(TimestampedModel): strategy_version = models.ForeignKey(StrategyVersion, on_delete=models.PROTECT, related_name="capital_allocations") tier = models.CharField(max_length=32, choices=AllocationTier.choices) amount = models.DecimalField(max_digits=12, decimal_places=2, default=0) active = models.BooleanField(default=False) policy_snapshot = models.JSONField(default=dict) class TradingResearchReport(TimestampedModel): trading_project = models.ForeignKey(TradingProject, on_delete=models.CASCADE, related_name="research_reports") report_type = models.CharField(max_length=80) title = models.CharField(max_length=255) content = models.JSONField(default=dict) markdown = models.TextField(blank=True) evidence_status = models.CharField(max_length=16, choices=EvidenceStatus.choices, default=EvidenceStatus.UNKNOWN)