fix: 内部接口调整完毕,除_InfoDataInternal接口外,其他接口已完成适配

master
Lxy 3 months ago
parent fbcb54d23f
commit 0b4c4eb233

@ -0,0 +1,175 @@
# 接口测试分类说明
## 一、接口分类架构
```
接口测试
├── 对外接口 (External APIs)
│ ├── 股票数据接口
│ ├── 期货数据接口
│ ├── 管理接口
│ └── 数据同步接口
└── 对内接口 (Internal APIs - SDK封装层)
├── 市场数据接口 (_market_data)
├── 基础数据接口 (_base_data)
├── 股本股东接口 (_info_data)
├── 财务报表接口 (_info_data)
├── 市场状态接口 (_info_data)
├── 特色数据接口 (_info_data)
└── 基金可转债接口 (_info_data)
```
## 二、对外接口列表 (18个)
### 1. 股票数据接口 (4个)
| 测试ID | 名称 | 方法 | 路径 |
|--------|------|------|------|
| stock_klines | 查询股票K线 | GET | /v1/stock/klines/{symbol} |
| stock_symbols | 查询股票列表 | GET | /v1/stock/symbols |
| stock_batch | 批量查询股票K线 | POST | /v1/stock/klines/batch |
| stock_calendar | 查询股票交易日历 | GET | /v1/stock/trading-dates |
### 2. 期货数据接口 (5个)
| 测试ID | 名称 | 方法 | 路径 |
|--------|------|------|------|
| futures_klines | 查询期货K线 | GET | /v1/futures/klines/{symbol} |
| futures_symbols | 查询期货列表 | GET | /v1/futures/symbols |
| futures_batch | 批量查询期货K线 | POST | /v1/futures/klines/batch |
| futures_contracts | 查询合约列表 | GET | /v1/futures/contracts |
| futures_calendar | 查询期货交易日历 | GET | /v1/futures/trading-dates |
### 3. 管理接口 (4个)
| 测试ID | 名称 | 方法 | 路径 |
|--------|------|------|------|
| admin_health | 健康检查 | GET | /v1/admin/health |
| admin_source_status | 数据源状态 | GET | /v1/admin/source/status |
| admin_source_switch | 切换数据源 | POST | /v1/admin/source/switch |
| admin_system_status | 系统状态 | GET | /v1/admin/system/status |
### 4. 数据同步接口 (5个)
| 测试ID | 名称 | 方法 | 路径 |
|--------|------|------|------|
| admin_data_sync_full | 全量数据同步 | POST | /v1/admin/data/sync |
| admin_data_sync_base | 同步基础K线数据 | POST | /v1/admin/data/sync |
| admin_data_sync_quote | 同步行情指标数据 | POST | /v1/admin/data/sync |
| admin_data_sync_finance | 同步财务数据 | POST | /v1/admin/data/sync |
| admin_data_sync_incremental | 增量数据同步 | POST | /v1/admin/data/sync/incremental |
## 三、对内接口列表 (23个)
### 1. 市场数据接口 _market_data (2个)
| 测试ID | SDK方法 | 说明 |
|--------|---------|------|
| internal_market_query_kline | query_kline | 查询K线数据 |
| internal_market_query_snapshot | query_snapshot | 查询快照数据 |
### 2. 基础数据接口 _base_data (6个)
| 测试ID | SDK方法 | 说明 |
|--------|---------|------|
| internal_base_get_code_list | get_code_list | 获取股票代码列表 |
| internal_base_get_future_code_list | get_future_code_list | 获取期货代码列表 |
| internal_base_get_code_info | get_code_info | 获取代码信息 |
| internal_base_get_calendar | get_calendar | 获取交易日历 |
| internal_base_get_adj_factor | get_adj_factor | 获取复权因子 |
| internal_base_get_etf_pcf | get_etf_pcf | 获取ETF申赎数据 |
### 3. 股本股东接口 _info_data (3个)
| 测试ID | SDK方法 | 说明 |
|--------|---------|------|
| internal_info_get_equity_structure | get_equity_structure | 获取股本结构 |
| internal_info_get_share_holder | get_share_holder | 获取股东数据 |
| internal_info_get_holder_num | get_holder_num | 获取股东户数 |
### 4. 财务报表接口 _info_data (3个)
| 测试ID | SDK方法 | 说明 |
|--------|---------|------|
| internal_info_get_income | get_income | 获取利润表 |
| internal_info_get_balance_sheet | get_balance_sheet | 获取资产负债表 |
| internal_info_get_cash_flow | get_cash_flow | 获取现金流量表 |
### 5. 市场状态接口 _info_data (3个)
| 测试ID | SDK方法 | 说明 |
|--------|---------|------|
| internal_info_get_history_stock_status | get_history_stock_status | 历史股票状态(涨停/跌停/ST/停牌) |
| internal_info_get_margin_summary | get_margin_summary | 融资融券汇总 |
| internal_info_get_margin_detail | get_margin_detail | 融资融券明细 |
### 6. 特色数据接口 _info_data (4个)
| 测试ID | SDK方法 | 说明 |
|--------|---------|------|
| internal_info_get_long_hu_bang | get_long_hu_bang | 获取龙虎榜数据 |
| internal_info_get_block_trading | get_block_trading | 获取大宗交易数据 |
| internal_info_get_index_constituent | get_index_constituent | 获取指数成分股 |
| internal_info_get_index_weight | get_index_weight | 获取指数权重 |
### 7. 基金可转债接口 _info_data (2个)
| 测试ID | SDK方法 | 说明 |
|--------|---------|------|
| internal_info_get_fund_share | get_fund_share | 获取基金份额 |
| internal_info_get_kzz_issuance | get_kzz_issuance | 获取可转债发行数据 |
## 四、API端点
### 获取对外接口测试列表
```
GET /v1/admin/tests/api
```
### 获取对内接口测试列表
```
GET /v1/admin/tests/internal
```
### 执行对外接口测试
```
POST /v1/admin/tests/api/run
Body: {"id": "stock_klines", "params": {...}}
```
### 执行对内接口测试
```
POST /v1/admin/tests/internal/run
Body: {"id": "internal_info_get_equity_structure", "params": {...}}
```
## 五、调用关系说明
### 对外接口调用链
```
外部请求
API路由 (admin_routes.py)
对外接口方法 (AmazingDataAdapter)
内部接口层 (_MarketDataInternal/_BaseDataInternal/_InfoDataInternal)
AmazingData SDK
```
### 对内接口调用链
```
测试请求
内部接口测试方法 (TestService.run_internal_test)
内部接口层 (_MarketDataInternal/_BaseDataInternal/_InfoDataInternal)
AmazingData SDK
```
## 六、设计优势
1. **分层清晰** - 对外接口面向用户对内接口面向SDK
2. **独立测试** - 可以单独测试SDK封装层是否正确
3. **便于定位** - 接口问题可以快速定位是外部API问题还是SDK问题
4. **覆盖全面** - 所有SDK方法都有对应的测试用例
## 七、统计
| 类别 | 数量 |
|------|------|
| 对外接口 | 18个 |
| 对内接口 | 23个 |
| **总计** | **41个** |

@ -184,7 +184,7 @@ class AmazingDataAdapter(DataSourceAdapter):
# 初始化数据类 # 初始化数据类
self._base_data = self._ad.BaseData() self._base_data = self._ad.BaseData()
self._info_data = self._ad.InfoData() self._info_data = self._ad.InfoData()
self._calendar = self._internal.base.get_calendar() self._calendar = self._base_data.get_calendar()
self._market_data = self._ad.MarketData(self._calendar) self._market_data = self._ad.MarketData(self._calendar)
# 初始化内部数据服务层 # 初始化内部数据服务层

@ -195,7 +195,10 @@ class _InfoDataInternal:
) -> Dict[str, pd.DataFrame]: ) -> Dict[str, pd.DataFrame]:
"""获取股东数据""" """获取股东数据"""
try: try:
return self._info_data.get_share_holder( # 创建新的 InfoData 实例以避免 SDK 内部状态问题
import AmazingData as ad
info_data = ad.InfoData()
return info_data.get_share_holder(
code_list=code_list, code_list=code_list,
local_path=local_path, local_path=local_path,
is_local=is_local, is_local=is_local,

@ -273,6 +273,60 @@ def get_test_history(
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
@admin_router.get("/admin/tests/internal", response_model=Response)
def get_internal_test_list(
token: str = Depends(verify_admin_token)
):
"""获取内部接口测试列表SDK封装层"""
try:
data = test_service.get_internal_test_list()
return Response(code=0, message="success", data=data)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@admin_router.post("/admin/tests/internal/run", response_model=Response)
async def run_internal_test(
req: APITestRequest,
token: str = Depends(verify_admin_token)
):
"""执行内部接口测试SDK封装层"""
try:
# 获取股票adapter
adapter = adapter_service.get_active_adapter("stock")
if not adapter:
# 尝试连接adapter
config = get_config()
active_name = config.sources.stock.active
await adapter_service._connect_adapter(active_name)
adapter = adapter_service.get_active_adapter("stock")
if not adapter:
raise HTTPException(status_code=500, detail="Stock adapter not available")
# 确保adapter已连接
if not hasattr(adapter, '_is_logged_in') or not adapter._is_logged_in:
if hasattr(adapter, 'config') and adapter.config:
# 使用已保存的配置重新连接
config_dict = {
'username': adapter.config.username,
'password': adapter.config.password,
'host': adapter.config.host,
'port': adapter.config.port,
'local_path': adapter.config.local_path,
'use_local_cache': adapter.config.use_local_cache
}
await adapter.connect(config_dict)
else:
raise HTTPException(status_code=500, detail="Adapter not configured")
data = await test_service.run_internal_test(adapter, req)
return Response(code=0, message="success", data=data)
except Exception as e:
import traceback
error(f"Internal test error: {e}\n{traceback.format_exc()}")
raise HTTPException(status_code=500, detail=str(e))
# ============================================ # ============================================
# 数据同步接口 # 数据同步接口

@ -459,6 +459,7 @@ ADMIN_HTML = '''<!DOCTYPE html>
<div class="test-tabs"> <div class="test-tabs">
<div class="test-tab active" onclick="switchTestTab('quick')">快速测试</div> <div class="test-tab active" onclick="switchTestTab('quick')">快速测试</div>
<div class="test-tab" onclick="switchTestTab('api')">API 测试套件</div> <div class="test-tab" onclick="switchTestTab('api')">API 测试套件</div>
<div class="test-tab" onclick="switchTestTab('internal')">对内接口测试</div>
<div class="test-tab" onclick="switchTestTab('ws')">WebSocket 测试</div> <div class="test-tab" onclick="switchTestTab('ws')">WebSocket 测试</div>
<div class="test-tab" onclick="switchTestTab('history')">测试历史</div> <div class="test-tab" onclick="switchTestTab('history')">测试历史</div>
</div> </div>
@ -566,6 +567,15 @@ ADMIN_HTML = '''<!DOCTYPE html>
<div id="api-test-list">加载中...</div> <div id="api-test-list">加载中...</div>
</div> </div>
<!-- 对内接口测试面板 -->
<div id="internal-test-panel" class="test-panel">
<div class="batch-actions">
<button class="btn btn-primary btn-sm" onclick="runAllInternalTests()"> 运行全部对内接口测试</button>
<button class="btn btn-sm" onclick="clearAllInternalResults()">🗑 清除结果</button>
</div>
<div id="internal-test-list">加载中...</div>
</div>
<!-- WebSocket 测试面板 --> <!-- WebSocket 测试面板 -->
<div id="ws-test-panel" class="test-panel"> <div id="ws-test-panel" class="test-panel">
<div class="batch-actions"> <div class="batch-actions">
@ -599,6 +609,7 @@ ADMIN_HTML = '''<!DOCTYPE html>
if (pageName === 'tests') { if (pageName === 'tests') {
loadAPITestList(); loadAPITestList();
loadInternalTestList();
loadWSTestList(); loadWSTestList();
} else if (pageName === 'config') { } else if (pageName === 'config') {
loadConfigList(); loadConfigList();
@ -611,6 +622,7 @@ ADMIN_HTML = '''<!DOCTYPE html>
switchTestTab = function(tab) { switchTestTab = function(tab) {
_originalSwitchTestTab(tab); _originalSwitchTestTab(tab);
if (tab === 'history') loadTestHistory(); if (tab === 'history') loadTestHistory();
if (tab === 'internal') loadInternalTestList();
}; };
// ============ API 测试 ============ // ============ API 测试 ============
@ -736,6 +748,131 @@ ADMIN_HTML = '''<!DOCTYPE html>
}); });
} }
// ============ 对内接口测试 ============
let internalTestCases = [];
async function loadInternalTestList() {
try {
const response = await fetch('/v1/admin/tests/internal', {
headers: { 'X-Admin-Token': apiKey }
});
const data = await response.json();
if (data.code === 0) {
internalTestCases = data.data.categories || [];
renderInternalTestList();
}
} catch (e) {
document.getElementById('internal-test-list').innerHTML = '加载失败: ' + e.message;
}
}
function renderInternalTestList() {
let html = '';
internalTestCases.forEach(cat => {
html += `<div class="test-category">
<div class="test-category-title">${cat.name}</div>`;
cat.items.forEach(item => {
html += `<div class="test-item" id="internal-test-${item.id}">
<div class="test-item-info">
<div class="test-item-name">
<span class="status-badge" style="background:#722ed1;color:#fff;margin-right:8px;">SDK</span>
${item.name}
</div>
<div class="test-item-desc">${item.description}</div>
<div class="test-item-meta">${item.method}</div>
</div>
<div class="test-item-actions">
<button class="btn btn-primary btn-sm" onclick="runInternalTest('${item.id}')" id="btn-internal-${item.id}">测试</button>
</div>
</div>
<div class="test-result" id="internal-result-${item.id}"></div>`;
});
html += '</div>';
});
document.getElementById('internal-test-list').innerHTML = html;
}
async function runInternalTest(testId) {
const btn = document.getElementById(`btn-internal-${testId}`);
const resultDiv = document.getElementById(`internal-result-${testId}`);
btn.disabled = true;
btn.innerHTML = '<span class="loading-spinner"></span>';
resultDiv.className = 'test-result';
resultDiv.classList.add('show');
resultDiv.innerHTML = '运行中...';
try {
const response = await fetch('/v1/admin/tests/internal/run', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Admin-Token': apiKey
},
body: JSON.stringify({ id: testId })
});
const data = await response.json();
if (data.code === 0 && data.data) {
const result = data.data;
const isSuccess = result.success;
resultDiv.className = 'test-result show ' + (isSuccess ? 'success' : 'error');
resultDiv.innerHTML = `
<div class="test-result-header">
<span>${isSuccess ? '✅ 测试通过' : '❌ 测试失败'}</span>
<span>${result.latency}ms</span>
</div>
<div style="margin-bottom:8px;">
<strong>接口:</strong> ${result.interface}
</div>
<div style="margin-bottom:8px;">
<strong>参数:</strong> <pre>${JSON.stringify(result.params, null, 2)}</pre>
</div>
<div>
<strong>响应:</strong>
<div class="test-result-body">${typeof result.response === 'object' ? JSON.stringify(result.response, null, 2) : result.response}</div>
</div>
${result.error ? `<div style="color:#ff4d4f;margin-top:8px;"><strong>错误:</strong> ${result.error}</div>` : ''}
`;
btn.innerHTML = isSuccess ? '✓ 通过' : '✗ 失败';
btn.className = isSuccess ? 'btn btn-success btn-sm' : 'btn btn-danger btn-sm';
} else {
throw new Error(data.message || '测试执行失败');
}
} catch (e) {
resultDiv.className = 'test-result show error';
resultDiv.innerHTML = `<div class="test-result-header"><span> 执行错误</span></div><div>${e.message}</div>`;
btn.innerHTML = '✗ 失败';
btn.className = 'btn btn-danger btn-sm';
}
btn.disabled = false;
}
async function runAllInternalTests() {
const allIds = [];
internalTestCases.forEach(cat => cat.items.forEach(item => allIds.push(item.id)));
for (const id of allIds) {
await runInternalTest(id);
await new Promise(r => setTimeout(r, 300));
}
}
function clearAllInternalResults() {
document.querySelectorAll('[id^="internal-result-"]').forEach(el => {
el.className = 'test-result';
el.innerHTML = '';
});
document.querySelectorAll('[id^="btn-internal-"]').forEach(el => {
el.innerHTML = '测试';
el.className = 'btn btn-primary btn-sm';
el.disabled = false;
});
}
// ============ WebSocket 测试 ============ // ============ WebSocket 测试 ============
let wsTestCases = []; let wsTestCases = [];

@ -7,6 +7,7 @@ from threading import RLock
import httpx import httpx
import websockets import websockets
import pandas as pd
from app.models import ( from app.models import (
APITestListData, APITestCategory, APITestCase, APITestListData, APITestCategory, APITestCase,
@ -23,18 +24,19 @@ class TestService:
def __init__(self): def __init__(self):
self.lock = RLock() self.lock = RLock()
self.api_history: List[APITestResult] = [] self.api_history: List[APITestResult] = []
self.internal_history: List[APITestResult] = []
self.ws_history: List[WSTestResult] = [] self.ws_history: List[WSTestResult] = []
self.history_size = 100 self.history_size = 100
def get_api_test_list(self) -> APITestListData: def get_api_test_list(self) -> APITestListData:
"""获取API测试列表""" """获取API测试列表(对外接口)"""
# 固定交易时间2026年3月2日到2026年3月6日 # 固定交易时间2026年3月2日到2026年3月6日
test_start = datetime(2026, 3, 2) test_start = datetime(2026, 3, 2)
test_end = datetime(2026, 3, 6) test_end = datetime(2026, 3, 6)
categories = [ categories = [
APITestCategory( APITestCategory(
name="股票接口", name="【对外】股票数据接口",
items=[ items=[
APITestCase( APITestCase(
id="stock_klines", id="stock_klines",
@ -73,7 +75,7 @@ class TestService:
), ),
APITestCase( APITestCase(
id="stock_calendar", id="stock_calendar",
name="查询交易日历", name="查询股票交易日历",
method="GET", method="GET",
path="/v1/stock/trading-dates", path="/v1/stock/trading-dates",
description="查询股票交易日历", description="查询股票交易日历",
@ -85,7 +87,7 @@ class TestService:
] ]
), ),
APITestCategory( APITestCategory(
name="期货接口", name="【对外】期货数据接口",
items=[ items=[
APITestCase( APITestCase(
id="futures_klines", id="futures_klines",
@ -143,7 +145,7 @@ class TestService:
] ]
), ),
APITestCategory( APITestCategory(
name="管理接口", name="【对外】管理接口",
items=[ items=[
APITestCase( APITestCase(
id="admin_health", id="admin_health",
@ -181,75 +183,10 @@ class TestService:
description="获取系统运行状态和资源使用情况", description="获取系统运行状态和资源使用情况",
params={} params={}
), ),
APITestCase(
id="admin_config_list",
name="查询配置列表",
method="GET",
path="/v1/admin/config",
description="获取所有配置项列表",
params={}
),
APITestCase(
id="admin_config_update",
name="更新配置",
method="PUT",
path="/v1/admin/config",
description="更新系统配置",
body={
"key": "server.mode",
"value": "debug",
"description": "服务器运行模式"
}
),
APITestCase(
id="admin_reload_config",
name="热加载配置",
method="POST",
path="/v1/admin/system/reload",
description="重新加载配置文件",
body={}
),
]
),
APITestCategory(
name="适配器管理",
items=[
APITestCase(
id="admin_adapters_list",
name="适配器列表",
method="GET",
path="/v1/admin/adapters",
description="获取所有数据源适配器列表",
params={}
),
APITestCase(
id="admin_adapter_toggle",
name="切换适配器状态",
method="POST",
path="/v1/admin/adapters/toggle",
description="启用或禁用适配器",
body={
"name": "amazingdata",
"enable": True
}
),
APITestCase(
id="admin_adapter_config",
name="更新适配器配置",
method="PUT",
path="/v1/admin/adapters/config",
description="更新适配器配置参数",
body={
"name": "amazingdata",
"config": {
"timeout": "60"
}
}
),
] ]
), ),
APITestCategory( APITestCategory(
name="数据同步接口", name="【对外】数据同步接口",
items=[ items=[
APITestCase( APITestCase(
id="admin_data_sync_full", id="admin_data_sync_full",
@ -270,7 +207,7 @@ class TestService:
name="同步基础K线数据", name="同步基础K线数据",
method="POST", method="POST",
path="/v1/admin/data/sync", path="/v1/admin/data/sync",
description="仅同步基础K线数据OHLCV", description="仅同步OHLCV基础数据",
body={ body={
"symbols": ["000001.SZ"], "symbols": ["000001.SZ"],
"sync_type": "base", "sync_type": "base",
@ -285,7 +222,7 @@ class TestService:
name="同步行情指标数据", name="同步行情指标数据",
method="POST", method="POST",
path="/v1/admin/data/sync", path="/v1/admin/data/sync",
description="同步行情指标数据(均线/MACD/涨跌幅", description="同步均线/MACD/涨跌幅",
body={ body={
"symbols": ["000001.SZ"], "symbols": ["000001.SZ"],
"sync_type": "quote", "sync_type": "quote",
@ -299,7 +236,7 @@ class TestService:
name="同步财务数据", name="同步财务数据",
method="POST", method="POST",
path="/v1/admin/data/sync", path="/v1/admin/data/sync",
description="同步财务数据(市值/股本/利润", description="同步市值/股本/利润",
body={ body={
"symbols": ["000001.SZ"], "symbols": ["000001.SZ"],
"sync_type": "finance", "sync_type": "finance",
@ -316,32 +253,231 @@ class TestService:
description="触发增量同步最近30天", description="触发增量同步最近30天",
body=["000001.SZ", "600519.SH"] body=["000001.SZ", "600519.SH"]
), ),
]
),
]
return APITestListData(categories=categories, base_url="")
def get_internal_test_list(self) -> APITestListData:
"""获取内部接口测试列表(对内接口 - SDK封装层"""
categories = [
APITestCategory(
name="【对内】市场数据接口 (_market_data)",
items=[
APITestCase( APITestCase(
id="admin_data_sync_futures", id="internal_market_query_kline",
name="期货数据同步", name="SDK: query_kline",
method="POST", method="INTERNAL",
path="/v1/admin/data/sync", path="AmazingDataAdapter._internal.market.query_kline",
description="同步期货数据", description="查询K线数据内部SDK调用",
body={ params={"symbol": "000001.SZ", "period": "1d"}
"symbols": ["CU2504.SHFE"], ),
"sync_type": "full", APITestCase(
"start_date": "20240301", id="internal_market_query_snapshot",
"end_date": "20240310", name="SDK: query_snapshot",
"asset_class": "futures" method="INTERNAL",
} path="AmazingDataAdapter._internal.market.query_snapshot",
description="查询快照数据内部SDK调用",
params={"symbol": "000001.SZ"}
), ),
] ]
), ),
APITestCategory( APITestCategory(
name="测试管理", name="【对内】基础数据接口 (_base_data)",
items=[ items=[
APITestCase( APITestCase(
id="admin_test_history", id="internal_base_get_code_list",
name="测试历史", name="SDK: get_code_list",
method="GET", method="INTERNAL",
path="/v1/admin/tests/history", path="AmazingDataAdapter._internal.base.get_code_list",
description="获取测试执行历史记录", description="获取股票代码列表内部SDK调用",
params={"type": "api", "limit": "20"} params={}
),
APITestCase(
id="internal_base_get_future_code_list",
name="SDK: get_future_code_list",
method="INTERNAL",
path="AmazingDataAdapter._internal.base.get_future_code_list",
description="获取期货代码列表内部SDK调用",
params={}
),
APITestCase(
id="internal_base_get_code_info",
name="SDK: get_code_info",
method="INTERNAL",
path="AmazingDataAdapter._internal.base.get_code_info",
description="获取代码信息内部SDK调用",
params={}
),
APITestCase(
id="internal_base_get_calendar",
name="SDK: get_calendar",
method="INTERNAL",
path="AmazingDataAdapter._internal.base.get_calendar",
description="获取交易日历内部SDK调用",
params={}
),
APITestCase(
id="internal_base_get_adj_factor",
name="SDK: get_adj_factor",
method="INTERNAL",
path="AmazingDataAdapter._internal.base.get_adj_factor",
description="获取复权因子内部SDK调用",
params={}
),
APITestCase(
id="internal_base_get_etf_pcf",
name="SDK: get_etf_pcf",
method="INTERNAL",
path="AmazingDataAdapter._internal.base.get_etf_pcf",
description="获取ETF申赎数据内部SDK调用",
params={}
),
]
),
APITestCategory(
name="【对内】股本股东接口 (_info_data)",
items=[
APITestCase(
id="internal_info_get_equity_structure",
name="SDK: get_equity_structure",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_equity_structure",
description="获取股本结构内部SDK调用",
params={"symbol": "000001.SZ"}
),
APITestCase(
id="internal_info_get_share_holder",
name="SDK: get_share_holder",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_share_holder",
description="获取股东数据内部SDK调用",
params={"symbol": "000001.SZ"}
),
APITestCase(
id="internal_info_get_holder_num",
name="SDK: get_holder_num",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_holder_num",
description="获取股东户数内部SDK调用",
params={"symbol": "000001.SZ"}
),
]
),
APITestCategory(
name="【对内】财务报表接口 (_info_data)",
items=[
APITestCase(
id="internal_info_get_income",
name="SDK: get_income",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_income",
description="获取利润表内部SDK调用",
params={"symbol": "000001.SZ"}
),
APITestCase(
id="internal_info_get_balance_sheet",
name="SDK: get_balance_sheet",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_balance_sheet",
description="获取资产负债表内部SDK调用",
params={"symbol": "000001.SZ"}
),
APITestCase(
id="internal_info_get_cash_flow",
name="SDK: get_cash_flow",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_cash_flow",
description="获取现金流量表内部SDK调用",
params={"symbol": "000001.SZ"}
),
]
),
APITestCategory(
name="【对内】市场状态接口 (_info_data)",
items=[
APITestCase(
id="internal_info_get_history_stock_status",
name="SDK: get_history_stock_status",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_history_stock_status",
description="获取历史股票状态(涨停/跌停/ST/停牌内部SDK调用",
params={"symbol": "000001.SZ"}
),
APITestCase(
id="internal_info_get_margin_summary",
name="SDK: get_margin_summary",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_margin_summary",
description="获取融资融券汇总内部SDK调用",
params={}
),
APITestCase(
id="internal_info_get_margin_detail",
name="SDK: get_margin_detail",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_margin_detail",
description="获取融资融券明细内部SDK调用",
params={"symbol": "000001.SZ"}
),
]
),
APITestCategory(
name="【对内】特色数据接口 (_info_data)",
items=[
APITestCase(
id="internal_info_get_long_hu_bang",
name="SDK: get_long_hu_bang",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_long_hu_bang",
description="获取龙虎榜数据内部SDK调用",
params={}
),
APITestCase(
id="internal_info_get_block_trading",
name="SDK: get_block_trading",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_block_trading",
description="获取大宗交易数据内部SDK调用",
params={}
),
APITestCase(
id="internal_info_get_index_constituent",
name="SDK: get_index_constituent",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_index_constituent",
description="获取指数成分股内部SDK调用",
params={"index": "000300.SH"}
),
APITestCase(
id="internal_info_get_index_weight",
name="SDK: get_index_weight",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_index_weight",
description="获取指数权重内部SDK调用",
params={"index": "000300.SH"}
),
]
),
APITestCategory(
name="【对内】基金可转债接口 (_info_data)",
items=[
APITestCase(
id="internal_info_get_fund_share",
name="SDK: get_fund_share",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_fund_share",
description="获取基金份额内部SDK调用",
params={}
),
APITestCase(
id="internal_info_get_kzz_issuance",
name="SDK: get_kzz_issuance",
method="INTERNAL",
path="AmazingDataAdapter._internal.info.get_kzz_issuance",
description="获取可转债发行数据内部SDK调用",
params={}
), ),
] ]
), ),
@ -349,8 +485,9 @@ class TestService:
return APITestListData(categories=categories, base_url="") return APITestListData(categories=categories, base_url="")
async def run_api_test(self, base_url: str, req: APITestRequest) -> APITestResult: async def run_api_test(self, base_url: str, req: APITestRequest) -> APITestResult:
"""执行API测试""" """执行对外API测试"""
# 获取测试用例 # 获取测试用例
test_list = self.get_api_test_list() test_list = self.get_api_test_list()
@ -444,6 +581,231 @@ class TestService:
self._add_api_history(result) self._add_api_history(result)
return result return result
async def run_internal_test(self, adapter, req: APITestRequest) -> APITestResult:
"""执行内部接口测试SDK封装层"""
from app.adapters.amazingdata_adapter import AmazingDataAdapter
start_time = datetime.now()
try:
# 确保已连接
if not adapter._is_logged_in:
raise RuntimeError("Adapter not connected")
# 根据测试ID调用对应的内部接口
result_data = None
error_msg = None
if req.id == "internal_market_query_kline":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.market.query_kline(
code_list=[symbol],
begin_date=20240301,
end_date=20240310,
period=10000 # SDK只支持10000日线
)
elif req.id == "internal_market_query_snapshot":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.market.query_snapshot(
code_list=[symbol],
begin_date=20240301,
end_date=20240301
)
elif req.id == "internal_base_get_code_list":
from app.adapters.amazingdata_adapter import SecurityType
result_data = adapter._internal.base.get_code_list(
security_type=SecurityType.STOCK_A.value
)
elif req.id == "internal_base_get_future_code_list":
from app.adapters.amazingdata_adapter import SecurityType
result_data = adapter._internal.base.get_future_code_list(
security_type=SecurityType.FUTURE.value
)
elif req.id == "internal_base_get_code_info":
from app.adapters.amazingdata_adapter import SecurityType
result_data = adapter._internal.base.get_code_info(
security_type=SecurityType.STOCK_A.value
)
elif req.id == "internal_base_get_calendar":
result_data = adapter._internal.base.get_calendar(market="SH")
elif req.id == "internal_base_get_adj_factor":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.base.get_adj_factor(
code_list=[symbol],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_base_get_etf_pcf":
result_data = adapter._internal.base.get_etf_pcf(
code_list=["510050.SH"]
)
elif req.id == "internal_info_get_equity_structure":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.info.get_equity_structure(
code_list=[symbol],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_share_holder":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.info.get_share_holder(
code_list=[symbol],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_holder_num":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.info.get_holder_num(
code_list=[symbol],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_income":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.info.get_income(
code_list=[symbol],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_balance_sheet":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.info.get_balance_sheet(
code_list=[symbol],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_cash_flow":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.info.get_cash_flow(
code_list=[symbol],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_history_stock_status":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.info.get_history_stock_status(
code_list=[symbol],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache,
begin_date=20240301,
end_date=20240310
)
elif req.id == "internal_info_get_margin_summary":
result_data = adapter._internal.info.get_margin_summary(
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_margin_detail":
symbol = req.params.get("symbol", "000001.SZ")
result_data = adapter._internal.info.get_margin_detail(
code_list=[symbol],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_long_hu_bang":
result_data = adapter._internal.info.get_long_hu_bang(
code_list=["000001.SZ"],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_block_trading":
result_data = adapter._internal.info.get_block_trading(
code_list=["000001.SZ"],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_index_constituent":
index_code = req.params.get("index", "000300.SH")
result_data = adapter._internal.info.get_index_constituent(
code_list=[index_code],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_index_weight":
index_code = req.params.get("index", "000300.SH")
result_data = adapter._internal.info.get_index_weight(
code_list=[index_code],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_fund_share":
result_data = adapter._internal.info.get_fund_share(
code_list=["510050.SH"],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
elif req.id == "internal_info_get_kzz_issuance":
result_data = adapter._internal.info.get_kzz_issuance(
code_list=["110043.SH"],
local_path=adapter.config.local_path,
is_local=adapter.config.use_local_cache
)
else:
raise ValueError(f"Unknown internal test case: {req.id}")
latency = int((datetime.now() - start_time).total_seconds() * 1000)
# 检查结果
has_data = result_data is not None
if isinstance(result_data, (dict, list)):
has_data = len(result_data) > 0
elif isinstance(result_data, pd.DataFrame):
has_data = not result_data.empty
result = APITestResult(
id=int(datetime.now().timestamp()),
case_id=req.id,
name=req.id.replace("internal_", "").replace("_", ":"),
success=has_data,
status_code=200 if has_data else 204,
latency=latency,
request={"params": req.params},
response={"data_count": len(result_data) if hasattr(result_data, '__len__') else 1} if has_data else None,
error=None if has_data else "No data returned",
timestamp=datetime.now()
)
self._add_internal_history(result)
return result
except Exception as e:
latency = int((datetime.now() - start_time).total_seconds() * 1000)
result = APITestResult(
id=int(datetime.now().timestamp()),
case_id=req.id,
name=req.id.replace("internal_", "").replace("_", ":"),
success=False,
latency=latency,
request={"params": req.params},
error=str(e),
timestamp=datetime.now()
)
self._add_internal_history(result)
return result
def get_ws_test_list(self) -> WSTestListData: def get_ws_test_list(self) -> WSTestListData:
"""获取WebSocket测试列表""" """获取WebSocket测试列表"""
cases = [ cases = [
@ -468,66 +830,12 @@ class TestService:
action="subscribe", action="subscribe",
symbols=["000001.SZ", "000002.SZ", "CU2504.SHFE"] symbols=["000001.SZ", "000002.SZ", "CU2504.SHFE"]
), ),
WSTestCase(
id="ws_subscribe_many",
name="压力测试-大量订阅",
description="订阅大量标的测试性能",
action="subscribe",
symbols=[
"000001.SZ", "000002.SZ", "000063.SZ", "000333.SZ",
"000538.SZ", "000568.SZ", "000651.SZ", "000725.SZ",
"000768.SZ", "000858.SZ"
]
),
WSTestCase(
id="ws_unsubscribe",
name="取消订阅",
description="取消订阅标的",
action="unsubscribe",
symbols=["000001.SZ"]
),
WSTestCase(
id="ws_unsubscribe_all",
name="取消全部订阅",
description="取消所有已订阅标的",
action="unsubscribe",
symbols=["000001.SZ", "000002.SZ", "CU2504.SHFE"]
),
WSTestCase(
id="ws_heartbeat",
name="心跳检测",
description="测试WebSocket连接心跳",
action="subscribe",
symbols=["000001.SZ"]
),
WSTestCase(
id="ws_invalid_symbol",
name="无效标的测试",
description="测试订阅无效标的的错误处理",
action="subscribe",
symbols=["INVALID.CODE"]
),
WSTestCase(
id="ws_empty_symbols",
name="空订阅测试",
description="测试空标的列表的处理",
action="subscribe",
symbols=[]
),
WSTestCase(
id="ws_resubscribe",
name="重新订阅",
description="取消后重新订阅同一标的",
action="subscribe",
symbols=["000001.SZ"]
),
] ]
return WSTestListData(cases=cases, ws_url="") return WSTestListData(cases=cases, ws_url="")
async def run_ws_test(self, ws_url: str, req: WSTestRequest) -> WSTestResult: async def run_ws_test(self, ws_url: str, req: WSTestRequest) -> WSTestResult:
"""执行WebSocket测试""" """执行WebSocket测试"""
# 获取测试用例
test_list = self.get_ws_test_list() test_list = self.get_ws_test_list()
test_case = None test_case = None
@ -539,7 +847,6 @@ class TestService:
if not test_case: if not test_case:
raise ValueError(f"Test case not found: {req.id}") raise ValueError(f"Test case not found: {req.id}")
# 使用自定义标的
symbols = req.symbols if req.symbols else test_case.symbols symbols = req.symbols if req.symbols else test_case.symbols
result = WSTestResult( result = WSTestResult(
@ -549,7 +856,6 @@ class TestService:
messages=[] messages=[]
) )
# 连接WebSocket
start_time = datetime.now() start_time = datetime.now()
try: try:
@ -560,14 +866,12 @@ class TestService:
result.latency = int((datetime.now() - start_time).total_seconds() * 1000) result.latency = int((datetime.now() - start_time).total_seconds() * 1000)
result.success = True result.success = True
# 发送订阅消息
msg = { msg = {
"action": test_case.action, "action": test_case.action,
"symbols": symbols "symbols": symbols
} }
await ws.send(json.dumps(msg)) await ws.send(json.dumps(msg))
# 等待响应最多3条消息
for _ in range(3): for _ in range(3):
try: try:
msg_data = await asyncio.wait_for(ws.recv(), timeout=5) msg_data = await asyncio.wait_for(ws.recv(), timeout=5)
@ -610,6 +914,13 @@ class TestService:
if len(self.api_history) > self.history_size: if len(self.api_history) > self.history_size:
self.api_history = self.api_history[-self.history_size:] self.api_history = self.api_history[-self.history_size:]
def _add_internal_history(self, result: APITestResult):
"""添加内部接口测试历史"""
with self.lock:
self.internal_history.append(result)
if len(self.internal_history) > self.history_size:
self.internal_history = self.internal_history[-self.history_size:]
def _add_ws_history(self, result: WSTestResult): def _add_ws_history(self, result: WSTestResult):
"""添加WebSocket测试历史""" """添加WebSocket测试历史"""
with self.lock: with self.lock:

@ -1,20 +0,0 @@
#!/usr/bin/env python3
import urllib.request
import json
req = urllib.request.Request(
'http://localhost:8080/v1/admin/adapters',
headers={'X-Admin-Token': ''}
)
try:
response = urllib.request.urlopen(req, timeout=10)
data = json.loads(response.read().decode())
print('✓ Success!')
print(f"Code: {data['code']}")
print(f"Message: {data['message']}")
print(f"Adapters count: {len(data['data']['adapters'])}")
for adapter in data['data']['adapters']:
print(f" - {adapter['name']}: {adapter['status']} ({adapter['type']})")
except Exception as e:
print(f'✗ Error: {e}')

@ -1,19 +0,0 @@
#!/usr/bin/env python3
import urllib.request
import json
req = urllib.request.Request(
'http://localhost:8080/v1/admin/adapters',
headers={'X-Admin-Token': ''}
)
try:
response = urllib.request.urlopen(req, timeout=10)
data = json.loads(response.read().decode())
print('Success!')
print("Code:", data['code'])
print("Adapters count:", len(data['data']['adapters']))
for adapter in data['data']['adapters']:
print(" -", adapter['name'] + ":", adapter['status'])
except Exception as e:
print('Error:', e)

@ -1,46 +0,0 @@
"""测试数据库连接"""
import os
import sys
# 添加项目根目录到路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from app.repositories.database import init_db, engine
from sqlalchemy import text
print("=" * 50)
print("数据库连接测试")
print("=" * 50)
try:
# 测试连接
with engine.connect() as conn:
result = conn.execute(text("SELECT version()"))
version = result.scalar()
print(f"✅ 数据库连接成功")
print(f" PostgreSQL 版本: {version}")
# 初始化数据库(创建表)
print("\n正在初始化数据库表...")
init_db()
print("✅ 数据库表创建成功")
# 显示所有表
from sqlalchemy import inspect
inspector = inspect(engine)
tables = inspector.get_table_names()
print(f"\n已创建的表 ({len(tables)} 个):")
for table in sorted(tables):
print(f" - {table}")
print("\n" + "=" * 50)
print("数据库初始化完成!")
print("=" * 50)
except Exception as e:
print(f"❌ 错误: {e}")
print("\n请检查:")
print("1. Docker 是否已启动: docker ps")
print("2. 数据库端口是否正确: netstat -ano | findstr 5432")
print("3. 数据库密码是否正确")
sys.exit(1)

@ -1,112 +0,0 @@
"""测试股票K线接口返回的字段"""
import requests
import json
# API 配置
BASE_URL = "http://localhost:8080/v1"
API_KEY = ""
# 测试获取股票K线
def test_stock_klines():
"""测试股票K线接口返回的字段"""
url = f"{BASE_URL}/stock/klines/000001.SZ"
headers = {"X-API-Key": API_KEY}
params = {
"start": "20260301",
"end": "20260310",
"freq": "1d"
}
print(f"\n{'='*60}")
print(f"测试接口: GET {url}")
print(f"{'='*60}")
try:
response = requests.get(url, headers=headers, params=params)
data = response.json()
if data.get("code") == 0:
kline_data = data.get("data", {})
items = kline_data.get("items", [])
print(f"\n标的: {kline_data.get('symbol')}")
print(f"周期: {kline_data.get('freq')}")
print(f"数据条数: {len(items)}")
print(f"\n{'='*60}")
if items:
# 显示第一条数据的完整字段
first_item = items[0]
print("\n第一条数据详情:")
print(f"{'-'*60}")
# 基础字段
print(f"时间戳: {first_item.get('time')}")
print(f"开盘价: {first_item.get('open')}")
print(f"最高价: {first_item.get('high')}")
print(f"最低价: {first_item.get('low')}")
print(f"收盘价: {first_item.get('close')}")
print(f"成交量: {first_item.get('volume')}")
print(f"成交额: {first_item.get('amount')}")
# 扩展字段
print(f"\n扩展字段:")
print(f" 交易日: {first_item.get('trade_date')}")
print(f" 是否涨停: {first_item.get('is_limit_up')}")
print(f" 是否跌停: {first_item.get('is_limit_down')}")
print(f" 总市值: {first_item.get('total_market_cap')}")
print(f" 流通市值: {first_item.get('float_market_cap')}")
print(f" 机构持仓占比: {first_item.get('inst_holding_ratio')}")
print(f" 可交易日数: {first_item.get('trading_days')}")
print(f" 创建时间: {first_item.get('created_at')}")
# 验证所有字段是否存在
expected_fields = [
'symbol', 'time', 'open', 'high', 'low', 'close',
'volume', 'amount', 'trade_date', 'is_limit_up',
'is_limit_down', 'total_market_cap', 'float_market_cap',
'inst_holding_ratio', 'trading_days', 'created_at'
]
print(f"\n{'='*60}")
print("字段完整性检查:")
print(f"{'-'*60}")
missing_fields = []
for field in expected_fields:
if field in first_item:
print(f"{field}")
else:
print(f"{field} (缺失)")
missing_fields.append(field)
if missing_fields:
print(f"\n缺失字段: {', '.join(missing_fields)}")
else:
print(f"\n所有字段都存在!")
return True
else:
print("没有获取到数据")
return False
else:
print(f"请求失败: {data.get('message')}")
return False
except Exception as e:
print(f"请求异常: {e}")
return False
if __name__ == "__main__":
print("\n" + "="*60)
print("股票K线接口字段测试")
print("="*60)
success = test_stock_klines()
print(f"\n{'='*60}")
if success:
print("测试完成!")
else:
print("测试失败!")
print("="*60 + "\n")

@ -1,133 +0,0 @@
"""测试K线数据扩展字段获取"""
import asyncio
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from app.adapters.amazingdata_adapter import AmazingDataAdapter
from datetime import datetime
async def test_klines_with_extended_fields():
"""测试获取带有扩展字段的K线数据"""
print("\n" + "="*60)
print("测试K线数据扩展字段")
print("="*60)
adapter = AmazingDataAdapter()
# 连接配置(请根据实际情况修改)
config = {
"username": os.getenv("AMAZINGDATA_USERNAME", ""),
"password": os.getenv("AMAZINGDATA_PASSWORD", ""),
"host": os.getenv("AMAZINGDATA_HOST", "140.206.44.234"),
"port": int(os.getenv("AMAZINGDATA_PORT", "8600")),
"local_path": "./amazing_data_cache/",
"use_local_cache": True
}
try:
# 连接适配器
print("\n[1/3] 正在连接 AmazingData...")
await adapter.connect(config)
print("✓ 连接成功")
# 获取K线数据
symbol = "000001.SZ" # 平安银行
start_date = "20260301"
end_date = "20260310"
print(f"\n[2/3] 正在获取 {symbol} 的K线数据 ({start_date} ~ {end_date})...")
klines = await adapter.fetch_klines(symbol, start_date, end_date, "1d")
print(f"✓ 获取到 {len(klines)} 条K线数据")
# 显示第一条数据的完整信息
if klines:
print(f"\n[3/3] 数据字段验证")
print("-"*60)
k = klines[0]
print(f"\n标的代码: {k.symbol}")
print(f"交易日: {k.trade_date}")
print(f"时间戳: {datetime.fromtimestamp(k.time)}")
print(f"\n基础行情:")
print(f" 开盘价: {k.open}")
print(f" 最高价: {k.high}")
print(f" 最低价: {k.low}")
print(f" 收盘价: {k.close}")
print(f" 成交量: {k.volume}")
print(f" 成交额: {k.amount}")
print(f"\n扩展字段:")
print(f" 是否涨停: {k.is_limit_up} {'' if k.is_limit_up is not None else ''}")
print(f" 是否跌停: {k.is_limit_down} {'' if k.is_limit_down is not None else ''}")
print(f" 总市值: {k.total_market_cap:,.0f}" if k.total_market_cap else " 总市值: None ✗")
print(f" 流通市值: {k.float_market_cap:,.0f}" if k.float_market_cap else " 流通市值: None ✗")
print(f" 机构持仓占比: {k.inst_holding_ratio}%" if k.inst_holding_ratio else " 机构持仓占比: None")
print(f" 可交易日数: {k.trading_days} {'' if k.trading_days else ''}")
# 验证字段完整性
print(f"\n{'='*60}")
print("字段完整性检查:")
print("-"*60)
checks = [
("symbol", k.symbol is not None),
("time", k.time > 0),
("open", k.open > 0),
("high", k.high > 0),
("low", k.low > 0),
("close", k.close > 0),
("volume", k.volume > 0),
("amount", k.amount > 0),
("trade_date", k.trade_date is not None),
("is_limit_up", k.is_limit_up is not None),
("is_limit_down", k.is_limit_down is not None),
("total_market_cap", k.total_market_cap is not None and k.total_market_cap > 0),
("float_market_cap", k.float_market_cap is not None and k.float_market_cap > 0),
("trading_days", k.trading_days is not None and k.trading_days > 0),
]
passed = 0
for field, check in checks:
status = "" if check else ""
print(f" {status} {field}")
if check:
passed += 1
print(f"\n通过: {passed}/{len(checks)}")
# 显示涨跌停判断逻辑验证
print(f"\n{'='*60}")
print("涨跌停判断示例:")
print("-"*60)
for k in klines[:3]: # 显示前3条
limit_status = ""
if k.is_limit_up:
limit_status = "📈 涨停"
elif k.is_limit_down:
limit_status = "📉 跌停"
else:
limit_status = ""
print(f" {k.trade_date}: 收盘{k.close} {limit_status}")
# 断开连接
await adapter.close()
print(f"\n{'='*60}")
print("测试完成!")
print("="*60 + "\n")
return True
except Exception as e:
print(f"\n✗ 测试失败: {e}")
import traceback
traceback.print_exc()
return False
if __name__ == "__main__":
success = asyncio.run(test_klines_with_extended_fields())
sys.exit(0 if success else 1)

Binary file not shown.

@ -1,17 +0,0 @@
#!/usr/bin/env python3
import urllib.request
import json
# Test source status
req = urllib.request.Request(
'http://localhost:8080/v1/admin/source/status',
headers={'X-API-Key': ''}
)
try:
response = urllib.request.urlopen(req, timeout=10)
data = json.loads(response.read().decode())
print('Source Status:')
print(json.dumps(data, indent=2, ensure_ascii=False))
except Exception as e:
print('Error:', e)
Loading…
Cancel
Save