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
@@ -10,6 +10,7 @@ files generated by other spreadsheets.
10
10
  """
11
11
 
12
12
  import re
13
+ from ast import literal_eval
13
14
 
14
15
 
15
16
  class Table:
@@ -185,7 +186,7 @@ class SYLK:
185
186
  self.cury = int(val)
186
187
 
187
188
  elif ftd == "K":
188
- val = eval(self.escape(val))
189
+ val = literal_eval(self.escape(val))
189
190
  # if type(val) == int:
190
191
  # if self.currenttype == "date":
191
192
  # # value is offset in days from datebase
@@ -208,20 +209,20 @@ class SYLK:
208
209
  self.printformats.append((format, self.knownformats[format]))
209
210
  else:
210
211
  # hack to guess type...
211
- hasY = "y" in format
212
- hasD = "d" in format
213
- hasH = "h" in format
214
- hasZ = "0" in format
215
- hasP = "." in format
216
- if (hasD or hasY) and hasH:
212
+ has_y = "y" in format
213
+ has_d = "d" in format
214
+ has_h = "h" in format
215
+ has_z = "0" in format
216
+ has_p = "." in format
217
+ if (has_d or has_y) and has_h:
217
218
  dtype = "datetime"
218
- elif hasD or hasY:
219
+ elif has_d or has_y:
219
220
  dtype = "date"
220
- elif hasH:
221
+ elif has_h:
221
222
  dtype = "time"
222
- elif hasP and hasZ:
223
+ elif has_p and has_z:
223
224
  dtype = "float"
224
- elif hasZ:
225
+ elif has_z:
225
226
  dtype = "int"
226
227
  else:
227
228
  dtype = "string"
@@ -17,7 +17,7 @@ def parse_transaction_reference(trans_ref: str) -> tuple[str, str, bool]:
17
17
 
18
18
 
19
19
  def assemble_transaction_reference(data: Dict[str, Any]) -> str:
20
- transaction_ref = data["external_identifier2"]
20
+ transaction_ref = data["external_id_alternative"]
21
21
  register_reference = data["register__register_reference"]
22
22
  if data.get("TRANSFER_REGISTER", None):
23
23
  register_reference = data["TRANSFER_REGISTER"]
@@ -29,7 +29,7 @@ def assemble_transaction_reference(data: Dict[str, Any]) -> str:
29
29
 
30
30
 
31
31
  def create_transaction_reference(customer_trade: "Trade") -> str:
32
- transaction_id = customer_trade.external_identifier2
32
+ transaction_id = customer_trade.external_id_alternative
33
33
  outlet_id = customer_trade.register.clearing_reference
34
34
  credit_debit = "C" if customer_trade.initial_shares > 0 else "D"
35
35
 
@@ -10,8 +10,10 @@ def file_name_parse(file_name):
10
10
  dates = re.findall(r"([0-9]{4}-[0-9]{2}-[0-9]{2})", file_name)
11
11
  isin = re.findall(r"\.([a-zA-Z0-9]*)_", file_name)
12
12
 
13
- assert len(dates) == 2, "Not 2 dates found in the filename"
14
- assert len(isin) == 1, "Not exactly 1 isin found in the filename"
13
+ if len(dates) != 2:
14
+ raise ValueError("Not 2 dates found in the filename")
15
+ if len(isin) != 1:
16
+ raise ValueError("Not exactly 1 isin found in the filename")
15
17
 
16
18
  return {
17
19
  "isin": isin[0],
@@ -14,8 +14,8 @@ logger = logging.getLogger("importers.parsers.jp_morgan.strategy")
14
14
  def file_name_parse(file_name):
15
15
  dates = re.findall("([0-9]{8})", file_name)
16
16
 
17
- assert len(dates) == 1, "Not exactly 1 date found in the filename"
18
-
17
+ if len(dates) != 1:
18
+ raise ValueError("Not exactly 1 date found in the filename")
19
19
  return {"valuation_date": datetime.datetime.strptime(dates[0], "%Y%m%d").date()}
20
20
 
21
21
 
@@ -23,7 +23,7 @@ def parse(import_source):
23
23
  data = list()
24
24
  prices = list()
25
25
  df_dict = pd.read_excel(BytesIO(import_source.file.read()), engine="openpyxl", sheet_name=None)
26
- for sheet_name, df in df_dict.items():
26
+ for df in df_dict.values():
27
27
  xx, yy = np.where(df == "Ticker")
28
28
  if len(xx) == 1 and len(yy) == 1:
29
29
  df_info = df.iloc[: xx[0] - 1, :].transpose()
@@ -73,8 +73,8 @@ def parse(import_source):
73
73
  "exchange": exchange,
74
74
  "asset_type": "equity",
75
75
  "currency__key": position["currency__key"],
76
- "initial_currency_fx_rate": round(position["initial_currency_fx_rate"], 6),
77
- "weighting": round(position["weighting"], 6),
76
+ "initial_currency_fx_rate": round(position["initial_currency_fx_rate"], 14),
77
+ "weighting": round(position["weighting"], 8),
78
78
  "initial_price": round(position["initial_price"], 6),
79
79
  "date": valuation_date.strftime("%Y-%m-%d"),
80
80
  }
@@ -20,7 +20,8 @@ product_mapping = {
20
20
 
21
21
  def file_name_parse(file_name):
22
22
  identifier = re.findall("([0-9]{4}).*", file_name)
23
- assert len(identifier) == 1, "Not exactly one identifier was found."
23
+ if len(identifier) != 1:
24
+ raise ValueError("Not exactly 1 identifier found in the filename")
24
25
  return identifier[0]
25
26
 
26
27
 
@@ -7,9 +7,10 @@ import re
7
7
  def file_name_parse(file_name):
8
8
  dates = re.findall(r"([0-9]{4}-[0-9]{2}-[0-9]{2})", file_name)
9
9
  isin = re.findall(r"\.([a-zA-Z0-9]*)_", file_name)
10
-
11
- assert len(dates) == 2, "Not 2 dates found in the filename"
12
- assert len(isin) == 1, "Not exactly 1 isin found in the filename"
10
+ if len(dates) != 2:
11
+ raise ValueError("Not 2 dates found in the filename")
12
+ if len(isin) != 1:
13
+ raise ValueError("Not exactly 1 isin found in the filename")
13
14
 
14
15
  return {
15
16
  "isin": isin[0],
@@ -4,14 +4,14 @@ import pandas as pd
4
4
 
5
5
  from wbportfolio.models import Fees
6
6
 
7
- BASE_MAPPING = {"managementFee": "total_value", "performanceFee": "total_value", "date": "transaction_date"}
7
+ BASE_MAPPING = {"managementFee": "total_value", "performanceFee": "total_value", "date": "fee_date"}
8
8
 
9
9
 
10
10
  def parse(import_source):
11
11
  def _process_df(df, product_isin):
12
12
  df = df.rename(columns=BASE_MAPPING).dropna(how="all", axis=1)
13
13
  df = df.drop(columns=df.columns.difference(BASE_MAPPING.values()))
14
- df["linked_product"] = [{"isin": product_isin}] * df.shape[0]
14
+ df["product"] = [{"isin": product_isin}] * df.shape[0]
15
15
  return df
16
16
 
17
17
  content = json.load(import_source.file)
@@ -0,0 +1,39 @@
1
+ import json
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ from django.utils.dateparse import parse_date
6
+
7
+ BASE_MAPPING = {
8
+ "instrument": "underlying_instrument__name",
9
+ "ric": "underlying_instrument__refinitiv_identifier_code",
10
+ "bbTicker": "underlying_instrument__ticker",
11
+ "isin": "underlying_instrument__isin",
12
+ "assetClass": "underlying_instrument__instrument_type",
13
+ "direction": "transaction_subtype",
14
+ "currency": "currency__key",
15
+ "quantityTraded": "shares",
16
+ "localPrice": "price",
17
+ "fxMultiplier": "currency_fx_rate",
18
+ "tradeDate": "book_date",
19
+ }
20
+
21
+
22
+ def parse(import_source):
23
+ content = json.load(import_source.file)
24
+ data = []
25
+ if (rebalances := content.get("rebalances", None)) and (isin := content.get("isin", None)):
26
+ for rebalance_data in rebalances:
27
+ rebalancing_date = parse_date(rebalance_data["rebalanceDate"])
28
+ df = pd.DataFrame(rebalance_data["items"]).replace([np.inf, -np.inf, np.nan], None)
29
+ df = df.rename(columns=BASE_MAPPING)
30
+ df = df.drop(columns=df.columns.difference(BASE_MAPPING.values()))
31
+ df["underlying_instrument__instrument_type"] = df["underlying_instrument__instrument_type"].str.lower()
32
+ df["transaction_date"] = rebalancing_date.strftime("%Y-%m-%d")
33
+ df["portfolio__isin"] = isin
34
+ df.loc[df["book_date"].isnull(), "book_date"] = df.loc[df["book_date"].isnull(), "transaction_date"]
35
+ df["currency_fx_rate"] = 1 / df["currency_fx_rate"]
36
+ df.loc[df["price"].isnull() & (df["underlying_instrument__instrument_type"] == "cash"), "price"] = 1.0
37
+ df["underlying_instrument__currency__key"] = df["currency__key"]
38
+ data.extend(df.to_dict(orient="records"))
39
+ return {"data": data}
@@ -3,15 +3,15 @@ from typing import Dict
3
3
 
4
4
  import numpy as np
5
5
  import pandas as pd
6
- from wbcore.contrib.io.models import ImportSource
6
+ from slugify import slugify
7
7
 
8
8
  from wbportfolio.models import Trade
9
9
 
10
10
 
11
- def parse_row(obj: Dict, import_source: ImportSource) -> Dict:
11
+ def parse_row(obj: Dict, negate_shares: bool = False) -> Dict:
12
12
  isin = obj["underlying_instrument__isin"]
13
13
  shares = obj["shares"]
14
- if import_source.source.import_parameters.get("negate_shares", False):
14
+ if negate_shares:
15
15
  shares = -1 * shares
16
16
  return {
17
17
  "underlying_instrument": {"isin": isin, "instrument_type": "product"},
@@ -30,6 +30,9 @@ def parse(import_source):
30
30
  xx, yy = np.where(df == "Trade Date")
31
31
  df = df.iloc[xx[0] :, yy[0] :]
32
32
  df = df.rename(columns=df.iloc[0]).drop(df.index[0]).dropna(how="all")
33
+ negate_shares = "net-quantity" in list(
34
+ map(lambda c: slugify(c), df.columns)
35
+ ) # we slugified the column to be more robust
33
36
  df = df.rename(columns=lambda x: x.lower())
34
37
  df = df.rename(
35
38
  columns={
@@ -50,6 +53,5 @@ def parse(import_source):
50
53
  df.loc[df["custodian"].isnull(), "custodian"] = "N/A"
51
54
  data = list()
52
55
  for d in df.to_dict("records"):
53
- data.append(parse_row(d, import_source))
54
-
56
+ data.append(parse_row(d, negate_shares=negate_shares))
55
57
  return {"data": data}
@@ -7,7 +7,8 @@ import xlrd
7
7
  def file_name_parse(file_name):
8
8
  isin = re.findall("([A-Z]{2}[A-Z0-9]{9}[0-9]{1})", file_name)
9
9
 
10
- assert len(isin) == 1, "Not exactly 1 isin found in the filename"
10
+ if len(isin) != 1:
11
+ raise ValueError("Not exactly 1 isin found in the filename")
11
12
 
12
13
  return {"isin": isin[0]}
13
14
 
@@ -40,7 +41,7 @@ def parse(import_source):
40
41
  for row in range(first_row, last_row, 1):
41
42
  initial_shares = round(equity_sheet.cell_value(row, 8), 4)
42
43
  close = round(equity_sheet.cell_value(row, 9), 4)
43
- initial_currency_fx_rate = round(equity_sheet.cell_value(row, 11), 4)
44
+ initial_currency_fx_rate = round(equity_sheet.cell_value(row, 11), 14)
44
45
 
45
46
  bbg = equity_sheet.cell_value(row, 3)
46
47
  ric = equity_sheet.cell_value(row, 2)
@@ -11,7 +11,8 @@ from wbportfolio.import_export.utils import get_file_extension
11
11
  def file_name_parse(file_name):
12
12
  isin = re.findall("([A-Z]{2}[A-Z0-9]{9}[0-9]{1})", file_name)
13
13
 
14
- assert len(isin) == 1, "Not exactly 1 isin found in the filename"
14
+ if len(isin) != 1:
15
+ raise ValueError("Not exactly 1 isin found in the filename")
15
16
 
16
17
  return {
17
18
  "isin": isin[0],
@@ -11,10 +11,9 @@ FIELD_MAP = {
11
11
  "Trade_Date": "book_date",
12
12
  "Value_Date": "value_date",
13
13
  "Price": "price",
14
- "Quantity": "total_value",
15
14
  "Currency": "currency__key",
16
15
  "Portfolio_Identifier": "underlying_instrument__identifier",
17
- "External_Reference": "external_identifier2",
16
+ "External_Reference": "external_id_alternative",
18
17
  "Booking_Comment": "comment",
19
18
  }
20
19
 
@@ -36,8 +35,8 @@ def parse(import_source):
36
35
  data = []
37
36
  if not df.empty:
38
37
  df = df.rename(columns=FIELD_MAP)
38
+ df["shares"] = df["Quantity"] / df["price"]
39
39
  df = df.drop(columns=df.columns.difference(FIELD_MAP.values()))
40
- df["shares"] = df["total_value"] / df["price"]
41
40
  df["transaction_date"] = pd.to_datetime(df["transaction_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
42
41
  df["book_date"] = pd.to_datetime(df["book_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
43
42
  df["value_date"] = pd.to_datetime(df["value_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
@@ -8,7 +8,6 @@ from wbportfolio.models import Trade
8
8
 
9
9
  FIELD_MAP = {
10
10
  "Trade Date": "book_date",
11
- "Volume": "total_value",
12
11
  "Price": "price",
13
12
  "Qty": "shares",
14
13
  "Custodian": "bank",
@@ -6,12 +6,10 @@ import pandas as pd
6
6
  from wbportfolio.models import FeeProductPercentage, Fees, Product
7
7
 
8
8
  FIELD_MAP = {
9
- "Transaction_Date": "transaction_date",
10
- "Trade_Date": "book_date",
11
- "Value_Date": "value_date",
9
+ "Transaction_Date": "fee_date",
12
10
  "Quantity": "total_value",
13
11
  "Currency": "currency__key",
14
- "Portfolio_Identifier": "linked_product",
12
+ "Portfolio_Identifier": "product",
15
13
  "Booking_Comment": "comment",
16
14
  }
17
15
 
@@ -25,21 +23,14 @@ def parse(import_source):
25
23
  data = []
26
24
  if not df.empty:
27
25
  df = df.rename(columns=FIELD_MAP)
28
- df.linked_product = df.linked_product.apply(lambda x: Product.objects.filter(identifier=x).first())
29
- df["transaction_date"] = pd.to_datetime(df["transaction_date"], dayfirst=True)
30
- df["book_date"] = pd.to_datetime(df["book_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
31
- df["value_date"] = pd.to_datetime(df["value_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
32
- df["portfolio"] = df.linked_product.apply(lambda x: x.primary_portfolio if x else None)
26
+ df["product"] = df["product"].apply(lambda x: Product.objects.filter(identifier=x).first())
27
+ df["fee_date"] = pd.to_datetime(df["fee_date"], dayfirst=True)
33
28
  df["base_management_fees"] = df.apply(
34
- lambda row: float(
35
- row["linked_product"].get_fees_percent(row["transaction_date"], FeeProductPercentage.Type.MANAGEMENT)
36
- ),
29
+ lambda row: float(row["product"].get_fees_percent(row["fee_date"], FeeProductPercentage.Type.MANAGEMENT)),
37
30
  axis=1,
38
31
  )
39
32
  df["base_bank_fees"] = df.apply(
40
- lambda row: float(
41
- row["linked_product"].get_fees_percent(row["transaction_date"], FeeProductPercentage.Type.BANK)
42
- ),
33
+ lambda row: float(row["product"].get_fees_percent(row["fee_date"], FeeProductPercentage.Type.BANK)),
43
34
  axis=1,
44
35
  )
45
36
  df.total_value = -df.total_value
@@ -63,8 +54,8 @@ def parse(import_source):
63
54
  df = pd.concat([df_management, df_bank], axis=0)
64
55
  df = df.drop(columns=df.columns.difference(["transaction_subtype", *FIELD_MAP.values()]))
65
56
 
66
- df["linked_product"] = df.linked_product.apply(lambda x: x.id)
67
- df = df.dropna(subset=["linked_product"])
57
+ df["product"] = df["product"].apply(lambda x: x.id)
58
+ df = df.dropna(subset=["product"])
68
59
 
69
60
  for row in df.to_dict("records"):
70
61
  # The fee are sometime aggregated. The aggregate date range information is given in the comment. We therefore try to extract it and duplicate the averaged fees
@@ -72,14 +63,15 @@ def parse(import_source):
72
63
  from_date = datetime.strptime(match.group(1), "%d.%m.%Y").date()
73
64
  to_date = datetime.strptime(match.group(2), "%d.%m.%Y").date()
74
65
  else:
75
- from_date = (row["transaction_date"] - pd.tseries.offsets.BDay(1)).date()
76
- to_date = row["transaction_date"]
66
+ from_date = (row["fee_date"] - pd.tseries.offsets.BDay(1)).date()
67
+ to_date = row["fee_date"]
77
68
 
78
69
  dates = pd.date_range(from_date, to_date, freq="B", inclusive="right")
79
70
  for ts in pd.date_range(from_date, to_date, freq="B", inclusive="right"):
80
71
  row_copy = row.copy()
81
- row_copy["transaction_date"] = ts.strftime("%Y-%m-%d")
72
+ row_copy["fee_date"] = ts.strftime("%Y-%m-%d")
82
73
  row_copy["total_value"] = row["total_value"] / len(dates)
74
+ del row_copy["comment"]
83
75
  data.append(row_copy)
84
76
 
85
77
  return {"data": data}
@@ -5,13 +5,11 @@ from wbportfolio.models import Fees
5
5
  from .utils import get_perf_fee_isin
6
6
 
7
7
  FIELD_MAP = {
8
- "LastPriceDate": "transaction_date",
9
- "EndOfDay": "value_date",
8
+ "Transaction_Date": "fee_date",
10
9
  "AccrValueInstrCcy": "total_value",
11
10
  "FxRate": "currency_fx_rate",
12
11
  "TradingCurrency": "currency__key",
13
- "#ClientName": "linked_product",
14
- "InstrumentName": "comment",
12
+ "#ClientName": "product",
15
13
  }
16
14
 
17
15
  PERF_FEES_INSTRUMENT_ISIN = "CH0040602242"
@@ -27,9 +25,8 @@ def parse(import_source):
27
25
  df = df.drop(columns=df.columns.difference(FIELD_MAP.values()))
28
26
  df.total_value = -df.total_value
29
27
  df["transaction_subtype"] = Fees.Type.MANAGEMENT
30
- df["transaction_date"] = pd.to_datetime(df["transaction_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
31
- df["value_date"] = pd.to_datetime(df["value_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
32
- df = df.dropna(subset=["linked_product"])
33
- df.linked_product = df.linked_product.apply(lambda x: {"identifier": x})
28
+ df["fee_date"] = pd.to_datetime(df["fee_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
29
+ df = df.dropna(subset=["product"])
30
+ df.product = df["product"].apply(lambda x: {"identifier": x})
34
31
  data = df.to_dict("records")
35
32
  return {"data": data}
@@ -10,7 +10,10 @@ def parse(import_source):
10
10
  with suppress(KeyError):
11
11
  series_data = json.loads(import_source.file.read())["payload"]["series"]
12
12
  for series in series_data:
13
- isin = series["item"]["priceIdentifier"]
13
+ try:
14
+ isin = series["key"]["priceIdentifier"]
15
+ except KeyError:
16
+ isin = series["priceIdentifier"]
14
17
  if Product.objects.filter(isin=isin).exists(): # ensure the timeseries contain data for products we handle
15
18
  for point in series["points"]:
16
19
  data.append(
@@ -10,16 +10,16 @@ from wbportfolio.models import Trade
10
10
  fake = Faker()
11
11
 
12
12
 
13
- class TradeProposalTradeResource(FilterModelResource):
13
+ class OrderProposalTradeResource(FilterModelResource):
14
14
  """
15
- Trade Resource class to use to import trade from the trade proposal
15
+ Trade Resource class to use to import trade from the order proposal
16
16
  """
17
17
 
18
18
  DUMMY_FIELD_MAP = {
19
19
  "underlying_instrument__isin": lambda: rstr.xeger("([A-Z]{2}[A-Z0-9]{9}[0-9]{1})"),
20
20
  "underlying_instrument__ticker": "AAA",
21
21
  "underlying_instrument__name": "stock name",
22
- "weighting": 1.0,
22
+ "weighting": 0.015,
23
23
  "shares": 1000.2536,
24
24
  "comment": lambda: fake.sentence(),
25
25
  "order": 1,
@@ -24,7 +24,9 @@ def convert_string_to_number(string):
24
24
  return 0.0
25
25
 
26
26
 
27
- def parse_date(date, formats=[]):
27
+ def parse_date(date, formats: list | None = None):
28
+ if formats is None:
29
+ formats = []
28
30
  if isinstance(date, int) or isinstance(date, float):
29
31
  return xldate_as_datetime(int(date), 0).date()
30
32
  if isinstance(date, str):
@@ -21,7 +21,7 @@ claims AS (
21
21
  JOIN wbcrm_account AS account
22
22
  ON claim.account_id = account.id
23
23
  JOIN wbportfolio_trade AS trade
24
- ON claim.trade_id = trade.transaction_ptr_id
24
+ ON claim.trade_id = trade.id
25
25
  WHERE claim.status = 'APPROVED'
26
26
  ),
27
27
  -- CTE for claims, that fetches them with their shares and price at transaction date
@@ -35,7 +35,7 @@ claims_with_nav AS (
35
35
  trade.marked_as_internal as marked_as_internal
36
36
  FROM wbportfolio_claim AS claims
37
37
  JOIN wbportfolio_trade AS trade
38
- ON claims.trade_id = trade.transaction_ptr_id
38
+ ON claims.trade_id = trade.id
39
39
  LEFT JOIN wbcrm_account AS accounts
40
40
  ON claims.account_id = accounts.id
41
41
  LEFT JOIN LATERAL (
@@ -4,7 +4,7 @@ import numpy as np
4
4
  import pandas as pd
5
5
  from django.db.models import QuerySet
6
6
  from wbfdm.contrib.metric.backends.base import AbstractBackend, Metric
7
- from wbfdm.contrib.metric.exceptions import MetricInvalidParameterException
7
+ from wbfdm.contrib.metric.exceptions import MetricInvalidParameterError
8
8
 
9
9
  from wbportfolio.models import AssetPosition, Portfolio
10
10
 
@@ -62,7 +62,7 @@ class PortfolioMetricBaseBackend(AbstractBackend[Portfolio]):
62
62
  try:
63
63
  return qs.latest("date").date
64
64
  except AssetPosition.DoesNotExist:
65
- raise MetricInvalidParameterException()
65
+ raise MetricInvalidParameterError() from None
66
66
 
67
67
  def get_queryset(self) -> QuerySet[Portfolio]:
68
68
  product_portfolios = super().get_queryset().filter_active_and_tracked()
@@ -45,7 +45,7 @@ class Migration(migrations.Migration):
45
45
  migrations.AddConstraint(
46
46
  model_name="fees",
47
47
  constraint=models.UniqueConstraint(
48
- fields=("linked_product", "fee_date", "transaction_subtype", "calculated"), name="unique_fees"
48
+ fields=("product", "fee_date", "transaction_subtype", "calculated"), name="unique_fees"
49
49
  ),
50
50
  ),
51
51
  ]