2026-08-17 13:48:50 +07:00
|
|
|
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")
|
|
|
|
|
|
|
|
|
|
|
2026-08-18 02:00:53 +07:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
2026-08-17 13:48:50 +07:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
2026-08-18 02:00:53 +07:00
|
|
|
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"),
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
2026-08-17 13:48:50 +07:00
|
|
|
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)
|