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.
Files changed (284) hide show
  1. agents/__init__.py +32 -0
  2. agents/base.py +190 -0
  3. agents/deep/__init__.py +37 -0
  4. agents/deep/calibration_loop.py +144 -0
  5. agents/deep/critic.py +125 -0
  6. agents/deep/deepen.py +193 -0
  7. agents/deep/models.py +149 -0
  8. agents/deep/pipeline.py +164 -0
  9. agents/deep/quant_fusion.py +192 -0
  10. agents/deep/themes.py +95 -0
  11. agents/deep/tiers.py +106 -0
  12. agents/financial/__init__.py +10 -0
  13. agents/financial/catalyst.py +279 -0
  14. agents/financial/debate.py +145 -0
  15. agents/financial/earnings.py +303 -0
  16. agents/financial/fundamental.py +159 -0
  17. agents/financial/macro.py +99 -0
  18. agents/financial/news.py +207 -0
  19. agents/financial/risk.py +132 -0
  20. agents/financial/sector.py +279 -0
  21. agents/financial/synthesis.py +274 -0
  22. agents/financial/technical.py +258 -0
  23. agents/portfolio_agent.py +333 -0
  24. agents/realty/__init__.py +62 -0
  25. agents/realty/asset_diagnosis.py +150 -0
  26. agents/realty/business_match.py +165 -0
  27. agents/realty/cashflow_verify.py +208 -0
  28. agents/realty/contract_rules.py +209 -0
  29. agents/realty/energy_anomaly.py +188 -0
  30. agents/realty/exit_settlement.py +207 -0
  31. agents/realty/fulfillment_risk.py +205 -0
  32. agents/realty/ops_optimize.py +159 -0
  33. agents/realty/revenue_share.py +214 -0
  34. agents/registry.py +144 -0
  35. agents/sports/__init__.py +0 -0
  36. agents/sports/football_agent.py +169 -0
  37. agents/team.py +289 -0
  38. aliyun_data_client.py +660 -0
  39. apps/README.md +12 -0
  40. apps/__init__.py +2 -0
  41. apps/channels/README.md +15 -0
  42. apps/cli/README.md +13 -0
  43. apps/cli/__init__.py +2 -0
  44. apps/cli/bootstrap.py +99 -0
  45. apps/cli/codegen_paths.py +29 -0
  46. apps/cli/commands/__init__.py +16 -0
  47. apps/cli/commands/analysis_cmds.py +288 -0
  48. apps/cli/commands/backtest_cmds.py +1887 -0
  49. apps/cli/commands/broker_cmds.py +1154 -0
  50. apps/cli/commands/business_workflow_cmds.py +289 -0
  51. apps/cli/commands/catalog.py +84 -0
  52. apps/cli/commands/data_cmds.py +405 -0
  53. apps/cli/commands/diagnostic_cmds.py +179 -0
  54. apps/cli/commands/diagnostic_ops_cmds.py +696 -0
  55. apps/cli/commands/finance_render.py +12 -0
  56. apps/cli/commands/market.py +399 -0
  57. apps/cli/commands/market_cmds.py +1276 -0
  58. apps/cli/commands/market_context.py +425 -0
  59. apps/cli/commands/market_render.py +7 -0
  60. apps/cli/commands/model_cmds.py +1579 -0
  61. apps/cli/commands/ops_cmds.py +668 -0
  62. apps/cli/commands/portfolio_cmds.py +962 -0
  63. apps/cli/commands/report.py +377 -0
  64. apps/cli/commands/scaffold_templates.py +617 -0
  65. apps/cli/commands/session_cmds.py +179 -0
  66. apps/cli/commands/session_ux_cmds.py +280 -0
  67. apps/cli/commands/team.py +588 -0
  68. apps/cli/commands/team_render.py +8 -0
  69. apps/cli/commands/ui_cmds.py +358 -0
  70. apps/cli/commands/workflow_cmds.py +279 -0
  71. apps/cli/commands/workspace_cmds.py +1414 -0
  72. apps/cli/config_paths.py +70 -0
  73. apps/cli/config_store.py +61 -0
  74. apps/cli/deterministic.py +122 -0
  75. apps/cli/direct.py +48 -0
  76. apps/cli/github_app_auth.py +135 -0
  77. apps/cli/handlers/__init__.py +11 -0
  78. apps/cli/handlers/broker_handlers.py +122 -0
  79. apps/cli/handlers/chart_handlers.py +1309 -0
  80. apps/cli/handlers/market_handlers.py +2509 -0
  81. apps/cli/handlers/realty_handlers.py +114 -0
  82. apps/cli/handlers/strategy_advice.py +82 -0
  83. apps/cli/hooks.py +180 -0
  84. apps/cli/i18n.py +284 -0
  85. apps/cli/intent.py +136 -0
  86. apps/cli/intent_router.py +217 -0
  87. apps/cli/lifecycle_hooks.py +48 -0
  88. apps/cli/main.py +29 -0
  89. apps/cli/market_metadata.py +135 -0
  90. apps/cli/market_universe.py +265 -0
  91. apps/cli/message_processing.py +257 -0
  92. apps/cli/plan_mode.py +139 -0
  93. apps/cli/plotly_html.py +15 -0
  94. apps/cli/prediction_feedback.py +202 -0
  95. apps/cli/preflight.py +497 -0
  96. apps/cli/project_aria.py +60 -0
  97. apps/cli/prompts/__init__.py +0 -0
  98. apps/cli/prompts/coding.py +658 -0
  99. apps/cli/prompts/system_prompts.py +531 -0
  100. apps/cli/prompts/ui.py +434 -0
  101. apps/cli/providers/__init__.py +1 -0
  102. apps/cli/providers/base.py +271 -0
  103. apps/cli/providers/chat_routing.py +80 -0
  104. apps/cli/providers/llm/__init__.py +1 -0
  105. apps/cli/providers/llm/ollama_stream.py +1170 -0
  106. apps/cli/providers/llm/sse_stream.py +216 -0
  107. apps/cli/providers/runtime_bridge.py +185 -0
  108. apps/cli/runtime_consumer.py +489 -0
  109. apps/cli/session_export.py +87 -0
  110. apps/cli/session_jsonl.py +207 -0
  111. apps/cli/session_store.py +112 -0
  112. apps/cli/todo_tracker.py +190 -0
  113. apps/cli/tools/__init__.py +40 -0
  114. apps/cli/tools/context.py +46 -0
  115. apps/cli/tools/file_tools.py +112 -0
  116. apps/cli/tools/market_tools.py +549 -0
  117. apps/cli/tools/notebook_tools.py +111 -0
  118. apps/cli/tools/system_tools.py +669 -0
  119. apps/cli/tools/write_tools.py +715 -0
  120. apps/cli/tradingview_bridge.py +434 -0
  121. apps/cli/update_check.py +152 -0
  122. apps/cli/utils/__init__.py +0 -0
  123. apps/cli/utils/market_detect.py +1578 -0
  124. apps/daemon/README.md +14 -0
  125. apps/vscode/README.md +115 -0
  126. apps/vscode/package.json +70 -0
  127. aria_cli.py +11636 -0
  128. aria_code-4.1.3.dist-info/METADATA +952 -0
  129. aria_code-4.1.3.dist-info/RECORD +284 -0
  130. aria_code-4.1.3.dist-info/WHEEL +5 -0
  131. aria_code-4.1.3.dist-info/entry_points.txt +2 -0
  132. aria_code-4.1.3.dist-info/licenses/LICENSE +121 -0
  133. aria_code-4.1.3.dist-info/top_level.txt +50 -0
  134. aria_daemon.py +1295 -0
  135. aria_feishu_bot.py +1359 -0
  136. aria_relay_client.py +182 -0
  137. aria_relay_server.py +405 -0
  138. aria_telegram_bot.py +202 -0
  139. ariarc.py +328 -0
  140. artifacts.py +491 -0
  141. backtest_report.py +472 -0
  142. brokers/__init__.py +72 -0
  143. brokers/base.py +207 -0
  144. brokers/capabilities.py +264 -0
  145. brokers/cn/__init__.py +10 -0
  146. brokers/cn/easytrader_broker.py +193 -0
  147. brokers/cn/futu_broker.py +194 -0
  148. brokers/cn/longbridge_broker.py +190 -0
  149. brokers/cn/tiger_broker.py +196 -0
  150. brokers/cn/xtquant_broker.py +175 -0
  151. brokers/config.py +364 -0
  152. brokers/intl/__init__.py +5 -0
  153. brokers/intl/alpaca_broker.py +183 -0
  154. brokers/intl/ibkr_broker.py +215 -0
  155. brokers/intl/webull_broker.py +156 -0
  156. brokers/paper_broker.py +259 -0
  157. brokers/planning.py +296 -0
  158. brokers/registry.py +181 -0
  159. brokers/trading.py +237 -0
  160. change_store.py +127 -0
  161. command_safety.py +19 -0
  162. computer_use_tools.py +504 -0
  163. dashboard_generator.py +578 -0
  164. data_analysis_tools.py +808 -0
  165. data_cleaner.py +483 -0
  166. data_service.py +481 -0
  167. datasources/__init__.py +23 -0
  168. datasources/base.py +166 -0
  169. datasources/router.py +221 -0
  170. datasources/sources/__init__.py +15 -0
  171. datasources/sources/akshare_source.py +269 -0
  172. datasources/sources/alpha_vantage_source.py +202 -0
  173. datasources/sources/edgar_source.py +218 -0
  174. datasources/sources/finnhub_source.py +197 -0
  175. datasources/sources/fred_source.py +219 -0
  176. datasources/sources/tushare_source.py +141 -0
  177. datasources/sources/web_scraper_source.py +278 -0
  178. datasources/sources/world_bank_source.py +205 -0
  179. datasources/sources/yfinance_source.py +152 -0
  180. demo_player.py +204 -0
  181. doctor.py +508 -0
  182. file_analysis_tools.py +734 -0
  183. finance_formulas.py +389 -0
  184. football_data_client.py +1670 -0
  185. intent_classifier.py +358 -0
  186. local_finance_tools.py +3221 -0
  187. local_llm_provider.py +552 -0
  188. macro_tools.py +368 -0
  189. market_data_client.py +1899 -0
  190. mcp_client.py +506 -0
  191. memory_manager.py +245 -0
  192. model_capability.py +416 -0
  193. notification_tools.py +248 -0
  194. packages/__init__.py +23 -0
  195. packages/aria_agents/__init__.py +5 -0
  196. packages/aria_agents/manifest.py +69 -0
  197. packages/aria_core/__init__.py +34 -0
  198. packages/aria_core/architecture.py +192 -0
  199. packages/aria_core/export.py +124 -0
  200. packages/aria_core/manifest.py +65 -0
  201. packages/aria_infra/__init__.py +15 -0
  202. packages/aria_infra/arthera.py +52 -0
  203. packages/aria_infra/doctor.py +246 -0
  204. packages/aria_infra/product.py +37 -0
  205. packages/aria_mcp/__init__.py +25 -0
  206. packages/aria_mcp/bridge.py +38 -0
  207. packages/aria_mcp/config.py +97 -0
  208. packages/aria_mcp/tools.py +61 -0
  209. packages/aria_sdk/__init__.py +19 -0
  210. packages/aria_sdk/client.py +396 -0
  211. packages/aria_sdk/providers.py +70 -0
  212. packages/aria_sdk/streaming.py +73 -0
  213. packages/aria_sdk/types.py +86 -0
  214. packages/aria_services/__init__.py +55 -0
  215. packages/aria_services/context.py +258 -0
  216. packages/aria_services/data.py +11 -0
  217. packages/aria_services/provider_health.py +189 -0
  218. packages/aria_services/registry.py +213 -0
  219. packages/aria_services/usage.py +138 -0
  220. packages/aria_skills/__init__.py +5 -0
  221. packages/aria_skills/registry.py +59 -0
  222. packages/aria_tools/__init__.py +5 -0
  223. packages/aria_tools/registry.py +128 -0
  224. packages/quant_engine/__init__.py +6 -0
  225. packages/quant_engine/sports/__init__.py +72 -0
  226. packages/quant_engine/sports/calibrator.py +353 -0
  227. packages/quant_engine/sports/dixon_coles.py +234 -0
  228. packages/quant_engine/sports/elo.py +299 -0
  229. packages/quant_engine/sports/form.py +188 -0
  230. packages/quant_engine/sports/h2h.py +195 -0
  231. packages/quant_engine/sports/ml_model.py +354 -0
  232. packages/quant_engine/sports/predictor.py +311 -0
  233. packages/quant_engine/sports/tracker.py +664 -0
  234. packages/quant_engine/stochastic/__init__.py +27 -0
  235. packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  236. packages/quant_engine/stochastic/ito_calculus.py +477 -0
  237. packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  238. packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  239. packages/quant_engine/stochastic/options_pricing.py +573 -0
  240. packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  241. plan_utils.py +194 -0
  242. plugin_loader.py +328 -0
  243. portfolio_ledger.py +262 -0
  244. privacy/__init__.py +5 -0
  245. privacy/feedback.py +123 -0
  246. project_tools.py +525 -0
  247. providers/__init__.py +30 -0
  248. providers/llm/__init__.py +19 -0
  249. providers/llm/anthropic.py +184 -0
  250. providers/llm/base.py +139 -0
  251. providers/llm/ollama.py +128 -0
  252. providers/llm/openai_compat.py +282 -0
  253. providers/llm/registry.py +358 -0
  254. realty_data_tools.py +659 -0
  255. report_generator.py +1314 -0
  256. runtime/__init__.py +103 -0
  257. runtime/agent_loop.py +1183 -0
  258. runtime/approval.py +51 -0
  259. runtime/events.py +102 -0
  260. runtime/gateway.py +128 -0
  261. runtime/lsp.py +346 -0
  262. runtime/subagent.py +258 -0
  263. runtime/tool_executor.py +104 -0
  264. runtime/tool_policy.py +106 -0
  265. safety/__init__.py +21 -0
  266. safety/permissions.py +275 -0
  267. setup_wizard.py +653 -0
  268. strategy_vault.py +420 -0
  269. ui/__init__.py +100 -0
  270. ui/banner.py +310 -0
  271. ui/completer.py +391 -0
  272. ui/console.py +271 -0
  273. ui/image_render.py +243 -0
  274. ui/input_box.py +376 -0
  275. ui/picker.py +195 -0
  276. ui/render/__init__.py +11 -0
  277. ui/render/finance.py +1480 -0
  278. ui/render/market.py +225 -0
  279. ui/render/output.py +681 -0
  280. ui/render/team.py +346 -0
  281. ui/robot.py +235 -0
  282. workspace/__init__.py +6 -0
  283. workspace/files.py +170 -0
  284. workspace/verify.py +113 -0
@@ -0,0 +1,617 @@
1
+ # Auto-extracted from aria_cli.py (SlashCommands._SCAFFOLD_TEMPLATES)
2
+
3
+ SCAFFOLD_TEMPLATES = {
4
+ "quant": {
5
+ "desc": "量化策略项目(数据层 / 信号层 / 回测引擎 / 报告)",
6
+ "dirs": ["data/raw", "data/processed", "strategy", "backtest", "report", "tests"],
7
+ "files": {
8
+ "requirements.txt": "akshare\nyfinance\npandas\nnumpy\nmatplotlib\nscipy\n",
9
+ "data/fetcher.py": '''\
10
+ """数据获取层:akshare A股 / yfinance 美股,带本地 CSV 缓存。"""
11
+ import os, pathlib
12
+ import akshare as ak
13
+ import yfinance as yf
14
+ import pandas as pd
15
+
16
+ CACHE = pathlib.Path(__file__).parent / "processed"
17
+ CACHE.mkdir(exist_ok=True)
18
+
19
+ def fetch_ashare(symbol: str, start: str, end: str, adjust: str = "qfq") -> pd.DataFrame:
20
+ cache_f = CACHE / f"{symbol}_{adjust}.csv"
21
+ if cache_f.exists():
22
+ df = pd.read_csv(cache_f, index_col=0, parse_dates=True)
23
+ if str(df.index[-1].date()) >= end:
24
+ return df
25
+ df = ak.stock_zh_a_hist(symbol=symbol, period="daily",
26
+ start_date=start.replace("-",""),
27
+ end_date=end.replace("-",""), adjust=adjust)
28
+ df = df.rename(columns={"日期":"Date","开盘":"Open","最高":"High",
29
+ "最低":"Low","收盘":"Close","成交量":"Volume"})
30
+ df["Date"] = pd.to_datetime(df["Date"])
31
+ df = df.set_index("Date").sort_index()
32
+ df.to_csv(cache_f)
33
+ return df
34
+
35
+ def fetch_us(symbol: str, period: str = "2y") -> pd.DataFrame:
36
+ return yf.Ticker(symbol).history(period=period, auto_adjust=True)[
37
+ ["Open","High","Low","Close","Volume"]]
38
+ ''',
39
+ "strategy/base.py": '''\
40
+ """抽象策略基类:子类只需实现 generate_signals()。"""
41
+ from abc import ABC, abstractmethod
42
+ import pandas as pd
43
+
44
+ class Strategy(ABC):
45
+ name: str = "base"
46
+
47
+ @abstractmethod
48
+ def generate_signals(self, df: pd.DataFrame) -> pd.Series:
49
+ """返回 +1(做多)/ -1(做空)/ 0(空仓)的 Series,与 df.index 对齐。"""
50
+ ...
51
+ ''',
52
+ "strategy/dual_ma.py": '''\
53
+ """双均线策略:短期 MA 上穿长期 MA 买入,下穿卖出。"""
54
+ import pandas as pd
55
+ from .base import Strategy
56
+
57
+ class DualMA(Strategy):
58
+ name = "dual_ma"
59
+
60
+ def __init__(self, fast: int = 5, slow: int = 20):
61
+ self.fast = fast
62
+ self.slow = slow
63
+
64
+ def generate_signals(self, df: pd.DataFrame) -> pd.Series:
65
+ close = df["Close"]
66
+ fast_ma = close.rolling(self.fast).mean()
67
+ slow_ma = close.rolling(self.slow).mean()
68
+ sig = pd.Series(0, index=df.index)
69
+ sig[fast_ma > slow_ma] = 1
70
+ sig[fast_ma < slow_ma] = -1
71
+ # 只在交叉时换仓(减少换手)
72
+ return sig.diff().fillna(0).clip(-1, 1).cumsum().clip(-1, 1)
73
+ ''',
74
+ "strategy/__init__.py": "from .dual_ma import DualMA\n",
75
+ "backtest/engine.py": '''\
76
+ """向量化回测引擎,含 A股交易成本。"""
77
+ import pandas as pd
78
+ import numpy as np
79
+
80
+ # A股交易成本:双边手续费 0.025%×2 + 印花税 0.05%(卖出)+ 滑点 0.1%
81
+ A_SHARE_COST = 0.025/100 * 2 + 0.05/100 + 0.1/100
82
+
83
+ def backtest(df: pd.DataFrame, signals: pd.Series,
84
+ cost_rate: float = A_SHARE_COST,
85
+ initial_capital: float = 1_000_000) -> pd.DataFrame:
86
+ """
87
+ df : OHLCV DataFrame(index=DatetimeIndex)
88
+ signals : +1 做多 / -1 做空 / 0 空仓
89
+ 返回含 equity_curve 和各日 pnl 的 DataFrame
90
+ """
91
+ ret = df["Close"].pct_change()
92
+ pos = signals.shift(1).fillna(0) # 信号次日执行
93
+ turnover = pos.diff().abs().fillna(0)
94
+ strat_ret = pos * ret - turnover * cost_rate
95
+ bnh_ret = ret.copy()
96
+
97
+ cum_strat = (1 + strat_ret).cumprod() * initial_capital
98
+ cum_bnh = (1 + bnh_ret).cumprod() * initial_capital
99
+
100
+ result = pd.DataFrame({
101
+ "strategy": cum_strat, "buy_hold": cum_bnh,
102
+ "daily_ret": strat_ret, "position": pos,
103
+ })
104
+
105
+ # 统计指标
106
+ ann = 252
107
+ sharpe = strat_ret.mean() / strat_ret.std() * np.sqrt(ann) if strat_ret.std() else 0
108
+ max_dd = (cum_strat / cum_strat.cummax() - 1).min()
109
+ total_r = cum_strat.iloc[-1] / initial_capital - 1
110
+ ann_r = (1 + total_r) ** (ann / len(result)) - 1
111
+ wins = (strat_ret[pos.shift(-1) != pos] > 0).mean()
112
+
113
+ result.attrs = dict(sharpe=round(sharpe,2), max_drawdown=round(max_dd,4),
114
+ total_return=round(total_r,4), annual_return=round(ann_r,4),
115
+ win_rate=round(wins,4))
116
+ return result
117
+ ''',
118
+ "backtest/__init__.py": "from .engine import backtest\n",
119
+ "report/plot.py": '''\
120
+ """生成净值曲线 + 回撤图,保存为 PNG。"""
121
+ import matplotlib
122
+ matplotlib.use("Agg")
123
+ import matplotlib.pyplot as plt
124
+ import matplotlib.gridspec as gridspec
125
+ import pandas as pd
126
+ import pathlib
127
+
128
+ OUT = pathlib.Path(__file__).parent / "output"
129
+ OUT.mkdir(exist_ok=True)
130
+
131
+ def plot_result(result: pd.DataFrame, title: str = "Backtest") -> pathlib.Path:
132
+ fig = plt.figure(figsize=(12, 7), facecolor="#0d1117")
133
+ gs = gridspec.GridSpec(2, 1, height_ratios=[3,1], hspace=0.08)
134
+
135
+ ax1 = fig.add_subplot(gs[0])
136
+ ax1.plot(result.index, result["strategy"], color="#3fb950", lw=1.5, label="Strategy")
137
+ ax1.plot(result.index, result["buy_hold"], color="#58a6ff", lw=1.0, label="Buy & Hold", alpha=0.7)
138
+ ax1.set_facecolor("#161b22"); ax1.tick_params(colors="#8b949e"); ax1.legend(facecolor="#21262d", edgecolor="#30363d", labelcolor="#e6edf3")
139
+ ax1.set_title(title, color="#e6edf3", fontsize=13)
140
+ for spine in ax1.spines.values(): spine.set_color("#30363d")
141
+
142
+ dd = result["strategy"] / result["strategy"].cummax() - 1
143
+ ax2 = fig.add_subplot(gs[1], sharex=ax1)
144
+ ax2.fill_between(result.index, dd, 0, color="#f85149", alpha=0.6)
145
+ ax2.set_facecolor("#161b22"); ax2.tick_params(colors="#8b949e")
146
+ ax2.set_ylabel("Drawdown", color="#8b949e", fontsize=9)
147
+ for spine in ax2.spines.values(): spine.set_color("#30363d")
148
+
149
+ a = result.attrs
150
+ fig.text(0.12, 0.02, f"Annual {a.get('annual_return',0):.1%} Sharpe {a.get('sharpe',0):.2f} MaxDD {a.get('max_drawdown',0):.1%} Win {a.get('win_rate',0):.1%}", color="#8b949e", fontsize=9)
151
+
152
+ out_f = OUT / f"{title.replace(' ','_')}.png"
153
+ plt.savefig(out_f, dpi=130, bbox_inches="tight", facecolor=fig.get_facecolor())
154
+ plt.close(fig)
155
+ print(f"图表已保存: {out_f}")
156
+ return out_f
157
+ ''',
158
+ "report/__init__.py": "from .plot import plot_result\n",
159
+ "main.py": '''\
160
+ """运行量化策略回测的主入口。"""
161
+ import sys, pathlib
162
+ sys.path.insert(0, str(pathlib.Path(__file__).parent))
163
+
164
+ from data.fetcher import fetch_ashare
165
+ from strategy import DualMA
166
+ from backtest import backtest
167
+ from report import plot_result
168
+ from datetime import datetime, timedelta
169
+
170
+ SYMBOL = "600519" # 贵州茅台(示例)
171
+ END = datetime.now().strftime("%Y-%m-%d")
172
+ START = (datetime.now() - timedelta(days=730)).strftime("%Y-%m-%d")
173
+
174
+ print(f"抓取 {SYMBOL} 数据 {START} → {END}...")
175
+ df = fetch_ashare(SYMBOL, START, END)
176
+ print(f"共 {len(df)} 条 K线")
177
+
178
+ strategy = DualMA(fast=5, slow=20)
179
+ signals = strategy.generate_signals(df)
180
+ result = backtest(df, signals)
181
+
182
+ a = result.attrs
183
+ print(f"\n===== 回测结果 ({SYMBOL} · 双均线5/20) =====")
184
+ print(f"总收益: {a['total_return']:.2%}")
185
+ print(f"年化收益: {a['annual_return']:.2%}")
186
+ print(f"夏普比率: {a['sharpe']:.2f}")
187
+ print(f"最大回撤: {a['max_drawdown']:.2%}")
188
+ print(f"胜率: {a['win_rate']:.2%}")
189
+
190
+ plot_result(result, f"{SYMBOL}_DualMA")
191
+ ''',
192
+ "tests/test_strategy.py": '''\
193
+ """基础单元测试:策略信号生成 + 回测引擎。"""
194
+ import sys, pathlib
195
+ sys.path.insert(0, str(pathlib.Path(__file__).parent.parent))
196
+ import pandas as pd
197
+ import numpy as np
198
+ from strategy import DualMA
199
+ from backtest import backtest
200
+
201
+ def _make_df(n=100):
202
+ dates = pd.date_range("2024-01-01", periods=n)
203
+ close = pd.Series(100 + np.cumsum(np.random.randn(n)), index=dates)
204
+ return pd.DataFrame({"Open":close,"High":close*1.01,"Low":close*0.99,"Close":close,"Volume":1e6})
205
+
206
+ def test_signal_shape():
207
+ df = _make_df()
208
+ sig = DualMA(fast=5,slow=20).generate_signals(df)
209
+ assert len(sig) == len(df)
210
+ assert set(sig.unique()).issubset({-1,0,1})
211
+
212
+ def test_backtest_runs():
213
+ df = _make_df()
214
+ sig = DualMA().generate_signals(df)
215
+ res = backtest(df, sig)
216
+ assert "strategy" in res.columns
217
+ assert res.attrs.get("sharpe") is not None
218
+
219
+ if __name__ == "__main__":
220
+ test_signal_shape(); test_backtest_runs(); print("All tests passed.")
221
+ ''',
222
+ },
223
+ },
224
+ "analysis": {
225
+ "desc": "数据分析项目(数据加载 / 清洗 / 可视化 / 报告)",
226
+ "dirs": ["data/raw", "data/processed", "src", "output", "notebooks"],
227
+ "files": {
228
+ "requirements.txt": "pandas\nnumpy\nmatplotlib\nseaborn\nopenpyxl\nyfinance\n",
229
+ "src/loader.py": '''\
230
+ """数据加载工具:支持 CSV / Excel / yfinance。"""
231
+ import pandas as pd, pathlib, yfinance as yf
232
+
233
+ DATA = pathlib.Path(__file__).parent.parent / "data"
234
+
235
+ def load_csv(filename: str, **kw) -> pd.DataFrame:
236
+ return pd.read_csv(DATA / "raw" / filename, **kw)
237
+
238
+ def load_excel(filename: str, sheet=0, **kw) -> pd.DataFrame:
239
+ return pd.read_excel(DATA / "raw" / filename, sheet_name=sheet, **kw)
240
+
241
+ def load_stock(symbol: str, period: str = "1y") -> pd.DataFrame:
242
+ df = yf.Ticker(symbol).history(period=period, auto_adjust=True, progress=False)
243
+ return df[["Open","High","Low","Close","Volume"]]
244
+
245
+ def save_processed(df: pd.DataFrame, name: str) -> pathlib.Path:
246
+ out = DATA / "processed" / name
247
+ df.to_csv(out); return out
248
+ ''',
249
+ "src/analyzer.py": '''\
250
+ """常用分析函数:描述统计 / 相关性 / 滚动指标。"""
251
+ import pandas as pd, numpy as np
252
+
253
+ def describe_df(df: pd.DataFrame) -> pd.DataFrame:
254
+ return df.describe().round(4)
255
+
256
+ def correlation_matrix(df: pd.DataFrame) -> pd.DataFrame:
257
+ return df.corr().round(4)
258
+
259
+ def rolling_stats(series: pd.Series, window: int = 20) -> pd.DataFrame:
260
+ return pd.DataFrame({
261
+ "mean": series.rolling(window).mean(),
262
+ "std": series.rolling(window).std(),
263
+ "zscore": (series - series.rolling(window).mean()) / series.rolling(window).std(),
264
+ })
265
+
266
+ def annualized_return(series: pd.Series) -> float:
267
+ ret = series.pct_change().dropna()
268
+ return float((1 + ret.mean()) ** 252 - 1)
269
+
270
+ def max_drawdown(series: pd.Series) -> float:
271
+ return float((series / series.cummax() - 1).min())
272
+ ''',
273
+ "src/visualizer.py": '''\
274
+ """可视化工具:折线图 / 直方图 / 热力图。"""
275
+ import matplotlib
276
+ matplotlib.use("Agg")
277
+ import matplotlib.pyplot as plt
278
+ import seaborn as sns
279
+ import pandas as pd, pathlib
280
+
281
+ OUT = pathlib.Path(__file__).parent.parent / "output"
282
+ OUT.mkdir(exist_ok=True)
283
+ plt.style.use("dark_background")
284
+
285
+ def plot_series(series: pd.Series, title: str = "", filename: str = "plot.png") -> pathlib.Path:
286
+ fig, ax = plt.subplots(figsize=(11,4), facecolor="#0d1117")
287
+ ax.plot(series.index, series.values, color="#58a6ff", lw=1.2)
288
+ ax.set_title(title, color="#e6edf3"); ax.set_facecolor("#161b22")
289
+ for sp in ax.spines.values(): sp.set_color("#30363d")
290
+ ax.tick_params(colors="#8b949e")
291
+ out = OUT / filename
292
+ plt.savefig(out, dpi=130, bbox_inches="tight", facecolor=fig.get_facecolor())
293
+ plt.close(); return out
294
+
295
+ def plot_heatmap(df: pd.DataFrame, title: str = "Correlation", filename: str = "heatmap.png") -> pathlib.Path:
296
+ fig, ax = plt.subplots(figsize=(8,6), facecolor="#0d1117")
297
+ sns.heatmap(df, annot=True, fmt=".2f", cmap="RdYlGn", ax=ax,
298
+ linewidths=0.3, linecolor="#30363d", cbar_kws={"shrink":0.7})
299
+ ax.set_title(title, color="#e6edf3"); ax.tick_params(colors="#8b949e")
300
+ out = OUT / filename
301
+ plt.savefig(out, dpi=130, bbox_inches="tight", facecolor=fig.get_facecolor())
302
+ plt.close(); return out
303
+ ''',
304
+ "main.py": '''\
305
+ """数据分析项目入口示例。"""
306
+ import sys, pathlib
307
+ sys.path.insert(0, str(pathlib.Path(__file__).parent))
308
+ from src.loader import load_stock
309
+ from src.analyzer import describe_df, correlation_matrix, annualized_return, max_drawdown
310
+ from src.visualizer import plot_series
311
+
312
+ # 示例:分析多支股票
313
+ SYMBOLS = ["AAPL","MSFT","NVDA"]
314
+ closes = {}
315
+ for sym in SYMBOLS:
316
+ df = load_stock(sym, period="1y")
317
+ closes[sym] = df["Close"]
318
+ print(f"{sym}: 年化收益 {annualized_return(df['Close']):.2%} 最大回撤 {max_drawdown(df['Close']):.2%}")
319
+
320
+ import pandas as pd
321
+ closes_df = pd.DataFrame(closes)
322
+ print("\n相关性矩阵:")
323
+ print(correlation_matrix(closes_df))
324
+
325
+ plot_series(closes_df["AAPL"], "AAPL Close Price", "aapl_close.png")
326
+ print("图表已保存到 output/")
327
+ ''',
328
+ },
329
+ },
330
+ "fastapi": {
331
+ "desc": "FastAPI 金融数据 API 服务(行情 / 技术指标 / 基本面)",
332
+ "dirs": ["app/routers", "app/schemas", "app/services", "tests"],
333
+ "files": {
334
+ "requirements.txt": "fastapi\nuvicorn[standard]\nyfinance\nrequests\npandas\nnumpy\n",
335
+ "app/__init__.py": "",
336
+ "app/main.py": '''\
337
+ """FastAPI 主应用入口。"""
338
+ from fastapi import FastAPI
339
+ from app.routers import market, health
340
+
341
+ app = FastAPI(title="Aria Finance API", version="1.0.0",
342
+ description="金融行情与分析 REST API")
343
+
344
+ app.include_router(health.router)
345
+ app.include_router(market.router, prefix="/market", tags=["market"])
346
+
347
+ if __name__ == "__main__":
348
+ import uvicorn
349
+ uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True)
350
+ ''',
351
+ "app/routers/__init__.py": "",
352
+ "app/routers/health.py": '''\
353
+ from fastapi import APIRouter
354
+ from datetime import datetime
355
+
356
+ router = APIRouter()
357
+
358
+ @router.get("/health")
359
+ def health_check():
360
+ return {"status": "ok", "timestamp": datetime.now().isoformat()}
361
+ ''',
362
+ "app/routers/market.py": '''\
363
+ """行情路由:报价 / 历史 K 线 / 技术指标。"""
364
+ from fastapi import APIRouter, HTTPException, Query
365
+ from app.services.market_service import get_quote, get_history, get_technicals
366
+
367
+ router = APIRouter()
368
+
369
+ @router.get("/quote/{symbol}")
370
+ def quote(symbol: str):
371
+ data = get_quote(symbol.upper())
372
+ if not data:
373
+ raise HTTPException(status_code=404, detail=f"No data for {symbol}")
374
+ return data
375
+
376
+ @router.get("/history/{symbol}")
377
+ def history(symbol: str, period: str = Query("3mo", description="1mo/3mo/6mo/1y/2y")):
378
+ records = get_history(symbol.upper(), period)
379
+ return {"symbol": symbol.upper(), "period": period, "data": records}
380
+
381
+ @router.get("/technicals/{symbol}")
382
+ def technicals(symbol: str):
383
+ return get_technicals(symbol.upper())
384
+ ''',
385
+ "app/services/__init__.py": "",
386
+ "app/services/market_service.py": '''\
387
+ """市场数据服务层(基于 yfinance)。"""
388
+ import yfinance as yf
389
+ import pandas as pd, numpy as np
390
+ from typing import Optional
391
+
392
+ def get_quote(symbol: str) -> Optional[dict]:
393
+ try:
394
+ t = yf.Ticker(symbol)
395
+ info = t.info or {}
396
+ price = info.get("currentPrice") or info.get("regularMarketPrice")
397
+ if not price:
398
+ hist = t.history(period="1d", auto_adjust=True)
399
+ price = float(hist["Close"].iloc[-1]) if not hist.empty else None
400
+ return {
401
+ "symbol": symbol,
402
+ "price": price,
403
+ "prev_close": info.get("previousClose"),
404
+ "open": info.get("open"),
405
+ "day_high": info.get("dayHigh"),
406
+ "day_low": info.get("dayLow"),
407
+ "volume": info.get("volume"),
408
+ "market_cap": info.get("marketCap"),
409
+ "pe_ratio": info.get("trailingPE"),
410
+ "name": info.get("longName", symbol),
411
+ "currency": info.get("currency", "USD"),
412
+ }
413
+ except Exception:
414
+ return None
415
+
416
+ def get_history(symbol: str, period: str = "3mo") -> list:
417
+ try:
418
+ df = yf.Ticker(symbol).history(period=period, auto_adjust=True)
419
+ df = df.reset_index()
420
+ df["Date"] = df["Date"].dt.strftime("%Y-%m-%d")
421
+ return df[["Date","Open","High","Low","Close","Volume"]].round(4).to_dict(orient="records")
422
+ except Exception:
423
+ return []
424
+
425
+ def get_technicals(symbol: str) -> dict:
426
+ try:
427
+ df = yf.Ticker(symbol).history(period="6mo", auto_adjust=True)
428
+ close = df["Close"]
429
+ rsi_period = 14
430
+ delta = close.diff()
431
+ gain = delta.clip(lower=0).rolling(rsi_period).mean()
432
+ loss = (-delta.clip(upper=0)).rolling(rsi_period).mean()
433
+ rs = gain / loss
434
+ rsi = float((100 - 100 / (1 + rs)).iloc[-1])
435
+ ema12 = close.ewm(span=12).mean()
436
+ ema26 = close.ewm(span=26).mean()
437
+ macd = ema12 - ema26
438
+ signal = macd.ewm(span=9).mean()
439
+ return {
440
+ "symbol": symbol,
441
+ "rsi": round(rsi, 2),
442
+ "macd": round(float(macd.iloc[-1]), 4),
443
+ "macd_signal": round(float(signal.iloc[-1]), 4),
444
+ "ma20": round(float(close.rolling(20).mean().iloc[-1]), 2),
445
+ "ma60": round(float(close.rolling(60).mean().iloc[-1]), 2),
446
+ "bb_upper": round(float(close.rolling(20).mean().iloc[-1] + 2*close.rolling(20).std().iloc[-1]), 2),
447
+ "bb_lower": round(float(close.rolling(20).mean().iloc[-1] - 2*close.rolling(20).std().iloc[-1]), 2),
448
+ }
449
+ except Exception as e:
450
+ return {"error": str(e)}
451
+ ''',
452
+ "tests/test_api.py": '''\
453
+ """API 单元测试(不启动服务器)。"""
454
+ import sys, pathlib
455
+ sys.path.insert(0, str(pathlib.Path(__file__).parent.parent))
456
+ from fastapi.testclient import TestClient
457
+ from app.main import app
458
+
459
+ client = TestClient(app)
460
+
461
+ def test_health():
462
+ r = client.get("/health")
463
+ assert r.status_code == 200
464
+ assert r.json()["status"] == "ok"
465
+
466
+ def test_quote_valid():
467
+ r = client.get("/market/quote/AAPL")
468
+ assert r.status_code == 200
469
+ data = r.json()
470
+ assert "price" in data
471
+ assert data["symbol"] == "AAPL"
472
+
473
+ def test_history():
474
+ r = client.get("/market/history/AAPL?period=1mo")
475
+ assert r.status_code == 200
476
+ assert len(r.json()["data"]) > 0
477
+
478
+ if __name__ == "__main__":
479
+ test_health(); test_quote_valid(); test_history(); print("All tests passed.")
480
+ ''',
481
+ "run.py": '''\
482
+ """启动 FastAPI 开发服务器。"""
483
+ import uvicorn
484
+ uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True)
485
+ ''',
486
+ },
487
+ },
488
+ "dashboard": {
489
+ "desc": "Plotly Dash 交互式金融看板",
490
+ "dirs": ["assets", "components", "data"],
491
+ "files": {
492
+ "requirements.txt": "dash\nplotly\nyfinance\npandas\nnumpy\n",
493
+ "components/__init__.py": "",
494
+ "components/chart.py": '''\
495
+ """K线图 + 均线组件。"""
496
+ import yfinance as yf
497
+ import plotly.graph_objects as go
498
+ from plotly.subplots import make_subplots
499
+ import pandas as pd, numpy as np
500
+
501
+ def build_candlestick(symbol: str, period: str = "6mo") -> go.Figure:
502
+ df = yf.Ticker(symbol).history(period=period, auto_adjust=True)
503
+ df["MA20"] = df["Close"].rolling(20).mean()
504
+ df["MA60"] = df["Close"].rolling(60).mean()
505
+
506
+ fig = make_subplots(rows=2, cols=1, shared_xaxes=True,
507
+ row_heights=[0.75,0.25], vertical_spacing=0.03)
508
+ fig.add_trace(go.Candlestick(
509
+ x=df.index, open=df["Open"], high=df["High"],
510
+ low=df["Low"], close=df["Close"], name="K线",
511
+ increasing_line_color="#3fb950", decreasing_line_color="#f85149"
512
+ ), row=1, col=1)
513
+ fig.add_trace(go.Scatter(x=df.index, y=df["MA20"], name="MA20",
514
+ line=dict(color="#58a6ff", width=1)), row=1, col=1)
515
+ fig.add_trace(go.Scatter(x=df.index, y=df["MA60"], name="MA60",
516
+ line=dict(color="#e3b341", width=1)), row=1, col=1)
517
+ fig.add_trace(go.Bar(x=df.index, y=df["Volume"], name="成交量",
518
+ marker_color="#30363d"), row=2, col=1)
519
+ fig.update_layout(
520
+ template="plotly_dark", plot_bgcolor="#0d1117", paper_bgcolor="#010409",
521
+ title=f"{symbol} | {period}", height=600,
522
+ xaxis_rangeslider_visible=False,
523
+ legend=dict(bgcolor="#161b22", bordercolor="#30363d"),
524
+ )
525
+ return fig
526
+ ''',
527
+ "app.py": '''\
528
+ """Dash 交互式金融看板主应用。"""
529
+ import dash
530
+ from dash import dcc, html, Input, Output, State
531
+ from components.chart import build_candlestick
532
+ import yfinance as yf
533
+
534
+ app = dash.Dash(__name__, title="Aria Dashboard")
535
+
536
+ PERIODS = ["1mo","3mo","6mo","1y","2y","5y"]
537
+
538
+ app.layout = html.Div([
539
+ html.Div([
540
+ html.H1("Aria 金融看板", style={"color":"#e6edf3","fontSize":"22px","fontWeight":"700","margin":"0"}),
541
+ html.Div([
542
+ dcc.Input(id="symbol-input", value="AAPL", type="text", debounce=True,
543
+ placeholder="输入股票代码",
544
+ style={"background":"#161b22","color":"#e6edf3","border":"1px solid #30363d",
545
+ "borderRadius":"6px","padding":"8px 12px","width":"160px","marginRight":"8px"}),
546
+ dcc.Dropdown(id="period-dd", options=[{"label":p,"value":p} for p in PERIODS],
547
+ value="6mo", clearable=False,
548
+ style={"width":"100px","background":"#161b22","color":"#0d1117"}),
549
+ ], style={"display":"flex","alignItems":"center","gap":"8px"}),
550
+ ], style={"display":"flex","justifyContent":"space-between","alignItems":"center",
551
+ "padding":"16px 24px","borderBottom":"1px solid #21262d","background":"#010409"}),
552
+
553
+ dcc.Loading(dcc.Graph(id="main-chart", style={"height":"600px"}),
554
+ color="#58a6ff"),
555
+
556
+ html.Div(id="stats-row", style={"display":"flex","gap":"10px",
557
+ "padding":"12px 24px","background":"#010409"}),
558
+ ], style={"background":"#010409","minHeight":"100vh","fontFamily":"-apple-system,sans-serif"})
559
+
560
+ @app.callback(
561
+ [Output("main-chart","figure"), Output("stats-row","children")],
562
+ [Input("symbol-input","value"), Input("period-dd","value")],
563
+ prevent_initial_call=False,
564
+ )
565
+ def update_chart(symbol, period):
566
+ if not symbol:
567
+ return dash.no_update, dash.no_update
568
+ symbol = symbol.strip().upper()
569
+ fig = build_candlestick(symbol, period)
570
+ try:
571
+ info = yf.Ticker(symbol).info or {}
572
+ price = info.get("currentPrice") or info.get("regularMarketPrice","—")
573
+ mktcap = info.get("marketCap")
574
+ pe = info.get("trailingPE","—")
575
+ name = info.get("longName", symbol)
576
+ mktcap_s = f"${mktcap/1e9:.1f}B" if mktcap else "—"
577
+ except Exception:
578
+ name,price,mktcap_s,pe = symbol,"—","—","—"
579
+
580
+ def kpi(label, val):
581
+ return html.Div([
582
+ html.Div(label, style={"fontSize":"11px","color":"#8b949e","marginBottom":"4px"}),
583
+ html.Div(str(val), style={"fontSize":"18px","fontWeight":"700","color":"#e6edf3"}),
584
+ ], style={"background":"#161b22","border":"1px solid #30363d","borderRadius":"8px",
585
+ "padding":"12px 16px","minWidth":"120px"})
586
+
587
+ stats = [kpi("公司", name[:20]), kpi("现价", price),
588
+ kpi("市值", mktcap_s), kpi("市盈率", pe)]
589
+ return fig, stats
590
+
591
+ if __name__ == "__main__":
592
+ app.run(debug=True, host="0.0.0.0", port=8050)
593
+ ''',
594
+ "README.md": '''\
595
+ # Aria Dashboard
596
+
597
+ 交互式金融看板(Plotly Dash)。
598
+
599
+ ## 启动
600
+
601
+ ```bash
602
+ pip install -r requirements.txt
603
+ python app.py
604
+ ```
605
+
606
+ 浏览器访问 http://localhost:8050
607
+
608
+ ## 功能
609
+
610
+ - K线图 + 成交量(MA20/MA60)
611
+ - 多周期切换(1mo 到 5y)
612
+ - 基本面 KPI(市值、市盈率)
613
+ ''',
614
+ },
615
+ },
616
+ }
617
+