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.
- app/__init__.py +3 -0
- app/config.py +449 -0
- app/core/__init__.py +16 -0
- app/core/arbitrage_executor.py +3900 -0
- app/core/diagnostics.py +571 -0
- app/core/exchange_dialects/__init__.py +11 -0
- app/core/exchange_dialects/base.py +310 -0
- app/core/exchange_dialects/binance.py +84 -0
- app/core/exchange_dialects/bitget.py +318 -0
- app/core/exchange_dialects/bybit.py +123 -0
- app/core/exchange_dialects/factory.py +15 -0
- app/core/exchange_dialects/gate.py +138 -0
- app/core/exchange_dialects/generic.py +46 -0
- app/core/exchange_dialects/htx.py +187 -0
- app/core/exchange_dialects/okx.py +186 -0
- app/core/exchange_manager.py +186 -0
- app/core/exchange_wrapper.py +462 -0
- app/core/execution/state_machine.py +57 -0
- app/core/execution/strategies/__init__.py +5 -0
- app/core/execution/strategies/base_strategy.py +30 -0
- app/core/execution/strategies/dual_maker_strategy.py +430 -0
- app/core/execution/strategies/taker_fallback_strategy.py +60 -0
- app/core/execution/strategies/twap_strategy.py +266 -0
- app/core/execution_observation.py +871 -0
- app/core/funding_rate_fetcher.py +839 -0
- app/core/helpers.py +242 -0
- app/core/lsi_manager.py +311 -0
- app/core/notification.py +236 -0
- app/core/real_trade_guard.py +267 -0
- app/core/twap_backfillers.py +112 -0
- app/core/twap_manager.py +1110 -0
- app/data/__init__.py +5 -0
- app/data/mock_store.py +164 -0
- app/data/persistent_store.py +1088 -0
- app/dependencies.py +67 -0
- app/domain/opportunity_evaluator.py +841 -0
- app/logger.py +173 -0
- app/main.py +424 -0
- app/presentation/opportunity_formatter.py +1294 -0
- app/routers/__init__.py +1 -0
- app/routers/exchanges.py +98 -0
- app/routers/opportunities.py +110 -0
- app/routers/performance.py +123 -0
- app/routers/positions.py +125 -0
- app/routers/risk.py +113 -0
- app/scheduler/__init__.py +5 -0
- app/scheduler/jobs/__init__.py +13 -0
- app/scheduler/jobs/auto_close_check.py +1370 -0
- app/scheduler/jobs/daily_profit_report.py +4191 -0
- app/scheduler/jobs/funding_rate_sync.py +54 -0
- app/scheduler/jobs/llm_review_report.py +73 -0
- app/scheduler/jobs/opportunity_scan.py +329 -0
- app/scheduler/jobs/paper_trading_job.py +54 -0
- app/scheduler/jobs/position_monitor.py +199 -0
- app/scheduler/jobs/report_self_healing.py +107 -0
- app/scheduler/scheduler_manager.py +217 -0
- app/schemas/__init__.py +1 -0
- app/schemas/audit.py +36 -0
- app/schemas/common.py +36 -0
- app/schemas/exchange.py +38 -0
- app/schemas/funding_rate.py +39 -0
- app/schemas/opportunity.py +406 -0
- app/schemas/paper_trading.py +28 -0
- app/schemas/performance.py +130 -0
- app/schemas/position.py +115 -0
- app/schemas/risk.py +105 -0
- app/services/__init__.py +1 -0
- app/services/exchange_service.py +101 -0
- app/services/funding_sync_service.py +2323 -0
- app/services/llm_service.py +37 -0
- app/services/opportunity_service.py +947 -0
- app/services/paper_trading_service.py +418 -0
- app/services/performance_service.py +494 -0
- app/services/position_service.py +287 -0
- app/services/position_sync_service.py +950 -0
- app/services/risk_monitoring_service.py +322 -0
- app/services/risk_service.py +299 -0
- openfund_arbitrage-3.9.1.dist-info/METADATA +172 -0
- openfund_arbitrage-3.9.1.dist-info/RECORD +80 -0
- openfund_arbitrage-3.9.1.dist-info/WHEEL +4 -0
app/__init__.py
ADDED
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
|
+
]
|