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,88 @@
1
+ # Generated by Django 5.0.6 on 2024-05-27 08:56
2
+
3
+ import django.core.serializers.json
4
+ import django.db.models.deletion
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+ initial = True
10
+
11
+ dependencies = [
12
+ ("contenttypes", "0002_remove_content_type_name"),
13
+ ("wbfdm", "0021_delete_instrumentdailystatistics"),
14
+ ]
15
+
16
+ operations = [
17
+ migrations.CreateModel(
18
+ name="InstrumentMetric",
19
+ fields=[
20
+ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
21
+ ("basket_id", models.PositiveIntegerField()),
22
+ ("basket_repr", models.CharField(max_length=256)),
23
+ (
24
+ "date",
25
+ models.DateField(
26
+ blank=True,
27
+ help_text="If date is null, the metric is considered static",
28
+ null=True,
29
+ verbose_name="Metric Date",
30
+ ),
31
+ ),
32
+ ("key", models.CharField(max_length=255, verbose_name="Metric Key")),
33
+ (
34
+ "metrics",
35
+ models.JSONField(
36
+ default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder, verbose_name="Metrics"
37
+ ),
38
+ ),
39
+ (
40
+ "basket_content_type",
41
+ models.ForeignKey(
42
+ on_delete=django.db.models.deletion.CASCADE,
43
+ related_name="instrument_metrics",
44
+ to="contenttypes.contenttype",
45
+ ),
46
+ ),
47
+ (
48
+ "instrument",
49
+ models.ForeignKey(
50
+ blank=True,
51
+ help_text="Instrument where this metric belongs to. If null, the metric is applicable to all instruments related to the basket",
52
+ null=True,
53
+ on_delete=django.db.models.deletion.CASCADE,
54
+ related_name="metrics",
55
+ to="wbfdm.instrument",
56
+ ),
57
+ ),
58
+ (
59
+ "parent_metric",
60
+ models.ForeignKey(
61
+ blank=True,
62
+ null=True,
63
+ on_delete=django.db.models.deletion.SET_NULL,
64
+ related_name="dependent_metrics",
65
+ to="metric.instrumentmetric",
66
+ verbose_name="Parent Metric",
67
+ ),
68
+ ),
69
+ ],
70
+ options={
71
+ "verbose_name": "Instrument Metric",
72
+ "verbose_name_plural": "Instrument Metrics",
73
+ "indexes": [
74
+ models.Index(
75
+ fields=["basket_content_type", "basket_id", "key", "date"],
76
+ name="metric_inst_basket__30dcda_idx",
77
+ )
78
+ ],
79
+ },
80
+ ),
81
+ migrations.AddConstraint(
82
+ model_name="instrumentmetric",
83
+ constraint=models.UniqueConstraint(
84
+ fields=("basket_content_type", "basket_id", "instrument", "date", "key"),
85
+ name="unique_instrument_metric",
86
+ ),
87
+ ),
88
+ ]
@@ -0,0 +1,26 @@
1
+ # Generated by Django 5.0.9 on 2024-11-06 13:20
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("contenttypes", "0002_remove_content_type_name"),
9
+ ("metric", "0001_initial"),
10
+ ("wbfdm", "0027_remove_instrument_unique_ric_and_more"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.RemoveConstraint(
15
+ model_name="instrumentmetric",
16
+ name="unique_instrument_metric",
17
+ ),
18
+ migrations.AddConstraint(
19
+ model_name="instrumentmetric",
20
+ constraint=models.UniqueConstraint(
21
+ fields=("basket_content_type", "basket_id", "instrument", "date", "key"),
22
+ name="unique_instrument_metric",
23
+ nulls_distinct=False,
24
+ ),
25
+ ),
26
+ ]
File without changes
@@ -0,0 +1,180 @@
1
+ from datetime import date as date_lib
2
+ from typing import Self, Type
3
+
4
+ from django.contrib.contenttypes.fields import GenericForeignKey
5
+ from django.contrib.contenttypes.models import ContentType
6
+ from django.core.serializers.json import DjangoJSONEncoder
7
+ from django.db import models
8
+ from django.db.models.fields.json import KeyTextTransform
9
+ from django.db.models.functions import Cast, NullIf
10
+ from wbcore.models import WBModel
11
+
12
+ from .backends.base import AbstractBackend
13
+ from .dto import Metric, MetricKey
14
+ from .registry import backend_registry
15
+
16
+
17
+ class InstrumentMetric(models.Model):
18
+ basket_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, related_name="instrument_metrics")
19
+ basket_id = models.PositiveIntegerField()
20
+ basket_repr = models.CharField(max_length=256)
21
+
22
+ basket = GenericForeignKey("basket_content_type", "basket_id")
23
+
24
+ instrument = models.ForeignKey(
25
+ "wbfdm.Instrument",
26
+ on_delete=models.CASCADE,
27
+ related_name="metrics",
28
+ null=True,
29
+ blank=True,
30
+ help_text="Instrument where this metric belongs to. If null, the metric is applicable to all instruments related to the basket",
31
+ )
32
+ date = models.DateField(
33
+ verbose_name="Metric Date", null=True, blank=True, help_text="If date is null, the metric is considered static"
34
+ )
35
+ key = models.CharField(max_length=255, verbose_name="Metric Key")
36
+ metrics = models.JSONField(default=dict, verbose_name="Metrics", encoder=DjangoJSONEncoder)
37
+
38
+ parent_metric = models.ForeignKey(
39
+ "self",
40
+ related_name="dependent_metrics",
41
+ blank=True,
42
+ null=True,
43
+ on_delete=models.SET_NULL,
44
+ verbose_name="Parent Metric",
45
+ )
46
+
47
+ def save(self, *args, **kwargs):
48
+ self.basket_repr = str(self.basket)
49
+ super().save(*args, **kwargs)
50
+
51
+ @classmethod
52
+ def update_or_create_from_metric(cls, metric: Metric, parent_instrument_metric: Self | None = None):
53
+ """
54
+ Update or create an InstrumentMetric instance based on a given Metric DTO object.
55
+
56
+ Args:
57
+ metric (Metric): The DTO to base the creation or update on.
58
+ parent_instrument_metric (Optional[InstrumentMetric]): The parent of the created/updated InstrumentMetric instance, if any. Defaults to None.
59
+
60
+ Returns:
61
+ None
62
+
63
+ Side Effects:
64
+ - Creates or updates an InstrumentMetric instance in the database.
65
+ - Recursively creates or updates dependent InstrumentMetric instances.
66
+ """
67
+ base_metric, _ = InstrumentMetric.objects.update_or_create(
68
+ basket_id=metric.basket_id,
69
+ basket_content_type_id=metric.basket_content_type_id,
70
+ instrument_id=metric.instrument_id,
71
+ date=metric.date,
72
+ key=metric.key,
73
+ defaults={
74
+ "metrics": metric.metrics,
75
+ "parent_metric": parent_instrument_metric,
76
+ },
77
+ )
78
+ for dependency_metric in metric.dependency_metrics:
79
+ cls.update_or_create_from_metric(dependency_metric, parent_instrument_metric=base_metric)
80
+
81
+ @classmethod
82
+ def annotate_with_metrics(
83
+ cls,
84
+ queryset: models.QuerySet,
85
+ metric_key: MetricKey,
86
+ metric_basket_class: Type[WBModel],
87
+ val_date: date_lib | None = None,
88
+ basket_label: str = "id",
89
+ instrument_label: str | None = None,
90
+ ) -> models.QuerySet:
91
+ """
92
+ Annotate metrics to a queryset related to the given metric key and basket class.
93
+
94
+ Args:
95
+ queryset (models.QuerySet): The queryset to be annotated.
96
+ metric_key (MetricKey): The metric key or MetricKey instance to use for annotation.
97
+ metric_basket_class (Type[WBModel]): The basket class associated with the metric.
98
+ val_date (date, optional): The date for the metrics. Defaults to None.
99
+ basket_label (str, optional): The label to identify the basket in the subquery queryset. Defaults to "id".
100
+ instrument_label (Union[str, None], optional): The label to identify the instrument in the subquery queryset, if any. Defaults to None.
101
+
102
+ Returns:
103
+ models.QuerySet: The annotated queryset with the metrics included. (with field keys as returned by MetricField.get_fields)
104
+
105
+ Side Effects:
106
+ - Annotates the given queryset with subqueries for the specified metrics.
107
+ """
108
+
109
+ backend_class: AbstractBackend = backend_registry[metric_key, metric_basket_class]
110
+
111
+ content_type = ContentType.objects.get_for_model(backend_class.BASKET_MODEL_CLASS)
112
+ subquery = InstrumentMetric.objects.filter(
113
+ basket_content_type=content_type,
114
+ basket_id=models.OuterRef(basket_label),
115
+ key=metric_key.key,
116
+ date=val_date,
117
+ )
118
+ if instrument_label:
119
+ subquery = subquery.filter(instrument=models.OuterRef(instrument_label))
120
+ queryset = queryset.annotate(
121
+ **{
122
+ k: models.Subquery(
123
+ subquery.annotate(
124
+ casted_metric=Cast(
125
+ NullIf(models.F("metrics__" + v), models.Value("null")),
126
+ output_field=models.FloatField(),
127
+ )
128
+ ).values("casted_metric")[:1]
129
+ )
130
+ for k, v in metric_key.subfields_filter_map.items()
131
+ }
132
+ )
133
+
134
+ # annotate extra subfields
135
+ queryset = queryset.annotate(
136
+ **{
137
+ f"{metric_key.key}_{extra_subfield.key}": models.Subquery(
138
+ subquery.annotate(
139
+ casted_metric=Cast(
140
+ KeyTextTransform(extra_subfield.key, "metrics"), output_field=extra_subfield.field_type()
141
+ ),
142
+ ).values("casted_metric")[:1]
143
+ )
144
+ for extra_subfield in metric_key.extra_subfields
145
+ }
146
+ )
147
+
148
+ return queryset
149
+
150
+ @classmethod
151
+ def get_endpoint_basename(cls) -> str:
152
+ return "metric:instrumentmetric"
153
+
154
+ @classmethod
155
+ def get_representation_endpoint(cls) -> str:
156
+ return "metric:instrumentmetricrepresentation-list"
157
+
158
+ @classmethod
159
+ def get_representation_value_key(cls) -> str:
160
+ return "id"
161
+
162
+ @classmethod
163
+ def get_representation_label_key(cls) -> str:
164
+ return "{{key}} - {{date}} - {{basket_repr}}"
165
+
166
+ class Meta:
167
+ verbose_name = "Instrument Metric"
168
+ verbose_name_plural = "Instrument Metrics"
169
+ constraints = [
170
+ models.UniqueConstraint(
171
+ name="unique_instrument_metric",
172
+ fields=["basket_content_type", "basket_id", "instrument", "date", "key"],
173
+ nulls_distinct=False,
174
+ )
175
+ ]
176
+ indexes = [
177
+ models.Index(
178
+ fields=["basket_content_type", "basket_id", "key", "date"],
179
+ )
180
+ ]
@@ -0,0 +1,94 @@
1
+ from contextlib import suppress
2
+ from datetime import date
3
+ from typing import Any, Generator
4
+
5
+ from django.db import models
6
+ from django.db.utils import DataError
7
+ from tqdm import tqdm
8
+
9
+ from .backends.base import AbstractBackend
10
+ from .dto import Metric
11
+ from .exceptions import MetricInvalidParameterException
12
+ from .models import InstrumentMetric
13
+ from .registry import backend_registry
14
+
15
+
16
+ class MetricOrchestrator:
17
+ """
18
+ Orchestrator class responsible to gather the proper metric backends and their respective queryset of baskets
19
+
20
+ For each parameters, the orchestrator is responsible to compute and store the generated metrics
21
+ """
22
+
23
+ def __init__(self, val_date: date | None, key: str | None = None, basket: Any | None = None, **kwargs):
24
+ self.basket = basket
25
+ self.val_date = val_date
26
+ self.backend_classes = backend_registry.get(metric_key=key, model_class=basket.__class__ if basket else None)
27
+
28
+ def _iterate_baskets(self, backend: AbstractBackend) -> models.QuerySet:
29
+ """
30
+ Retrieve and filter the queryset of baskets from the given backend.
31
+
32
+ Args:
33
+ backend (AbstractBackend): The backend from which to retrieve the queryset of baskets.
34
+
35
+ Returns:
36
+ models.QuerySet: A queryset of baskets, filtered by the current basket if specified.
37
+ """
38
+ qs = backend.get_queryset()
39
+ if self.basket:
40
+ qs = qs.filter(id=self.basket.id)
41
+ return qs
42
+
43
+ def _get_parameters(self) -> list[tuple[AbstractBackend, Any]]:
44
+ """
45
+ Generate a list of parameters consisting of metric backend instances and baskets.
46
+
47
+ This method initializes backends for each backend class with the validation date,
48
+ iterates through the baskets, and creates a list of tuples containing the backend
49
+ and each basket.
50
+
51
+ Returns:
52
+ List[Tuple[AbstractBackend, Any]]: A list of tuples where each tuple contains
53
+ an instance of a instantiated metric backend and a corresponding basket to compute metrics from.
54
+ """
55
+ parameters = []
56
+ for backend_class in self.backend_classes:
57
+ backend = backend_class(self.val_date)
58
+ for basket in self._iterate_baskets(backend):
59
+ parameters.append((backend, basket))
60
+ return parameters
61
+
62
+ def get_results(self, debug: bool = False) -> Generator[Metric, None, None]:
63
+ """
64
+ Compute and yield metrics based on the parameters obtained from `_get_parameters`
65
+
66
+ Args:
67
+ debug (bool, optional): If True, wraps the parameters list in a tqdm generator for progress tracking. Defaults to False.
68
+
69
+ Yields:
70
+ Metric: Each DTO metric computed by the backend for the given baskets.
71
+ """
72
+ parameters = self._get_parameters()
73
+ if debug:
74
+ # if debug mode is enabled, we wrap the parameters list into a tqdm generator
75
+ parameters = tqdm(parameters)
76
+ for parameters in parameters:
77
+ with suppress(MetricInvalidParameterException):
78
+ yield from parameters[0].compute_metrics(parameters[1])
79
+
80
+ def process(self, debug: bool = False):
81
+ """
82
+ Process the computation of metrics and update or create InstrumentMetric instances accordingly.
83
+
84
+ Side Effects:
85
+ - Updates or creates InstrumentMetric instances based on the computed metrics.
86
+ """
87
+ # we need to see how one threaded loop is fast enough
88
+ errors = []
89
+ for metric in self.get_results(debug=debug):
90
+ try:
91
+ InstrumentMetric.update_or_create_from_metric(metric)
92
+ except DataError:
93
+ errors.append(metric)
94
+ return errors
@@ -0,0 +1,80 @@
1
+ from collections import OrderedDict, defaultdict
2
+ from typing import TYPE_CHECKING, Type
3
+
4
+ from django.db.models import Model
5
+
6
+ from .dto import MetricKey
7
+
8
+ if TYPE_CHECKING:
9
+ from wbfdm.contrib.metric.backends.base import AbstractBackend
10
+
11
+
12
+ # The cached metric backend registry as a dictionary of dictionary (e.g. {key: {model_class: {backend_class}})
13
+ class BackendRegistry:
14
+ _key_label_map: dict[str, str] = dict()
15
+ _metric_key_map: dict[str, MetricKey] = dict()
16
+ _overrode_model_class: dict[MetricKey, list[Type[Model]]] = defaultdict(list)
17
+ registry: dict[MetricKey, OrderedDict] = defaultdict(OrderedDict)
18
+
19
+ def __getitem__(self, index):
20
+ key = index[0]
21
+ if isinstance(key, str):
22
+ key = self._metric_key_map[key]
23
+ return self.registry[key][index[1]]
24
+
25
+ def get_choices(self, keys: list[MetricKey] | None = None) -> list[tuple[str, str]]:
26
+ if not keys:
27
+ keys = list(self.registry.keys())
28
+ return [(key.key, key.label) for key in keys]
29
+
30
+ def set(
31
+ self,
32
+ metric_key: MetricKey,
33
+ model_class: Type[Model],
34
+ backend: Type,
35
+ move_first: bool = False,
36
+ override_backend: bool = False,
37
+ ):
38
+ self._metric_key_map[metric_key.key] = metric_key
39
+ self._key_label_map[metric_key.key] = metric_key.label
40
+ # we ensure that the registered key and model class pair are not already registered and lock as override
41
+ if model_class not in self._overrode_model_class[metric_key]:
42
+ self.registry[metric_key][model_class] = backend
43
+ if move_first:
44
+ self.registry[metric_key].move_to_end(model_class, last=False)
45
+ if override_backend:
46
+ self._overrode_model_class[metric_key].append(model_class)
47
+
48
+ def get(
49
+ self, metric_key: MetricKey | str | None = None, model_class: Type[Model] | None = None
50
+ ) -> list[Type["AbstractBackend"]]:
51
+ # Initialize the backend classes list to iterate over
52
+ registry = self.registry
53
+ if isinstance(metric_key, str):
54
+ metric_key = self._metric_key_map[metric_key]
55
+ if metric_key:
56
+ # if key is provided, we return only the backends associated with that metric key
57
+ if metric_key not in self.registry:
58
+ raise ValueError(f"key {metric_key.key} does not belong to a registered backend")
59
+ registry = {metric_key: registry[metric_key]}
60
+
61
+ if model_class:
62
+ # if the metric needs to be computed only for a specific basket, we filter out the backend classes related to his basket class
63
+ registry = {
64
+ key: {_class: _dataclass}
65
+ for key, d in registry.items()
66
+ for _class, _dataclass in d.items()
67
+ if _class == model_class
68
+ }
69
+ backends = []
70
+ for _, ordered_dict in registry.items():
71
+ for _, backend in ordered_dict.items():
72
+ if backend not in backends:
73
+ backends.append(backend)
74
+ return backends
75
+
76
+ def keys(self):
77
+ return self._metric_key_map.keys()
78
+
79
+
80
+ backend_registry = BackendRegistry()
@@ -0,0 +1,44 @@
1
+ from rest_framework.reverse import reverse
2
+ from wbcore import serializers as wb_serializers
3
+ from wbcore.content_type.serializers import ContentTypeRepresentationSerializer
4
+ from wbfdm.contrib.metric.models import InstrumentMetric
5
+ from wbfdm.serializers.instruments import InstrumentRepresentationSerializer
6
+
7
+
8
+ class InstrumentMetricRepresentationSerializer(wb_serializers.RepresentationSerializer):
9
+ class Meta:
10
+ model = InstrumentMetric
11
+ fields = ("id", "key", "date", "basket_repr")
12
+
13
+
14
+ class InstrumentMetricModelSerializer(wb_serializers.ModelSerializer):
15
+ _basket_content_type = ContentTypeRepresentationSerializer(source="basket_content_type")
16
+ _instrument = InstrumentRepresentationSerializer(source="instrument")
17
+ _parent_metric = InstrumentMetricRepresentationSerializer(source="parent_metric")
18
+ _group_key = wb_serializers.CharField(read_only=True)
19
+
20
+ @wb_serializers.register_resource()
21
+ def additional_resources(self, instance, request, user):
22
+ return {
23
+ "children_metrics": f'{reverse("metric:instrumentmetric-list", args=[], request=request)}?parent_metric={instance.id}'
24
+ }
25
+
26
+ class Meta:
27
+ model = InstrumentMetric
28
+ fields = (
29
+ "id",
30
+ "basket_content_type",
31
+ "_basket_content_type",
32
+ "basket_repr",
33
+ "basket_id",
34
+ "instrument",
35
+ "_instrument",
36
+ "date",
37
+ "key",
38
+ "metrics",
39
+ "parent_metric",
40
+ "_parent_metric",
41
+ "_group_key",
42
+ "_additional_resources",
43
+ )
44
+ read_only_fields = fields
@@ -0,0 +1,27 @@
1
+ from datetime import date
2
+
3
+ from celery import shared_task
4
+ from django.contrib.contenttypes.models import ContentType
5
+ from wbfdm.contrib.metric.orchestrators import MetricOrchestrator
6
+
7
+
8
+ @shared_task(queue="portfolio")
9
+ def compute_metrics_as_task(
10
+ val_date: date | None = None,
11
+ key: str | None = None,
12
+ basket_content_type_id: int | None = None,
13
+ basket_id: int | None = None,
14
+ **kwargs
15
+ ):
16
+ """
17
+ Run the orchestrator as a async task periodically for all keys and baskets
18
+
19
+ Args:
20
+ val_date (date): Compute the metric for the given date (Default to None). If None, let the metric backends decide what is the last valide date to use
21
+
22
+ """
23
+ basket = None
24
+ if basket_content_type_id and basket_id:
25
+ basket = ContentType.objects.get(id=basket_content_type_id).get_object_for_this_type(pk=basket_id)
26
+ routine = MetricOrchestrator(val_date, key=key, basket=basket, **kwargs)
27
+ routine.process()
File without changes
File without changes