Add live broker positions to portfolio API
This commit is contained in:
parent
c41f6f2411
commit
565de64459
@ -22,9 +22,11 @@ from app.services.groww_service import (
|
||||
fetch_funds as fetch_groww_funds,
|
||||
fetch_holdings as fetch_groww_holdings,
|
||||
fetch_ltp as fetch_groww_ltp,
|
||||
fetch_positions as fetch_groww_positions,
|
||||
fetch_profile as fetch_groww_profile,
|
||||
generate_access_token,
|
||||
normalize_holding as normalize_groww_holding,
|
||||
normalize_position as normalize_groww_position,
|
||||
)
|
||||
from app.services.groww_storage import get_session as get_groww_session
|
||||
from app.services.live_equity_service import capture_live_equity_snapshot, get_live_equity_curve
|
||||
@ -35,7 +37,9 @@ from app.services.zerodha_service import (
|
||||
exchange_request_token,
|
||||
fetch_funds as fetch_zerodha_funds,
|
||||
fetch_holdings as fetch_zerodha_holdings,
|
||||
fetch_positions as fetch_zerodha_positions,
|
||||
normalize_holding as normalize_zerodha_holding,
|
||||
normalize_position as normalize_zerodha_position,
|
||||
)
|
||||
from app.services.zerodha_storage import (
|
||||
clear_session as clear_zerodha_session,
|
||||
@ -181,7 +185,13 @@ def _fetch_normalized_groww_holdings(access_token: str) -> list[dict]:
|
||||
items = fetch_groww_holdings(access_token)
|
||||
holdings: list[dict] = []
|
||||
for item in items:
|
||||
normalized = normalize_groww_holding(item)
|
||||
normalized = _normalize_enrich_groww_item(access_token, item, normalize_groww_holding)
|
||||
holdings.append(normalized)
|
||||
return holdings
|
||||
|
||||
|
||||
def _normalize_enrich_groww_item(access_token: str, item: dict, normalizer) -> dict:
|
||||
normalized = normalizer(item)
|
||||
tradingsymbol = _groww_holding_tradingsymbol(normalized)
|
||||
exchange = _groww_holding_exchange(normalized)
|
||||
segment = _groww_holding_segment(normalized)
|
||||
@ -202,13 +212,24 @@ def _fetch_normalized_groww_holdings(access_token: str) -> list[dict]:
|
||||
)
|
||||
normalized["close_price"] = normalized["last_price"]
|
||||
normalized["holding_value"] = normalized.get("effective_quantity", 0) * normalized["last_price"]
|
||||
normalized["display_pnl"] = normalized.get("effective_quantity", 0) * (
|
||||
normalized["last_price"] - normalized.get("average_price", 0)
|
||||
normalized["display_pnl"] = _first_number(
|
||||
normalized.get("display_pnl"),
|
||||
normalized.get("effective_quantity", 0)
|
||||
* (normalized["last_price"] - normalized.get("average_price", 0)),
|
||||
default=0.0,
|
||||
)
|
||||
except GrowwApiError:
|
||||
pass
|
||||
holdings.append(normalized)
|
||||
return holdings
|
||||
return normalized
|
||||
|
||||
|
||||
def _fetch_normalized_groww_positions(access_token: str) -> list[dict]:
|
||||
items = fetch_groww_positions(access_token)
|
||||
positions: list[dict] = []
|
||||
for item in items:
|
||||
normalized = _normalize_enrich_groww_item(access_token, item, normalize_groww_position)
|
||||
positions.append(normalized)
|
||||
return positions
|
||||
|
||||
|
||||
def _normalize_groww_funds(data: dict | None) -> dict:
|
||||
@ -603,6 +624,33 @@ async def broker_holdings(request: Request):
|
||||
raise HTTPException(status_code=400, detail=f"Unsupported broker: {broker_name}")
|
||||
|
||||
|
||||
@router.get("/positions")
|
||||
async def broker_positions(request: Request):
|
||||
user = _require_user(request)
|
||||
_entry, broker_name = _resolve_connected_broker(user["id"])
|
||||
if broker_name == "ZERODHA":
|
||||
session = get_zerodha_session(user["id"])
|
||||
if not session:
|
||||
raise HTTPException(status_code=400, detail="Zerodha is not connected")
|
||||
try:
|
||||
data = fetch_zerodha_positions(session["api_key"], session["access_token"])
|
||||
except KiteApiError as exc:
|
||||
_raise_zerodha_error(user["id"], exc)
|
||||
return {"broker": broker_name, "positions": [normalize_zerodha_position(item) for item in data]}
|
||||
|
||||
if broker_name == "GROWW":
|
||||
session = get_groww_session(user["id"])
|
||||
if not session or not session.get("access_token"):
|
||||
raise HTTPException(status_code=400, detail="Groww is not connected")
|
||||
try:
|
||||
positions = _fetch_normalized_groww_positions(session["access_token"])
|
||||
except GrowwApiError as exc:
|
||||
_raise_groww_error(user["id"], exc)
|
||||
return {"broker": broker_name, "positions": positions}
|
||||
|
||||
raise HTTPException(status_code=400, detail=f"Unsupported broker: {broker_name}")
|
||||
|
||||
|
||||
@router.get("/funds")
|
||||
async def broker_funds(request: Request):
|
||||
user = _require_user(request)
|
||||
|
||||
@ -358,3 +358,9 @@ def normalize_holding(item: dict | None) -> dict:
|
||||
entry["display_pnl"] = quantity * (last_price - average_price)
|
||||
entry["holding_value"] = quantity * last_price
|
||||
return entry
|
||||
|
||||
|
||||
def normalize_position(item: dict | None) -> dict:
|
||||
entry = normalize_holding(item)
|
||||
entry["product"] = _first_text(entry.get("product"), entry.get("product_type"), default="CNC").upper()
|
||||
return entry
|
||||
|
||||
@ -157,6 +157,19 @@ def fetch_holdings(api_key: str, access_token: str) -> list:
|
||||
return response.get("data", [])
|
||||
|
||||
|
||||
def fetch_positions(api_key: str, access_token: str) -> list:
|
||||
url = f"{KITE_API_BASE}/portfolio/positions"
|
||||
response = _request("GET", url, headers=_auth_headers(api_key, access_token))
|
||||
data = response.get("data", {})
|
||||
if isinstance(data, dict):
|
||||
net_positions = data.get("net")
|
||||
if isinstance(net_positions, list):
|
||||
return net_positions
|
||||
if isinstance(data, list):
|
||||
return data
|
||||
return []
|
||||
|
||||
|
||||
def fetch_funds(api_key: str, access_token: str) -> dict:
|
||||
url = f"{KITE_API_BASE}/user/margins"
|
||||
response = _request("GET", url, headers=_auth_headers(api_key, access_token))
|
||||
@ -260,6 +273,55 @@ def fetch_order_history(api_key: str, access_token: str, order_id: str) -> list:
|
||||
return response.get("data", [])
|
||||
|
||||
|
||||
def position_quantity(item: dict | None) -> float:
|
||||
entry = item or {}
|
||||
return _first_float(
|
||||
entry.get("net_quantity"),
|
||||
entry.get("quantity"),
|
||||
entry.get("qty"),
|
||||
default=0.0,
|
||||
)
|
||||
|
||||
|
||||
def position_average_price(item: dict | None) -> float:
|
||||
entry = item or {}
|
||||
return _first_float(
|
||||
entry.get("average_price"),
|
||||
entry.get("avg_price"),
|
||||
entry.get("buy_price"),
|
||||
default=0.0,
|
||||
)
|
||||
|
||||
|
||||
def position_last_price(item: dict | None) -> float:
|
||||
entry = item or {}
|
||||
return _first_float(
|
||||
entry.get("last_price"),
|
||||
entry.get("close_price"),
|
||||
entry.get("average_price"),
|
||||
entry.get("avg_price"),
|
||||
default=0.0,
|
||||
)
|
||||
|
||||
|
||||
def normalize_position(item: dict | None) -> dict:
|
||||
entry = dict(item or {})
|
||||
quantity = position_quantity(entry)
|
||||
average_price = position_average_price(entry)
|
||||
last_price = position_last_price(entry)
|
||||
pnl = _first_float(entry.get("pnl"), default=quantity * (last_price - average_price))
|
||||
entry["settled_quantity"] = quantity
|
||||
entry["t1_quantity"] = 0.0
|
||||
entry["effective_quantity"] = quantity
|
||||
entry["quantity"] = quantity
|
||||
entry["average_price"] = average_price
|
||||
entry["last_price"] = last_price
|
||||
entry["close_price"] = last_price
|
||||
entry["display_pnl"] = pnl
|
||||
entry["holding_value"] = quantity * last_price
|
||||
return entry
|
||||
|
||||
|
||||
def cancel_order(
|
||||
api_key: str,
|
||||
access_token: str,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user