eabc96371b
- Updated .env.example to recommend using docker.env and secrets for sensitive data. - Modified .gitignore to include docker.env and secrets directory. - Adjusted docker-compose.yml to utilize environment files and updated port mappings. - Implemented read_env_or_file utility for better environment variable management. - Refactored portfolio and trades routers to use the new utility for fetching environment variables. - Added integration and phase 2 test updates for new environment variable handling. - Created new documentation for Docker operations and secret management. - Added placeholder files for Docker secrets and configuration.
93 lines
2.5 KiB
Python
93 lines
2.5 KiB
Python
import os
|
|
import time
|
|
|
|
from fastapi import APIRouter, Request
|
|
from fastapi.responses import HTMLResponse
|
|
from fastapi.templating import Jinja2Templates
|
|
from ib_async import IB
|
|
|
|
import dependencies
|
|
|
|
router = APIRouter(prefix="/portfolio")
|
|
templates = Jinja2Templates(directory="templates")
|
|
|
|
|
|
def _extract_summary_values(summary) -> dict:
|
|
keys = {"NetLiquidation", "TotalCashValue", "UnrealizedPnL", "RealizedPnL"}
|
|
result = {}
|
|
for item in summary:
|
|
if item.tag in keys:
|
|
try:
|
|
result[item.tag] = float(item.value)
|
|
except (ValueError, TypeError):
|
|
result[item.tag] = item.value
|
|
return result
|
|
|
|
|
|
def _extract_cached_account_values(ib) -> dict:
|
|
getter = getattr(ib, "accountValues", None)
|
|
if getter is None:
|
|
return {}
|
|
return _extract_summary_values(getter() or [])
|
|
|
|
|
|
async def _read_summary_values(ib) -> dict:
|
|
result = _extract_cached_account_values(ib)
|
|
if result:
|
|
return result
|
|
|
|
summary = await ib.reqAccountSummaryAsync()
|
|
result = _extract_summary_values(summary)
|
|
if result:
|
|
return result
|
|
|
|
return _extract_cached_account_values(ib)
|
|
|
|
|
|
async def _fetch_summary_with_fallback() -> dict:
|
|
ib = dependencies.get_ib()
|
|
result = await _read_summary_values(ib)
|
|
if result:
|
|
return result
|
|
|
|
# A fresh client recovers account summary when the shared session returns an empty snapshot.
|
|
fallback_ib = IB()
|
|
host = os.getenv("IBKR_HOST", "127.0.0.1")
|
|
port = int(os.getenv("IBKR_PORT", "7497"))
|
|
client_id = int(time.time()) % 100000 + 5000
|
|
try:
|
|
await fallback_ib.connectAsync(host, port, clientId=client_id)
|
|
return await _read_summary_values(fallback_ib)
|
|
finally:
|
|
fallback_ib.disconnect()
|
|
|
|
|
|
@router.get("", response_class=HTMLResponse)
|
|
async def portfolio_page(request: Request):
|
|
return templates.TemplateResponse(request, "portfolio.html", {})
|
|
|
|
|
|
@router.get("/data")
|
|
async def portfolio_data():
|
|
try:
|
|
ib = dependencies.get_ib()
|
|
positions = ib.positions()
|
|
return [
|
|
{
|
|
"symbol": pos.contract.symbol,
|
|
"position": pos.position,
|
|
"avg_cost": pos.avgCost,
|
|
}
|
|
for pos in positions
|
|
]
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
@router.get("/pnl")
|
|
async def portfolio_pnl():
|
|
try:
|
|
return await _fetch_summary_with_fallback()
|
|
except Exception as e:
|
|
return {"error": str(e)}
|