wbportfolio 1.52.1__py2.py3-none-any.whl → 1.52.2rc0__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/admin/__init__.py +1 -1
- wbportfolio/admin/transactions/__init__.py +0 -1
- wbportfolio/admin/transactions/dividends.py +40 -4
- wbportfolio/admin/transactions/fees.py +24 -14
- wbportfolio/admin/transactions/trades.py +34 -12
- wbportfolio/defaults/fees/default.py +7 -15
- wbportfolio/factories/__init__.py +0 -1
- wbportfolio/factories/dividends.py +8 -3
- wbportfolio/factories/fees.py +8 -4
- wbportfolio/factories/trades.py +10 -3
- wbportfolio/filters/transactions/__init__.py +1 -2
- wbportfolio/filters/transactions/fees.py +5 -10
- wbportfolio/filters/transactions/trades.py +17 -8
- wbportfolio/filters/transactions/utils.py +42 -0
- wbportfolio/import_export/handlers/dividend.py +7 -7
- wbportfolio/import_export/handlers/fees.py +11 -21
- wbportfolio/import_export/handlers/trade.py +5 -7
- wbportfolio/import_export/parsers/jpmorgan/fees.py +2 -2
- wbportfolio/import_export/parsers/leonteq/customer_trade.py +5 -5
- wbportfolio/import_export/parsers/leonteq/fees.py +11 -7
- wbportfolio/import_export/parsers/leonteq/trade.py +0 -5
- wbportfolio/import_export/parsers/natixis/d1_fees.py +2 -2
- wbportfolio/import_export/parsers/natixis/dividend.py +4 -9
- wbportfolio/import_export/parsers/natixis/fees.py +7 -9
- wbportfolio/import_export/parsers/sg_lux/customer_trade_pending_slk.py +1 -1
- wbportfolio/import_export/parsers/sg_lux/fees.py +2 -2
- wbportfolio/import_export/parsers/sg_lux/perf_fees.py +2 -2
- wbportfolio/import_export/parsers/sg_lux/utils.py +2 -2
- wbportfolio/import_export/parsers/ubs/api/fees.py +2 -2
- wbportfolio/import_export/parsers/vontobel/customer_trade.py +2 -3
- wbportfolio/import_export/parsers/vontobel/historical_customer_trade.py +0 -1
- wbportfolio/import_export/parsers/vontobel/management_fees.py +7 -7
- wbportfolio/import_export/parsers/vontobel/performance_fees.py +3 -3
- wbportfolio/jinja2/wbportfolio/sql/aum_nnm.sql +2 -2
- wbportfolio/migrations/0059_fees_unique_fees.py +1 -1
- wbportfolio/migrations/0077_remove_transaction_currency_and_more.py +622 -0
- wbportfolio/models/mixins/liquidity_stress_test.py +3 -3
- wbportfolio/models/transactions/__init__.py +0 -2
- wbportfolio/models/transactions/claim.py +1 -1
- wbportfolio/models/transactions/dividends.py +41 -5
- wbportfolio/models/transactions/fees.py +55 -22
- wbportfolio/models/transactions/trade_proposals.py +25 -3
- wbportfolio/models/transactions/trades.py +111 -50
- wbportfolio/models/transactions/transactions.py +60 -156
- wbportfolio/serializers/signals.py +15 -10
- wbportfolio/serializers/transactions/__init__.py +0 -5
- wbportfolio/serializers/transactions/dividends.py +37 -9
- wbportfolio/serializers/transactions/fees.py +39 -10
- wbportfolio/serializers/transactions/trades.py +56 -16
- wbportfolio/tasks.py +2 -2
- wbportfolio/tests/conftest.py +2 -8
- wbportfolio/tests/models/test_imports.py +2 -7
- wbportfolio/tests/models/transactions/test_fees.py +7 -13
- wbportfolio/tests/models/transactions/test_trade_proposals.py +4 -2
- wbportfolio/urls.py +3 -6
- wbportfolio/viewsets/configs/buttons/mixins.py +2 -2
- wbportfolio/viewsets/configs/display/__init__.py +2 -3
- wbportfolio/viewsets/configs/display/fees.py +3 -3
- wbportfolio/viewsets/configs/endpoints/__init__.py +3 -4
- wbportfolio/viewsets/configs/endpoints/fees.py +2 -2
- wbportfolio/viewsets/configs/menu/__init__.py +0 -1
- wbportfolio/viewsets/configs/titles/__init__.py +2 -3
- wbportfolio/viewsets/configs/titles/fees.py +4 -8
- wbportfolio/viewsets/mixins.py +5 -1
- wbportfolio/viewsets/products.py +6 -6
- wbportfolio/viewsets/transactions/__init__.py +2 -7
- wbportfolio/viewsets/transactions/fees.py +22 -22
- {wbportfolio-1.52.1.dist-info → wbportfolio-1.52.2rc0.dist-info}/METADATA +1 -1
- {wbportfolio-1.52.1.dist-info → wbportfolio-1.52.2rc0.dist-info}/RECORD +71 -80
- wbportfolio/admin/transactions/transactions.py +0 -38
- wbportfolio/factories/transactions.py +0 -22
- wbportfolio/filters/transactions/transactions.py +0 -99
- wbportfolio/models/transactions/expiry.py +0 -7
- wbportfolio/serializers/transactions/expiry.py +0 -18
- wbportfolio/serializers/transactions/transactions.py +0 -85
- wbportfolio/viewsets/configs/display/transactions.py +0 -55
- wbportfolio/viewsets/configs/endpoints/transactions.py +0 -14
- wbportfolio/viewsets/configs/menu/transactions.py +0 -9
- wbportfolio/viewsets/configs/titles/transactions.py +0 -9
- wbportfolio/viewsets/transactions/transactions.py +0 -122
- {wbportfolio-1.52.1.dist-info → wbportfolio-1.52.2rc0.dist-info}/WHEEL +0 -0
- {wbportfolio-1.52.1.dist-info → wbportfolio-1.52.2rc0.dist-info}/licenses/LICENSE +0 -0
wbportfolio/admin/__init__.py
CHANGED
|
@@ -8,5 +8,5 @@ from .product_groups import ProductGroupAdmin
|
|
|
8
8
|
from .portfolio import PortfolioModelAdmin
|
|
9
9
|
from .registers import RegisterModelAdmin
|
|
10
10
|
from .roles import PortfolioRoleAdmin
|
|
11
|
-
from .transactions import DividendAdmin, FeesAdmin, TradeAdmin
|
|
11
|
+
from .transactions import DividendAdmin, FeesAdmin, TradeAdmin
|
|
12
12
|
from .reconciliations import AccountReconciliationAdmin
|
|
@@ -1,15 +1,51 @@
|
|
|
1
1
|
from django.contrib import admin
|
|
2
2
|
|
|
3
|
-
from wbportfolio.admin.transactions import TransactionModelAdmin
|
|
4
3
|
from wbportfolio.models import DividendTransaction
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
@admin.register(DividendTransaction)
|
|
8
|
-
class DividendAdmin(
|
|
7
|
+
class DividendAdmin(admin.ModelAdmin):
|
|
8
|
+
search_fields = ["portfolio__name", "underlying_instrument__computed_str"]
|
|
9
|
+
list_filter = ("distribution_method", "portfolio")
|
|
10
|
+
list_display = (
|
|
11
|
+
"distribution_method",
|
|
12
|
+
"portfolio",
|
|
13
|
+
"underlying_instrument",
|
|
14
|
+
"value_date",
|
|
15
|
+
"currency",
|
|
16
|
+
"currency_fx_rate",
|
|
17
|
+
"total_value",
|
|
18
|
+
"total_value_fx_portfolio",
|
|
19
|
+
)
|
|
9
20
|
fieldsets = (
|
|
10
|
-
("Transactions Information", TransactionModelAdmin.fieldsets[0][1]),
|
|
11
21
|
(
|
|
12
22
|
"Dividend Information",
|
|
13
|
-
{
|
|
23
|
+
{
|
|
24
|
+
"fields": (
|
|
25
|
+
(
|
|
26
|
+
"portfolio",
|
|
27
|
+
"underlying_instrument",
|
|
28
|
+
"import_source",
|
|
29
|
+
),
|
|
30
|
+
("ex_date", "record_date", "value_date"),
|
|
31
|
+
("currency", "currency_fx_rate"),
|
|
32
|
+
("price", "shares", "retrocession"),
|
|
33
|
+
("total_value", "total_value_gross"),
|
|
34
|
+
("total_value_fx_portfolio", "total_value_gross_fx_portfolio"),
|
|
35
|
+
("created", "updated"),
|
|
36
|
+
("comment",),
|
|
37
|
+
)
|
|
38
|
+
},
|
|
14
39
|
),
|
|
15
40
|
)
|
|
41
|
+
readonly_fields = [
|
|
42
|
+
"total_value",
|
|
43
|
+
"total_value_gross",
|
|
44
|
+
"total_value_fx_portfolio",
|
|
45
|
+
"total_value_gross_fx_portfolio",
|
|
46
|
+
"created",
|
|
47
|
+
"updated",
|
|
48
|
+
]
|
|
49
|
+
autocomplete_fields = ["portfolio", "underlying_instrument", "currency"]
|
|
50
|
+
ordering = ("-value_date",)
|
|
51
|
+
raw_id_fields = ["import_source"]
|
|
@@ -1,27 +1,36 @@
|
|
|
1
1
|
from django.contrib import admin
|
|
2
|
-
from django.db.models import Prefetch
|
|
3
2
|
|
|
4
|
-
from wbportfolio.
|
|
5
|
-
from wbportfolio.models import Fees, Portfolio
|
|
3
|
+
from wbportfolio.models import Fees
|
|
6
4
|
from wbportfolio.models.transactions.fees import FeeCalculation
|
|
7
5
|
|
|
8
6
|
|
|
9
7
|
@admin.register(Fees)
|
|
10
|
-
class FeesAdmin(
|
|
11
|
-
list_filter = [
|
|
12
|
-
list_display = [
|
|
8
|
+
class FeesAdmin(admin.ModelAdmin):
|
|
9
|
+
list_filter = ["product"]
|
|
10
|
+
list_display = [
|
|
11
|
+
"transaction_subtype",
|
|
12
|
+
"fee_date",
|
|
13
|
+
"product",
|
|
14
|
+
"currency",
|
|
15
|
+
"total_value",
|
|
16
|
+
]
|
|
13
17
|
fieldsets = (
|
|
14
|
-
(
|
|
15
|
-
|
|
18
|
+
(
|
|
19
|
+
"Fees Information",
|
|
20
|
+
{
|
|
21
|
+
"fields": (
|
|
22
|
+
("transaction_subtype", "calculated", "fee_date", "product"),
|
|
23
|
+
("currency", "currency_fx_rate"),
|
|
24
|
+
("total_value", "total_value_fx_portfolio"),
|
|
25
|
+
("created", "updated"),
|
|
26
|
+
)
|
|
27
|
+
},
|
|
28
|
+
),
|
|
16
29
|
)
|
|
17
|
-
autocomplete_fields = [
|
|
30
|
+
autocomplete_fields = ["currency", "product"]
|
|
18
31
|
|
|
19
32
|
def get_queryset(self, request):
|
|
20
|
-
return (
|
|
21
|
-
super()
|
|
22
|
-
.get_queryset(request)
|
|
23
|
-
.prefetch_related(Prefetch("portfolio", queryset=Portfolio.objects.all().only("id", "name")))
|
|
24
|
-
)
|
|
33
|
+
return super().get_queryset(request).select_related("product", "currency")
|
|
25
34
|
|
|
26
35
|
def calculate(self, request, queryset):
|
|
27
36
|
for fees in queryset:
|
|
@@ -29,6 +38,7 @@ class FeesAdmin(TransactionModelAdmin):
|
|
|
29
38
|
|
|
30
39
|
calculate.short_description = "Recalculate selected Fees"
|
|
31
40
|
actions = [calculate]
|
|
41
|
+
readonly_fields = ["total_value_fx_portfolio", "created", "updated"]
|
|
32
42
|
|
|
33
43
|
|
|
34
44
|
@admin.register(FeeCalculation)
|
|
@@ -1,45 +1,67 @@
|
|
|
1
1
|
from django.contrib import admin
|
|
2
2
|
|
|
3
|
-
from wbportfolio.admin.transactions import TransactionModelAdmin
|
|
4
3
|
from wbportfolio.models import Trade, TradeProposal
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
@admin.register(Trade)
|
|
8
|
-
class TradeAdmin(
|
|
9
|
-
search_fields = [
|
|
10
|
-
list_filter = (
|
|
7
|
+
class TradeAdmin(admin.ModelAdmin):
|
|
8
|
+
search_fields = ["portfolio__name", "underlying_instrument__computed_str", "bank"]
|
|
9
|
+
list_filter = ("portfolio", "pending")
|
|
11
10
|
list_display = (
|
|
12
|
-
"transaction_subtype",
|
|
13
11
|
"status",
|
|
12
|
+
"transaction_subtype",
|
|
13
|
+
"transaction_date",
|
|
14
|
+
"underlying_instrument",
|
|
15
|
+
"portfolio",
|
|
14
16
|
"shares",
|
|
15
17
|
"price",
|
|
18
|
+
"total_value",
|
|
16
19
|
"pending",
|
|
17
20
|
"marked_for_deletion",
|
|
18
21
|
"exclude_from_history",
|
|
19
|
-
|
|
22
|
+
"import_source",
|
|
20
23
|
)
|
|
24
|
+
|
|
21
25
|
readonly_fields = [
|
|
22
26
|
"_effective_weight",
|
|
23
27
|
"_target_weight",
|
|
24
28
|
"_effective_shares",
|
|
25
29
|
"_target_shares",
|
|
30
|
+
"total_value",
|
|
31
|
+
"total_value_gross",
|
|
32
|
+
"total_value_fx_portfolio",
|
|
33
|
+
"total_value_gross_fx_portfolio",
|
|
34
|
+
"created",
|
|
35
|
+
"updated",
|
|
26
36
|
]
|
|
27
37
|
fieldsets = (
|
|
28
|
-
("Transaction Information", TransactionModelAdmin.fieldsets[0][1]),
|
|
29
38
|
(
|
|
30
|
-
"
|
|
39
|
+
"Transaction Information",
|
|
31
40
|
{
|
|
32
41
|
"fields": (
|
|
33
|
-
("transaction_subtype", "status"
|
|
34
|
-
("
|
|
35
|
-
(
|
|
42
|
+
("transaction_subtype", "status"),
|
|
43
|
+
("pending", "marked_for_deletion", "exclude_from_history"),
|
|
44
|
+
(
|
|
45
|
+
"portfolio",
|
|
46
|
+
"underlying_instrument",
|
|
47
|
+
"import_source",
|
|
48
|
+
),
|
|
49
|
+
("transaction_date", "book_date", "value_date"),
|
|
50
|
+
("price", "currency", "currency_fx_rate"),
|
|
51
|
+
("total_value", "total_value_gross"),
|
|
52
|
+
("total_value_fx_portfolio", "total_value_gross_fx_portfolio"),
|
|
36
53
|
("_effective_weight", "_target_weight", "weighting"),
|
|
37
54
|
("_effective_shares", "_target_shares", "shares"),
|
|
55
|
+
("register", "custodian", "bank", "external_id", "external_id_alternative"),
|
|
56
|
+
("created", "updated"),
|
|
57
|
+
("comment",),
|
|
38
58
|
)
|
|
39
59
|
},
|
|
40
60
|
),
|
|
41
61
|
)
|
|
42
|
-
autocomplete_fields = [
|
|
62
|
+
autocomplete_fields = ["portfolio", "underlying_instrument", "currency", "register", "custodian"]
|
|
63
|
+
ordering = ("-transaction_date",)
|
|
64
|
+
raw_id_fields = ["import_source", "underlying_instrument", "portfolio", "register", "custodian"]
|
|
43
65
|
|
|
44
66
|
|
|
45
67
|
@admin.register(TradeProposal)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from wbfdm.models import
|
|
1
|
+
from wbfdm.models import InstrumentPrice
|
|
2
2
|
|
|
3
3
|
from wbportfolio.models import FeeProductPercentage, Fees, Product
|
|
4
4
|
|
|
@@ -8,7 +8,6 @@ def fees_calculation(price_id):
|
|
|
8
8
|
if price.calculated:
|
|
9
9
|
raise ValueError("Cannot compute fees on calculated price")
|
|
10
10
|
currency = price.instrument.currency
|
|
11
|
-
underlying_instrument = Cash.objects.filter(currency=currency).first()
|
|
12
11
|
product = Product.objects.get(id=price.instrument.id)
|
|
13
12
|
portfolio = product.portfolio
|
|
14
13
|
previous_price = price.previous_price
|
|
@@ -54,40 +53,33 @@ def fees_calculation(price_id):
|
|
|
54
53
|
performance_fees_gross = product_gross_performance_fees * multiplicator
|
|
55
54
|
base_fields = [
|
|
56
55
|
"total_value",
|
|
57
|
-
"total_value_fx_portfolio",
|
|
58
56
|
"total_value_gross",
|
|
59
|
-
"total_value_gross_fx_portfolio",
|
|
60
57
|
]
|
|
61
58
|
yield {
|
|
62
59
|
"portfolio": portfolio,
|
|
63
|
-
"
|
|
64
|
-
"
|
|
60
|
+
"product": product,
|
|
61
|
+
"fee_date": price.date,
|
|
65
62
|
"transaction_subtype": Fees.Type.MANAGEMENT,
|
|
66
|
-
"underlying_instrument": underlying_instrument,
|
|
67
63
|
"currency": currency,
|
|
68
64
|
"calculated": True,
|
|
69
65
|
**{f: management_fees for f in base_fields},
|
|
70
66
|
}
|
|
71
67
|
yield {
|
|
72
68
|
"portfolio": portfolio,
|
|
73
|
-
"
|
|
74
|
-
"
|
|
69
|
+
"product": product,
|
|
70
|
+
"fee_date": price.date,
|
|
75
71
|
"transaction_subtype": Fees.Type.ISSUER,
|
|
76
|
-
"underlying_instrument": underlying_instrument,
|
|
77
72
|
"currency": currency,
|
|
78
73
|
"calculated": True,
|
|
79
74
|
**{f: bank_fees for f in base_fields},
|
|
80
75
|
}
|
|
81
76
|
yield {
|
|
82
77
|
"portfolio": portfolio,
|
|
83
|
-
"
|
|
84
|
-
"
|
|
78
|
+
"product": product,
|
|
79
|
+
"fee_date": price.date,
|
|
85
80
|
"transaction_subtype": Fees.Type.PERFORMANCE,
|
|
86
|
-
"underlying_instrument": underlying_instrument,
|
|
87
81
|
"currency": currency,
|
|
88
82
|
"calculated": True,
|
|
89
83
|
"total_value": performance_fees_net,
|
|
90
|
-
"total_value_fx_portfolio": performance_fees_net,
|
|
91
84
|
"total_value_gross": performance_fees_gross,
|
|
92
|
-
"total_value_gross_fx_portfolio": performance_fees_gross,
|
|
93
85
|
}
|
|
@@ -22,6 +22,5 @@ from .products import IndexProductFactory, ProductFactory, WhiteLabelProductFact
|
|
|
22
22
|
from .reconciliations import AccountReconciliationFactory, AccountReconciliationLineFactory
|
|
23
23
|
from .roles import ManagerPortfolioRoleFactory, ProductPortfolioRoleFactory
|
|
24
24
|
from .trades import CustomerTradeFactory, TradeFactory, TradeProposalFactory
|
|
25
|
-
from .transactions import TransactionFactory
|
|
26
25
|
from .indexes import IndexFactory
|
|
27
26
|
from .rebalancing import (RebalancingModelFactory, RebalancerFactory)
|
|
@@ -4,13 +4,18 @@ import factory
|
|
|
4
4
|
|
|
5
5
|
from wbportfolio.models import DividendTransaction
|
|
6
6
|
|
|
7
|
-
from .transactions import TransactionFactory
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
class DividendTransactionsFactory(TransactionFactory):
|
|
8
|
+
class DividendTransactionsFactory(factory.django.DjangoModelFactory):
|
|
11
9
|
class Meta:
|
|
12
10
|
model = DividendTransaction
|
|
13
11
|
|
|
12
|
+
currency_fx_rate = 1.0
|
|
13
|
+
portfolio = factory.SubFactory("wbportfolio.factories.PortfolioFactory")
|
|
14
|
+
underlying_instrument = factory.SubFactory("wbfdm.factories.InstrumentFactory")
|
|
15
|
+
currency = factory.SubFactory("wbcore.contrib.currency.factories.CurrencyFactory")
|
|
16
|
+
value_date = factory.Faker("date_object")
|
|
17
|
+
ex_date = factory.Faker("date_object")
|
|
18
|
+
record_date = factory.LazyAttribute(lambda o: o.ex_date)
|
|
14
19
|
retrocession = 1.0
|
|
15
20
|
shares = factory.LazyAttribute(lambda o: random.randint(10, 10000))
|
|
16
21
|
price = factory.LazyAttribute(lambda o: random.randint(10, 10000))
|
wbportfolio/factories/fees.py
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
1
3
|
import factory
|
|
2
4
|
from faker import Faker
|
|
3
5
|
|
|
4
6
|
from wbportfolio.models import Fees
|
|
5
7
|
|
|
6
|
-
from .transactions import TransactionFactory
|
|
7
|
-
|
|
8
8
|
faker = Faker()
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class FeesFactory(
|
|
11
|
+
class FeesFactory(factory.django.DjangoModelFactory):
|
|
12
12
|
class Meta:
|
|
13
13
|
model = Fees
|
|
14
14
|
|
|
15
|
+
currency_fx_rate = 1.0
|
|
16
|
+
fee_date = factory.Faker("date_object")
|
|
17
|
+
total_value = factory.LazyAttribute(lambda o: random.randint(1, 1000))
|
|
15
18
|
transaction_subtype = factory.Faker("random_element", elements=[x[0] for x in Fees.Type.choices])
|
|
16
|
-
|
|
19
|
+
product = factory.SubFactory("wbportfolio.factories.products.ProductFactory")
|
|
20
|
+
currency = factory.SubFactory("wbcore.contrib.currency.factories.CurrencyFactory")
|
wbportfolio/factories/trades.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import random
|
|
2
|
+
from datetime import timedelta
|
|
3
|
+
from decimal import Decimal
|
|
2
4
|
|
|
3
5
|
import factory
|
|
4
6
|
from faker import Faker
|
|
@@ -6,15 +8,20 @@ from pandas._libs.tslibs.offsets import BDay
|
|
|
6
8
|
|
|
7
9
|
from wbportfolio.models import Trade, TradeProposal
|
|
8
10
|
|
|
9
|
-
from .transactions import TransactionFactory
|
|
10
|
-
|
|
11
11
|
fake = Faker()
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
class TradeFactory(
|
|
14
|
+
class TradeFactory(factory.django.DjangoModelFactory):
|
|
15
15
|
class Meta:
|
|
16
16
|
model = Trade
|
|
17
17
|
|
|
18
|
+
currency_fx_rate = Decimal(1.0)
|
|
19
|
+
fees = Decimal(0.0)
|
|
20
|
+
portfolio = factory.SubFactory("wbportfolio.factories.PortfolioFactory")
|
|
21
|
+
underlying_instrument = factory.SubFactory("wbfdm.factories.InstrumentFactory")
|
|
22
|
+
currency = factory.SubFactory("wbcore.contrib.currency.factories.CurrencyFactory")
|
|
23
|
+
transaction_date = factory.Faker("date_object")
|
|
24
|
+
value_date = factory.LazyAttribute(lambda o: o.transaction_date + timedelta(days=1))
|
|
18
25
|
bank = factory.Faker("company")
|
|
19
26
|
marked_for_deletion = False
|
|
20
27
|
shares = factory.Faker("pydecimal", min_value=10, max_value=1000, right_digits=4)
|
|
@@ -10,7 +10,7 @@ from .claim import (
|
|
|
10
10
|
NegativeTermimalAccountPerProductFilterSet,
|
|
11
11
|
ProfitAndLossPandasFilter,
|
|
12
12
|
)
|
|
13
|
-
from .fees import FeesAggregatedFilter, FeesFilter,
|
|
13
|
+
from .fees import FeesAggregatedFilter, FeesFilter, FeesProductFilterSet
|
|
14
14
|
from .trades import (
|
|
15
15
|
SubscriptionRedemptionFilterSet,
|
|
16
16
|
SubscriptionRedemptionPortfolioFilterSet,
|
|
@@ -18,4 +18,3 @@ from .trades import (
|
|
|
18
18
|
TradeInstrumentFilterSet,
|
|
19
19
|
TradePortfolioFilter,
|
|
20
20
|
)
|
|
21
|
-
from .transactions import TransactionFilterSet, TransactionPortfolioFilterSet
|
|
@@ -4,10 +4,10 @@ from wbcore.pandas.filterset import PandasFilterSetMixin
|
|
|
4
4
|
|
|
5
5
|
from wbportfolio.models import Fees
|
|
6
6
|
|
|
7
|
-
from .
|
|
7
|
+
from .utils import get_transaction_default_date_range
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class FeesFilter(
|
|
10
|
+
class FeesFilter(wb_filters.FilterSet):
|
|
11
11
|
"""FilterSet for Fees
|
|
12
12
|
|
|
13
13
|
Currently filters:
|
|
@@ -21,23 +21,20 @@ class FeesFilter(TransactionFilterSet):
|
|
|
21
21
|
initial=get_transaction_default_date_range,
|
|
22
22
|
required=True,
|
|
23
23
|
)
|
|
24
|
-
total_value_usd__gte = total_value_usd__lte = transaction_underlying_type = transaction_date = None
|
|
25
24
|
|
|
26
25
|
class Meta:
|
|
27
26
|
model = Fees
|
|
28
27
|
fields = {
|
|
29
28
|
"transaction_subtype": ["exact"],
|
|
30
29
|
"currency_fx_rate": ["gte", "exact", "lte"],
|
|
31
|
-
"
|
|
30
|
+
"product": ["exact"],
|
|
32
31
|
"currency": ["exact"],
|
|
33
32
|
"total_value": ["gte", "exact", "lte"],
|
|
34
33
|
"total_value_fx_portfolio": ["gte", "exact", "lte"],
|
|
35
|
-
"total_value_gross": ["gte", "exact", "lte"],
|
|
36
|
-
"total_value_gross_fx_portfolio": ["gte", "exact", "lte"],
|
|
37
34
|
}
|
|
38
35
|
|
|
39
36
|
|
|
40
|
-
class
|
|
37
|
+
class FeesProductFilterSet(FeesFilter):
|
|
41
38
|
portfolio = None
|
|
42
39
|
|
|
43
40
|
class Meta:
|
|
@@ -49,13 +46,11 @@ class FeesPortfolioFilterSet(FeesFilter):
|
|
|
49
46
|
"currency": ["exact"],
|
|
50
47
|
"total_value": ["gte", "exact", "lte"],
|
|
51
48
|
"total_value_fx_portfolio": ["gte", "exact", "lte"],
|
|
52
|
-
"total_value_gross": ["gte", "exact", "lte"],
|
|
53
|
-
"total_value_gross_fx_portfolio": ["gte", "exact", "lte"],
|
|
54
49
|
}
|
|
55
50
|
|
|
56
51
|
|
|
57
52
|
class FeesAggregatedFilter(PandasFilterSetMixin, wb_filters.FilterSet):
|
|
58
|
-
|
|
53
|
+
fee_date = wb_filters.DateRangeFilter(
|
|
59
54
|
label="Date Range",
|
|
60
55
|
method=wb_filters.DateRangeFilter.base_date_range_filter_method,
|
|
61
56
|
required=True,
|
|
@@ -5,14 +5,28 @@ from wbcore import filters as wb_filters
|
|
|
5
5
|
from wbcrm.models.accounts import Account
|
|
6
6
|
from wbfdm.models import Classification, Instrument
|
|
7
7
|
|
|
8
|
-
from wbportfolio.models import Product, Trade
|
|
8
|
+
from wbportfolio.models import Portfolio, Product, Trade
|
|
9
9
|
from wbportfolio.models.transactions.claim import Claim
|
|
10
10
|
|
|
11
11
|
from .mixins import OppositeSharesFieldMethodMixin
|
|
12
|
-
from .
|
|
12
|
+
from .utils import get_transaction_default_date_range
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
class TradeFilter(OppositeSharesFieldMethodMixin,
|
|
15
|
+
class TradeFilter(OppositeSharesFieldMethodMixin, wb_filters.FilterSet):
|
|
16
|
+
transaction_date = wb_filters.DateRangeFilter(
|
|
17
|
+
method=wb_filters.DateRangeFilter.base_date_range_filter_method,
|
|
18
|
+
label="Date Range",
|
|
19
|
+
initial=get_transaction_default_date_range,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
portfolio = wb_filters.ModelChoiceFilter(
|
|
23
|
+
label="Portfolio",
|
|
24
|
+
queryset=Portfolio.objects.all(),
|
|
25
|
+
endpoint=Portfolio.get_representation_endpoint(),
|
|
26
|
+
value_key=Portfolio.get_representation_value_key(),
|
|
27
|
+
label_key=Portfolio.get_representation_label_key(),
|
|
28
|
+
)
|
|
29
|
+
|
|
16
30
|
# we have to redefine the mixin fields because django_filters does not allow class extension with mixin
|
|
17
31
|
opposite_shares = wb_filters.NumberFilter(
|
|
18
32
|
field_name="shares",
|
|
@@ -28,10 +42,6 @@ class TradeFilter(OppositeSharesFieldMethodMixin, TransactionFilterSet):
|
|
|
28
42
|
lookup_icon="≈±",
|
|
29
43
|
lookup_label="Opposite Approximate (+- 10%)",
|
|
30
44
|
)
|
|
31
|
-
transaction_date = wb_filters.DateRangeFilter(
|
|
32
|
-
method=wb_filters.DateRangeFilter.base_date_range_filter_method,
|
|
33
|
-
label="Date Range",
|
|
34
|
-
)
|
|
35
45
|
underlying_instrument = wb_filters.ModelChoiceFilter(
|
|
36
46
|
label="Instrument",
|
|
37
47
|
queryset=Instrument.objects.all(),
|
|
@@ -69,7 +79,6 @@ class TradeFilter(OppositeSharesFieldMethodMixin, TransactionFilterSet):
|
|
|
69
79
|
# value_key=Register.get_representation_value_key(),
|
|
70
80
|
# label_key = Register.get_representation_label_key(),
|
|
71
81
|
# )
|
|
72
|
-
total_value_usd__gte = total_value_usd__lte = transaction_underlying_type = None
|
|
73
82
|
marked_for_deletion = wb_filters.BooleanFilter(
|
|
74
83
|
label="Marked For Deletion", initial=False, field_name="marked_for_deletion", lookup_expr="exact"
|
|
75
84
|
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from datetime import date, timedelta
|
|
2
|
+
|
|
3
|
+
from psycopg.types.range import DateRange
|
|
4
|
+
|
|
5
|
+
from wbportfolio.models import Fees, Trade
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_transaction_gte_default(field, request, view):
|
|
9
|
+
filter_date = date.today() - timedelta(days=90)
|
|
10
|
+
qs = Trade.objects.none()
|
|
11
|
+
if "instrument_id" in view.kwargs:
|
|
12
|
+
qs = Trade.objects.filter(underlying_instrument__id=view.kwargs["instrument_id"])
|
|
13
|
+
elif "portfolio_id" in view.kwargs:
|
|
14
|
+
qs = Trade.objects.filter(portfolio__id=view.kwargs["portfolio_id"])
|
|
15
|
+
if qs.exists():
|
|
16
|
+
filter_date = qs.earliest("transaction_date").transaction_date
|
|
17
|
+
return filter_date
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_transaction_underlying_type_choices(*args):
|
|
21
|
+
models = [Fees, Trade]
|
|
22
|
+
choices = []
|
|
23
|
+
for model in models:
|
|
24
|
+
for choice in model.Type.choices:
|
|
25
|
+
choices.append(choice)
|
|
26
|
+
return choices
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_transaction_lte_default(field, request, view):
|
|
30
|
+
filter_date = date.today() + timedelta(days=7)
|
|
31
|
+
qs = Trade.objects.none()
|
|
32
|
+
if "instrument_id" in view.kwargs:
|
|
33
|
+
qs = Trade.objects.filter(underlying_instrument__id=view.kwargs["instrument_id"])
|
|
34
|
+
elif "portfolio_id" in view.kwargs:
|
|
35
|
+
qs = Trade.objects.filter(portfolio__id=view.kwargs["portfolio_id"])
|
|
36
|
+
if qs.exists():
|
|
37
|
+
filter_date = qs.latest("transaction_date").transaction_date
|
|
38
|
+
return filter_date
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_transaction_default_date_range(*args, **kwargs):
|
|
42
|
+
return DateRange(get_transaction_gte_default(*args, **kwargs), get_transaction_lte_default(*args, **kwargs))
|
|
@@ -19,7 +19,7 @@ class DividendImportHandler(ImportExportHandler):
|
|
|
19
19
|
self.currency_handler = CurrencyImportHandler(self.import_source)
|
|
20
20
|
|
|
21
21
|
def _deserialize(self, data):
|
|
22
|
-
data["
|
|
22
|
+
data["ex_date"] = datetime.strptime(data["ex_date"], "%Y-%m-%d").date()
|
|
23
23
|
data["value_date"] = datetime.strptime(data["value_date"], "%Y-%m-%d").date()
|
|
24
24
|
from wbportfolio.models import Portfolio
|
|
25
25
|
|
|
@@ -42,12 +42,12 @@ class DividendImportHandler(ImportExportHandler):
|
|
|
42
42
|
|
|
43
43
|
def _get_instance(self, data, history=None, **kwargs):
|
|
44
44
|
self.import_source.log += "\nGet DividendTransaction Instance."
|
|
45
|
-
self.import_source.log += f"\nParameter: Portfolio={data['portfolio']} Underlying={data['underlying_instrument']} Date={data['
|
|
45
|
+
self.import_source.log += f"\nParameter: Portfolio={data['portfolio']} Underlying={data['underlying_instrument']} Date={data['ex_date']}"
|
|
46
46
|
dividends = history if history is not None else self.model.objects
|
|
47
47
|
|
|
48
48
|
dividends = dividends.filter(
|
|
49
49
|
portfolio=data["portfolio"],
|
|
50
|
-
|
|
50
|
+
ex_date=data["ex_date"],
|
|
51
51
|
value_date=data["value_date"],
|
|
52
52
|
underlying_instrument=data["underlying_instrument"],
|
|
53
53
|
price_gross=data["price_gross"],
|
|
@@ -63,10 +63,10 @@ class DividendImportHandler(ImportExportHandler):
|
|
|
63
63
|
def _get_history(self: models.Model, history: Dict[str, Any]) -> models.QuerySet:
|
|
64
64
|
from wbportfolio.models import Product
|
|
65
65
|
|
|
66
|
-
val_date = datetime.strptime(history["
|
|
66
|
+
val_date = datetime.strptime(history["value_date"], "%Y-%m-%d")
|
|
67
67
|
try:
|
|
68
68
|
product = Product.objects.get(**history.get("product", {}))
|
|
69
|
-
dividends = self.model.objects.filter(
|
|
69
|
+
dividends = self.model.objects.filter(ex_date__lte=val_date, portfolio=product.primary_portfolio)
|
|
70
70
|
if underlying_instrument_data := history.get("underlying_instrument"):
|
|
71
71
|
if isinstance(underlying_instrument_data, dict):
|
|
72
72
|
dividends = dividends.filter(
|
|
@@ -83,8 +83,8 @@ class DividendImportHandler(ImportExportHandler):
|
|
|
83
83
|
self.import_source.log += (
|
|
84
84
|
"It was a historical import and the following DividendTransaction have to be deleted:"
|
|
85
85
|
)
|
|
86
|
-
for dividend in history.order_by("
|
|
86
|
+
for dividend in history.order_by("value_date"):
|
|
87
87
|
self.import_source.log += (
|
|
88
|
-
f"\n{dividend.
|
|
88
|
+
f"\n{dividend.value_date:%d.%m.%Y}: {dividend.shares} {dividend.price} ==> Deleted"
|
|
89
89
|
)
|
|
90
90
|
dividend.delete()
|
|
@@ -3,7 +3,6 @@ from datetime import datetime
|
|
|
3
3
|
from wbcore.contrib.currency.import_export.handlers import CurrencyImportHandler
|
|
4
4
|
from wbcore.contrib.io.exceptions import DeserializationError
|
|
5
5
|
from wbcore.contrib.io.imports import ImportExportHandler
|
|
6
|
-
from wbfdm.models.instruments import Cash
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
class FeesImportHandler(ImportExportHandler):
|
|
@@ -14,31 +13,22 @@ class FeesImportHandler(ImportExportHandler):
|
|
|
14
13
|
self.currency_handler = CurrencyImportHandler(self.import_source)
|
|
15
14
|
|
|
16
15
|
def _deserialize(self, data):
|
|
17
|
-
data["
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if book_date_str := data.get("book_date", None):
|
|
22
|
-
data["book_date"] = datetime.strptime(book_date_str, "%Y-%m-%d").date()
|
|
23
|
-
|
|
24
|
-
from wbportfolio.models import Portfolio, Product
|
|
16
|
+
data["fee_date"] = datetime.strptime(
|
|
17
|
+
data.get("fee_date", data.pop("transaction_date", None)), "%Y-%m-%d"
|
|
18
|
+
).date()
|
|
19
|
+
from wbportfolio.models import Product
|
|
25
20
|
|
|
26
21
|
try:
|
|
27
|
-
|
|
28
|
-
if isinstance(
|
|
29
|
-
data["
|
|
22
|
+
product_data = data.pop("product", None)
|
|
23
|
+
if isinstance(product_data, dict):
|
|
24
|
+
data["product"] = Product.objects.get(**product_data)
|
|
30
25
|
else:
|
|
31
|
-
data["
|
|
26
|
+
data["product"] = Product.objects.get(id=product_data)
|
|
32
27
|
except Product.DoesNotExist:
|
|
33
28
|
raise DeserializationError("There is no valid linked product for in this row.")
|
|
34
29
|
|
|
35
|
-
if "porfolio" in data:
|
|
36
|
-
data["portfolio"] = Portfolio.all_objects.get(id=data["portfolio"])
|
|
37
|
-
else:
|
|
38
|
-
data["portfolio"] = data["linked_product"].primary_portfolio
|
|
39
|
-
data["underlying_instrument"] = Cash.objects.filter(currency=data["portfolio"].currency).first()
|
|
40
30
|
if "currency" not in data:
|
|
41
|
-
data["currency"] = data["
|
|
31
|
+
data["currency"] = data["product"].currency
|
|
42
32
|
else:
|
|
43
33
|
data["currency"] = self.currency_handler.process_object(data["currency"], read_only=True)[0]
|
|
44
34
|
data["currency_fx_rate"] = 1.0
|
|
@@ -48,9 +38,9 @@ class FeesImportHandler(ImportExportHandler):
|
|
|
48
38
|
|
|
49
39
|
def _get_instance(self, data, history=None, **kwargs):
|
|
50
40
|
self.import_source.log += "\nGet Fees Instance."
|
|
51
|
-
self.import_source.log += f"\nParameter:
|
|
41
|
+
self.import_source.log += f"\nParameter: Product={data['product']} Date={data['fee_date']}"
|
|
52
42
|
fees = self.model.objects.filter(
|
|
53
|
-
|
|
43
|
+
product=data["product"],
|
|
54
44
|
fee_date=data["fee_date"],
|
|
55
45
|
transaction_subtype=data["transaction_subtype"],
|
|
56
46
|
calculated=data["calculated"],
|