wbportfolio 1.55.9__py2.py3-none-any.whl → 1.55.10rc0__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.
Files changed (56) hide show
  1. wbportfolio/api_clients/ubs.py +11 -9
  2. wbportfolio/contrib/company_portfolio/configs/display.py +4 -4
  3. wbportfolio/contrib/company_portfolio/configs/previews.py +3 -3
  4. wbportfolio/contrib/company_portfolio/filters.py +10 -10
  5. wbportfolio/contrib/company_portfolio/scripts.py +7 -2
  6. wbportfolio/factories/product_groups.py +3 -3
  7. wbportfolio/factories/products.py +3 -3
  8. wbportfolio/import_export/handlers/asset_position.py +3 -3
  9. wbportfolio/import_export/handlers/dividend.py +1 -1
  10. wbportfolio/import_export/handlers/fees.py +2 -2
  11. wbportfolio/import_export/handlers/trade.py +3 -3
  12. wbportfolio/import_export/parsers/default_mapping.py +1 -1
  13. wbportfolio/import_export/parsers/jpmorgan/customer_trade.py +2 -2
  14. wbportfolio/import_export/parsers/jpmorgan/fees.py +2 -2
  15. wbportfolio/import_export/parsers/jpmorgan/strategy.py +2 -2
  16. wbportfolio/import_export/parsers/jpmorgan/valuation.py +2 -2
  17. wbportfolio/import_export/parsers/leonteq/trade.py +2 -1
  18. wbportfolio/import_export/parsers/natixis/utils.py +6 -2
  19. wbportfolio/import_export/parsers/sg_lux/equity.py +4 -3
  20. wbportfolio/import_export/parsers/sg_lux/sylk.py +12 -11
  21. wbportfolio/import_export/parsers/sg_lux/valuation.py +4 -2
  22. wbportfolio/import_export/parsers/societe_generale/strategy.py +3 -3
  23. wbportfolio/import_export/parsers/tellco/customer_trade.py +2 -1
  24. wbportfolio/import_export/parsers/tellco/valuation.py +4 -3
  25. wbportfolio/import_export/parsers/ubs/equity.py +2 -1
  26. wbportfolio/import_export/parsers/ubs/valuation.py +2 -1
  27. wbportfolio/import_export/utils.py +3 -1
  28. wbportfolio/metric/backends/base.py +2 -2
  29. wbportfolio/models/asset.py +2 -1
  30. wbportfolio/models/custodians.py +3 -3
  31. wbportfolio/models/exceptions.py +1 -1
  32. wbportfolio/models/graphs/utils.py +11 -11
  33. wbportfolio/models/orders/order_proposals.py +2 -2
  34. wbportfolio/models/portfolio.py +7 -7
  35. wbportfolio/models/portfolio_relationship.py +6 -0
  36. wbportfolio/models/products.py +3 -0
  37. wbportfolio/models/rebalancing.py +3 -0
  38. wbportfolio/models/roles.py +4 -10
  39. wbportfolio/models/transactions/claim.py +6 -5
  40. wbportfolio/pms/typing.py +1 -1
  41. wbportfolio/rebalancing/models/market_capitalization_weighted.py +1 -5
  42. wbportfolio/risk_management/backends/controversy_portfolio.py +1 -1
  43. wbportfolio/risk_management/backends/instrument_list_portfolio.py +1 -1
  44. wbportfolio/risk_management/tests/test_stop_loss_instrument.py +2 -2
  45. wbportfolio/risk_management/tests/test_stop_loss_portfolio.py +1 -1
  46. wbportfolio/serializers/transactions/claim.py +2 -2
  47. wbportfolio/tests/models/test_portfolios.py +5 -5
  48. wbportfolio/tests/models/test_splits.py +1 -6
  49. wbportfolio/tests/viewsets/test_products.py +1 -0
  50. wbportfolio/viewsets/charts/assets.py +6 -4
  51. wbportfolio/viewsets/configs/buttons/mixins.py +2 -2
  52. wbportfolio/viewsets/configs/display/reconciliations.py +4 -4
  53. {wbportfolio-1.55.9.dist-info → wbportfolio-1.55.10rc0.dist-info}/METADATA +1 -1
  54. {wbportfolio-1.55.9.dist-info → wbportfolio-1.55.10rc0.dist-info}/RECORD +56 -56
  55. {wbportfolio-1.55.9.dist-info → wbportfolio-1.55.10rc0.dist-info}/WHEEL +0 -0
  56. {wbportfolio-1.55.9.dist-info → wbportfolio-1.55.10rc0.dist-info}/licenses/LICENSE +0 -0
@@ -3,21 +3,21 @@ import plotly.graph_objects as go
3
3
  from networkx.drawing.nx_agraph import graphviz_layout
4
4
 
5
5
 
6
- def reformat_graph_layout(G, layout):
6
+ def reformat_graph_layout(g, layout):
7
7
  """
8
8
  this method provide positions based on layout algorithm
9
- :param G:
9
+ :param g:
10
10
  :param layout:
11
11
  :return:
12
12
  """
13
13
  if layout == "graphviz":
14
- positions = graphviz_layout(G)
14
+ positions = graphviz_layout(g)
15
15
  elif layout == "spring":
16
- positions = nx.fruchterman_reingold_layout(G, k=0.5, iterations=1000)
16
+ positions = nx.fruchterman_reingold_layout(g, k=0.5, iterations=1000)
17
17
  elif layout == "spectral":
18
- positions = nx.spectral_layout(G, scale=0.1)
18
+ positions = nx.spectral_layout(g, scale=0.1)
19
19
  elif layout == "random":
20
- positions = nx.random_layout(G)
20
+ positions = nx.random_layout(g)
21
21
  else:
22
22
  raise Exception("please specify the layout from graphviz, spring, spectral or random")
23
23
 
@@ -25,7 +25,7 @@ def reformat_graph_layout(G, layout):
25
25
 
26
26
 
27
27
  def networkx_graph_to_plotly(
28
- G: nx.Graph,
28
+ g: nx.Graph,
29
29
  labels: dict[str, str] | None = None,
30
30
  node_size: int = 10,
31
31
  edge_weight: int = 1,
@@ -36,12 +36,12 @@ def networkx_graph_to_plotly(
36
36
  """
37
37
  Visualize a NetworkX graph using Plotly.
38
38
  """
39
- positions = reformat_graph_layout(G, layout)
39
+ positions = reformat_graph_layout(g, layout)
40
40
  if not labels:
41
41
  labels = {}
42
42
  # Initialize edge traces
43
43
  edge_traces = []
44
- for edge in G.edges():
44
+ for edge in g.edges():
45
45
  x0, y0 = positions[edge[0]]
46
46
  x1, y1 = positions[edge[1]]
47
47
 
@@ -52,12 +52,12 @@ def networkx_graph_to_plotly(
52
52
 
53
53
  # Initialize node trace
54
54
  node_x, node_y, node_colors, node_labels = [], [], [], []
55
- for node in G.nodes():
55
+ for node in g.nodes():
56
56
  x, y = positions[node]
57
57
  node_x.append(x)
58
58
  node_y.append(y)
59
59
  node_labels.append(labels.get(node, node))
60
- node_colors.append(len(list(G.neighbors(node)))) # Color based on degree
60
+ node_colors.append(len(list(g.neighbors(node)))) # Color based on degree
61
61
 
62
62
  node_trace = go.Scatter(
63
63
  x=node_x,
@@ -675,7 +675,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
675
675
  except (ValidationError, DatabaseError) as e:
676
676
  self.status = OrderProposal.Status.FAILED
677
677
  if not silent_exception:
678
- raise ValidationError(e)
678
+ raise ValidationError(e) from e
679
679
  return
680
680
  logger.info("Submitting order proposal ...")
681
681
  self.submit()
@@ -1193,7 +1193,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
1193
1193
  return warning
1194
1194
 
1195
1195
  def can_cancelexecution(self):
1196
- if not self.execution_status in [ExecutionStatus.PENDING, ExecutionStatus.IN_DRAFT]:
1196
+ if self.execution_status not in [ExecutionStatus.PENDING, ExecutionStatus.IN_DRAFT]:
1197
1197
  return {"execution_status": "Execution can only be cancelled if it is not already executed"}
1198
1198
 
1199
1199
  def update_execution_status(self):
@@ -72,7 +72,7 @@ class DefaultPortfolioQueryset(QuerySet):
72
72
  """
73
73
  A method to sort the given queryset to return undependable portfolio first. This is very useful if a routine needs to be applied sequentially on portfolios by order of dependence.
74
74
  """
75
- MAX_ITERATIONS: int = (
75
+ max_iterations: int = (
76
76
  5 # in order to avoid circular dependency and infinite loop, we need to stop recursion at a max depth
77
77
  )
78
78
  remaining_portfolios = set(self)
@@ -85,7 +85,7 @@ class DefaultPortfolioQueryset(QuerySet):
85
85
  dependency_relationships = PortfolioPortfolioThroughModel.objects.filter(
86
86
  portfolio=p, dependency_portfolio__in=remaining_portfolios
87
87
  ) # get dependency portfolios
88
- if iterator_counter >= MAX_ITERATIONS or (
88
+ if iterator_counter >= max_iterations or (
89
89
  not dependency_relationships.exists() and not bool(parent_portfolios)
90
90
  ): # if not dependency portfolio or parent portfolio that remained, then we yield
91
91
  remaining_portfolios.remove(p)
@@ -949,7 +949,7 @@ class Portfolio(DeleteToDisableMixin, WBModel):
949
949
  except IndexError:
950
950
  position.portfolio_created = None
951
951
 
952
- setattr(position, "path", path)
952
+ position.path = path
953
953
  position.initial_shares = None
954
954
  if portfolio_total_asset_value and (price_fx_portfolio := position.price * position.currency_fx_rate):
955
955
  position.initial_shares = (position.weighting * portfolio_total_asset_value) / price_fx_portfolio
@@ -982,13 +982,13 @@ class Portfolio(DeleteToDisableMixin, WBModel):
982
982
  )
983
983
  if not to_date:
984
984
  to_date = from_date
985
- for from_date in pd.date_range(from_date, to_date, freq="B").date:
986
- logger.info(f"Compute Look-Through for {self} at {from_date}")
985
+ for val_date in pd.date_range(from_date, to_date, freq="B").date:
986
+ logger.info(f"Compute Look-Through for {self} at {val_date}")
987
987
  portfolio_total_asset_value = (
988
- self.primary_portfolio.get_total_asset_under_management(from_date) if not self.only_weighting else None
988
+ self.primary_portfolio.get_total_asset_under_management(val_date) if not self.only_weighting else None
989
989
  )
990
990
  self.builder.add(
991
- list(self.primary_portfolio.get_lookthrough_positions(from_date, portfolio_total_asset_value)),
991
+ list(self.primary_portfolio.get_lookthrough_positions(val_date, portfolio_total_asset_value)),
992
992
  infer_underlying_quote_price=True,
993
993
  )
994
994
  self.builder.bulk_create_positions(delete_leftovers=True)
@@ -61,6 +61,9 @@ class InstrumentPortfolioThroughModel(models.Model):
61
61
  models.UniqueConstraint(fields=["instrument", "portfolio"], name="unique_portfolio_relationship"),
62
62
  ]
63
63
 
64
+ def __str__(self) -> str:
65
+ return f"{self.instrument} - {self.portfolio}"
66
+
64
67
  @classmethod
65
68
  def get_portfolio(cls, instrument):
66
69
  with suppress(InstrumentPortfolioThroughModel.DoesNotExist):
@@ -99,6 +102,9 @@ class PortfolioInstrumentPreferredClassificationThroughModel(models.Model):
99
102
  related_name="preferred_classification_group_throughs",
100
103
  )
101
104
 
105
+ def __str__(self) -> str:
106
+ return f"{self.portfolio} - {self.instrument}: ({self.classification})"
107
+
102
108
  def save(self, *args, **kwargs) -> None:
103
109
  if not self.classification_group and self.classification:
104
110
  self.classification_group = self.classification.group
@@ -190,6 +190,9 @@ class FeeProductPercentage(models.Model):
190
190
  ),
191
191
  ]
192
192
 
193
+ def __str__(self) -> str:
194
+ return f"{self.product.name} ({self.type})"
195
+
193
196
  @property
194
197
  def net_percent(self) -> Decimal:
195
198
  return self.percent
@@ -89,6 +89,9 @@ class Rebalancer(ComplexToStringMixin, models.Model):
89
89
  help_text=_("The Evaluation Frequency in RRULE format"),
90
90
  )
91
91
 
92
+ def __str__(self) -> str:
93
+ return f"{self.portfolio.name} ({self.rebalancing_model})"
94
+
92
95
  def save(self, *args, **kwargs):
93
96
  if not self.activation_date:
94
97
  try:
@@ -52,16 +52,10 @@ class PortfolioRole(models.Model):
52
52
  return f"{self.role_type} {self.person.computed_str}"
53
53
 
54
54
  def save(self, *args, **kwargs):
55
- assert (
56
- self.role_type in [self.RoleType.MANAGER, self.RoleType.RISK_MANAGER] and not self.instrument
57
- ) or self.role_type in [
58
- self.RoleType.PORTFOLIO_MANAGER,
59
- self.RoleType.ANALYST,
60
- ], self.default_error_messages["manager"].format(model="instrument")
61
-
62
- assert (self.start and self.end and self.start < self.end) or (
63
- not self.start or not self.end
64
- ), self.default_error_messages["start_end"]
55
+ if self.role_type in [self.RoleType.MANAGER, self.RoleType.RISK_MANAGER] and self.instrument:
56
+ raise ValueError(self.default_error_messages["manager"].format(model="instrument"))
57
+ if self.start and self.end and self.start > self.end:
58
+ raise ValueError(self.default_error_messages["start_end"])
65
59
 
66
60
  super().save(*args, **kwargs)
67
61
 
@@ -259,9 +259,10 @@ class Claim(ReferenceIDMixin, WBModel):
259
259
  return f"{self.reference_id} {self.product.name} ({self.bank} - {self.shares:,} shares - {self.date}) "
260
260
 
261
261
  def save(self, *args, auto_match: bool = True, **kwargs):
262
- assert (
263
- self.shares is not None or self.nominal_amount is not None
264
- ), f"Either shares or nominal amount have to be provided. Shares={self.shares}, Nominal={self.nominal_amount}"
262
+ if self.shares is None and self.nominal_amount is None:
263
+ raise ValueError(
264
+ f"Either shares or nominal amount have to be provided. Shares={self.shares}, Nominal={self.nominal_amount}"
265
+ )
265
266
  if self.product:
266
267
  if self.shares is not None:
267
268
  self.nominal_amount = self.shares * self.product.share_price
@@ -447,7 +448,7 @@ class Claim(ReferenceIDMixin, WBModel):
447
448
  return self.can_approve()
448
449
 
449
450
  def auto_match(self) -> Trade | None:
450
- SHARES_EPSILON = 1 # share
451
+ shares_epsilon = 1 # share
451
452
  auto_match_trade = None
452
453
  # Obvious filtering
453
454
  trades = Trade.valid_customer_trade_objects.filter(
@@ -458,7 +459,7 @@ class Claim(ReferenceIDMixin, WBModel):
458
459
  trades = trades.filter(underlying_instrument=self.product)
459
460
  # Find trades by shares (or remaining to be claimed)
460
461
  trades = trades.filter(
461
- Q(diff_shares__lte=self.shares + SHARES_EPSILON) & Q(diff_shares__gte=self.shares - SHARES_EPSILON)
462
+ Q(diff_shares__lte=self.shares + shares_epsilon) & Q(diff_shares__gte=self.shares - shares_epsilon)
462
463
  )
463
464
  if trades.count() == 1:
464
465
  auto_match_trade = trades.first()
wbportfolio/pms/typing.py CHANGED
@@ -269,7 +269,7 @@ class TradeBatch:
269
269
 
270
270
  def convert_to_portfolio(self, use_effective: bool = False, *extra_positions):
271
271
  positions = []
272
- for instrument, trade in self.trades_map.items():
272
+ for trade in self.trades_map.values():
273
273
  positions.append(
274
274
  Position(
275
275
  underlying_instrument=trade.underlying_instrument,
@@ -108,11 +108,7 @@ class MarketCapitalizationRebalancing(AbstractRebalancingModel):
108
108
  return df.any()
109
109
  else:
110
110
  if missing_exchanges.exists():
111
- setattr(
112
- self,
113
- "_validation_errors",
114
- f"Couldn't find any market capitalization for exchanges {', '.join([str(e) for e in missing_exchanges])}",
115
- )
111
+ self._validation_errors = f"Couldn't find any market capitalization for exchanges {', '.join([str(e) for e in missing_exchanges])}"
116
112
  return df.all()
117
113
  return False
118
114
 
@@ -44,7 +44,7 @@ class RuleBackend(ActivePortfolioRelationshipMixin):
44
44
  return RuleBackendSerializer
45
45
 
46
46
  def _process_dto(self, portfolio: PortfolioDTO, **kwargs) -> Generator[backend.IncidentResult, None, None]:
47
- for instrument_id, weight in portfolio.positions_map.items():
47
+ for instrument_id in portfolio.positions_map.keys():
48
48
  instrument = Instrument.objects.get(id=instrument_id)
49
49
  if (
50
50
  controversies := Controversy.objects.filter(
@@ -64,7 +64,7 @@ class RuleBackend(ActivePortfolioRelationshipMixin):
64
64
  return RuleBackendSerializer
65
65
 
66
66
  def _process_dto(self, portfolio: PortfolioDTO, **kwargs) -> Generator[backend.IncidentResult, None, None]:
67
- for instrument_id, weight in portfolio.positions_map.items():
67
+ for instrument_id in portfolio.positions_map.keys():
68
68
  instrument = Instrument.objects.get(id=instrument_id)
69
69
  relationships = self.instruments_relationship.filter(instrument=instrument, validated=True)
70
70
 
@@ -92,7 +92,7 @@ class TestStopLossInstrumentRuleModel(PortfolioTestMixin):
92
92
  date=weekday, net_value=500, calculated=False, instrument=benchmark
93
93
  )
94
94
  RelatedInstrumentThroughModel.objects.create(instrument=product, related_instrument=benchmark, is_primary=True)
95
- setattr(stop_loss_instrument_backend, "dynamic_benchmark_type", "PRIMARY_BENCHMARK")
95
+ stop_loss_instrument_backend.dynamic_benchmark_type = "PRIMARY_BENCHMARK"
96
96
 
97
97
  res = list(stop_loss_instrument_backend.check_rule())
98
98
  assert len(res) == 0
@@ -105,7 +105,7 @@ class TestStopLossInstrumentRuleModel(PortfolioTestMixin):
105
105
  assert len(res) == 1
106
106
  assert res[0].breached_object.id == product.id
107
107
 
108
- setattr(stop_loss_instrument_backend, "static_benchmark", benchmark)
108
+ stop_loss_instrument_backend.static_benchmark = benchmark
109
109
  res = list(stop_loss_instrument_backend.check_rule())
110
110
  assert len(res) == 1
111
111
  assert res[0].breached_object.id == product.id
@@ -119,7 +119,7 @@ class TestStopLossPortfolioRuleModel(PortfolioTestMixin):
119
119
  res = list(stop_loss_portfolio_backend.check_rule())
120
120
  assert len(res) == 0
121
121
 
122
- setattr(stop_loss_portfolio_backend, "static_benchmark", benchmark)
122
+ stop_loss_portfolio_backend.static_benchmark = benchmark
123
123
  res = list(stop_loss_portfolio_backend.check_rule())
124
124
  assert len(res) == 1
125
125
  assert res[0].breached_object.id == instrument.id
@@ -49,8 +49,8 @@ class ClaimAPIModelSerializer(serializers.ModelSerializer):
49
49
  if "isin" in request.data:
50
50
  try:
51
51
  data["product"] = Product.objects.get(isin=request.data["isin"])
52
- except Product.DoesNotExist:
53
- raise ValidationError({"isin": "A product with this ISIN does not exist."})
52
+ except Product.DoesNotExist as e:
53
+ raise ValidationError({"isin": "A product with this ISIN does not exist."}) from e
54
54
  if trade := data.get("trade", None):
55
55
  if not trade.is_claimable:
56
56
  raise ValidationError({"trade": "Only a claimable trade can be selected"})
@@ -924,13 +924,13 @@ class TestPortfolioModel(PortfolioTestMixin):
924
924
 
925
925
  analytic_portfolio = portfolio.get_analytic_portfolio(weekday)
926
926
  assert analytic_portfolio.weights.tolist() == [float(a1.weighting), float(a2.weighting)]
927
- expected_X = pd.DataFrame(
927
+ expected_x = pd.DataFrame(
928
928
  [[float(p11.net_value / p10.net_value - Decimal(1)), float(p21.net_value / p20.net_value - Decimal(1))]],
929
929
  columns=[i1.id, i2.id],
930
930
  index=[(weekday + BDay(1)).date()],
931
931
  )
932
- expected_X.index = pd.to_datetime(expected_X.index)
933
- pd.testing.assert_frame_equal(analytic_portfolio.X, expected_X, check_names=False, check_freq=False)
932
+ expected_x.index = pd.to_datetime(expected_x.index)
933
+ pd.testing.assert_frame_equal(analytic_portfolio.X, expected_x, check_names=False, check_freq=False)
934
934
 
935
935
  def test_get_total_asset_value(self, weekday, portfolio, asset_position_factory):
936
936
  a1 = asset_position_factory.create(date=weekday, portfolio=portfolio)
@@ -1010,7 +1010,7 @@ class TestPortfolioModel(PortfolioTestMixin):
1010
1010
  assert len(res) == 1
1011
1011
  assert a.date == weekday
1012
1012
  assert a.underlying_quote == instrument
1013
- assert a.underlying_quote_price == None
1013
+ assert a.underlying_quote_price is None
1014
1014
  assert a.initial_price == p.net_value
1015
1015
  assert a.weighting == pytest.approx(weights[instrument.id], abs=Decimal(10e-6))
1016
1016
  assert a.currency_fx_rate_portfolio_to_usd == fx_portfolio
@@ -1053,7 +1053,7 @@ class TestPortfolioModel(PortfolioTestMixin):
1053
1053
  assert next(gen)[0] == middle_date, "Drifting weight with a non automatic rebalancer stops the iteration"
1054
1054
  try:
1055
1055
  next(gen)
1056
- assert False, "the next iteration should stop and return the rebalancing"
1056
+ raise AssertionError("the next iteration should stop and return the rebalancing")
1057
1057
  except StopIteration as e:
1058
1058
  rebalancing_order_proposal = e.value
1059
1059
  assert rebalancing_order_proposal.trade_date == rebalancing_date
@@ -14,10 +14,6 @@ fake = Faker()
14
14
 
15
15
  @pytest.mark.django_db
16
16
  class TestAdjustmentModel:
17
- @pytest.fixture()
18
- def applied_adjustment(self):
19
- return AdjustmentFactory.create(status=Adjustment.Status.APPLIED)
20
-
21
17
  @pytest.fixture()
22
18
  def old_adjustment(self):
23
19
  return AdjustmentFactory.create(status=Adjustment.Status.PENDING, date=fake.past_date())
@@ -205,7 +201,7 @@ class TestAdjustmentModel:
205
201
  post_adjustment_on_prices(adjustment.id)
206
202
  a1.refresh_from_db()
207
203
  adjustment.refresh_from_db()
208
- a1.applied_adjustment == adjustment
204
+ assert a1.applied_adjustment == adjustment
209
205
  assert adjustment.status == Adjustment.Status.APPLIED
210
206
 
211
207
  @patch("wbportfolio.models.adjustments.send_notification")
@@ -230,5 +226,4 @@ class TestAdjustmentModel:
230
226
  mock_check_fct.return_value = False
231
227
  post_adjustment_on_prices(adjustment.id)
232
228
  adjustment.refresh_from_db()
233
- mock_delay_fct.call_args[0] == user_porftolio_manager.id
234
229
  assert adjustment.status == Adjustment.Status.PENDING
@@ -23,6 +23,7 @@ class TestProductModelViewSet:
23
23
  portfolio_factory.create_batch(4, invested_timespan=DateRange(date.min, date.max))
24
24
  + portfolio_factory.create_batch(2, invested_timespan=DateRange(date.min, date.max)),
25
25
  product_factory.create_batch(6),
26
+ strict=False,
26
27
  ):
27
28
  InstrumentPortfolioThroughModel.objects.update_or_create(
28
29
  instrument=product, defaults={"portfolio": portfolio}
@@ -101,7 +101,9 @@ class AbstractDistributionMixin(UserPortfolioRequestPermissionMixin):
101
101
  columns = {}
102
102
  level_representations = self.classification_group.get_levels_representation()
103
103
  for key, label in zip(
104
- reversed(self.classification_group.get_fields_names(sep="_")), reversed(level_representations[1:])
104
+ reversed(self.classification_group.get_fields_names(sep="_")),
105
+ reversed(level_representations[1:]),
106
+ strict=False,
105
107
  ):
106
108
  columns[key] = label
107
109
  columns["label"] = "Classification"
@@ -169,8 +171,8 @@ class AbstractDistributionMixin(UserPortfolioRequestPermissionMixin):
169
171
  dict(Classification.objects.filter(id__in=df["classification"].dropna()).values_list("id", "name"))
170
172
  )
171
173
  elif self.group_by == AssetPositionGroupBy.CASH:
172
- df.loc[df["id"] == True, "label"] = "Cash"
173
- df.loc[df["id"] == False, "label"] = "Non-Cash"
174
+ df.loc[df["id"], "label"] = "Cash"
175
+ df.loc[~df["id"], "label"] = "Non-Cash"
174
176
  elif self.group_by == AssetPositionGroupBy.COUNTRY:
175
177
  df["label"] = df["id"].map(dict(Geography.objects.filter(id__in=df["id"]).values_list("id", "name")))
176
178
  elif self.group_by == AssetPositionGroupBy.CURRENCY:
@@ -307,7 +309,7 @@ class ContributorPortfolioChartView(UserPortfolioRequestPermissionMixin, viewset
307
309
 
308
310
  text_forex = df_forex.contribution_forex.apply(lambda x: f"{x:,.2%}")
309
311
  text_equity = contribution_equity.apply(lambda x: f"{x:,.2%}")
310
- setattr(self, "nb_rows", df.shape[0])
312
+ self.nb_rows = df.shape[0]
311
313
  fig.add_trace(
312
314
  go.Bar(
313
315
  y=df.instrument_id,
@@ -7,7 +7,7 @@ from wbfdm.models.instruments import Instrument
7
7
 
8
8
  class InstrumentButtonMixin:
9
9
  @classmethod
10
- def add_instrument_request_button(self, request=None, view=None, pk=None, **kwargs):
10
+ def add_instrument_request_button(cls, request=None, view=None, pk=None, **kwargs):
11
11
  buttons = [
12
12
  bt.WidgetButton(key="assets", label="Implemented Portfolios (Assets)"),
13
13
  # bt.WidgetButton(
@@ -44,7 +44,7 @@ class InstrumentButtonMixin:
44
44
  )
45
45
 
46
46
  @classmethod
47
- def add_transactions_request_button(self, request=None, view=None, pk=None, **kwargs):
47
+ def add_transactions_request_button(cls, request=None, view=None, pk=None, **kwargs):
48
48
  return bt.DropDownButton(
49
49
  label="Transactions",
50
50
  icon=WBIcon.UNFOLD.icon,
@@ -49,14 +49,14 @@ class AccountReconciliationLineDisplayViewConfig(DisplayViewConfig):
49
49
  ]
50
50
  editable = [dp.FormattingRule(style={"fontWeight": "bold"})]
51
51
  equal = [dp.FormattingRule(condition=("==", False, "is_equal"), style={"backgroundColor": "orange"})]
52
- borderLeft = [
52
+ border_left = [
53
53
  dp.FormattingRule(
54
54
  style={
55
55
  "borderLeft": "1px solid #bdc3c7",
56
56
  }
57
57
  )
58
58
  ]
59
- borderRight = [
59
+ border_right = [
60
60
  dp.FormattingRule(
61
61
  style={
62
62
  "borderRight": "1px solid #bdc3c7",
@@ -111,7 +111,7 @@ class AccountReconciliationLineDisplayViewConfig(DisplayViewConfig):
111
111
  key="shares_external",
112
112
  label="Shares",
113
113
  width=90,
114
- formatting_rules=[*equal, *editable, *borderLeft],
114
+ formatting_rules=[*equal, *editable, *border_left],
115
115
  ),
116
116
  dp.Field(
117
117
  key="nominal_value_external",
@@ -128,7 +128,7 @@ class AccountReconciliationLineDisplayViewConfig(DisplayViewConfig):
128
128
  key="assets_under_management_external",
129
129
  label="AuM",
130
130
  width=120,
131
- formatting_rules=[*equal, *borderRight],
131
+ formatting_rules=[*equal, *border_right],
132
132
  ),
133
133
  ],
134
134
  ),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbportfolio
3
- Version: 1.55.9
3
+ Version: 1.55.10rc0
4
4
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
5
  License-File: LICENSE
6
6
  Requires-Dist: cryptography==3.4.*