File size: 7,404 Bytes
ce4bc73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
"""Tests for the allocations stacked bar chart."""

import pytest

from src.folio.chart_data import transform_for_allocations_chart
from src.folio.data_model import ExposureBreakdown, PortfolioSummary
from src.folio.portfolio_value import get_portfolio_component_values


class TestAllocationsChart:
    """Tests for the allocations stacked bar chart."""

    @pytest.fixture
    def mock_portfolio_summary(self):
        """Create a mock portfolio summary for testing."""
        # Create exposure breakdowns
        long_exposure = ExposureBreakdown(
            stock_exposure=10000.0,
            stock_beta_adjusted=12000.0,
            option_delta_exposure=2000.0,
            option_beta_adjusted=2400.0,
            total_exposure=12000.0,
            total_beta_adjusted=14400.0,
            description="Long exposure",
            formula="Long formula",
            components={
                "Long Stocks Exposure": 10000.0,
                "Long Options Delta Exp": 2000.0,
            },
        )

        short_exposure = ExposureBreakdown(
            stock_exposure=-5000.0,
            stock_beta_adjusted=-6000.0,
            option_delta_exposure=-1000.0,
            option_beta_adjusted=-1200.0,
            total_exposure=-6000.0,
            total_beta_adjusted=-7200.0,
            description="Short exposure",
            formula="Short formula",
            components={
                "Short Stocks Exposure": -5000.0,
                "Short Options Delta Exp": -1000.0,
            },
        )

        options_exposure = ExposureBreakdown(
            stock_exposure=0.0,
            stock_beta_adjusted=0.0,
            option_delta_exposure=1000.0,
            option_beta_adjusted=1200.0,
            total_exposure=1000.0,
            total_beta_adjusted=1200.0,
            description="Options exposure",
            formula="Options formula",
            components={
                "Long Options Delta Exp": 2000.0,
                "Short Options Delta Exp": -1000.0,
                "Net Options Delta Exp": 1000.0,
            },
        )

        # Create portfolio summary with a net market exposure of 6000.0 (12000.0 - 6000.0)
        return PortfolioSummary(
            net_market_exposure=6000.0,
            portfolio_beta=1.2,
            long_exposure=long_exposure,
            short_exposure=short_exposure,
            options_exposure=options_exposure,
            short_percentage=33.0,
            cash_like_value=4000.0,
            cash_like_count=1,
            cash_percentage=40.0,
            stock_value=5000.0,  # Net stock value (10000 - 5000)
            option_value=1000.0,  # Net option value (2000 - 1000)
            pending_activity_value=500.0,
            portfolio_estimate_value=10500.0,  # 5000 + 1000 + 4000 + 500
        )

    def test_allocations_stacked_bar_chart(self, mock_portfolio_summary):
        """Test that allocations chart correctly creates a bar chart with four main categories."""
        # Given a portfolio summary with known values
        # When we transform it for the allocations chart
        chart_data = transform_for_allocations_chart(mock_portfolio_summary)

        # Then the chart should have the correct structure
        assert "data" in chart_data
        assert "layout" in chart_data
        assert chart_data["layout"]["barmode"] == "relative"  # Using relative mode now

        # And it should have four traces (Long, Short, Cash, Pending)
        traces = chart_data["data"]
        assert len(traces) == 4

        # And the traces should be correctly named
        trace_names = [trace["name"] for trace in traces]
        assert "Long" in trace_names
        assert "Short" in trace_names
        assert "Cash" in trace_names
        assert "Pending" in trace_names

        # And the x values should place the traces in the correct categories
        long_trace = next(t for t in traces if t["name"] == "Long")
        short_trace = next(t for t in traces if t["name"] == "Short")
        cash_trace = next(t for t in traces if t["name"] == "Cash")
        pending_trace = next(t for t in traces if t["name"] == "Pending")

        assert long_trace["x"] == ["Long"]
        assert short_trace["x"] == ["Short"]
        assert cash_trace["x"] == ["Cash"]
        assert pending_trace["x"] == ["Pending"]

        # And the y values should match the expected values from the get_portfolio_component_values function
        component_values = get_portfolio_component_values(mock_portfolio_summary)

        # Check combined values
        long_total = component_values["long_stock"] + component_values["long_option"]
        short_total = component_values["short_stock"] + component_values["short_option"]

        assert long_trace["y"][0] == long_total
        assert short_trace["y"][0] == short_total  # Should be negative
        assert cash_trace["y"][0] == mock_portfolio_summary.cash_like_value
        assert pending_trace["y"][0] == mock_portfolio_summary.pending_activity_value

        # And the layout should have the correct y-axis
        assert "yaxis" in chart_data["layout"]

        # Verify text is displayed on bars (compact format)
        assert "$" in long_trace["text"][0]  # Should contain dollar sign
        assert long_trace["textposition"] == "inside"  # Text should be inside bars

        assert "$" in short_trace["text"][0]  # Should contain dollar sign
        assert short_trace["textposition"] == "inside"  # Text should be inside bars

        # Verify hover template contains detailed breakdown information
        assert "Long Total" in long_trace["hovertemplate"]
        assert "Stocks" in long_trace["hovertemplate"]
        assert "Options" in long_trace["hovertemplate"]

        assert "Short Total" in short_trace["hovertemplate"]
        assert "Stocks" in short_trace["hovertemplate"]
        assert "Options" in short_trace["hovertemplate"]

    def test_allocations_chart_with_empty_portfolio(self):
        """Test that allocations chart handles empty portfolios correctly."""
        # Create an empty portfolio summary
        empty_exposure = ExposureBreakdown(
            stock_exposure=0.0,
            stock_beta_adjusted=0.0,
            option_delta_exposure=0.0,
            option_beta_adjusted=0.0,
            total_exposure=0.0,
            total_beta_adjusted=0.0,
            description="Empty exposure",
            formula="N/A",
            components={},
        )

        empty_summary = PortfolioSummary(
            net_market_exposure=0.0,
            portfolio_beta=0.0,
            long_exposure=empty_exposure,
            short_exposure=empty_exposure,
            options_exposure=empty_exposure,
            short_percentage=0.0,
            cash_like_value=0.0,
            cash_like_count=0,
            cash_percentage=0.0,
            stock_value=0.0,
            option_value=0.0,
            pending_activity_value=0.0,
            portfolio_estimate_value=0.0,
        )

        # Get the chart data
        chart_data = transform_for_allocations_chart(empty_summary)

        # Verify that an empty chart is returned
        assert "data" in chart_data
        assert len(chart_data["data"]) == 0
        assert "annotations" in chart_data["layout"]
        assert (
            "No portfolio data available"
            in chart_data["layout"]["annotations"][0]["text"]
        )