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

@@ -24,7 +24,6 @@ from django_fsm import FSMField, transition
24
24
  from pandas._libs.tslibs.offsets import BDay
25
25
  from wbcompliance.models.risk_management.mixins import RiskCheckMixin
26
26
  from wbcore.contrib.authentication.models import User
27
- from wbcore.contrib.currency.models import Currency
28
27
  from wbcore.contrib.icons import WBIcon
29
28
  from wbcore.contrib.notifications.dispatch import send_notification
30
29
  from wbcore.enums import RequestType
@@ -188,6 +187,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
188
187
  effective_weight=Round(
189
188
  F("contribution") / Value(portfolio_contribution), precision=Order.ORDER_WEIGHTING_PRECISION
190
189
  ),
190
+ tmp_effective_weight=F("contribution") / Value(portfolio_contribution),
191
191
  target_weight=Round(F("effective_weight") + F("weighting"), precision=Order.ORDER_WEIGHTING_PRECISION),
192
192
  effective_shares=Coalesce(
193
193
  Subquery(
@@ -206,7 +206,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
206
206
  )
207
207
  total_effective_weight = orders.aggregate(s=models.Sum("effective_weight"))["s"] or Decimal("1")
208
208
  with suppress(Order.DoesNotExist):
209
- largest_order = orders.latest("weighting")
209
+ largest_order = orders.latest("effective_weight")
210
210
  if quant_error := Decimal("1") - total_effective_weight:
211
211
  orders = orders.annotate(
212
212
  effective_weight=models.Case(
@@ -226,7 +226,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
226
226
  def __str__(self) -> str:
227
227
  return f"{self.portfolio.name}: {self.trade_date} ({self.status})"
228
228
 
229
- def convert_to_portfolio(self, use_effective: bool = False) -> PortfolioDTO:
229
+ def convert_to_portfolio(self, use_effective: bool = False, with_cash: bool = True) -> PortfolioDTO:
230
230
  """
231
231
  Data Transfer Object
232
232
  Returns:
@@ -258,6 +258,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
258
258
  except InvalidAnalyticPortfolio:
259
259
  last_returns, portfolio_contribution = {}, 1
260
260
  positions = []
261
+ total_weighting = Decimal("0")
261
262
  for instrument, row in portfolio.items():
262
263
  weighting = row["weighting"]
263
264
  daily_return = Decimal(last_returns.get(instrument.id, 0))
@@ -285,6 +286,10 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
285
286
  currency_fx_rate=row["currency_fx_rate"],
286
287
  )
287
288
  )
289
+ total_weighting += weighting
290
+ if with_cash and (cash_weight := Decimal("1") - total_weighting):
291
+ cash_position = self.get_estimated_target_cash(target_cash_weight=cash_weight)
292
+ positions.append(cash_position._build_dto())
288
293
  return PortfolioDTO(positions)
289
294
 
290
295
  # Start tools methods
@@ -325,7 +330,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
325
330
  service = TradingService(
326
331
  self.trade_date,
327
332
  effective_portfolio=self._get_default_effective_portfolio(),
328
- target_portfolio=self.convert_to_portfolio(),
333
+ target_portfolio=self.convert_to_portfolio(use_effective=False, with_cash=False),
329
334
  total_target_weight=total_target_weight,
330
335
  )
331
336
  leftovers_orders = self.orders.all()
@@ -352,7 +357,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
352
357
  return self.rebalancing_model.get_target_portfolio(
353
358
  self.portfolio, self.trade_date, self.value_date, **params
354
359
  )
355
- return self.convert_to_portfolio()
360
+ return self.convert_to_portfolio(use_effective=False)
356
361
 
357
362
  def _get_default_effective_portfolio(self):
358
363
  return self.convert_to_portfolio(use_effective=True)
@@ -389,35 +394,34 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
389
394
  orders = service.trades_batch.trades_map.values()
390
395
  for order_dto in orders:
391
396
  instrument = Instrument.objects.get(id=order_dto.underlying_instrument)
392
- if not instrument.is_cash: # we do not save order that includes cash component
393
- currency_fx_rate = instrument.currency.convert(
394
- self.value_date, self.portfolio.currency, exact_lookup=True
397
+ currency_fx_rate = instrument.currency.convert(
398
+ self.value_date, self.portfolio.currency, exact_lookup=True
399
+ )
400
+ # we cannot do a bulk-create because Order is a multi table inheritance
401
+ weighting = round(order_dto.delta_weight, Order.ORDER_WEIGHTING_PRECISION)
402
+ daily_return = order_dto.daily_return
403
+ try:
404
+ order = self.orders.get(underlying_instrument=instrument)
405
+ order.weighting = weighting
406
+ order.currency_fx_rate = currency_fx_rate
407
+ order.daily_return = daily_return
408
+ except Order.DoesNotExist:
409
+ order = Order(
410
+ underlying_instrument=instrument,
411
+ order_proposal=self,
412
+ value_date=self.trade_date,
413
+ weighting=weighting,
414
+ daily_return=daily_return,
415
+ currency_fx_rate=currency_fx_rate,
395
416
  )
396
- # we cannot do a bulk-create because Order is a multi table inheritance
397
- weighting = round(order_dto.delta_weight, Order.ORDER_WEIGHTING_PRECISION)
398
- daily_return = order_dto.daily_return
399
- try:
400
- order = self.orders.get(underlying_instrument=instrument)
401
- order.weighting = weighting
402
- order.currency_fx_rate = currency_fx_rate
403
- order.daily_return = daily_return
404
- except Order.DoesNotExist:
405
- order = Order(
406
- underlying_instrument=instrument,
407
- order_proposal=self,
408
- value_date=self.trade_date,
409
- weighting=weighting,
410
- daily_return=daily_return,
411
- currency_fx_rate=currency_fx_rate,
412
- )
413
- order.price = order.get_price()
414
- order.order_type = Order.get_type(weighting, order_dto.previous_weight, order_dto.target_weight)
415
- # if we cannot automatically find a price, we consider the stock is invalid and we sell it
416
- if not order.price:
417
- order.price = Decimal("0.0")
418
- order.weighting = -order_dto.effective_weight
419
-
420
- order.save()
417
+ order.price = order.get_price()
418
+ order.order_type = Order.get_type(weighting, order_dto.previous_weight, order_dto.target_weight)
419
+ # if we cannot automatically find a price, we consider the stock is invalid and we sell it
420
+ if not order.price:
421
+ order.price = Decimal("0.0")
422
+ order.weighting = -order_dto.effective_weight
423
+
424
+ order.save()
421
425
  # final sanity check to make sure invalid order with effective and target weight of 0 are automatically removed:
422
426
  self.get_orders().filter(target_weight=0, effective_weight=0).delete()
423
427
 
@@ -455,8 +459,8 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
455
459
  last_order_proposal = self
456
460
  last_order_proposal_created = False
457
461
  while last_order_proposal and last_order_proposal.status == OrderProposal.Status.APPROVED:
462
+ logger.info(f"Replaying order proposal {last_order_proposal}")
458
463
  if not last_order_proposal_created:
459
- logger.info(f"Replaying order proposal {last_order_proposal}")
460
464
  last_order_proposal.approve_workflow(
461
465
  silent_exception=True,
462
466
  force_reset_order=True,
@@ -557,50 +561,43 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
557
561
  shares = math.floor(shares / round_lot_size) * round_lot_size
558
562
  return shares
559
563
 
560
- def get_estimated_target_cash(self, currency: Currency) -> AssetPosition:
564
+ def get_estimated_target_cash(self, target_cash_weight: Decimal | None = None) -> AssetPosition:
561
565
  """
562
566
  Estimates the target cash weight and shares for a order proposal.
563
567
 
564
568
  This method calculates the target cash weight by summing the weights of cash orders and adding any leftover weight from non-cash orders. It then estimates the target shares for this cash component if the portfolio is not only weighting-based.
565
569
 
566
570
  Args:
567
- currency (Currency): The currency for the target currency component
571
+ target_cash_weight (Decimal): the expected target cash weight (Optional). If not provided, we estimate from the existing orders
568
572
 
569
573
  Returns:
570
574
  tuple[Decimal, Decimal]: A tuple containing the target cash weight and the estimated target shares.
571
575
  """
572
576
  # Retrieve orders with base information
573
577
  orders = self.get_orders()
578
+ currency = self.portfolio.currency
574
579
 
575
- # Calculate the target cash weight from cash orders
576
- target_cash_weight = orders.filter(
577
- underlying_instrument__is_cash=True, underlying_instrument__currency=currency
578
- ).aggregate(s=models.Sum("target_weight"))["s"] or Decimal(0)
579
- # if the specified currency match the portfolio's currency, we include the weight leftover to this cash compoenent
580
- if currency == self.portfolio.currency:
581
- # Calculate the total target weight of all orders
582
- total_target_weight = orders.aggregate(s=models.Sum("target_weight"))["s"] or Decimal(0)
580
+ # Calculate the total target weight of all orders
581
+ total_target_weight = orders.exclude(underlying_instrument__is_cash=True).aggregate(
582
+ s=models.Sum("target_weight")
583
+ )["s"] or Decimal(0)
583
584
 
584
- # Add any leftover weight as cash
585
- target_cash_weight += Decimal(1) - total_target_weight
585
+ if target_cash_weight is None:
586
+ target_cash_weight = Decimal("1") - total_target_weight
586
587
 
587
588
  # Initialize target shares to zero
588
589
  total_target_shares = Decimal(0)
589
590
 
591
+ # Get or create a cash component for the portfolio's currency
592
+ cash_component = Cash.objects.get_or_create(
593
+ currency=currency, defaults={"is_cash": True, "name": currency.title}
594
+ )[0]
590
595
  # If the portfolio is not only weighting-based, estimate the target shares for the cash component
591
596
  if not self.portfolio.only_weighting:
592
- # Get or create a cash component for the portfolio's currency
593
- cash_component = Cash.objects.get_or_create(
594
- currency=currency, defaults={"is_cash": True, "name": currency.title}
595
- )[0]
596
-
597
597
  # Estimate the target shares for the cash component
598
598
  with suppress(ValueError):
599
599
  total_target_shares = self.get_estimated_shares(target_cash_weight, cash_component, Decimal("1.0"))
600
600
 
601
- cash_component = Cash.objects.get_or_create(
602
- currency=self.portfolio.currency, defaults={"name": self.portfolio.currency.title}
603
- )[0]
604
601
  # otherwise, we create a new position
605
602
  underlying_quote_price = InstrumentPrice.objects.get_or_create(
606
603
  instrument=cash_component,
@@ -657,7 +654,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
657
654
  Order.objects.bulk_update(orders, ["shares", "weighting"])
658
655
 
659
656
  # If we estimate cash on this order proposal, we make sure to create the corresponding cash component
660
- estimated_cash_position = self.get_estimated_target_cash(self.portfolio.currency)
657
+ estimated_cash_position = self.get_estimated_target_cash()
661
658
  target_portfolio = self.validated_trading_service.trades_batch.convert_to_portfolio(
662
659
  estimated_cash_position._build_dto()
663
660
  )
@@ -717,7 +714,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
717
714
  assets = []
718
715
  warnings = []
719
716
  # We do not want to create the estimated cash position if there is not orders in the order proposal (shouldn't be possible anyway)
720
- estimated_cash_position = self.get_estimated_target_cash(self.portfolio.currency)
717
+ estimated_cash_position = self.get_estimated_target_cash()
721
718
  for order in self.get_orders():
722
719
  with suppress(ValueError):
723
720
  asset = order.get_asset()
@@ -143,33 +143,32 @@ class TradingService:
143
143
 
144
144
  trades: list[Trade] = []
145
145
  for instrument_id, pos in instruments.items():
146
- if not pos.is_cash:
147
- previous_weight = target_weight = 0
148
- effective_shares = target_shares = 0
149
- daily_return = 0
150
- if effective_pos := effective_portfolio.positions_map.get(instrument_id, None):
151
- previous_weight = effective_pos.weighting
152
- effective_shares = effective_pos.shares
153
- daily_return = effective_pos.daily_return
154
- if target_pos := target_portfolio.positions_map.get(instrument_id, None):
155
- target_weight = target_pos.weighting
156
- if target_pos.shares is not None:
157
- target_shares = target_pos.shares
158
- trade = Trade(
159
- underlying_instrument=instrument_id,
160
- previous_weight=previous_weight,
161
- target_weight=target_weight,
162
- effective_shares=effective_shares,
163
- target_shares=target_shares,
164
- date=self.trade_date,
165
- instrument_type=pos.instrument_type,
166
- currency=pos.currency,
167
- price=Decimal(pos.price) if pos.price is not None else Decimal("0"),
168
- currency_fx_rate=Decimal(pos.currency_fx_rate),
169
- daily_return=Decimal(daily_return),
170
- portfolio_contribution=effective_portfolio.portfolio_contribution,
171
- )
172
- trades.append(trade)
146
+ previous_weight = target_weight = 0
147
+ effective_shares = target_shares = 0
148
+ daily_return = 0
149
+ if effective_pos := effective_portfolio.positions_map.get(instrument_id, None):
150
+ previous_weight = effective_pos.weighting
151
+ effective_shares = effective_pos.shares
152
+ daily_return = effective_pos.daily_return
153
+ if target_pos := target_portfolio.positions_map.get(instrument_id, None):
154
+ target_weight = target_pos.weighting
155
+ if target_pos.shares is not None:
156
+ target_shares = target_pos.shares
157
+ trade = Trade(
158
+ underlying_instrument=instrument_id,
159
+ previous_weight=previous_weight,
160
+ target_weight=target_weight,
161
+ effective_shares=effective_shares,
162
+ target_shares=target_shares,
163
+ date=self.trade_date,
164
+ instrument_type=pos.instrument_type,
165
+ currency=pos.currency,
166
+ price=Decimal(pos.price) if pos.price is not None else Decimal("0"),
167
+ currency_fx_rate=Decimal(pos.currency_fx_rate),
168
+ daily_return=Decimal(daily_return),
169
+ portfolio_contribution=effective_portfolio.portfolio_contribution,
170
+ )
171
+ trades.append(trade)
173
172
  return TradeBatch(trades)
174
173
 
175
174
  def is_valid(self, ignore_error: bool = False) -> bool:
@@ -23,7 +23,7 @@ class OrderProposalModelSerializer(wb_serializers.ModelSerializer):
23
23
  rebalancing_model = wb_serializers.PrimaryKeyRelatedField(queryset=RebalancingModel.objects.all(), required=False)
24
24
  _rebalancing_model = RebalancingModelRepresentationSerializer(source="rebalancing_model")
25
25
  target_portfolio = wb_serializers.PrimaryKeyRelatedField(
26
- queryset=Portfolio.objects.all(), write_only=True, required=False, default=DefaultFromView("portfolio")
26
+ queryset=Portfolio.objects.all(), write_only=True, required=False
27
27
  )
28
28
  _target_portfolio = PortfolioRepresentationSerializer(source="target_portfolio")
29
29
  total_cash_weight = wb_serializers.DecimalField(
@@ -51,8 +51,13 @@ class OrderProposalModelSerializer(wb_serializers.ModelSerializer):
51
51
  obj = super().create(validated_data)
52
52
 
53
53
  target_portfolio_dto = None
54
- if target_portfolio and not rebalancing_model:
54
+ if target_portfolio:
55
55
  target_portfolio_dto = target_portfolio._build_dto(obj.trade_date)
56
+ elif rebalancing_model:
57
+ target_portfolio_dto = rebalancing_model.get_target_portfolio(
58
+ obj.portfolio, obj.trade_date, obj.last_effective_date
59
+ )
60
+
56
61
  try:
57
62
  obj.reset_orders(
58
63
  target_portfolio=target_portfolio_dto, total_target_weight=Decimal("1.0") - total_cash_weight
@@ -248,7 +248,9 @@ class TestOrderProposal:
248
248
  assert t3.weighting == normalized_t3_weight
249
249
 
250
250
  # Test resetting orders
251
- def test_reset_orders(self, order_proposal, instrument_factory, instrument_price_factory, asset_position_factory):
251
+ def test_reset_orders(
252
+ self, order_proposal, instrument_factory, cash, instrument_price_factory, asset_position_factory
253
+ ):
252
254
  """
253
255
  Verify orders are correctly reset based on effective and target portfolios.
254
256
  """
@@ -334,6 +336,31 @@ class TestOrderProposal:
334
336
  assert t2.weighting == Decimal("0")
335
337
  assert t3.weighting == Decimal("0.5")
336
338
 
339
+ # assert cash position creates a proper order
340
+ # build the target portfolio
341
+ target_portfolio_with_cash = PortfolioDTO(
342
+ [
343
+ Position(
344
+ underlying_instrument=i1.id,
345
+ date=order_proposal.trade_date,
346
+ weighting=Decimal("0.5"),
347
+ price=float(p1.net_value),
348
+ ),
349
+ Position(
350
+ underlying_instrument=cash.id,
351
+ date=order_proposal.trade_date,
352
+ weighting=Decimal("0.5"),
353
+ price=1.0,
354
+ ),
355
+ ]
356
+ )
357
+ order_proposal.reset_orders(target_portfolio=target_portfolio_with_cash)
358
+
359
+ # Assert existing trade weights are correctly updated
360
+ assert order_proposal.orders.get(underlying_instrument=i1).weighting == Decimal("-0.2")
361
+ assert order_proposal.orders.get(underlying_instrument=i2).weighting == Decimal("-0.3")
362
+ assert order_proposal.orders.get(underlying_instrument=cash).weighting == Decimal("0.5")
363
+
337
364
  def test_reset_orders_remove_invalid_orders(self, order_proposal, order_factory, instrument_price_factory):
338
365
  # create a invalid trade and its price
339
366
  invalid_trade = order_factory.create(order_proposal=order_proposal, weighting=Decimal(0))
@@ -437,9 +464,8 @@ class TestOrderProposal:
437
464
  underlying_instrument=cash,
438
465
  weighting=Decimal("0.2"),
439
466
  )
440
-
441
- target_cash_position = order_proposal.get_estimated_target_cash(order_proposal.portfolio.currency)
442
- assert target_cash_position.weighting == Decimal("0.2") + Decimal("1.0") - (Decimal("0.7") + Decimal("0.2"))
467
+ target_cash_position = order_proposal.get_estimated_target_cash()
468
+ assert target_cash_position.weighting == Decimal("0.3")
443
469
  assert target_cash_position.initial_shares == Decimal(1_000_000) * Decimal("0.3")
444
470
 
445
471
  def test_order_proposal_update_inception_date(self, order_proposal_factory, portfolio, instrument_factory):
@@ -198,22 +198,26 @@ class OrderOrderProposalModelViewSet(
198
198
  return Order.objects.none()
199
199
 
200
200
  def get_queryset(self):
201
- return self.orders.annotate(
202
- underlying_instrument_isin=F("underlying_instrument__isin"),
203
- underlying_instrument_ticker=F("underlying_instrument__ticker"),
204
- underlying_instrument_refinitiv_identifier_code=F("underlying_instrument__refinitiv_identifier_code"),
205
- underlying_instrument_instrument_type=Case(
206
- When(
207
- underlying_instrument__parent__is_security=True,
208
- then=F("underlying_instrument__parent__instrument_type__short_name"),
201
+ return (
202
+ self.orders.exclude(underlying_instrument__is_cash=True)
203
+ .annotate(
204
+ underlying_instrument_isin=F("underlying_instrument__isin"),
205
+ underlying_instrument_ticker=F("underlying_instrument__ticker"),
206
+ underlying_instrument_refinitiv_identifier_code=F("underlying_instrument__refinitiv_identifier_code"),
207
+ underlying_instrument_instrument_type=Case(
208
+ When(
209
+ underlying_instrument__parent__is_security=True,
210
+ then=F("underlying_instrument__parent__instrument_type__short_name"),
211
+ ),
212
+ default=F("underlying_instrument__instrument_type__short_name"),
209
213
  ),
210
- default=F("underlying_instrument__instrument_type__short_name"),
211
- ),
212
- effective_total_value_fx_portfolio=F("effective_weight") * Value(self.portfolio_total_asset_value),
213
- target_total_value_fx_portfolio=F("target_weight") * Value(self.portfolio_total_asset_value),
214
- portfolio_currency=F("portfolio__currency__symbol"),
215
- security=F("underlying_instrument__parent"),
216
- company=F("underlying_instrument__parent__parent"),
217
- ).select_related(
218
- "underlying_instrument", "underlying_instrument__parent", "underlying_instrument__parent__parent"
214
+ effective_total_value_fx_portfolio=F("effective_weight") * Value(self.portfolio_total_asset_value),
215
+ target_total_value_fx_portfolio=F("target_weight") * Value(self.portfolio_total_asset_value),
216
+ portfolio_currency=F("portfolio__currency__symbol"),
217
+ security=F("underlying_instrument__parent"),
218
+ company=F("underlying_instrument__parent__parent"),
219
+ )
220
+ .select_related(
221
+ "underlying_instrument", "underlying_instrument__parent", "underlying_instrument__parent__parent"
222
+ )
219
223
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbportfolio
3
- Version: 1.54.15
3
+ Version: 1.54.17
4
4
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
5
  License-File: LICENSE
6
6
  Requires-Dist: cryptography==3.4.*
@@ -288,7 +288,7 @@ wbportfolio/models/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
288
288
  wbportfolio/models/mixins/instruments.py,sha256=SgBreTpa_X3uyCWo7t8B0VaTtl49IjmBMe4Pab6TjAM,6796
289
289
  wbportfolio/models/mixins/liquidity_stress_test.py,sha256=iQVzT3QM7VtHnqfj9gT6KUIe4wC4MJXery-AXJHUYns,58820
290
290
  wbportfolio/models/orders/__init__.py,sha256=EH9UacGR3npBMje5FGTeLOh1xqFBh9kc24WbGmBIA3g,69
291
- wbportfolio/models/orders/order_proposals.py,sha256=Yb8KVUyfYsE7-HgYLEfyOxFoQXzqFU-MhvR5Yrcf9oE,40464
291
+ wbportfolio/models/orders/order_proposals.py,sha256=U1vLwI_yS2E1TUs04NJGperusqmNyiE42XGlAVoPatk,40196
292
292
  wbportfolio/models/orders/orders.py,sha256=hVVw7NAFmAFHosMMs39V9DjGmWyFC_msSxF8rpDDG60,9683
293
293
  wbportfolio/models/reconciliations/__init__.py,sha256=MXH5fZIPGDRBgJkO6wVu_NLRs8fkP1im7G6d-h36lQY,127
294
294
  wbportfolio/models/reconciliations/account_reconciliation_lines.py,sha256=QP6M7hMcyFbuXBa55Y-azui6Dl_WgbzMntEqWzQkbfM,7394
@@ -307,7 +307,7 @@ wbportfolio/pms/analytics/portfolio.py,sha256=u_S-e6HUQwAyq90gweDmxyTHWrIc5nd84s
307
307
  wbportfolio/pms/analytics/utils.py,sha256=EfhKdo9B2ABaUPppb8DgZSqpNkSze8Rjej1xDjv-XcQ,282
308
308
  wbportfolio/pms/statistics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
309
309
  wbportfolio/pms/trading/__init__.py,sha256=R_yLKc54sCak8A1cW0O1Aszrcv5KV8mC_3h17Hr20e4,36
310
- wbportfolio/pms/trading/handler.py,sha256=iNcqiFgHqABD1gOniFo3_uBHG2s1lDhMZ8D8mEF27wo,8678
310
+ wbportfolio/pms/trading/handler.py,sha256=o31jtevfnSSv0aSzAsnthz2luM7F-D5d061LaqfOHUw,8542
311
311
  wbportfolio/rebalancing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
312
312
  wbportfolio/rebalancing/base.py,sha256=wpeoxdkLz5osxm5mRjkOoML7YkYvwuAlqSLLtHBbWp8,984
313
313
  wbportfolio/rebalancing/decorators.py,sha256=162ZmXV2YQGI830LWvEnJ95RexMzHvaCGfcVOnXTOXM,502
@@ -360,7 +360,7 @@ wbportfolio/serializers/registers.py,sha256=zhdKH_mHEBE0VOhm6xpY54bTMtcSaY5BskEa
360
360
  wbportfolio/serializers/roles.py,sha256=T-9NqTldpvaEMFy-Bib5MB6MeboygEOqcMP61mzzD3Q,2146
361
361
  wbportfolio/serializers/signals.py,sha256=hD6R4oFtwhvnsJPteytPKy2JwEelmxrapdfoLSnluaE,7053
362
362
  wbportfolio/serializers/orders/__init__.py,sha256=PKJRksA1pWsh8nVfGASoB0m3LyUzVRnq1m9VPp90J7k,271
363
- wbportfolio/serializers/orders/order_proposals.py,sha256=pXduRWC-Ad9-L5OlCPr3PK1Wa7LZdCcS6e_MkpbSNos,4301
363
+ wbportfolio/serializers/orders/order_proposals.py,sha256=FegiVe1d1AZoLtOuNOUfLN7HwGy4mKGrdYHKDwuUjPY,4430
364
364
  wbportfolio/serializers/orders/orders.py,sha256=pAKjJLRANOo1iMlcv18twuQ0aAVDVKYt-pPx6avnWRQ,7464
365
365
  wbportfolio/serializers/transactions/__init__.py,sha256=-7Pan4n7YI3iDvGXff6okzk4ycEURRxp5n_SHCY_g_I,493
366
366
  wbportfolio/serializers/transactions/claim.py,sha256=kC4E2RZRrpd9i8tGfoiV-gpWDk3ikR5F1Wf0v_IGIvw,11599
@@ -403,7 +403,7 @@ wbportfolio/tests/models/test_roles.py,sha256=4Cn7WyrA2ztJNeWLk5cy9kYo5XLWMbFSvo
403
403
  wbportfolio/tests/models/test_splits.py,sha256=ytKcHsI_90kj1L4s8It-KEcc24rkDcElxwQ8q0QxEvk,9689
404
404
  wbportfolio/tests/models/utils.py,sha256=ORNJq6NMo1Za22jGZXfTfKeNEnTRlfEt_8SJ6xLaQWg,325
405
405
  wbportfolio/tests/models/orders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
406
- wbportfolio/tests/models/orders/test_order_proposals.py,sha256=LrPs6HXU9BXsV8JNiFG_Xuaidw2kbRKd8-e4FYCOrbA,29791
406
+ wbportfolio/tests/models/orders/test_order_proposals.py,sha256=7OvZn9IhTNQ66lm2nLXhrdc9OzGuklrnbRgp2WeYQms,30802
407
407
  wbportfolio/tests/models/transactions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
408
408
  wbportfolio/tests/models/transactions/test_claim.py,sha256=NG3BKB-FVcIDgHSJHCjImxgMM3ISVUMl24xUPmEcPec,5570
409
409
  wbportfolio/tests/models/transactions/test_fees.py,sha256=tAp18x2wCNQr11LUnLtHNbBDbbX0v1DZnmW7i-cEi5Q,2423
@@ -532,7 +532,7 @@ wbportfolio/viewsets/configs/titles/roles.py,sha256=9LoJa3jgenXJ5UWRlIErTzdbjpSW
532
532
  wbportfolio/viewsets/configs/titles/trades.py,sha256=29XCLxvY0Xe3a2tjCno3tN2rRXCr9RWpbWnzurJfnYI,1986
533
533
  wbportfolio/viewsets/orders/__init__.py,sha256=N8v9jdEXryOzrLlc7ML3iBCO2lmNXph9_TWoQ7PTvi4,195
534
534
  wbportfolio/viewsets/orders/order_proposals.py,sha256=mw385zzU52nCq8p6xgionh43xLmOn5aX-2BPlCHnqlE,6214
535
- wbportfolio/viewsets/orders/orders.py,sha256=WH96S97MjZvCudP4b2Hp7YZPmdKfLNGKi90HY3oVQYY,10708
535
+ wbportfolio/viewsets/orders/orders.py,sha256=mekuUVkG9-tEtWGrIW2QfgTOuwOZWONiRnbztYUmcVM,10875
536
536
  wbportfolio/viewsets/orders/configs/__init__.py,sha256=5MU57JXiKi32_PicHtiNr7YHmMN020FrlF5NFJf_Wds,94
537
537
  wbportfolio/viewsets/orders/configs/buttons/__init__.py,sha256=EHzNmAfa0UQFITEF-wxj_s4wn3Y5DE3DCbEUmmvCTIs,106
538
538
  wbportfolio/viewsets/orders/configs/buttons/order_proposals.py,sha256=Q_7LrsuLzjSXCIoscFQXMMHl8cuCporDNM5k735W7d8,3584
@@ -550,7 +550,7 @@ wbportfolio/viewsets/transactions/claim.py,sha256=Pb1WftoO-w-ZSTbLRhmQubhy7hgd68
550
550
  wbportfolio/viewsets/transactions/fees.py,sha256=WT2bWWfgozz4_rpyTKX7dgBBTXD-gu0nlsd2Nk2Zh1Q,7028
551
551
  wbportfolio/viewsets/transactions/mixins.py,sha256=WipvJoi5hylkpD0y9VATe30WAcwIHUIroVkK10FYw7k,636
552
552
  wbportfolio/viewsets/transactions/trades.py,sha256=xBgOGaJ8aEg-2RxEJ4FDaBs4SGwuLasun3nhpis0WQY,12363
553
- wbportfolio-1.54.15.dist-info/METADATA,sha256=M1xqTeTLrYRvHviRzlNHBsh7GU-zCmYS1p5MlJBtiL4,703
554
- wbportfolio-1.54.15.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
555
- wbportfolio-1.54.15.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
556
- wbportfolio-1.54.15.dist-info/RECORD,,
553
+ wbportfolio-1.54.17.dist-info/METADATA,sha256=iv55pLD6PfL1brFSH400eFZJ1u_BieHNt_tO2nrQ3Us,703
554
+ wbportfolio-1.54.17.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
555
+ wbportfolio-1.54.17.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
556
+ wbportfolio-1.54.17.dist-info/RECORD,,