aria-code 4.1.3__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.
- agents/__init__.py +32 -0
- agents/base.py +190 -0
- agents/deep/__init__.py +37 -0
- agents/deep/calibration_loop.py +144 -0
- agents/deep/critic.py +125 -0
- agents/deep/deepen.py +193 -0
- agents/deep/models.py +149 -0
- agents/deep/pipeline.py +164 -0
- agents/deep/quant_fusion.py +192 -0
- agents/deep/themes.py +95 -0
- agents/deep/tiers.py +106 -0
- agents/financial/__init__.py +10 -0
- agents/financial/catalyst.py +279 -0
- agents/financial/debate.py +145 -0
- agents/financial/earnings.py +303 -0
- agents/financial/fundamental.py +159 -0
- agents/financial/macro.py +99 -0
- agents/financial/news.py +207 -0
- agents/financial/risk.py +132 -0
- agents/financial/sector.py +279 -0
- agents/financial/synthesis.py +274 -0
- agents/financial/technical.py +258 -0
- agents/portfolio_agent.py +333 -0
- agents/realty/__init__.py +62 -0
- agents/realty/asset_diagnosis.py +150 -0
- agents/realty/business_match.py +165 -0
- agents/realty/cashflow_verify.py +208 -0
- agents/realty/contract_rules.py +209 -0
- agents/realty/energy_anomaly.py +188 -0
- agents/realty/exit_settlement.py +207 -0
- agents/realty/fulfillment_risk.py +205 -0
- agents/realty/ops_optimize.py +159 -0
- agents/realty/revenue_share.py +214 -0
- agents/registry.py +144 -0
- agents/sports/__init__.py +0 -0
- agents/sports/football_agent.py +169 -0
- agents/team.py +289 -0
- aliyun_data_client.py +660 -0
- apps/README.md +12 -0
- apps/__init__.py +2 -0
- apps/channels/README.md +15 -0
- apps/cli/README.md +13 -0
- apps/cli/__init__.py +2 -0
- apps/cli/bootstrap.py +99 -0
- apps/cli/codegen_paths.py +29 -0
- apps/cli/commands/__init__.py +16 -0
- apps/cli/commands/analysis_cmds.py +288 -0
- apps/cli/commands/backtest_cmds.py +1887 -0
- apps/cli/commands/broker_cmds.py +1154 -0
- apps/cli/commands/business_workflow_cmds.py +289 -0
- apps/cli/commands/catalog.py +84 -0
- apps/cli/commands/data_cmds.py +405 -0
- apps/cli/commands/diagnostic_cmds.py +179 -0
- apps/cli/commands/diagnostic_ops_cmds.py +696 -0
- apps/cli/commands/finance_render.py +12 -0
- apps/cli/commands/market.py +399 -0
- apps/cli/commands/market_cmds.py +1276 -0
- apps/cli/commands/market_context.py +425 -0
- apps/cli/commands/market_render.py +7 -0
- apps/cli/commands/model_cmds.py +1579 -0
- apps/cli/commands/ops_cmds.py +668 -0
- apps/cli/commands/portfolio_cmds.py +962 -0
- apps/cli/commands/report.py +377 -0
- apps/cli/commands/scaffold_templates.py +617 -0
- apps/cli/commands/session_cmds.py +179 -0
- apps/cli/commands/session_ux_cmds.py +280 -0
- apps/cli/commands/team.py +588 -0
- apps/cli/commands/team_render.py +8 -0
- apps/cli/commands/ui_cmds.py +358 -0
- apps/cli/commands/workflow_cmds.py +279 -0
- apps/cli/commands/workspace_cmds.py +1414 -0
- apps/cli/config_paths.py +70 -0
- apps/cli/config_store.py +61 -0
- apps/cli/deterministic.py +122 -0
- apps/cli/direct.py +48 -0
- apps/cli/github_app_auth.py +135 -0
- apps/cli/handlers/__init__.py +11 -0
- apps/cli/handlers/broker_handlers.py +122 -0
- apps/cli/handlers/chart_handlers.py +1309 -0
- apps/cli/handlers/market_handlers.py +2509 -0
- apps/cli/handlers/realty_handlers.py +114 -0
- apps/cli/handlers/strategy_advice.py +82 -0
- apps/cli/hooks.py +180 -0
- apps/cli/i18n.py +284 -0
- apps/cli/intent.py +136 -0
- apps/cli/intent_router.py +217 -0
- apps/cli/lifecycle_hooks.py +48 -0
- apps/cli/main.py +29 -0
- apps/cli/market_metadata.py +135 -0
- apps/cli/market_universe.py +265 -0
- apps/cli/message_processing.py +257 -0
- apps/cli/plan_mode.py +139 -0
- apps/cli/plotly_html.py +15 -0
- apps/cli/prediction_feedback.py +202 -0
- apps/cli/preflight.py +497 -0
- apps/cli/project_aria.py +60 -0
- apps/cli/prompts/__init__.py +0 -0
- apps/cli/prompts/coding.py +658 -0
- apps/cli/prompts/system_prompts.py +531 -0
- apps/cli/prompts/ui.py +434 -0
- apps/cli/providers/__init__.py +1 -0
- apps/cli/providers/base.py +271 -0
- apps/cli/providers/chat_routing.py +80 -0
- apps/cli/providers/llm/__init__.py +1 -0
- apps/cli/providers/llm/ollama_stream.py +1170 -0
- apps/cli/providers/llm/sse_stream.py +216 -0
- apps/cli/providers/runtime_bridge.py +185 -0
- apps/cli/runtime_consumer.py +489 -0
- apps/cli/session_export.py +87 -0
- apps/cli/session_jsonl.py +207 -0
- apps/cli/session_store.py +112 -0
- apps/cli/todo_tracker.py +190 -0
- apps/cli/tools/__init__.py +40 -0
- apps/cli/tools/context.py +46 -0
- apps/cli/tools/file_tools.py +112 -0
- apps/cli/tools/market_tools.py +549 -0
- apps/cli/tools/notebook_tools.py +111 -0
- apps/cli/tools/system_tools.py +669 -0
- apps/cli/tools/write_tools.py +715 -0
- apps/cli/tradingview_bridge.py +434 -0
- apps/cli/update_check.py +152 -0
- apps/cli/utils/__init__.py +0 -0
- apps/cli/utils/market_detect.py +1578 -0
- apps/daemon/README.md +14 -0
- apps/vscode/README.md +115 -0
- apps/vscode/package.json +70 -0
- aria_cli.py +11636 -0
- aria_code-4.1.3.dist-info/METADATA +952 -0
- aria_code-4.1.3.dist-info/RECORD +284 -0
- aria_code-4.1.3.dist-info/WHEEL +5 -0
- aria_code-4.1.3.dist-info/entry_points.txt +2 -0
- aria_code-4.1.3.dist-info/licenses/LICENSE +121 -0
- aria_code-4.1.3.dist-info/top_level.txt +50 -0
- aria_daemon.py +1295 -0
- aria_feishu_bot.py +1359 -0
- aria_relay_client.py +182 -0
- aria_relay_server.py +405 -0
- aria_telegram_bot.py +202 -0
- ariarc.py +328 -0
- artifacts.py +491 -0
- backtest_report.py +472 -0
- brokers/__init__.py +72 -0
- brokers/base.py +207 -0
- brokers/capabilities.py +264 -0
- brokers/cn/__init__.py +10 -0
- brokers/cn/easytrader_broker.py +193 -0
- brokers/cn/futu_broker.py +194 -0
- brokers/cn/longbridge_broker.py +190 -0
- brokers/cn/tiger_broker.py +196 -0
- brokers/cn/xtquant_broker.py +175 -0
- brokers/config.py +364 -0
- brokers/intl/__init__.py +5 -0
- brokers/intl/alpaca_broker.py +183 -0
- brokers/intl/ibkr_broker.py +215 -0
- brokers/intl/webull_broker.py +156 -0
- brokers/paper_broker.py +259 -0
- brokers/planning.py +296 -0
- brokers/registry.py +181 -0
- brokers/trading.py +237 -0
- change_store.py +127 -0
- command_safety.py +19 -0
- computer_use_tools.py +504 -0
- dashboard_generator.py +578 -0
- data_analysis_tools.py +808 -0
- data_cleaner.py +483 -0
- data_service.py +481 -0
- datasources/__init__.py +23 -0
- datasources/base.py +166 -0
- datasources/router.py +221 -0
- datasources/sources/__init__.py +15 -0
- datasources/sources/akshare_source.py +269 -0
- datasources/sources/alpha_vantage_source.py +202 -0
- datasources/sources/edgar_source.py +218 -0
- datasources/sources/finnhub_source.py +197 -0
- datasources/sources/fred_source.py +219 -0
- datasources/sources/tushare_source.py +141 -0
- datasources/sources/web_scraper_source.py +278 -0
- datasources/sources/world_bank_source.py +205 -0
- datasources/sources/yfinance_source.py +152 -0
- demo_player.py +204 -0
- doctor.py +508 -0
- file_analysis_tools.py +734 -0
- finance_formulas.py +389 -0
- football_data_client.py +1670 -0
- intent_classifier.py +358 -0
- local_finance_tools.py +3221 -0
- local_llm_provider.py +552 -0
- macro_tools.py +368 -0
- market_data_client.py +1899 -0
- mcp_client.py +506 -0
- memory_manager.py +245 -0
- model_capability.py +416 -0
- notification_tools.py +248 -0
- packages/__init__.py +23 -0
- packages/aria_agents/__init__.py +5 -0
- packages/aria_agents/manifest.py +69 -0
- packages/aria_core/__init__.py +34 -0
- packages/aria_core/architecture.py +192 -0
- packages/aria_core/export.py +124 -0
- packages/aria_core/manifest.py +65 -0
- packages/aria_infra/__init__.py +15 -0
- packages/aria_infra/arthera.py +52 -0
- packages/aria_infra/doctor.py +246 -0
- packages/aria_infra/product.py +37 -0
- packages/aria_mcp/__init__.py +25 -0
- packages/aria_mcp/bridge.py +38 -0
- packages/aria_mcp/config.py +97 -0
- packages/aria_mcp/tools.py +61 -0
- packages/aria_sdk/__init__.py +19 -0
- packages/aria_sdk/client.py +396 -0
- packages/aria_sdk/providers.py +70 -0
- packages/aria_sdk/streaming.py +73 -0
- packages/aria_sdk/types.py +86 -0
- packages/aria_services/__init__.py +55 -0
- packages/aria_services/context.py +258 -0
- packages/aria_services/data.py +11 -0
- packages/aria_services/provider_health.py +189 -0
- packages/aria_services/registry.py +213 -0
- packages/aria_services/usage.py +138 -0
- packages/aria_skills/__init__.py +5 -0
- packages/aria_skills/registry.py +59 -0
- packages/aria_tools/__init__.py +5 -0
- packages/aria_tools/registry.py +128 -0
- packages/quant_engine/__init__.py +6 -0
- packages/quant_engine/sports/__init__.py +72 -0
- packages/quant_engine/sports/calibrator.py +353 -0
- packages/quant_engine/sports/dixon_coles.py +234 -0
- packages/quant_engine/sports/elo.py +299 -0
- packages/quant_engine/sports/form.py +188 -0
- packages/quant_engine/sports/h2h.py +195 -0
- packages/quant_engine/sports/ml_model.py +354 -0
- packages/quant_engine/sports/predictor.py +311 -0
- packages/quant_engine/sports/tracker.py +664 -0
- packages/quant_engine/stochastic/__init__.py +27 -0
- packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
- packages/quant_engine/stochastic/ito_calculus.py +477 -0
- packages/quant_engine/stochastic/kelly_criterion.py +181 -0
- packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
- packages/quant_engine/stochastic/options_pricing.py +573 -0
- packages/quant_engine/stochastic/stochastic_processes.py +90 -0
- plan_utils.py +194 -0
- plugin_loader.py +328 -0
- portfolio_ledger.py +262 -0
- privacy/__init__.py +5 -0
- privacy/feedback.py +123 -0
- project_tools.py +525 -0
- providers/__init__.py +30 -0
- providers/llm/__init__.py +19 -0
- providers/llm/anthropic.py +184 -0
- providers/llm/base.py +139 -0
- providers/llm/ollama.py +128 -0
- providers/llm/openai_compat.py +282 -0
- providers/llm/registry.py +358 -0
- realty_data_tools.py +659 -0
- report_generator.py +1314 -0
- runtime/__init__.py +103 -0
- runtime/agent_loop.py +1183 -0
- runtime/approval.py +51 -0
- runtime/events.py +102 -0
- runtime/gateway.py +128 -0
- runtime/lsp.py +346 -0
- runtime/subagent.py +258 -0
- runtime/tool_executor.py +104 -0
- runtime/tool_policy.py +106 -0
- safety/__init__.py +21 -0
- safety/permissions.py +275 -0
- setup_wizard.py +653 -0
- strategy_vault.py +420 -0
- ui/__init__.py +100 -0
- ui/banner.py +310 -0
- ui/completer.py +391 -0
- ui/console.py +271 -0
- ui/image_render.py +243 -0
- ui/input_box.py +376 -0
- ui/picker.py +195 -0
- ui/render/__init__.py +11 -0
- ui/render/finance.py +1480 -0
- ui/render/market.py +225 -0
- ui/render/output.py +681 -0
- ui/render/team.py +346 -0
- ui/robot.py +235 -0
- workspace/__init__.py +6 -0
- workspace/files.py +170 -0
- workspace/verify.py +113 -0
strategy_vault.py
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
"""
|
|
2
|
+
strategy_vault.py — Arthera Strategy Version Control System
|
|
3
|
+
|
|
4
|
+
类似 Git,但专为量化策略设计:
|
|
5
|
+
|
|
6
|
+
/strategy save "v1.2 加入波动率过滤" → 保存当前策略快照
|
|
7
|
+
/strategy list → 列出所有版本
|
|
8
|
+
/strategy diff v1 v2 → 显示两版本差异
|
|
9
|
+
/strategy load v3 → 加载指定版本
|
|
10
|
+
/strategy review → AI 审查当前策略(过拟合/前视偏差)
|
|
11
|
+
/strategy bench AAPL 2020-01-01 → 对标基准表现
|
|
12
|
+
|
|
13
|
+
数据存储: ~/.arthera/strategy_vault.db (SQLite)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import difflib
|
|
19
|
+
import hashlib
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
import sqlite3
|
|
24
|
+
import textwrap
|
|
25
|
+
import time
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
from datetime import datetime
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
30
|
+
|
|
31
|
+
_VAULT_DIR = Path.home() / ".arthera" / "strategies"
|
|
32
|
+
_VAULT_DB = _VAULT_DIR / "vault.db"
|
|
33
|
+
|
|
34
|
+
# ── Database setup ────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
def _get_db() -> sqlite3.Connection:
|
|
37
|
+
_VAULT_DIR.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
conn = sqlite3.connect(str(_VAULT_DB))
|
|
39
|
+
conn.row_factory = sqlite3.Row
|
|
40
|
+
conn.executescript("""
|
|
41
|
+
CREATE TABLE IF NOT EXISTS strategies (
|
|
42
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
43
|
+
name TEXT NOT NULL,
|
|
44
|
+
version_tag TEXT NOT NULL,
|
|
45
|
+
message TEXT DEFAULT '',
|
|
46
|
+
code TEXT NOT NULL,
|
|
47
|
+
metadata TEXT DEFAULT '{}',
|
|
48
|
+
backtest_result TEXT DEFAULT NULL,
|
|
49
|
+
review_result TEXT DEFAULT NULL,
|
|
50
|
+
code_hash TEXT NOT NULL,
|
|
51
|
+
created_at TEXT NOT NULL
|
|
52
|
+
);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_name ON strategies(name);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_hash ON strategies(code_hash);
|
|
55
|
+
""")
|
|
56
|
+
conn.commit()
|
|
57
|
+
return conn
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ── Dataclass ─────────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class StrategyVersion:
|
|
64
|
+
id: int
|
|
65
|
+
name: str
|
|
66
|
+
version_tag: str
|
|
67
|
+
message: str
|
|
68
|
+
code: str
|
|
69
|
+
metadata: Dict[str, Any]
|
|
70
|
+
backtest_result: Optional[Dict]
|
|
71
|
+
review_result: Optional[Dict]
|
|
72
|
+
code_hash: str
|
|
73
|
+
created_at: str
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def from_row(cls, row) -> "StrategyVersion":
|
|
77
|
+
meta = {}
|
|
78
|
+
try:
|
|
79
|
+
meta = json.loads(row["metadata"] or "{}")
|
|
80
|
+
except Exception:
|
|
81
|
+
pass
|
|
82
|
+
bt = None
|
|
83
|
+
try:
|
|
84
|
+
bt = json.loads(row["backtest_result"]) if row["backtest_result"] else None
|
|
85
|
+
except Exception:
|
|
86
|
+
pass
|
|
87
|
+
rv = None
|
|
88
|
+
try:
|
|
89
|
+
rv = json.loads(row["review_result"]) if row["review_result"] else None
|
|
90
|
+
except Exception:
|
|
91
|
+
pass
|
|
92
|
+
return cls(
|
|
93
|
+
id=row["id"], name=row["name"],
|
|
94
|
+
version_tag=row["version_tag"], message=row["message"],
|
|
95
|
+
code=row["code"], metadata=meta,
|
|
96
|
+
backtest_result=bt, review_result=rv,
|
|
97
|
+
code_hash=row["code_hash"], created_at=row["created_at"],
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def summary_line(self) -> str:
|
|
101
|
+
tag = f"[{self.version_tag}]"
|
|
102
|
+
ts = self.created_at[:16]
|
|
103
|
+
bt = ""
|
|
104
|
+
if self.backtest_result:
|
|
105
|
+
br = self.backtest_result
|
|
106
|
+
sharpe = br.get("sharpe_ratio")
|
|
107
|
+
ret = br.get("total_return_pct")
|
|
108
|
+
if sharpe is not None:
|
|
109
|
+
bt = f" sharpe={sharpe:.2f} ret={ret:.1f}%"
|
|
110
|
+
reviewed = " ✓reviewed" if self.review_result else ""
|
|
111
|
+
msg = f" {self.message[:50]}" if self.message else ""
|
|
112
|
+
return f"{self.id:4d} {tag:12s} {ts}{msg}{bt}{reviewed}"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
116
|
+
# StrategyVault
|
|
117
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
118
|
+
|
|
119
|
+
class StrategyVault:
|
|
120
|
+
|
|
121
|
+
# ── Save ──────────────────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
def save(
|
|
124
|
+
self,
|
|
125
|
+
code: str,
|
|
126
|
+
name: str = "strategy",
|
|
127
|
+
message: str = "",
|
|
128
|
+
metadata: Optional[Dict] = None,
|
|
129
|
+
) -> StrategyVersion:
|
|
130
|
+
"""Save a new version. Returns the saved StrategyVersion."""
|
|
131
|
+
code_hash = hashlib.sha256(code.encode()).hexdigest()[:12]
|
|
132
|
+
# Auto version tag: v{n+1}
|
|
133
|
+
with _get_db() as conn:
|
|
134
|
+
row = conn.execute(
|
|
135
|
+
"SELECT COUNT(*) as c FROM strategies WHERE name=?", (name,)
|
|
136
|
+
).fetchone()
|
|
137
|
+
n = row["c"] if row else 0
|
|
138
|
+
tag = f"v{n + 1}"
|
|
139
|
+
conn.execute(
|
|
140
|
+
"""INSERT INTO strategies
|
|
141
|
+
(name, version_tag, message, code, metadata, code_hash, created_at)
|
|
142
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)""",
|
|
143
|
+
(name, tag, message, code,
|
|
144
|
+
json.dumps(metadata or {}),
|
|
145
|
+
code_hash, datetime.now().isoformat()),
|
|
146
|
+
)
|
|
147
|
+
conn.commit()
|
|
148
|
+
last_id = conn.execute("SELECT last_insert_rowid() as lid").fetchone()["lid"]
|
|
149
|
+
row = conn.execute("SELECT * FROM strategies WHERE id=?", (last_id,)).fetchone()
|
|
150
|
+
return StrategyVersion.from_row(row)
|
|
151
|
+
|
|
152
|
+
# ── List ──────────────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
def list(self, name: str = "strategy", limit: int = 20) -> List[StrategyVersion]:
|
|
155
|
+
with _get_db() as conn:
|
|
156
|
+
rows = conn.execute(
|
|
157
|
+
"SELECT * FROM strategies WHERE name=? ORDER BY id DESC LIMIT ?",
|
|
158
|
+
(name, limit),
|
|
159
|
+
).fetchall()
|
|
160
|
+
return [StrategyVersion.from_row(r) for r in rows]
|
|
161
|
+
|
|
162
|
+
def list_all_names(self) -> List[str]:
|
|
163
|
+
with _get_db() as conn:
|
|
164
|
+
rows = conn.execute(
|
|
165
|
+
"SELECT DISTINCT name FROM strategies ORDER BY name"
|
|
166
|
+
).fetchall()
|
|
167
|
+
return [r["name"] for r in rows]
|
|
168
|
+
|
|
169
|
+
# ── Load ──────────────────────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
def load(self, name: str = "strategy",
|
|
172
|
+
version_tag: Optional[str] = None,
|
|
173
|
+
version_id: Optional[int] = None) -> Optional[StrategyVersion]:
|
|
174
|
+
with _get_db() as conn:
|
|
175
|
+
if version_id:
|
|
176
|
+
row = conn.execute(
|
|
177
|
+
"SELECT * FROM strategies WHERE id=?", (version_id,)
|
|
178
|
+
).fetchone()
|
|
179
|
+
elif version_tag:
|
|
180
|
+
row = conn.execute(
|
|
181
|
+
"SELECT * FROM strategies WHERE name=? AND version_tag=? ORDER BY id DESC LIMIT 1",
|
|
182
|
+
(name, version_tag),
|
|
183
|
+
).fetchone()
|
|
184
|
+
else:
|
|
185
|
+
# Latest
|
|
186
|
+
row = conn.execute(
|
|
187
|
+
"SELECT * FROM strategies WHERE name=? ORDER BY id DESC LIMIT 1",
|
|
188
|
+
(name,),
|
|
189
|
+
).fetchone()
|
|
190
|
+
return StrategyVersion.from_row(row) if row else None
|
|
191
|
+
|
|
192
|
+
# ── Diff ──────────────────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
def diff(
|
|
195
|
+
self,
|
|
196
|
+
name: str = "strategy",
|
|
197
|
+
tag_a: Optional[str] = None,
|
|
198
|
+
tag_b: Optional[str] = None,
|
|
199
|
+
) -> str:
|
|
200
|
+
"""Unified diff between two versions (default: last two)."""
|
|
201
|
+
versions = self.list(name, limit=10)
|
|
202
|
+
if len(versions) < 2:
|
|
203
|
+
return "需要至少2个版本才能比较差异。"
|
|
204
|
+
|
|
205
|
+
if tag_a and tag_b:
|
|
206
|
+
va = next((v for v in versions if v.version_tag == tag_a), None)
|
|
207
|
+
vb = next((v for v in versions if v.version_tag == tag_b), None)
|
|
208
|
+
elif tag_a:
|
|
209
|
+
va = next((v for v in versions if v.version_tag == tag_a), None)
|
|
210
|
+
vb = versions[0]
|
|
211
|
+
else:
|
|
212
|
+
vb = versions[0]
|
|
213
|
+
va = versions[1]
|
|
214
|
+
|
|
215
|
+
if not va or not vb:
|
|
216
|
+
return f"找不到指定版本: {tag_a} / {tag_b}"
|
|
217
|
+
|
|
218
|
+
diff_lines = list(difflib.unified_diff(
|
|
219
|
+
va.code.splitlines(keepends=True),
|
|
220
|
+
vb.code.splitlines(keepends=True),
|
|
221
|
+
fromfile=f"{name} {va.version_tag} ({va.created_at[:10]})",
|
|
222
|
+
tofile=f"{name} {vb.version_tag} ({vb.created_at[:10]})",
|
|
223
|
+
n=3,
|
|
224
|
+
))
|
|
225
|
+
if not diff_lines:
|
|
226
|
+
return f"{va.version_tag} 与 {vb.version_tag} 代码完全相同。"
|
|
227
|
+
return "".join(diff_lines)
|
|
228
|
+
|
|
229
|
+
# ── Store backtest / review results ───────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
def save_backtest(self, version_id: int, result: Dict):
|
|
232
|
+
with _get_db() as conn:
|
|
233
|
+
conn.execute(
|
|
234
|
+
"UPDATE strategies SET backtest_result=? WHERE id=?",
|
|
235
|
+
(json.dumps(result), version_id),
|
|
236
|
+
)
|
|
237
|
+
conn.commit()
|
|
238
|
+
|
|
239
|
+
def save_review(self, version_id: int, review: Dict):
|
|
240
|
+
with _get_db() as conn:
|
|
241
|
+
conn.execute(
|
|
242
|
+
"UPDATE strategies SET review_result=? WHERE id=?",
|
|
243
|
+
(json.dumps(review), version_id),
|
|
244
|
+
)
|
|
245
|
+
conn.commit()
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
249
|
+
# AI Strategy Reviewer (过拟合 / 前视偏差 / 代码质量)
|
|
250
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
251
|
+
|
|
252
|
+
# 静态规则检测(不需要 AI,速度快)
|
|
253
|
+
|
|
254
|
+
_LOOKAHEAD_PATTERNS = [
|
|
255
|
+
(r"\.shift\(-\d+\)", "负向shift:使用了未来数据"),
|
|
256
|
+
(r"df\[.+\]\s*=.*\.shift\(0\)", "shift(0)可能引入当日收盘前视偏差"),
|
|
257
|
+
(r"fillna\(method=['\"]bfill", "后向填充(bfill)引入前视偏差"),
|
|
258
|
+
(r"\.rolling\([^)]+\)\.apply\(", "rolling.apply 注意是否含未来数据"),
|
|
259
|
+
(r"target\s*=\s*.+\[.+[+>]\d", "目标变量使用未来标签"),
|
|
260
|
+
]
|
|
261
|
+
|
|
262
|
+
_OVERFIT_PATTERNS = [
|
|
263
|
+
(r"\.optimize\(", "参数优化可能导致过拟合"),
|
|
264
|
+
(r"for.*param.*range\(", "网格搜索参数可能过拟合"),
|
|
265
|
+
(r"if.*sharpe.*>[5-9]", "Sharpe>5 在样本外几乎不可能,可能过拟合"),
|
|
266
|
+
(r"n_estimators\s*=\s*[5-9]\d{2,}", "超大集成树数量,可能过拟合"),
|
|
267
|
+
(r"in_sample.*backtest", "样本内回测不代表真实表现"),
|
|
268
|
+
]
|
|
269
|
+
|
|
270
|
+
def static_review(code: str) -> Dict[str, Any]:
|
|
271
|
+
"""快速静态规则审查,不调用 AI。"""
|
|
272
|
+
warnings = []
|
|
273
|
+
errors = []
|
|
274
|
+
|
|
275
|
+
for pattern, msg in _LOOKAHEAD_PATTERNS:
|
|
276
|
+
if re.search(pattern, code, re.IGNORECASE):
|
|
277
|
+
errors.append({"type": "look_ahead_bias", "detail": msg, "pattern": pattern})
|
|
278
|
+
|
|
279
|
+
for pattern, msg in _OVERFIT_PATTERNS:
|
|
280
|
+
if re.search(pattern, code, re.IGNORECASE):
|
|
281
|
+
warnings.append({"type": "overfit_risk", "detail": msg, "pattern": pattern})
|
|
282
|
+
|
|
283
|
+
# 回测质量检查
|
|
284
|
+
quality_checks = []
|
|
285
|
+
if "train_test_split" not in code and "out_of_sample" not in code:
|
|
286
|
+
quality_checks.append("未见样本外验证(train/test split 或 walk-forward)")
|
|
287
|
+
if "commission" not in code and "slippage" not in code:
|
|
288
|
+
quality_checks.append("未设置手续费/滑点,回测结果偏乐观")
|
|
289
|
+
if "random_state" in code and "shuffle" in code:
|
|
290
|
+
quality_checks.append("时序数据注意不要随机shuffle,会引入未来信息")
|
|
291
|
+
|
|
292
|
+
risk_score = len(errors) * 3 + len(warnings) + len(quality_checks)
|
|
293
|
+
grade = "A" if risk_score == 0 else "B" if risk_score <= 2 else "C" if risk_score <= 5 else "D"
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
"grade": grade,
|
|
297
|
+
"risk_score": risk_score,
|
|
298
|
+
"errors": errors, # 严重(前视偏差)
|
|
299
|
+
"warnings": warnings, # 中等(过拟合风险)
|
|
300
|
+
"quality_checks": quality_checks, # 建议改进
|
|
301
|
+
"summary": (f"发现 {len(errors)} 个严重问题, "
|
|
302
|
+
f"{len(warnings)} 个警告, "
|
|
303
|
+
f"{len(quality_checks)} 条优化建议"),
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
async def ai_review_strategy(
|
|
308
|
+
code: str,
|
|
309
|
+
backtest_result: Optional[Dict],
|
|
310
|
+
ollama_url: str,
|
|
311
|
+
model: str,
|
|
312
|
+
on_token: Optional[callable] = None,
|
|
313
|
+
) -> Dict[str, Any]:
|
|
314
|
+
"""AI 深度审查策略代码(结合回测结果)."""
|
|
315
|
+
import aiohttp
|
|
316
|
+
|
|
317
|
+
# 先跑静态检测
|
|
318
|
+
static = static_review(code)
|
|
319
|
+
|
|
320
|
+
bt_summary = ""
|
|
321
|
+
if backtest_result:
|
|
322
|
+
br = backtest_result
|
|
323
|
+
bt_summary = f"""
|
|
324
|
+
【回测结果】
|
|
325
|
+
年化收益: {br.get('annual_return_pct', 'N/A')}%
|
|
326
|
+
夏普比率: {br.get('sharpe_ratio', 'N/A')}
|
|
327
|
+
最大回撤: {br.get('max_drawdown_pct', 'N/A')}%
|
|
328
|
+
总收益: {br.get('total_return_pct', 'N/A')}%
|
|
329
|
+
胜率: {br.get('win_rate_pct', 'N/A')}%
|
|
330
|
+
交易次数: {br.get('total_trades', 'N/A')}"""
|
|
331
|
+
|
|
332
|
+
static_issues = ""
|
|
333
|
+
if static["errors"]:
|
|
334
|
+
static_issues += "\n静态检测发现严重问题:\n"
|
|
335
|
+
for e in static["errors"]:
|
|
336
|
+
static_issues += f" ❌ {e['detail']}\n"
|
|
337
|
+
if static["warnings"]:
|
|
338
|
+
static_issues += "静态检测警告:\n"
|
|
339
|
+
for w in static["warnings"]:
|
|
340
|
+
static_issues += f" ⚠️ {w['detail']}\n"
|
|
341
|
+
|
|
342
|
+
code_preview = code[:2000] + ("...(截断)" if len(code) > 2000 else "")
|
|
343
|
+
|
|
344
|
+
prompt = f"""你是量化策略审查专家,请对以下策略进行深度代码审查。
|
|
345
|
+
|
|
346
|
+
{bt_summary}
|
|
347
|
+
{static_issues}
|
|
348
|
+
|
|
349
|
+
【策略代码】
|
|
350
|
+
```python
|
|
351
|
+
{code_preview}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
请从以下5个维度审查(每项给出1-2个具体问题或确认通过):
|
|
355
|
+
|
|
356
|
+
**1. 前视偏差 (Look-Ahead Bias)**
|
|
357
|
+
检查是否使用了未来数据(如未来收益、未来价格、负向lag等)
|
|
358
|
+
|
|
359
|
+
**2. 过拟合风险 (Overfitting)**
|
|
360
|
+
参数数量是否合理?是否有样本外验证?夏普比率是否过高?
|
|
361
|
+
|
|
362
|
+
**3. 执行可行性 (Execution Reality)**
|
|
363
|
+
交易成本、流动性、订单执行假设是否合理?
|
|
364
|
+
|
|
365
|
+
**4. 策略逻辑 (Logic Soundness)**
|
|
366
|
+
策略的核心假设是否有理论支撑?边界条件是否处理?
|
|
367
|
+
|
|
368
|
+
**5. 改进建议 (Improvements)**
|
|
369
|
+
最重要的2-3个改进点
|
|
370
|
+
|
|
371
|
+
最后给出:**综合评级** [A/B/C/D] 和 **是否推荐实盘测试** [是/否/待改进后]
|
|
372
|
+
|
|
373
|
+
中文回答,专业且直接。"""
|
|
374
|
+
|
|
375
|
+
messages = [
|
|
376
|
+
{"role": "system", "content": "你是严格的量化策略审查专家,专注于发现策略缺陷,防止真金白银的亏损。"},
|
|
377
|
+
{"role": "user", "content": prompt},
|
|
378
|
+
]
|
|
379
|
+
|
|
380
|
+
full_text = ""
|
|
381
|
+
try:
|
|
382
|
+
async with aiohttp.ClientSession() as sess:
|
|
383
|
+
async with sess.post(
|
|
384
|
+
f"{ollama_url}/api/chat",
|
|
385
|
+
json={"model": model, "messages": messages, "stream": True,
|
|
386
|
+
"options": {"temperature": 0.2, "num_predict": 800}},
|
|
387
|
+
timeout=aiohttp.ClientTimeout(total=120),
|
|
388
|
+
) as resp:
|
|
389
|
+
async for line in resp.content:
|
|
390
|
+
text = line.decode("utf-8", errors="ignore").strip()
|
|
391
|
+
if not text:
|
|
392
|
+
continue
|
|
393
|
+
try:
|
|
394
|
+
data = json.loads(text)
|
|
395
|
+
tok = data.get("message", {}).get("content", "")
|
|
396
|
+
if tok:
|
|
397
|
+
full_text += tok
|
|
398
|
+
if on_token:
|
|
399
|
+
on_token(tok)
|
|
400
|
+
except Exception:
|
|
401
|
+
pass
|
|
402
|
+
except Exception as e:
|
|
403
|
+
full_text = f"[AI审查失败: {e}]"
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
"static": static,
|
|
407
|
+
"ai_review": full_text,
|
|
408
|
+
"reviewed_at": datetime.now().isoformat(),
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
# ── Singleton ─────────────────────────────────────────────────────────────────
|
|
413
|
+
|
|
414
|
+
_vault: Optional[StrategyVault] = None
|
|
415
|
+
|
|
416
|
+
def get_vault() -> StrategyVault:
|
|
417
|
+
global _vault
|
|
418
|
+
if _vault is None:
|
|
419
|
+
_vault = StrategyVault()
|
|
420
|
+
return _vault
|
ui/__init__.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Aria Code — UI package.
|
|
2
|
+
|
|
3
|
+
Public surface:
|
|
4
|
+
|
|
5
|
+
from ui import console, HAS_RICH, HAS_PT # shared console + flags
|
|
6
|
+
from ui import _EscWatcher, _esc_watcher # ESC cancellation
|
|
7
|
+
from ui import AriaPTCompleter, ARIA_PT_STYLE # prompt_toolkit completer
|
|
8
|
+
from ui import arrow_select, run_picker_in_thread # arrow-key picker
|
|
9
|
+
from ui import PanelInputConfig, run_panel_input # structured input panel
|
|
10
|
+
from ui.render import render_verdict_banner, ... # Rich output renderers
|
|
11
|
+
from ui.banner import render_full_banner, ... # startup banner
|
|
12
|
+
from ui.render.output import print_error, print_tool_result, ...
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from .console import (
|
|
16
|
+
console,
|
|
17
|
+
HAS_RICH,
|
|
18
|
+
HAS_PT,
|
|
19
|
+
_SYNTAX_THEME,
|
|
20
|
+
_EscWatcher,
|
|
21
|
+
_esc_watcher,
|
|
22
|
+
_HAS_TERMIOS,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
from .completer import AriaPTCompleter, ARIA_PT_STYLE
|
|
26
|
+
|
|
27
|
+
from .picker import (
|
|
28
|
+
arrow_select,
|
|
29
|
+
run_picker_in_thread,
|
|
30
|
+
_arrow_select,
|
|
31
|
+
_run_picker_in_thread,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
from .input_box import (
|
|
35
|
+
PanelInputConfig,
|
|
36
|
+
run_panel_input,
|
|
37
|
+
detect_terminal_theme,
|
|
38
|
+
PromptAndPlaceholderProcessor,
|
|
39
|
+
PlaceholderProcessor,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
from .robot import (
|
|
43
|
+
RobotState,
|
|
44
|
+
set_robot_state,
|
|
45
|
+
get_robot_state,
|
|
46
|
+
get_robot_row,
|
|
47
|
+
get_robot_frame,
|
|
48
|
+
get_status_dot,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
from .banner import (
|
|
52
|
+
privacy_status_label,
|
|
53
|
+
control_status_label,
|
|
54
|
+
ollama_status_label,
|
|
55
|
+
bottom_toolbar_parts,
|
|
56
|
+
render_compact_banner,
|
|
57
|
+
render_full_banner,
|
|
58
|
+
render_try_hints,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
from .render.output import (
|
|
62
|
+
FINANCE_TOOL_NAMES,
|
|
63
|
+
clean_tool_error_message,
|
|
64
|
+
error_hint,
|
|
65
|
+
print_error,
|
|
66
|
+
print_tool_result,
|
|
67
|
+
print_tool_activity_group,
|
|
68
|
+
print_fallback_toast,
|
|
69
|
+
print_context_warning,
|
|
70
|
+
print_tool_blocked,
|
|
71
|
+
print_thinking_header,
|
|
72
|
+
print_done_footer,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
__all__ = [
|
|
76
|
+
# console
|
|
77
|
+
"console", "HAS_RICH", "HAS_PT", "_SYNTAX_THEME",
|
|
78
|
+
"_EscWatcher", "_esc_watcher", "_HAS_TERMIOS",
|
|
79
|
+
# completer
|
|
80
|
+
"AriaPTCompleter", "ARIA_PT_STYLE",
|
|
81
|
+
# picker
|
|
82
|
+
"arrow_select", "run_picker_in_thread",
|
|
83
|
+
"_arrow_select", "_run_picker_in_thread",
|
|
84
|
+
# input panel
|
|
85
|
+
"PanelInputConfig", "run_panel_input",
|
|
86
|
+
"detect_terminal_theme",
|
|
87
|
+
"PromptAndPlaceholderProcessor", "PlaceholderProcessor",
|
|
88
|
+
# robot mascot
|
|
89
|
+
"RobotState", "set_robot_state", "get_robot_state",
|
|
90
|
+
"get_robot_row", "get_robot_frame", "get_status_dot",
|
|
91
|
+
# banner
|
|
92
|
+
"privacy_status_label", "control_status_label", "ollama_status_label",
|
|
93
|
+
"bottom_toolbar_parts", "render_compact_banner", "render_full_banner", "render_try_hints",
|
|
94
|
+
# output rendering
|
|
95
|
+
"FINANCE_TOOL_NAMES", "clean_tool_error_message", "error_hint",
|
|
96
|
+
"print_error", "print_tool_result",
|
|
97
|
+
"print_tool_activity_group", "print_fallback_toast",
|
|
98
|
+
"print_context_warning", "print_tool_blocked",
|
|
99
|
+
"print_thinking_header", "print_done_footer",
|
|
100
|
+
]
|