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
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""
|
|
2
|
+
datasources/sources/fred_source.py — FRED (Federal Reserve Economic Data)
|
|
3
|
+
=========================================================================
|
|
4
|
+
完全免费,无需 API key(可选 key 提升频率限制)。
|
|
5
|
+
覆盖:宏观经济指标、利率、CPI、GDP、就业数据等 800,000+ 系列。
|
|
6
|
+
|
|
7
|
+
配置(可选): FRED_API_KEY 环境变量 或 ~/.aria/.env
|
|
8
|
+
|
|
9
|
+
FRED series 示例:
|
|
10
|
+
DGS10 — 10年美国国债收益率
|
|
11
|
+
FEDFUNDS — 联邦基金利率
|
|
12
|
+
CPIAUCSL — 消费者价格指数
|
|
13
|
+
GDP — 美国 GDP
|
|
14
|
+
UNRATE — 失业率
|
|
15
|
+
SP500 — S&P 500 指数
|
|
16
|
+
DEXCNUS — 人民币/美元汇率
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
import os
|
|
23
|
+
from datetime import date, timedelta
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any, Dict, List, Optional
|
|
26
|
+
|
|
27
|
+
from ..base import BaseDataSource, HistoryResult, QuoteResult
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
_FRED_BASE = "https://fred.stlouisfed.org/graph/fredgraph.csv?id="
|
|
32
|
+
_FRED_API = "https://api.stlouisfed.org/fred"
|
|
33
|
+
|
|
34
|
+
# 常用宏观指标 symbol → FRED series id 映射
|
|
35
|
+
MACRO_ALIASES: Dict[str, str] = {
|
|
36
|
+
"US10Y": "DGS10",
|
|
37
|
+
"US2Y": "DGS2",
|
|
38
|
+
"US3M": "DTB3",
|
|
39
|
+
"FEDFUNDS": "FEDFUNDS",
|
|
40
|
+
"CPI": "CPIAUCSL",
|
|
41
|
+
"CPIYOY": "CPIAUCSL",
|
|
42
|
+
"PCE": "PCEPI",
|
|
43
|
+
"GDP": "GDP",
|
|
44
|
+
"UNRATE": "UNRATE",
|
|
45
|
+
"NFP": "PAYEMS",
|
|
46
|
+
"SP500": "SP500",
|
|
47
|
+
"NASDAQ": "NASDAQCOM",
|
|
48
|
+
"WILSHIRE": "WILL5000PR",
|
|
49
|
+
"USDINR": "DEXINUS",
|
|
50
|
+
"USDCNY": "DEXCHUS",
|
|
51
|
+
"USDEUR": "DEXUSEU",
|
|
52
|
+
"VIX": "VIXCLS",
|
|
53
|
+
"M2": "M2SL",
|
|
54
|
+
"MORTGAGE": "MORTGAGE30US",
|
|
55
|
+
"HOUSING": "HOUST",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _load_api_key() -> str:
|
|
60
|
+
key = os.getenv("FRED_API_KEY", "")
|
|
61
|
+
if not key:
|
|
62
|
+
for p in [Path.home() / ".aria" / ".env", Path.home() / ".arthera" / ".env"]:
|
|
63
|
+
if p.exists():
|
|
64
|
+
for line in p.read_text(encoding="utf-8").splitlines():
|
|
65
|
+
if line.startswith("FRED_API_KEY="):
|
|
66
|
+
key = line.split("=", 1)[1].strip()
|
|
67
|
+
break
|
|
68
|
+
if key:
|
|
69
|
+
break
|
|
70
|
+
if not key:
|
|
71
|
+
try:
|
|
72
|
+
import json as _json
|
|
73
|
+
_p = Path.home() / ".arthera" / "providers.json"
|
|
74
|
+
if _p.exists():
|
|
75
|
+
_raw = _json.loads(_p.read_text(encoding="utf-8"))
|
|
76
|
+
key = _raw.get("data", {}).get("fred", {}).get("api_key", "")
|
|
77
|
+
except Exception:
|
|
78
|
+
pass
|
|
79
|
+
return key
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class FREDSource(BaseDataSource):
|
|
83
|
+
"""
|
|
84
|
+
Federal Reserve Economic Data — 宏观经济数据源。
|
|
85
|
+
无需 key 即可使用 CSV 下载接口;有 key 可使用 JSON API 获得更多元数据。
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
name = "fred"
|
|
89
|
+
markets = ["us", "macro"]
|
|
90
|
+
requires_key = False
|
|
91
|
+
|
|
92
|
+
def __init__(self, config=None):
|
|
93
|
+
super().__init__(config)
|
|
94
|
+
self._api_key = _load_api_key()
|
|
95
|
+
|
|
96
|
+
def is_configured(self) -> bool:
|
|
97
|
+
return True # 无需 key,免费开放
|
|
98
|
+
|
|
99
|
+
def supports(self, symbol: str) -> bool:
|
|
100
|
+
s = symbol.upper()
|
|
101
|
+
return s in MACRO_ALIASES or s in MACRO_ALIASES.values()
|
|
102
|
+
|
|
103
|
+
def _resolve_series(self, symbol: str) -> str:
|
|
104
|
+
s = symbol.upper()
|
|
105
|
+
return MACRO_ALIASES.get(s, s)
|
|
106
|
+
|
|
107
|
+
def quote(self, symbol: str) -> Optional[QuoteResult]:
|
|
108
|
+
try:
|
|
109
|
+
h = self.history(symbol, days=30)
|
|
110
|
+
if h is None or h.data is None or h.data.empty:
|
|
111
|
+
return None
|
|
112
|
+
last = h.data.iloc[-1]
|
|
113
|
+
val = float(last.get("close", last.iloc[0]))
|
|
114
|
+
series_id = self._resolve_series(symbol)
|
|
115
|
+
return QuoteResult(
|
|
116
|
+
symbol = symbol,
|
|
117
|
+
name = f"FRED:{series_id}",
|
|
118
|
+
price = val,
|
|
119
|
+
currency = "USD",
|
|
120
|
+
market = "macro",
|
|
121
|
+
source = self.name,
|
|
122
|
+
timestamp = str(h.data.index[-1].date()),
|
|
123
|
+
)
|
|
124
|
+
except Exception as e:
|
|
125
|
+
logger.debug(f"[fred] quote {symbol} 失败: {e}")
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
def history(
|
|
129
|
+
self,
|
|
130
|
+
symbol: str,
|
|
131
|
+
days: int = 365,
|
|
132
|
+
interval: str = "1d",
|
|
133
|
+
_timeout: int = 12,
|
|
134
|
+
) -> Optional[HistoryResult]:
|
|
135
|
+
try:
|
|
136
|
+
import pandas as pd
|
|
137
|
+
import urllib.request, json
|
|
138
|
+
|
|
139
|
+
series_id = self._resolve_series(symbol)
|
|
140
|
+
start = (date.today() - timedelta(days=days)).strftime("%Y-%m-%d")
|
|
141
|
+
end = date.today().strftime("%Y-%m-%d")
|
|
142
|
+
|
|
143
|
+
def _parse_api(url: str) -> Optional[pd.DataFrame]:
|
|
144
|
+
req = urllib.request.Request(url, headers={"User-Agent": "aria-code/1.0"})
|
|
145
|
+
with urllib.request.urlopen(req, timeout=_timeout) as resp:
|
|
146
|
+
data = json.loads(resp.read())
|
|
147
|
+
obs = data.get("observations", [])
|
|
148
|
+
rows = []
|
|
149
|
+
for o in obs:
|
|
150
|
+
try:
|
|
151
|
+
rows.append({"date": o["date"], "close": float(o["value"])})
|
|
152
|
+
except (ValueError, KeyError):
|
|
153
|
+
pass
|
|
154
|
+
if not rows:
|
|
155
|
+
return None
|
|
156
|
+
_df = pd.DataFrame(rows)
|
|
157
|
+
_df["date"] = pd.to_datetime(_df["date"])
|
|
158
|
+
return _df.set_index("date").sort_index()
|
|
159
|
+
|
|
160
|
+
def _parse_csv(url: str) -> Optional[pd.DataFrame]:
|
|
161
|
+
req = urllib.request.Request(url, headers={"User-Agent": "aria-code/1.0"})
|
|
162
|
+
with urllib.request.urlopen(req, timeout=_timeout) as resp:
|
|
163
|
+
_df = pd.read_csv(resp, parse_dates=["DATE"], index_col="DATE")
|
|
164
|
+
_df.columns = ["close"]
|
|
165
|
+
_df = _df.replace(".", float("nan")).dropna()
|
|
166
|
+
_df["close"] = _df["close"].astype(float)
|
|
167
|
+
return _df if not _df.empty else None
|
|
168
|
+
|
|
169
|
+
df = None
|
|
170
|
+
|
|
171
|
+
# 优先使用 JSON API(有 key 速率更高)
|
|
172
|
+
if self._api_key:
|
|
173
|
+
api_url = (
|
|
174
|
+
f"{_FRED_API}/series/observations?series_id={series_id}"
|
|
175
|
+
f"&observation_start={start}&observation_end={end}"
|
|
176
|
+
f"&api_key={self._api_key}&file_type=json"
|
|
177
|
+
)
|
|
178
|
+
try:
|
|
179
|
+
df = _parse_api(api_url)
|
|
180
|
+
except Exception as _e1:
|
|
181
|
+
logger.debug(f"[fred] JSON API 失败,尝试 CSV: {_e1}")
|
|
182
|
+
|
|
183
|
+
# 回落:免费 CSV 下载接口(无需 key,但国内可能超时)
|
|
184
|
+
if df is None:
|
|
185
|
+
csv_url = (
|
|
186
|
+
f"{_FRED_BASE}{series_id}"
|
|
187
|
+
f"&cosd={start}&coed={end}"
|
|
188
|
+
)
|
|
189
|
+
try:
|
|
190
|
+
df = _parse_csv(csv_url)
|
|
191
|
+
except Exception as _e2:
|
|
192
|
+
logger.debug(f"[fred] CSV 也失败: {_e2}")
|
|
193
|
+
|
|
194
|
+
if df is None:
|
|
195
|
+
return None
|
|
196
|
+
return HistoryResult(symbol=symbol, data=df, source=self.name, interval="1d")
|
|
197
|
+
except Exception as e:
|
|
198
|
+
logger.debug(f"[fred] history {symbol} 失败: {e}")
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
def search_series(self, query: str, limit: int = 10) -> List[Dict[str, Any]]:
|
|
202
|
+
"""用关键词搜索 FRED 系列(需要 API key)。"""
|
|
203
|
+
if not self._api_key:
|
|
204
|
+
return [{"error": "需要 FRED_API_KEY 才能搜索系列"}]
|
|
205
|
+
try:
|
|
206
|
+
import urllib.request, json, urllib.parse
|
|
207
|
+
q = urllib.parse.quote(query)
|
|
208
|
+
url = (f"{_FRED_API}/series/search?search_text={q}"
|
|
209
|
+
f"&limit={limit}&api_key={self._api_key}&file_type=json")
|
|
210
|
+
req = urllib.request.Request(url, headers={"User-Agent": "aria-code/1.0"})
|
|
211
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
212
|
+
data = json.loads(resp.read())
|
|
213
|
+
return [
|
|
214
|
+
{"id": s["id"], "title": s["title"], "frequency": s.get("frequency_short"),
|
|
215
|
+
"units": s.get("units_short"), "last_updated": s.get("last_updated")}
|
|
216
|
+
for s in data.get("seriess", [])
|
|
217
|
+
]
|
|
218
|
+
except Exception as e:
|
|
219
|
+
return [{"error": str(e)}]
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""
|
|
2
|
+
datasources/sources/tushare_source.py — Tushare A股数据源
|
|
3
|
+
==========================================================
|
|
4
|
+
需要 TUSHARE_TOKEN 环境变量(已在 .env 中配置)。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
from datetime import date, timedelta
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
from ..base import BaseDataSource, FundamentalsResult, HistoryResult, QuoteResult
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _load_token() -> str:
|
|
21
|
+
token = os.getenv("TUSHARE_TOKEN", "")
|
|
22
|
+
if not token:
|
|
23
|
+
env_paths = [
|
|
24
|
+
Path.cwd() / ".env",
|
|
25
|
+
Path.cwd().parent / ".env",
|
|
26
|
+
Path.home() / ".aria" / ".env",
|
|
27
|
+
Path.home() / ".arthera" / ".env",
|
|
28
|
+
]
|
|
29
|
+
for p in env_paths:
|
|
30
|
+
if p.exists():
|
|
31
|
+
for line in p.read_text(encoding="utf-8").splitlines():
|
|
32
|
+
if line.startswith("TUSHARE_TOKEN="):
|
|
33
|
+
token = line.split("=", 1)[1].strip()
|
|
34
|
+
break
|
|
35
|
+
if token:
|
|
36
|
+
break
|
|
37
|
+
return token
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TushareSource(BaseDataSource):
|
|
41
|
+
|
|
42
|
+
name = "tushare"
|
|
43
|
+
markets = ["a_share"]
|
|
44
|
+
requires_key = True
|
|
45
|
+
|
|
46
|
+
def __init__(self, config=None):
|
|
47
|
+
super().__init__(config)
|
|
48
|
+
self._token = _load_token()
|
|
49
|
+
self._pro = None
|
|
50
|
+
|
|
51
|
+
def is_configured(self) -> bool:
|
|
52
|
+
return bool(self._token)
|
|
53
|
+
|
|
54
|
+
def _get_pro(self):
|
|
55
|
+
if self._pro is None:
|
|
56
|
+
import tushare as ts
|
|
57
|
+
ts.set_token(self._token)
|
|
58
|
+
self._pro = ts.pro_api()
|
|
59
|
+
return self._pro
|
|
60
|
+
|
|
61
|
+
def _to_ts_code(self, symbol: str) -> str:
|
|
62
|
+
s = symbol.upper().replace("SH","").replace("SZ","").replace(".","").strip()
|
|
63
|
+
code = s[:6]
|
|
64
|
+
return f"{code}.SH" if code.startswith(("6","9")) else f"{code}.SZ"
|
|
65
|
+
|
|
66
|
+
def quote(self, symbol: str) -> Optional[QuoteResult]:
|
|
67
|
+
"""
|
|
68
|
+
使用 pro.daily 获取最近交易日收盘价(不调用 daily_basic 避免限频)。
|
|
69
|
+
PE/PB 留给 fundamentals() 单独获取。
|
|
70
|
+
"""
|
|
71
|
+
try:
|
|
72
|
+
import time as _t
|
|
73
|
+
pro = self._get_pro()
|
|
74
|
+
ts_code = self._to_ts_code(symbol)
|
|
75
|
+
end = date.today().strftime("%Y%m%d")
|
|
76
|
+
start = (date.today() - timedelta(days=10)).strftime("%Y%m%d")
|
|
77
|
+
df = pro.daily(ts_code=ts_code, start_date=start, end_date=end)
|
|
78
|
+
_t.sleep(0.35)
|
|
79
|
+
if df is None or df.empty:
|
|
80
|
+
return None
|
|
81
|
+
# trade_date 格式 YYYYMMDD,字符串排序等价于日期排序
|
|
82
|
+
latest = df.sort_values("trade_date", ascending=False).iloc[0]
|
|
83
|
+
price = float(latest.get("close", 0))
|
|
84
|
+
prev = float(latest.get("pre_close", price))
|
|
85
|
+
return QuoteResult(
|
|
86
|
+
symbol = symbol,
|
|
87
|
+
price = price,
|
|
88
|
+
change = round(price - prev, 4),
|
|
89
|
+
change_pct = float(latest.get("pct_chg", 0)),
|
|
90
|
+
volume = float(latest.get("vol", 0)),
|
|
91
|
+
currency = "CNY",
|
|
92
|
+
market = "a_share",
|
|
93
|
+
source = self.name,
|
|
94
|
+
timestamp = str(latest.get("trade_date", "")),
|
|
95
|
+
)
|
|
96
|
+
except Exception as e:
|
|
97
|
+
logger.debug(f"[tushare] quote {symbol} 失败: {e}")
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
def history(self, symbol: str, days: int = 90, interval: str = "1d") -> Optional[HistoryResult]:
|
|
101
|
+
try:
|
|
102
|
+
import time as _t, pandas as pd
|
|
103
|
+
pro = self._get_pro()
|
|
104
|
+
ts_code = self._to_ts_code(symbol)
|
|
105
|
+
end = date.today().strftime("%Y%m%d")
|
|
106
|
+
start = (date.today() - timedelta(days=days + 15)).strftime("%Y%m%d")
|
|
107
|
+
df = pro.daily(ts_code=ts_code, start_date=start, end_date=end)
|
|
108
|
+
_t.sleep(0.4)
|
|
109
|
+
if df is None or df.empty:
|
|
110
|
+
return None
|
|
111
|
+
df["trade_date"] = pd.to_datetime(df["trade_date"])
|
|
112
|
+
df = df.rename(columns={"trade_date": "date", "vol": "volume"})
|
|
113
|
+
df = df.set_index("date").sort_index()
|
|
114
|
+
return HistoryResult(symbol=symbol, data=df, source=self.name, interval=interval)
|
|
115
|
+
except Exception as e:
|
|
116
|
+
logger.debug(f"[tushare] history {symbol} 失败: {e}")
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
def fundamentals(self, symbol: str) -> Optional[FundamentalsResult]:
|
|
120
|
+
try:
|
|
121
|
+
import time as _t
|
|
122
|
+
pro = self._get_pro()
|
|
123
|
+
ts_code = self._to_ts_code(symbol)
|
|
124
|
+
for period in ["20241231", "20231231"]:
|
|
125
|
+
df = pro.fina_indicator(
|
|
126
|
+
ts_code=ts_code, period=period,
|
|
127
|
+
fields="ts_code,roe,netprofit_yoy,revenue_yoy"
|
|
128
|
+
)
|
|
129
|
+
_t.sleep(0.4)
|
|
130
|
+
if df is not None and not df.empty:
|
|
131
|
+
r = df.iloc[0]
|
|
132
|
+
return FundamentalsResult(
|
|
133
|
+
symbol = symbol,
|
|
134
|
+
roe = float(r.get("roe", 0) or 0),
|
|
135
|
+
revenue_growth = float(r.get("revenue_yoy", 0) or 0),
|
|
136
|
+
net_profit_growth = float(r.get("netprofit_yoy", 0) or 0),
|
|
137
|
+
source = self.name,
|
|
138
|
+
)
|
|
139
|
+
except Exception as e:
|
|
140
|
+
logger.debug(f"[tushare] fundamentals {symbol} 失败: {e}")
|
|
141
|
+
return None
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""
|
|
2
|
+
datasources/sources/web_scraper_source.py — 公开财务数据网络爬虫
|
|
3
|
+
================================================================
|
|
4
|
+
爬取完全公开的金融数据网站,无需账号或 API key。
|
|
5
|
+
所有请求遵守 robots.txt 和频率限制。
|
|
6
|
+
|
|
7
|
+
覆盖:
|
|
8
|
+
- Macrotrends — 历史财务数据(收入/利润/现金流/估值)
|
|
9
|
+
- Wisesheets — 免费财务摘要
|
|
10
|
+
- 东方财富 — A股公告/研报摘要
|
|
11
|
+
- 同花顺 — A股财务数据
|
|
12
|
+
- Yahoo Finance — 基本面摘要(HTML解析)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
import re
|
|
19
|
+
import time
|
|
20
|
+
import urllib.parse
|
|
21
|
+
import urllib.request
|
|
22
|
+
from typing import Any, Dict, List, Optional
|
|
23
|
+
|
|
24
|
+
from ..base import BaseDataSource, FundamentalsResult, HistoryResult, QuoteResult
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
_DEFAULT_HEADERS = {
|
|
29
|
+
"User-Agent": (
|
|
30
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
|
|
31
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
|
32
|
+
"Chrome/120.0.0.0 Safari/537.36"
|
|
33
|
+
),
|
|
34
|
+
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
|
35
|
+
"Accept": "text/html,application/xhtml+xml,application/json,*/*;q=0.8",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _fetch_html(url: str, timeout: int = 12, encoding: str = "utf-8") -> Optional[str]:
|
|
40
|
+
try:
|
|
41
|
+
req = urllib.request.Request(url, headers=_DEFAULT_HEADERS)
|
|
42
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
43
|
+
raw = resp.read()
|
|
44
|
+
try:
|
|
45
|
+
return raw.decode(encoding)
|
|
46
|
+
except UnicodeDecodeError:
|
|
47
|
+
return raw.decode("gbk", errors="replace")
|
|
48
|
+
except Exception as e:
|
|
49
|
+
logger.debug(f"[web_scraper] fetch {url[:80]} 失败: {e}")
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _fetch_json(url: str, timeout: int = 12, extra_headers: dict = None) -> Optional[Any]:
|
|
54
|
+
import json
|
|
55
|
+
try:
|
|
56
|
+
headers = {**_DEFAULT_HEADERS, **(extra_headers or {})}
|
|
57
|
+
req = urllib.request.Request(url, headers=headers)
|
|
58
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
59
|
+
return json.loads(resp.read())
|
|
60
|
+
except Exception as e:
|
|
61
|
+
logger.debug(f"[web_scraper] json {url[:80]} 失败: {e}")
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ─── 东方财富 A股公告爬虫 ────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
def scrape_eastmoney_announcements(symbol: str, count: int = 10) -> List[Dict]:
|
|
68
|
+
"""
|
|
69
|
+
爬取东方财富 A股公告列表(免费公开页面)。
|
|
70
|
+
symbol: 6位A股代码(如 '600519')
|
|
71
|
+
"""
|
|
72
|
+
s = symbol.replace(".SS", "").replace(".SZ", "").zfill(6)
|
|
73
|
+
url = (
|
|
74
|
+
f"https://np-anotice-stock.eastmoney.com/api/security/ann?"
|
|
75
|
+
f"sr=-1&page_size={count}&page_index=1&ann_type=A&client_source=web"
|
|
76
|
+
f"&stock_list={s}&f_node=0&s_node=0"
|
|
77
|
+
)
|
|
78
|
+
data = _fetch_json(url, extra_headers={"Referer": "https://www.eastmoney.com/"})
|
|
79
|
+
if not data or "data" not in data:
|
|
80
|
+
return []
|
|
81
|
+
anns = data["data"].get("list", [])
|
|
82
|
+
result = []
|
|
83
|
+
for a in anns[:count]:
|
|
84
|
+
result.append({
|
|
85
|
+
"title": a.get("notice_title", ""),
|
|
86
|
+
"date": a.get("notice_date", ""),
|
|
87
|
+
"type": a.get("notice_type", ""),
|
|
88
|
+
"url": f"https://data.eastmoney.com/notices/detail/{s}/{a.get('art_code','')}.html",
|
|
89
|
+
"source": "eastmoney",
|
|
90
|
+
})
|
|
91
|
+
return result
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def scrape_eastmoney_news(symbol: str, count: int = 10) -> List[Dict]:
|
|
95
|
+
"""爬取东方财富个股新闻。"""
|
|
96
|
+
s = symbol.replace(".SS", "").replace(".SZ", "").zfill(6)
|
|
97
|
+
url = (
|
|
98
|
+
f"https://newsapi.eastmoney.com/kuaixun/v1/getlist_101_ajaxResult_50_{s},,50,1.html"
|
|
99
|
+
)
|
|
100
|
+
html = _fetch_html(url)
|
|
101
|
+
if not html:
|
|
102
|
+
return []
|
|
103
|
+
# 尝试从 JSON 响应中解析
|
|
104
|
+
try:
|
|
105
|
+
import json
|
|
106
|
+
data = json.loads(html)
|
|
107
|
+
items = data.get("LiveList", [])[:count]
|
|
108
|
+
return [{
|
|
109
|
+
"title": i.get("title", ""),
|
|
110
|
+
"time": i.get("showtime", ""),
|
|
111
|
+
"source": i.get("medianame", ""),
|
|
112
|
+
"url": i.get("url", ""),
|
|
113
|
+
} for i in items]
|
|
114
|
+
except Exception:
|
|
115
|
+
pass
|
|
116
|
+
return []
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ─── Macrotrends 历史财务数据爬虫 ────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
def scrape_macrotrends_revenue(symbol: str) -> Optional[List[Dict]]:
|
|
122
|
+
"""
|
|
123
|
+
从 Macrotrends 爬取年度营收历史(免费公开页面,无需登录)。
|
|
124
|
+
注意:依赖页面结构,如网站改版可能失效。
|
|
125
|
+
"""
|
|
126
|
+
slug_map = {"AAPL": "apple", "MSFT": "microsoft", "GOOGL": "alphabet",
|
|
127
|
+
"AMZN": "amazon", "META": "meta-platforms", "TSLA": "tesla",
|
|
128
|
+
"NVDA": "nvidia", "JPM": "jpmorgan-chase", "V": "visa"}
|
|
129
|
+
slug = slug_map.get(symbol.upper())
|
|
130
|
+
if not slug:
|
|
131
|
+
slug = symbol.lower()
|
|
132
|
+
|
|
133
|
+
url = f"https://www.macrotrends.net/stocks/charts/{symbol.upper()}/{slug}/revenue"
|
|
134
|
+
html = _fetch_html(url)
|
|
135
|
+
if not html:
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
# 提取嵌入的 JSON 数据
|
|
139
|
+
match = re.search(r'var originalData\s*=\s*(\[.*?\]);', html, re.DOTALL)
|
|
140
|
+
if not match:
|
|
141
|
+
return None
|
|
142
|
+
try:
|
|
143
|
+
import json
|
|
144
|
+
rows = json.loads(match.group(1))
|
|
145
|
+
return [
|
|
146
|
+
{"date": r.get("date", ""), "revenue_usd": r.get("v1", 0)}
|
|
147
|
+
for r in rows
|
|
148
|
+
][-20:] # 最近20个数据点
|
|
149
|
+
except Exception:
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ─── SSE/SZSE 交易所公开数据爬虫 ─────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
def scrape_sse_financials(symbol: str) -> Optional[Dict]:
|
|
156
|
+
"""
|
|
157
|
+
爬取上交所公开的公司财务摘要(适用于 6 开头的A股)。
|
|
158
|
+
"""
|
|
159
|
+
s = symbol.replace(".SS", "").zfill(6)
|
|
160
|
+
if not s.startswith(("6", "9")):
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
url = (
|
|
164
|
+
f"https://query.sse.com.cn/commonSoaQuery.do?"
|
|
165
|
+
f"isPagination=false&sqlId=COMMON_SSE_CP_GPLB_GPXG_JBXX_L&stockCode={s}"
|
|
166
|
+
)
|
|
167
|
+
data = _fetch_json(url, extra_headers={
|
|
168
|
+
"Referer": "https://www.sse.com.cn/",
|
|
169
|
+
"Host": "query.sse.com.cn",
|
|
170
|
+
})
|
|
171
|
+
if not data or "result" not in data:
|
|
172
|
+
return None
|
|
173
|
+
r = data["result"]
|
|
174
|
+
if isinstance(r, list) and r:
|
|
175
|
+
r = r[0]
|
|
176
|
+
if not isinstance(r, dict):
|
|
177
|
+
return None
|
|
178
|
+
return {
|
|
179
|
+
"symbol": s,
|
|
180
|
+
"name": r.get("FULL_NAME", ""),
|
|
181
|
+
"industry": r.get("INDUSTRY", ""),
|
|
182
|
+
"listing_date": r.get("LISTING_DATE", ""),
|
|
183
|
+
"total_shares": r.get("TOTAL_SHARES", ""),
|
|
184
|
+
"source": "sse",
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def scrape_szse_financials(symbol: str) -> Optional[Dict]:
|
|
189
|
+
"""爬取深交所公开的公司基本信息(适用于 0/3 开头的A股)。"""
|
|
190
|
+
s = symbol.replace(".SZ", "").zfill(6)
|
|
191
|
+
if not s.startswith(("0", "3", "2")):
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
url = (
|
|
195
|
+
f"https://www.szse.cn/api/report/show/nature/detail?"
|
|
196
|
+
f"id=&"
|
|
197
|
+
)
|
|
198
|
+
# 深交所 API
|
|
199
|
+
data = _fetch_json(
|
|
200
|
+
f"https://www.szse.cn/api/market/smstock/list?random=0.1&tab2PAGENUM=1"
|
|
201
|
+
f"&tab2PAGENO=1&tab2COUNT=1&tab2SORTTAB=tab2&tab2SORTKEY=",
|
|
202
|
+
extra_headers={"Referer": "https://www.szse.cn/"},
|
|
203
|
+
)
|
|
204
|
+
return None # 深交所API结构复杂,返回None让上层fallback
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# ─── 统一爬虫接口 ─────────────────────────────────────────────────────────────
|
|
208
|
+
|
|
209
|
+
class WebScraperSource(BaseDataSource):
|
|
210
|
+
"""
|
|
211
|
+
公开网页数据爬虫 — 汇总多个免费公开金融数据网站。
|
|
212
|
+
实现 BaseDataSource 接口以便接入路由器;quote/history 委托给主数据源,
|
|
213
|
+
fundamentals() 提供爬取的补充数据(公告、交易所基本信息等)。
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
name = "web_scraper"
|
|
217
|
+
markets = ["a_share", "us"]
|
|
218
|
+
requires_key = False
|
|
219
|
+
|
|
220
|
+
def is_configured(self) -> bool:
|
|
221
|
+
return True
|
|
222
|
+
|
|
223
|
+
def quote(self, symbol: str) -> Optional[QuoteResult]:
|
|
224
|
+
return None # 不提供实时行情,由 akshare/yfinance 负责
|
|
225
|
+
|
|
226
|
+
def fundamentals(self, symbol: str) -> Optional[FundamentalsResult]:
|
|
227
|
+
"""从交易所公开页面补充基本信息(仅 A 股)。"""
|
|
228
|
+
s = symbol.replace(".SS", "").replace(".SZ", "").zfill(6)
|
|
229
|
+
info = None
|
|
230
|
+
if s.startswith(("6", "9")):
|
|
231
|
+
info = scrape_sse_financials(s)
|
|
232
|
+
elif s.startswith(("0", "3")):
|
|
233
|
+
info = scrape_szse_financials(s)
|
|
234
|
+
if not info:
|
|
235
|
+
return None
|
|
236
|
+
return FundamentalsResult(
|
|
237
|
+
symbol = symbol,
|
|
238
|
+
source = self.name,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# ── 扩展方法(供上层直接调用) ─────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
def get_announcements(self, symbol: str, count: int = 10) -> List[Dict]:
|
|
244
|
+
"""A股公告列表(东方财富)。"""
|
|
245
|
+
return scrape_eastmoney_announcements(symbol, count)
|
|
246
|
+
|
|
247
|
+
def get_news(self, symbol: str, count: int = 10) -> List[Dict]:
|
|
248
|
+
"""个股新闻。"""
|
|
249
|
+
return scrape_eastmoney_news(symbol, count)
|
|
250
|
+
|
|
251
|
+
def get_historical_revenue(self, symbol: str) -> Optional[List[Dict]]:
|
|
252
|
+
"""美股历史营收(Macrotrends)。"""
|
|
253
|
+
return scrape_macrotrends_revenue(symbol)
|
|
254
|
+
|
|
255
|
+
def get_exchange_info(self, symbol: str) -> Optional[Dict]:
|
|
256
|
+
"""A股交易所公开信息。"""
|
|
257
|
+
s = symbol.replace(".SS", "").replace(".SZ", "").zfill(6)
|
|
258
|
+
if s.startswith(("6", "9")):
|
|
259
|
+
return scrape_sse_financials(s)
|
|
260
|
+
elif s.startswith(("0", "3")):
|
|
261
|
+
return scrape_szse_financials(s)
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
def bulk_scrape(self, symbols: List[str], data_type: str = "announcements") -> Dict[str, Any]:
|
|
265
|
+
"""批量爬取,带速率限制(每次请求间隔 1 秒)。"""
|
|
266
|
+
results = {}
|
|
267
|
+
for sym in symbols:
|
|
268
|
+
try:
|
|
269
|
+
if data_type == "announcements":
|
|
270
|
+
results[sym] = self.get_announcements(sym)
|
|
271
|
+
elif data_type == "news":
|
|
272
|
+
results[sym] = self.get_news(sym)
|
|
273
|
+
elif data_type == "revenue":
|
|
274
|
+
results[sym] = self.get_historical_revenue(sym)
|
|
275
|
+
time.sleep(1.0)
|
|
276
|
+
except Exception as e:
|
|
277
|
+
results[sym] = {"error": str(e)}
|
|
278
|
+
return results
|