import json
from datetime import datetime
from typing import Optional, List
from uuid import UUID
from serenity_sdk.api.core import CallType, SerenityApi, SerenityClient
from serenity_types.risk.simulators.greek_measures import GreekMeasureRequest, GreekMeasureResponse
from serenity_types.portfolio.core import Balance
from serenity_types.utils.common import Response
from serenity_types.valuation.portfolio_analytic import (
    BrinsonAttributionOutput,
    BrinsonAttributionRequest,
    PortfolioAnalyticOutput,
    PortfolioAnalyticRequest
)
[docs]
class RiskAnalyticsAPI(SerenityApi):
    """
    Helper class for the Risk Analytics API.
    """
    def __init__(self, client: SerenityClient):
        """
        :param client: the raw client to delegate to when making API calls
        """
        # NOTE: I don't think the base endpoint should be valuation in the future but
        # understand this is the most practical arrangement to start; however, the
        # SDK client at least should be independent from valuation.
        super().__init__(client, "risk/analytics")
[docs]
    def compute_portfolio_statistics(
        self, request: PortfolioAnalyticRequest
    ) -> Response[PortfolioAnalyticOutput]:
        """
        Given a rich portfolio object with periodic positions, trades and transfers,
        computes standard portfolio analytics like Sharpe, Sortino, max drawdown, etc..
        Optionally you can also include a set of "simulated trades" so you can see
        how portfolio performance would differ if you execute those trades, essentially
        perform a what-if analysis on past trading decisions or alternative portfolios.
        """
        req_json = json.loads(request.json(exclude_unset=True, by_alias=True))
        raw_json = self._call_api(
            api_path="/portfolio/valuation/compute",
            body_json=req_json,
            call_type=CallType.POST,
            api_version='v2'
        )
        return PortfolioAnalyticOutput.parse_obj(raw_json["result"]) 
[docs]
    def compute_greek_measures(
            self,
            as_of_time: Optional[datetime] = None,
            pf_as_of_time: Optional[datetime] = None,
            pf_metadata_id: Optional[UUID] = None,
            pf_balances: Optional[List[Balance]] = None,
            simulation_id: Optional[UUID] = None) -> GreekMeasureResponse:
        """
        Calculate the greek measures of the given portfolio balances.
        """
        if (pf_balances and pf_metadata_id) or (pf_balances is None and pf_metadata_id is None):
            raise ValueError("Please specify one of 'pf_metadata_id' or 'pf_balances'")
        body = GreekMeasureRequest(
            as_of_time=as_of_time,
            portfolio_as_of_time=pf_as_of_time,
            portfolio_metadata_id=pf_metadata_id,
            portfolio_balances=pf_balances,
            simulation_id=simulation_id
        )
        raw_json = self._call_api(
            api_path="/greek_measures/compute",
            body_json=json.loads(body.json()),
            call_type=CallType.POST,
            api_version='v2'
        )
        return GreekMeasureResponse.parse_obj(raw_json["result"])