wbfdm 2.2.1__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 wbfdm might be problematic. Click here for more details.

Files changed (337) hide show
  1. wbfdm/__init__.py +2 -0
  2. wbfdm/admin/__init__.py +42 -0
  3. wbfdm/admin/classifications.py +39 -0
  4. wbfdm/admin/esg.py +23 -0
  5. wbfdm/admin/exchanges.py +53 -0
  6. wbfdm/admin/instrument_lists.py +23 -0
  7. wbfdm/admin/instrument_prices.py +62 -0
  8. wbfdm/admin/instrument_requests.py +33 -0
  9. wbfdm/admin/instruments.py +117 -0
  10. wbfdm/admin/instruments_relationships.py +25 -0
  11. wbfdm/admin/options.py +101 -0
  12. wbfdm/analysis/__init__.py +2 -0
  13. wbfdm/analysis/esg/__init__.py +0 -0
  14. wbfdm/analysis/esg/enums.py +82 -0
  15. wbfdm/analysis/esg/esg_analysis.py +217 -0
  16. wbfdm/analysis/esg/utils.py +13 -0
  17. wbfdm/analysis/financial_analysis/__init__.py +1 -0
  18. wbfdm/analysis/financial_analysis/financial_metric_analysis.py +88 -0
  19. wbfdm/analysis/financial_analysis/financial_ratio_analysis.py +125 -0
  20. wbfdm/analysis/financial_analysis/financial_statistics_analysis.py +271 -0
  21. wbfdm/analysis/financial_analysis/statement_with_estimates.py +558 -0
  22. wbfdm/analysis/financial_analysis/utils.py +316 -0
  23. wbfdm/analysis/technical_analysis/__init__.py +1 -0
  24. wbfdm/analysis/technical_analysis/technical_analysis.py +138 -0
  25. wbfdm/analysis/technical_analysis/traces.py +165 -0
  26. wbfdm/analysis/utils.py +32 -0
  27. wbfdm/apps.py +14 -0
  28. wbfdm/contrib/__init__.py +0 -0
  29. wbfdm/contrib/dsws/__init__.py +0 -0
  30. wbfdm/contrib/dsws/client.py +285 -0
  31. wbfdm/contrib/internal/__init__.py +0 -0
  32. wbfdm/contrib/internal/dataloaders/__init__.py +0 -0
  33. wbfdm/contrib/internal/dataloaders/market_data.py +87 -0
  34. wbfdm/contrib/metric/__init__.py +0 -0
  35. wbfdm/contrib/metric/admin/__init__.py +2 -0
  36. wbfdm/contrib/metric/admin/instruments.py +12 -0
  37. wbfdm/contrib/metric/admin/metrics.py +43 -0
  38. wbfdm/contrib/metric/apps.py +10 -0
  39. wbfdm/contrib/metric/backends/__init__.py +2 -0
  40. wbfdm/contrib/metric/backends/base.py +159 -0
  41. wbfdm/contrib/metric/backends/performances.py +265 -0
  42. wbfdm/contrib/metric/backends/statistics.py +182 -0
  43. wbfdm/contrib/metric/decorators.py +14 -0
  44. wbfdm/contrib/metric/dispatch.py +23 -0
  45. wbfdm/contrib/metric/dto.py +88 -0
  46. wbfdm/contrib/metric/exceptions.py +6 -0
  47. wbfdm/contrib/metric/factories.py +33 -0
  48. wbfdm/contrib/metric/filters.py +28 -0
  49. wbfdm/contrib/metric/migrations/0001_initial.py +88 -0
  50. wbfdm/contrib/metric/migrations/0002_remove_instrumentmetric_unique_instrument_metric_and_more.py +26 -0
  51. wbfdm/contrib/metric/migrations/__init__.py +0 -0
  52. wbfdm/contrib/metric/models.py +180 -0
  53. wbfdm/contrib/metric/orchestrators.py +94 -0
  54. wbfdm/contrib/metric/registry.py +80 -0
  55. wbfdm/contrib/metric/serializers.py +44 -0
  56. wbfdm/contrib/metric/tasks.py +27 -0
  57. wbfdm/contrib/metric/tests/__init__.py +0 -0
  58. wbfdm/contrib/metric/tests/backends/__init__.py +0 -0
  59. wbfdm/contrib/metric/tests/backends/test_performances.py +152 -0
  60. wbfdm/contrib/metric/tests/backends/test_statistics.py +48 -0
  61. wbfdm/contrib/metric/tests/conftest.py +92 -0
  62. wbfdm/contrib/metric/tests/test_dto.py +73 -0
  63. wbfdm/contrib/metric/tests/test_models.py +72 -0
  64. wbfdm/contrib/metric/tests/test_tasks.py +24 -0
  65. wbfdm/contrib/metric/tests/test_viewsets.py +79 -0
  66. wbfdm/contrib/metric/urls.py +19 -0
  67. wbfdm/contrib/metric/viewsets/__init__.py +1 -0
  68. wbfdm/contrib/metric/viewsets/configs/__init__.py +1 -0
  69. wbfdm/contrib/metric/viewsets/configs/display.py +92 -0
  70. wbfdm/contrib/metric/viewsets/configs/menus.py +11 -0
  71. wbfdm/contrib/metric/viewsets/configs/utils.py +137 -0
  72. wbfdm/contrib/metric/viewsets/mixins.py +245 -0
  73. wbfdm/contrib/metric/viewsets/viewsets.py +40 -0
  74. wbfdm/contrib/msci/__init__.py +0 -0
  75. wbfdm/contrib/msci/client.py +92 -0
  76. wbfdm/contrib/msci/dataloaders/__init__.py +0 -0
  77. wbfdm/contrib/msci/dataloaders/esg.py +87 -0
  78. wbfdm/contrib/msci/dataloaders/esg_controversies.py +81 -0
  79. wbfdm/contrib/msci/sync.py +58 -0
  80. wbfdm/contrib/msci/tests/__init__.py +0 -0
  81. wbfdm/contrib/msci/tests/conftest.py +1 -0
  82. wbfdm/contrib/msci/tests/test_client.py +70 -0
  83. wbfdm/contrib/qa/__init__.py +0 -0
  84. wbfdm/contrib/qa/apps.py +22 -0
  85. wbfdm/contrib/qa/database_routers.py +25 -0
  86. wbfdm/contrib/qa/dataloaders/__init__.py +0 -0
  87. wbfdm/contrib/qa/dataloaders/adjustments.py +56 -0
  88. wbfdm/contrib/qa/dataloaders/corporate_actions.py +59 -0
  89. wbfdm/contrib/qa/dataloaders/financials.py +83 -0
  90. wbfdm/contrib/qa/dataloaders/market_data.py +117 -0
  91. wbfdm/contrib/qa/dataloaders/officers.py +59 -0
  92. wbfdm/contrib/qa/dataloaders/reporting_dates.py +67 -0
  93. wbfdm/contrib/qa/dataloaders/statements.py +267 -0
  94. wbfdm/contrib/qa/tasks.py +0 -0
  95. wbfdm/dataloaders/__init__.py +0 -0
  96. wbfdm/dataloaders/cache.py +129 -0
  97. wbfdm/dataloaders/protocols.py +112 -0
  98. wbfdm/dataloaders/proxies.py +201 -0
  99. wbfdm/dataloaders/types.py +209 -0
  100. wbfdm/dynamic_preferences_registry.py +45 -0
  101. wbfdm/enums.py +657 -0
  102. wbfdm/factories/__init__.py +13 -0
  103. wbfdm/factories/classifications.py +56 -0
  104. wbfdm/factories/controversies.py +27 -0
  105. wbfdm/factories/exchanges.py +21 -0
  106. wbfdm/factories/instrument_list.py +22 -0
  107. wbfdm/factories/instrument_prices.py +79 -0
  108. wbfdm/factories/instruments.py +63 -0
  109. wbfdm/factories/instruments_relationships.py +31 -0
  110. wbfdm/factories/options.py +66 -0
  111. wbfdm/figures/__init__.py +1 -0
  112. wbfdm/figures/financials/__init__.py +1 -0
  113. wbfdm/figures/financials/financial_analysis_charts.py +469 -0
  114. wbfdm/figures/financials/financials_charts.py +711 -0
  115. wbfdm/filters/__init__.py +31 -0
  116. wbfdm/filters/classifications.py +100 -0
  117. wbfdm/filters/exchanges.py +22 -0
  118. wbfdm/filters/financials.py +95 -0
  119. wbfdm/filters/financials_analysis.py +119 -0
  120. wbfdm/filters/instrument_prices.py +112 -0
  121. wbfdm/filters/instruments.py +198 -0
  122. wbfdm/filters/utils.py +44 -0
  123. wbfdm/import_export/__init__.py +0 -0
  124. wbfdm/import_export/backends/__init__.py +0 -0
  125. wbfdm/import_export/backends/cbinsights/__init__.py +2 -0
  126. wbfdm/import_export/backends/cbinsights/deals.py +44 -0
  127. wbfdm/import_export/backends/cbinsights/equities.py +41 -0
  128. wbfdm/import_export/backends/cbinsights/mixin.py +15 -0
  129. wbfdm/import_export/backends/cbinsights/utils/__init__.py +0 -0
  130. wbfdm/import_export/backends/cbinsights/utils/classifications.py +4150 -0
  131. wbfdm/import_export/backends/cbinsights/utils/client.py +217 -0
  132. wbfdm/import_export/backends/refinitiv/__init__.py +5 -0
  133. wbfdm/import_export/backends/refinitiv/daily_fundamental.py +36 -0
  134. wbfdm/import_export/backends/refinitiv/fiscal_period.py +63 -0
  135. wbfdm/import_export/backends/refinitiv/forecast.py +178 -0
  136. wbfdm/import_export/backends/refinitiv/fundamental.py +103 -0
  137. wbfdm/import_export/backends/refinitiv/geographic_segment.py +32 -0
  138. wbfdm/import_export/backends/refinitiv/instrument.py +55 -0
  139. wbfdm/import_export/backends/refinitiv/instrument_price.py +77 -0
  140. wbfdm/import_export/backends/refinitiv/mixin.py +29 -0
  141. wbfdm/import_export/backends/refinitiv/utils/__init__.py +1 -0
  142. wbfdm/import_export/backends/refinitiv/utils/controller.py +182 -0
  143. wbfdm/import_export/handlers/__init__.py +0 -0
  144. wbfdm/import_export/handlers/instrument.py +253 -0
  145. wbfdm/import_export/handlers/instrument_list.py +101 -0
  146. wbfdm/import_export/handlers/instrument_price.py +71 -0
  147. wbfdm/import_export/handlers/option.py +54 -0
  148. wbfdm/import_export/handlers/private_equities.py +49 -0
  149. wbfdm/import_export/parsers/__init__.py +0 -0
  150. wbfdm/import_export/parsers/cbinsights/__init__.py +0 -0
  151. wbfdm/import_export/parsers/cbinsights/deals.py +39 -0
  152. wbfdm/import_export/parsers/cbinsights/equities.py +56 -0
  153. wbfdm/import_export/parsers/cbinsights/fundamentals.py +45 -0
  154. wbfdm/import_export/parsers/refinitiv/__init__.py +0 -0
  155. wbfdm/import_export/parsers/refinitiv/daily_fundamental.py +7 -0
  156. wbfdm/import_export/parsers/refinitiv/forecast.py +7 -0
  157. wbfdm/import_export/parsers/refinitiv/fundamental.py +9 -0
  158. wbfdm/import_export/parsers/refinitiv/geographic_segment.py +7 -0
  159. wbfdm/import_export/parsers/refinitiv/instrument.py +75 -0
  160. wbfdm/import_export/parsers/refinitiv/instrument_price.py +26 -0
  161. wbfdm/import_export/parsers/refinitiv/utils.py +96 -0
  162. wbfdm/import_export/resources/__init__.py +0 -0
  163. wbfdm/import_export/resources/classification.py +23 -0
  164. wbfdm/import_export/resources/instrument_prices.py +33 -0
  165. wbfdm/import_export/resources/instruments.py +176 -0
  166. wbfdm/jinja2.py +7 -0
  167. wbfdm/management/__init__.py +30 -0
  168. wbfdm/menu.py +11 -0
  169. wbfdm/migrations/0001_initial.py +71 -0
  170. wbfdm/migrations/0002_rename_statements_instrumentlookup_financials_and_more.py +144 -0
  171. wbfdm/migrations/0003_instrument_estimate_backend_and_more.py +34 -0
  172. wbfdm/migrations/0004_rename_financials_instrumentlookup_statements_and_more.py +86 -0
  173. wbfdm/migrations/0005_instrument_corporate_action_backend.py +29 -0
  174. wbfdm/migrations/0006_instrument_officer_backend.py +29 -0
  175. wbfdm/migrations/0007_instrument_country_instrument_currency_and_more.py +117 -0
  176. wbfdm/migrations/0008_controversy.py +75 -0
  177. wbfdm/migrations/0009_alter_controversy_flag_alter_controversy_initiated_and_more.py +85 -0
  178. wbfdm/migrations/0010_classification_classificationgroup_deal_exchange_and_more.py +1299 -0
  179. wbfdm/migrations/0011_delete_instrumentlookup_instrument_corporate_actions_and_more.py +169 -0
  180. wbfdm/migrations/0012_instrumentprice_created_instrumentprice_modified.py +564 -0
  181. wbfdm/migrations/0013_instrument_is_investable_universe_and_more.py +199 -0
  182. wbfdm/migrations/0014_alter_controversy_instrument.py +22 -0
  183. wbfdm/migrations/0015_instrument_instrument_investible_index.py +16 -0
  184. wbfdm/migrations/0016_instrumenttype_name_repr.py +18 -0
  185. wbfdm/migrations/0017_instrument_instrument_security_index.py +16 -0
  186. wbfdm/migrations/0018_instrument_instrument_level_index.py +20 -0
  187. wbfdm/migrations/0019_alter_controversy_source.py +17 -0
  188. wbfdm/migrations/0020_optionaggregate_option_and_more.py +249 -0
  189. wbfdm/migrations/0021_delete_instrumentdailystatistics.py +15 -0
  190. wbfdm/migrations/0022_instrument_cusip_option_open_interest_20d_and_more.py +91 -0
  191. wbfdm/migrations/0023_instrument_unique_ric_instrument_unique_rmc_and_more.py +53 -0
  192. wbfdm/migrations/0024_option_open_interest_10d_option_volume_10d_and_more.py +36 -0
  193. wbfdm/migrations/0025_instrument_is_primary_and_more.py +29 -0
  194. wbfdm/migrations/0026_instrument_is_cash_equivalent.py +30 -0
  195. wbfdm/migrations/0027_remove_instrument_unique_ric_and_more.py +100 -0
  196. wbfdm/migrations/__init__.py +0 -0
  197. wbfdm/models/__init__.py +4 -0
  198. wbfdm/models/esg/__init__.py +1 -0
  199. wbfdm/models/esg/controversies.py +81 -0
  200. wbfdm/models/exchanges/__init__.py +1 -0
  201. wbfdm/models/exchanges/exchanges.py +223 -0
  202. wbfdm/models/fields.py +117 -0
  203. wbfdm/models/fk_fields.py +403 -0
  204. wbfdm/models/indicators.py +0 -0
  205. wbfdm/models/instruments/__init__.py +19 -0
  206. wbfdm/models/instruments/classifications.py +265 -0
  207. wbfdm/models/instruments/instrument_lists.py +120 -0
  208. wbfdm/models/instruments/instrument_prices.py +540 -0
  209. wbfdm/models/instruments/instrument_relationships.py +251 -0
  210. wbfdm/models/instruments/instrument_requests.py +196 -0
  211. wbfdm/models/instruments/instruments.py +991 -0
  212. wbfdm/models/instruments/llm/__init__.py +1 -0
  213. wbfdm/models/instruments/llm/create_instrument_news_relationships.py +78 -0
  214. wbfdm/models/instruments/mixin/__init__.py +0 -0
  215. wbfdm/models/instruments/mixin/financials_computed.py +804 -0
  216. wbfdm/models/instruments/mixin/financials_serializer_fields.py +1407 -0
  217. wbfdm/models/instruments/mixin/instruments.py +294 -0
  218. wbfdm/models/instruments/options.py +225 -0
  219. wbfdm/models/instruments/private_equities.py +59 -0
  220. wbfdm/models/instruments/querysets.py +73 -0
  221. wbfdm/models/instruments/utils.py +41 -0
  222. wbfdm/preferences.py +21 -0
  223. wbfdm/serializers/__init__.py +4 -0
  224. wbfdm/serializers/esg.py +36 -0
  225. wbfdm/serializers/exchanges.py +39 -0
  226. wbfdm/serializers/instruments/__init__.py +37 -0
  227. wbfdm/serializers/instruments/classifications.py +139 -0
  228. wbfdm/serializers/instruments/instrument_lists.py +61 -0
  229. wbfdm/serializers/instruments/instrument_prices.py +73 -0
  230. wbfdm/serializers/instruments/instrument_relationships.py +170 -0
  231. wbfdm/serializers/instruments/instrument_requests.py +61 -0
  232. wbfdm/serializers/instruments/instruments.py +274 -0
  233. wbfdm/serializers/instruments/mixins.py +104 -0
  234. wbfdm/serializers/officers.py +20 -0
  235. wbfdm/signals.py +7 -0
  236. wbfdm/sync/__init__.py +0 -0
  237. wbfdm/sync/abstract.py +31 -0
  238. wbfdm/sync/runner.py +22 -0
  239. wbfdm/tasks.py +69 -0
  240. wbfdm/tests/__init__.py +0 -0
  241. wbfdm/tests/analysis/__init__.py +0 -0
  242. wbfdm/tests/analysis/financial_analysis/__init__.py +0 -0
  243. wbfdm/tests/analysis/financial_analysis/test_statement_with_estimates.py +392 -0
  244. wbfdm/tests/analysis/financial_analysis/test_utils.py +322 -0
  245. wbfdm/tests/analysis/test_esg.py +159 -0
  246. wbfdm/tests/conftest.py +92 -0
  247. wbfdm/tests/dataloaders/__init__.py +0 -0
  248. wbfdm/tests/dataloaders/test_cache.py +73 -0
  249. wbfdm/tests/models/__init__.py +0 -0
  250. wbfdm/tests/models/test_classifications.py +99 -0
  251. wbfdm/tests/models/test_exchanges.py +7 -0
  252. wbfdm/tests/models/test_instrument_list.py +117 -0
  253. wbfdm/tests/models/test_instrument_prices.py +306 -0
  254. wbfdm/tests/models/test_instruments.py +202 -0
  255. wbfdm/tests/models/test_merge.py +99 -0
  256. wbfdm/tests/models/test_options.py +69 -0
  257. wbfdm/tests/test_tasks.py +6 -0
  258. wbfdm/tests/tests.py +10 -0
  259. wbfdm/urls.py +222 -0
  260. wbfdm/utils.py +54 -0
  261. wbfdm/viewsets/__init__.py +10 -0
  262. wbfdm/viewsets/configs/__init__.py +5 -0
  263. wbfdm/viewsets/configs/buttons/__init__.py +8 -0
  264. wbfdm/viewsets/configs/buttons/classifications.py +23 -0
  265. wbfdm/viewsets/configs/buttons/exchanges.py +9 -0
  266. wbfdm/viewsets/configs/buttons/instrument_prices.py +49 -0
  267. wbfdm/viewsets/configs/buttons/instruments.py +283 -0
  268. wbfdm/viewsets/configs/display/__init__.py +22 -0
  269. wbfdm/viewsets/configs/display/classifications.py +138 -0
  270. wbfdm/viewsets/configs/display/esg.py +75 -0
  271. wbfdm/viewsets/configs/display/exchanges.py +42 -0
  272. wbfdm/viewsets/configs/display/instrument_lists.py +137 -0
  273. wbfdm/viewsets/configs/display/instrument_prices.py +199 -0
  274. wbfdm/viewsets/configs/display/instrument_requests.py +116 -0
  275. wbfdm/viewsets/configs/display/instruments.py +618 -0
  276. wbfdm/viewsets/configs/display/instruments_relationships.py +65 -0
  277. wbfdm/viewsets/configs/display/monthly_performances.py +72 -0
  278. wbfdm/viewsets/configs/display/officers.py +16 -0
  279. wbfdm/viewsets/configs/display/prices.py +21 -0
  280. wbfdm/viewsets/configs/display/statement_with_estimates.py +101 -0
  281. wbfdm/viewsets/configs/display/statements.py +48 -0
  282. wbfdm/viewsets/configs/endpoints/__init__.py +41 -0
  283. wbfdm/viewsets/configs/endpoints/classifications.py +87 -0
  284. wbfdm/viewsets/configs/endpoints/esg.py +20 -0
  285. wbfdm/viewsets/configs/endpoints/exchanges.py +6 -0
  286. wbfdm/viewsets/configs/endpoints/financials_analysis.py +65 -0
  287. wbfdm/viewsets/configs/endpoints/instrument_lists.py +38 -0
  288. wbfdm/viewsets/configs/endpoints/instrument_prices.py +51 -0
  289. wbfdm/viewsets/configs/endpoints/instrument_requests.py +20 -0
  290. wbfdm/viewsets/configs/endpoints/instruments.py +13 -0
  291. wbfdm/viewsets/configs/endpoints/instruments_relationships.py +31 -0
  292. wbfdm/viewsets/configs/endpoints/statements.py +6 -0
  293. wbfdm/viewsets/configs/menus/__init__.py +9 -0
  294. wbfdm/viewsets/configs/menus/classifications.py +19 -0
  295. wbfdm/viewsets/configs/menus/exchanges.py +10 -0
  296. wbfdm/viewsets/configs/menus/instrument_lists.py +10 -0
  297. wbfdm/viewsets/configs/menus/instruments.py +20 -0
  298. wbfdm/viewsets/configs/menus/instruments_relationships.py +33 -0
  299. wbfdm/viewsets/configs/titles/__init__.py +42 -0
  300. wbfdm/viewsets/configs/titles/classifications.py +79 -0
  301. wbfdm/viewsets/configs/titles/esg.py +11 -0
  302. wbfdm/viewsets/configs/titles/exchanges.py +12 -0
  303. wbfdm/viewsets/configs/titles/financial_ratio_analysis.py +6 -0
  304. wbfdm/viewsets/configs/titles/financials_analysis.py +50 -0
  305. wbfdm/viewsets/configs/titles/instrument_prices.py +50 -0
  306. wbfdm/viewsets/configs/titles/instrument_requests.py +16 -0
  307. wbfdm/viewsets/configs/titles/instruments.py +31 -0
  308. wbfdm/viewsets/configs/titles/instruments_relationships.py +21 -0
  309. wbfdm/viewsets/configs/titles/market_data.py +13 -0
  310. wbfdm/viewsets/configs/titles/prices.py +15 -0
  311. wbfdm/viewsets/configs/titles/statement_with_estimates.py +10 -0
  312. wbfdm/viewsets/esg.py +72 -0
  313. wbfdm/viewsets/exchanges.py +63 -0
  314. wbfdm/viewsets/financial_analysis/__init__.py +3 -0
  315. wbfdm/viewsets/financial_analysis/financial_metric_analysis.py +85 -0
  316. wbfdm/viewsets/financial_analysis/financial_ratio_analysis.py +85 -0
  317. wbfdm/viewsets/financial_analysis/statement_with_estimates.py +145 -0
  318. wbfdm/viewsets/instruments/__init__.py +80 -0
  319. wbfdm/viewsets/instruments/classifications.py +279 -0
  320. wbfdm/viewsets/instruments/financials_analysis.py +614 -0
  321. wbfdm/viewsets/instruments/instrument_lists.py +77 -0
  322. wbfdm/viewsets/instruments/instrument_prices.py +542 -0
  323. wbfdm/viewsets/instruments/instrument_requests.py +51 -0
  324. wbfdm/viewsets/instruments/instruments.py +106 -0
  325. wbfdm/viewsets/instruments/instruments_relationships.py +235 -0
  326. wbfdm/viewsets/instruments/utils.py +27 -0
  327. wbfdm/viewsets/market_data.py +172 -0
  328. wbfdm/viewsets/mixins.py +9 -0
  329. wbfdm/viewsets/officers.py +27 -0
  330. wbfdm/viewsets/prices.py +62 -0
  331. wbfdm/viewsets/statements/__init__.py +1 -0
  332. wbfdm/viewsets/statements/statements.py +100 -0
  333. wbfdm/viewsets/technical_analysis/__init__.py +1 -0
  334. wbfdm/viewsets/technical_analysis/monthly_performances.py +93 -0
  335. wbfdm-2.2.1.dist-info/METADATA +15 -0
  336. wbfdm-2.2.1.dist-info/RECORD +337 -0
  337. wbfdm-2.2.1.dist-info/WHEEL +5 -0
@@ -0,0 +1,294 @@
1
+ import calendar
2
+ import math
3
+ from collections import defaultdict
4
+ from contextlib import suppress
5
+ from datetime import date, timedelta
6
+ from decimal import Decimal
7
+ from typing import Dict, Optional
8
+
9
+ import numpy as np
10
+ import pandas as pd
11
+ from pandas.tseries.offsets import BDay
12
+ from wbcore.contrib.currency.models import CurrencyFXRates
13
+ from wbfdm.analysis.financial_analysis.financial_statistics_analysis import (
14
+ FinancialStatistics,
15
+ )
16
+ from wbfdm.backends.dto import PriceDTO
17
+ from wbfdm.enums import MarketData
18
+ from wbfdm.models.instruments.instrument_prices import InstrumentPrice
19
+
20
+
21
+ class InstrumentPMSMixin:
22
+ def get_prices_df_with_calculated(self, market_data: MarketData = MarketData.CLOSE, **kwargs) -> pd.DataFrame:
23
+ prices = pd.DataFrame(self.get_prices(values=[market_data], **kwargs)).rename(
24
+ columns={"valuation_date": "date"}
25
+ )
26
+ if "calculated" not in prices.columns:
27
+ prices["calculated"] = False
28
+
29
+ if not prices.empty and market_data.value in prices.columns:
30
+ prices = prices[[market_data.value, "calculated", "date"]].sort_values(by="calculated")
31
+ prices = prices.groupby("date").first()
32
+ prices.index = pd.to_datetime(prices.index)
33
+ prices = prices.replace([np.inf, -np.inf, np.nan], None)
34
+ return prices.sort_index()
35
+ return pd.DataFrame()
36
+
37
+ def get_prices_df(self, market_data: MarketData = MarketData.CLOSE, **kwargs) -> pd.Series:
38
+ prices = self.get_prices_df_with_calculated(market_data=market_data, **kwargs)
39
+
40
+ if market_data.value in prices.columns:
41
+ return prices[market_data.value].astype(float)
42
+ return pd.Series(dtype="float64")
43
+
44
+ def get_price(self, val_date: date, price_date_timedelta: int = 3) -> Decimal:
45
+ if self.is_cash:
46
+ return Decimal(1)
47
+ return self._build_dto(val_date, price_date_timedelta=price_date_timedelta).close
48
+
49
+ def _build_dto(self, val_date: date, price_date_timedelta: int = 3) -> PriceDTO: # for backward compatibility
50
+ try:
51
+ price = self.valuations.get(date=val_date)
52
+ close = float(price.net_value)
53
+ return PriceDTO(
54
+ pk=price.id,
55
+ instrument=self.id,
56
+ date=val_date,
57
+ open=close,
58
+ close=close,
59
+ high=close,
60
+ low=close,
61
+ volume=close,
62
+ market_capitalization=price.market_capitalization,
63
+ outstanding_shares=float(price.outstanding_shares) if price.outstanding_shares else None,
64
+ )
65
+ except InstrumentPrice.DoesNotExist:
66
+ prices = sorted(
67
+ self.get_prices(from_date=(val_date - BDay(price_date_timedelta)).date(), to_date=val_date),
68
+ key=lambda x: x["valuation_date"],
69
+ reverse=True,
70
+ )
71
+ if (
72
+ prices
73
+ and (p := prices[0])
74
+ and (close := p.get("close", None))
75
+ and (p_date := p.get("valuation_date", None))
76
+ ):
77
+ return PriceDTO(
78
+ pk=p["id"],
79
+ instrument=self.id,
80
+ date=p_date,
81
+ open=p.get("open", None),
82
+ close=close,
83
+ high=p.get("high", None),
84
+ low=p.get("low", None),
85
+ volume=p.get("volume", None),
86
+ market_capitalization=p.get("market_capitalization", None),
87
+ outstanding_shares=p.get("outstanding_shares", None),
88
+ )
89
+ raise ValueError("Not price was found")
90
+
91
+ # Instrument Prices Utility Functions
92
+ @classmethod
93
+ def _compute_performance(cls, prices: pd.Series, freq: str = "BME") -> pd.DataFrame:
94
+ if prices.empty:
95
+ raise ValueError("Price series cannot be empty")
96
+ performance = FinancialStatistics(prices).compute_performance(freq=freq) # For backward compatibility
97
+ return pd.concat([prices, performance], axis=1).dropna(
98
+ how="any", subset=["performance"]
99
+ ) # For backward compatibility
100
+
101
+ @classmethod
102
+ def extract_monthly_performance_df(cls, prices: pd.Series) -> pd.DataFrame:
103
+ if prices.empty:
104
+ raise ValueError("Price series cannot be empty")
105
+ performance = FinancialStatistics(prices).extract_monthly_performance_df() # For backward compatibility
106
+ df = pd.concat([performance], axis=1, keys=["performance"])
107
+ df["year"] = df.index.year
108
+ df["month"] = df.index.month
109
+ return df.dropna(how="any", subset=["performance"]).reset_index(drop=True) # For backward compatibility
110
+
111
+ @classmethod
112
+ def extract_annual_performance_df(cls, prices: pd.Series) -> pd.DataFrame:
113
+ if prices.empty:
114
+ raise ValueError("Price series cannot be empty")
115
+ performance = FinancialStatistics(prices).extract_annual_performance_df() # For backward compatibility
116
+ df = pd.concat([performance], axis=1, keys=["performance"])
117
+ df["year"] = df.index.year
118
+ return df.dropna(how="any", subset=["performance"]).reset_index(drop=True) # For backward compatibility
119
+
120
+ @classmethod
121
+ def extract_inception_performance_df(cls, prices: pd.Series) -> float:
122
+ if prices.empty:
123
+ raise ValueError("Price series cannot be empty")
124
+ return FinancialStatistics(prices).extract_inception_performance_df() # For backward compatibility
125
+
126
+ @classmethod
127
+ def extract_daily_performance_df(cls, prices: pd.Series) -> pd.DataFrame:
128
+ if prices.empty:
129
+ raise ValueError("Price series cannot be empty")
130
+ performance = FinancialStatistics(prices).extract_daily_performance_df()
131
+ df = pd.concat([performance], axis=1, keys=["performance"])
132
+ df["year"] = df.index.year
133
+ return df.dropna(how="any", subset=["performance"]) # For backward compatibility
134
+
135
+ def get_monthly_return_summary(
136
+ self, start: Optional[date] = None, end: Optional[date] = None, **kwargs
137
+ ) -> pd.DataFrame:
138
+ if not (prices := self.get_prices_df_with_calculated(from_date=start, to_date=end, **kwargs)).empty:
139
+ calculated_mask = prices[["calculated"]].copy().asfreq("BME", method="ffill")
140
+ calculated_mask["year"] = calculated_mask.index.year
141
+ calculated_mask["month"] = calculated_mask.index.month
142
+ calculated_mask = (
143
+ calculated_mask[["year", "month", "calculated"]]
144
+ .reset_index(drop=True)
145
+ .groupby(["year", "month"])
146
+ .any()
147
+ )
148
+ monthly_perfs = self.extract_monthly_performance_df(prices.close)
149
+ annual_perfs = self.extract_annual_performance_df(prices.close)
150
+ annual_perfs["month"] = "annual"
151
+ perfs = pd.concat([monthly_perfs, annual_perfs], axis=0, ignore_index=True)
152
+
153
+ return perfs.replace([np.inf, -np.inf, np.nan], None), calculated_mask
154
+ return pd.DataFrame(), pd.DataFrame()
155
+
156
+ def get_monthly_return_summary_dict(
157
+ self, start: Optional[date] = None, end: Optional[date] = None, **kwargs
158
+ ) -> Dict:
159
+ perfs, calculated_mask = self.get_monthly_return_summary(start, end, **kwargs)
160
+ res = defaultdict(dict)
161
+ if not perfs.empty:
162
+ for year, df in perfs.sort_values(by="year", ascending=False).groupby("year", sort=False):
163
+ df = df.set_index("month")
164
+ for i in range(1, 13):
165
+ try:
166
+ perf = float(df.loc[i, "performance"])
167
+ except (IndexError, KeyError):
168
+ perf = None
169
+ try:
170
+ calculated = bool(calculated_mask.loc[(year, i), "calculated"])
171
+ except (IndexError, KeyError):
172
+ calculated = False
173
+ res[year][calendar.month_abbr[i]] = {"performance": perf, "calculated": calculated}
174
+ try:
175
+ res[year]["annual"] = {
176
+ "performance": float(df.loc["annual", "performance"]),
177
+ "calculated": bool(calculated_mask.loc[(year, slice(None)), "calculated"].any()),
178
+ }
179
+ except (IndexError, KeyError):
180
+ res[year]["annual"] = {"performance": None, "calculated": False}
181
+
182
+ return dict(res)
183
+
184
+ def build_benchmark_df(self, end_date: Optional[date] = None, **kwargs) -> pd.Series:
185
+ df = pd.Series(dtype="float64")
186
+ prices_df = self.get_prices_df(to_date=end_date).rename("net_value")
187
+ if not prices_df.empty and (benchmark := self.primary_benchmark) and self.primary_risk_instrument:
188
+ start_date = prices_df.index[0]
189
+ end_date = prices_df.index[-1]
190
+ kwargs = {"from_date": start_date, "to_date": end_date}
191
+ # Get and prepare Risk free rate dataframe from stainly
192
+ risk_df = self.primary_risk_instrument.get_prices_df(**kwargs).rename("rate")
193
+
194
+ benchmark_df = benchmark.get_prices_df(**kwargs).rename("benchmark_net_value")
195
+ # Prepare final dataframe, fill the NAN with backward index
196
+ df = pd.concat([risk_df, benchmark_df, prices_df], axis=1).astype("float64").ffill(axis=0).sort_index()
197
+ df.index = pd.to_datetime(df.index)
198
+
199
+ return df
200
+
201
+ def save_prices_in_db(self, from_date: date, to_date: date, clear: bool = False):
202
+ df = pd.DataFrame(
203
+ self.__class__.objects.filter(id=self.id).dl.market_data(
204
+ from_date=from_date
205
+ - timedelta(
206
+ days=90
207
+ ), # we make sure to at least import the last 80 days to be sure to be able to compute the volume 50d
208
+ to_date=to_date,
209
+ values=[MarketData.CLOSE, MarketData.VOLUME, MarketData.MARKET_CAPITALIZATION],
210
+ )
211
+ )
212
+ if not df.empty:
213
+ df["calculated"] = False
214
+ df = df.set_index("valuation_date").sort_index()
215
+
216
+ # # if market cap is not found, maybe we have better chance on the primary exhange
217
+ if not df.market_capitalization.notnull().any() and self.parent and (company := self.parent.get_root()):
218
+ with suppress(KeyError):
219
+ df["market_capitalization"] = pd.DataFrame(
220
+ self.__class__.objects.filter(id=company.id).dl.market_data(
221
+ from_date=from_date,
222
+ to_date=to_date,
223
+ values=[MarketData.MARKET_CAPITALIZATION],
224
+ )
225
+ ).set_index("valuation_date")["market_capitalization"]
226
+
227
+ ts = pd.date_range(df.index.min(), df.index.max(), freq="B")
228
+ # fill forward missing data
229
+ df = df.reindex(ts)
230
+ df[["close", "market_capitalization"]] = df[["close", "market_capitalization"]].ffill()
231
+ df.volume = df.volume.fillna(0)
232
+ df.calculated = df.calculated.astype(bool).fillna(
233
+ True
234
+ ) # we do not ffill calculated but set the to True to mark them as "estimated"/"not real"
235
+ df["volume_50d"] = df["volume"].rolling(50).mean()
236
+ df = df[df.index.date >= from_date] # we import only from the requested from_date
237
+ df = df.reset_index().dropna(subset=["index", "close"])
238
+ df = df.replace([np.inf, -np.inf, np.nan], None)
239
+ update_objs = []
240
+ create_objs = []
241
+ for row in df.to_dict("records"):
242
+ if (_date := row.get("index")) and (close := row.get("close", None)):
243
+ # we make sure data does not exist 10 digits (for db constraint)
244
+ if int(math.log10(close)) + 1 < 10:
245
+ try:
246
+ p = InstrumentPrice.objects.get(instrument=self, date=_date)
247
+
248
+ # update only if net value is different with existing instrument price
249
+ if (
250
+ round(p.net_value, 2) != round(close, 2)
251
+ or (p.market_capitalization != row.get("market_capitalization"))
252
+ or (p.volume != row.get("volume"))
253
+ or (p.calculated != row.get("calculated"))
254
+ or clear
255
+ ):
256
+ p.net_value = close
257
+ p.gross_value = close
258
+ p.calculated = row["calculated"]
259
+ p.volume = row.get("volume", p.volume)
260
+ p.volume_50d = row.get("volume_50d", p.volume_50d)
261
+ p.market_capitalization = row.get("market_capitalization", p.market_capitalization)
262
+ p.market_capitalization_consolidated = p.market_capitalization
263
+ p.set_dynamic_field(False)
264
+ update_objs.append(p)
265
+ except InstrumentPrice.DoesNotExist:
266
+ with suppress(CurrencyFXRates.DoesNotExist):
267
+ p = InstrumentPrice(
268
+ currency_fx_rate_to_usd=CurrencyFXRates.objects.get( # we need to get the currency rate because we bulk create the object, and thus save is not called
269
+ date=_date, currency=self.currency
270
+ ),
271
+ instrument=self,
272
+ date=_date,
273
+ calculated=row["calculated"],
274
+ net_value=close,
275
+ gross_value=close,
276
+ volume=row.get("volume", None),
277
+ market_capitalization=row.get("market_capitalization", None),
278
+ volume_50d=row.get("volume_50d", None),
279
+ )
280
+ p.set_dynamic_field(False)
281
+ create_objs.append(p)
282
+ InstrumentPrice.objects.bulk_update(
283
+ update_objs,
284
+ fields=[
285
+ "net_value",
286
+ "gross_value",
287
+ "volume",
288
+ "market_capitalization",
289
+ "market_capitalization_consolidated",
290
+ "calculated",
291
+ "volume_50d",
292
+ ],
293
+ )
294
+ InstrumentPrice.objects.bulk_create(create_objs, ignore_conflicts=True)
@@ -0,0 +1,225 @@
1
+ from django.db import models
2
+ from wbcore.contrib.io.mixins import ImportMixin
3
+ from wbfdm.import_export.handlers.option import (
4
+ OptionAggregateImportHandler,
5
+ OptionImportHandler,
6
+ )
7
+
8
+
9
+ class BaseOptionAbstractModel(ImportMixin, models.Model):
10
+ class Type(models.TextChoices):
11
+ PUT = "PUT", "Put"
12
+ CALL = "CALL", "Call"
13
+
14
+ type = models.CharField(choices=Type.choices, max_length=6)
15
+ date = models.DateField()
16
+ instrument = models.ForeignKey(
17
+ to="wbfdm.Instrument",
18
+ related_name="%(class)s",
19
+ on_delete=models.PROTECT,
20
+ limit_choices_to=models.Q(children__isnull=True),
21
+ verbose_name="Instrument",
22
+ )
23
+
24
+ # Option Metrics
25
+
26
+ volume = models.FloatField(
27
+ null=True,
28
+ blank=True,
29
+ verbose_name="Volume",
30
+ help_text="Option Volume",
31
+ )
32
+
33
+ volume_5d = models.FloatField(
34
+ null=True,
35
+ blank=True,
36
+ verbose_name="Volume 5D",
37
+ help_text="Option Volume (5D)",
38
+ )
39
+
40
+ volume_10d = models.FloatField(
41
+ null=True,
42
+ blank=True,
43
+ verbose_name="Volume 10D",
44
+ help_text="Option Volume (10D)",
45
+ )
46
+
47
+ volume_20d = models.FloatField(
48
+ null=True,
49
+ blank=True,
50
+ verbose_name="Volume 20D",
51
+ help_text="Option Volume (20D)",
52
+ )
53
+
54
+ volume_50d = models.FloatField(
55
+ null=True,
56
+ blank=True,
57
+ verbose_name="Volume 50D",
58
+ help_text="Option Volume (50D)",
59
+ )
60
+
61
+ open_interest = models.FloatField(
62
+ null=True,
63
+ blank=True,
64
+ verbose_name="Open Interest",
65
+ help_text="Open Interest",
66
+ )
67
+ open_interest_5d = models.FloatField(
68
+ null=True,
69
+ blank=True,
70
+ verbose_name="Open Interest 5D",
71
+ help_text="Option Open Interest (5D)",
72
+ )
73
+ open_interest_10d = models.FloatField(
74
+ null=True,
75
+ blank=True,
76
+ verbose_name="Open Interest 10D",
77
+ help_text="Option Open Interest (10D)",
78
+ )
79
+ open_interest_20d = models.FloatField(
80
+ null=True,
81
+ blank=True,
82
+ verbose_name="Open Interest 20D",
83
+ help_text="Option Open Interest (20D)",
84
+ )
85
+ open_interest_50d = models.FloatField(
86
+ null=True,
87
+ blank=True,
88
+ verbose_name="Open Interest 50D",
89
+ help_text="Option Open Interest (50D)",
90
+ )
91
+
92
+ volatility = models.FloatField(
93
+ null=True,
94
+ blank=True,
95
+ verbose_name="Volatility",
96
+ )
97
+
98
+ volatility_30d = models.FloatField(
99
+ null=True,
100
+ blank=True,
101
+ verbose_name="Volatility (30D)",
102
+ )
103
+
104
+ volatility_60d = models.FloatField(
105
+ null=True,
106
+ blank=True,
107
+ verbose_name="Volatility (60D)",
108
+ )
109
+
110
+ volatility_90d = models.FloatField(
111
+ null=True,
112
+ blank=True,
113
+ verbose_name="Volatility (90D)",
114
+ )
115
+
116
+ class Meta:
117
+ abstract = True
118
+
119
+
120
+ class OptionAggregate(BaseOptionAbstractModel):
121
+ import_export_handler_class = OptionAggregateImportHandler
122
+
123
+ class Meta:
124
+ verbose_name = "Option Aggregate"
125
+ verbose_name_plural = "Options Aggregates"
126
+ constraints = [
127
+ models.CheckConstraint(
128
+ check=~models.Q(date__week_day__in=[1, 7]),
129
+ name="%(app_label)s_%(class)s_weekday_constraint",
130
+ ),
131
+ models.UniqueConstraint(fields=["instrument", "date", "type"], name="unique_option_aggregate"),
132
+ ]
133
+ indexes = [
134
+ models.Index(
135
+ fields=["instrument", "date", "type"],
136
+ ),
137
+ models.Index(
138
+ fields=["instrument", "date"],
139
+ ),
140
+ models.Index(
141
+ fields=["type"],
142
+ ),
143
+ ]
144
+
145
+
146
+ class Option(BaseOptionAbstractModel):
147
+ import_export_handler_class = OptionImportHandler
148
+
149
+ contract_identifier = models.CharField(verbose_name="Contract Name", max_length=255)
150
+ strike = models.FloatField(verbose_name="Strike")
151
+ expiration_date = models.DateField(verbose_name="Expiration Date")
152
+
153
+ # EOD data
154
+ open = models.FloatField(null=True, blank=True, verbose_name="Open")
155
+ high = models.FloatField(null=True, blank=True, verbose_name="High")
156
+ low = models.FloatField(null=True, blank=True, verbose_name="Low")
157
+ close = models.FloatField(null=True, blank=True, verbose_name="Close")
158
+ bid = models.FloatField(null=True, blank=True, verbose_name="Bid")
159
+ ask = models.FloatField(null=True, blank=True, verbose_name="Ask")
160
+ vwap = models.FloatField(
161
+ null=True,
162
+ blank=True,
163
+ verbose_name="Open Interest",
164
+ help_text="Open Interest",
165
+ )
166
+
167
+ # Option risk metrics:
168
+
169
+ risk_delta = models.FloatField(
170
+ null=True, blank=True, verbose_name="Delta", help_text='Option risk metrics "Delta"'
171
+ )
172
+ risk_theta = models.FloatField(
173
+ null=True, blank=True, verbose_name="Theta", help_text='Option risk metrics "Theta"'
174
+ )
175
+ risk_gamma = models.FloatField(
176
+ null=True, blank=True, verbose_name="Gamma", help_text='Option risk metrics "Gamma"'
177
+ )
178
+ risk_vega = models.FloatField(null=True, blank=True, verbose_name="Vega", help_text='Option risk metrics "Vega"')
179
+ risk_rho = models.FloatField(null=True, blank=True, verbose_name="Rho", help_text='Option risk metrics "Rho"')
180
+ risk_lambda = models.FloatField(
181
+ null=True, blank=True, verbose_name="Lambda", help_text='Option risk metrics "Lambda"'
182
+ )
183
+ risk_epsilon = models.FloatField(
184
+ null=True, blank=True, verbose_name="Epsilon", help_text='Option risk metrics "Epsilon"'
185
+ )
186
+ risk_vomma = models.FloatField(
187
+ null=True, blank=True, verbose_name="Vomma", help_text='Option risk metrics "Vomma"'
188
+ )
189
+ risk_vera = models.FloatField(null=True, blank=True, verbose_name="Vera", help_text='Option risk metrics "Vera"')
190
+ risk_speed = models.FloatField(
191
+ null=True, blank=True, verbose_name="Speed", help_text='Option risk metrics "Speed"'
192
+ )
193
+ risk_zomma = models.FloatField(
194
+ null=True, blank=True, verbose_name="Zomma", help_text='Option risk metrics "Zomma"'
195
+ )
196
+ risk_color = models.FloatField(
197
+ null=True, blank=True, verbose_name="Color", help_text='Option risk metrics "Color"'
198
+ )
199
+ risk_ultima = models.FloatField(
200
+ null=True, blank=True, verbose_name="Ultima", help_text='Option risk metrics "Ultima"'
201
+ )
202
+
203
+ class Meta:
204
+ verbose_name = "Option"
205
+ verbose_name_plural = "Options"
206
+ constraints = [
207
+ models.CheckConstraint(
208
+ check=~models.Q(date__week_day__in=[1, 7]),
209
+ name="%(app_label)s_%(class)s_weekday_constraint",
210
+ ),
211
+ models.UniqueConstraint(
212
+ fields=["instrument", "contract_identifier", "date", "type"], name="unique_option"
213
+ ),
214
+ ]
215
+ indexes = [
216
+ models.Index(
217
+ fields=["instrument", "date", "type"],
218
+ ),
219
+ models.Index(
220
+ fields=["instrument", "date"],
221
+ ),
222
+ models.Index(
223
+ fields=["type"],
224
+ ),
225
+ ]
@@ -0,0 +1,59 @@
1
+ from django.contrib.postgres.fields import ArrayField
2
+ from django.db import models
3
+ from wbcore.contrib.io.mixins import ImportMixin
4
+ from wbfdm.import_export.handlers.private_equities import DealImportHandler
5
+
6
+
7
+ class Deal(ImportMixin, models.Model):
8
+ import_export_handler_class = DealImportHandler
9
+
10
+ class Types(models.TextChoices):
11
+ DEAL = "DEAL", "Deal"
12
+ FUNDING = "FUNDING", "Funding"
13
+ INVESTMENT = "INVESTMENT", "Investment"
14
+ PORTFOLIO_EXIT = "PORTFOLIO_EXIT", "Portfolio Exit"
15
+
16
+ type = models.CharField(
17
+ default=Types.DEAL, choices=Types.choices, max_length=14, verbose_name="Type", help_text="The deal type"
18
+ )
19
+ external_id = models.CharField(max_length=64, blank=True, null=True)
20
+ date = models.DateField()
21
+ equity = models.ForeignKey(
22
+ "wbfdm.Instrument",
23
+ related_name="deals",
24
+ on_delete=models.CASCADE,
25
+ limit_choices_to=models.Q(instrument_type__key="equity"),
26
+ )
27
+ transaction_amount = models.FloatField(help_text="Deal Size (in millions")
28
+ investors = models.ManyToManyField(
29
+ "wbfdm.Instrument",
30
+ related_name="invested_deals",
31
+ blank=True,
32
+ verbose_name="Investors",
33
+ help_text="Investors",
34
+ )
35
+
36
+ funding_round = models.CharField(max_length=128, verbose_name="Funding Round")
37
+ funding_round_category = models.CharField(max_length=128, verbose_name="Funding Round Category")
38
+
39
+ valuation = models.FloatField(
40
+ verbose_name="Valuaton",
41
+ blank=True,
42
+ null=True,
43
+ help_text="Valuation of the funded organization after this transaction (in Millions USD).",
44
+ )
45
+ valuation_estimated = models.BooleanField(
46
+ default=False, verbose_name="Is valuation estimated", help_text="True if the valuation is an estimate"
47
+ )
48
+ valuation_source_type = models.CharField(
49
+ max_length=24,
50
+ blank=True,
51
+ null=True,
52
+ help_text="The source type of the valuation",
53
+ )
54
+ valuation_media_mention_source_urls = ArrayField(
55
+ models.URLField(),
56
+ blank=True,
57
+ null=True,
58
+ help_text="List of URLs used to source the valuation for the Media Mentions source type.",
59
+ )
@@ -0,0 +1,73 @@
1
+ from django.db.models import (
2
+ AutoField,
3
+ Exists,
4
+ ExpressionWrapper,
5
+ F,
6
+ OuterRef,
7
+ QuerySet,
8
+ Subquery,
9
+ )
10
+ from django.db.models.functions import Coalesce
11
+
12
+
13
+ class InstrumentQuerySet(QuerySet):
14
+ def annotate_classification_for_group(
15
+ self, classification_group, classification_height: int = 0, **kwargs
16
+ ) -> QuerySet:
17
+ return classification_group.annotate_queryset(
18
+ self, classification_height, "", annotation_label="ancestor_classifications", **kwargs
19
+ )
20
+
21
+ def annotate_base_data(self):
22
+ base_qs = InstrumentQuerySet(self.model, using=self._db)
23
+ return self.annotate(
24
+ is_investable=~Exists(base_qs.filter(parent=OuterRef("pk"))),
25
+ root=Subquery(base_qs.filter(tree_id=OuterRef("tree_id"), level=0).values("id")[:1]),
26
+ primary_security=ExpressionWrapper(
27
+ Coalesce(
28
+ Subquery(
29
+ base_qs.filter(
30
+ parent=OuterRef("pk"),
31
+ is_primary=True,
32
+ is_security=True,
33
+ ).values(
34
+ "id"
35
+ )[:1]
36
+ ),
37
+ F("id"),
38
+ ),
39
+ output_field=AutoField(),
40
+ ),
41
+ primary_quote=ExpressionWrapper(
42
+ Coalesce(
43
+ Subquery(
44
+ base_qs.filter(
45
+ parent=OuterRef("primary_security"),
46
+ is_primary=True,
47
+ ).values(
48
+ "id"
49
+ )[:1]
50
+ ),
51
+ F("primary_security"),
52
+ ),
53
+ output_field=AutoField(),
54
+ ),
55
+ )
56
+
57
+ def annotate_all(self):
58
+ return self.annotate_base_data()
59
+
60
+ @property
61
+ def dl(self):
62
+ """Provides access to the dataloader proxy for the entities in the QuerySet.
63
+
64
+ This method allows for easy retrieval of the DataloaderProxy instance
65
+ associated with the QuerySet. It enables the utilization of dataloader
66
+ functionalities directly from the QuerySet, facilitating data fetching and
67
+ processing tasks.
68
+
69
+ Returns:
70
+ DataloaderProxy: An instance of DataloaderProxy associated with the
71
+ entities in the QuerySet.
72
+ """
73
+ return self.model.dl_proxy(self)