Coverage for api/v1/endpoints/assets.py: 40.68%
177 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-13 18:58 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-13 18:58 +0000
1"""
2资产相关 API 端点(修复版本)
3"""
5from decimal import Decimal
6from typing import List, Optional
8from fastapi import APIRouter, Depends, HTTPException, Query, status
9from fastapi.responses import JSONResponse
10from pydantic import BaseModel
12from core.models.asset import (AssetOverview, AssetType, CurrencyType,
13 MarketType, SimulatedAssetCreate,
14 SimulatedAssetOverview, SimulatedAssetResponse,
15 SimulatedAssetUpdate, SimulatedPositionCreate,
16 SimulatedPositionResponse,
17 SimulatedPositionUpdate, SyncRequest,
18 SyncResponse, UserAssetCreate,
19 UserAssetResponse, UserAssetUpdate,
20 UserPositionCreate, UserPositionResponse,
21 UserPositionUpdate)
24# 前端输入模型(不需要user_id和asset_type)
25class SimulatedPositionInput(BaseModel):
26 symbol: str
27 symbol_name: str
28 quantity: Decimal
29 cost_price: Decimal
30 current_price: Optional[Decimal] = None
31 market: MarketType
32 currency: CurrencyType
35from core.data_source.adapters.asset_adapter import AssetAdapter
36from core.data_source.adapters.data_adapter import DataAdapter
37from core.middleware.auth_middleware import get_current_user
38from core.models.user import UserResponse
39from core.repositories.asset_repository import AssetRepository
40from core.repositories.stock_repository import StockRepository
42router = APIRouter(prefix="/assets", tags=["assets"])
45# 用户资产相关端点
46@router.post("/", response_model=UserAssetResponse, status_code=status.HTTP_201_CREATED)
47def create_user_asset(
48 asset: UserAssetCreate, current_user: UserResponse = Depends(get_current_user)
49):
50 """创建用户资产(使用新的统一架构)"""
51 try:
52 adapter = AssetAdapter(current_user.id)
53 return adapter.create_user_asset(asset)
54 except Exception as e:
55 raise HTTPException(
56 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
57 detail=f"创建用户资产失败: {str(e)}",
58 )
61@router.get("/", response_model=Optional[UserAssetResponse])
62def get_user_asset(current_user: UserResponse = Depends(get_current_user)):
63 """获取用户资产(使用新的统一架构)"""
64 try:
65 adapter = AssetAdapter(current_user.id)
66 return adapter.get_user_asset(current_user.id)
67 except Exception as e:
68 raise HTTPException(
69 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
70 detail=f"获取用户资产失败: {str(e)}",
71 )
74@router.put("/", response_model=Optional[UserAssetResponse])
75def update_user_asset(
76 asset_update: UserAssetUpdate,
77 current_user: UserResponse = Depends(get_current_user),
78):
79 """更新用户资产(使用新的统一架构)"""
80 try:
81 adapter = AssetAdapter(current_user.id)
82 return adapter.update_user_asset(current_user.id, asset_update)
83 except Exception as e:
84 raise HTTPException(
85 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
86 detail=f"更新用户资产失败: {str(e)}",
87 )
90@router.delete("/", status_code=status.HTTP_204_NO_CONTENT)
91def delete_user_asset(current_user: UserResponse = Depends(get_current_user)):
92 """删除用户资产(使用新的统一架构)"""
93 try:
94 adapter = AssetAdapter(current_user.id)
95 success = adapter.delete_user_asset(current_user.id)
96 if not success:
97 raise HTTPException(
98 status_code=status.HTTP_404_NOT_FOUND, detail="用户资产不存在"
99 )
100 except Exception as e:
101 raise HTTPException(
102 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
103 detail=f"删除用户资产失败: {str(e)}",
104 )
107# 用户持仓相关端点
108@router.post(
109 "/positions",
110 response_model=UserPositionResponse,
111 status_code=status.HTTP_201_CREATED,
112)
113def create_user_position(
114 position: UserPositionCreate, current_user: UserResponse = Depends(get_current_user)
115):
116 """创建用户持仓(使用新的统一架构)"""
117 try:
118 adapter = AssetAdapter(current_user.id)
119 return adapter.create_position(position)
120 except Exception as e:
121 raise HTTPException(
122 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
123 detail=f"创建用户持仓失败: {str(e)}",
124 )
127@router.get("/positions", response_model=List[UserPositionResponse])
128def get_user_positions(current_user: UserResponse = Depends(get_current_user)):
129 """获取用户持仓列表(使用新的统一架构)"""
130 try:
131 adapter = AssetAdapter(current_user.id)
132 return adapter.get_positions()
133 except Exception as e:
134 raise HTTPException(
135 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
136 detail=f"获取用户持仓失败: {str(e)}",
137 )
140@router.put("/positions/{position_id}", response_model=Optional[UserPositionResponse])
141def update_user_position(
142 position_id: str,
143 position_update: UserPositionUpdate,
144 current_user: UserResponse = Depends(get_current_user),
145):
146 """更新用户持仓(使用新的统一架构)"""
147 try:
148 adapter = AssetAdapter(current_user.id)
149 return adapter.update_position(position_id, position_update)
150 except Exception as e:
151 raise HTTPException(
152 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
153 detail=f"更新用户持仓失败: {str(e)}",
154 )
157@router.delete("/positions/{position_id}", status_code=status.HTTP_204_NO_CONTENT)
158def delete_user_position(
159 position_id: str, current_user: UserResponse = Depends(get_current_user)
160):
161 """删除用户持仓(使用新的统一架构)"""
162 try:
163 adapter = AssetAdapter(current_user.id)
164 success = adapter.delete_position(position_id)
165 if not success:
166 raise HTTPException(
167 status_code=status.HTTP_404_NOT_FOUND, detail="用户持仓不存在"
168 )
169 except Exception as e:
170 raise HTTPException(
171 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
172 detail=f"删除用户持仓失败: {str(e)}",
173 )
176# 资产概览端点
177@router.get("/overview", response_model=Optional[AssetOverview])
178def get_asset_overview(
179 force_refresh: bool = Query(False, description="是否强制刷新数据"),
180 current_user: UserResponse = Depends(get_current_user),
181):
182 """获取资产概览(使用新的统一架构)"""
183 try:
184 # 使用新的业务适配器
185 adapter = AssetAdapter(current_user.id)
186 return adapter.get_or_create_asset_overview(force_refresh=force_refresh)
187 except Exception as e:
188 raise HTTPException(
189 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
190 detail=f"获取资产概览失败: {str(e)}",
191 )
194# 模拟资产相关端点
195@router.post(
196 "/simulated/",
197 response_model=SimulatedAssetResponse,
198 status_code=status.HTTP_201_CREATED,
199)
200def create_simulated_asset(
201 asset: SimulatedAssetCreate, current_user: UserResponse = Depends(get_current_user)
202):
203 """创建模拟资产(使用新的统一架构)"""
204 try:
205 adapter = AssetAdapter(current_user.id)
206 return adapter.create_simulated_asset(asset)
207 except Exception as e:
208 raise HTTPException(
209 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
210 detail=f"创建模拟资产失败: {str(e)}",
211 )
214@router.get("/simulated/", response_model=Optional[SimulatedAssetResponse])
215def get_simulated_asset(current_user: UserResponse = Depends(get_current_user)):
216 """获取模拟资产(使用新的统一架构)"""
217 try:
218 adapter = AssetAdapter(current_user.id)
219 return adapter.get_simulated_asset()
220 except Exception as e:
221 raise HTTPException(
222 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
223 detail=f"获取模拟资产失败: {str(e)}",
224 )
227@router.put("/simulated/", response_model=Optional[SimulatedAssetResponse])
228def update_simulated_asset(
229 asset_update: SimulatedAssetUpdate,
230 current_user: UserResponse = Depends(get_current_user),
231 # asset_service: AssetService = Depends() # 已经迁移到BusinessAssetAdapter
232):
233 """更新模拟资产"""
234 try:
235 adapter = AssetAdapter(current_user.id)
236 return adapter.update_simulated_asset(current_user.id, asset_update)
237 except Exception as e:
238 raise HTTPException(
239 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
240 detail=f"更新模拟资产失败: {str(e)}",
241 )
244# 模拟持仓相关端点
245@router.post(
246 "/simulated/positions",
247 response_model=SimulatedPositionResponse,
248 status_code=status.HTTP_201_CREATED,
249)
250def create_simulated_position(
251 position_input: SimulatedPositionInput,
252 current_user: UserResponse = Depends(get_current_user),
253 # asset_service: AssetService = Depends() # 已经迁移到BusinessAssetAdapter
254):
255 """创建模拟持仓"""
256 try:
257 # 从输入构建完整的持仓对象
258 position = SimulatedPositionCreate(
259 user_id=current_user.id,
260 symbol=position_input.symbol,
261 symbol_name=position_input.symbol_name,
262 asset_type=AssetType.STOCK, # 默认设置为股票类型
263 quantity=position_input.quantity,
264 cost_price=position_input.cost_price,
265 current_price=position_input.current_price,
266 market=position_input.market,
267 currency=position_input.currency,
268 )
269 adapter = AssetAdapter(current_user.id)
270 return adapter.create_simulated_position(position)
271 except Exception as e:
272 raise HTTPException(
273 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
274 detail=f"创建模拟持仓失败: {str(e)}",
275 )
278@router.get("/simulated/positions", response_model=List[SimulatedPositionResponse])
279def get_simulated_positions(
280 current_user: UserResponse = Depends(get_current_user),
281 # asset_service: AssetService = Depends() # 已经迁移到BusinessAssetAdapter
282):
283 """获取模拟持仓列表"""
284 try:
285 adapter = AssetAdapter(current_user.id)
286 return adapter.get_simulated_positions(current_user.id)
287 except Exception as e:
288 raise HTTPException(
289 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
290 detail=f"获取模拟持仓失败: {str(e)}",
291 )
294@router.put(
295 "/simulated/positions/{position_id}",
296 response_model=Optional[SimulatedPositionResponse],
297)
298def update_simulated_position(
299 position_id: str,
300 position_update: SimulatedPositionUpdate,
301 current_user: UserResponse = Depends(get_current_user),
302 # asset_service: AssetService = Depends() # 已经迁移到BusinessAssetAdapter
303):
304 """更新模拟持仓"""
305 try:
306 adapter = AssetAdapter(current_user.id)
307 return adapter.update_simulated_position(position_id, position_update)
308 except Exception as e:
309 raise HTTPException(
310 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
311 detail=f"更新模拟持仓失败: {str(e)}",
312 )
315@router.delete(
316 "/simulated/positions/{position_id}", status_code=status.HTTP_204_NO_CONTENT
317)
318def delete_simulated_position(
319 position_id: str,
320 current_user: UserResponse = Depends(get_current_user),
321 # asset_service: AssetService = Depends() # 已经迁移到BusinessAssetAdapter
322):
323 """删除模拟持仓"""
324 try:
325 adapter = AssetAdapter(current_user.id)
326 success = adapter.delete_simulated_position(position_id)
327 if not success:
328 raise HTTPException(
329 status_code=status.HTTP_404_NOT_FOUND, detail="模拟持仓不存在"
330 )
331 except Exception as e:
332 raise HTTPException(
333 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
334 detail=f"删除模拟持仓失败: {str(e)}",
335 )
338# 模拟资产概览端点
339@router.get("/simulated/overview", response_model=Optional[SimulatedAssetOverview])
340def get_simulated_asset_overview(
341 current_user: UserResponse = Depends(get_current_user),
342) -> Optional[SimulatedAssetOverview]:
343 """获取模拟资产概览(使用新的统一架构)"""
344 try:
345 # 获取模拟持仓数据
346 simulated_positions = AssetAdapter(current_user.id).get_simulated_positions(
347 current_user.id
348 )
350 # 从repository获取模拟资产概览
351 asset_repo = AssetRepository()
352 overview = asset_repo.get_simulated_asset_overview(current_user.id)
354 if not overview:
355 # 如果没有概览,创建默认的
356 overview = SimulatedAssetOverview(
357 positions=simulated_positions,
358 positions_by_currency={},
359 total_assets_by_currency={},
360 total_assets=Decimal("0"),
361 cash_assets=Decimal("0"),
362 position_assets=Decimal("0"),
363 today_pnl=Decimal("0"),
364 )
366 return overview
368 except Exception as e:
369 raise HTTPException(
370 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
371 detail=f"获取模拟资产概览失败: {str(e)}",
372 )
375# 同步相关端点
376@router.post("/sync", response_model=SyncResponse)
377def sync_from_broker(
378 sync_request: SyncRequest,
379 current_user: UserResponse = Depends(get_current_user),
380 # asset_service: AssetService = Depends() # 已经迁移到BusinessAssetAdapter
381):
382 """从券商同步资产数据"""
383 try:
384 adapter = AssetAdapter(current_user.id)
385 success = adapter.sync_from_broker(current_user.id)
386 return SyncResponse(
387 success=success, message="同步成功" if success else "同步失败"
388 )
389 except Exception as e:
390 raise HTTPException(
391 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
392 detail=f"同步资产数据失败: {str(e)}",
393 )
396@router.post("/sync-to-simulated", response_model=SyncResponse)
397def sync_to_simulated(
398 current_user: UserResponse = Depends(get_current_user),
399 # asset_service: AssetService = Depends() # 已经迁移到BusinessAssetAdapter
400):
401 """同步真实资产到模拟资产"""
402 try:
403 adapter = AssetAdapter(current_user.id)
404 success = adapter.sync_to_simulated(current_user.id)
405 return SyncResponse(
406 success=success, message="同步成功" if success else "同步失败"
407 )
408 except Exception as e:
409 raise HTTPException(
410 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
411 detail=f"同步到模拟资产失败: {str(e)}",
412 )
415@router.post("/sync-from-longport", response_model=SyncResponse)
416def sync_from_longport(current_user: UserResponse = Depends(get_current_user)):
417 """从长桥API手动同步真实资产数据(使用新的统一架构)"""
418 try:
419 # 使用新的业务适配器
420 adapter = AssetAdapter(current_user.id)
421 result = adapter.sync_from_longport(force_refresh=True)
423 if result:
424 return SyncResponse(success=True, message="从长桥API同步成功")
425 else:
426 return SyncResponse(success=False, message="从长桥API同步失败")
427 except Exception as e:
428 raise HTTPException(
429 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
430 detail=f"从长桥API同步失败: {str(e)}",
431 )