wbportfolio 1.49.3__py2.py3-none-any.whl → 1.49.5__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.

@@ -9,7 +9,7 @@ from wbfdm.models import Classification, ClassificationGroup
9
9
  from wbfdm.preferences import get_default_classification_group
10
10
 
11
11
  from wbportfolio.filters.transactions.mixins import OppositeSharesFieldMethodMixin
12
- from wbportfolio.models import Product, ProductGroup
12
+ from wbportfolio.models import Custodian, Product, ProductGroup, Register
13
13
  from wbportfolio.models.transactions.claim import Claim, ClaimGroupbyChoice
14
14
  from wbportfolio.preferences import get_monthly_nnm_target
15
15
 
@@ -61,15 +61,13 @@ class CommissionBaseFilterSet(wb_filters.FilterSet):
61
61
 
62
62
  product = wb_filters.ModelMultipleChoiceFilter(
63
63
  label="Product",
64
- method="filter_product",
65
64
  queryset=Product.objects.all(),
66
65
  endpoint=Product.get_representation_endpoint(),
67
66
  value_key=Product.get_representation_value_key(),
68
67
  label_key=Product.get_representation_label_key(),
69
68
  )
70
- product_group = wb_filters.ModelChoiceFilter(
69
+ product__parent = wb_filters.ModelMultipleChoiceFilter(
71
70
  label="Product Group",
72
- method="filter_productgroup",
73
71
  queryset=ProductGroup.objects.all(),
74
72
  endpoint=ProductGroup.get_representation_endpoint(),
75
73
  value_key=ProductGroup.get_representation_value_key(),
@@ -95,14 +93,13 @@ class CommissionBaseFilterSet(wb_filters.FilterSet):
95
93
  method="filter_manager_role",
96
94
  )
97
95
 
98
- classification = wb_filters.ModelChoiceFilter(
99
- label="Classification",
96
+ product__classifications = wb_filters.ModelMultipleChoiceFilter(
97
+ label="Classifications",
100
98
  queryset=Classification.objects.all(),
101
99
  endpoint=Classification.get_representation_endpoint(),
102
100
  value_key=Classification.get_representation_value_key(),
103
101
  label_key=Classification.get_representation_label_key(),
104
102
  filter_params={"instrument_type_key": "product"},
105
- method="filter_classification",
106
103
  )
107
104
 
108
105
  def filter_account(self, queryset, name, value):
@@ -110,16 +107,6 @@ class CommissionBaseFilterSet(wb_filters.FilterSet):
110
107
  return queryset.filter(account__in=value.get_descendants(include_self=True).values("id"))
111
108
  return queryset
112
109
 
113
- def filter_product(self, queryset, name, value):
114
- if value:
115
- return queryset.filter(product__in=value)
116
- return queryset
117
-
118
- def filter_productgroup(self, queryset, name, value):
119
- if value:
120
- return queryset.filter(product__parent=value)
121
- return queryset
122
-
123
110
  def filter_account_owner(self, queryset, name, value):
124
111
  if value:
125
112
  return queryset.filter(account__in=Account.get_accounts_for_customer(value))
@@ -135,11 +122,6 @@ class CommissionBaseFilterSet(wb_filters.FilterSet):
135
122
  return queryset.filter(account__in=Account.get_managed_accounts_for_entry(value).values("id"))
136
123
  return queryset
137
124
 
138
- def filter_classification(self, queryset, name, value):
139
- if value:
140
- return queryset.filter(product__classifications=value)
141
- return queryset
142
-
143
125
 
144
126
  class ClaimFilter(OppositeSharesFieldMethodMixin, CommissionBaseFilterSet):
145
127
  # we have to redefine the mixin fields because django_filters does not allow class extension with mixin
@@ -169,6 +151,42 @@ class ClaimFilter(OppositeSharesFieldMethodMixin, CommissionBaseFilterSet):
169
151
  label_key=Person.get_representation_label_key(),
170
152
  )
171
153
 
154
+ # Trade related filter fields
155
+ trade__custodian = wb_filters.ModelChoiceFilter(
156
+ label="Trade Custodian",
157
+ queryset=Custodian.objects.all(),
158
+ endpoint=Custodian.get_representation_endpoint(),
159
+ value_key=Custodian.get_representation_value_key(),
160
+ label_key=Custodian.get_representation_label_key(),
161
+ )
162
+ trade__register = wb_filters.ModelChoiceFilter(
163
+ label="Trade Register",
164
+ queryset=Register.objects.all(),
165
+ endpoint=Register.get_representation_endpoint(),
166
+ value_key=Register.get_representation_value_key(),
167
+ label_key=Register.get_representation_label_key(),
168
+ )
169
+
170
+ trade_comment = wb_filters.CharFilter(lookup_expr="icontains", label="Trade Comment")
171
+
172
+ last_nav = wb_filters.NumberFilter(field_name="last_nav", lookup_expr="exact", label="Last Nav")
173
+ last_nav__lte = wb_filters.NumberFilter(field_name="last_nav", lookup_expr="lte", label="Last Nav")
174
+ last_nav__gte = wb_filters.NumberFilter(field_name="last_nav", lookup_expr="gte", label="Last Nav")
175
+
176
+ total_value = wb_filters.NumberFilter(field_name="total_value", lookup_expr="exact", label="Total Value")
177
+ total_value__lte = wb_filters.NumberFilter(field_name="total_value", lookup_expr="lte", label="Total Value")
178
+ total_value__gte = wb_filters.NumberFilter(field_name="total_value", lookup_expr="gte", label="Total Value")
179
+
180
+ total_value_usd = wb_filters.NumberFilter(
181
+ field_name="total_value_usd", lookup_expr="exact", label="Total Value (USD)"
182
+ )
183
+ total_value_usd__lte = wb_filters.NumberFilter(
184
+ field_name="total_value_usd", lookup_expr="lte", label="Total Value (USD)"
185
+ )
186
+ total_value_usd__gte = wb_filters.NumberFilter(
187
+ field_name="total_value_usd", lookup_expr="gte", label="Total Value (USD)"
188
+ )
189
+
172
190
  def filter_in_charge_of_customer(self, queryset, name, value):
173
191
  if value:
174
192
  return queryset.filter(
@@ -3,7 +3,7 @@ from datetime import timedelta
3
3
  from django.db.models import Count, OuterRef, Subquery
4
4
  from wbcore import filters as wb_filters
5
5
  from wbcrm.models.accounts import Account
6
- from wbfdm.models import Instrument
6
+ from wbfdm.models import Classification, Instrument
7
7
 
8
8
  from wbportfolio.models import Product, Trade
9
9
  from wbportfolio.models.transactions.claim import Claim
@@ -175,6 +175,14 @@ class SubscriptionRedemptionFilterSet(TradeFilter):
175
175
  value_key=Product.get_representation_value_key(),
176
176
  label_key=Product.get_representation_label_key(),
177
177
  )
178
+ underlying_instrument__classifications = wb_filters.ModelMultipleChoiceFilter(
179
+ label="Classifications",
180
+ queryset=Classification.objects.all(),
181
+ endpoint=Classification.get_representation_endpoint(),
182
+ value_key=Classification.get_representation_value_key(),
183
+ label_key=Classification.get_representation_label_key(),
184
+ filter_params={"instrument_type_key": "product"},
185
+ )
178
186
 
179
187
  class Meta:
180
188
  model = Trade
@@ -718,10 +718,13 @@ class Portfolio(DeleteToDisableMixin, WBModel):
718
718
  for rel in PortfolioPortfolioThroughModel.objects.filter(
719
719
  dependency_portfolio=self, type=PortfolioPortfolioThroughModel.Type.PRIMARY, portfolio__is_lookthrough=True
720
720
  ):
721
+ lookthrough_portfolio = rel.portfolio
721
722
  portfolio_total_asset_value = (
722
- self.get_total_asset_under_management(val_date) if not self.only_weighting else None
723
+ self.get_total_asset_under_management(val_date) if not lookthrough_portfolio.only_weighting else None
724
+ )
725
+ lookthrough_portfolio.compute_lookthrough(
726
+ val_date, portfolio_total_asset_value=portfolio_total_asset_value
723
727
  )
724
- rel.portfolio.compute_lookthrough(val_date, portfolio_total_asset_value=portfolio_total_asset_value)
725
728
  for rel in PortfolioPortfolioThroughModel.objects.filter(
726
729
  dependency_portfolio=self, type=PortfolioPortfolioThroughModel.Type.MODEL
727
730
  ):
@@ -922,7 +925,7 @@ class Portfolio(DeleteToDisableMixin, WBModel):
922
925
  positions = []
923
926
  for val_date in pd.date_range(from_date, to_date, freq="B").date:
924
927
  portfolio_total_asset_value = (
925
- self.get_total_asset_under_management(val_date) if not self.only_weighting else None
928
+ self.primary_portfolio.get_total_asset_under_management(val_date) if not self.only_weighting else None
926
929
  )
927
930
  positions.extend(self.primary_portfolio.get_lookthrough_positions(val_date, portfolio_total_asset_value))
928
931
  self.bulk_create_positions(list(positions), delete_leftovers=True, compute_metrics=False)
@@ -685,7 +685,7 @@ class TestPortfolioModel(PortfolioTestMixin):
685
685
  )
686
686
 
687
687
  product_base_portfolio = active_product.primary_portfolio
688
- product_portfolio = portfolio_factory.create(is_lookthrough=True)
688
+ product_portfolio = portfolio_factory.create(is_lookthrough=True, only_weighting=True)
689
689
  instrument_portfolio_through_model_factory.create(instrument=active_product, portfolio=product_portfolio)
690
690
  trade_factory.create(
691
691
  underlying_instrument=active_product,
@@ -766,6 +766,18 @@ class TestPortfolioModel(PortfolioTestMixin):
766
766
  )
767
767
  assert Decimal(1.0) == pytest.approx(product_portfolio.assets.aggregate(s=Sum("weighting"))["s"])
768
768
 
769
+ product_portfolio.is_weighting = True
770
+ product_portfolio.save()
771
+ product_portfolio.compute_lookthrough(weekday, portfolio_total_asset_value=Decimal(1_000_000))
772
+ position = product_portfolio.assets.get(
773
+ portfolio_created=index1_portfolio,
774
+ underlying_instrument=a1_1.underlying_instrument,
775
+ date=weekday,
776
+ )
777
+ assert position.initial_shares == (position.weighting * Decimal(1_000_000)) / (
778
+ position.initial_price * position.initial_currency_fx_rate
779
+ )
780
+
769
781
  def test_estimate_net_asset_values(
770
782
  self,
771
783
  weekday,
@@ -1144,3 +1156,24 @@ class TestPortfolioModel(PortfolioTestMixin):
1144
1156
  )
1145
1157
  expected_returns.index = pd.to_datetime(expected_returns.index)
1146
1158
  pd.testing.assert_frame_equal(returns, expected_returns, check_names=False, check_freq=False, atol=1e-6)
1159
+
1160
+ @patch.object(Portfolio, "get_total_asset_under_management")
1161
+ @patch.object(Portfolio, "compute_lookthrough", autospec=True)
1162
+ def test_handle_controlling_portfolio_change_at_date(
1163
+ self, mock_compute_lookthrough, mock_get_total_asset_under_management, weekday, portfolio_factory
1164
+ ):
1165
+ portfolio_total_asset_value = Decimal(1_000_000)
1166
+ mock_get_total_asset_under_management.return_value = portfolio_total_asset_value
1167
+
1168
+ primary_portfolio = portfolio_factory.create(only_weighting=True)
1169
+ lookthrough_portfolio = portfolio_factory.create(is_lookthrough=True, only_weighting=False)
1170
+ PortfolioPortfolioThroughModel.objects.create(
1171
+ portfolio=lookthrough_portfolio,
1172
+ dependency_portfolio=primary_portfolio,
1173
+ type=PortfolioPortfolioThroughModel.Type.PRIMARY,
1174
+ )
1175
+
1176
+ primary_portfolio.handle_controlling_portfolio_change_at_date(weekday)
1177
+ mock_compute_lookthrough.assert_called_once_with(
1178
+ lookthrough_portfolio, weekday, portfolio_total_asset_value=portfolio_total_asset_value
1179
+ )
@@ -150,8 +150,22 @@ class ClaimModelViewSet(ClaimPermissionMixin, wb_viewsets.ModelViewSet):
150
150
  "product__isin",
151
151
  "bank",
152
152
  "account__title",
153
+ "trade_comment",
153
154
  ]
154
- ordering_fields = ("date", "shares", "claimant__computed_str", "product__name", "account__title", "bank")
155
+ ordering_fields = (
156
+ "date",
157
+ "product__name",
158
+ "claimant",
159
+ "account__title",
160
+ "trade_comment",
161
+ "shares",
162
+ "last_nav",
163
+ "total_value",
164
+ "total_value_usd",
165
+ "bank",
166
+ "creator",
167
+ "reference",
168
+ )
155
169
  ordering = ["id", "-date"]
156
170
 
157
171
  display_config_class = ClaimDisplayConfig
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbportfolio
3
- Version: 1.49.3
3
+ Version: 1.49.5
4
4
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
5
  License-File: LICENSE
6
6
  Requires-Dist: cryptography==3.4.*
@@ -86,10 +86,10 @@ wbportfolio/filters/products.py,sha256=_QFL3I132q-3JUDji_Wh2Qkv45DKnQrR3N8_luGK0
86
86
  wbportfolio/filters/roles.py,sha256=-0tOl-WJDqwXAfcJePpkjQfZB9o13I99euem5XGpMjk,920
87
87
  wbportfolio/filters/signals.py,sha256=3GrsxPwZg-6bvvZ222VfF212yNTJYQP4n5hWIEnTtpA,3144
88
88
  wbportfolio/filters/transactions/__init__.py,sha256=WHAwqZLRTAS46lLscGJYtKYWyEknsY5MqeSD0ue1r-o,662
89
- wbportfolio/filters/transactions/claim.py,sha256=WCglwAycHGuEVVwsxeSHcxWlY2_qj7ohCjjFBqAMN1k,16641
89
+ wbportfolio/filters/transactions/claim.py,sha256=GqWSFNro_bmUwVOLhsQEjV1kGHzQ_1iJBvmSRoCWCU8,17913
90
90
  wbportfolio/filters/transactions/fees.py,sha256=4EDmwgianx0iIIaIx7XgEjRDPI9nqBUq1zL1FpdtYAg,2134
91
91
  wbportfolio/filters/transactions/mixins.py,sha256=TEV3MUsiQTeu4NdFYHMIIMonmC7CdFF80JTpWYIvfRQ,550
92
- wbportfolio/filters/transactions/trades.py,sha256=87a9rPCEC0P2Hv1Mx_Tukgmm-jQIZfFkZrpTV6cOx9A,9342
92
+ wbportfolio/filters/transactions/trades.py,sha256=wq39DQwKpSioNwcQkHQ8ydDklZRPonUiXUH7NzTJCEs,9778
93
93
  wbportfolio/filters/transactions/transactions.py,sha256=TQNusdKIit_JKWFRVk3JERmHd4YvxLq-NiyEShJ_eDI,3538
94
94
  wbportfolio/fixtures/product_factsheets.yaml,sha256=z5o-viDbgwWkssDJXZYayasFgw1nJ0uiOiYrJNDkRtg,5455
95
95
  wbportfolio/fixtures/wbportfolio.yaml.gz,sha256=902nxQZM6VcVcc0wI9AYaSedcJIfsK5letLF31j1Jdg,1479453
@@ -249,7 +249,7 @@ wbportfolio/models/asset.py,sha256=yf4vBPfN1eZlWXG9SowQwr5-goG8rO1yYDitHDLZCBs,3
249
249
  wbportfolio/models/custodians.py,sha256=owTiS2Vm5CRKzh9M_P9GOVg-s-ndQ9UvRmw3yZP7cw0,3815
250
250
  wbportfolio/models/exceptions.py,sha256=3ix0tWUO-O6jpz8f07XIwycw2x3JFRoWzjwil8FVA2Q,52
251
251
  wbportfolio/models/indexes.py,sha256=iLYF2gzNzX4GLj_Nh3fybUcAQ1TslnT0wgQ6mN164QI,728
252
- wbportfolio/models/portfolio.py,sha256=KtuPMWtYoKrIOwp9T_REnOjT9CI2kjtEijF3uA0Twsc,59595
252
+ wbportfolio/models/portfolio.py,sha256=NxJVUtIUxmm_iHnO9UU0_HWmGOwExTz5NZRzHdehhj0,59718
253
253
  wbportfolio/models/portfolio_cash_flow.py,sha256=2blPiXSw7dbhUVd-7LcxDBb4v0SheNOdvRK3MFYiChA,7273
254
254
  wbportfolio/models/portfolio_cash_targets.py,sha256=WmgG-etPisZsh2yaFQpz7EkpvAudKBEzqPsO715w52U,1498
255
255
  wbportfolio/models/portfolio_relationship.py,sha256=mMb18UMRWg9kx_9uIPkMktwORuXXLjKdgRPQQvB6fVE,5486
@@ -374,7 +374,7 @@ wbportfolio/tests/models/test_merge.py,sha256=sdsjiZsmR6vsUKwTa5kkvL6QTeAZqtd_EP
374
374
  wbportfolio/tests/models/test_portfolio_cash_flow.py,sha256=X8dsXexsb1b0lBiuGzu40ps_Az_1UmmKT0eo1vbXH94,5792
375
375
  wbportfolio/tests/models/test_portfolio_cash_targets.py,sha256=q8QWAwt-kKRkLC0E05GyRhF_TTQXIi8bdHjXVU0fCV0,965
376
376
  wbportfolio/tests/models/test_portfolio_swing_pricings.py,sha256=kr2AOcQkyg2pX3ULjU-o9ye-NVpjMrrfoe-DVbYCbjs,1656
377
- wbportfolio/tests/models/test_portfolios.py,sha256=IxJy0fLkUx3m4qKzvkqixYl_0lR7TU-cQJ_DhTkh8V8,52207
377
+ wbportfolio/tests/models/test_portfolios.py,sha256=LFAYrfhEbcDB2fyC0q1TNUy9vWE9oQpqLNKJ5sx_aCw,53872
378
378
  wbportfolio/tests/models/test_product_groups.py,sha256=AcdxhurV-n_bBuUsfD1GqVtwLFcs7VI2CRrwzsIUWbU,3337
379
379
  wbportfolio/tests/models/test_products.py,sha256=nBEgyUoY-4F_pfHYnAr7KXdNYvdIkSu-PWJrqp5tPHg,9482
380
380
  wbportfolio/tests/models/test_roles.py,sha256=4Cn7WyrA2ztJNeWLk5cy9kYo5XLWMbFSvo1O-9JYxeA,3323
@@ -514,14 +514,14 @@ wbportfolio/viewsets/configs/titles/roles.py,sha256=9LoJa3jgenXJ5UWRlIErTzdbjpSW
514
514
  wbportfolio/viewsets/configs/titles/trades.py,sha256=29XCLxvY0Xe3a2tjCno3tN2rRXCr9RWpbWnzurJfnYI,1986
515
515
  wbportfolio/viewsets/configs/titles/transactions.py,sha256=Pq9yCbEkSwidXSNsqFzQIcBuofLCSxKQNhWYZJtQ-Tg,328
516
516
  wbportfolio/viewsets/transactions/__init__.py,sha256=rJOaLqiVCzRW8zqxDC1ZIDQpfhSqT-OcVUPJeiqXATc,1294
517
- wbportfolio/viewsets/transactions/claim.py,sha256=m_Fy4J_QZSve1VlR_sPQrVBDopgCqqCw27L-gdFJF1Q,38773
517
+ wbportfolio/viewsets/transactions/claim.py,sha256=IzVvCwx2g2XoX8aXzpd7bv7wb1lxbUbmn5Vn9tJ_KXM,38974
518
518
  wbportfolio/viewsets/transactions/fees.py,sha256=7VUXIogmRrXCz_D9tvDiiTae0t5j09W9zPUzxXzBGTE,7031
519
519
  wbportfolio/viewsets/transactions/mixins.py,sha256=WipvJoi5hylkpD0y9VATe30WAcwIHUIroVkK10FYw7k,636
520
520
  wbportfolio/viewsets/transactions/rebalancing.py,sha256=6rIrdK0rtKL1afJ-tYfAGdQVTN2MH1kG_yCeVkmyK8k,1263
521
521
  wbportfolio/viewsets/transactions/trade_proposals.py,sha256=iQpC_Thbj56SmM05vPRsF1JZguGBDaTUH3I-_iCHCV0,5958
522
522
  wbportfolio/viewsets/transactions/trades.py,sha256=xeEzx7GP34aBNPlDmiUmT86labsbb8_f1U2RCN1Jatg,21494
523
523
  wbportfolio/viewsets/transactions/transactions.py,sha256=ixDp-nsNA8t_A06rBCT19hOMJHy0iRmdz1XKdV1OwAs,4450
524
- wbportfolio-1.49.3.dist-info/METADATA,sha256=juDIyj3pwmZDS4xegw-suJ-JTKaEKhCfpNKFG6ubqqc,734
525
- wbportfolio-1.49.3.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
526
- wbportfolio-1.49.3.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
527
- wbportfolio-1.49.3.dist-info/RECORD,,
524
+ wbportfolio-1.49.5.dist-info/METADATA,sha256=5rVe6Q67fXJTmyNtQe8MP7cfiBkqOQ7z-sRlfgU5f-U,734
525
+ wbportfolio-1.49.5.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
526
+ wbportfolio-1.49.5.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
527
+ wbportfolio-1.49.5.dist-info/RECORD,,