openfund-arbitrage 3.9.1__py3-none-any.whl

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.
Files changed (80) hide show
  1. app/__init__.py +3 -0
  2. app/config.py +449 -0
  3. app/core/__init__.py +16 -0
  4. app/core/arbitrage_executor.py +3900 -0
  5. app/core/diagnostics.py +571 -0
  6. app/core/exchange_dialects/__init__.py +11 -0
  7. app/core/exchange_dialects/base.py +310 -0
  8. app/core/exchange_dialects/binance.py +84 -0
  9. app/core/exchange_dialects/bitget.py +318 -0
  10. app/core/exchange_dialects/bybit.py +123 -0
  11. app/core/exchange_dialects/factory.py +15 -0
  12. app/core/exchange_dialects/gate.py +138 -0
  13. app/core/exchange_dialects/generic.py +46 -0
  14. app/core/exchange_dialects/htx.py +187 -0
  15. app/core/exchange_dialects/okx.py +186 -0
  16. app/core/exchange_manager.py +186 -0
  17. app/core/exchange_wrapper.py +462 -0
  18. app/core/execution/state_machine.py +57 -0
  19. app/core/execution/strategies/__init__.py +5 -0
  20. app/core/execution/strategies/base_strategy.py +30 -0
  21. app/core/execution/strategies/dual_maker_strategy.py +430 -0
  22. app/core/execution/strategies/taker_fallback_strategy.py +60 -0
  23. app/core/execution/strategies/twap_strategy.py +266 -0
  24. app/core/execution_observation.py +871 -0
  25. app/core/funding_rate_fetcher.py +839 -0
  26. app/core/helpers.py +242 -0
  27. app/core/lsi_manager.py +311 -0
  28. app/core/notification.py +236 -0
  29. app/core/real_trade_guard.py +267 -0
  30. app/core/twap_backfillers.py +112 -0
  31. app/core/twap_manager.py +1110 -0
  32. app/data/__init__.py +5 -0
  33. app/data/mock_store.py +164 -0
  34. app/data/persistent_store.py +1088 -0
  35. app/dependencies.py +67 -0
  36. app/domain/opportunity_evaluator.py +841 -0
  37. app/logger.py +173 -0
  38. app/main.py +424 -0
  39. app/presentation/opportunity_formatter.py +1294 -0
  40. app/routers/__init__.py +1 -0
  41. app/routers/exchanges.py +98 -0
  42. app/routers/opportunities.py +110 -0
  43. app/routers/performance.py +123 -0
  44. app/routers/positions.py +125 -0
  45. app/routers/risk.py +113 -0
  46. app/scheduler/__init__.py +5 -0
  47. app/scheduler/jobs/__init__.py +13 -0
  48. app/scheduler/jobs/auto_close_check.py +1370 -0
  49. app/scheduler/jobs/daily_profit_report.py +4191 -0
  50. app/scheduler/jobs/funding_rate_sync.py +54 -0
  51. app/scheduler/jobs/llm_review_report.py +73 -0
  52. app/scheduler/jobs/opportunity_scan.py +329 -0
  53. app/scheduler/jobs/paper_trading_job.py +54 -0
  54. app/scheduler/jobs/position_monitor.py +199 -0
  55. app/scheduler/jobs/report_self_healing.py +107 -0
  56. app/scheduler/scheduler_manager.py +217 -0
  57. app/schemas/__init__.py +1 -0
  58. app/schemas/audit.py +36 -0
  59. app/schemas/common.py +36 -0
  60. app/schemas/exchange.py +38 -0
  61. app/schemas/funding_rate.py +39 -0
  62. app/schemas/opportunity.py +406 -0
  63. app/schemas/paper_trading.py +28 -0
  64. app/schemas/performance.py +130 -0
  65. app/schemas/position.py +115 -0
  66. app/schemas/risk.py +105 -0
  67. app/services/__init__.py +1 -0
  68. app/services/exchange_service.py +101 -0
  69. app/services/funding_sync_service.py +2323 -0
  70. app/services/llm_service.py +37 -0
  71. app/services/opportunity_service.py +947 -0
  72. app/services/paper_trading_service.py +418 -0
  73. app/services/performance_service.py +494 -0
  74. app/services/position_service.py +287 -0
  75. app/services/position_sync_service.py +950 -0
  76. app/services/risk_monitoring_service.py +322 -0
  77. app/services/risk_service.py +299 -0
  78. openfund_arbitrage-3.9.1.dist-info/METADATA +172 -0
  79. openfund_arbitrage-3.9.1.dist-info/RECORD +80 -0
  80. openfund_arbitrage-3.9.1.dist-info/WHEEL +4 -0
app/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """OpenFund Arbitrage - 跨交易所资金费率套利系统"""
2
+
3
+ __version__ = "3.9.1"
app/config.py ADDED
@@ -0,0 +1,449 @@
1
+ """配置管理模块"""
2
+
3
+ import os
4
+ import re
5
+ from decimal import Decimal, InvalidOperation
6
+ from pathlib import Path
7
+ from typing import Any, Optional
8
+
9
+ import yaml
10
+ from pydantic import BaseModel, Field
11
+ from dotenv import load_dotenv
12
+
13
+ # 加载 .env 环境变量
14
+ load_dotenv()
15
+
16
+
17
+ def expand_env_vars(value: Any) -> Any:
18
+ """递归展开环境变量"""
19
+ if isinstance(value, str):
20
+ # 匹配 ${VAR_NAME} 格式的环境变量
21
+ pattern = r'\$\{([^}]+)\}'
22
+ matches = re.findall(pattern, value)
23
+ for match in matches:
24
+ env_value = os.environ.get(match, "")
25
+ value = value.replace(f"${{{match}}}", env_value)
26
+ return value
27
+ elif isinstance(value, dict):
28
+ return {k: expand_env_vars(v) for k, v in value.items()}
29
+ elif isinstance(value, list):
30
+ return [expand_env_vars(item) for item in value]
31
+ return value
32
+
33
+
34
+ class ExchangeConfig(BaseModel):
35
+ """单个交易所配置"""
36
+ enabled: bool = True
37
+ apiKey: str = ""
38
+ secret: str = ""
39
+ password: Optional[str] = None
40
+ is_demo_trading: bool = False
41
+ options: dict = Field(default_factory=lambda: {"defaultType": "swap"})
42
+ maker_fee: Decimal = Decimal("0.0002")
43
+ taker_fee: Decimal = Decimal("0.0005")
44
+ settlement_interval: int = 1 # 兜底值,实际值由 dialect 动态获取
45
+ margin_buffer: Decimal = Decimal("1.01") # 保证金缓冲比例, 可按交易所独立配置
46
+
47
+
48
+ class ScheduleConfig(BaseModel):
49
+ """调度配置"""
50
+ enabled: bool = True
51
+ funding_rate_sync_interval: int = 5 # 分钟
52
+ opportunity_scan_interval: int = 1 # 分钟
53
+ auto_close_check_interval: int = 1 # 分钟 - 独立控制平仓检查
54
+ paper_trading_report_interval: int = 30 # 分钟
55
+ daily_report_minute: int = 5 # 昨天日报更新/每小时滚动更新的时间(分钟)
56
+ intraday_report_minute: int = 7 # 当天日报初始化创建的时间(分钟)
57
+
58
+
59
+ class CommonConfig(BaseModel):
60
+ """通用配置"""
61
+ proxy: str = ""
62
+ timezone: str = "Asia/Shanghai" # 默认使用北京时间 (UTC+8)
63
+ data_source_timeout: int = 3
64
+ cache_ttl: int = 60
65
+ feishu_webhook: str = ""
66
+ schedule: ScheduleConfig = ScheduleConfig()
67
+
68
+ def get_tzinfo(self):
69
+ """获取 timezone 对象"""
70
+ from datetime import timezone, timedelta
71
+ if self.timezone == "Asia/Shanghai" or self.timezone == "UTC+8":
72
+ return timezone(timedelta(hours=8))
73
+ return timezone.utc
74
+
75
+
76
+ class AutoTradingConfig(BaseModel):
77
+ """自动交易配置"""
78
+ enabled: bool = False
79
+ default_principal: Decimal = Decimal("100") # 默认本金 (USDT)
80
+ default_leverage: int = 3 # 默认杠杆
81
+ max_concurrent_positions: int = 5 # 最大同时持仓数
82
+ min_rate_diff_to_open: Decimal = Decimal("0.0005") # 开仓最小费率差 (0.05%)
83
+ min_rate_diff_to_close: Decimal = Decimal("0.0001") # 平仓最小费率差 (0.01%)
84
+ cooldown_minutes: int = 30 # 同一交易对开仓冷却时间
85
+ pending_maker_close_interval: int = 10 # 挂起平仓监测时的开仓临时冷却期(秒),真实平仓监测由 auto_close_check_interval 决定
86
+ ghost_backfill_hours: int = 24 # Ghost 持仓认领时的资金费回溯小时数
87
+ max_pending_holding_minutes: int = 480 # 待无损平仓状态的最长持有挂起限时(分钟),作为兜底平仓红线 deadline
88
+ max_close_attempts: int = 5 # 最大平仓重试次数(强平熔断阀值)
89
+ force_close_lead_minutes: int = 5 # 强制平仓提前时间(分钟),默认 5 分钟
90
+
91
+ class OpeningAuditConfig(BaseModel):
92
+ """开仓前历史审计配置"""
93
+ enabled: bool = True
94
+ window_hours: float = 4.0
95
+ min_confidence: float = 0.7
96
+ min_volatility_ratio: float = 1.5 # 波动率夏普准入安全阈值 (RateDiff / TotalStdDev)
97
+ strict_opening_threshold: bool = True
98
+ fee_coverage_threshold: Decimal = Decimal("0.000041") # 经济可行性审计门槛 (1h 费率)
99
+ asymmetric_jitter_window_minutes: int = 30 # 非对称套利短线防抖窗口
100
+ asymmetric_jitter_threshold: float = 0.7 # 利差萎缩拦截门槛 (例如 0.7 代表利差相比均值萎缩 30% 则拦截)
101
+
102
+
103
+ class PremierPoolConfig(BaseModel):
104
+ """优质机会池配置"""
105
+ enabled: bool = True
106
+ opportunity_list_limit: int = Field(default=50, ge=1, le=100)
107
+ min_achievement_rate: Decimal = Decimal("0.8") # 达成率阈值 (80%)
108
+ min_premier_apr: Decimal = Decimal("0.10") # 平仓净 APR 阈值 (10%)
109
+ monitoring_boost: Decimal = Decimal("2.0") # 优先级提升倍数
110
+
111
+
112
+ class AsymmetricArbitrageConfig(BaseModel):
113
+ """不对称结算周期套利配置"""
114
+ enabled: bool = True
115
+ exclusive_mode: bool = True # 排他模式:仅允许执行不对称套利
116
+ min_profit_margin: Decimal = Decimal("0.0001") # 预期净利润率安全垫 (0.01%)
117
+ exit_buffer_minutes: int = 2 # 逃跑缓冲时间(分钟)
118
+ entry_window_minutes: int = 30 # 小周期结算前允许打点入场的窗口
119
+ min_interval_multiple: Decimal = Field(default=Decimal("4"), ge=Decimal("1")) # 不对称套利最小结算周期倍数
120
+ basis_admission_threshold: Decimal = Decimal("0.002") # 专属吃单基差阈值 (0.2%)
121
+
122
+
123
+ class NetProfitAdmissionConfig(BaseModel):
124
+ """成本感知净收益准入配置"""
125
+ enabled: bool = True
126
+ min_net_profit_usdt: Decimal = Decimal("0.02")
127
+ fee_coverage_multiplier: Decimal = Decimal("2.0")
128
+ entry_spread_buffer: Decimal | None = None
129
+ exit_spread_buffer: Decimal | None = None
130
+ slippage_buffer: Decimal | None = None
131
+ dynamic_expected_periods: bool = True
132
+ max_expected_periods: int = Field(default=3, ge=1, le=3)
133
+ periods_mapping: dict[str, int] = Field(default_factory=lambda: {"0.90": 3, "0.75": 2})
134
+
135
+
136
+ class FundingDirectionReversalConfig(BaseModel):
137
+ """资金费方向近期翻转评估配置"""
138
+ evaluation_window_hours: Decimal = Decimal("4")
139
+ min_sample_count: int = Field(default=1, ge=1)
140
+ max_flip_count: int = Field(default=0, ge=0)
141
+
142
+
143
+ class FundingOffsetConfig(BaseModel):
144
+ """双腿资金费收益抵消评估配置"""
145
+ min_net_directional_profit_usdt: Decimal = Field(default=Decimal("0"), ge=Decimal("0"))
146
+ evaluation_window_hours: Decimal | None = None
147
+
148
+
149
+ class GreylistRecommendationConfig(BaseModel):
150
+ """灰名单候选建议配置"""
151
+ enabled: bool = True
152
+ min_sample_count: int = Field(default=2, ge=1)
153
+ observation_window_days: int = Field(default=30, ge=1)
154
+ actual_loss_threshold_usdt: Decimal = Field(default=Decimal("1"), ge=Decimal("0"))
155
+ loss_count_threshold: int = Field(default=2, ge=1)
156
+ expected_actual_deviation_threshold_usdt: Decimal = Field(default=Decimal("1"), ge=Decimal("0"))
157
+ expected_actual_deviation_ratio: Decimal = Field(default=Decimal("0.10"), ge=Decimal("0"))
158
+ fee_to_funding_gross_ratio_threshold: Decimal = Field(default=Decimal("0.50"), ge=Decimal("0"))
159
+ slippage_loss_threshold_usdt: Decimal = Field(default=Decimal("1"), ge=Decimal("0"))
160
+ action_mapping: dict[str, str] = Field(default_factory=lambda: {
161
+ "historical_loss_exceeded": "reject",
162
+ "expected_actual_deviation_exceeded": "increase_historical_error_buffer",
163
+ "fee_break_even_eroded": "raise_threshold",
164
+ "slippage_buffer_exceeded": "raise_threshold",
165
+ "data_insufficient": "watching",
166
+ "no_greylist_trigger": "watching",
167
+ })
168
+
169
+
170
+ class RuntimeGreylistRuleConfig(BaseModel):
171
+ """运行时灰名单准入配置条目"""
172
+ enabled: bool = True
173
+ exchange: str = ""
174
+ symbol: str = ""
175
+ action: str = "watching"
176
+ config_version: str | None = None
177
+ trigger_basis: list[str] = Field(default_factory=list)
178
+ threshold_adjustment_usdt: Decimal = Decimal("0")
179
+ historical_error_buffer_usdt: Decimal = Decimal("0")
180
+
181
+
182
+ class RuntimeGreylistConfig(BaseModel):
183
+ """已启用运行时灰名单准入配置"""
184
+ enabled: bool = False
185
+ config_version: str | None = None
186
+ rules: list[RuntimeGreylistRuleConfig] = Field(default_factory=list)
187
+
188
+
189
+ def _safe_greylist_decimal_config(value, default: Decimal) -> Decimal:
190
+ try:
191
+ amount = Decimal(str(value))
192
+ except (InvalidOperation, ValueError, TypeError):
193
+ return default
194
+ return amount if amount.is_finite() and amount >= 0 else default
195
+
196
+
197
+ def _safe_greylist_int_config(value, default: int) -> int:
198
+ try:
199
+ amount = Decimal(str(value))
200
+ if amount != amount.to_integral_value():
201
+ return default
202
+ amount = int(amount)
203
+ except (InvalidOperation, ValueError, TypeError, OverflowError):
204
+ return default
205
+ return amount if amount >= 1 else default
206
+
207
+
208
+ def _coerce_greylist_recommendation_config(data):
209
+ if not isinstance(data, dict):
210
+ return data
211
+ defaults = GreylistRecommendationConfig()
212
+ coerced = dict(data)
213
+ for field_name in ("min_sample_count", "observation_window_days", "loss_count_threshold"):
214
+ coerced[field_name] = _safe_greylist_int_config(coerced.get(field_name, getattr(defaults, field_name)), getattr(defaults, field_name))
215
+ for field_name in (
216
+ "actual_loss_threshold_usdt",
217
+ "expected_actual_deviation_threshold_usdt",
218
+ "expected_actual_deviation_ratio",
219
+ "fee_to_funding_gross_ratio_threshold",
220
+ "slippage_loss_threshold_usdt",
221
+ ):
222
+ coerced[field_name] = _safe_greylist_decimal_config(coerced.get(field_name, getattr(defaults, field_name)), getattr(defaults, field_name))
223
+ return coerced
224
+
225
+
226
+ class LiveTradingConfig(BaseModel):
227
+ """实盘交易安全配置"""
228
+ max_position_value: Decimal = Decimal("1.0") # 最大单腿头寸 (USDT),-1 为不限制
229
+ fixed_leverage: int = 1 # 硬锁杠杆倍数
230
+ margin_mode: str = "isolated" # 强制仓位模式: isolated (逐仓), crossed (全仓)
231
+ min_notional_validation: bool = True # 是否校验最小下单额
232
+ price_slippage_protection: Decimal = Decimal("0.0005") # IOC 价格保护偏移量 (0.05%)
233
+ max_latency_ms: int = 3000 # 最大允许延迟 (ms)
234
+ max_rollback_attempts: int = 3 # 触发熔断的连续回滚尝试次数
235
+ opening_strategy: str = "limit_ioc" # 开仓策略: limit_ioc, dual_bbo, dual_maker
236
+ bbo_poll_interval: Decimal = Decimal("1.5") # BBO 轮询间隔 (秒)
237
+ bbo_timeout: Decimal = Decimal("15.0") # BBO 追踪超时 (秒)
238
+ bbo_min_diff_notional: Decimal = Decimal("5.0") # 触发对冲的最小差异金额 (USDT)
239
+ taker_slippage_buffer: Decimal = Decimal("0.002") # 补单/Taker 滑点保护缓冲 (0.2%)
240
+ post_only_timeout_seconds: int = Field(default=20, gt=0) # Post-Only 挂单的超时追单时间限制 (秒)
241
+
242
+
243
+ class MarketFilterConfig(BaseModel):
244
+ """市场过滤配置"""
245
+ quote: str = "USDT"
246
+ settle: str = "USDT"
247
+ type: str = "swap"
248
+
249
+
250
+ class ArbitrageConfig(BaseModel):
251
+ """套利配置"""
252
+ is_paper_trading: bool = True # 是否为模拟交易模式
253
+ replacement_enabled: bool = True # 是否开启动态调仓
254
+ replacement_min_apr_diff: Decimal = Decimal("0.05") # 换仓的最小收益差门槛 (5%)
255
+ replacement_min_holding_minutes: int = 60 # 换仓前的最短持仓分钟数
256
+ replacement_min_settlements: int = 1 # 换仓前必须经历的最小结算次数
257
+ negative_funding_tolerance: Decimal = Decimal("-0.00005") # 负收益保护费率容忍度 (-0.005%)
258
+ negative_funding_tolerance_minutes: int = 3 # 负收益保护费率持续时间 (分钟)
259
+ one_position_per_exchange: bool = True # 每个交易所是否仅允许存在一个套利路径
260
+ slicing_cooldown_minutes: int = 15 # 分片平仓重试等待冷却时间(分钟)
261
+ max_closing_slippage: Decimal = Decimal("0.01") # Taker 平仓最大容忍滑点
262
+ min_rate_diff: Decimal = Decimal("0.00005") # 最小费率差 0.005%
263
+ min_annual_rate: Decimal = Decimal("0.05") # 最小年化收益率 5%
264
+ market_filters: MarketFilterConfig = MarketFilterConfig()
265
+ excluded_symbols: list[str] = Field(default_factory=list)
266
+ supported_symbols: list[str] = Field(default_factory=list) # 保持兼容,但优先使用过滤
267
+ auto_trading: AutoTradingConfig = AutoTradingConfig()
268
+ live_trading: LiveTradingConfig = LiveTradingConfig()
269
+ opening_audit: OpeningAuditConfig = OpeningAuditConfig()
270
+ premier_pool: PremierPoolConfig = PremierPoolConfig()
271
+ asymmetric_arbitrage: AsymmetricArbitrageConfig = AsymmetricArbitrageConfig()
272
+ net_profit_admission: NetProfitAdmissionConfig = NetProfitAdmissionConfig()
273
+ funding_direction_reversal: FundingDirectionReversalConfig = FundingDirectionReversalConfig()
274
+ funding_offset: FundingOffsetConfig = FundingOffsetConfig()
275
+ greylist_recommendation: GreylistRecommendationConfig = GreylistRecommendationConfig()
276
+ runtime_greylist: RuntimeGreylistConfig = RuntimeGreylistConfig()
277
+
278
+
279
+ class StopLossConfig(BaseModel):
280
+ """止损配置"""
281
+ enabled: bool = True
282
+ threshold: Decimal = Decimal("-0.005")
283
+
284
+
285
+ class RiskConfig(BaseModel):
286
+ """风险配置"""
287
+ margin_warning_threshold: Decimal = Decimal("0.50") # 50% 触发 WARNING
288
+ margin_critical_threshold: Decimal = Decimal("0.30") # 30% 触发 CRITICAL
289
+ basis_info_threshold: Decimal = Decimal("0.01") # 1% 触发 INFO
290
+ basis_warning_threshold: Decimal = Decimal("0.02") # 2% 触发 WARNING
291
+ basis_critical_threshold: Decimal = Decimal("0.05") # 5% 触发告警 (CRITICAL)
292
+ basis_admission_threshold: Decimal = Decimal("0.01") # 1% 交易准入控制阈值
293
+ basis_exit_threshold: Decimal = Decimal("0.01") # 1% 平仓最大容忍基差偏离度
294
+ funding_protection_enabled: bool = True # 是否开启负资金费保护
295
+ funding_protection_warmup_hours: int = 1 # 开仓保护期 (小时)
296
+ funding_volatility_tolerance: Decimal = Decimal("-0.2") # 负收益容忍度 (USDT)
297
+ max_total_position_usdt: Decimal = Decimal("50000")
298
+ max_single_position_usdt: Decimal = Decimal("10000")
299
+ total_initial_capital: Decimal = Decimal("0") # 总本金,用于分析本金安全 (0 则不分析)
300
+ stop_loss: StopLossConfig = StopLossConfig()
301
+
302
+
303
+
304
+ class LLMConfig(BaseModel):
305
+ """LLM 复盘配置"""
306
+ enabled: bool = False
307
+ api_key: str = ""
308
+ base_url: str = "https://api.openai.com/v1"
309
+ model_name: str = "gpt-4o"
310
+
311
+
312
+ class Settings(BaseModel):
313
+ """全局配置"""
314
+ app_name: str = "OpenFund Arbitrage"
315
+ api_v1_prefix: str = "/api/v1"
316
+ debug: bool = True
317
+ user_name: Optional[str] = None
318
+ exchanges: dict[str, ExchangeConfig] = Field(default_factory=dict)
319
+ common: CommonConfig = CommonConfig()
320
+ arbitrage: ArbitrageConfig = ArbitrageConfig()
321
+ risk: RiskConfig = RiskConfig()
322
+ llm: LLMConfig = Field(default_factory=LLMConfig)
323
+
324
+ def get_enabled_exchanges(self) -> dict[str, ExchangeConfig]:
325
+ """获取所有启用的交易所配置"""
326
+ return {k: v for k, v in self.exchanges.items() if v.enabled}
327
+
328
+ def get_exchange(self, exchange_id: str) -> Optional[ExchangeConfig]:
329
+ """获取指定交易所配置"""
330
+ return self.exchanges.get(exchange_id)
331
+
332
+
333
+ def load_config(config_path: Optional[str | Path] = None) -> Settings:
334
+ """
335
+ 从 YAML 文件加载配置
336
+
337
+ Args:
338
+ config_path: 配置文件路径,默认为 arbitrage_config.yaml
339
+
340
+ Returns:
341
+ Settings 配置对象
342
+ """
343
+ path: Path
344
+ if config_path is None:
345
+ # 默认配置文件路径
346
+ base_dir = Path(__file__).parent.parent
347
+ path = base_dir / "arbitrage_config.yaml"
348
+ else:
349
+ path = Path(config_path)
350
+
351
+ if not path.exists():
352
+ # 配置文件不存在,返回默认配置
353
+ return Settings()
354
+
355
+ with open(path, "r", encoding="utf-8") as f:
356
+ raw_config = yaml.safe_load(f)
357
+
358
+ if raw_config is None:
359
+ return Settings()
360
+
361
+ # 展开环境变量
362
+ config_data = expand_env_vars(raw_config)
363
+
364
+ # 解析交易所配置
365
+ exchanges = {}
366
+ if "exchanges" in config_data:
367
+ for exchange_id, exchange_config in config_data["exchanges"].items():
368
+ exchanges[exchange_id] = ExchangeConfig(**exchange_config)
369
+
370
+ # 解析通用配置
371
+ common = CommonConfig()
372
+ if "common" in config_data:
373
+ common_data = config_data["common"]
374
+ if "schedule" in common_data:
375
+ common_data["schedule"] = ScheduleConfig(**common_data["schedule"])
376
+ common = CommonConfig(**common_data)
377
+
378
+ # 解析套利配置
379
+ arbitrage = ArbitrageConfig()
380
+ if "arbitrage" in config_data:
381
+ arb_data = config_data["arbitrage"]
382
+ if "auto_trading" in arb_data:
383
+ arb_data["auto_trading"] = AutoTradingConfig(**arb_data["auto_trading"])
384
+ if "opening_audit" in arb_data:
385
+ arb_data["opening_audit"] = OpeningAuditConfig(**arb_data["opening_audit"])
386
+ if "premier_pool" in arb_data:
387
+ arb_data["premier_pool"] = PremierPoolConfig(**arb_data["premier_pool"])
388
+ if "net_profit_admission" in arb_data:
389
+ arb_data["net_profit_admission"] = NetProfitAdmissionConfig(**arb_data["net_profit_admission"])
390
+ if "greylist_recommendation" in arb_data:
391
+ arb_data["greylist_recommendation"] = _coerce_greylist_recommendation_config(arb_data["greylist_recommendation"])
392
+ if "runtime_greylist" in arb_data:
393
+ runtime_greylist_data = dict(arb_data["runtime_greylist"] or {})
394
+ runtime_greylist_data["rules"] = [RuntimeGreylistRuleConfig(**item) for item in runtime_greylist_data.get("rules", [])]
395
+ arb_data["runtime_greylist"] = RuntimeGreylistConfig(**runtime_greylist_data)
396
+ arbitrage = ArbitrageConfig(**arb_data)
397
+
398
+ # 解析风险配置
399
+ risk = RiskConfig()
400
+ if "risk" in config_data:
401
+ risk_data = config_data["risk"]
402
+ if "stop_loss" in risk_data:
403
+ risk_data["stop_loss"] = StopLossConfig(**risk_data["stop_loss"])
404
+ risk = RiskConfig(**risk_data)
405
+
406
+ # 解析 LLM 配置
407
+ llm_data = config_data.get("llm", {})
408
+ # 显式检查环境变量(支持 .env 优先或作为补充)
409
+ llm_enabled = os.environ.get("OPENFUND_LLM_ENABLED", "").lower() == "true" or llm_data.get("enabled", False)
410
+ if isinstance(llm_enabled, str) and llm_enabled.lower() in ("true", "1"):
411
+ llm_enabled = True
412
+ elif isinstance(llm_enabled, str):
413
+ llm_enabled = False
414
+
415
+ llm = LLMConfig(
416
+ enabled=llm_enabled,
417
+ api_key=os.environ.get("OPENFUND_LLM_API_KEY", llm_data.get("api_key", "")),
418
+ base_url=os.environ.get("OPENFUND_LLM_BASE_URL", llm_data.get("base_url", "https://api.openai.com/v1")),
419
+ model_name=os.environ.get("OPENFUND_LLM_MODEL_NAME", llm_data.get("model_name", "gpt-4o")),
420
+ )
421
+
422
+ # 动态嗅探操作用户名 (Story 3.2)
423
+ user_name = config_data.get("user_name")
424
+ if not user_name:
425
+ try:
426
+ user_name = os.getlogin()
427
+ except Exception:
428
+ pass
429
+ if not user_name:
430
+ try:
431
+ import getpass
432
+ user_name = getpass.getuser()
433
+ except Exception:
434
+ pass
435
+ if not user_name:
436
+ user_name = os.environ.get("USER") or os.environ.get("USERNAME") or "UNKNOWN_OPERATOR"
437
+
438
+ return Settings(
439
+ exchanges=exchanges,
440
+ common=common,
441
+ arbitrage=arbitrage,
442
+ risk=risk,
443
+ llm=llm,
444
+ user_name=user_name,
445
+ )
446
+
447
+
448
+ # 全局配置实例
449
+ settings = load_config()
app/core/__init__.py ADDED
@@ -0,0 +1,16 @@
1
+ """核心执行层模块"""
2
+
3
+ from app.core.exchange_manager import ExchangeManager
4
+ from app.core.funding_rate_fetcher import FundingRateFetcher
5
+ from app.core.arbitrage_executor import ArbitrageExecutor
6
+ from app.core.notification import NotificationService
7
+ from app.core.twap_manager import TWAPManager, twap_manager
8
+
9
+ __all__ = [
10
+ "ExchangeManager",
11
+ "FundingRateFetcher",
12
+ "ArbitrageExecutor",
13
+ "NotificationService",
14
+ "TWAPManager",
15
+ "twap_manager",
16
+ ]