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,251 @@
1
+ from django.core.validators import MaxValueValidator, MinValueValidator
2
+ from django.db import models
3
+ from django.db.models.signals import m2m_changed
4
+ from django.dispatch import receiver
5
+ from rest_framework.reverse import reverse
6
+ from wbcore.contrib.tags.models import TagModelMixin
7
+ from wbcore.models import WBModel
8
+ from wbcore.signals import pre_merge
9
+
10
+
11
+ class InstrumentClassificationRelatedInstrument(models.Model):
12
+ class Type(models.TextChoices):
13
+ PARTNER = "PARTNER", "Partner"
14
+ SUPPLIER = "SUPPLIER", "Supplier"
15
+ PEER = "PEER", "Peer"
16
+ COMPETITOR = "COMPETITOR", "Competitor"
17
+ BIGGEST_THREAT = "BIGGEST_THREAT", "Biggest Threat"
18
+ CUSTOMER = "CUSTOMER", "Customer"
19
+
20
+ related_instrument_type = models.CharField(max_length=16, choices=Type.choices, null=True, blank=True)
21
+ classified_instrument = models.ForeignKey(
22
+ to="wbfdm.InstrumentClassificationThroughModel",
23
+ related_name="classification_instrument_relationships",
24
+ on_delete=models.CASCADE,
25
+ )
26
+ related_instrument = models.ForeignKey(
27
+ to="wbfdm.Instrument",
28
+ limit_choices_to=(models.Q(instrument_type__is_classifiable=True) & models.Q(level=0)),
29
+ related_name="instrument_classification_related",
30
+ on_delete=models.CASCADE,
31
+ )
32
+
33
+ def __str__(self) -> str:
34
+ return f"{self.classified_instrument} {self.related_instrument}"
35
+
36
+
37
+ class InstrumentClassificationThroughModel(TagModelMixin, models.Model):
38
+ instrument = models.ForeignKey(
39
+ "wbfdm.Instrument",
40
+ on_delete=models.CASCADE,
41
+ related_name="classifications_through",
42
+ limit_choices_to=(models.Q(instrument_type__is_classifiable=True) & models.Q(level=0)),
43
+ )
44
+ classification = models.ForeignKey(
45
+ "wbfdm.Classification",
46
+ on_delete=models.CASCADE,
47
+ related_name="instruments_through",
48
+ )
49
+ is_favorite = models.BooleanField(default=False)
50
+ reason = models.TextField(default="", blank=True, verbose_name="Reason for the choice")
51
+ pure_player = models.BooleanField(default=False, help_text="Pure Players Companies", verbose_name="Pure Player")
52
+ top_player = models.BooleanField(default=False, help_text="Top Players Companies", verbose_name="Top Player")
53
+ percent_of_revenue = models.DecimalField(
54
+ decimal_places=4,
55
+ max_digits=5,
56
+ null=True,
57
+ blank=True,
58
+ verbose_name="% of revenue",
59
+ validators=[MinValueValidator(0), MaxValueValidator(1)],
60
+ )
61
+ related_instruments = models.ManyToManyField(
62
+ to="wbfdm.Instrument",
63
+ limit_choices_to=(models.Q(instrument_type__is_classifiable=True) & models.Q(level=0)),
64
+ through=InstrumentClassificationRelatedInstrument,
65
+ through_fields=("classified_instrument", "related_instrument"),
66
+ blank=True,
67
+ )
68
+
69
+ def get_tag_detail_endpoint(self):
70
+ return reverse("wbfdm:classifiedinstrument-detail", [self.id])
71
+
72
+ def get_tag_representation(self):
73
+ return f"{self.instrument} - {self.classification}"
74
+
75
+ class Meta:
76
+ constraints = [
77
+ models.UniqueConstraint(name="unique_classifiedinstruments", fields=["instrument", "classification"])
78
+ ]
79
+
80
+ def __str__(self) -> str:
81
+ return f"{self.instrument} {self.classification}"
82
+
83
+ def save(self, *args, **kwargs):
84
+ if self.pure_player and not self.percent_of_revenue:
85
+ self.percent_of_revenue = 1
86
+ return super().save(*args, **kwargs)
87
+
88
+ @classmethod
89
+ def get_endpoint_basename(cls) -> str:
90
+ return "wbfdm:instrumentclassificationrelationship"
91
+
92
+
93
+ class InstrumentFavoriteGroup(WBModel):
94
+ name = models.CharField(max_length=256)
95
+ instruments = models.ManyToManyField(
96
+ "wbfdm.Instrument",
97
+ related_name="favorite_groups",
98
+ blank=True,
99
+ verbose_name="Favorite Instruments Group",
100
+ limit_choices_to=models.Q(children__isnull=True),
101
+ )
102
+ owner = models.ForeignKey(
103
+ "directory.Person", on_delete=models.CASCADE, blank=True, null=True, related_name="favorite_instruments_groups"
104
+ )
105
+ public = models.BooleanField(default=False, help_text="If set to True, this group will be available to everyone.")
106
+ primary = models.BooleanField(
107
+ default=False,
108
+ help_text="If set to True, this group will be set as default filter for instrument based viewset (Only one primary group allowed).",
109
+ )
110
+
111
+ def __str__(self):
112
+ return self.name
113
+
114
+ def save(self, *args, **kwargs):
115
+ if self.primary:
116
+ InstrumentFavoriteGroup.objects.filter(owner=self.owner, primary=True).exclude(id=self.id).update(
117
+ primary=False
118
+ )
119
+ return super().save(*args, **kwargs)
120
+
121
+ @classmethod
122
+ def get_representation_endpoint(cls) -> str:
123
+ return "wbfdm:favoritegroup-list"
124
+
125
+ @classmethod
126
+ def get_representation_value_key(cls) -> str:
127
+ return "id"
128
+
129
+ @classmethod
130
+ def get_representation_label_key(cls) -> str:
131
+ return "{{name}} - (Public: {{public}})"
132
+
133
+ @classmethod
134
+ def get_endpoint_basename(cls) -> str:
135
+ return "wbfdm:favoritegroup"
136
+
137
+
138
+ class RelatedInstrumentThroughModel(models.Model):
139
+ class RelatedTypeChoices(models.TextChoices):
140
+ BENCHMARK = "BENCHMARK", "Benchmark"
141
+ PEER = "PEER", "Peer"
142
+ RISK_INSTRUMENT = "RISK_INSTRUMENT", "Risk Instrument"
143
+
144
+ instrument = models.ForeignKey(
145
+ "wbfdm.Instrument",
146
+ on_delete=models.CASCADE,
147
+ related_name="related_instruments_through",
148
+ limit_choices_to=models.Q(children__isnull=True),
149
+ )
150
+ related_instrument = models.ForeignKey(
151
+ "wbfdm.Instrument",
152
+ on_delete=models.CASCADE,
153
+ related_name="dependent_instruments_through",
154
+ limit_choices_to=models.Q(children__isnull=True),
155
+ )
156
+ is_primary = models.BooleanField(default=False)
157
+ related_type = models.CharField(
158
+ max_length=32, default=RelatedTypeChoices.BENCHMARK, choices=RelatedTypeChoices.choices
159
+ )
160
+
161
+ class Meta:
162
+ unique_together = ("instrument", "related_instrument", "is_primary", "related_type")
163
+
164
+ def save(self, *args, **kwargs):
165
+ qs = RelatedInstrumentThroughModel.objects.filter(
166
+ instrument=self.instrument, related_type=self.related_type, is_primary=True
167
+ ).exclude(id=self.id)
168
+ if self.is_primary:
169
+ qs.update(is_primary=False)
170
+ elif not qs.exists():
171
+ self.is_primary = True
172
+ return super().save(*args, **kwargs)
173
+
174
+
175
+ @receiver(m2m_changed, sender="wbfdm.RelatedInstrumentThroughModel")
176
+ def add_related_instrument(sender, instance, action, pk_set, **kwargs):
177
+ if action == "post_add" and pk_set:
178
+ for related_instrument_id in pk_set:
179
+ through = RelatedInstrumentThroughModel.objects.filter(
180
+ instrument=instance.id, related_instrument=related_instrument_id
181
+ ).first()
182
+ through.save()
183
+ if action == "post_remove" and pk_set:
184
+ qs = RelatedInstrumentThroughModel.objects.filter(instrument=instance, related_type=instance.related_type)
185
+ if not qs.filter(is_primary=True).exists():
186
+ instance = qs.first()
187
+ instance.is_primary = True
188
+ instance.save()
189
+
190
+
191
+ @receiver(m2m_changed, sender="wbfdm.InstrumentClassificationThroughModel")
192
+ def add_classification(sender, instance, action, pk_set, **kwargs):
193
+ if action == "post_add" and pk_set:
194
+ for classification_id in pk_set:
195
+ if through := InstrumentClassificationThroughModel.objects.filter(
196
+ instrument=instance.id, classification=classification_id
197
+ ).first():
198
+ through.save()
199
+
200
+
201
+ @receiver(pre_merge, sender="wbfdm.Instrument")
202
+ def pre_merge_instrument(sender: models.Model, merged_object, main_object, **kwargs):
203
+ """
204
+ Reassign all merged instrument preferred classification relationship to the main instrument
205
+ """
206
+ # For every favorite group where the merged instrument is present, we remove it and assign the main instrument instad
207
+ for favorite_group in InstrumentFavoriteGroup.objects.filter(instruments=merged_object):
208
+ favorite_group.instruments.remove(merged_object)
209
+ favorite_group.instruments.add(main_object)
210
+
211
+ # For all related instruments relationship of the merged instrument, we reassign them to the main instrument if they don't exist yet. The relationship is then deleted.
212
+ for through in RelatedInstrumentThroughModel.objects.filter(instrument=merged_object):
213
+ RelatedInstrumentThroughModel.objects.get_or_create(
214
+ instrument=main_object,
215
+ related_instrument=through.related_instrument,
216
+ is_primary=through.is_primary,
217
+ related_type=through.related_type,
218
+ )
219
+ through.delete()
220
+ # We also reassign the reverse related instrument relationship where the merged instrument is the related instrument.
221
+ for through in RelatedInstrumentThroughModel.objects.filter(related_instrument=merged_object):
222
+ RelatedInstrumentThroughModel.objects.get_or_create(
223
+ instrument=through.instrument,
224
+ related_instrument=main_object,
225
+ is_primary=through.is_primary,
226
+ related_type=through.related_type,
227
+ )
228
+ through.delete()
229
+
230
+ # For all classification relationships of the merged instrument, we reassign them to the main instrument if they don't exist yet. The relationship is then deleted.
231
+ for through in InstrumentClassificationThroughModel.objects.filter(instrument=merged_object):
232
+ new_rel, created = InstrumentClassificationThroughModel.objects.get_or_create(
233
+ instrument=main_object,
234
+ classification=through.classification,
235
+ defaults={
236
+ "is_favorite": through.is_favorite,
237
+ "reason": through.reason,
238
+ "pure_player": through.pure_player,
239
+ "top_player": through.top_player,
240
+ "percent_of_revenue": through.percent_of_revenue,
241
+ },
242
+ )
243
+ for related_instrument in through.related_instruments.all():
244
+ if related_instrument not in new_rel.related_instruments.all():
245
+ new_rel.related_instruments.add(related_instrument)
246
+ through.delete()
247
+
248
+ # We also reassign the reverse classification relationship where the merged instrument is the related instrument.
249
+ for through in InstrumentClassificationThroughModel.objects.filter(related_instruments=merged_object):
250
+ through.related_instruments.remove(merged_object)
251
+ through.related_instruments.add(main_object)
@@ -0,0 +1,196 @@
1
+ from typing import Any, Dict, Optional
2
+
3
+ from django.db import models
4
+ from django_fsm import FSMField, transition
5
+ from wbcore.contrib.authentication.models import User
6
+ from wbcore.contrib.currency.models import Currency
7
+ from wbcore.contrib.geography.models import Geography
8
+ from wbcore.contrib.icons import WBIcon
9
+ from wbcore.contrib.tags.models import Tag
10
+ from wbcore.enums import RequestType
11
+ from wbcore.metadata.configs.buttons import ActionButton, ButtonDefaultColor
12
+ from wbcore.models import WBModel
13
+ from wbfdm.models.instruments.classifications import Classification
14
+
15
+ from .instruments import Instrument, InstrumentType
16
+
17
+
18
+ class InstrumentRequest(WBModel):
19
+ class Status(models.TextChoices):
20
+ PENDING = "PENDING", "Pending"
21
+ APPROVED = "APPROVED", "Approved"
22
+ DENIED = "DENIED", "Denied"
23
+ DRAFT = "DRAFT", "Draft"
24
+
25
+ status = FSMField(
26
+ default=Status.DRAFT,
27
+ choices=Status.choices,
28
+ verbose_name="Status",
29
+ help_text="The Request Status (default to Pending)",
30
+ )
31
+ requester = models.ForeignKey(
32
+ "directory.Person",
33
+ related_name="instrument_requests",
34
+ on_delete=models.SET_NULL,
35
+ verbose_name="Requester",
36
+ null=True,
37
+ blank=True,
38
+ )
39
+ handler = models.ForeignKey(
40
+ "directory.Person",
41
+ related_name="handled_instrument_requests",
42
+ on_delete=models.SET_NULL,
43
+ verbose_name="Handler",
44
+ null=True,
45
+ blank=True,
46
+ )
47
+ notes = models.TextField(null=True, blank=True, verbose_name="Notes")
48
+ created = models.DateTimeField(auto_now_add=True, verbose_name="Created", help_text="The request creation time")
49
+ instrument_data = models.JSONField(default=dict, verbose_name="Instrument Data")
50
+ created_instrument = models.OneToOneField(
51
+ "wbfdm.Instrument", on_delete=models.CASCADE, blank=True, null=True, related_name="creation_request"
52
+ )
53
+
54
+ def __str__(self):
55
+ return f'Instrument Request - {self.Status[self.status].label} ({"".join([f"{k}={v}" for k, v in self.instrument_data.items()])})'
56
+
57
+ @property
58
+ def deserialize_instrument_data(self) -> tuple[Dict[str, Any], Dict[str, Any]]:
59
+ instrument_data = self.instrument_data.copy()
60
+ many_to_many_data = dict()
61
+ if currency_id := instrument_data.get("currency", None):
62
+ instrument_data["currency"] = Currency.objects.filter(id=currency_id).first()
63
+ if country_id := instrument_data.get("country", None):
64
+ instrument_data["country"] = Geography.countries.filter(id=country_id).first()
65
+ if instrument_type_id := instrument_data.get("instrument_type", None):
66
+ instrument_data["instrument_type"] = InstrumentType.objects.get(id=instrument_type_id)
67
+ if tags_list := instrument_data.pop("tags", None):
68
+ many_to_many_data["tags"] = [Tag.objects.filter(id=tag_id).first() for tag_id in tags_list]
69
+ if classifications_list := instrument_data.pop("classifications", None):
70
+ many_to_many_data["classifications"] = [
71
+ Classification.objects.filter(id=classification_id).first()
72
+ for classification_id in classifications_list
73
+ ]
74
+ instrument_data["is_investable_universe"] = True
75
+ return instrument_data, many_to_many_data
76
+
77
+ def _check_already_existing_instrument(self):
78
+ return (
79
+ (isin := self.instrument_data.get("isin", None)) and Instrument.objects.filter(isin=isin).exists()
80
+ ) or (
81
+ (ric := self.instrument_data.get("refinitiv_identifier_code", None))
82
+ and Instrument.objects.filter(refinitiv_identifier_code=ric).exists()
83
+ )
84
+
85
+ @transition(
86
+ field=status,
87
+ source=[Status.PENDING],
88
+ target=Status.APPROVED,
89
+ permission=lambda instance, user: user.has_perm("wbfdm.administrate_instrument"),
90
+ custom={
91
+ "_transition_button": ActionButton(
92
+ method=RequestType.PATCH,
93
+ color=ButtonDefaultColor.WARNING,
94
+ identifiers=("wbfdm:instrumentrequest",),
95
+ icon=WBIcon.APPROVE.icon,
96
+ key="approve",
97
+ label="Approve",
98
+ action_label="Approve",
99
+ description_fields="<p>You are sure to approve this request?</p>",
100
+ )
101
+ },
102
+ )
103
+ def approve(self, by: Optional[User] = None, **kwargs):
104
+ deserialize_instrument_data, many_to_many_data = self.deserialize_instrument_data
105
+ created_instrument = Instrument.objects.create(**deserialize_instrument_data)
106
+ for key, items in many_to_many_data.items():
107
+ getattr(created_instrument, key).set(items)
108
+ if profile := getattr(by, "profile", None):
109
+ self.handler = profile
110
+ self.created_instrument = created_instrument
111
+
112
+ def can_approve(self):
113
+ if self._check_already_existing_instrument():
114
+ return {"non_field_errors": "An instrument already exists with the proposed identifier"}
115
+
116
+ @transition(
117
+ field=status,
118
+ source=[Status.PENDING],
119
+ target=Status.DENIED,
120
+ permission=lambda instance, user: user.has_perm("wbfdm.administrate_instrument"),
121
+ custom={
122
+ "_transition_button": ActionButton(
123
+ method=RequestType.PATCH,
124
+ color=ButtonDefaultColor.WARNING,
125
+ identifiers=("wbfdm:instrumentrequest",),
126
+ icon=WBIcon.DENY.icon,
127
+ key="deny",
128
+ label="Deny",
129
+ action_label="Deny",
130
+ description_fields="<p>You are sure to deny this request?</p>",
131
+ )
132
+ },
133
+ )
134
+ def deny(self, by: Optional[User] = None, **kwargs):
135
+ if profile := getattr(by, "profile", None):
136
+ self.handler = profile
137
+
138
+ @transition(
139
+ field=status,
140
+ source=[Status.DENIED],
141
+ target=Status.DRAFT,
142
+ custom={
143
+ "_transition_button": ActionButton(
144
+ method=RequestType.PATCH,
145
+ color=ButtonDefaultColor.WARNING,
146
+ identifiers=("wbfdm:instrumentrequest",),
147
+ icon=WBIcon.SEND.icon,
148
+ key="backtodraft",
149
+ label="Back To Draft",
150
+ action_label="Back To Draft",
151
+ description_fields="<p>You are sure to put this request back to draft?</p>",
152
+ )
153
+ },
154
+ )
155
+ def backtodraft(self, by: Optional[User] = None, **kwargs):
156
+ pass
157
+
158
+ @transition(
159
+ field=status,
160
+ source=[Status.DRAFT],
161
+ target=Status.PENDING,
162
+ custom={
163
+ "_transition_button": ActionButton(
164
+ method=RequestType.PATCH,
165
+ color=ButtonDefaultColor.WARNING,
166
+ identifiers=("wbfdm:instrumentrequest",),
167
+ icon=WBIcon.SEND.icon,
168
+ key="submit",
169
+ label="Submit",
170
+ action_label="Submit",
171
+ description_fields="<p>You are sure to submit this request back?</p>",
172
+ )
173
+ },
174
+ )
175
+ def submit(self, by: Optional[User] = None, **kwargs):
176
+ pass
177
+
178
+ def can_submit(self) -> Dict[str, Any]:
179
+ if self._check_already_existing_instrument():
180
+ return {"non_field_errors": "An instrument already exists with the proposed identifier"}
181
+
182
+ @classmethod
183
+ def get_representation_endpoint(cls) -> str:
184
+ return "wbfdm:instrumentrequestrepresentation-list"
185
+
186
+ @classmethod
187
+ def get_representation_value_key(cls) -> str:
188
+ return "id"
189
+
190
+ @classmethod
191
+ def get_representation_label_key(cls) -> str:
192
+ return "Request {{id}} ({{status}})"
193
+
194
+ @classmethod
195
+ def get_endpoint_basename(cls) -> str:
196
+ return "wbfdm:instrumentrequest"