|
|
"""Tests for position details component and modal.""" |
|
|
|
|
|
import pytest |
|
|
from dash import html |
|
|
|
|
|
from src.folio.components.position_details import create_position_details |
|
|
from src.folio.data_model import OptionPosition, PortfolioGroup, StockPosition |
|
|
|
|
|
|
|
|
class TestPositionDetails: |
|
|
"""Tests for position details component.""" |
|
|
|
|
|
def test_create_position_details(self): |
|
|
"""Test that position details can be created correctly.""" |
|
|
|
|
|
stock_position = StockPosition( |
|
|
ticker="AAPL", |
|
|
quantity=100, |
|
|
market_exposure=15000.0, |
|
|
beta=1.2, |
|
|
beta_adjusted_exposure=18000.0, |
|
|
) |
|
|
|
|
|
option_position = OptionPosition( |
|
|
ticker="AAPL", |
|
|
position_type="option", |
|
|
quantity=10, |
|
|
market_exposure=1500.0, |
|
|
beta=1.2, |
|
|
beta_adjusted_exposure=1800.0, |
|
|
strike=150.0, |
|
|
expiry="2023-01-01", |
|
|
option_type="CALL", |
|
|
delta=0.7, |
|
|
delta_exposure=1050.0, |
|
|
notional_value=15000.0, |
|
|
underlying_beta=1.2, |
|
|
) |
|
|
|
|
|
|
|
|
portfolio_group = PortfolioGroup( |
|
|
ticker="AAPL", |
|
|
stock_position=stock_position, |
|
|
option_positions=[option_position], |
|
|
net_exposure=16500.0, |
|
|
beta=1.2, |
|
|
beta_adjusted_exposure=19800.0, |
|
|
total_delta_exposure=1050.0, |
|
|
options_delta_exposure=1050.0, |
|
|
) |
|
|
|
|
|
|
|
|
details = create_position_details(portfolio_group) |
|
|
|
|
|
|
|
|
assert isinstance(details, html.Div) |
|
|
|
|
|
|
|
|
assert "AAPL" in details.children[0].children |
|
|
|
|
|
|
|
|
assert len(details.children[1].children) == 3 |
|
|
|
|
|
def test_create_position_details_no_stock(self): |
|
|
"""Test that position details can be created correctly without a stock position.""" |
|
|
|
|
|
option_position = OptionPosition( |
|
|
ticker="AAPL", |
|
|
position_type="option", |
|
|
quantity=10, |
|
|
market_exposure=1500.0, |
|
|
beta=1.2, |
|
|
beta_adjusted_exposure=1800.0, |
|
|
strike=150.0, |
|
|
expiry="2023-01-01", |
|
|
option_type="CALL", |
|
|
delta=0.7, |
|
|
delta_exposure=1050.0, |
|
|
notional_value=15000.0, |
|
|
underlying_beta=1.2, |
|
|
) |
|
|
|
|
|
|
|
|
portfolio_group = PortfolioGroup( |
|
|
ticker="AAPL", |
|
|
stock_position=None, |
|
|
option_positions=[option_position], |
|
|
net_exposure=1500.0, |
|
|
beta=1.2, |
|
|
beta_adjusted_exposure=1800.0, |
|
|
total_delta_exposure=1050.0, |
|
|
options_delta_exposure=1050.0, |
|
|
) |
|
|
|
|
|
|
|
|
details = create_position_details(portfolio_group) |
|
|
|
|
|
|
|
|
assert isinstance(details, html.Div) |
|
|
|
|
|
|
|
|
assert "AAPL" in details.children[0].children |
|
|
|
|
|
|
|
|
assert len(details.children[1].children) == 2 |
|
|
|
|
|
def test_create_position_details_no_options(self): |
|
|
"""Test that position details can be created correctly without option positions.""" |
|
|
|
|
|
stock_position = StockPosition( |
|
|
ticker="AAPL", |
|
|
quantity=100, |
|
|
market_exposure=15000.0, |
|
|
beta=1.2, |
|
|
beta_adjusted_exposure=18000.0, |
|
|
) |
|
|
|
|
|
|
|
|
portfolio_group = PortfolioGroup( |
|
|
ticker="AAPL", |
|
|
stock_position=stock_position, |
|
|
option_positions=[], |
|
|
net_exposure=15000.0, |
|
|
beta=1.2, |
|
|
beta_adjusted_exposure=18000.0, |
|
|
total_delta_exposure=0.0, |
|
|
options_delta_exposure=0.0, |
|
|
) |
|
|
|
|
|
|
|
|
details = create_position_details(portfolio_group) |
|
|
|
|
|
|
|
|
assert isinstance(details, html.Div) |
|
|
|
|
|
|
|
|
assert "AAPL" in details.children[0].children |
|
|
|
|
|
|
|
|
assert len(details.children[1].children) == 2 |
|
|
|
|
|
def test_position_modal_integration(self): |
|
|
"""Test the full position details modal flow as it would work in the UI. |
|
|
|
|
|
This test simulates what happens when a user clicks the 'details' button on a position. |
|
|
It tests the entire flow from position data to displaying the modal, ensuring that |
|
|
the modal can be created and displayed correctly regardless of the internal implementation. |
|
|
""" |
|
|
|
|
|
position_data = { |
|
|
"ticker": "AAPL", |
|
|
"stock_position": { |
|
|
"ticker": "AAPL", |
|
|
"quantity": 100, |
|
|
"market_exposure": 15000.0, |
|
|
"beta": 1.2, |
|
|
"beta_adjusted_exposure": 18000.0, |
|
|
"position_type": "stock", |
|
|
}, |
|
|
"option_positions": [], |
|
|
"net_exposure": 15000.0, |
|
|
"beta": 1.2, |
|
|
"beta_adjusted_exposure": 18000.0, |
|
|
"total_delta_exposure": 0.0, |
|
|
"options_delta_exposure": 0.0, |
|
|
} |
|
|
|
|
|
try: |
|
|
|
|
|
stock_position_data = position_data["stock_position"].copy() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stock_position = StockPosition(**stock_position_data) |
|
|
|
|
|
|
|
|
group = PortfolioGroup( |
|
|
ticker=position_data["ticker"], |
|
|
stock_position=stock_position, |
|
|
option_positions=[], |
|
|
net_exposure=position_data["net_exposure"], |
|
|
beta=position_data["beta"], |
|
|
beta_adjusted_exposure=position_data["beta_adjusted_exposure"], |
|
|
total_delta_exposure=position_data["total_delta_exposure"], |
|
|
options_delta_exposure=position_data["options_delta_exposure"], |
|
|
) |
|
|
|
|
|
|
|
|
details = create_position_details(group) |
|
|
|
|
|
|
|
|
assert details is not None |
|
|
|
|
|
|
|
|
assert "AAPL" in details.children[0].children |
|
|
|
|
|
|
|
|
assert len(details.children[1].children) == 2 |
|
|
|
|
|
|
|
|
assert stock_position.position_type == "stock" |
|
|
assert stock_position.to_dict()["position_type"] == "stock" |
|
|
except Exception as e: |
|
|
pytest.fail(f"Position modal failed to load: {e}") |
|
|
|