folio / tests /test_position_details.py
dystomachina's picture
Initial commit for Folio project
ce4bc73
"""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."""
# Create test positions
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,
)
# Create portfolio group
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,
)
# Create position details
details = create_position_details(portfolio_group)
# Verify that details is a Dash component
assert isinstance(details, html.Div)
# Verify that the ticker is in the title
assert "AAPL" in details.children[0].children
# Verify that there are sections for stock, options, and combined metrics
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."""
# Create test positions
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,
)
# Create portfolio group
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,
)
# Create position details
details = create_position_details(portfolio_group)
# Verify that details is a Dash component
assert isinstance(details, html.Div)
# Verify that the ticker is in the title
assert "AAPL" in details.children[0].children
# Verify that there are sections for options and combined metrics (no stock)
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."""
# Create test positions
stock_position = StockPosition(
ticker="AAPL",
quantity=100,
market_exposure=15000.0,
beta=1.2,
beta_adjusted_exposure=18000.0,
)
# Create portfolio group
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,
)
# Create position details
details = create_position_details(portfolio_group)
# Verify that details is a Dash component
assert isinstance(details, html.Div)
# Verify that the ticker is in the title
assert "AAPL" in details.children[0].children
# Verify that there are sections for stock and combined metrics (no options)
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.
"""
# Create position data as it would be stored in the UI
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", # This field comes from to_dict() serialization
},
"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:
# Create a StockPosition from the position data
stock_position_data = position_data["stock_position"].copy()
# The position_type field should now be accepted by StockPosition
# No need to remove it
stock_position = StockPosition(**stock_position_data)
# Create a PortfolioGroup manually
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"],
)
# Now create the position details component
details = create_position_details(group)
# Verify that the details component is created correctly
assert details is not None
# Verify that the ticker is in the title
assert "AAPL" in details.children[0].children
# Verify that the details has the expected sections
assert len(details.children[1].children) == 2 # Stock and combined metrics
# Verify that the position_type field is properly stored and returned
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}")