wbportfolio 1.44.2__py2.py3-none-any.whl → 1.44.4__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/analysis/claims.py +12 -7
- wbportfolio/filters/products.py +1 -1
- wbportfolio/filters/signals.py +0 -20
- wbportfolio/filters/transactions/__init__.py +1 -0
- wbportfolio/filters/transactions/claim.py +14 -2
- wbportfolio/migrations/0072_trade_diff_shares.py +19 -0
- wbportfolio/models/asset.py +4 -1
- wbportfolio/models/portfolio.py +2 -1
- wbportfolio/models/portfolio_cash_flow.py +3 -2
- wbportfolio/models/transactions/trades.py +57 -51
- wbportfolio/risk_management/tests/test_accounts.py +24 -12
- wbportfolio/serializers/transactions/trades.py +2 -6
- wbportfolio/viewsets/transactions/claim.py +46 -20
- wbportfolio/viewsets/transactions/trades.py +23 -5
- wbportfolio/viewsets/transactions/transactions.py +2 -4
- {wbportfolio-1.44.2.dist-info → wbportfolio-1.44.4.dist-info}/METADATA +1 -1
- {wbportfolio-1.44.2.dist-info → wbportfolio-1.44.4.dist-info}/RECORD +19 -18
- {wbportfolio-1.44.2.dist-info → wbportfolio-1.44.4.dist-info}/WHEEL +0 -0
- {wbportfolio-1.44.2.dist-info → wbportfolio-1.44.4.dist-info}/licenses/LICENSE +0 -0
wbportfolio/analysis/claims.py
CHANGED
|
@@ -3,7 +3,8 @@ from typing import TYPE_CHECKING
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
import pandas as pd
|
|
5
5
|
from django.db import connection
|
|
6
|
-
from django.db.models import DecimalField, ExpressionWrapper, F, OuterRef, Subquery
|
|
6
|
+
from django.db.models import DecimalField, ExpressionWrapper, F, OuterRef, Subquery, DateField
|
|
7
|
+
from django.db.models.functions import Greatest
|
|
7
8
|
from django.template.loader import get_template
|
|
8
9
|
from jinjasql import JinjaSql
|
|
9
10
|
from wbcore.contrib.currency.models import CurrencyFXRates
|
|
@@ -47,6 +48,7 @@ class ConsolidatedTradeSummary:
|
|
|
47
48
|
pivot_label: str,
|
|
48
49
|
classification_group: ClassificationGroup | None = None,
|
|
49
50
|
classification_height: int = 0,
|
|
51
|
+
date_label: str = "date_considered",
|
|
50
52
|
):
|
|
51
53
|
self.queryset = queryset.filter(trade__isnull=False, status="APPROVED")
|
|
52
54
|
self.start_date = start_date
|
|
@@ -60,6 +62,9 @@ class ConsolidatedTradeSummary:
|
|
|
60
62
|
fx_rate=CurrencyFXRates.get_fx_rates_subquery("date", lookup_expr="exact"),
|
|
61
63
|
aum=ExpressionWrapper(F("fx_rate") * F("net_value") * F("shares"), output_field=DecimalField()),
|
|
62
64
|
internal_trade=F("trade__marked_as_internal"),
|
|
65
|
+
date_considered=ExpressionWrapper(
|
|
66
|
+
Greatest("trade__transaction_date", "date") + 1, output_field=DateField()
|
|
67
|
+
),
|
|
63
68
|
)
|
|
64
69
|
self.queryset = Account.annotate_root_account_info(self.queryset)
|
|
65
70
|
|
|
@@ -71,17 +76,17 @@ class ConsolidatedTradeSummary:
|
|
|
71
76
|
"account",
|
|
72
77
|
"product__parent",
|
|
73
78
|
)
|
|
74
|
-
self.df = self._prepare_df()
|
|
79
|
+
self.df = self._prepare_df(date_label)
|
|
75
80
|
|
|
76
|
-
def _prepare_df(self) -> pd.DataFrame:
|
|
77
|
-
columns = ["shares",
|
|
81
|
+
def _prepare_df(self, date_label: str) -> pd.DataFrame:
|
|
82
|
+
columns = ["shares", date_label, "product__id", "aum", "internal_trade"]
|
|
78
83
|
if self.pivot == "classification_id":
|
|
79
84
|
columns.append("classifications")
|
|
80
85
|
else:
|
|
81
86
|
columns.append(self.pivot)
|
|
82
87
|
columns.append(self.pivot_label)
|
|
83
88
|
|
|
84
|
-
df = pd.DataFrame(self.queryset.values_list(*columns), columns=columns)
|
|
89
|
+
df = pd.DataFrame(self.queryset.values_list(*columns), columns=columns).rename(columns={date_label: "date"})
|
|
85
90
|
if self.pivot == "classification_id":
|
|
86
91
|
df = (
|
|
87
92
|
df.explode("classifications")
|
|
@@ -112,7 +117,7 @@ class ConsolidatedTradeSummary:
|
|
|
112
117
|
"net_value",
|
|
113
118
|
date_name="net_value_date_start",
|
|
114
119
|
instrument_pk_name="pk",
|
|
115
|
-
date_lookup="exact"
|
|
120
|
+
date_lookup="exact",
|
|
116
121
|
# we get all net value (even estimated) to avoir showing None price on holiday
|
|
117
122
|
),
|
|
118
123
|
fx_rate_start=CurrencyFXRates.get_fx_rates_subquery(
|
|
@@ -123,7 +128,7 @@ class ConsolidatedTradeSummary:
|
|
|
123
128
|
"net_value",
|
|
124
129
|
date_name="net_value_date_end",
|
|
125
130
|
instrument_pk_name="pk",
|
|
126
|
-
date_lookup="exact"
|
|
131
|
+
date_lookup="exact",
|
|
127
132
|
# we get all net value (even estimated) to avoir showing None price on holiday
|
|
128
133
|
),
|
|
129
134
|
fx_rate_end=CurrencyFXRates.get_fx_rates_subquery(
|
wbportfolio/filters/products.py
CHANGED
|
@@ -22,7 +22,7 @@ class BaseProductFilterSet(InstrumentFilterSet):
|
|
|
22
22
|
label="Bank",
|
|
23
23
|
queryset=Company.objects.all(),
|
|
24
24
|
endpoint=Company.get_representation_endpoint(),
|
|
25
|
-
filter_params={"
|
|
25
|
+
filter_params={"notnull_related_name": "issues_products"},
|
|
26
26
|
value_key=Company.get_representation_value_key(),
|
|
27
27
|
label_key=Company.get_representation_label_key(),
|
|
28
28
|
)
|
wbportfolio/filters/signals.py
CHANGED
|
@@ -3,32 +3,12 @@ from datetime import date
|
|
|
3
3
|
from django.db.models import Exists, OuterRef
|
|
4
4
|
from django.dispatch import receiver
|
|
5
5
|
from wbcore import filters as wb_filters
|
|
6
|
-
from wbcore.contrib.directory.filters import CompanyFilter
|
|
7
6
|
from wbcore.signals.filters import add_filters
|
|
8
7
|
from wbfdm.filters import BaseClassifiedInstrumentFilterSet, ClassificationFilter
|
|
9
8
|
from wbfdm.models import InstrumentClassificationThroughModel
|
|
10
9
|
from wbportfolio.models import AssetPosition, Portfolio
|
|
11
10
|
|
|
12
11
|
|
|
13
|
-
@receiver(add_filters, sender=CompanyFilter)
|
|
14
|
-
def add_bank_product_filter(sender, request=None, *args, **kwargs):
|
|
15
|
-
def method_bank(queryset, name, value):
|
|
16
|
-
if value is True:
|
|
17
|
-
return queryset.filter(issues_products__isnull=False).distinct()
|
|
18
|
-
elif value is False:
|
|
19
|
-
return queryset.filter(issues_products__isnull=True).distinct()
|
|
20
|
-
return queryset
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
"bank_product": wb_filters.BooleanFilter(
|
|
24
|
-
field_name="bank_product",
|
|
25
|
-
label="Is a Bank",
|
|
26
|
-
help_text="Filter for companies that are a bank and serve as the custodian of a product in the PMS",
|
|
27
|
-
method=method_bank,
|
|
28
|
-
)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
12
|
@receiver(add_filters, sender=ClassificationFilter)
|
|
33
13
|
def add_portfolio_filter(sender, request=None, *args, **kwargs):
|
|
34
14
|
def _filter_portfolio(queryset, name, value):
|
|
@@ -82,7 +82,6 @@ class CommissionBaseFilterSet(wb_filters.FilterSet):
|
|
|
82
82
|
endpoint=Account.get_representation_endpoint(),
|
|
83
83
|
value_key=Account.get_representation_value_key(),
|
|
84
84
|
label_key=Account.get_representation_label_key(),
|
|
85
|
-
filter_params={"status": "OPEN"},
|
|
86
85
|
)
|
|
87
86
|
|
|
88
87
|
manager_role = wb_filters.ModelChoiceFilter(
|
|
@@ -205,7 +204,7 @@ class ClaimFilter(OppositeSharesFieldMethodMixin, CommissionBaseFilterSet):
|
|
|
205
204
|
}
|
|
206
205
|
|
|
207
206
|
|
|
208
|
-
class ClaimGroupByFilter(
|
|
207
|
+
class ClaimGroupByFilter(CommissionBaseFilterSet):
|
|
209
208
|
pending_approval = in_charge_of_customer = linked_trade = None
|
|
210
209
|
|
|
211
210
|
only_new_customer = wb_filters.BooleanFilter(method="filter_only_new_customer", label="Only new customers")
|
|
@@ -303,6 +302,19 @@ class ConsolidatedTradeSummaryTableFilterSet(PandasFilterSetMixin, ClaimGroupByF
|
|
|
303
302
|
}
|
|
304
303
|
|
|
305
304
|
|
|
305
|
+
class DistributionNNMChartFilter(ClaimGroupByFilter):
|
|
306
|
+
percent = wb_filters.BooleanFilter(
|
|
307
|
+
method="fake_filter",
|
|
308
|
+
default=False,
|
|
309
|
+
help_text="True if the value are displayed in percentage of the initial total AUM",
|
|
310
|
+
label="Show percentage",
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
class Meta:
|
|
314
|
+
model = Claim
|
|
315
|
+
fields = {}
|
|
316
|
+
|
|
317
|
+
|
|
306
318
|
class CumulativeNNMChartFilter(ClaimGroupByFilter):
|
|
307
319
|
groupby_classification_group = group_by = None
|
|
308
320
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Generated by Django 5.0.10 on 2025-01-28 10:17
|
|
2
|
+
|
|
3
|
+
import django.db.models.expressions
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('wbportfolio', '0071_alter_trade_options_alter_trade_order'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AddField(
|
|
15
|
+
model_name='trade',
|
|
16
|
+
name='diff_shares',
|
|
17
|
+
field=models.GeneratedField(db_persist=True, expression=django.db.models.expressions.CombinedExpression(models.F('shares'), '-', models.F('claimed_shares')), output_field=models.DecimalField(decimal_places=4, max_digits=15)),
|
|
18
|
+
),
|
|
19
|
+
]
|
wbportfolio/models/asset.py
CHANGED
|
@@ -416,12 +416,15 @@ class AssetPosition(ImportMixin, models.Model):
|
|
|
416
416
|
net_value *= self.currency.convert(
|
|
417
417
|
self.asset_valuation_date, self.underlying_instrument.currency
|
|
418
418
|
)
|
|
419
|
-
self.underlying_instrument_price = InstrumentPrice
|
|
419
|
+
self.underlying_instrument_price = InstrumentPrice(
|
|
420
420
|
calculated=False,
|
|
421
421
|
instrument=self.underlying_instrument,
|
|
422
422
|
date=self.asset_valuation_date,
|
|
423
423
|
net_value=net_value,
|
|
424
|
+
import_source=self.import_source, # we set the import source to know where this price is coming from
|
|
424
425
|
)
|
|
426
|
+
self.underlying_instrument_price.fill_market_capitalization()
|
|
427
|
+
self.underlying_instrument_price.save()
|
|
425
428
|
else: # sometime, the asset valuation date does not correspond to a valid market date. In that case, we get the latest valid instrument price for that product
|
|
426
429
|
self.underlying_instrument_price = (
|
|
427
430
|
InstrumentPrice.objects.filter(
|
wbportfolio/models/portfolio.py
CHANGED
|
@@ -25,6 +25,7 @@ from django.db.models.signals import post_save
|
|
|
25
25
|
from django.dispatch import receiver
|
|
26
26
|
from psycopg.types.range import DateRange
|
|
27
27
|
from wbcore.contrib.currency.models import CurrencyFXRates
|
|
28
|
+
from wbcore.contrib.notifications.utils import create_notification_type
|
|
28
29
|
from wbcore.models import WBModel
|
|
29
30
|
from wbcore.utils.models import ActiveObjectManager, DeleteToDisableMixin
|
|
30
31
|
from wbfdm.contrib.metric.dispatch import compute_metrics
|
|
@@ -232,7 +233,7 @@ class Portfolio(DeleteToDisableMixin, WBModel):
|
|
|
232
233
|
verbose_name_plural = "Portfolios"
|
|
233
234
|
|
|
234
235
|
notification_types = [
|
|
235
|
-
(
|
|
236
|
+
create_notification_type(
|
|
236
237
|
"wbportfolio.portfolio.check_custodian_portfolio",
|
|
237
238
|
"Check Custodian Portfolio",
|
|
238
239
|
"Sends a notification when a portfolio does not match with its custodian portfolio",
|
|
@@ -3,6 +3,7 @@ from decimal import Decimal
|
|
|
3
3
|
|
|
4
4
|
from django.db import models
|
|
5
5
|
from wbcore.contrib.io.mixins import ImportMixin
|
|
6
|
+
from wbcore.contrib.notifications.utils import create_notification_type
|
|
6
7
|
from wbcore.models import WBModel
|
|
7
8
|
from wbportfolio.import_export.handlers.portfolio_cash_flow import (
|
|
8
9
|
DailyPortfolioCashFlowImportHandler,
|
|
@@ -132,7 +133,7 @@ class DailyPortfolioCashFlow(ImportMixin, WBModel):
|
|
|
132
133
|
]
|
|
133
134
|
permissions = [("administrate_dailyportfoliocashflow", "Can administrate Daily Portfolio CashFlow")]
|
|
134
135
|
notification_types = [
|
|
135
|
-
(
|
|
136
|
+
create_notification_type(
|
|
136
137
|
"wbportfolio.dailyportfoliocashflow.notify_rebalance",
|
|
137
138
|
"Rebalancing suggested",
|
|
138
139
|
"Sends a notification, when the system suggests to rebalance a portfolio due to being outside of the cash target parameters",
|
|
@@ -140,7 +141,7 @@ class DailyPortfolioCashFlow(ImportMixin, WBModel):
|
|
|
140
141
|
True,
|
|
141
142
|
False,
|
|
142
143
|
),
|
|
143
|
-
(
|
|
144
|
+
create_notification_type(
|
|
144
145
|
"wbportfolio.dailyportfoliocashflow.notify_swingpricing",
|
|
145
146
|
"Swing Pricing Notification",
|
|
146
147
|
"Sends a notification, when the system detects a future swing pricing event",
|
|
@@ -21,7 +21,7 @@ from django.db.models.signals import post_save
|
|
|
21
21
|
from django.dispatch import receiver
|
|
22
22
|
from django.utils.functional import cached_property
|
|
23
23
|
from django_fsm import GET_STATE, FSMField, transition
|
|
24
|
-
from ordered_model.models import OrderedModel, OrderedModelManager
|
|
24
|
+
from ordered_model.models import OrderedModel, OrderedModelManager, OrderedModelQuerySet
|
|
25
25
|
from wbcore.contrib.icons import WBIcon
|
|
26
26
|
from wbcore.enums import RequestType
|
|
27
27
|
from wbcore.metadata.configs.buttons import ActionButton
|
|
@@ -37,58 +37,65 @@ from wbportfolio.pms.typing import Trade as TradeDTO
|
|
|
37
37
|
from .transactions import ShareMixin, Transaction
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
class
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
40
|
+
class TradeQueryset(OrderedModelQuerySet):
|
|
41
|
+
def annotate_base_info(self):
|
|
42
|
+
return self.annotate(
|
|
43
|
+
last_effective_date=Subquery(
|
|
44
|
+
AssetPosition.objects.filter(
|
|
45
|
+
underlying_instrument=OuterRef("underlying_instrument"),
|
|
46
|
+
date__lt=OuterRef("transaction_date"),
|
|
47
|
+
portfolio=OuterRef("portfolio"),
|
|
48
|
+
)
|
|
49
|
+
.order_by("-date")
|
|
50
|
+
.values("date")[:1]
|
|
51
|
+
),
|
|
52
|
+
effective_weight=Coalesce(
|
|
53
|
+
Subquery(
|
|
51
54
|
AssetPosition.objects.filter(
|
|
52
55
|
underlying_instrument=OuterRef("underlying_instrument"),
|
|
53
|
-
|
|
56
|
+
date=OuterRef("last_effective_date"),
|
|
54
57
|
portfolio=OuterRef("portfolio"),
|
|
55
58
|
)
|
|
56
|
-
.
|
|
57
|
-
.
|
|
58
|
-
|
|
59
|
-
effective_weight=Coalesce(
|
|
60
|
-
Subquery(
|
|
61
|
-
AssetPosition.objects.filter(
|
|
62
|
-
underlying_instrument=OuterRef("underlying_instrument"),
|
|
63
|
-
date=OuterRef("last_effective_date"),
|
|
64
|
-
portfolio=OuterRef("portfolio"),
|
|
65
|
-
)
|
|
66
|
-
.values("portfolio")
|
|
67
|
-
.annotate(s=Sum("weighting"))
|
|
68
|
-
.values("s")[:1]
|
|
69
|
-
),
|
|
70
|
-
Decimal(0),
|
|
59
|
+
.values("portfolio")
|
|
60
|
+
.annotate(s=Sum("weighting"))
|
|
61
|
+
.values("s")[:1]
|
|
71
62
|
),
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
)
|
|
84
|
-
|
|
63
|
+
Decimal(0),
|
|
64
|
+
),
|
|
65
|
+
target_weight=F("effective_weight") + F("weighting"),
|
|
66
|
+
effective_shares=Coalesce(
|
|
67
|
+
Subquery(
|
|
68
|
+
AssetPosition.objects.filter(
|
|
69
|
+
underlying_instrument=OuterRef("underlying_instrument"),
|
|
70
|
+
date=OuterRef("last_effective_date"),
|
|
71
|
+
portfolio=OuterRef("portfolio"),
|
|
72
|
+
)
|
|
73
|
+
.values("portfolio")
|
|
74
|
+
.annotate(s=Sum("shares"))
|
|
75
|
+
.values("s")[:1]
|
|
85
76
|
),
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
)
|
|
77
|
+
Decimal(0),
|
|
78
|
+
),
|
|
79
|
+
target_shares=F("effective_weight") * F("weighting"),
|
|
89
80
|
)
|
|
90
81
|
|
|
91
82
|
|
|
83
|
+
class DefaultTradeManager(OrderedModelManager):
|
|
84
|
+
"""This manager is expect to be the trade default manager and annotate by default the effective weight (extracted
|
|
85
|
+
from the associated portfolio) and the target weight as an addition between the effective weight and the delta weight
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def __init__(self, with_annotation: bool = False, *args, **kwargs):
|
|
89
|
+
self.with_annotation = with_annotation
|
|
90
|
+
super().__init__(*args, **kwargs)
|
|
91
|
+
|
|
92
|
+
def get_queryset(self) -> TradeQueryset:
|
|
93
|
+
qs = TradeQueryset(self.model, using=self._db)
|
|
94
|
+
if self.with_annotation:
|
|
95
|
+
qs = qs.annotate_base_info()
|
|
96
|
+
return qs
|
|
97
|
+
|
|
98
|
+
|
|
92
99
|
class ValidCustomerTradeManager(DefaultTradeManager):
|
|
93
100
|
def __init__(self, without_internal_trade: bool = False):
|
|
94
101
|
self.without_internal_trade = without_internal_trade
|
|
@@ -202,7 +209,13 @@ class Trade(ShareMixin, Transaction, OrderedModel, WBModel):
|
|
|
202
209
|
help_text="The number of shares that were claimed.",
|
|
203
210
|
verbose_name="Claimed Shares",
|
|
204
211
|
)
|
|
212
|
+
diff_shares = models.GeneratedField(
|
|
213
|
+
expression=F("shares") - F("claimed_shares"),
|
|
214
|
+
output_field=models.DecimalField(max_digits=15, decimal_places=4),
|
|
215
|
+
db_persist=True,
|
|
216
|
+
)
|
|
205
217
|
objects = DefaultTradeManager()
|
|
218
|
+
annotated_objects = DefaultTradeManager(with_annotation=True)
|
|
206
219
|
valid_customer_trade_objects = ValidCustomerTradeManager()
|
|
207
220
|
valid_external_customer_trade_objects = ValidCustomerTradeManager(without_internal_trade=True)
|
|
208
221
|
|
|
@@ -436,13 +449,6 @@ class Trade(ShareMixin, Transaction, OrderedModel, WBModel):
|
|
|
436
449
|
def _target_shares(self) -> Decimal:
|
|
437
450
|
return getattr(self, "target_shares", self._effective_shares * self.weighting)
|
|
438
451
|
|
|
439
|
-
@cached_property
|
|
440
|
-
@admin.display(description="Diff Claims")
|
|
441
|
-
def _diff_shares(self) -> Decimal:
|
|
442
|
-
if hasattr(self, "diff_shares"):
|
|
443
|
-
return self.diff_shares
|
|
444
|
-
return self.shares - self.claimed_shares
|
|
445
|
-
|
|
446
452
|
order_with_respect_to = "trade_proposal"
|
|
447
453
|
|
|
448
454
|
class Meta(OrderedModel.Meta):
|
|
@@ -51,46 +51,58 @@ class TestAccountRuleModel:
|
|
|
51
51
|
account = account_factory.create(owner=entry)
|
|
52
52
|
other_account = account_factory.create()
|
|
53
53
|
claim_factory.create(
|
|
54
|
-
date=(weekday - BDay(business_days_interval +
|
|
54
|
+
date=(weekday - BDay(business_days_interval + 3)).date(),
|
|
55
55
|
account=account,
|
|
56
|
-
trade=customer_trade_factory.create(
|
|
56
|
+
trade=customer_trade_factory.create(
|
|
57
|
+
underlying_instrument=product, transaction_date=(weekday - BDay(business_days_interval + 3)).date()
|
|
58
|
+
),
|
|
57
59
|
status="APPROVED",
|
|
58
60
|
shares=100,
|
|
59
61
|
)
|
|
60
62
|
claim_factory.create(
|
|
61
|
-
date=(weekday - BDay(business_days_interval +
|
|
63
|
+
date=(weekday - BDay(business_days_interval + 2)).date(),
|
|
62
64
|
account=account,
|
|
63
|
-
trade=customer_trade_factory.create(
|
|
65
|
+
trade=customer_trade_factory.create(
|
|
66
|
+
underlying_instrument=product, transaction_date=(weekday - BDay(business_days_interval + 2)).date()
|
|
67
|
+
),
|
|
64
68
|
status="APPROVED",
|
|
65
69
|
shares=-50,
|
|
66
70
|
) # this drop should not be detected
|
|
67
71
|
|
|
68
72
|
claim_factory.create(
|
|
69
|
-
date=(weekday - BDay(business_days_interval)).date(),
|
|
73
|
+
date=(weekday - BDay(business_days_interval + 1)).date(),
|
|
70
74
|
account=account,
|
|
71
|
-
trade=customer_trade_factory.create(
|
|
75
|
+
trade=customer_trade_factory.create(
|
|
76
|
+
underlying_instrument=product, transaction_date=(weekday - BDay(business_days_interval + 1)).date()
|
|
77
|
+
),
|
|
72
78
|
status="APPROVED",
|
|
73
79
|
shares=150,
|
|
74
80
|
)
|
|
75
81
|
claim_factory.create(
|
|
76
|
-
date=weekday,
|
|
82
|
+
date=(weekday - BDay(1)).date(),
|
|
77
83
|
account=account,
|
|
78
|
-
trade=customer_trade_factory.create(
|
|
84
|
+
trade=customer_trade_factory.create(
|
|
85
|
+
underlying_instrument=product, transaction_date=(weekday - BDay(1)).date()
|
|
86
|
+
),
|
|
79
87
|
status="APPROVED",
|
|
80
88
|
shares=-50,
|
|
81
89
|
) # this drop should be detected
|
|
82
90
|
|
|
83
91
|
claim_factory.create(
|
|
84
|
-
date=(weekday - BDay(business_days_interval)).date(),
|
|
92
|
+
date=(weekday - BDay(business_days_interval + 1)).date(),
|
|
85
93
|
account=other_account,
|
|
86
|
-
trade=customer_trade_factory.create(
|
|
94
|
+
trade=customer_trade_factory.create(
|
|
95
|
+
underlying_instrument=product, transaction_date=(weekday - BDay(business_days_interval + 1)).date()
|
|
96
|
+
),
|
|
87
97
|
status="APPROVED",
|
|
88
98
|
shares=150,
|
|
89
99
|
)
|
|
90
100
|
claim_factory.create(
|
|
91
|
-
date=weekday,
|
|
101
|
+
date=(weekday - BDay(1)).date(),
|
|
92
102
|
account=other_account,
|
|
93
|
-
trade=customer_trade_factory.create(
|
|
103
|
+
trade=customer_trade_factory.create(
|
|
104
|
+
underlying_instrument=product, transaction_date=(weekday - BDay(1)).date()
|
|
105
|
+
),
|
|
94
106
|
status="APPROVED",
|
|
95
107
|
shares=-50,
|
|
96
108
|
) # this drop is valid but an another account so won't be detected
|
|
@@ -45,11 +45,7 @@ class TradeProposalRepresentationSerializer(wb_serializers.RepresentationSeriali
|
|
|
45
45
|
|
|
46
46
|
class TradeRepresentationSerializer(TransactionRepresentationSerializer):
|
|
47
47
|
_detail = wb_serializers.HyperlinkField(reverse_name="wbportfolio:trade-detail")
|
|
48
|
-
|
|
49
|
-
diff_shares = wb_serializers.SerializerMethodField()
|
|
50
|
-
|
|
51
|
-
def get_diff_shares(self, obj):
|
|
52
|
-
return obj._diff_shares
|
|
48
|
+
diff_shares = wb_serializers.DecimalField(max_digits=15, decimal_places=4)
|
|
53
49
|
|
|
54
50
|
class Meta:
|
|
55
51
|
model = Trade
|
|
@@ -241,7 +237,7 @@ class TradeTradeProposalModelSerializer(TradeModelSerializer):
|
|
|
241
237
|
percent_fields = ["effective_weight", "target_weight", "weighting"]
|
|
242
238
|
read_only_fields = (
|
|
243
239
|
"transaction_subtype",
|
|
244
|
-
"shares"
|
|
240
|
+
"shares",
|
|
245
241
|
# "underlying_instrument",
|
|
246
242
|
# "_underlying_instrument",
|
|
247
243
|
)
|
|
@@ -46,7 +46,6 @@ from wbfdm.preferences import get_default_classification_group
|
|
|
46
46
|
from wbportfolio.analysis.claims import ConsolidatedTradeSummary
|
|
47
47
|
from wbportfolio.filters import (
|
|
48
48
|
ClaimFilter,
|
|
49
|
-
ClaimGroupByFilter,
|
|
50
49
|
ConsolidatedTradeSummaryTableFilterSet,
|
|
51
50
|
CumulativeNNMChartFilter,
|
|
52
51
|
CustomerAPIFilter,
|
|
@@ -54,6 +53,7 @@ from wbportfolio.filters import (
|
|
|
54
53
|
CustomerClaimGroupByFilter,
|
|
55
54
|
NegativeTermimalAccountPerProductFilterSet,
|
|
56
55
|
ProfitAndLossPandasFilter,
|
|
56
|
+
DistributionNNMChartFilter,
|
|
57
57
|
)
|
|
58
58
|
from wbportfolio.models import Product, Trade
|
|
59
59
|
from wbportfolio.models.transactions.claim import Claim, ClaimGroupbyChoice
|
|
@@ -607,10 +607,18 @@ class ConsolidatedTradeSummaryTableView(ClaimPermissionMixin, ExportPandasAPIVie
|
|
|
607
607
|
def get_filterset_class(self, request):
|
|
608
608
|
profile = request.user.profile
|
|
609
609
|
if profile.is_internal or request.user.is_superuser:
|
|
610
|
-
return
|
|
610
|
+
return ConsolidatedTradeSummaryTableFilterSet
|
|
611
611
|
return CustomerClaimGroupByFilter
|
|
612
612
|
|
|
613
613
|
|
|
614
|
+
def _sanitize_df(df: pd.DataFrame, normalization_factor: pd.Series | None = None) -> pd.DataFrame:
|
|
615
|
+
df = df.replace({0: np.nan})
|
|
616
|
+
scalar_columns = df.columns.difference(["title", "id"])
|
|
617
|
+
if normalization_factor is not None:
|
|
618
|
+
df[scalar_columns] = df[scalar_columns].div(normalization_factor.replace({0: np.nan}), axis=0)
|
|
619
|
+
return df.dropna(axis=0, how="all", subset=scalar_columns)
|
|
620
|
+
|
|
621
|
+
|
|
614
622
|
class ConsolidatedTradeSummaryDistributionChart(ConsolidatedTradeSummaryTableView):
|
|
615
623
|
WIDGET_TYPE = WidgetType.CHART.value
|
|
616
624
|
IDENTIFIER = "wbportfolio:consolidatedtradesummarydistributionchart"
|
|
@@ -618,7 +626,13 @@ class ConsolidatedTradeSummaryDistributionChart(ConsolidatedTradeSummaryTableVie
|
|
|
618
626
|
title_config_class = ConsolidatedTradeSummaryDistributionChartTitleConfig
|
|
619
627
|
endpoint_config_class = ConsolidatedTradeSummaryDistributionChartEndpointConfig
|
|
620
628
|
button_config_class = None
|
|
621
|
-
|
|
629
|
+
|
|
630
|
+
def get_filterset_class(self, request):
|
|
631
|
+
return DistributionNNMChartFilter
|
|
632
|
+
|
|
633
|
+
@cached_property
|
|
634
|
+
def is_percent(self) -> bool:
|
|
635
|
+
return self.request.GET.get("percent", "false") == "true"
|
|
622
636
|
|
|
623
637
|
# TODO This is not really optimal. We need to change it at some point
|
|
624
638
|
def list(self, request, *args, **kwargs):
|
|
@@ -642,36 +656,46 @@ class ConsolidatedTradeSummaryDistributionChart(ConsolidatedTradeSummaryTableVie
|
|
|
642
656
|
def get_plotly(self, df):
|
|
643
657
|
fig = go.Figure()
|
|
644
658
|
# create the groupby NNM distribution histogram
|
|
645
|
-
df = df.sort_values(by="title")
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
659
|
+
df = df.sort_values(by="title").set_index("id")
|
|
660
|
+
normalization_factor = None
|
|
661
|
+
if self.is_percent:
|
|
662
|
+
normalization_factor = df["sum_aum_start"]
|
|
663
|
+
nnm_monthly_columns = dict(self.nnm_monthly_columns)
|
|
664
|
+
df = _sanitize_df(
|
|
665
|
+
df.drop(columns=df.columns.difference(["title", *nnm_monthly_columns.keys(), "sum_nnm_total"])),
|
|
666
|
+
normalization_factor=normalization_factor,
|
|
667
|
+
)
|
|
668
|
+
for key, label in nnm_monthly_columns.items():
|
|
669
|
+
if len(nnm_monthly_columns.keys()) == 1 and hasattr(self, "df_nnm_neg") and hasattr(self, "df_nnm_pos"):
|
|
670
|
+
df_nnm_pos = _sanitize_df(self.df_nnm_pos.set_index("id"), normalization_factor)
|
|
671
|
+
df_nnm_neg = _sanitize_df(self.df_nnm_neg.set_index("id"), normalization_factor)
|
|
672
|
+
if key in df_nnm_neg.columns:
|
|
649
673
|
fig.add_trace(
|
|
650
674
|
go.Histogram(
|
|
651
675
|
histfunc="sum",
|
|
652
|
-
y=
|
|
653
|
-
x=
|
|
654
|
-
name=
|
|
676
|
+
y=df_nnm_neg[key],
|
|
677
|
+
x=df_nnm_neg["title"],
|
|
678
|
+
name=label + " (Negative)",
|
|
655
679
|
marker_color="#FF6961",
|
|
656
680
|
)
|
|
657
681
|
)
|
|
658
|
-
if
|
|
682
|
+
if key in df_nnm_pos.columns:
|
|
659
683
|
fig.add_trace(
|
|
660
684
|
go.Histogram(
|
|
661
685
|
histfunc="sum",
|
|
662
|
-
y=
|
|
663
|
-
x=
|
|
664
|
-
name=
|
|
686
|
+
y=df_nnm_pos[key],
|
|
687
|
+
x=df_nnm_pos["title"],
|
|
688
|
+
name=label + " (Positive)",
|
|
665
689
|
marker_color="#77DD77",
|
|
666
690
|
)
|
|
667
691
|
)
|
|
668
|
-
if
|
|
669
|
-
figure_kwargs = {"name":
|
|
670
|
-
if len(
|
|
692
|
+
if key in df.columns:
|
|
693
|
+
figure_kwargs = {"name": label}
|
|
694
|
+
if len(nnm_monthly_columns.keys()) == 1:
|
|
671
695
|
figure_kwargs["marker_color"] = "#D3D3D3"
|
|
672
|
-
figure_kwargs["name"] =
|
|
673
|
-
fig.add_trace(go.Histogram(histfunc="sum", y=df[
|
|
674
|
-
if len(
|
|
696
|
+
figure_kwargs["name"] = label + " (Total)"
|
|
697
|
+
fig.add_trace(go.Histogram(histfunc="sum", y=df[key], x=df["title"], **figure_kwargs))
|
|
698
|
+
if len(nnm_monthly_columns.keys()) > 1:
|
|
675
699
|
fig.add_trace(
|
|
676
700
|
go.Histogram(
|
|
677
701
|
histfunc="sum",
|
|
@@ -692,6 +716,8 @@ class ConsolidatedTradeSummaryDistributionChart(ConsolidatedTradeSummaryTableVie
|
|
|
692
716
|
"x": 0.5,
|
|
693
717
|
},
|
|
694
718
|
)
|
|
719
|
+
if self.is_percent:
|
|
720
|
+
fig.update_yaxes(tickformat=".2%")
|
|
695
721
|
return fig
|
|
696
722
|
|
|
697
723
|
|
|
@@ -3,12 +3,24 @@ from decimal import Decimal
|
|
|
3
3
|
import pandas as pd
|
|
4
4
|
import plotly.graph_objects as go
|
|
5
5
|
from django.contrib.messages import warning
|
|
6
|
-
from django.db.models import
|
|
6
|
+
from django.db.models import (
|
|
7
|
+
BooleanField,
|
|
8
|
+
Case,
|
|
9
|
+
F,
|
|
10
|
+
OuterRef,
|
|
11
|
+
Subquery,
|
|
12
|
+
Sum,
|
|
13
|
+
Value,
|
|
14
|
+
When,
|
|
15
|
+
DecimalField,
|
|
16
|
+
ExpressionWrapper,
|
|
17
|
+
)
|
|
7
18
|
from django.db.models.functions import Coalesce
|
|
8
19
|
from django.shortcuts import get_object_or_404
|
|
9
20
|
from django.utils.functional import cached_property
|
|
10
21
|
from django_filters.rest_framework import DjangoFilterBackend
|
|
11
22
|
from wbcore import viewsets
|
|
23
|
+
from wbcore.contrib.currency.models import CurrencyFXRates
|
|
12
24
|
from wbcore.permissions.permissions import InternalUserPermissionMixin
|
|
13
25
|
from wbcore.utils.strings import format_number
|
|
14
26
|
from wbcrm.models import Account
|
|
@@ -55,7 +67,6 @@ from ..configs import (
|
|
|
55
67
|
TradeTradeProposalEndpointConfig,
|
|
56
68
|
)
|
|
57
69
|
from ..mixins import UserPortfolioRequestPermissionMixin
|
|
58
|
-
from .transactions import TransactionModelViewSet
|
|
59
70
|
|
|
60
71
|
|
|
61
72
|
class TradeProposalRepresentationViewSet(InternalUserPermissionMixin, viewsets.RepresentationViewSet):
|
|
@@ -93,7 +104,7 @@ class TradeRepresentationViewSet(InternalUserPermissionMixin, viewsets.Represent
|
|
|
93
104
|
)
|
|
94
105
|
|
|
95
106
|
|
|
96
|
-
class TradeModelViewSet(
|
|
107
|
+
class TradeModelViewSet(UserPortfolioRequestPermissionMixin, InternalUserPermissionMixin, viewsets.ModelViewSet):
|
|
97
108
|
IDENTIFIER = "wbportfolio:trade"
|
|
98
109
|
|
|
99
110
|
ordering_fields = (
|
|
@@ -141,7 +152,9 @@ class TradeModelViewSet(TransactionModelViewSet):
|
|
|
141
152
|
"shares": {
|
|
142
153
|
"Σ": format_number(queryset.aggregate(s=Sum("shares"))["s"] or Decimal(0)),
|
|
143
154
|
},
|
|
144
|
-
|
|
155
|
+
"total_value_usd": {
|
|
156
|
+
"Σ": format_number(queryset.aggregate(s=Sum("total_value_usd"))["s"] or Decimal(0)),
|
|
157
|
+
},
|
|
145
158
|
}
|
|
146
159
|
|
|
147
160
|
def get_queryset(self):
|
|
@@ -150,6 +163,11 @@ class TradeModelViewSet(TransactionModelViewSet):
|
|
|
150
163
|
qs = qs.filter(transaction_subtype__in=[Trade.Type.REDEMPTION, Trade.Type.SUBSCRIPTION])
|
|
151
164
|
qs = (
|
|
152
165
|
qs.annotate(
|
|
166
|
+
fx_rate=CurrencyFXRates.get_fx_rates_subquery(
|
|
167
|
+
"transaction_date", currency="currency", lookup_expr="exact"
|
|
168
|
+
), # this slow down the request. An alternative would be to store the value in the model.
|
|
169
|
+
total_value_usd=ExpressionWrapper(F("total_value"), output_field=DecimalField()),
|
|
170
|
+
total_value_gross_usd=ExpressionWrapper(F("total_value_gross"), output_field=DecimalField()),
|
|
153
171
|
approved_claimed_shares=Coalesce(
|
|
154
172
|
Subquery(
|
|
155
173
|
Claim.objects.filter(status=Claim.Status.APPROVED, trade=OuterRef("pk"))
|
|
@@ -391,5 +409,5 @@ class TradeTradeProposalModelViewSet(
|
|
|
391
409
|
|
|
392
410
|
def get_queryset(self):
|
|
393
411
|
if self.is_portfolio_manager:
|
|
394
|
-
return super().get_queryset().filter(trade_proposal=self.kwargs["trade_proposal_id"])
|
|
412
|
+
return super().get_queryset().filter(trade_proposal=self.kwargs["trade_proposal_id"]).annotate_base_info()
|
|
395
413
|
return TradeProposal.objects.none()
|
|
@@ -77,10 +77,8 @@ class TransactionModelViewSet(UserPortfolioRequestPermissionMixin, InternalUserP
|
|
|
77
77
|
fx_rate=CurrencyFXRates.get_fx_rates_subquery(
|
|
78
78
|
"transaction_date", currency="currency", lookup_expr="exact"
|
|
79
79
|
),
|
|
80
|
-
total_value_usd=ExpressionWrapper(F("total_value")
|
|
81
|
-
total_value_gross_usd=ExpressionWrapper(
|
|
82
|
-
F("total_value_gross") * F("fx_rate"), output_field=DecimalField()
|
|
83
|
-
),
|
|
80
|
+
total_value_usd=ExpressionWrapper(F("total_value"), output_field=DecimalField()),
|
|
81
|
+
total_value_gross_usd=ExpressionWrapper(F("total_value_gross"), output_field=DecimalField()),
|
|
84
82
|
transaction_underlying_type_trade=Subquery(
|
|
85
83
|
Trade.objects.filter(id=OuterRef("pk")).values("transaction_subtype")[:1]
|
|
86
84
|
),
|
|
@@ -27,7 +27,7 @@ wbportfolio/admin/transactions/fees.py,sha256=_r0nPN9d2UM0Ef4axmh0GMZW13HlOCW9Ty
|
|
|
27
27
|
wbportfolio/admin/transactions/trades.py,sha256=Di_dq6wCUy2ZwOBTCOVckzNsTUGBb4wvdcYANK5yMVI,1728
|
|
28
28
|
wbportfolio/admin/transactions/transactions.py,sha256=nsUjfZ5wDCD6vdj4aBgYo8h9ZtyZ3HC1cH7H2XBvOww,1274
|
|
29
29
|
wbportfolio/analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
|
-
wbportfolio/analysis/claims.py,sha256=
|
|
30
|
+
wbportfolio/analysis/claims.py,sha256=TKw1T1BHeJuFflUpszjsbOdO5MeOKew8yIU5f-JOCdQ,10597
|
|
31
31
|
wbportfolio/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
32
|
wbportfolio/contrib/company_portfolio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
33
|
wbportfolio/contrib/company_portfolio/admin.py,sha256=roUsuOjYDWX0OGWVBjCcW3MAcwJHJYHnqZ_pLpwakmQ,549
|
|
@@ -87,11 +87,11 @@ wbportfolio/filters/esg.py,sha256=UaJEX-oT4TNVE1CgUf7nDxzyFXEMvuSqwvM4GSuTjBg,68
|
|
|
87
87
|
wbportfolio/filters/performances.py,sha256=wIcaAfFLz31g7gBniW1c4EfRnLJ9WmiaJi7Dsdu6iOE,7324
|
|
88
88
|
wbportfolio/filters/portfolios.py,sha256=aK1bI7G7ffwWGUAg5A-JmMEBu2azJjnqBoNsAUqtxcc,818
|
|
89
89
|
wbportfolio/filters/positions.py,sha256=3JQE0CPdFTGTUBVm3VsBr86XgZIsU-u7Y_IfQcyIomg,7899
|
|
90
|
-
wbportfolio/filters/products.py,sha256=
|
|
90
|
+
wbportfolio/filters/products.py,sha256=mE1cLExkUWJ3ZYGNdUuwLPZ2wBsEBEOsMjxx_oSXlx4,6491
|
|
91
91
|
wbportfolio/filters/roles.py,sha256=-yDD_18nFL7aGGiIlO2R0SrFDV8eXD_9vnT_u9-6q-g,919
|
|
92
|
-
wbportfolio/filters/signals.py,sha256=
|
|
93
|
-
wbportfolio/filters/transactions/__init__.py,sha256=
|
|
94
|
-
wbportfolio/filters/transactions/claim.py,sha256=
|
|
92
|
+
wbportfolio/filters/signals.py,sha256=eukEjQCPOHYeMTGhq5Fm6YLoSCq71cTim-XAF6SB9cQ,3143
|
|
93
|
+
wbportfolio/filters/transactions/__init__.py,sha256=WHAwqZLRTAS46lLscGJYtKYWyEknsY5MqeSD0ue1r-o,662
|
|
94
|
+
wbportfolio/filters/transactions/claim.py,sha256=3lnS0aCFjsnt1rCNZj3JOb6cEiA18kJZTjqbvBKaWW0,16640
|
|
95
95
|
wbportfolio/filters/transactions/fees.py,sha256=9iqJuF31pf8C18OyNseSIgBZPDyWxgpBWu6S7nEJaoo,2133
|
|
96
96
|
wbportfolio/filters/transactions/mixins.py,sha256=TEV3MUsiQTeu4NdFYHMIIMonmC7CdFF80JTpWYIvfRQ,550
|
|
97
97
|
wbportfolio/filters/transactions/trades.py,sha256=wXWBKe-yv2ihpOlccRvQoyMQ6Oi_JSiIvGV6mtdqpfA,9341
|
|
@@ -241,14 +241,15 @@ wbportfolio/migrations/0068_trade_internal_trade_trade_marked_as_internal_and_mo
|
|
|
241
241
|
wbportfolio/migrations/0069_remove_portfolio_is_invested_and_more.py,sha256=yIW_OLyGjX8OdgcDXUU5OXgAm9vlsYwQm5KG4tgXPqI,2106
|
|
242
242
|
wbportfolio/migrations/0070_remove_assetposition_unique_asset_position_and_more.py,sha256=O4YNqVhCIA5o_KkY82eJchFmec-qGl-FOYgahkYwyHE,3222
|
|
243
243
|
wbportfolio/migrations/0071_alter_trade_options_alter_trade_order.py,sha256=QjAyQr1eSs2X73zL03uG_MjfcGZhSJV9YQ0UJ39FpVk,695
|
|
244
|
+
wbportfolio/migrations/0072_trade_diff_shares.py,sha256=aTKa1SbIiwmlXaFtBg-ENrSxfM_cf3RPNQBQlk2VEZ0,635
|
|
244
245
|
wbportfolio/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
245
246
|
wbportfolio/models/__init__.py,sha256=ORf7HHu7PUwIN7XTev_n6USl0r484W4NiyRLtKmnIn0,992
|
|
246
247
|
wbportfolio/models/adjustments.py,sha256=kd6ufcB8LkxQiQf_enXwGnLRYQ2afLTh5oxeCNyg4ZI,10143
|
|
247
|
-
wbportfolio/models/asset.py,sha256=
|
|
248
|
+
wbportfolio/models/asset.py,sha256=2iYTFXkQ5BAvDJe1KfLQBZO7qg3R2YoDZWMUAg7klis,36979
|
|
248
249
|
wbportfolio/models/custodians.py,sha256=owTiS2Vm5CRKzh9M_P9GOVg-s-ndQ9UvRmw3yZP7cw0,3815
|
|
249
250
|
wbportfolio/models/indexes.py,sha256=GLtT8QFOkMzHSsV5ucCgL9YgkIpjQQan1YX3YmQ5cfc,1071
|
|
250
|
-
wbportfolio/models/portfolio.py,sha256=
|
|
251
|
-
wbportfolio/models/portfolio_cash_flow.py,sha256=
|
|
251
|
+
wbportfolio/models/portfolio.py,sha256=DhDzyDnk4BzEKkf057RrxzNKPJpZcSDOMOEzvRxrNyo,47434
|
|
252
|
+
wbportfolio/models/portfolio_cash_flow.py,sha256=sh1NFCLu69Dbx71QVX0UvdBQlCl2-kBfDzhBBERnXbs,7272
|
|
252
253
|
wbportfolio/models/portfolio_cash_targets.py,sha256=WmgG-etPisZsh2yaFQpz7EkpvAudKBEzqPsO715w52U,1498
|
|
253
254
|
wbportfolio/models/portfolio_relationship.py,sha256=b6DF5NVtyzcuqIr7VqvTrjvQqeblC0KeF0EYUlZyWEY,5388
|
|
254
255
|
wbportfolio/models/portfolio_swing_pricings.py,sha256=_LqYC1VRjnnFFmVqFPRdnbgYsVxocMVpClTk2dnooig,1778
|
|
@@ -275,7 +276,7 @@ wbportfolio/models/transactions/dividends.py,sha256=BPgIC4-c_cgISlcVza-MAUeCNI0M
|
|
|
275
276
|
wbportfolio/models/transactions/expiry.py,sha256=vnNHdcC1hf2HP4rAbmoGgOfagBYKNFytqOwzOI0MlVI,144
|
|
276
277
|
wbportfolio/models/transactions/fees.py,sha256=DLCl8HFUpti2XE7C61BNKdZx6EPY8CtQYoyCSD97q2s,5711
|
|
277
278
|
wbportfolio/models/transactions/trade_proposals.py,sha256=tdWurtxxf-95-RV435OXrwOuvOXsyfmD8Jedvj6gaTA,20175
|
|
278
|
-
wbportfolio/models/transactions/trades.py,sha256=
|
|
279
|
+
wbportfolio/models/transactions/trades.py,sha256=CPup2hj7RqlSFzC1ExbZsZDHiHvcADy2nOThGRNMD0k,27058
|
|
279
280
|
wbportfolio/models/transactions/transactions.py,sha256=krVtVcBOYGMMM9CIrNc1u5k7h9Btvcre5arIbjuYoUU,7561
|
|
280
281
|
wbportfolio/pms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
281
282
|
wbportfolio/pms/typing.py,sha256=VReB9w5MlZ07VMKhmcSzJBzKJzbVZXUNE7NQkvwH6uk,6655
|
|
@@ -299,7 +300,7 @@ wbportfolio/risk_management/backends/stop_loss_portfolio.py,sha256=nEx5U6ao-qptu
|
|
|
299
300
|
wbportfolio/risk_management/backends/ucits_portfolio.py,sha256=lnQNIngc9Tsni6irYIqTP_IERS1ErLlA5Te1MYsDeRw,3365
|
|
300
301
|
wbportfolio/risk_management/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
301
302
|
wbportfolio/risk_management/tests/conftest.py,sha256=ChVocx8SDmh13ixkRmnKe_MMudQAX0495o7nG4NDlsg,440
|
|
302
|
-
wbportfolio/risk_management/tests/test_accounts.py,sha256=
|
|
303
|
+
wbportfolio/risk_management/tests/test_accounts.py,sha256=xh2vdegoOVpb9E60c4qOXqN1Kd3qOBCLvyCw9jp9qZQ,3989
|
|
303
304
|
wbportfolio/risk_management/tests/test_controversy_portfolio.py,sha256=EMOn5VLNPES0aUo-rECRcBoeltlqkt7esBZf8aMOsBQ,1365
|
|
304
305
|
wbportfolio/risk_management/tests/test_exposure_portfolio.py,sha256=PUt7ZkHKSXQ79utA-wmBjP4Onp0r8Aggosk2tgvYgVk,3511
|
|
305
306
|
wbportfolio/risk_management/tests/test_instrument_list_portfolio.py,sha256=b5xiFc3O5pZ2Rsnrr6qtWPxGOpwPU7g-WiAIOUq2B3o,2762
|
|
@@ -330,7 +331,7 @@ wbportfolio/serializers/transactions/claim.py,sha256=EWLWuf92akq6LARi_CSJZD4vZvf
|
|
|
330
331
|
wbportfolio/serializers/transactions/dividends.py,sha256=EULwKDumHBv4r2HsdEGZMZGFaye4dRUNNyXg6-wZXzc,520
|
|
331
332
|
wbportfolio/serializers/transactions/expiry.py,sha256=K3XOSbCyef-xRzOjCr4Qg_YFJ_JuuiJ9u6tDS86l0hg,477
|
|
332
333
|
wbportfolio/serializers/transactions/fees.py,sha256=Q0MFtl9fbmiGXwxmg58UTXWRaM-L7rcNknL6NKF25tE,1067
|
|
333
|
-
wbportfolio/serializers/transactions/trades.py,sha256=
|
|
334
|
+
wbportfolio/serializers/transactions/trades.py,sha256=B29WJks8PCdI3Jh4wQTnG5epMF5K4k629C9iRUb7SSQ,11591
|
|
334
335
|
wbportfolio/serializers/transactions/transactions.py,sha256=X3ME7j5gR2oaTO84wcV4VzrGYSbZ9uPhC7tTbkPVOgQ,4184
|
|
335
336
|
wbportfolio/static/wbportfolio/css/macro_review.css,sha256=FAVVO8nModxwPXcTKpcfzVxBGPZGJVK1Xn-0dkSfGyc,233
|
|
336
337
|
wbportfolio/static/wbportfolio/markdown/documentation/account_holding_reconciliation.md,sha256=MabxOvOne8s5gl6osDoow6-3ghaXLAYg9THWpvy6G5I,921
|
|
@@ -495,13 +496,13 @@ wbportfolio/viewsets/configs/titles/roles.py,sha256=0HNUY3W7EFw36GMmz5zplwloU1yS
|
|
|
495
496
|
wbportfolio/viewsets/configs/titles/trades.py,sha256=_mx27Oy2nFFEm8GyC4dnpYATpG2qnQDjaUapTxA_OPo,1985
|
|
496
497
|
wbportfolio/viewsets/configs/titles/transactions.py,sha256=y33FHEnd_S37vQpL2ZMevHfYF24DOANRCqBJ7HNZqQU,327
|
|
497
498
|
wbportfolio/viewsets/transactions/__init__.py,sha256=3diCf2o_2AtZbUgAnm61Ntt8-a4dNwwnDfBLe_Cm6Ag,1174
|
|
498
|
-
wbportfolio/viewsets/transactions/claim.py,sha256=
|
|
499
|
+
wbportfolio/viewsets/transactions/claim.py,sha256=RwCR-7JPJD9l_DT5tefm32viJ4kWcFd0dwUlKhpbzBU,38772
|
|
499
500
|
wbportfolio/viewsets/transactions/fees.py,sha256=GR7Cikz4Adv9R5KQSj6IUubxhjCrfDgrDKoIrnxo7N4,7030
|
|
500
501
|
wbportfolio/viewsets/transactions/mixins.py,sha256=i9ICaUXZfryIrbgS-bdCcoBJO-pTnnoFKvW5zK3qRKQ,635
|
|
501
502
|
wbportfolio/viewsets/transactions/trade_proposals.py,sha256=5mgAk60cINZHd2HfTu7StFOc882FGakNcIVZpNSwBCs,3528
|
|
502
|
-
wbportfolio/viewsets/transactions/trades.py,sha256=
|
|
503
|
-
wbportfolio/viewsets/transactions/transactions.py,sha256=
|
|
504
|
-
wbportfolio-1.44.
|
|
505
|
-
wbportfolio-1.44.
|
|
506
|
-
wbportfolio-1.44.
|
|
507
|
-
wbportfolio-1.44.
|
|
503
|
+
wbportfolio/viewsets/transactions/trades.py,sha256=GcVwfjU4N_BKXEjoKYnrtfey9tUj0-H3vdnEZfzTJy0,15457
|
|
504
|
+
wbportfolio/viewsets/transactions/transactions.py,sha256=HIQPGH5oDDGo1e2r3NbHXh68-yZcWhHmUBqOcwcjCRU,4449
|
|
505
|
+
wbportfolio-1.44.4.dist-info/METADATA,sha256=_M-1vC2g0H1H6iHLe1OFddwOnDSvim10Xtyz98W58e4,645
|
|
506
|
+
wbportfolio-1.44.4.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
|
|
507
|
+
wbportfolio-1.44.4.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
|
|
508
|
+
wbportfolio-1.44.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|