fix: 增加导入模块

master
Lxy 2 months ago
parent 7a9aad43a8
commit 6bf57ef398

@ -1,7 +1,7 @@
# API v1模块
from fastapi import APIRouter
from app.api.v1 import auth, configs, base_data, stock, future, realtime, finance, cache, test
from app.api.v1 import auth, configs, base_data, stock, future, realtime, finance, cache, test, data_import, index
api_router = APIRouter(prefix="/api/v1")
@ -10,7 +10,9 @@ api_router.include_router(configs.router, prefix="/configs", tags=["配置管理
api_router.include_router(base_data.router, prefix="/base", tags=["基础数据"])
api_router.include_router(stock.router, prefix="/stock", tags=["股票数据"])
api_router.include_router(future.router, prefix="/future", tags=["期货数据"])
api_router.include_router(index.router, prefix="/index", tags=["指数数据"])
api_router.include_router(realtime.router, prefix="/realtime", tags=["实时数据"])
api_router.include_router(finance.router, prefix="/finance", tags=["财务数据"])
api_router.include_router(cache.router, prefix="/cache", tags=["缓存管理"])
api_router.include_router(test.router, prefix="/test", tags=["测试中心"])
api_router.include_router(data_import.router, prefix="/import", tags=["数据导入"])

@ -9,6 +9,7 @@ from app.db.session import get_db
from app.schemas.base import ResponseModel
from app.schemas.kline import KlineRequest, BatchKlineRequest
from app.services.stock_service import StockService
from app.models.stock_basic import StockBasic
from app.core.security import get_current_user
from app.models.user import User
from app.utils.date_utils import parse_date
@ -25,14 +26,34 @@ async def get_stock_kline(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""获取股票K线数据"""
"""获取股票K线数据(含基础信息)"""
service = StockService(db)
code_list = [c.strip() for c in codes.split(",")]
start = parse_date(start_date)
end = parse_date(end_date)
data = service.get_kline(code_list, start, end, period)
return ResponseModel(data=data)
kline_data = service.get_kline(code_list, start, end, period)
result = {}
for code in code_list:
stock_basic = db.query(StockBasic).filter(StockBasic.code == code).first()
result[code] = {
"basic": {
"code": stock_basic.code if stock_basic else code,
"name": stock_basic.name if stock_basic else None,
"total_shares": stock_basic.total_shares if stock_basic else None,
"float_shares": stock_basic.float_shares if stock_basic else None,
"industry_index_name": stock_basic.industry_index_name if stock_basic else None,
"industry_index_code": stock_basic.industry_index_code if stock_basic else None,
"institution_hold_ratio": float(stock_basic.institution_hold_ratio) if stock_basic and stock_basic.institution_hold_ratio else None,
"industry_level3": stock_basic.industry_level3 if stock_basic else None,
"list_date": str(stock_basic.list_date) if stock_basic and stock_basic.list_date else None
},
"kline": kline_data.get(code, [])
}
return ResponseModel(data=result)
@router.post("/kline/batch", response_model=ResponseModel)
@ -41,13 +62,33 @@ async def batch_get_stock_kline(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""批量获取股票K线数据"""
"""批量获取股票K线数据(含基础信息)"""
service = StockService(db)
start = parse_date(request.start_date)
end = parse_date(request.end_date)
data = service.get_kline(request.codes, start, end, request.period)
return ResponseModel(data=data)
kline_data = service.get_kline(request.codes, start, end, request.period)
result = {}
for code in request.codes:
stock_basic = db.query(StockBasic).filter(StockBasic.code == code).first()
result[code] = {
"basic": {
"code": stock_basic.code if stock_basic else code,
"name": stock_basic.name if stock_basic else None,
"total_shares": stock_basic.total_shares if stock_basic else None,
"float_shares": stock_basic.float_shares if stock_basic else None,
"industry_index_name": stock_basic.industry_index_name if stock_basic else None,
"industry_index_code": stock_basic.industry_index_code if stock_basic else None,
"institution_hold_ratio": float(stock_basic.institution_hold_ratio) if stock_basic and stock_basic.institution_hold_ratio else None,
"industry_level3": stock_basic.industry_level3 if stock_basic else None,
"list_date": str(stock_basic.list_date) if stock_basic and stock_basic.list_date else None
},
"kline": kline_data.get(code, [])
}
return ResponseModel(data=result)
@router.get("/kline/{code}/chart", response_model=ResponseModel)
@ -59,13 +100,53 @@ async def get_stock_kline_chart(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""获取股票K线图数据ECharts格式"""
"""获取股票K线图数据ECharts格式,含基础信息"""
service = StockService(db)
start = parse_date(start_date)
end = parse_date(end_date)
data = service.get_kline_chart_data(code, start, end, period)
return ResponseModel(data=data)
chart_data = service.get_kline_chart_data(code, start, end, period)
stock_basic = db.query(StockBasic).filter(StockBasic.code == code).first()
chart_data["basic"] = {
"code": stock_basic.code if stock_basic else code,
"name": stock_basic.name if stock_basic else None,
"total_shares": stock_basic.total_shares if stock_basic else None,
"float_shares": stock_basic.float_shares if stock_basic else None,
"industry_index_name": stock_basic.industry_index_name if stock_basic else None,
"industry_index_code": stock_basic.industry_index_code if stock_basic else None,
"institution_hold_ratio": float(stock_basic.institution_hold_ratio) if stock_basic and stock_basic.institution_hold_ratio else None,
"industry_level3": stock_basic.industry_level3 if stock_basic else None,
"list_date": str(stock_basic.list_date) if stock_basic and stock_basic.list_date else None
}
return ResponseModel(data=chart_data)
@router.get("/basic/{code}", response_model=ResponseModel)
async def get_stock_basic(
code: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""获取股票基础信息"""
stock_basic = db.query(StockBasic).filter(StockBasic.code == code).first()
if not stock_basic:
return ResponseModel(code=404, message="股票不存在")
return ResponseModel(data={
"code": stock_basic.code,
"name": stock_basic.name,
"total_shares": stock_basic.total_shares,
"float_shares": stock_basic.float_shares,
"industry_index_name": stock_basic.industry_index_name,
"industry_index_code": stock_basic.industry_index_code,
"institution_hold_ratio": float(stock_basic.institution_hold_ratio) if stock_basic.institution_hold_ratio else None,
"industry_level3": stock_basic.industry_level3,
"list_date": str(stock_basic.list_date) if stock_basic.list_date else None
})
@router.get("/snapshot", response_model=ResponseModel)
@ -77,5 +158,4 @@ async def get_stock_snapshot(
current_user: User = Depends(get_current_user)
):
"""获取股票历史快照数据"""
# 这里可以实现快照数据查询
return ResponseModel(data={"message": "功能开发中"})

@ -908,6 +908,316 @@ result = wait_for_task(client, task_id)
---
## 十一、联系与支持
## 十一、指数数据接口
### 11.1 获取指数列表
**接口**: `GET /index/list`
**功能**: 获取所有指数基础信息列表
**请求参数**: 无
**响应参数**:
| 参数名 | 类型 | 说明 |
|--------|------|------|
| data | array | 指数列表 |
| data[].code | string | 指数代码 |
| data[].name | string | 指数名称 |
| data[].component_count | int | 成分个数 |
**调用示例**:
```python
response = requests.get(
'http://localhost:8000/api/v1/index/list',
headers=headers
)
indexes = response.json()['data']
```
---
### 11.2 获取指数交易数据
**接口**: `GET /index/trade`
**功能**: 获取指数交易数据(含基础信息)
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| codes | string | 是 | 指数代码列表 (逗号分隔) |
| start_date | string | 是 | 开始日期 (YYYYMMDD) |
| end_date | string | 是 | 结束日期 (YYYYMMDD) |
**响应参数**:
| 参数名 | 类型 | 说明 |
|--------|------|------|
| data.{code}.basic | object | 指数基础信息 |
| data.{code}.basic.code | string | 指数代码 |
| data.{code}.basic.name | string | 指数名称 |
| data.{code}.basic.component_count | int | 成分个数 |
| data.{code}.trades | array | 交易数据列表 |
| data.{code}.trades[].trade_date | string | 交易日期 |
| data.{code}.trades[].open | float | 开盘价 |
| data.{code}.trades[].close | float | 收盘价 |
| data.{code}.trades[].high | float | 最高价 |
| data.{code}.trades[].low | float | 最低价 |
| data.{code}.trades[].change_pct | float | 涨跌幅(%) |
| data.{code}.trades[].volume | int | 成交量 |
| data.{code}.trades[].amount | float | 成交额(百万元) |
| data.{code}.trades[].total_market_value | float | 总市值(百万元) |
| data.{code}.trades[].float_market_value | float | 流通市值(百万元) |
| data.{code}.trades[].up_count | int | 上涨家数 |
| data.{code}.trades[].down_count | int | 下跌家数 |
| data.{code}.trades[].flat_count | int | 平盘家数 |
| data.{code}.trades[].limit_up_count | int | 涨停家数 |
| data.{code}.trades[].limit_down_count | int | 跌停家数 |
| data.{code}.trades[].suspend_count | int | 停牌家数 |
| data.{code}.trades[].pe_ratio | float | 市盈率 |
| data.{code}.trades[].pe_median | float | 市盈率中位值 |
| data.{code}.trades[].is_new_high | bool | 是否创近期新高 |
| data.{code}.trades[].is_new_low | bool | 是否创近期新低 |
**调用示例**:
```python
response = requests.get(
'http://localhost:8000/api/v1/index/trade',
params={
'codes': 'BK0001,BK0002',
'start_date': '20240101',
'end_date': '20240131'
},
headers=headers
)
data = response.json()['data']
```
---
### 11.3 获取指数K线图表数据
**接口**: `GET /index/{code}/chart`
**功能**: 获取指数K线图表数据 (ECharts格式含基础信息)
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| code | string | 是 | 指数代码 (URL路径参数) |
| start_date | string | 是 | 开始日期 |
| end_date | string | 是 | 结束日期 |
**响应参数**:
| 参数名 | 类型 | 说明 |
|--------|------|------|
| data.basic | object | 指数基础信息 |
| data.basic.code | string | 指数代码 |
| data.basic.name | string | 指数名称 |
| data.basic.component_count | int | 成分个数 |
| data.categoryData | array | 日期列表 |
| data.values | array | K线值 [open, close, low, high, volume] |
| data.volumes | array | 成交量数据 |
**调用示例**:
```python
response = requests.get(
'http://localhost:8000/api/v1/index/BK0001/chart',
params={
'start_date': '20240101',
'end_date': '20240131'
},
headers=headers
)
chart_data = response.json()['data']
```
---
## 十二、数据导入接口
### 12.1 导入指数数据
**接口**: `POST /import/index-data`
**功能**: 导入指数数据(同时更新指数基础表和指数交易表)
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| file | file | 是 | Excel文件 (multipart/form-data) |
| trade_date | string | 是 | 交易日期 (YYYY-MM-DD格式Query参数) |
**Excel文件格式**:
第一行标题:
- 证券代码
- 证券名称
- 成分个数 [交易日期]最新
- 开盘价 [交易日期]最新
- 收盘价 [交易日期]最新
- 成交量 [交易日期]最新 [单位]股
- 成交额 [交易日期]最新 [单位]百万元
- 总市值 [截止日期]最新 [单位]百万元
- 自由流通市值 [交易日期]最新 [单位]百万元
- 涨跌幅 [交易日期]最新 [单位]%
- 最高价 [交易日期]最新
- 最低价 [交易日期]最新
- 上涨家数 [交易日期]最新
- 下跌家数 [交易日期]最新
- 平盘家数 [交易日期]最新
- 涨停家数 [交易日期]最新
- 跌停家数 [交易日期]最新
- 停牌家数 [交易日期]最新
- 近期创历史新高 [交易日期]最新 [近N日内]300 [复权方式]不复权
- 近期创历史新低 [交易日期]最新 [近N日内]300 [复权方式]不复权
- 市盈率PE(TTM) [交易日期]最新 [剔除规则]不调整
- 市盈率PE(TTM)中位值 [交易日期]最新 [剔除规则]不调整
**响应参数**:
| 参数名 | 类型 | 说明 |
|--------|------|------|
| data.success_count | int | 成功导入数量 |
| data.error_count | int | 失败数量 |
| data.total_count | int | 总数量 |
| data.index_basic_added | int | 新增指数基础数据数量 |
| data.index_basic_updated | int | 更新指数基础数据数量 |
| data.trade_date | string | 交易日期 |
**调用示例**:
```python
import requests
files = {'file': open('index_data.xlsx', 'rb')}
response = requests.post(
'http://localhost:8000/api/v1/import/index-data?trade_date=2024-01-31',
files=files,
headers=headers
)
result = response.json()['data']
print(f"成功: {result['success_count']}, 新增指数: {result['index_basic_added']}")
```
---
### 12.2 导入股票基础数据
**接口**: `POST /import/stock-basic`
**功能**: 导入股票基础数据
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| file | file | 是 | Excel文件 |
**Excel文件格式**:
必须列code, name, total_shares, float_shares, industry_index_name, industry_index_code, institution_hold_ratio, industry_level3, list_date
**响应参数**:
| 参数名 | 类型 | 说明 |
|--------|------|------|
| data.success_count | int | 成功导入数量 |
| data.error_count | int | 失败数量 |
| data.total_count | int | 总数量 |
---
### 12.3 导入指数基础数据
**接口**: `POST /import/index-basic`
**功能**: 导入指数基础数据
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| file | file | 是 | Excel文件 |
**Excel文件格式**:
必须列code, name, component_count
---
### 12.4 导入指数交易数据
**接口**: `POST /import/index-trade`
**功能**: 导入指数交易数据
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| file | file | 是 | Excel文件 |
**Excel文件格式**:
必须列index_code, trade_date, open, close, high, low
可选列change_pct, volume, amount, total_market_value, float_market_value, up_count, down_count, flat_count, limit_up_count, limit_down_count, suspend_count, pe_ratio, pe_median, is_new_high, is_new_low
---
## 十三、股票基础信息接口
### 13.1 获取股票基础信息
**接口**: `GET /stock/basic/{code}`
**功能**: 获取指定股票的基础信息
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| code | string | 是 | 股票代码 (URL路径参数) |
**响应参数**:
| 参数名 | 类型 | 说明 |
|--------|------|------|
| data.code | string | 股票代码 |
| data.name | string | 股票名称 |
| data.total_shares | int | 总股本 |
| data.float_shares | int | 流通股本 |
| data.industry_index_name | string | 所属东财行业指数 |
| data.industry_index_code | string | 所属东财行业指数代码 |
| data.institution_hold_ratio | float | 机构持股比例合计(%) |
| data.industry_level3 | string | 所属东财3级行业 |
| data.list_date | string | 上市日期 |
**调用示例**:
```python
response = requests.get(
'http://localhost:8000/api/v1/stock/basic/600000.SH',
headers=headers
)
stock_info = response.json()['data']
print(f"股票名称: {stock_info['name']}")
print(f"总股本: {stock_info['total_shares']}")
print(f"所属行业: {stock_info['industry_index_name']}")
```
---
## 十四、联系与支持
如有问题,请联系技术支持团队。

@ -38,6 +38,12 @@ const routes = [
component: () => import('@/views/CacheManager/index.vue'),
meta: { title: '缓存管理', icon: 'Box' }
},
{
path: 'import',
name: 'DataImport',
component: () => import('@/views/DataImport/index.vue'),
meta: { title: '数据导入', icon: 'Upload' }
},
{
path: 'test',
name: 'TestCenter',

@ -41,9 +41,26 @@
</el-form>
</el-card>
<el-card class="basic-card" v-if="stockBasic">
<template #header>
<span>股票基础信息</span>
</template>
<el-descriptions :column="3" border>
<el-descriptions-item label="股票代码">{{ stockBasic.code }}</el-descriptions-item>
<el-descriptions-item label="股票名称">{{ stockBasic.name }}</el-descriptions-item>
<el-descriptions-item label="上市日期">{{ stockBasic.list_date }}</el-descriptions-item>
<el-descriptions-item label="总股本">{{ formatShares(stockBasic.total_shares) }}</el-descriptions-item>
<el-descriptions-item label="流通股本">{{ formatShares(stockBasic.float_shares) }}</el-descriptions-item>
<el-descriptions-item label="机构持股比例">{{ stockBasic.institution_hold_ratio ? stockBasic.institution_hold_ratio + '%' : '-' }}</el-descriptions-item>
<el-descriptions-item label="所属行业">{{ stockBasic.industry_index_name }}</el-descriptions-item>
<el-descriptions-item label="行业代码">{{ stockBasic.industry_index_code }}</el-descriptions-item>
<el-descriptions-item label="三级行业">{{ stockBasic.industry_level3 }}</el-descriptions-item>
</el-descriptions>
</el-card>
<el-card class="chart-card" v-if="chartData.categoryData.length > 0">
<template #header>
<span>股票K线图 - {{ queryForm.code }}</span>
<span>股票K线图 - {{ stockBasic?.name || queryForm.code }}</span>
</template>
<div ref="chartRef" class="kline-chart"></div>
</el-card>
@ -87,6 +104,7 @@ const chartData = reactive({
volumes: [] as number[][]
})
const stockBasic = ref<any>(null)
const tableData = ref<any[]>([])
function getDefaultStartDate() {
@ -118,6 +136,17 @@ function formatVolume(row: any, column: any, value: number) {
return value.toString()
}
function formatShares(value: number) {
if (!value) return '-'
if (value >= 100000000) {
return (value / 100000000).toFixed(2) + '亿股'
}
if (value >= 10000) {
return (value / 10000).toFixed(2) + '万股'
}
return value.toString() + '股'
}
const handleQuery = async () => {
if (!queryForm.code) {
ElMessage.warning('请输入股票代码')
@ -133,6 +162,7 @@ const handleQuery = async () => {
})
if (res.data) {
stockBasic.value = res.data.basic || null
chartData.categoryData = res.data.categoryData || []
chartData.values = res.data.values || []
chartData.volumes = res.data.volumes || []
@ -255,6 +285,10 @@ window.addEventListener('resize', () => {
padding: 10px;
}
.basic-card {
margin-top: 20px;
}
.chart-card {
margin-top: 20px;
}

@ -21,6 +21,9 @@
</el-tab-pane>
</el-tabs>
</el-tab-pane>
<el-tab-pane label="指数数据查询" name="index">
<IndexQuery />
</el-tab-pane>
</el-tabs>
</div>
</template>
@ -31,6 +34,7 @@ import StockKlineQuery from './StockKlineQuery.vue'
import StockBatchQuery from './StockBatchQuery.vue'
import FutureKlineQuery from './FutureKlineQuery.vue'
import FutureBatchQuery from './FutureBatchQuery.vue'
import IndexQuery from './IndexQuery.vue'
const activeTab = ref('stock')
const stockSubTab = ref('kline')

@ -62,6 +62,7 @@ const menuItems = [
{ path: '/data-query', title: '数据查询', icon: 'DataLine' },
{ path: '/config', title: '配置管理', icon: 'Setting' },
{ path: '/cache', title: '缓存管理', icon: 'Box' },
{ path: '/import', title: '数据导入', icon: 'Upload' },
{ path: '/test', title: '测试中心', icon: 'CircleCheck' }
]

Loading…
Cancel
Save