From f8a47cb473c6e83ddc3d38d011ecd046ba550963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E9=92=B0?= Date: Wed, 8 Jul 2026 11:26:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=85=A8=E9=9D=A2SSH=E4=B8=AD=E8=BD=AC?= =?UTF-8?q?=E4=BB=A3=E7=90=86=20+=20mimo-v2.5-pro=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Yahoo Finance: 跳过本地限流,直接走SSH中转 - StockTwits: 直连403时自动fallback到SSH中转 - Reddit: 网络不可达时自动fallback到SSH中转 - Polymarket: 网络不可达时自动fallback到SSH中转 - FRED: 配置API Key (825283926dd016ea66e96cc919e047da) - .env: 新增FRED_API_KEY和OPENAI_COMPATIBLE_API_KEY - test_xiaomi.py: 小米mimo-v2.5-pro测试脚本 --- test_xiaomi.py | 31 +++++++++++++++++ tradingagents/dataflows/polymarket.py | 31 ++++++++++++++--- tradingagents/dataflows/reddit.py | 10 ++++-- tradingagents/dataflows/stockstats_utils.py | 38 ++++++--------------- tradingagents/dataflows/stocktwits.py | 10 ++++-- tradingagents/dataflows/y_finance.py | 22 +++++------- 6 files changed, 93 insertions(+), 49 deletions(-) create mode 100644 test_xiaomi.py diff --git a/test_xiaomi.py b/test_xiaomi.py new file mode 100644 index 0000000..67f415a --- /dev/null +++ b/test_xiaomi.py @@ -0,0 +1,31 @@ +"""使用小米 mimo-v2.5-pro 模型运行 TradingAgents 示例""" +from tradingagents.graph.trading_graph import TradingAgentsGraph +from tradingagents.default_config import DEFAULT_CONFIG + +config = DEFAULT_CONFIG.copy() + +# 使用 openai_compatible 对接小米 API +config["llm_provider"] = "openai_compatible" +config["backend_url"] = "https://token-plan-cn.xiaomimimo.com/v1" +config["deep_think_llm"] = "mimo-v2.5-pro" +config["quick_think_llm"] = "mimo-v2.5-pro" + +# 设置 API Key(也可通过环境变量 OPENAI_COMPATIBLE_API_KEY) +import os +os.environ["OPENAI_COMPATIBLE_API_KEY"] = "tp-ce6eaqg1gh1uev46i4oexh4sahloyfo2hfpxuc2n9m7yl9dd" + +# 减少辩论轮数加速测试 +config["max_debate_rounds"] = 1 +config["max_risk_discuss_rounds"] = 1 + +print("配置完成,开始运行 TradingAgents...") +print(f"Provider: {config['llm_provider']}") +print(f"Backend: {config['backend_url']}") +print(f"Model: {config['deep_think_llm']}") + +ta = TradingAgentsGraph(debug=True, config=config) + +# 测试分析茅台(A股) +_, decision = ta.propagate("600519.SS", "2026-01-15") +print("\n=== 交易决策 ===") +print(decision) diff --git a/tradingagents/dataflows/polymarket.py b/tradingagents/dataflows/polymarket.py index b76dfe7..a6e52d0 100644 --- a/tradingagents/dataflows/polymarket.py +++ b/tradingagents/dataflows/polymarket.py @@ -13,8 +13,12 @@ import json import logging from datetime import datetime, timezone +import subprocess + import requests +from .stockstats_utils import _get_ssh_config + logger = logging.getLogger(__name__) GAMMA_BASE = "https://gamma-api.polymarket.com" @@ -27,11 +31,28 @@ DEFAULT_LIMIT = 6 def _request(path: str, params: dict) -> dict: - response = requests.get( - f"{GAMMA_BASE}/{path}", params=params, timeout=REQUEST_TIMEOUT - ) - response.raise_for_status() - return response.json() + url = f"{GAMMA_BASE}/{path}" + try: + response = requests.get(url, params=params, timeout=REQUEST_TIMEOUT) + response.raise_for_status() + return response.json() + except requests.RequestException: + # Fallback: fetch through SSH proxy + logger.info("Polymarket direct failed, trying SSH proxy for %s...", path) + query = "&".join(f"{k}={v}" for k, v in params.items()) + full_url = f"{url}?{query}" + host, port, password = _get_ssh_config() + cmd = [ + "sshpass", "-p", password, + "ssh", "-o", "StrictHostKeyChecking=no", + "-o", "ConnectTimeout=15", + "-p", str(port), f"root@{host}", + f'curl -s "{full_url}"' + ] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=REQUEST_TIMEOUT) + if result.returncode != 0: + raise requests.RequestException(f"SSH proxy failed: {result.stderr}") + return json.loads(result.stdout) def _parse_json_list(value) -> list: diff --git a/tradingagents/dataflows/reddit.py b/tradingagents/dataflows/reddit.py index edb4aed..d2f0962 100644 --- a/tradingagents/dataflows/reddit.py +++ b/tradingagents/dataflows/reddit.py @@ -31,6 +31,7 @@ from urllib.parse import urlencode from urllib.request import Request, urlopen from .symbol_utils import crypto_base +from .stockstats_utils import _fetch_via_ssh logger = logging.getLogger(__name__) @@ -123,8 +124,13 @@ def _fetch_subreddit_rss( except (OSError, http.client.HTTPException, ET.ParseError) as exc: # OSError covers URLError/TimeoutError/connection resets; HTTPException # covers chunked-transfer errors (IncompleteRead/BadStatusLine, #1024). - logger.warning("Reddit RSS fetch failed for r/%s · %s: %s", sub, ticker, exc) - return [] + logger.warning("Reddit RSS direct fetch failed for r/%s · %s: %s, trying SSH proxy...", sub, ticker, exc) + try: + proxy_data = _fetch_via_ssh(url, timeout=timeout) + root = ET.fromstring(proxy_data.encode() if isinstance(proxy_data, str) else proxy_data) + except Exception as proxy_exc: + logger.warning("Reddit RSS proxy also failed for r/%s · %s: %s", sub, ticker, proxy_exc) + return [] posts = [] for entry in root.findall("atom:entry", _ATOM_NS)[:limit]: diff --git a/tradingagents/dataflows/stockstats_utils.py b/tradingagents/dataflows/stockstats_utils.py index cb97dc1..48e4a19 100644 --- a/tradingagents/dataflows/stockstats_utils.py +++ b/tradingagents/dataflows/stockstats_utils.py @@ -321,34 +321,18 @@ def load_ohlcv(symbol: str, curr_date: str) -> pd.DataFrame: if use_alpha_vantage: downloaded = _load_ohlcv_alpha_vantage(symbol, curr_date) else: - downloaded = None + # Skip local Yahoo Finance, go directly through SSH proxy + logger.info(f"Yahoo Finance: fetching {canonical} via SSH proxy...") try: - raw = yf_retry(lambda: yf.download( - canonical, - start=start_str, - end=end_str, - multi_level_index=False, - progress=False, - auto_adjust=True, - )) - downloaded = _ensure_date_column(raw.reset_index()) - except Exception as yf_err: - pass # Will try proxy below - - # If local yfinance failed or returned empty, try SSH proxy - if downloaded is None or downloaded.empty or "Close" not in downloaded.columns: - if downloaded is None or downloaded.empty: - logger.warning(f"Yahoo Finance local failed, trying SSH proxy...") - try: - downloaded = _yf_download_via_proxy(canonical, start_str, end_str) - except Exception as proxy_err: - raise NoMarketDataError( - symbol, canonical, f"Yahoo Finance proxy also failed: {proxy_err}" - ) from proxy_err - if downloaded.empty: - raise NoMarketDataError( - symbol, canonical, f"Yahoo Finance proxy also returned no data" - ) + downloaded = _yf_download_via_proxy(canonical, start_str, end_str) + except Exception as proxy_err: + raise NoMarketDataError( + symbol, canonical, f"Yahoo Finance proxy failed: {proxy_err}" + ) from proxy_err + if downloaded.empty: + raise NoMarketDataError( + symbol, canonical, f"Yahoo Finance proxy returned no data" + ) if downloaded.empty or "Close" not in downloaded.columns: raise NoMarketDataError( symbol, canonical, "Yahoo Finance returned no rows" diff --git a/tradingagents/dataflows/stocktwits.py b/tradingagents/dataflows/stocktwits.py index 1c2fd02..920a171 100644 --- a/tradingagents/dataflows/stocktwits.py +++ b/tradingagents/dataflows/stocktwits.py @@ -20,6 +20,7 @@ import logging from urllib.request import Request, urlopen from .symbol_utils import crypto_base +from .stockstats_utils import _fetch_via_ssh logger = logging.getLogger(__name__) @@ -54,8 +55,13 @@ def fetch_stocktwits_messages(ticker: str, limit: int = 30, timeout: float = 10. except (OSError, http.client.HTTPException, json.JSONDecodeError) as exc: # OSError covers URLError/TimeoutError/connection resets; HTTPException # covers chunked-transfer errors (IncompleteRead/BadStatusLine, #1024). - logger.warning("StockTwits fetch failed for %s: %s", ticker, exc) - return f"" + logger.warning("StockTwits direct fetch failed for %s: %s, trying SSH proxy...", ticker, exc) + try: + proxy_data = _fetch_via_ssh(url, timeout=timeout) + data = json.loads(proxy_data) + except Exception as proxy_exc: + logger.warning("StockTwits proxy also failed for %s: %s", ticker, proxy_exc) + return f"" messages = data.get("messages", []) if isinstance(data, dict) else [] if not messages: diff --git a/tradingagents/dataflows/y_finance.py b/tradingagents/dataflows/y_finance.py index 9e36c6d..b2cba44 100644 --- a/tradingagents/dataflows/y_finance.py +++ b/tradingagents/dataflows/y_finance.py @@ -36,19 +36,15 @@ def get_YFin_data_online( # end_date row (and the current day when end_date is today). Request one day # past end_date so the requested range is actually inclusive (#986/#987). end_inclusive = (end_dt + relativedelta(days=1)).strftime("%Y-%m-%d") - try: - data = yf_retry(lambda: ticker.history(start=start_date, end=end_inclusive)) - except Exception: - # Fallback: use SSH proxy via foreign server - logger.warning(f"Yahoo Finance local failed for {symbol}, trying SSH proxy...") - proxy_df = _yf_download_via_proxy(canonical, start_date, end_inclusive) - if proxy_df.empty: - raise NoMarketDataError( - symbol, canonical, f"no rows between {start_date} and {end_date} (proxy also failed)" - ) - # Convert to match expected format - proxy_df = proxy_df.set_index("Date") - data = proxy_df + # Skip local Yahoo Finance (rate-limited), go directly through SSH proxy + logger.info(f"Yahoo Finance: fetching {canonical} via SSH proxy...") + proxy_df = _yf_download_via_proxy(canonical, start_date, end_inclusive) + if proxy_df.empty: + raise NoMarketDataError( + symbol, canonical, f"no rows between {start_date} and {end_date} (proxy failed)" + ) + proxy_df = proxy_df.set_index("Date") + data = proxy_df # Empty result means the symbol is unknown/delisted. Raise a typed error # instead of returning prose: the routing layer turns it into a single