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.
- wbportfolio/models/orders/order_proposals.py +30 -29
- wbportfolio/tests/models/orders/test_order_proposals.py +36 -2
- wbportfolio/viewsets/orders/orders.py +21 -17
- {wbportfolio-1.54.16.dist-info → wbportfolio-1.54.17.dist-info}/METADATA +1 -1
- {wbportfolio-1.54.16.dist-info → wbportfolio-1.54.17.dist-info}/RECORD +7 -7
- {wbportfolio-1.54.16.dist-info → wbportfolio-1.54.17.dist-info}/WHEEL +0 -0
- {wbportfolio-1.54.16.dist-info → wbportfolio-1.54.17.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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.
|
|
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(
|
|
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
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
)
|
|
@@ -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=
|
|
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=
|
|
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=
|
|
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.
|
|
554
|
-
wbportfolio-1.54.
|
|
555
|
-
wbportfolio-1.54.
|
|
556
|
-
wbportfolio-1.54.
|
|
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,,
|
|
File without changes
|
|
File without changes
|