wbportfolio 1.54.16__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.

@@ -394,35 +394,34 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
394
394
  orders = service.trades_batch.trades_map.values()
395
395
  for order_dto in orders:
396
396
  instrument = Instrument.objects.get(id=order_dto.underlying_instrument)
397
- if not instrument.is_cash: # we do not save order that includes cash component
398
- currency_fx_rate = instrument.currency.convert(
399
- 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,
400
416
  )
401
- # we cannot do a bulk-create because Order is a multi table inheritance
402
- weighting = round(order_dto.delta_weight, Order.ORDER_WEIGHTING_PRECISION)
403
- daily_return = order_dto.daily_return
404
- try:
405
- order = self.orders.get(underlying_instrument=instrument)
406
- order.weighting = weighting
407
- order.currency_fx_rate = currency_fx_rate
408
- order.daily_return = daily_return
409
- except Order.DoesNotExist:
410
- order = Order(
411
- underlying_instrument=instrument,
412
- order_proposal=self,
413
- value_date=self.trade_date,
414
- weighting=weighting,
415
- daily_return=daily_return,
416
- currency_fx_rate=currency_fx_rate,
417
- )
418
- order.price = order.get_price()
419
- order.order_type = Order.get_type(weighting, order_dto.previous_weight, order_dto.target_weight)
420
- # if we cannot automatically find a price, we consider the stock is invalid and we sell it
421
- if not order.price:
422
- order.price = Decimal("0.0")
423
- order.weighting = -order_dto.effective_weight
424
-
425
- 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()
426
425
  # final sanity check to make sure invalid order with effective and target weight of 0 are automatically removed:
427
426
  self.get_orders().filter(target_weight=0, effective_weight=0).delete()
428
427
 
@@ -579,7 +578,9 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
579
578
  currency = self.portfolio.currency
580
579
 
581
580
  # Calculate the total target weight of all orders
582
- total_target_weight = orders.aggregate(s=models.Sum("target_weight"))["s"] or Decimal(0)
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
585
  if target_cash_weight is None:
585
586
  target_cash_weight = Decimal("1") - total_target_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))
@@ -423,13 +450,20 @@ class TestOrderProposal:
423
450
  order_proposal.portfolio.only_weighting = False
424
451
  order_proposal.portfolio.save()
425
452
  mock_fct.return_value = Decimal(1_000_000) # 1 million cash
453
+ cash = cash_factory.create(currency=order_proposal.portfolio.currency)
426
454
  order_factory.create( # equity trade
427
455
  order_proposal=order_proposal,
428
456
  value_date=order_proposal.trade_date,
429
457
  portfolio=order_proposal.portfolio,
430
458
  weighting=Decimal("0.7"),
431
459
  )
432
-
460
+ order_factory.create( # cash trade
461
+ order_proposal=order_proposal,
462
+ value_date=order_proposal.trade_date,
463
+ portfolio=order_proposal.portfolio,
464
+ underlying_instrument=cash,
465
+ weighting=Decimal("0.2"),
466
+ )
433
467
  target_cash_position = order_proposal.get_estimated_target_cash()
434
468
  assert target_cash_position.weighting == Decimal("0.3")
435
469
  assert target_cash_position.initial_shares == Decimal(1_000_000) * Decimal("0.3")
@@ -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.16
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=uo0hWOjY6lKpGwa9fnBvskQmI0S_Usg7LYbY9PCJ4xo,40333
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
@@ -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=xVoKYHhzm_UihJuEY5P8G1kUURCYYSnyFVAR4aPFmUA,29353
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.16.dist-info/METADATA,sha256=ikKYoJRibnFOVA5vPklrR-QM2077_-0GboeKE0Xfp1A,703
554
- wbportfolio-1.54.16.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
555
- wbportfolio-1.54.16.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
556
- wbportfolio-1.54.16.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,,