Skip to content

feat: add A-shares support via AKShare data vendor#797

Open
yangbenchn-cmyk wants to merge 21 commits into
TauricResearch:mainfrom
yangbenchn-cmyk:ashares-support
Open

feat: add A-shares support via AKShare data vendor#797
yangbenchn-cmyk wants to merge 21 commits into
TauricResearch:mainfrom
yangbenchn-cmyk:ashares-support

Conversation

@yangbenchn-cmyk
Copy link
Copy Markdown

Summary

  • Adds AKShare as a third data vendor for Chinese A-shares (Shanghai .SS, Shenzhen .SZ) and Hong Kong (.HK) stocks
  • Supports Chinese stock name input (e.g. 贵州茅台 auto-resolves to 600519.SS)
  • Provides OHLCV, fundamentals, balance sheet, income statement, cash flow, technical indicators, stock news, and China macro news
  • Users configure per-category: config["data_vendors"]["core_stock_apis"] = "akshare"

How it works

  • New module tradingagents/dataflows/akshare_data.py with 9 tool functions matching existing vendor signatures
  • Registered as a peer vendor in interface.py alongside yfinance and Alpha Vantage
  • Ticker normalization handles plain codes, exchange-suffixed codes, and Chinese names
  • Chinese name resolution at CLI (get_ticker()) and programmatic entry point (propagate())

Test Plan

  • 23 new unit tests (ticker normalization, data functions with mocked AKShare)
  • 3 integration tests (live Chinese name resolution, stock data, fundamentals)
  • All 88 existing unit tests continue to pass
  • Manual smoke test: propagate('贵州茅台', '2024-05-31') with AKShare configured resolves name and fetches data

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request integrates AKShare as a new data vendor, providing support for A-shares (Shanghai/Shenzhen) and Hong Kong stocks. Key additions include a new akshare_data module for fetching OHLCV data, fundamentals, financial statements, and news, along with logic in the CLI and trading graph to resolve Chinese stock names to their respective tickers. Feedback from the review highlights several areas for improvement: adhering to PEP 8 by moving inline imports to the top of modules, expanding ticker resolution logic to support the Beijing Stock Exchange, ensuring date validation is handled within existing error-handling blocks, and optimizing technical indicator calculations by replacing inefficient string-based filtering with DataFrame indexing.

Comment thread cli/main.py
raw = ticker.strip() or "SPY"

# Resolve Chinese stock names
from tradingagents.dataflows.akshare_data import _is_chinese_name, resolve_ticker_name
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid using inline imports within functions unless it is strictly necessary to resolve a circular dependency. Moving this import to the top of the file is recommended to adhere to PEP 8 and improve code clarity.

References
  1. Imports are always put at the top of the file, just after any module comments and docstrings, and before module globals and constants. (link)

Comment on lines +103 to +106
if code.startswith('6'):
return f"{code}.SS"
else:
return f"{code}.SZ"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for determining the exchange suffix is too simplistic. It assumes any ticker not starting with '6' is a Shenzhen stock ('.SZ'), which incorrectly handles Beijing Stock Exchange tickers (starting with '4' or '8', typically suffixed with '.BJ'). It is better to explicitly check for Shenzhen prefixes and handle Beijing stocks separately.

Suggested change
if code.startswith('6'):
return f"{code}.SS"
else:
return f"{code}.SZ"
if code.startswith('6'):
return f"{code}.SS"
elif code.startswith(('0', '3')):
return f"{code}.SZ"
elif code.startswith(('4', '8')):
return f"{code}.BJ"
return f"{code}.SZ"

Comment on lines +121 to +124
import pandas as pd
from datetime import datetime

import akshare as ak
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These imports should be moved to the top of the file to comply with PEP 8. Placing imports in the middle of a module makes it harder to track dependencies and can lead to confusion regarding the module's global namespace.

References
  1. Imports are always put at the top of the file, just after any module comments and docstrings, and before module globals and constants. (link)

Comment thread tradingagents/dataflows/akshare_data.py Outdated
Comment on lines +144 to +145
datetime.strptime(start_date, "%Y-%m-%d")
datetime.strptime(end_date, "%Y-%m-%d")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These date validation calls should be moved inside the try...except block. If start_date or end_date are malformed, strptime will raise a ValueError that currently bypasses the tool's error handling, potentially causing the agent to crash instead of receiving a descriptive error message.

Comment on lines +395 to +403
while result_dt >= curr_dt - relativedelta(days=look_back_days):
date_str = result_dt.strftime("%Y-%m-%d")
matching = wrapped[wrapped["Date"].str.startswith(date_str)]
if not matching.empty:
val = matching[indicator].values[0]
lines.append(f"{date_str}: {val if not pd.isna(val) else 'N/A: Not a trading day (weekend or holiday)'}")
else:
lines.append(f"{date_str}: N/A: Not a trading day (weekend or holiday)")
result_dt -= relativedelta(days=1)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Iterating through the lookback period and performing a string-based filter on the DataFrame (wrapped["Date"].str.startswith(date_str)) in each iteration is inefficient. For better performance, consider setting the date as the index and using direct label-based lookup.

Suggested change
while result_dt >= curr_dt - relativedelta(days=look_back_days):
date_str = result_dt.strftime("%Y-%m-%d")
matching = wrapped[wrapped["Date"].str.startswith(date_str)]
if not matching.empty:
val = matching[indicator].values[0]
lines.append(f"{date_str}: {val if not pd.isna(val) else 'N/A: Not a trading day (weekend or holiday)'}")
else:
lines.append(f"{date_str}: N/A: Not a trading day (weekend or holiday)")
result_dt -= relativedelta(days=1)
wrapped = wrapped.set_index("Date")
result_dt = curr_dt
lines = []
while result_dt >= curr_dt - relativedelta(days=look_back_days):
date_str = result_dt.strftime("%Y-%m-%d")
if date_str in wrapped.index:
val = wrapped.loc[date_str, indicator]
if isinstance(val, pd.Series):
val = val.iloc[0]
lines.append(f"{date_str}: {val if not pd.isna(val) else 'N/A: Not a trading day'}")
else:
lines.append(f"{date_str}: N/A: Not a trading day")
result_dt -= relativedelta(days=1)

successful node on a subsequent invocation with the same ticker+date.
"""
# Resolve Chinese stock names before running the pipeline
from tradingagents.dataflows.akshare_data import _is_chinese_name, resolve_ticker_name
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Inline imports should be avoided where possible. Since akshare_data does not depend on trading_graph, there is no circular dependency risk. Moving this import to the top of the file is recommended for PEP 8 compliance.

References
  1. Imports are always put at the top of the file, just after any module comments and docstrings, and before module globals and constants. (link)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant