wbportfolio 1.49.11__py2.py3-none-any.whl → 1.50.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of wbportfolio might be problematic. Click here for more details.

@@ -60,7 +60,7 @@ class RuleBackend(backend.AbstractRuleBackend):
60
60
  @classmethod
61
61
  def get_parameter_fields(cls):
62
62
  return [
63
- "below_x_days",
63
+ "liquidation_factor",
64
64
  "redemption_pct",
65
65
  ]
66
66
 
@@ -1,9 +1,11 @@
1
+ from contextlib import suppress
1
2
  from datetime import date, timedelta
2
3
  from typing import Generator
3
4
 
4
5
  import pandas as pd
5
6
  from django.contrib.contenttypes.models import ContentType
6
7
  from django.db import models
8
+ from django.db.models import Sum
7
9
  from pandas.tseries.offsets import BDay
8
10
  from wbcompliance.models.risk_management import backend
9
11
  from wbcore import serializers as wb_serializers
@@ -14,7 +16,7 @@ from wbfdm.serializers import (
14
16
  SecurityRepresentationSerializer,
15
17
  )
16
18
 
17
- from wbportfolio.models import InstrumentPortfolioThroughModel, Portfolio
19
+ from wbportfolio.models import AssetPosition, InstrumentPortfolioThroughModel, Portfolio
18
20
 
19
21
 
20
22
  class ActivePortfolioRelationshipMixin(backend.AbstractRuleBackend):
@@ -173,10 +175,16 @@ class StopLossMixin(backend.AbstractRuleBackend):
173
175
  for threshold in self.thresholds:
174
176
  if threshold.is_inrange(total_perf):
175
177
  instrument = Instrument.objects.get(id=tested_instrument_id)
176
- report_details = {
177
- "Absolute Percentage": f"{perf_instrument:,.3%}",
178
- "Field": field_label,
179
- }
178
+ report_details = {}
179
+ with suppress(AssetPosition.DoesNotExist):
180
+ previous_portfolio_date = (
181
+ self.product.portfolio.assets.filter(date__lt=self.evaluation_date).latest("date").date
182
+ )
183
+ weighting = self.product.portfolio.assets.filter(
184
+ underlying_quote=instrument, date=previous_portfolio_date
185
+ ).aggregate(s=Sum("weighting"))["s"]
186
+ report_details["Portfolio Impact"] = f"{float(weighting) * total_perf:+,.2%}"
187
+ report_details["Field"] = field_label
180
188
  if self.benchmark:
181
189
  report_details[f"Relative Percentage VS {str(self.benchmark)}"] = f"{total_perf:,.3%}"
182
190
  if total_perf < 0:
@@ -3,11 +3,12 @@ from decimal import Decimal
3
3
 
4
4
  import pytest
5
5
  from faker import Faker
6
+ from pandas._libs.tslibs.offsets import BDay
6
7
  from psycopg.types.range import NumericRange
7
8
  from wbcompliance.factories.risk_management import RuleThresholdFactory
8
9
 
9
- from wbportfolio.risk_management.backends.stop_loss_instrument import (
10
- RuleBackend as StopLossInstrumentRuleBackend,
10
+ from wbportfolio.risk_management.backends.stop_loss_portfolio import (
11
+ RuleBackend as StopLossPortfolioRuleBackend,
11
12
  )
12
13
  from wbportfolio.tests.models.utils import PortfolioTestMixin
13
14
 
@@ -21,7 +22,7 @@ class TestStopLossPortfolioRuleModel(PortfolioTestMixin):
21
22
  parameters = {"freq": freq, "date_interval_option": date_interval_option}
22
23
  lower = random.random()
23
24
  upper = random.uniform(lower, 1)
24
- return StopLossInstrumentRuleBackend(
25
+ return StopLossPortfolioRuleBackend(
25
26
  weekday,
26
27
  product,
27
28
  parameters,
@@ -31,8 +32,8 @@ class TestStopLossPortfolioRuleModel(PortfolioTestMixin):
31
32
  @pytest.mark.parametrize(
32
33
  "date_interval_option, freq",
33
34
  [
34
- ("ROLLING_WINDOWS", StopLossInstrumentRuleBackend.FreqChoices.BUSINESS_DAY),
35
- *[("FREQUENCY", option) for option in StopLossInstrumentRuleBackend.FreqChoices.values],
35
+ ("ROLLING_WINDOWS", StopLossPortfolioRuleBackend.FreqChoices.BUSINESS_DAY),
36
+ *[("FREQUENCY", option) for option in StopLossPortfolioRuleBackend.FreqChoices.values],
36
37
  ],
37
38
  )
38
39
  def test_check_rule_frequency(
@@ -41,40 +42,49 @@ class TestStopLossPortfolioRuleModel(PortfolioTestMixin):
41
42
  date_interval_option,
42
43
  freq,
43
44
  product,
44
- portfolio,
45
+ instrument,
45
46
  instrument_price_factory,
46
47
  asset_position_factory,
47
48
  stop_loss_portfolio_backend,
48
- instrument_portfolio_through_model_factory,
49
49
  ):
50
- instrument_portfolio_through_model_factory.create(
51
- instrument=product,
52
- portfolio=portfolio,
53
- primary_portfolio=True,
54
- )
50
+ previous_date = (weekday - BDay(1)).date()
55
51
 
56
52
  d1 = stop_loss_portfolio_backend._get_start_interval()
57
53
 
58
54
  threshold = stop_loss_portfolio_backend.thresholds[0]
59
55
  breach_perf = random.uniform(threshold.range.lower, threshold.range.upper)
60
56
 
61
- i1 = instrument_price_factory.create(date=d1, net_value=100, calculated=False, instrument=product)
62
- instrument_price_factory.create(
63
- date=weekday, net_value=Decimal(breach_perf + 1) * i1.net_value, calculated=False, instrument=product
57
+ instrument_price_factory.create(date=d1, net_value=100, calculated=False, instrument=product)
58
+ instrument_price_factory.create(date=previous_date, net_value=100, calculated=False, instrument=product)
59
+ instrument_price_factory.create(date=weekday, net_value=100, calculated=False, instrument=product)
60
+
61
+ i1 = instrument_price_factory.create(date=d1, net_value=100, calculated=False, instrument=instrument)
62
+ instrument_price_factory.create(date=previous_date, calculated=False, instrument=instrument)
63
+ i2 = instrument_price_factory.create(
64
+ date=weekday, net_value=Decimal(breach_perf + 1) * i1.net_value, calculated=False, instrument=instrument
64
65
  )
65
66
 
66
- asset_position_factory.create(date=weekday, underlying_instrument=product, portfolio=portfolio)
67
- asset_position_factory.create(date=weekday, underlying_instrument=product, portfolio=portfolio)
67
+ previous_position = asset_position_factory.create(
68
+ date=previous_date, underlying_instrument=instrument, portfolio=product.portfolio
69
+ )
70
+ asset_position_factory.create(date=weekday, underlying_instrument=instrument, portfolio=product.portfolio)
68
71
 
72
+ expected_performance = i2.net_value / i1.net_value - Decimal(1.0)
69
73
  res = list(stop_loss_portfolio_backend.check_rule())
70
74
  assert len(res) == 1
71
- assert res[0].breached_object.id == product.id
75
+ incident = res[0]
76
+ assert incident.breached_object.id == instrument.id
77
+ assert incident.breached_value == f'<span style="color:green">{expected_performance:+,.2%}</span>'
78
+ assert incident.report_details == {
79
+ "Field": "Net Value",
80
+ "Portfolio Impact": f"{previous_position.weighting * expected_performance:+,.2%}",
81
+ }
72
82
 
73
83
  @pytest.mark.parametrize(
74
84
  "date_interval_option, freq",
75
85
  [
76
- ("ROLLING_WINDOWS", StopLossInstrumentRuleBackend.FreqChoices.BUSINESS_DAY),
77
- *[("FREQUENCY", option) for option in StopLossInstrumentRuleBackend.FreqChoices.values],
86
+ ("ROLLING_WINDOWS", StopLossPortfolioRuleBackend.FreqChoices.BUSINESS_DAY),
87
+ *[("FREQUENCY", option) for option in StopLossPortfolioRuleBackend.FreqChoices.values],
78
88
  ],
79
89
  )
80
90
  def test_check_rule_frequency_2(
@@ -83,33 +93,28 @@ class TestStopLossPortfolioRuleModel(PortfolioTestMixin):
83
93
  date_interval_option,
84
94
  freq,
85
95
  product,
86
- portfolio,
87
96
  instrument_price_factory,
88
97
  asset_position_factory,
89
98
  instrument_factory,
90
99
  stop_loss_portfolio_backend,
91
- instrument_portfolio_through_model_factory,
92
100
  ):
93
- instrument_portfolio_through_model_factory.create(
94
- instrument=product,
95
- portfolio=portfolio,
96
- primary_portfolio=True,
97
- )
98
101
  d1 = stop_loss_portfolio_backend._get_start_interval()
99
102
  benchmark = instrument_factory.create()
103
+ instrument = instrument_factory.create()
100
104
 
101
105
  threshold = stop_loss_portfolio_backend.thresholds[0]
102
106
  threshold.range = NumericRange(upper=-0.5, lower=None) # type: ignore
103
107
  threshold.save()
104
108
 
105
- instrument_price_factory.create(date=d1, net_value=100, calculated=False, instrument=product)
106
109
  instrument_price_factory.create(date=weekday, net_value=100, calculated=False, instrument=product)
107
110
 
111
+ instrument_price_factory.create(date=d1, net_value=100, calculated=False, instrument=instrument)
112
+ instrument_price_factory.create(date=weekday, net_value=100, calculated=False, instrument=instrument)
113
+
108
114
  instrument_price_factory.create(date=d1, net_value=100, calculated=False, instrument=benchmark)
109
115
  instrument_price_factory.create(date=weekday, net_value=300, calculated=False, instrument=benchmark)
110
116
 
111
- asset_position_factory.create(date=weekday, underlying_instrument=product, portfolio=portfolio)
112
- asset_position_factory.create(date=weekday, underlying_instrument=product, portfolio=portfolio)
117
+ asset_position_factory.create(date=weekday, underlying_instrument=instrument, portfolio=product.portfolio)
113
118
 
114
119
  res = list(stop_loss_portfolio_backend.check_rule())
115
120
  assert len(res) == 0
@@ -117,4 +122,4 @@ class TestStopLossPortfolioRuleModel(PortfolioTestMixin):
117
122
  setattr(stop_loss_portfolio_backend, "static_benchmark", benchmark)
118
123
  res = list(stop_loss_portfolio_backend.check_rule())
119
124
  assert len(res) == 1
120
- assert res[0].breached_object.id == product.id
125
+ assert res[0].breached_object.id == instrument.id
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbportfolio
3
- Version: 1.49.11
3
+ Version: 1.50.0
4
4
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
5
  License-File: LICENSE
6
6
  Requires-Dist: cryptography==3.4.*
@@ -302,9 +302,9 @@ wbportfolio/risk_management/backends/accounts.py,sha256=VMHwhzeFV2bzobb1RlEJadTV
302
302
  wbportfolio/risk_management/backends/controversy_portfolio.py,sha256=ncEHyJMVomv0ehx7LoWcux1YHLV6KYYmiOkIBzJ0P1M,2852
303
303
  wbportfolio/risk_management/backends/exposure_portfolio.py,sha256=8T97J0pbnGdme_Ji9ntA5FV9MCouSxDz5grMvRvpl70,10760
304
304
  wbportfolio/risk_management/backends/instrument_list_portfolio.py,sha256=Sh8PZHHKFAl4Aw8fOlu17fUSiEFYYemb-B0iltAXc10,4186
305
- wbportfolio/risk_management/backends/liquidity_risk.py,sha256=cRL7ZXvrC286yXCauNatuvSYikMNB-nmAt5ELmpDg0c,3688
305
+ wbportfolio/risk_management/backends/liquidity_risk.py,sha256=OnuUypmN86RDnch1JOb8UJo5j1z1_X7sjazqAo7cbwM,3694
306
306
  wbportfolio/risk_management/backends/liquidity_stress_instrument.py,sha256=oitzsaZu-HhYn9Avku3322GtDmf6QGsfyRzGPGZoM1Y,3612
307
- wbportfolio/risk_management/backends/mixins.py,sha256=anuOlop9gc9X8tywx7_wbH7fAJSBeQNhriZwXzw6rWk,9453
307
+ wbportfolio/risk_management/backends/mixins.py,sha256=C7D8MXInl3m05GiLxQP4K5uomq4mpvi81zqsCiXq6g0,10006
308
308
  wbportfolio/risk_management/backends/product_integrity.py,sha256=3BbQBYYzLdj2iuQb9QOIgPjWMz1FaPkvEJoNgxWVfz0,4598
309
309
  wbportfolio/risk_management/backends/stop_loss_instrument.py,sha256=qIzeV26TRdbgG8-Nsh5O0NPMrPmIXeaRdPR-EF_uaLo,1128
310
310
  wbportfolio/risk_management/backends/stop_loss_portfolio.py,sha256=yiDa3FkZKdrmHSf8p1XmT8RhJW6KyiMuypYpY39SiO8,1674
@@ -318,7 +318,7 @@ wbportfolio/risk_management/tests/test_instrument_list_portfolio.py,sha256=1pAsu
318
318
  wbportfolio/risk_management/tests/test_liquidity_risk.py,sha256=UtzBrW7LeY0Z0ARX9pg4CUjgdw8u5Hfrdkddro5EEZ8,1713
319
319
  wbportfolio/risk_management/tests/test_product_integrity.py,sha256=wqJ4b5DO2BrEVQF4eVX2B9AvfrRMAyC_lppGrgKc_ug,1959
320
320
  wbportfolio/risk_management/tests/test_stop_loss_instrument.py,sha256=ZueuA9qQOv6JdKuRiPLGV3auOLRnoUCZzfqcQzGJGig,4197
321
- wbportfolio/risk_management/tests/test_stop_loss_portfolio.py,sha256=bFQqIUs1FZVn1SwiNkzt7hVrvT-qs3EcxOfI20xAjOM,4595
321
+ wbportfolio/risk_management/tests/test_stop_loss_portfolio.py,sha256=_CLyC4mWPCP92LuERk6gnrr-JG78WCB83ezl5fPugLo,5195
322
322
  wbportfolio/risk_management/tests/test_ucits_portfolio.py,sha256=UcZLhatl8iU9AhNNYv6OBs_cjILZKaws2nvUen6orkc,1807
323
323
  wbportfolio/serializers/__init__.py,sha256=w1nPxkapcnGngYwiU7BGSLfyhJ67_PQWVjuya7YSxX0,1795
324
324
  wbportfolio/serializers/adjustments.py,sha256=0yTPgDjGRyzSa-ecCklENKtLJXZhq1dryMI3nTRsQRo,783
@@ -521,7 +521,7 @@ wbportfolio/viewsets/transactions/rebalancing.py,sha256=6rIrdK0rtKL1afJ-tYfAGdQV
521
521
  wbportfolio/viewsets/transactions/trade_proposals.py,sha256=iQpC_Thbj56SmM05vPRsF1JZguGBDaTUH3I-_iCHCV0,5958
522
522
  wbportfolio/viewsets/transactions/trades.py,sha256=-yJ4j8NJTu2VWyhCq5BXGNND_925Ietoxx9k07SLVh0,21634
523
523
  wbportfolio/viewsets/transactions/transactions.py,sha256=ixDp-nsNA8t_A06rBCT19hOMJHy0iRmdz1XKdV1OwAs,4450
524
- wbportfolio-1.49.11.dist-info/METADATA,sha256=vbQQ5UCTEyqLH-YcOOF6BVCDXb3ujdegBOUX6k_iQ-8,735
525
- wbportfolio-1.49.11.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
526
- wbportfolio-1.49.11.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
527
- wbportfolio-1.49.11.dist-info/RECORD,,
524
+ wbportfolio-1.50.0.dist-info/METADATA,sha256=NdlRGICQMfP56DoEYZOoiqLUhXqMOOjg1Rnrxl2KzVQ,734
525
+ wbportfolio-1.50.0.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
526
+ wbportfolio-1.50.0.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
527
+ wbportfolio-1.50.0.dist-info/RECORD,,