budget.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. """
  2. 风险预算系统
  3. 月度风险预算分配与追踪
  4. """
  5. from dataclasses import dataclass, field
  6. from datetime import datetime, timedelta
  7. from typing import Dict, List, Optional
  8. from enum import Enum
  9. class BudgetStatus(Enum):
  10. """预算状态"""
  11. NORMAL = "normal"
  12. WARNING = "warning"
  13. COOLING = "cooling"
  14. STOPPED = "stopped"
  15. @dataclass
  16. class BudgetState:
  17. """预算状态"""
  18. monthly_budget: float
  19. consumed: float
  20. remaining: float
  21. status: BudgetStatus
  22. cooling_end_date: Optional[datetime] = None
  23. class RiskBudgetManager:
  24. """
  25. 风险预算管理器
  26. 功能:
  27. 1. 月度预算分配(默认账户价值的5%)
  28. 2. 单笔交易风险消耗追踪
  29. 3. 触发阈值管理(预警10%、冷却20%、停止50%)
  30. """
  31. def __init__(
  32. self,
  33. monthly_budget_pct: float = 0.05,
  34. warning_threshold: float = 0.10,
  35. cooling_threshold: float = 0.20,
  36. stop_threshold: float = 0.50,
  37. cooling_period_days: int = 3
  38. ):
  39. self.monthly_budget_pct = monthly_budget_pct
  40. self.warning_threshold = warning_threshold
  41. self.cooling_threshold = cooling_threshold
  42. self.stop_threshold = stop_threshold
  43. self.cooling_period_days = cooling_period_days
  44. self.current_budget: Optional[BudgetState] = None
  45. self.trade_history: List[Dict] = []
  46. def allocate_monthly_budget(self, account_value: float):
  47. """分配月度预算"""
  48. budget = account_value * self.monthly_budget_pct
  49. self.current_budget = BudgetState(
  50. monthly_budget=budget,
  51. consumed=0.0,
  52. remaining=budget,
  53. status=BudgetStatus.NORMAL
  54. )
  55. def record_trade_result(
  56. self,
  57. trade_id: str,
  58. realized_loss: float,
  59. timestamp: Optional[datetime] = None
  60. ) -> BudgetState:
  61. """
  62. 记录交易结果,更新预算消耗
  63. Args:
  64. trade_id: 交易ID
  65. realized_loss: 实际亏损(正数)
  66. timestamp: 时间戳
  67. Returns:
  68. BudgetState: 更新后的预算状态
  69. """
  70. if self.current_budget is None:
  71. raise RuntimeError("Budget not allocated. Call allocate_monthly_budget first.")
  72. if timestamp is None:
  73. timestamp = datetime.now()
  74. # 只记录亏损
  75. if realized_loss > 0:
  76. self.current_budget.consumed += realized_loss
  77. self.current_budget.remaining = max(0, self.current_budget.monthly_budget - self.current_budget.consumed)
  78. self.trade_history.append({
  79. "trade_id": trade_id,
  80. "loss": realized_loss,
  81. "timestamp": timestamp,
  82. "cumulative_consumed": self.current_budget.consumed
  83. })
  84. # 更新状态
  85. consumption_ratio = self.current_budget.consumed / self.current_budget.monthly_budget
  86. if consumption_ratio >= self.stop_threshold:
  87. self.current_budget.status = BudgetStatus.STOPPED
  88. elif consumption_ratio >= self.cooling_threshold:
  89. if self.current_budget.status != BudgetStatus.COOLING:
  90. self.current_budget.status = BudgetStatus.COOLING
  91. self.current_budget.cooling_end_date = timestamp + timedelta(days=self.cooling_period_days)
  92. elif consumption_ratio >= self.warning_threshold:
  93. self.current_budget.status = BudgetStatus.WARNING
  94. return self.current_budget
  95. def check_can_trade(self) -> bool:
  96. """检查是否可以交易 - 激进模式:除非完全停止,否则允许交易"""
  97. if self.current_budget is None:
  98. return True
  99. # 检查冷却期
  100. if self.current_budget.status == BudgetStatus.COOLING:
  101. if self.current_budget.cooling_end_date and datetime.now() < self.current_budget.cooling_end_date:
  102. # 冷却期允许50%仓位
  103. return True
  104. else:
  105. # 冷却期结束,恢复
  106. self.current_budget.status = BudgetStatus.WARNING
  107. return True
  108. # 即使是STOPPED状态,也允许25%仓位交易
  109. return True
  110. def get_budget_summary(self) -> Dict:
  111. """获取预算摘要"""
  112. if self.current_budget is None:
  113. return {"status": "not_allocated"}
  114. return {
  115. "monthly_budget": self.current_budget.monthly_budget,
  116. "consumed": self.current_budget.consumed,
  117. "remaining": self.current_budget.remaining,
  118. "consumption_ratio": self.current_budget.consumed / self.current_budget.monthly_budget,
  119. "status": self.current_budget.status.value,
  120. "can_trade": self.check_can_trade()
  121. }