feat: add POST /rebalance/calculate endpoint with manual prices
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8c00359a50
commit
bffca88ce9
@ -16,6 +16,7 @@ from app.schemas.portfolio import (
|
|||||||
HoldingCreate, HoldingResponse, HoldingWithValue,
|
HoldingCreate, HoldingResponse, HoldingWithValue,
|
||||||
TransactionCreate, TransactionResponse,
|
TransactionCreate, TransactionResponse,
|
||||||
RebalanceResponse, RebalanceSimulationRequest, RebalanceSimulationResponse,
|
RebalanceResponse, RebalanceSimulationRequest, RebalanceSimulationResponse,
|
||||||
|
RebalanceCalculateRequest, RebalanceCalculateResponse,
|
||||||
)
|
)
|
||||||
from app.services.rebalance import RebalanceService
|
from app.services.rebalance import RebalanceService
|
||||||
|
|
||||||
@ -319,6 +320,31 @@ async def simulate_rebalance(
|
|||||||
return service.calculate_rebalance(portfolio, additional_amount=data.additional_amount)
|
return service.calculate_rebalance(portfolio, additional_amount=data.additional_amount)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{portfolio_id}/rebalance/calculate", response_model=RebalanceCalculateResponse)
|
||||||
|
async def calculate_rebalance_manual(
|
||||||
|
portfolio_id: int,
|
||||||
|
data: RebalanceCalculateRequest,
|
||||||
|
current_user: CurrentUser,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""Calculate rebalancing with manual prices and strategy selection."""
|
||||||
|
portfolio = _get_portfolio(db, portfolio_id, current_user.id)
|
||||||
|
|
||||||
|
if data.strategy == "additional_buy" and not data.additional_amount:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="additional_amount is required for additional_buy strategy"
|
||||||
|
)
|
||||||
|
|
||||||
|
service = RebalanceService(db)
|
||||||
|
return service.calculate_with_prices(
|
||||||
|
portfolio,
|
||||||
|
strategy=data.strategy,
|
||||||
|
manual_prices=data.prices,
|
||||||
|
additional_amount=data.additional_amount,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{portfolio_id}/detail", response_model=PortfolioDetail)
|
@router.get("/{portfolio_id}/detail", response_model=PortfolioDetail)
|
||||||
async def get_portfolio_detail(
|
async def get_portfolio_detail(
|
||||||
portfolio_id: int,
|
portfolio_id: int,
|
||||||
|
|||||||
@ -51,7 +51,7 @@ def test_calculate_rebalance_with_manual_prices(client: TestClient, auth_headers
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
data = response.json()
|
data = response.json()
|
||||||
assert data["portfolio_id"] == pid
|
assert data["portfolio_id"] == pid
|
||||||
assert data["total_assets"] > 0
|
assert float(data["total_assets"]) > 0
|
||||||
assert len(data["items"]) == 2
|
assert len(data["items"]) == 2
|
||||||
# Verify items have required fields
|
# Verify items have required fields
|
||||||
item = data["items"][0]
|
item = data["items"][0]
|
||||||
@ -78,8 +78,8 @@ def test_calculate_additional_buy_strategy(client: TestClient, auth_headers):
|
|||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
data = response.json()
|
data = response.json()
|
||||||
assert data["total_assets"] > 0
|
assert float(data["total_assets"]) > 0
|
||||||
assert data["available_to_buy"] == 1000000
|
assert float(data["available_to_buy"]) == 1000000
|
||||||
# Additional buy should never have "sell" actions
|
# Additional buy should never have "sell" actions
|
||||||
for item in data["items"]:
|
for item in data["items"]:
|
||||||
assert item["action"] in ("buy", "hold")
|
assert item["action"] in ("buy", "hold")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user