feat(DATA-003): calculate live option greeks
This commit is contained in:
@@ -5,9 +5,10 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import logging
|
||||
import math
|
||||
from datetime import UTC, datetime
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from app.core.calculations import option_row_greeks
|
||||
from app.services.cache import CacheService
|
||||
from app.strategies.engine import StrategySelectionEngine
|
||||
|
||||
@@ -41,7 +42,7 @@ class DataService:
|
||||
"portfolio_value": round(quote["price"] * 1000, 2),
|
||||
"loan_amount": 600_000.0,
|
||||
"ltv_ratio": round(600_000.0 / max(quote["price"] * 1000, 1), 4),
|
||||
"updated_at": datetime.now(UTC).isoformat(),
|
||||
"updated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"source": quote["source"],
|
||||
}
|
||||
await self.cache.set_json(cache_key, portfolio)
|
||||
@@ -91,7 +92,7 @@ class DataService:
|
||||
|
||||
payload = {
|
||||
"symbol": ticker_symbol,
|
||||
"updated_at": datetime.now(UTC).isoformat(),
|
||||
"updated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"expirations": expirations,
|
||||
"underlying_price": quote["price"],
|
||||
"source": "yfinance",
|
||||
@@ -146,8 +147,8 @@ class DataService:
|
||||
try:
|
||||
ticker = yf.Ticker(ticker_symbol)
|
||||
chain = await asyncio.to_thread(ticker.option_chain, target_expiry)
|
||||
calls = self._normalize_option_rows(chain.calls, ticker_symbol, target_expiry, "call")
|
||||
puts = self._normalize_option_rows(chain.puts, ticker_symbol, target_expiry, "put")
|
||||
calls = self._normalize_option_rows(chain.calls, ticker_symbol, target_expiry, "call", quote["price"])
|
||||
puts = self._normalize_option_rows(chain.puts, ticker_symbol, target_expiry, "put", quote["price"])
|
||||
|
||||
if not calls and not puts:
|
||||
payload = self._fallback_options_chain(
|
||||
@@ -164,7 +165,7 @@ class DataService:
|
||||
payload = {
|
||||
"symbol": ticker_symbol,
|
||||
"selected_expiry": target_expiry,
|
||||
"updated_at": datetime.now(UTC).isoformat(),
|
||||
"updated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"expirations": expirations,
|
||||
"calls": calls,
|
||||
"puts": puts,
|
||||
@@ -210,7 +211,7 @@ class DataService:
|
||||
|
||||
return {
|
||||
"symbol": ticker,
|
||||
"updated_at": datetime.now(UTC).isoformat(),
|
||||
"updated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"paper_parameters": {
|
||||
"portfolio_value": engine.portfolio_value,
|
||||
"loan_amount": engine.loan_amount,
|
||||
@@ -247,7 +248,7 @@ class DataService:
|
||||
"price": round(last, 4),
|
||||
"change": change,
|
||||
"change_percent": change_percent,
|
||||
"updated_at": datetime.now(UTC).isoformat(),
|
||||
"updated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"source": "yfinance",
|
||||
}
|
||||
except Exception as exc: # pragma: no cover - network dependent
|
||||
@@ -264,7 +265,7 @@ class DataService:
|
||||
) -> dict[str, Any]:
|
||||
payload = {
|
||||
"symbol": symbol,
|
||||
"updated_at": datetime.now(UTC).isoformat(),
|
||||
"updated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"expirations": [],
|
||||
"underlying_price": quote["price"],
|
||||
"source": source,
|
||||
@@ -286,7 +287,7 @@ class DataService:
|
||||
options_chain = {
|
||||
"symbol": symbol,
|
||||
"selected_expiry": selected_expiry,
|
||||
"updated_at": datetime.now(UTC).isoformat(),
|
||||
"updated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"expirations": expirations,
|
||||
"calls": [],
|
||||
"puts": [],
|
||||
@@ -298,7 +299,14 @@ class DataService:
|
||||
options_chain["error"] = error
|
||||
return options_chain
|
||||
|
||||
def _normalize_option_rows(self, frame: Any, symbol: str, expiry: str, option_type: str) -> list[dict[str, Any]]:
|
||||
def _normalize_option_rows(
|
||||
self,
|
||||
frame: Any,
|
||||
symbol: str,
|
||||
expiry: str,
|
||||
option_type: str,
|
||||
underlying_price: float,
|
||||
) -> list[dict[str, Any]]:
|
||||
if frame is None or getattr(frame, "empty", True):
|
||||
return []
|
||||
|
||||
@@ -314,27 +322,22 @@ class DataService:
|
||||
implied_volatility = self._safe_float(item.get("impliedVolatility"))
|
||||
contract_symbol = str(item.get("contractSymbol") or "").strip()
|
||||
|
||||
rows.append(
|
||||
{
|
||||
"contractSymbol": contract_symbol,
|
||||
"symbol": contract_symbol or f"{symbol} {expiry} {option_type.upper()} {strike:.2f}",
|
||||
"strike": strike,
|
||||
"bid": bid,
|
||||
"ask": ask,
|
||||
"premium": last_price or self._midpoint(bid, ask),
|
||||
"lastPrice": last_price,
|
||||
"impliedVolatility": implied_volatility,
|
||||
"expiry": expiry,
|
||||
"type": option_type,
|
||||
"openInterest": int(self._safe_float(item.get("openInterest"))),
|
||||
"volume": int(self._safe_float(item.get("volume"))),
|
||||
"delta": 0.0,
|
||||
"gamma": 0.0,
|
||||
"theta": 0.0,
|
||||
"vega": 0.0,
|
||||
"rho": 0.0,
|
||||
}
|
||||
)
|
||||
row = {
|
||||
"contractSymbol": contract_symbol,
|
||||
"symbol": contract_symbol or f"{symbol} {expiry} {option_type.upper()} {strike:.2f}",
|
||||
"strike": strike,
|
||||
"bid": bid,
|
||||
"ask": ask,
|
||||
"premium": last_price or self._midpoint(bid, ask),
|
||||
"lastPrice": last_price,
|
||||
"impliedVolatility": implied_volatility,
|
||||
"expiry": expiry,
|
||||
"type": option_type,
|
||||
"openInterest": int(self._safe_float(item.get("openInterest"))),
|
||||
"volume": int(self._safe_float(item.get("volume"))),
|
||||
}
|
||||
row.update(option_row_greeks(row, underlying_price))
|
||||
rows.append(row)
|
||||
return rows
|
||||
|
||||
@staticmethod
|
||||
@@ -358,6 +361,6 @@ class DataService:
|
||||
"price": 215.0,
|
||||
"change": 0.0,
|
||||
"change_percent": 0.0,
|
||||
"updated_at": datetime.now(UTC).isoformat(),
|
||||
"updated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"source": source,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user