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

Files changed (31) hide show
  1. wbportfolio/factories/orders/orders.py +10 -2
  2. wbportfolio/filters/assets.py +10 -2
  3. wbportfolio/import_export/handlers/orders.py +1 -1
  4. wbportfolio/migrations/0086_orderproposal_total_cash_weight.py +19 -0
  5. wbportfolio/models/asset.py +6 -20
  6. wbportfolio/models/builder.py +4 -6
  7. wbportfolio/models/orders/order_proposals.py +53 -20
  8. wbportfolio/models/orders/orders.py +4 -1
  9. wbportfolio/models/portfolio.py +1 -0
  10. wbportfolio/models/products.py +9 -0
  11. wbportfolio/pms/trading/handler.py +0 -1
  12. wbportfolio/serializers/orders/order_proposals.py +2 -18
  13. wbportfolio/tests/conftest.py +6 -2
  14. wbportfolio/tests/models/orders/test_order_proposals.py +61 -15
  15. wbportfolio/tests/models/test_products.py +11 -0
  16. wbportfolio/tests/signals.py +0 -10
  17. wbportfolio/tests/tests.py +2 -0
  18. wbportfolio/viewsets/__init__.py +7 -4
  19. wbportfolio/viewsets/assets.py +1 -215
  20. wbportfolio/viewsets/charts/__init__.py +6 -1
  21. wbportfolio/viewsets/charts/assets.py +338 -155
  22. wbportfolio/viewsets/configs/buttons/products.py +32 -2
  23. wbportfolio/viewsets/configs/display/assets.py +6 -19
  24. wbportfolio/viewsets/configs/display/products.py +1 -1
  25. wbportfolio/viewsets/orders/configs/buttons/order_proposals.py +12 -2
  26. wbportfolio/viewsets/orders/order_proposals.py +2 -1
  27. {wbportfolio-1.54.22.dist-info → wbportfolio-1.54.23.dist-info}/METADATA +3 -1
  28. {wbportfolio-1.54.22.dist-info → wbportfolio-1.54.23.dist-info}/RECORD +30 -30
  29. wbportfolio/viewsets/signals.py +0 -43
  30. {wbportfolio-1.54.22.dist-info → wbportfolio-1.54.23.dist-info}/WHEEL +0 -0
  31. {wbportfolio-1.54.22.dist-info → wbportfolio-1.54.23.dist-info}/licenses/LICENSE +0 -0
@@ -19,6 +19,8 @@ for key, value in default_config.items():
19
19
  "AccountReconciliationLine",
20
20
  "TopDownPortfolioCompositionPandasAPIView",
21
21
  "CompositionModelPortfolioPandasView",
22
+ "DistributionChartViewSet",
23
+ "DistributionTableViewSet",
22
24
  # "ClaimModelViewSet",
23
25
  # "ClaimModelSerializer",
24
26
  ],
@@ -2,12 +2,16 @@ from .assets import (
2
2
  AssetPositionInstrumentModelViewSet,
3
3
  AssetPositionModelViewSet,
4
4
  AssetPositionPortfolioModelViewSet,
5
- AssetPositionUnderlyingInstrumentChartViewSet,
6
5
  CashPositionPortfolioPandasAPIView,
7
6
  CompositionModelPortfolioPandasView,
8
- ContributorPortfolioChartView,
9
7
  )
10
- from .charts import *
8
+ from .charts import (
9
+ DistributionChartViewSet,
10
+ DistributionTableViewSet,
11
+ AssetPositionUnderlyingInstrumentChartViewSet,
12
+ ContributorPortfolioChartView
13
+
14
+ )
11
15
  from .custodians import CustodianModelViewSet, CustodianRepresentationViewSet
12
16
  from .portfolio_relationship import InstrumentPreferedClassificationThroughProductModelViewSet
13
17
  from .portfolios import (
@@ -23,7 +27,6 @@ from .positions import (
23
27
  )
24
28
  from .registers import RegisterModelViewSet, RegisterRepresentationViewSet
25
29
  from .roles import PortfolioRoleInstrumentModelViewSet, PortfolioRoleModelViewSet
26
- from .signals import *
27
30
  from .rebalancing import RebalancingModelRepresentationViewSet, RebalancerRepresentationViewSet, RebalancerModelViewSet
28
31
  from .transactions import *
29
32
  from .orders import *
@@ -2,23 +2,15 @@ from contextlib import suppress
2
2
  from datetime import date, datetime
3
3
 
4
4
  import pandas as pd
5
- import plotly.graph_objects as go
6
5
  from django.db.models import Exists, F, OuterRef, Q, Sum
7
6
  from django.utils.dateparse import parse_date
8
7
  from django.utils.functional import cached_property
9
- from plotly.subplots import make_subplots
10
8
  from wbcore import viewsets
11
- from wbcore.contrib.currency.models import Currency, CurrencyFXRates
9
+ from wbcore.contrib.currency.models import CurrencyFXRates
12
10
  from wbcore.contrib.io.viewsets import ExportPandasAPIViewSet
13
- from wbcore.filters import DjangoFilterBackend
14
11
  from wbcore.pandas import fields as pf
15
12
  from wbcore.permissions.permissions import InternalUserPermissionMixin
16
13
  from wbcore.serializers import decorator
17
- from wbcore.utils.date import get_date_interval_from_request
18
- from wbcore.utils.figures import (
19
- get_default_timeserie_figure,
20
- get_hovertemplate_timeserie,
21
- )
22
14
  from wbcore.utils.strings import format_number
23
15
  from wbfdm.contrib.metric.viewsets.mixins import InstrumentMetricMixin
24
16
  from wbfdm.models import Instrument
@@ -27,11 +19,8 @@ from wbportfolio.filters import (
27
19
  AssetPositionFilter,
28
20
  AssetPositionInstrumentFilter,
29
21
  AssetPositionPortfolioFilter,
30
- AssetPositionUnderlyingInstrumentChartFilter,
31
22
  CashPositionPortfolioFilterSet,
32
- CompositionContributionChartFilter,
33
23
  CompositionModelPortfolioPandasFilter,
34
- ContributionChartFilter,
35
24
  )
36
25
  from wbportfolio.import_export.resources.assets import AssetPositionResource
37
26
  from wbportfolio.metric.backends.portfolio_base import (
@@ -69,16 +58,12 @@ from .configs import (
69
58
  AssetPositionPortfolioEndpointConfig,
70
59
  AssetPositionPortfolioTitleConfig,
71
60
  AssetPositionTitleConfig,
72
- AssetPositionUnderlyingInstrumentChartEndpointConfig,
73
- AssetPositionUnderlyingInstrumentChartTitleConfig,
74
61
  CashPositionPortfolioDisplayConfig,
75
62
  CashPositionPortfolioEndpointConfig,
76
63
  CashPositionPortfolioTitleConfig,
77
64
  CompositionModelPortfolioPandasDisplayConfig,
78
65
  CompositionModelPortfolioPandasEndpointConfig,
79
66
  CompositionModelPortfolioPandasTitleConfig,
80
- ContributorPortfolioChartEndpointConfig,
81
- ContributorPortfolioChartTitleConfig,
82
67
  )
83
68
  from .mixins import UserPortfolioRequestPermissionMixin
84
69
 
@@ -344,205 +329,6 @@ class CashPositionPortfolioPandasAPIView(
344
329
  return AssetPosition.objects.none()
345
330
 
346
331
 
347
- # ##### CHART VIEWS #####
348
-
349
-
350
- class ContributorPortfolioChartView(UserPortfolioRequestPermissionMixin, viewsets.ChartViewSet):
351
- filterset_class = ContributionChartFilter
352
- filter_backends = (DjangoFilterBackend,)
353
- IDENTIFIER = "wbportfolio:portfolio-contributor"
354
- queryset = AssetPosition.objects.all()
355
-
356
- title_config_class = ContributorPortfolioChartTitleConfig
357
- endpoint_config_class = ContributorPortfolioChartEndpointConfig
358
-
359
- ROW_HEIGHT: int = 20
360
-
361
- @property
362
- def min_height(self):
363
- if hasattr(self, "nb_rows"):
364
- return self.nb_rows * self.ROW_HEIGHT
365
- return "300px"
366
-
367
- @cached_property
368
- def hedged_currency(self) -> Currency | None:
369
- if "hedged_currency" in self.request.GET:
370
- with suppress(Currency.DoesNotExist):
371
- return Currency.objects.get(pk=self.request.GET["hedged_currency"])
372
-
373
- @cached_property
374
- def show_lookthrough(self) -> bool:
375
- return self.portfolio.is_composition and self.request.GET.get("show_lookthrough", "false").lower() == "true"
376
-
377
- def get_filterset_class(self, request):
378
- if self.portfolio.is_composition:
379
- return CompositionContributionChartFilter
380
- return ContributionChartFilter
381
-
382
- def get_plotly(self, queryset):
383
- fig = go.Figure()
384
- data = []
385
- if self.show_lookthrough:
386
- d1, d2 = get_date_interval_from_request(self.request)
387
- for _d in pd.date_range(d1, d2):
388
- for pos in self.portfolio.get_lookthrough_positions(_d.date()):
389
- data.append(
390
- [
391
- pos.date,
392
- pos.initial_price,
393
- pos.initial_currency_fx_rate,
394
- pos.underlying_instrument_id,
395
- pos.weighting,
396
- ]
397
- )
398
- else:
399
- data = queryset.annotate_hedged_currency_fx_rate(self.hedged_currency).values_list(
400
- "date", "price", "hedged_currency_fx_rate", "underlying_instrument", "weighting"
401
- )
402
- df = Portfolio.get_contribution_df(data).rename(columns={"group_key": "underlying_instrument"})
403
- if not df.empty:
404
- df = df[["contribution_total", "contribution_forex", "underlying_instrument"]].sort_values(
405
- by="contribution_total", ascending=True
406
- )
407
-
408
- df["instrument_id"] = df.underlying_instrument.map(
409
- dict(Instrument.objects.filter(id__in=df["underlying_instrument"]).values_list("id", "name_repr"))
410
- )
411
- df_forex = df[["instrument_id", "contribution_forex"]]
412
- df_forex = df_forex[df_forex.contribution_forex != 0]
413
-
414
- contribution_equity = df.contribution_total - df.contribution_forex
415
-
416
- text_forex = df_forex.contribution_forex.apply(lambda x: f"{x:,.2%}")
417
- text_equity = contribution_equity.apply(lambda x: f"{x:,.2%}")
418
- setattr(self, "nb_rows", df.shape[0])
419
- fig.add_trace(
420
- go.Bar(
421
- y=df.instrument_id,
422
- x=contribution_equity,
423
- name="Contribution Equity",
424
- orientation="h",
425
- marker=dict(
426
- color="rgba(247,110,91,0.6)",
427
- line=dict(color="rgb(247,110,91,1.0)", width=2),
428
- ),
429
- text=text_equity.values,
430
- textposition="auto",
431
- )
432
- )
433
- fig.add_trace(
434
- go.Bar(
435
- y=df_forex.instrument_id,
436
- x=df_forex.contribution_forex,
437
- name="Contribution Forex",
438
- orientation="h",
439
- marker=dict(
440
- color="rgba(58, 71, 80, 0.6)",
441
- line=dict(color="rgba(58, 71, 80, 1.0)", width=2),
442
- ),
443
- text=text_forex.values,
444
- textposition="outside",
445
- )
446
- )
447
- fig.update_layout(
448
- barmode="relative",
449
- xaxis=dict(showgrid=False, showline=False, zeroline=False, tickformat=".2%"),
450
- yaxis=dict(showgrid=False, showline=False, zeroline=False, tickmode="linear"),
451
- margin=dict(b=0, r=20, l=20, t=0, pad=20),
452
- paper_bgcolor="rgba(0,0,0,0)",
453
- plot_bgcolor="rgba(0,0,0,0)",
454
- font=dict(family="roboto", size=12, color="black"),
455
- bargap=0.3,
456
- )
457
- # fig = get_horizontal_barplot(df, x_label="contribution_total", y_label="name")
458
- return fig
459
-
460
- def parse_figure_dict(self, figure_dict: dict[str, any]) -> dict[str, any]:
461
- figure_dict = super().parse_figure_dict(figure_dict)
462
- figure_dict["style"]["minHeight"] = self.min_height
463
- return figure_dict
464
-
465
- def get_queryset(self):
466
- if self.has_portfolio_access:
467
- return super().get_queryset().filter(portfolio=self.portfolio)
468
- return AssetPosition.objects.none()
469
-
470
-
471
- class AssetPositionUnderlyingInstrumentChartViewSet(UserPortfolioRequestPermissionMixin, viewsets.ChartViewSet):
472
- IDENTIFIER = "wbportfolio:assetpositionchart"
473
-
474
- queryset = AssetPosition.objects.all()
475
-
476
- title_config_class = AssetPositionUnderlyingInstrumentChartTitleConfig
477
- endpoint_config_class = AssetPositionUnderlyingInstrumentChartEndpointConfig
478
- filterset_class = AssetPositionUnderlyingInstrumentChartFilter
479
-
480
- def get_queryset(self):
481
- return AssetPosition.objects.filter(underlying_quote__in=self.instrument.get_descendants(include_self=True))
482
-
483
- def get_plotly(self, queryset):
484
- fig = make_subplots(specs=[[{"secondary_y": True}]])
485
- fig = get_default_timeserie_figure(fig)
486
- if queryset.exists():
487
- df_weight = pd.DataFrame(queryset.values("date", "weighting", "portfolio__name"))
488
- df_weight = df_weight.where(pd.notnull(df_weight), 0)
489
- df_weight = df_weight.groupby(["date", "portfolio__name"]).sum().reset_index()
490
- min_date = df_weight["date"].min()
491
- max_date = df_weight["date"].max()
492
-
493
- df_price = (
494
- pd.DataFrame(
495
- self.instrument.prices.filter_only_valid_prices()
496
- .annotate_base_data()
497
- .filter(date__gte=min_date, date__lte=max_date)
498
- .values_list("date", "net_value_usd"),
499
- columns=["date", "price_fx_usd"],
500
- )
501
- .set_index("date")
502
- .sort_index()
503
- )
504
-
505
- fig.add_trace(
506
- go.Scatter(
507
- x=df_price.index, y=df_price.price_fx_usd, mode="lines", marker_color="green", name="Price"
508
- ),
509
- secondary_y=False,
510
- )
511
-
512
- df_weight = pd.DataFrame(queryset.values("date", "weighting", "portfolio__name"))
513
- df_weight = df_weight.where(pd.notnull(df_weight), 0)
514
- df_weight = df_weight.groupby(["date", "portfolio__name"]).sum().reset_index()
515
- for portfolio_name, df_tmp in df_weight.groupby("portfolio__name"):
516
- fig.add_trace(
517
- go.Scatter(
518
- x=df_tmp.date,
519
- y=df_tmp.weighting,
520
- hovertemplate=get_hovertemplate_timeserie(is_percent=True),
521
- mode="lines",
522
- name=f"Allocation: {portfolio_name}",
523
- ),
524
- secondary_y=True,
525
- )
526
-
527
- # Set x-axis title
528
- fig.update_xaxes(title_text="Date")
529
- # Set y-axes titles
530
- fig.update_yaxes(
531
- title_text="<b>Price</b>",
532
- secondary_y=False,
533
- titlefont=dict(color="green"),
534
- tickfont=dict(color="green"),
535
- )
536
- fig.update_yaxes(
537
- title_text="<b>Portfolio Allocation (%)</b>",
538
- secondary_y=True,
539
- titlefont=dict(color="blue"),
540
- tickfont=dict(color="blue"),
541
- )
542
-
543
- return fig
544
-
545
-
546
332
  class CompositionModelPortfolioPandasView(
547
333
  UserPortfolioRequestPermissionMixin, InternalUserPermissionMixin, ExportPandasAPIViewSet
548
334
  ):
@@ -1 +1,6 @@
1
- from .assets import DistributionChartViewSet, DistributionTableViewSet
1
+ from .assets import (
2
+ DistributionChartViewSet,
3
+ DistributionTableViewSet,
4
+ AssetPositionUnderlyingInstrumentChartViewSet,
5
+ ContributorPortfolioChartView
6
+ )