wbportfolio 1.52.0__py2.py3-none-any.whl → 1.59.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.

Files changed (273) hide show
  1. wbportfolio/admin/__init__.py +3 -1
  2. wbportfolio/admin/indexes.py +1 -1
  3. wbportfolio/admin/orders/__init__.py +2 -0
  4. wbportfolio/admin/orders/order_proposals.py +16 -0
  5. wbportfolio/admin/orders/orders.py +32 -0
  6. wbportfolio/admin/portfolio.py +11 -5
  7. wbportfolio/admin/product_groups.py +1 -1
  8. wbportfolio/admin/products.py +2 -1
  9. wbportfolio/admin/{transactions/rebalancing.py → rebalancing.py} +1 -1
  10. wbportfolio/admin/transactions/__init__.py +0 -2
  11. wbportfolio/admin/transactions/dividends.py +40 -4
  12. wbportfolio/admin/transactions/fees.py +24 -14
  13. wbportfolio/admin/transactions/trades.py +34 -27
  14. wbportfolio/analysis/claims.py +5 -6
  15. wbportfolio/api_clients/ubs.py +162 -0
  16. wbportfolio/constants.py +1 -0
  17. wbportfolio/contrib/company_portfolio/configs/display.py +22 -10
  18. wbportfolio/contrib/company_portfolio/configs/previews.py +3 -3
  19. wbportfolio/contrib/company_portfolio/filters.py +10 -10
  20. wbportfolio/contrib/company_portfolio/models.py +69 -39
  21. wbportfolio/contrib/company_portfolio/scripts.py +7 -2
  22. wbportfolio/contrib/company_portfolio/serializers.py +32 -22
  23. wbportfolio/contrib/company_portfolio/tasks.py +12 -1
  24. wbportfolio/contrib/company_portfolio/tests/conftest.py +2 -2
  25. wbportfolio/defaults/fees/default.py +7 -15
  26. wbportfolio/factories/__init__.py +2 -2
  27. wbportfolio/factories/assets.py +1 -1
  28. wbportfolio/factories/dividends.py +8 -3
  29. wbportfolio/factories/fees.py +8 -4
  30. wbportfolio/factories/orders/__init__.py +2 -0
  31. wbportfolio/factories/orders/order_proposals.py +21 -0
  32. wbportfolio/factories/orders/orders.py +34 -0
  33. wbportfolio/factories/portfolios.py +2 -1
  34. wbportfolio/factories/product_groups.py +3 -3
  35. wbportfolio/factories/products.py +3 -3
  36. wbportfolio/factories/rebalancing.py +1 -1
  37. wbportfolio/factories/trades.py +12 -16
  38. wbportfolio/filters/assets.py +18 -4
  39. wbportfolio/filters/orders/__init__.py +2 -0
  40. wbportfolio/filters/orders/order_proposals.py +55 -0
  41. wbportfolio/filters/orders/orders.py +11 -0
  42. wbportfolio/filters/portfolios.py +38 -1
  43. wbportfolio/filters/positions.py +0 -1
  44. wbportfolio/filters/transactions/__init__.py +1 -2
  45. wbportfolio/filters/transactions/fees.py +5 -12
  46. wbportfolio/filters/transactions/trades.py +16 -8
  47. wbportfolio/filters/transactions/utils.py +42 -0
  48. wbportfolio/import_export/backends/ubs/__init__.py +1 -0
  49. wbportfolio/import_export/backends/ubs/asset_position.py +6 -7
  50. wbportfolio/import_export/backends/ubs/fees.py +10 -20
  51. wbportfolio/import_export/backends/ubs/instrument_price.py +6 -6
  52. wbportfolio/import_export/backends/ubs/trade.py +48 -0
  53. wbportfolio/import_export/backends/utils.py +0 -17
  54. wbportfolio/import_export/handlers/asset_position.py +22 -10
  55. wbportfolio/import_export/handlers/dividend.py +8 -8
  56. wbportfolio/import_export/handlers/fees.py +13 -23
  57. wbportfolio/import_export/handlers/orders.py +71 -0
  58. wbportfolio/import_export/handlers/trade.py +53 -77
  59. wbportfolio/import_export/parsers/default_mapping.py +1 -1
  60. wbportfolio/import_export/parsers/jpmorgan/customer_trade.py +2 -2
  61. wbportfolio/import_export/parsers/jpmorgan/fees.py +4 -4
  62. wbportfolio/import_export/parsers/jpmorgan/strategy.py +59 -85
  63. wbportfolio/import_export/parsers/jpmorgan/valuation.py +2 -2
  64. wbportfolio/import_export/parsers/leonteq/customer_trade.py +5 -5
  65. wbportfolio/import_export/parsers/leonteq/fees.py +11 -7
  66. wbportfolio/import_export/parsers/leonteq/trade.py +2 -6
  67. wbportfolio/import_export/parsers/natixis/d1_fees.py +2 -2
  68. wbportfolio/import_export/parsers/natixis/dividend.py +4 -9
  69. wbportfolio/import_export/parsers/natixis/equity.py +22 -4
  70. wbportfolio/import_export/parsers/natixis/fees.py +7 -9
  71. wbportfolio/import_export/parsers/natixis/utils.py +13 -19
  72. wbportfolio/import_export/parsers/sg_lux/customer_trade_pending_slk.py +1 -1
  73. wbportfolio/import_export/parsers/sg_lux/equity.py +10 -10
  74. wbportfolio/import_export/parsers/sg_lux/fees.py +2 -2
  75. wbportfolio/import_export/parsers/sg_lux/perf_fees.py +2 -2
  76. wbportfolio/import_export/parsers/sg_lux/sylk.py +12 -11
  77. wbportfolio/import_export/parsers/sg_lux/utils.py +2 -2
  78. wbportfolio/import_export/parsers/sg_lux/valuation.py +4 -2
  79. wbportfolio/import_export/parsers/societe_generale/strategy.py +5 -5
  80. wbportfolio/import_export/parsers/tellco/customer_trade.py +2 -1
  81. wbportfolio/import_export/parsers/tellco/valuation.py +4 -3
  82. wbportfolio/import_export/parsers/ubs/api/fees.py +2 -2
  83. wbportfolio/import_export/parsers/ubs/api/trade.py +39 -0
  84. wbportfolio/import_export/parsers/ubs/customer_trade.py +7 -5
  85. wbportfolio/import_export/parsers/ubs/equity.py +3 -2
  86. wbportfolio/import_export/parsers/ubs/valuation.py +2 -1
  87. wbportfolio/import_export/parsers/vontobel/customer_trade.py +2 -3
  88. wbportfolio/import_export/parsers/vontobel/historical_customer_trade.py +0 -1
  89. wbportfolio/import_export/parsers/vontobel/management_fees.py +12 -20
  90. wbportfolio/import_export/parsers/vontobel/performance_fees.py +5 -8
  91. wbportfolio/import_export/parsers/vontobel/valuation_api.py +4 -1
  92. wbportfolio/import_export/resources/trades.py +3 -3
  93. wbportfolio/import_export/utils.py +3 -1
  94. wbportfolio/jinja2/wbportfolio/sql/aum_nnm.sql +2 -2
  95. wbportfolio/metric/backends/base.py +2 -2
  96. wbportfolio/migrations/0059_fees_unique_fees.py +1 -1
  97. wbportfolio/migrations/0077_remove_transaction_currency_and_more.py +622 -0
  98. wbportfolio/migrations/0078_trade_drift_factor.py +26 -0
  99. wbportfolio/migrations/0079_alter_trade_drift_factor.py +19 -0
  100. wbportfolio/migrations/0080_alter_trade_drift_factor_alter_trade_weighting.py +19 -0
  101. wbportfolio/migrations/0081_alter_trade_drift_factor.py +19 -0
  102. wbportfolio/migrations/0082_remove_tradeproposal_creator_and_more.py +93 -0
  103. wbportfolio/migrations/0083_order_alter_trade_options_and_more.py +181 -0
  104. wbportfolio/migrations/0084_orderproposal_min_order_value.py +25 -0
  105. wbportfolio/migrations/0085_order_desired_target_weight.py +26 -0
  106. wbportfolio/migrations/0086_orderproposal_total_cash_weight.py +19 -0
  107. wbportfolio/migrations/0087_product_order_routing_custodian_adapter.py +94 -0
  108. wbportfolio/migrations/0088_orderproposal_total_effective_portfolio_contribution.py +19 -0
  109. wbportfolio/migrations/0089_orderproposal_min_weighting.py +71 -0
  110. wbportfolio/migrations/0090_dividendtransaction_price_fx_portfolio_and_more.py +44 -0
  111. wbportfolio/migrations/0091_remove_order_execution_confirmed_and_more.py +32 -0
  112. wbportfolio/migrations/0092_order_quantization_error_alter_orderproposal_status.py +49 -0
  113. wbportfolio/migrations/0093_remove_portfolioportfoliothroughmodel_unique_primary_and_more.py +35 -0
  114. wbportfolio/models/__init__.py +2 -0
  115. wbportfolio/models/adjustments.py +1 -1
  116. wbportfolio/models/asset.py +28 -170
  117. wbportfolio/models/builder.py +323 -0
  118. wbportfolio/models/custodians.py +3 -3
  119. wbportfolio/models/exceptions.py +1 -1
  120. wbportfolio/models/graphs/portfolio.py +1 -1
  121. wbportfolio/models/graphs/utils.py +11 -11
  122. wbportfolio/models/mixins/instruments.py +7 -0
  123. wbportfolio/models/mixins/liquidity_stress_test.py +4 -4
  124. wbportfolio/models/orders/__init__.py +2 -0
  125. wbportfolio/models/orders/order_proposals.py +1414 -0
  126. wbportfolio/models/orders/orders.py +410 -0
  127. wbportfolio/models/portfolio.py +311 -289
  128. wbportfolio/models/portfolio_relationship.py +6 -0
  129. wbportfolio/models/products.py +12 -0
  130. wbportfolio/models/{transactions/rebalancing.py → rebalancing.py} +40 -27
  131. wbportfolio/models/roles.py +4 -10
  132. wbportfolio/models/transactions/__init__.py +0 -4
  133. wbportfolio/models/transactions/claim.py +7 -6
  134. wbportfolio/models/transactions/dividends.py +42 -5
  135. wbportfolio/models/transactions/fees.py +55 -22
  136. wbportfolio/models/transactions/trades.py +121 -442
  137. wbportfolio/models/transactions/transactions.py +78 -158
  138. wbportfolio/models/utils.py +100 -1
  139. wbportfolio/order_routing/__init__.py +35 -0
  140. wbportfolio/order_routing/adapters/__init__.py +65 -0
  141. wbportfolio/order_routing/adapters/ubs.py +195 -0
  142. wbportfolio/order_routing/router.py +33 -0
  143. wbportfolio/order_routing/tests/__init__.py +0 -0
  144. wbportfolio/order_routing/tests/test_router.py +110 -0
  145. wbportfolio/permissions.py +7 -0
  146. wbportfolio/pms/analytics/portfolio.py +17 -9
  147. wbportfolio/pms/analytics/utils.py +9 -0
  148. wbportfolio/pms/trading/__init__.py +0 -1
  149. wbportfolio/pms/trading/optimizer.py +61 -0
  150. wbportfolio/pms/typing.py +198 -63
  151. wbportfolio/rebalancing/base.py +12 -1
  152. wbportfolio/rebalancing/decorators.py +1 -1
  153. wbportfolio/rebalancing/models/composite.py +4 -8
  154. wbportfolio/rebalancing/models/equally_weighted.py +13 -11
  155. wbportfolio/rebalancing/models/market_capitalization_weighted.py +21 -14
  156. wbportfolio/rebalancing/models/model_portfolio.py +14 -18
  157. wbportfolio/risk_management/backends/__init__.py +1 -0
  158. wbportfolio/risk_management/backends/controversy_portfolio.py +2 -2
  159. wbportfolio/risk_management/backends/esg_aggregation_portfolio.py +64 -0
  160. wbportfolio/risk_management/backends/exposure_portfolio.py +4 -4
  161. wbportfolio/risk_management/backends/instrument_list_portfolio.py +3 -3
  162. wbportfolio/risk_management/tests/test_esg_aggregation_portfolio.py +49 -0
  163. wbportfolio/risk_management/tests/test_exposure_portfolio.py +1 -1
  164. wbportfolio/risk_management/tests/test_stop_loss_instrument.py +2 -2
  165. wbportfolio/risk_management/tests/test_stop_loss_portfolio.py +1 -1
  166. wbportfolio/serializers/__init__.py +1 -0
  167. wbportfolio/serializers/orders/__init__.py +2 -0
  168. wbportfolio/serializers/orders/order_proposals.py +115 -0
  169. wbportfolio/serializers/orders/orders.py +283 -0
  170. wbportfolio/serializers/portfolios.py +7 -7
  171. wbportfolio/serializers/positions.py +2 -2
  172. wbportfolio/serializers/rebalancing.py +1 -1
  173. wbportfolio/serializers/signals.py +9 -12
  174. wbportfolio/serializers/transactions/__init__.py +1 -10
  175. wbportfolio/serializers/transactions/claim.py +2 -2
  176. wbportfolio/serializers/transactions/dividends.py +37 -9
  177. wbportfolio/serializers/transactions/fees.py +39 -10
  178. wbportfolio/serializers/transactions/trades.py +55 -157
  179. wbportfolio/tasks.py +43 -5
  180. wbportfolio/tests/analysis/__init__.py +0 -0
  181. wbportfolio/tests/analysis/test_claims.py +85 -0
  182. wbportfolio/tests/conftest.py +12 -12
  183. wbportfolio/tests/models/orders/__init__.py +0 -0
  184. wbportfolio/tests/models/orders/test_order_proposals.py +1046 -0
  185. wbportfolio/tests/models/test_assets.py +7 -3
  186. wbportfolio/tests/models/test_imports.py +9 -13
  187. wbportfolio/tests/models/test_portfolios.py +102 -95
  188. wbportfolio/tests/models/test_products.py +11 -0
  189. wbportfolio/tests/models/test_splits.py +1 -6
  190. wbportfolio/tests/models/test_utils.py +140 -0
  191. wbportfolio/tests/models/transactions/test_fees.py +7 -13
  192. wbportfolio/tests/models/transactions/test_rebalancing.py +5 -5
  193. wbportfolio/tests/models/transactions/test_trades.py +0 -20
  194. wbportfolio/tests/pms/test_analytics.py +22 -3
  195. wbportfolio/tests/rebalancing/test_models.py +51 -57
  196. wbportfolio/tests/signals.py +10 -20
  197. wbportfolio/tests/tests.py +3 -1
  198. wbportfolio/tests/viewsets/test_products.py +1 -0
  199. wbportfolio/urls.py +10 -13
  200. wbportfolio/viewsets/__init__.py +9 -4
  201. wbportfolio/viewsets/assets.py +3 -204
  202. wbportfolio/viewsets/charts/__init__.py +6 -1
  203. wbportfolio/viewsets/charts/assets.py +344 -154
  204. wbportfolio/viewsets/configs/buttons/__init__.py +2 -2
  205. wbportfolio/viewsets/configs/buttons/assets.py +1 -1
  206. wbportfolio/viewsets/configs/buttons/mixins.py +4 -4
  207. wbportfolio/viewsets/configs/buttons/portfolios.py +45 -1
  208. wbportfolio/viewsets/configs/buttons/products.py +32 -2
  209. wbportfolio/viewsets/configs/display/__init__.py +2 -5
  210. wbportfolio/viewsets/configs/display/assets.py +6 -19
  211. wbportfolio/viewsets/configs/display/fees.py +3 -3
  212. wbportfolio/viewsets/configs/display/portfolios.py +5 -5
  213. wbportfolio/viewsets/configs/display/products.py +1 -1
  214. wbportfolio/viewsets/configs/display/rebalancing.py +2 -2
  215. wbportfolio/viewsets/configs/display/reconciliations.py +4 -4
  216. wbportfolio/viewsets/configs/display/trades.py +1 -189
  217. wbportfolio/viewsets/configs/endpoints/__init__.py +3 -7
  218. wbportfolio/viewsets/configs/endpoints/fees.py +2 -2
  219. wbportfolio/viewsets/configs/endpoints/trades.py +0 -41
  220. wbportfolio/viewsets/configs/menu/__init__.py +1 -1
  221. wbportfolio/viewsets/configs/menu/orders.py +11 -0
  222. wbportfolio/viewsets/configs/titles/__init__.py +2 -3
  223. wbportfolio/viewsets/configs/titles/fees.py +4 -8
  224. wbportfolio/viewsets/esg.py +3 -5
  225. wbportfolio/viewsets/mixins.py +5 -1
  226. wbportfolio/viewsets/orders/__init__.py +6 -0
  227. wbportfolio/viewsets/orders/configs/__init__.py +4 -0
  228. wbportfolio/viewsets/orders/configs/buttons/__init__.py +2 -0
  229. wbportfolio/viewsets/orders/configs/buttons/order_proposals.py +188 -0
  230. wbportfolio/viewsets/orders/configs/buttons/orders.py +113 -0
  231. wbportfolio/viewsets/orders/configs/displays/__init__.py +2 -0
  232. wbportfolio/viewsets/orders/configs/displays/order_proposals.py +157 -0
  233. wbportfolio/viewsets/orders/configs/displays/orders.py +232 -0
  234. wbportfolio/viewsets/orders/configs/endpoints/__init__.py +2 -0
  235. wbportfolio/viewsets/orders/configs/endpoints/order_proposals.py +21 -0
  236. wbportfolio/viewsets/orders/configs/endpoints/orders.py +28 -0
  237. wbportfolio/viewsets/orders/configs/titles/__init__.py +0 -0
  238. wbportfolio/viewsets/orders/configs/titles/orders.py +0 -0
  239. wbportfolio/viewsets/orders/order_proposals.py +252 -0
  240. wbportfolio/viewsets/orders/orders.py +277 -0
  241. wbportfolio/viewsets/portfolios.py +36 -12
  242. wbportfolio/viewsets/positions.py +3 -2
  243. wbportfolio/viewsets/products.py +6 -6
  244. wbportfolio/viewsets/{transactions/rebalancing.py → rebalancing.py} +2 -2
  245. wbportfolio/viewsets/transactions/__init__.py +3 -14
  246. wbportfolio/viewsets/transactions/fees.py +22 -22
  247. wbportfolio/viewsets/transactions/trades.py +1 -180
  248. {wbportfolio-1.52.0.dist-info → wbportfolio-1.59.4.dist-info}/METADATA +3 -1
  249. {wbportfolio-1.52.0.dist-info → wbportfolio-1.59.4.dist-info}/RECORD +252 -203
  250. {wbportfolio-1.52.0.dist-info → wbportfolio-1.59.4.dist-info}/WHEEL +1 -1
  251. wbportfolio/admin/transactions/transactions.py +0 -38
  252. wbportfolio/factories/transactions.py +0 -22
  253. wbportfolio/fdm/tasks.py +0 -13
  254. wbportfolio/filters/transactions/transactions.py +0 -99
  255. wbportfolio/models/transactions/expiry.py +0 -7
  256. wbportfolio/models/transactions/trade_proposals.py +0 -704
  257. wbportfolio/pms/trading/handler.py +0 -161
  258. wbportfolio/serializers/transactions/expiry.py +0 -18
  259. wbportfolio/serializers/transactions/trade_proposals.py +0 -76
  260. wbportfolio/serializers/transactions/transactions.py +0 -85
  261. wbportfolio/tests/models/transactions/test_trade_proposals.py +0 -410
  262. wbportfolio/viewsets/configs/buttons/trade_proposals.py +0 -66
  263. wbportfolio/viewsets/configs/display/trade_proposals.py +0 -100
  264. wbportfolio/viewsets/configs/display/transactions.py +0 -55
  265. wbportfolio/viewsets/configs/endpoints/trade_proposals.py +0 -18
  266. wbportfolio/viewsets/configs/endpoints/transactions.py +0 -14
  267. wbportfolio/viewsets/configs/menu/transactions.py +0 -9
  268. wbportfolio/viewsets/configs/titles/transactions.py +0 -9
  269. wbportfolio/viewsets/signals.py +0 -43
  270. wbportfolio/viewsets/transactions/trade_proposals.py +0 -139
  271. wbportfolio/viewsets/transactions/transactions.py +0 -122
  272. /wbportfolio/{fdm → api_clients}/__init__.py +0 -0
  273. {wbportfolio-1.52.0.dist-info → wbportfolio-1.59.4.dist-info}/licenses/LICENSE +0 -0
@@ -12,106 +12,80 @@ logger = logging.getLogger("importers.parsers.jp_morgan.strategy")
12
12
 
13
13
  def file_name_parse(file_name):
14
14
  dates = re.findall("([0-9]{8})", file_name)
15
-
16
- assert len(dates) == 1, "Not exactly 1 date found in the filename"
17
-
18
- return {"valuation_date": datetime.datetime.strptime(dates[0], "%Y%m%d").date()}
19
-
20
-
21
- def manually_create_100_position(parent_strategies, valuation_date):
22
- data = []
23
- from wbportfolio.models import Index, Product
24
-
25
- for strategy_ticker in parent_strategies:
26
- if index := Index.objects.filter(ticker=strategy_ticker).first():
27
- for product in Product.objects.filter(ticker=strategy_ticker):
28
- valuations = product.valuations.filter(date__lte=valuation_date)
29
- last_price = 0
30
- if valuations.exists():
31
- last_price = float(valuations.latest("date").net_value)
32
- data.append(
33
- {
34
- "underlying_quote": index.id,
35
- "portfolio": {"instrument_type": "product", "id": product.id},
36
- "currency__key": index.currency.key,
37
- "initial_currency_fx_rate": 1.0,
38
- "weighting": 1.0,
39
- "is_estimated": True, # this position is not a real position, it is created by the importer.
40
- "initial_price": last_price,
41
- "date": valuation_date.strftime("%Y-%m-%d"),
42
- }
43
- )
44
- return data
15
+ if dates:
16
+ return {"valuation_date": datetime.datetime.strptime(dates[0], "%Y%m%d").date()}
17
+ return {}
45
18
 
46
19
 
47
20
  def parse(import_source):
48
21
  # Load file into a CSV DictReader
49
22
 
50
23
  df = pd.read_csv(import_source.file, encoding="utf-16", delimiter=",")
51
- df = df.replace([np.inf, -np.inf, np.nan], None)
52
24
 
53
25
  # Parse the Parts of the filename into the different parts
54
26
  parts = file_name_parse(import_source.file.name)
55
27
 
56
28
  # Get the valuation date from the parts list
57
- valuation_date = parts["valuation_date"]
29
+ report_date = parts.get("valuation_date")
58
30
 
59
31
  # Iterate through the CSV File and parse the data into a list
60
32
  data = list()
61
- parents_strategies = set()
33
+ if "Date" in df.columns:
34
+ df["Date"] = pd.to_datetime(df["Date"])
35
+ df.replace([np.inf, -np.inf, np.nan], None, inplace=True)
62
36
  for strategy_data in df.to_dict("records"):
63
- bbg_tickers = strategy_data["BBG Ticker"].split(" ")
64
- exchange = None
65
- if len(bbg_tickers) == 2:
66
- ticker = bbg_tickers[0]
67
- instrument_type = bbg_tickers[1]
68
- elif len(bbg_tickers) == 3:
69
- ticker = bbg_tickers[0]
70
- exchange = bbg_tickers[1]
71
- instrument_type = bbg_tickers[2]
72
-
73
- strategy = strategy_data["Strategy Ticker"].replace("Index", "").strip()
74
- strategy_currency_key = strategy_data["Strategy CCY"]
75
-
76
- position_currency_key = strategy_data["Position CCY"]
77
-
78
- isin = strategy_data["Position ISIN"]
79
- name = strategy_data["Position Description"]
80
- initial_price = convert_string_to_number(strategy_data["Prices"])
81
- initial_currency_fx_rate = convert_string_to_number(strategy_data["Fx Rates"])
82
- if exchange:
83
- exchange = {"bbg_exchange_codes": exchange}
84
- try:
85
- weighting = convert_string_to_number(strategy_data["Weight In Percent"].replace("%", "")) / 100
86
- except Exception:
87
- weighting = 0.0
88
- underlying_quote = {
89
- "ticker": ticker,
90
- "exchange": exchange,
91
- "isin": isin,
92
- "name": name,
93
- "currency__key": position_currency_key,
94
- "instrument_type": instrument_type.lower(),
95
- }
96
- if isin:
97
- underlying_quote["isin"] = isin
98
- data.append(
99
- {
100
- "underlying_quote": underlying_quote,
101
- "portfolio": {
102
- "instrument_type": "index",
103
- "ticker": strategy,
104
- "currency__key": strategy_currency_key,
105
- },
37
+ valuation_date = strategy_data.get("Date", report_date)
38
+ if valuation_date:
39
+ bbg_tickers = strategy_data["BBG Ticker"].split(" ")
40
+ exchange = None
41
+ if len(bbg_tickers) == 2:
42
+ ticker = bbg_tickers[0]
43
+ instrument_type = bbg_tickers[1]
44
+ elif len(bbg_tickers) == 3:
45
+ ticker = bbg_tickers[0]
46
+ exchange = bbg_tickers[1]
47
+ instrument_type = bbg_tickers[2]
48
+
49
+ strategy = strategy_data["Strategy Ticker"].replace("Index", "").strip()
50
+ strategy_currency_key = strategy_data["Strategy CCY"]
51
+
52
+ position_currency_key = strategy_data["Position CCY"]
53
+
54
+ isin = strategy_data["Position ISIN"]
55
+ name = strategy_data["Position Description"]
56
+ initial_price = convert_string_to_number(strategy_data["Prices"])
57
+ initial_currency_fx_rate = convert_string_to_number(strategy_data["Fx Rates"])
58
+ if exchange:
59
+ exchange = {"bbg_exchange_codes": exchange}
60
+ try:
61
+ weighting = convert_string_to_number(strategy_data["Weight In Percent"].replace("%", "")) / 100
62
+ except Exception:
63
+ weighting = 0.0
64
+ underlying_quote = {
65
+ "ticker": ticker,
106
66
  "exchange": exchange,
107
- "is_estimated": False,
67
+ "isin": isin,
68
+ "name": name,
108
69
  "currency__key": position_currency_key,
109
- "initial_currency_fx_rate": initial_currency_fx_rate,
110
- "weighting": weighting,
111
- "initial_price": initial_price,
112
- "date": valuation_date.strftime("%Y-%m-%d"),
70
+ "instrument_type": instrument_type.lower(),
113
71
  }
114
- )
115
- parents_strategies.add(strategy)
116
- manual_data = manually_create_100_position(parents_strategies, valuation_date)
117
- return {"data": data + manual_data}
72
+ if isin:
73
+ underlying_quote["isin"] = isin
74
+ data.append(
75
+ {
76
+ "underlying_quote": underlying_quote,
77
+ "portfolio": {
78
+ "instrument_type": "index",
79
+ "ticker": strategy,
80
+ "currency__key": strategy_currency_key,
81
+ },
82
+ "exchange": exchange,
83
+ "is_estimated": False,
84
+ "currency__key": position_currency_key,
85
+ "initial_currency_fx_rate": initial_currency_fx_rate,
86
+ "weighting": weighting,
87
+ "initial_price": initial_price,
88
+ "date": valuation_date.strftime("%Y-%m-%d"),
89
+ }
90
+ )
91
+ return {"data": data}
@@ -13,8 +13,8 @@ logger = logging.getLogger("importers.parsers.jpmorgan.index")
13
13
  def file_name_parse(file_name):
14
14
  dates = re.findall("([0-9]{8})", file_name)
15
15
 
16
- assert len(dates) == 1, "Not exactly 1 date found in the filename"
17
-
16
+ if len(dates) != 1:
17
+ raise ValueError("Not exactly 1 date found in the filename")
18
18
  return {"valuation_date": datetime.datetime.strptime(dates[0], "%Y%m%d").date()}
19
19
 
20
20
 
@@ -1,4 +1,5 @@
1
1
  from contextlib import suppress
2
+ from io import BytesIO
2
3
 
3
4
  import pandas as pd
4
5
 
@@ -10,7 +11,7 @@ def parse(import_source):
10
11
  df = pd.DataFrame()
11
12
  try:
12
13
  df = pd.read_excel(
13
- import_source.file.read(),
14
+ BytesIO(import_source.file.read()),
14
15
  engine="openpyxl",
15
16
  index_col=1,
16
17
  sheet_name="TRANSACTION CONSOLIDATION",
@@ -25,7 +26,8 @@ def parse(import_source):
25
26
  with suppress(Product.DoesNotExist):
26
27
  product = Product.objects.get(isin=trade["AMC ISIN"])
27
28
  shares = (-1 * trade["TOTAL QUANTITY"]) / product.share_price
28
- price = trade["GROSS PRICE"] * product.share_price
29
+ price = trade["NET PRICE"] * product.share_price
30
+ price_gross = trade["GROSS PRICE"] * product.share_price
29
31
  portfolio = product.primary_portfolio
30
32
  data.append(
31
33
  {
@@ -38,10 +40,8 @@ def parse(import_source):
38
40
  "bank": "Leonteq Cash Transfer",
39
41
  "currency__key": product.currency.key,
40
42
  "price": price,
41
- "total_value": trade["NET AMOUNT"],
42
- "total_value_gross": trade["GROSS AMOUNT"],
43
+ "price_gross": price_gross,
43
44
  "currency_fx_rate": trade["BOOKING FX"],
44
- "total_value_fx_portfolio": trade["BOOKING NET AMOUNT"],
45
45
  }
46
46
  )
47
47
 
@@ -1,3 +1,5 @@
1
+ from io import BytesIO
2
+
1
3
  import pandas as pd
2
4
 
3
5
  from wbportfolio.models import Fees
@@ -8,7 +10,11 @@ def parse(import_source):
8
10
  df = pd.DataFrame()
9
11
  try:
10
12
  df = pd.read_excel(
11
- import_source.file.read(), engine="openpyxl", sheet_name="FEE_CONSOLIDATION", header=[0, 2], skiprows=1
13
+ BytesIO(import_source.file.read()),
14
+ engine="openpyxl",
15
+ sheet_name="FEE_CONSOLIDATION",
16
+ header=[0, 2],
17
+ skiprows=1,
12
18
  ).dropna(axis=1)
13
19
  except (ValueError, IndexError):
14
20
  pass
@@ -19,15 +25,13 @@ def parse(import_source):
19
25
 
20
26
  product_fees_df = product_fees_df.rename(
21
27
  columns={
22
- "TRADE DATE": "transaction_date",
28
+ "TRADE DATE": "fee_date",
23
29
  "INDEX SPONSOR FEE": "total_value",
24
30
  "BUSINESS EVENT": "transaction_subtype",
25
31
  }
26
32
  )
27
33
 
28
- product_fees_df.loc[:, "transaction_date"] = pd.to_datetime(
29
- product_fees_df.loc[:, "transaction_date"], dayfirst=True
30
- )
34
+ product_fees_df.loc[:, "fee_date"] = pd.to_datetime(product_fees_df.loc[:, "fee_date"], dayfirst=True)
31
35
 
32
36
  management_fees_eur = product_fees_df[
33
37
  product_fees_df.loc[:, "transaction_subtype"] == "Management Fee EUR"
@@ -50,8 +54,8 @@ def parse(import_source):
50
54
  product_fees_df = product_fees_df.where(pd.notnull(product_fees_df), None)
51
55
  for fees in product_fees_df.to_dict("records"):
52
56
  base_data = {
53
- "linked_product": {"isin": isin},
54
- "transaction_date": fees["transaction_date"].strftime("%Y-%m-%d"),
57
+ "product": {"isin": isin},
58
+ "fee_date": fees["fee_date"].strftime("%Y-%m-%d"),
55
59
  "calculated": False,
56
60
  }
57
61
 
@@ -10,7 +10,8 @@ from wbportfolio.models import Product
10
10
  def file_name_parse(file_name):
11
11
  isin = re.findall("([A-Z]{2}[A-Z0-9]{9}[0-9]{1})", file_name)
12
12
 
13
- assert len(isin) == 1, "Not exactly 1 isin found in the filename"
13
+ if len(isin) != 1:
14
+ raise ValueError("Not exactly 1 isin found in the filename")
14
15
 
15
16
  return {"isin": isin[0]}
16
17
 
@@ -24,11 +25,8 @@ _fields_map = {
24
25
  "TOTAL QUANTITY": "shares",
25
26
  "GROSS PRICE": "price_gross",
26
27
  "NET PRICE": "price",
27
- "total_value_gross": "total_value_gross",
28
28
  "currency_fx_rate": "currency_fx_rate",
29
29
  "NET AMOUNT": "total_value",
30
- "GROSS AMOUNT": "total_value_gross_fx_portfolio",
31
- "total_value_fx_portfolio": "total_value_fx_portfolio",
32
30
  "N°": "external_id",
33
31
  }
34
32
 
@@ -78,8 +76,6 @@ def parse(import_source):
78
76
  df["exchange"] = df["IDENTIFIER"].apply(lambda x: _get_exchange(x))
79
77
  df["currency_fx_rate"] = df["BOOKING FX"] * df["BASE CCY FX"]
80
78
 
81
- df["total_value_gross"] = df["total_value_gross_fx_portfolio"] / df["BOOKING FX"]
82
- df["total_value_fx_portfolio"] = df["total_value"] * df["currency_fx_rate"]
83
79
  df = df.drop(df.columns.difference(_fields_map.values()), axis=1)
84
80
  df["portfolio"] = product.primary_portfolio.id
85
81
 
@@ -37,8 +37,8 @@ def parse(import_source):
37
37
  management_fees = row[management_fees_column].value
38
38
 
39
39
  base_data = {
40
- "linked_product": {"isin": isin},
41
- "transaction_date": valuation_date.strftime("%Y-%m-%d"),
40
+ "product": {"isin": isin},
41
+ "fee_date": valuation_date.strftime("%Y-%m-%d"),
42
42
  "calculated": False,
43
43
  }
44
44
  if management_fees != 0:
@@ -10,10 +10,9 @@ COLUMN_MAP = {
10
10
  "Div Crncy": "currency__key",
11
11
  "Quantity": "shares",
12
12
  "Fx Rate": "currency_fx_rate",
13
- "Net Amount": "total_value_fx_portfolio",
14
13
  "Retro in%": "retrocession",
15
14
  "Gross Div": "price_gross",
16
- "Ex Div Date": "transaction_date",
15
+ "Ex Div Date": "ex_date",
17
16
  "Value Date": "value_date",
18
17
  }
19
18
 
@@ -29,25 +28,21 @@ def parse(import_source):
29
28
  lambda x: _get_exchange_from_ticker(x)
30
29
  )
31
30
  df["underlying_instrument__ticker"] = df["underlying_instrument__ticker"].apply(lambda x: _get_ticker(x))
32
- df["transaction_date"] = pd.to_datetime(df["transaction_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
31
+ df["ex_date"] = pd.to_datetime(df["ex_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
33
32
  df["value_date"] = pd.to_datetime(df["value_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
34
33
  df = df[df["underlying_instrument__isin"].str.contains("([A-Z]{2})([A-Z0-9]{9})([0-9]{1})", regex=True)]
35
- float_columns = ["shares", "total_value_fx_portfolio", "price_gross", "currency_fx_rate", "retrocession"]
34
+ float_columns = ["shares", "price_gross", "currency_fx_rate", "retrocession"]
36
35
  for float_column in float_columns:
37
36
  df[float_column] = df[float_column].str.replace(" ", "").astype("float")
38
37
  df = df.drop(columns=df.columns.difference(COLUMN_MAP.values()))
39
38
 
40
39
  df["retrocession"] = df["retrocession"] / 100.0
41
- df["total_value"] = df["total_value_fx_portfolio"] / df["currency_fx_rate"]
42
- df["price"] = df["total_value"] / df["shares"] / df["retrocession"]
43
- df["total_value_gross"] = df["price_gross"] * df["shares"] * df["retrocession"]
44
- df["total_value_gross_fx_portfolio"] = df["total_value_gross"] * df["currency_fx_rate"]
45
40
  df["portfolio"] = [{"instrument_type": "product", **product_data}] * df.shape[0]
46
41
  df = df.replace([np.inf, -np.inf, np.nan], None)
47
42
  return {
48
43
  "data": df.to_dict("records"),
49
44
  "history": {
50
- "transaction_date": parts["valuation_date"].strftime("%Y-%m-%d"),
45
+ "value_date": parts["valuation_date"].strftime("%Y-%m-%d"),
51
46
  "product": product_data,
52
47
  },
53
48
  }
@@ -1,3 +1,5 @@
1
+ from zipfile import BadZipFile
2
+
1
3
  import numpy as np
2
4
  import pandas as pd
3
5
 
@@ -29,15 +31,28 @@ def _apply_adjusting_factor(row):
29
31
  def parse(import_source):
30
32
  # Parse the Parts of the filename into the different parts
31
33
  parts = file_name_parse_isin(import_source.file.name)
32
-
33
34
  # Get the valuation date and investment from the parts list
34
35
  valuation_date = parts["valuation_date"]
35
36
  product_data = parts["product"]
36
37
 
37
38
  # Load file into a CSV DictReader
38
- df = pd.read_csv(import_source.file, encoding="utf-16", delimiter=";")
39
+ if import_source.file.name.lower().endswith(".csv"):
40
+ df = pd.read_csv(import_source.file, encoding="utf-16", delimiter=";")
41
+ else:
42
+ try:
43
+ df = pd.read_excel(import_source.file, engine="openpyxl", sheet_name="Basket Valuation")
44
+ except BadZipFile:
45
+ df = pd.read_excel(import_source.file, engine="xlrd", sheet_name="Basket Valuation")
46
+ xx, yy = np.where(df.isin(["Ticker", "Code"]))
47
+ if xx.size > 0 and yy.size > 0:
48
+ df = df.iloc[xx[0] :, yy[0] :]
49
+ df = df.rename(columns=df.iloc[0]).drop(df.index[0]).dropna(how="all")
50
+ df["Quotity/Adj. factor"] = 1.0
51
+ df = df.rename(columns={"Code": "Ticker"})
52
+ else:
53
+ return {}
39
54
  df = df.rename(columns=FIELD_MAP)
40
- df = df.dropna(subset=["initial_price"])
55
+ df = df.dropna(subset=["initial_price", "Name"], how="any")
41
56
  df["initial_price"] = df["initial_price"].astype("str").str.replace(" ", "").astype("float")
42
57
  df["underlying_quote"] = df[["Ticker", "Name", "currency__key"]].apply(
43
58
  lambda x: _get_underlying_instrument(*x), axis=1
@@ -50,7 +65,10 @@ def parse(import_source):
50
65
  df = df.drop(columns=df.columns.difference(FIELD_MAP.values()))
51
66
 
52
67
  df["portfolio__instrument_type"] = "product"
53
- df["portfolio__isin"] = product_data["isin"]
68
+ if "isin" in product_data:
69
+ df["portfolio__isin"] = product_data["isin"]
70
+ if "ticker" in product_data:
71
+ df["portfolio__ticker"] = product_data["ticker"]
54
72
  df["is_estimated"] = False
55
73
  df["date"] = valuation_date.strftime("%Y-%m-%d")
56
74
  df["asset_valuation_date"] = pd.to_datetime(df["asset_valuation_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
@@ -4,7 +4,7 @@ from pandas.tseries.offsets import BDay
4
4
  from .utils import file_name_parse_isin
5
5
 
6
6
  FIELD_MAP = {
7
- "Date": "transaction_date",
7
+ "Date": "fee_date",
8
8
  "Manag. Fees Natixis": "ISSUER",
9
9
  "Manag. Fees Client": "MANAGEMENT",
10
10
  "Perf fees amount": "PERFORMANCE",
@@ -21,30 +21,28 @@ def parse(import_source):
21
21
  df = pd.read_csv(import_source.file, encoding="utf-8", delimiter=";")
22
22
  df = df.rename(columns=FIELD_MAP)
23
23
  df = df.drop(columns=df.columns.difference(FIELD_MAP.values()))
24
- df["transaction_date"] = pd.to_datetime(df["transaction_date"], dayfirst=True)
24
+ df["fee_date"] = pd.to_datetime(df["fee_date"], dayfirst=True)
25
25
 
26
26
  # Switch the weeekend day fees to the next monday
27
- df["transaction_date"] = df["transaction_date"].apply(lambda x: x + BDay(1) if x.weekday() in [5, 6] else x)
27
+ df["fee_date"] = df["fee_date"].apply(lambda x: x + BDay(1) if x.weekday() in [5, 6] else x)
28
28
 
29
29
  # Ensure float columns are number
30
30
  for col in ["MANAGEMENT", "ISSUER", "PERFORMANCE"]:
31
31
  df[col] = df[col].astype("str").str.replace(" ", "").astype("float")
32
32
 
33
33
  # Groupby and sum similar fees (e.g. Monday)
34
- df = df.groupby("transaction_date").sum().reset_index()
34
+ df = df.groupby("fee_date").sum().reset_index()
35
35
  df = pd.melt(
36
36
  df,
37
- id_vars=["transaction_date"],
37
+ id_vars=["fee_date"],
38
38
  value_vars=["MANAGEMENT", "ISSUER", "PERFORMANCE"],
39
39
  var_name="transaction_subtype",
40
40
  value_name="total_value",
41
41
  )
42
42
  df = df[df["total_value"] != 0]
43
43
 
44
- df["linked_product"] = [product] * df.shape[0]
45
- df["transaction_date"] = df["transaction_date"].dt.strftime("%Y-%m-%d")
44
+ df["product"] = [product] * df.shape[0]
45
+ df["fee_date"] = df["fee_date"].dt.strftime("%Y-%m-%d")
46
46
  df["calculated"] = False
47
47
  df["total_value_gross"] = df["total_value"]
48
- df["total_value_fx_portfolio"] = df["total_value"]
49
- df["total_value_gross_fx_portfolio"] = df["total_value"]
50
48
  return {"data": df.to_dict("records")}
@@ -1,6 +1,7 @@
1
- import datetime
2
1
  import re
3
2
 
3
+ from django.utils.dateparse import parse_date
4
+
4
5
  from wbportfolio.models import Product
5
6
 
6
7
  INSTRUMENT_MAP_NAME = {"EDA23_AtonRa Z class": "LU2170995018"}
@@ -53,21 +54,14 @@ def _get_underlying_instrument(bbg_code, name, currency, instrument_type="equity
53
54
 
54
55
 
55
56
  def file_name_parse_isin(file_name):
56
- dates = re.findall(r"_([0-9]{4}-?[0-9]{2}-?[0-9]{2})", file_name)
57
- identifier = re.findall(r"([A-Z]{2}(?![A-Z]{10}\b)[A-Z0-9]{10})_", file_name)
58
- assert len(dates) == 2, "Not 2 dates found in the filename"
59
- assert len(identifier) == 1, "Not exactly 1 identifier found in the filename"
60
- try:
61
- valuation_date = datetime.datetime.strptime(dates[0], "%Y-%m-%d").date()
62
- except ValueError:
63
- valuation_date = datetime.datetime.strptime(dates[0], "%Y%m%d").date()
64
- try:
65
- generation_date = datetime.datetime.strptime(dates[1], "%Y-%m-%d").date()
66
- except ValueError:
67
- generation_date = datetime.datetime.strptime(dates[1], "%Y%m%d").date()
68
-
69
- return {
70
- "product": {"isin": identifier[0]},
71
- "valuation_date": valuation_date,
72
- "generation_date": generation_date,
73
- }
57
+ dates = re.findall(r"_([0-9]{4}[-_]?[0-9]{2}[-_]?[0-9]{2})", file_name)
58
+ isin = re.findall(r"([A-Z]{2}(?![A-Z]{10}\b)[A-Z0-9]{10})_", file_name)
59
+ ticker = re.findall(r"(NX[A-Z]*)_", file_name)
60
+ if len(dates) == 0:
61
+ raise ValueError("Not dates found in the filename")
62
+ res = {"valuation_date": parse_date(dates[0].replace("_", "-"))}
63
+ if len(isin) >= 1:
64
+ res["product"] = {"isin": isin[0]}
65
+ elif len(ticker) == 1:
66
+ res["product"] = {"ticker": ticker[0]}
67
+ return res
@@ -129,7 +129,7 @@ def parse(import_source):
129
129
  )
130
130
 
131
131
  df["bank"] = df["REGISTER_DEAL_NAME"]
132
- df["external_identifier2"] = df["NORDER"]
132
+ df["external_id_alternative"] = df["NORDER"]
133
133
  df["register__register_reference"] = df["REGISTER_ID1"]
134
134
  df["external_id"] = df.apply(assemble_transaction_reference, axis=1)
135
135
 
@@ -11,9 +11,10 @@ from wbportfolio.models import ProductGroup
11
11
  def file_name_parse(file_name):
12
12
  dates = re.findall(r"([0-9]{4}-[0-9]{2}-[0-9]{2})", file_name)
13
13
  isin = re.findall(r"\.([a-zA-Z0-9]*)_", file_name)
14
-
15
- assert len(dates) == 2, "Not 2 dates found in the filename"
16
- assert len(isin) == 1, "Not exactly 1 isin found in the filename"
14
+ if len(dates) != 2:
15
+ raise ValueError("Not 2 dates found in the filename")
16
+ if len(isin) != 1:
17
+ raise ValueError("Not exactly 1 isin found in the filename")
17
18
 
18
19
  return {
19
20
  "isin": isin[0],
@@ -76,13 +77,13 @@ def parse(import_source):
76
77
  }
77
78
 
78
79
  df["date"] = df["date"].apply(lambda x: x.replace("/", "-"))
79
- df["initial_currency_fx_rate"] = df["initial_currency_fx_rate"].apply(lambda x: 1 / x if x else 1).round(5)
80
+ df["initial_currency_fx_rate"] = df["initial_currency_fx_rate"].apply(lambda x: 1 / x if x else 1).round(14)
80
81
 
81
82
  cash_mask = df["Accounting category"].isin(["T111"])
82
83
  cash = (
83
84
  df.loc[
84
85
  cash_mask,
85
- ["currency__key", "initial_currency_fx_rate", "portfolio", "date", "initial_price", "initial_shares"],
86
+ ["currency__key", "initial_currency_fx_rate", "portfolio", "date", "initial_shares"],
86
87
  ]
87
88
  .groupby(
88
89
  [
@@ -94,21 +95,20 @@ def parse(import_source):
94
95
  .agg(
95
96
  {
96
97
  "initial_currency_fx_rate": "mean",
97
- "initial_price": "mean",
98
98
  "initial_shares": "sum",
99
99
  }
100
100
  )
101
101
  .reset_index()
102
102
  ).copy()
103
103
  cash["underlying_quote"] = cash["currency__key"].apply(lambda x: {"currency__key": x, "instrument_type": "cash"})
104
-
104
+ cash["initial_price"] = 1.0
105
105
  # cash_equivalents_mask, all asset type code that match TRES and which don't have accounting category T111
106
106
  cash_equivalents_mask = df["Asset type code"].str.match("TRES") & ~df["Accounting category"].str.match("T111")
107
107
 
108
108
  cash_equivalents = (
109
109
  df.loc[
110
110
  cash_equivalents_mask,
111
- ["currency__key", "initial_currency_fx_rate", "portfolio", "date", "initial_price", "initial_shares"],
111
+ ["currency__key", "initial_currency_fx_rate", "portfolio", "date", "initial_shares"],
112
112
  ]
113
113
  .groupby(
114
114
  [
@@ -120,7 +120,6 @@ def parse(import_source):
120
120
  .agg(
121
121
  {
122
122
  "initial_currency_fx_rate": "mean",
123
- "initial_price": "mean",
124
123
  "initial_shares": "sum",
125
124
  }
126
125
  )
@@ -129,9 +128,11 @@ def parse(import_source):
129
128
  cash_equivalents["underlying_quote"] = cash_equivalents["currency__key"].apply(
130
129
  lambda x: {"currency__key": x, "instrument_type": "cash_equivalent"}
131
130
  )
131
+ cash_equivalents["initial_price"] = 1.0
132
132
 
133
133
  # equities = df.loc[df["Accounting category"].str.match("010"), :].copy() # Historically, we filter out equity base on the "accounting category" that matches "010", we had issue with equity having the code "020". We decided to use the column "Asset type code" and filter out with the code "VCOM"
134
134
  equities = df.loc[df["Asset type code"].str.match("VMOB"), :].copy()
135
+
135
136
  if not equities.empty:
136
137
  equities["underlying_quote"] = equities.apply(lambda x: get_underlying_quote(x), axis=1)
137
138
  equities["exchange"] = equities.apply(lambda x: get_exchange(x), axis=1)
@@ -140,7 +141,6 @@ def parse(import_source):
140
141
  del equities["Bloom Ticker"]
141
142
  del equities["Asset description"]
142
143
  df = pd.concat([cash, equities, cash_equivalents])
143
-
144
144
  df["asset_valuation_date"] = df["date"]
145
145
  # Rename the columns
146
146
 
@@ -40,12 +40,12 @@ def parse(import_source):
40
40
  date = datetime.datetime.strptime(fee_data["NAV Date"], "%Y/%m/%d")
41
41
  data.append(
42
42
  {
43
- "linked_product": {
43
+ "product": {
44
44
  "parent__identifier": fee_data["Code"],
45
45
  "currency__key": fee_data["Local ccy"],
46
46
  "identifier": share_class,
47
47
  },
48
- "transaction_date": date.strftime("%Y-%m-%d"),
48
+ "fee_date": date.strftime("%Y-%m-%d"),
49
49
  "calculated": False,
50
50
  "transaction_subtype": Fees.Type.MANAGEMENT,
51
51
  "total_value": round(convert_string_to_number(fee_data.get("Fees - Local ccy", 0)), 6),
@@ -26,7 +26,7 @@ def parse(import_source):
26
26
  date = datetime.datetime.strptime(fee_data[0], "%m/%d/%Y")
27
27
  data.append(
28
28
  {
29
- "linked_product": {"isin": isin},
29
+ "product": {"isin": isin},
30
30
  "transaction_date": date.strftime("%Y-%m-%d"),
31
31
  "calculated": False,
32
32
  "transaction_subtype": Fees.Type.PERFORMANCE,
@@ -35,7 +35,7 @@ def parse(import_source):
35
35
  )
36
36
  data.append(
37
37
  {
38
- "linked_product": {"isin": isin},
38
+ "product": {"isin": isin},
39
39
  "transaction_date": date.strftime("%Y-%m-%d"),
40
40
  "calculated": False,
41
41
  "transaction_subtype": Fees.Type.PERFORMANCE_CRYSTALIZED,