wbfdm 1.43.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 (351) 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 +277 -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/backends/dto.py +36 -0
  29. wbfdm/contrib/__init__.py +0 -0
  30. wbfdm/contrib/dsws/__init__.py +0 -0
  31. wbfdm/contrib/dsws/client.py +285 -0
  32. wbfdm/contrib/dsws/dataloaders/market_data.py +130 -0
  33. wbfdm/contrib/internal/__init__.py +0 -0
  34. wbfdm/contrib/internal/dataloaders/__init__.py +0 -0
  35. wbfdm/contrib/internal/dataloaders/market_data.py +87 -0
  36. wbfdm/contrib/metric/__init__.py +0 -0
  37. wbfdm/contrib/metric/admin/__init__.py +2 -0
  38. wbfdm/contrib/metric/admin/instruments.py +12 -0
  39. wbfdm/contrib/metric/admin/metrics.py +43 -0
  40. wbfdm/contrib/metric/apps.py +10 -0
  41. wbfdm/contrib/metric/backends/__init__.py +2 -0
  42. wbfdm/contrib/metric/backends/base.py +159 -0
  43. wbfdm/contrib/metric/backends/performances.py +265 -0
  44. wbfdm/contrib/metric/backends/statistics.py +182 -0
  45. wbfdm/contrib/metric/decorators.py +14 -0
  46. wbfdm/contrib/metric/dispatch.py +23 -0
  47. wbfdm/contrib/metric/dto.py +88 -0
  48. wbfdm/contrib/metric/exceptions.py +6 -0
  49. wbfdm/contrib/metric/factories.py +33 -0
  50. wbfdm/contrib/metric/filters.py +28 -0
  51. wbfdm/contrib/metric/migrations/0001_initial.py +88 -0
  52. wbfdm/contrib/metric/migrations/0002_remove_instrumentmetric_unique_instrument_metric_and_more.py +26 -0
  53. wbfdm/contrib/metric/migrations/__init__.py +0 -0
  54. wbfdm/contrib/metric/models.py +180 -0
  55. wbfdm/contrib/metric/orchestrators.py +94 -0
  56. wbfdm/contrib/metric/registry.py +80 -0
  57. wbfdm/contrib/metric/serializers.py +44 -0
  58. wbfdm/contrib/metric/tasks.py +27 -0
  59. wbfdm/contrib/metric/tests/__init__.py +0 -0
  60. wbfdm/contrib/metric/tests/backends/__init__.py +0 -0
  61. wbfdm/contrib/metric/tests/backends/test_performances.py +152 -0
  62. wbfdm/contrib/metric/tests/backends/test_statistics.py +48 -0
  63. wbfdm/contrib/metric/tests/conftest.py +92 -0
  64. wbfdm/contrib/metric/tests/test_dto.py +73 -0
  65. wbfdm/contrib/metric/tests/test_models.py +72 -0
  66. wbfdm/contrib/metric/tests/test_tasks.py +24 -0
  67. wbfdm/contrib/metric/tests/test_viewsets.py +79 -0
  68. wbfdm/contrib/metric/urls.py +19 -0
  69. wbfdm/contrib/metric/viewsets/__init__.py +1 -0
  70. wbfdm/contrib/metric/viewsets/configs/__init__.py +1 -0
  71. wbfdm/contrib/metric/viewsets/configs/display.py +92 -0
  72. wbfdm/contrib/metric/viewsets/configs/menus.py +11 -0
  73. wbfdm/contrib/metric/viewsets/configs/utils.py +137 -0
  74. wbfdm/contrib/metric/viewsets/mixins.py +248 -0
  75. wbfdm/contrib/metric/viewsets/viewsets.py +40 -0
  76. wbfdm/contrib/msci/__init__.py +0 -0
  77. wbfdm/contrib/msci/client.py +92 -0
  78. wbfdm/contrib/msci/dataloaders/__init__.py +0 -0
  79. wbfdm/contrib/msci/dataloaders/esg.py +87 -0
  80. wbfdm/contrib/msci/dataloaders/esg_controversies.py +81 -0
  81. wbfdm/contrib/msci/sync.py +58 -0
  82. wbfdm/contrib/msci/tests/__init__.py +0 -0
  83. wbfdm/contrib/msci/tests/conftest.py +1 -0
  84. wbfdm/contrib/msci/tests/test_client.py +70 -0
  85. wbfdm/contrib/qa/__init__.py +0 -0
  86. wbfdm/contrib/qa/apps.py +22 -0
  87. wbfdm/contrib/qa/database_routers.py +25 -0
  88. wbfdm/contrib/qa/dataloaders/__init__.py +0 -0
  89. wbfdm/contrib/qa/dataloaders/adjustments.py +56 -0
  90. wbfdm/contrib/qa/dataloaders/corporate_actions.py +59 -0
  91. wbfdm/contrib/qa/dataloaders/financials.py +83 -0
  92. wbfdm/contrib/qa/dataloaders/market_data.py +117 -0
  93. wbfdm/contrib/qa/dataloaders/officers.py +59 -0
  94. wbfdm/contrib/qa/dataloaders/reporting_dates.py +67 -0
  95. wbfdm/contrib/qa/dataloaders/statements.py +267 -0
  96. wbfdm/contrib/qa/jinja2/qa/sql/companies.sql +100 -0
  97. wbfdm/contrib/qa/jinja2/qa/sql/ibes/base_estimates.sql +33 -0
  98. wbfdm/contrib/qa/jinja2/qa/sql/ibes/calendarized.sql +37 -0
  99. wbfdm/contrib/qa/jinja2/qa/sql/ibes/complete.sql +9 -0
  100. wbfdm/contrib/qa/jinja2/qa/sql/ibes/estimates.sql +3 -0
  101. wbfdm/contrib/qa/jinja2/qa/sql/ibes/financials.sql +79 -0
  102. wbfdm/contrib/qa/jinja2/qa/sql/instruments.sql +100 -0
  103. wbfdm/contrib/qa/jinja2/qa/sql/quotes.sql +98 -0
  104. wbfdm/contrib/qa/sync/exchanges.py +70 -0
  105. wbfdm/contrib/qa/sync/instruments.py +94 -0
  106. wbfdm/contrib/qa/sync/utils.py +241 -0
  107. wbfdm/contrib/qa/tasks.py +0 -0
  108. wbfdm/dataloaders/__init__.py +0 -0
  109. wbfdm/dataloaders/cache.py +129 -0
  110. wbfdm/dataloaders/protocols.py +112 -0
  111. wbfdm/dataloaders/proxies.py +201 -0
  112. wbfdm/dataloaders/types.py +209 -0
  113. wbfdm/dynamic_preferences_registry.py +45 -0
  114. wbfdm/enums.py +657 -0
  115. wbfdm/factories/__init__.py +13 -0
  116. wbfdm/factories/classifications.py +56 -0
  117. wbfdm/factories/controversies.py +27 -0
  118. wbfdm/factories/exchanges.py +21 -0
  119. wbfdm/factories/instrument_list.py +22 -0
  120. wbfdm/factories/instrument_prices.py +79 -0
  121. wbfdm/factories/instruments.py +63 -0
  122. wbfdm/factories/instruments_relationships.py +31 -0
  123. wbfdm/factories/options.py +66 -0
  124. wbfdm/figures/__init__.py +1 -0
  125. wbfdm/figures/financials/__init__.py +1 -0
  126. wbfdm/figures/financials/financial_analysis_charts.py +469 -0
  127. wbfdm/figures/financials/financials_charts.py +711 -0
  128. wbfdm/filters/__init__.py +31 -0
  129. wbfdm/filters/classifications.py +100 -0
  130. wbfdm/filters/exchanges.py +22 -0
  131. wbfdm/filters/financials.py +95 -0
  132. wbfdm/filters/financials_analysis.py +119 -0
  133. wbfdm/filters/instrument_prices.py +112 -0
  134. wbfdm/filters/instruments.py +198 -0
  135. wbfdm/filters/utils.py +44 -0
  136. wbfdm/import_export/__init__.py +0 -0
  137. wbfdm/import_export/backends/__init__.py +0 -0
  138. wbfdm/import_export/backends/cbinsights/__init__.py +2 -0
  139. wbfdm/import_export/backends/cbinsights/deals.py +44 -0
  140. wbfdm/import_export/backends/cbinsights/equities.py +41 -0
  141. wbfdm/import_export/backends/cbinsights/mixin.py +15 -0
  142. wbfdm/import_export/backends/cbinsights/utils/__init__.py +0 -0
  143. wbfdm/import_export/backends/cbinsights/utils/classifications.py +4150 -0
  144. wbfdm/import_export/backends/cbinsights/utils/client.py +217 -0
  145. wbfdm/import_export/backends/refinitiv/__init__.py +5 -0
  146. wbfdm/import_export/backends/refinitiv/daily_fundamental.py +36 -0
  147. wbfdm/import_export/backends/refinitiv/fiscal_period.py +63 -0
  148. wbfdm/import_export/backends/refinitiv/forecast.py +178 -0
  149. wbfdm/import_export/backends/refinitiv/fundamental.py +103 -0
  150. wbfdm/import_export/backends/refinitiv/geographic_segment.py +32 -0
  151. wbfdm/import_export/backends/refinitiv/instrument.py +55 -0
  152. wbfdm/import_export/backends/refinitiv/instrument_price.py +77 -0
  153. wbfdm/import_export/backends/refinitiv/mixin.py +29 -0
  154. wbfdm/import_export/backends/refinitiv/utils/__init__.py +1 -0
  155. wbfdm/import_export/backends/refinitiv/utils/controller.py +182 -0
  156. wbfdm/import_export/handlers/__init__.py +0 -0
  157. wbfdm/import_export/handlers/instrument.py +253 -0
  158. wbfdm/import_export/handlers/instrument_list.py +101 -0
  159. wbfdm/import_export/handlers/instrument_price.py +71 -0
  160. wbfdm/import_export/handlers/option.py +54 -0
  161. wbfdm/import_export/handlers/private_equities.py +49 -0
  162. wbfdm/import_export/parsers/__init__.py +0 -0
  163. wbfdm/import_export/parsers/cbinsights/__init__.py +0 -0
  164. wbfdm/import_export/parsers/cbinsights/deals.py +39 -0
  165. wbfdm/import_export/parsers/cbinsights/equities.py +56 -0
  166. wbfdm/import_export/parsers/cbinsights/fundamentals.py +45 -0
  167. wbfdm/import_export/parsers/refinitiv/__init__.py +0 -0
  168. wbfdm/import_export/parsers/refinitiv/daily_fundamental.py +7 -0
  169. wbfdm/import_export/parsers/refinitiv/forecast.py +7 -0
  170. wbfdm/import_export/parsers/refinitiv/fundamental.py +9 -0
  171. wbfdm/import_export/parsers/refinitiv/geographic_segment.py +7 -0
  172. wbfdm/import_export/parsers/refinitiv/instrument.py +75 -0
  173. wbfdm/import_export/parsers/refinitiv/instrument_price.py +26 -0
  174. wbfdm/import_export/parsers/refinitiv/utils.py +96 -0
  175. wbfdm/import_export/resources/__init__.py +0 -0
  176. wbfdm/import_export/resources/classification.py +23 -0
  177. wbfdm/import_export/resources/instrument_prices.py +33 -0
  178. wbfdm/import_export/resources/instruments.py +176 -0
  179. wbfdm/jinja2.py +7 -0
  180. wbfdm/management/__init__.py +30 -0
  181. wbfdm/menu.py +11 -0
  182. wbfdm/migrations/0001_initial.py +71 -0
  183. wbfdm/migrations/0002_rename_statements_instrumentlookup_financials_and_more.py +144 -0
  184. wbfdm/migrations/0003_instrument_estimate_backend_and_more.py +34 -0
  185. wbfdm/migrations/0004_rename_financials_instrumentlookup_statements_and_more.py +86 -0
  186. wbfdm/migrations/0005_instrument_corporate_action_backend.py +29 -0
  187. wbfdm/migrations/0006_instrument_officer_backend.py +29 -0
  188. wbfdm/migrations/0007_instrument_country_instrument_currency_and_more.py +117 -0
  189. wbfdm/migrations/0008_controversy.py +75 -0
  190. wbfdm/migrations/0009_alter_controversy_flag_alter_controversy_initiated_and_more.py +85 -0
  191. wbfdm/migrations/0010_classification_classificationgroup_deal_exchange_and_more.py +1299 -0
  192. wbfdm/migrations/0011_delete_instrumentlookup_instrument_corporate_actions_and_more.py +169 -0
  193. wbfdm/migrations/0012_instrumentprice_created_instrumentprice_modified.py +564 -0
  194. wbfdm/migrations/0013_instrument_is_investable_universe_and_more.py +199 -0
  195. wbfdm/migrations/0014_alter_controversy_instrument.py +22 -0
  196. wbfdm/migrations/0015_instrument_instrument_investible_index.py +16 -0
  197. wbfdm/migrations/0016_instrumenttype_name_repr.py +18 -0
  198. wbfdm/migrations/0017_instrument_instrument_security_index.py +16 -0
  199. wbfdm/migrations/0018_instrument_instrument_level_index.py +20 -0
  200. wbfdm/migrations/0019_alter_controversy_source.py +17 -0
  201. wbfdm/migrations/0020_optionaggregate_option_and_more.py +249 -0
  202. wbfdm/migrations/0021_delete_instrumentdailystatistics.py +15 -0
  203. wbfdm/migrations/0022_instrument_cusip_option_open_interest_20d_and_more.py +91 -0
  204. wbfdm/migrations/0023_instrument_unique_ric_instrument_unique_rmc_and_more.py +53 -0
  205. wbfdm/migrations/0024_option_open_interest_10d_option_volume_10d_and_more.py +36 -0
  206. wbfdm/migrations/0025_instrument_is_primary_and_more.py +29 -0
  207. wbfdm/migrations/0026_instrument_is_cash_equivalent.py +30 -0
  208. wbfdm/migrations/0027_remove_instrument_unique_ric_and_more.py +100 -0
  209. wbfdm/migrations/0028_instrumentprice_annualized_daily_volatility.py +17 -0
  210. wbfdm/migrations/__init__.py +0 -0
  211. wbfdm/models/__init__.py +4 -0
  212. wbfdm/models/esg/__init__.py +1 -0
  213. wbfdm/models/esg/controversies.py +81 -0
  214. wbfdm/models/exchanges/__init__.py +1 -0
  215. wbfdm/models/exchanges/exchanges.py +223 -0
  216. wbfdm/models/fields.py +117 -0
  217. wbfdm/models/fk_fields.py +403 -0
  218. wbfdm/models/indicators.py +0 -0
  219. wbfdm/models/instruments/__init__.py +19 -0
  220. wbfdm/models/instruments/classifications.py +265 -0
  221. wbfdm/models/instruments/instrument_lists.py +120 -0
  222. wbfdm/models/instruments/instrument_prices.py +544 -0
  223. wbfdm/models/instruments/instrument_relationships.py +251 -0
  224. wbfdm/models/instruments/instrument_requests.py +196 -0
  225. wbfdm/models/instruments/instruments.py +991 -0
  226. wbfdm/models/instruments/llm/__init__.py +1 -0
  227. wbfdm/models/instruments/llm/create_instrument_news_relationships.py +78 -0
  228. wbfdm/models/instruments/mixin/__init__.py +0 -0
  229. wbfdm/models/instruments/mixin/financials_computed.py +804 -0
  230. wbfdm/models/instruments/mixin/financials_serializer_fields.py +1407 -0
  231. wbfdm/models/instruments/mixin/instruments.py +297 -0
  232. wbfdm/models/instruments/options.py +225 -0
  233. wbfdm/models/instruments/private_equities.py +59 -0
  234. wbfdm/models/instruments/querysets.py +73 -0
  235. wbfdm/models/instruments/utils.py +41 -0
  236. wbfdm/preferences.py +21 -0
  237. wbfdm/serializers/__init__.py +4 -0
  238. wbfdm/serializers/esg.py +36 -0
  239. wbfdm/serializers/exchanges.py +39 -0
  240. wbfdm/serializers/instruments/__init__.py +37 -0
  241. wbfdm/serializers/instruments/classifications.py +139 -0
  242. wbfdm/serializers/instruments/instrument_lists.py +61 -0
  243. wbfdm/serializers/instruments/instrument_prices.py +73 -0
  244. wbfdm/serializers/instruments/instrument_relationships.py +170 -0
  245. wbfdm/serializers/instruments/instrument_requests.py +61 -0
  246. wbfdm/serializers/instruments/instruments.py +274 -0
  247. wbfdm/serializers/instruments/mixins.py +104 -0
  248. wbfdm/serializers/officers.py +20 -0
  249. wbfdm/signals.py +7 -0
  250. wbfdm/sync/__init__.py +0 -0
  251. wbfdm/sync/abstract.py +31 -0
  252. wbfdm/sync/runner.py +22 -0
  253. wbfdm/tasks.py +69 -0
  254. wbfdm/tests/__init__.py +0 -0
  255. wbfdm/tests/analysis/__init__.py +0 -0
  256. wbfdm/tests/analysis/financial_analysis/__init__.py +0 -0
  257. wbfdm/tests/analysis/financial_analysis/test_statement_with_estimates.py +392 -0
  258. wbfdm/tests/analysis/financial_analysis/test_utils.py +322 -0
  259. wbfdm/tests/analysis/test_esg.py +159 -0
  260. wbfdm/tests/conftest.py +92 -0
  261. wbfdm/tests/dataloaders/__init__.py +0 -0
  262. wbfdm/tests/dataloaders/test_cache.py +73 -0
  263. wbfdm/tests/models/__init__.py +0 -0
  264. wbfdm/tests/models/test_classifications.py +99 -0
  265. wbfdm/tests/models/test_exchanges.py +7 -0
  266. wbfdm/tests/models/test_instrument_list.py +117 -0
  267. wbfdm/tests/models/test_instrument_prices.py +306 -0
  268. wbfdm/tests/models/test_instruments.py +202 -0
  269. wbfdm/tests/models/test_merge.py +99 -0
  270. wbfdm/tests/models/test_options.py +69 -0
  271. wbfdm/tests/test_tasks.py +6 -0
  272. wbfdm/tests/tests.py +10 -0
  273. wbfdm/urls.py +222 -0
  274. wbfdm/utils.py +54 -0
  275. wbfdm/viewsets/__init__.py +10 -0
  276. wbfdm/viewsets/configs/__init__.py +5 -0
  277. wbfdm/viewsets/configs/buttons/__init__.py +8 -0
  278. wbfdm/viewsets/configs/buttons/classifications.py +23 -0
  279. wbfdm/viewsets/configs/buttons/exchanges.py +9 -0
  280. wbfdm/viewsets/configs/buttons/instrument_prices.py +49 -0
  281. wbfdm/viewsets/configs/buttons/instruments.py +283 -0
  282. wbfdm/viewsets/configs/display/__init__.py +22 -0
  283. wbfdm/viewsets/configs/display/classifications.py +138 -0
  284. wbfdm/viewsets/configs/display/esg.py +75 -0
  285. wbfdm/viewsets/configs/display/exchanges.py +42 -0
  286. wbfdm/viewsets/configs/display/instrument_lists.py +137 -0
  287. wbfdm/viewsets/configs/display/instrument_prices.py +199 -0
  288. wbfdm/viewsets/configs/display/instrument_requests.py +116 -0
  289. wbfdm/viewsets/configs/display/instruments.py +340 -0
  290. wbfdm/viewsets/configs/display/instruments_relationships.py +65 -0
  291. wbfdm/viewsets/configs/display/monthly_performances.py +72 -0
  292. wbfdm/viewsets/configs/display/officers.py +16 -0
  293. wbfdm/viewsets/configs/display/prices.py +21 -0
  294. wbfdm/viewsets/configs/display/statement_with_estimates.py +101 -0
  295. wbfdm/viewsets/configs/display/statements.py +48 -0
  296. wbfdm/viewsets/configs/endpoints/__init__.py +41 -0
  297. wbfdm/viewsets/configs/endpoints/classifications.py +87 -0
  298. wbfdm/viewsets/configs/endpoints/esg.py +20 -0
  299. wbfdm/viewsets/configs/endpoints/exchanges.py +6 -0
  300. wbfdm/viewsets/configs/endpoints/financials_analysis.py +65 -0
  301. wbfdm/viewsets/configs/endpoints/instrument_lists.py +38 -0
  302. wbfdm/viewsets/configs/endpoints/instrument_prices.py +51 -0
  303. wbfdm/viewsets/configs/endpoints/instrument_requests.py +20 -0
  304. wbfdm/viewsets/configs/endpoints/instruments.py +13 -0
  305. wbfdm/viewsets/configs/endpoints/instruments_relationships.py +31 -0
  306. wbfdm/viewsets/configs/endpoints/statements.py +6 -0
  307. wbfdm/viewsets/configs/menus/__init__.py +9 -0
  308. wbfdm/viewsets/configs/menus/classifications.py +19 -0
  309. wbfdm/viewsets/configs/menus/exchanges.py +10 -0
  310. wbfdm/viewsets/configs/menus/instrument_lists.py +10 -0
  311. wbfdm/viewsets/configs/menus/instruments.py +20 -0
  312. wbfdm/viewsets/configs/menus/instruments_relationships.py +33 -0
  313. wbfdm/viewsets/configs/titles/__init__.py +42 -0
  314. wbfdm/viewsets/configs/titles/classifications.py +79 -0
  315. wbfdm/viewsets/configs/titles/esg.py +11 -0
  316. wbfdm/viewsets/configs/titles/exchanges.py +12 -0
  317. wbfdm/viewsets/configs/titles/financial_ratio_analysis.py +6 -0
  318. wbfdm/viewsets/configs/titles/financials_analysis.py +50 -0
  319. wbfdm/viewsets/configs/titles/instrument_prices.py +50 -0
  320. wbfdm/viewsets/configs/titles/instrument_requests.py +16 -0
  321. wbfdm/viewsets/configs/titles/instruments.py +31 -0
  322. wbfdm/viewsets/configs/titles/instruments_relationships.py +21 -0
  323. wbfdm/viewsets/configs/titles/market_data.py +13 -0
  324. wbfdm/viewsets/configs/titles/prices.py +15 -0
  325. wbfdm/viewsets/configs/titles/statement_with_estimates.py +10 -0
  326. wbfdm/viewsets/esg.py +72 -0
  327. wbfdm/viewsets/exchanges.py +63 -0
  328. wbfdm/viewsets/financial_analysis/__init__.py +3 -0
  329. wbfdm/viewsets/financial_analysis/financial_metric_analysis.py +85 -0
  330. wbfdm/viewsets/financial_analysis/financial_ratio_analysis.py +85 -0
  331. wbfdm/viewsets/financial_analysis/statement_with_estimates.py +145 -0
  332. wbfdm/viewsets/instruments/__init__.py +80 -0
  333. wbfdm/viewsets/instruments/classifications.py +279 -0
  334. wbfdm/viewsets/instruments/financials_analysis.py +614 -0
  335. wbfdm/viewsets/instruments/instrument_lists.py +77 -0
  336. wbfdm/viewsets/instruments/instrument_prices.py +542 -0
  337. wbfdm/viewsets/instruments/instrument_requests.py +51 -0
  338. wbfdm/viewsets/instruments/instruments.py +106 -0
  339. wbfdm/viewsets/instruments/instruments_relationships.py +235 -0
  340. wbfdm/viewsets/instruments/utils.py +27 -0
  341. wbfdm/viewsets/market_data.py +172 -0
  342. wbfdm/viewsets/mixins.py +9 -0
  343. wbfdm/viewsets/officers.py +27 -0
  344. wbfdm/viewsets/prices.py +62 -0
  345. wbfdm/viewsets/statements/__init__.py +1 -0
  346. wbfdm/viewsets/statements/statements.py +100 -0
  347. wbfdm/viewsets/technical_analysis/__init__.py +1 -0
  348. wbfdm/viewsets/technical_analysis/monthly_performances.py +93 -0
  349. wbfdm-1.43.1.dist-info/METADATA +15 -0
  350. wbfdm-1.43.1.dist-info/RECORD +351 -0
  351. wbfdm-1.43.1.dist-info/WHEEL +5 -0
@@ -0,0 +1,265 @@
1
+ from typing import List, Optional
2
+
3
+ import roman
4
+ from django.contrib.postgres.expressions import ArraySubquery
5
+ from django.db import models
6
+ from django.db.models.functions import Cast
7
+ from django.db.models.signals import post_save
8
+ from django.dispatch import receiver
9
+ from mptt.models import MPTTModel, TreeForeignKey
10
+ from wbcore.models import WBModel
11
+ from wbcore.utils.models import ComplexToStringMixin
12
+
13
+ from .instrument_relationships import InstrumentClassificationThroughModel
14
+
15
+
16
+ class ClassificationGroup(WBModel):
17
+ name = models.CharField(max_length=128, verbose_name="Name")
18
+ is_primary = models.BooleanField(
19
+ default=False,
20
+ verbose_name="Primary",
21
+ help_text="Set to True if this " "classification must be used as " "default if not specified " "otherwise",
22
+ )
23
+ max_depth = models.IntegerField(default=0, verbose_name="Maximum Depth")
24
+ code_level_digits = models.IntegerField(default=2, verbose_name="The number of digits per code level")
25
+
26
+ def __str__(self) -> str:
27
+ return f'{self.name} ({"Primary" if self.is_primary else "Non Primary"})'
28
+
29
+ def save(self, *args, **kwargs):
30
+ qs = ClassificationGroup.objects.filter(is_primary=True).exclude(id=self.id)
31
+ if self.is_primary:
32
+ qs.update(is_primary=False)
33
+ elif not qs.exists():
34
+ self.is_primary = True
35
+ return super().save(*args, **kwargs)
36
+
37
+ def get_levels_representation(self) -> List[str]:
38
+ return [
39
+ self.classifications.filter(height=i).first().level_representation
40
+ for i in range(self.max_depth + 1)
41
+ if self.classifications.filter(height=i).exists()
42
+ ]
43
+
44
+ def get_fields_names(self, sep: str = "__") -> list[str]:
45
+ return [f"parent{f'{sep}parent' * height}" for height in range(self.max_depth)]
46
+
47
+ def annotate_queryset(
48
+ self,
49
+ queryset: models.QuerySet,
50
+ classification_height: int,
51
+ instrument_label_key: str,
52
+ unique: bool = False,
53
+ annotation_label: str = "classifications",
54
+ ):
55
+ ref_id = "classification__"
56
+ if classification_height:
57
+ ref_id += f"{'parent__' * classification_height}"
58
+ ref_id += "id"
59
+ if instrument_label_key:
60
+ instrument_label_key += "__"
61
+ base_subquery = InstrumentClassificationThroughModel.objects.filter(
62
+ classification__group=self,
63
+ instrument__tree_id=models.OuterRef(f"{instrument_label_key}tree_id"),
64
+ instrument__lft__lte=models.OuterRef(f"{instrument_label_key}lft"),
65
+ instrument__rght__gte=models.OuterRef(f"{instrument_label_key}rght"),
66
+ )
67
+ if unique:
68
+ expression = models.Subquery(base_subquery.values(ref_id)[:1])
69
+ else:
70
+ expression = ArraySubquery(
71
+ base_subquery.values(ref_id).distinct(ref_id)
72
+ ) # we use distinct in order to remove duplicated classification (e.g. classification added to the parent as well as the children)
73
+
74
+ return queryset.annotate(**{annotation_label: expression})
75
+
76
+ class Meta:
77
+ verbose_name = "Classification Group"
78
+ verbose_name_plural = "Classification Groups"
79
+
80
+ @classmethod
81
+ def get_representation_endpoint(cls) -> str:
82
+ return "wbfdm:classificationgrouprepresentation-list"
83
+
84
+ @classmethod
85
+ def get_representation_value_key(cls) -> str:
86
+ return "id"
87
+
88
+ @classmethod
89
+ def get_representation_label_key(cls) -> str:
90
+ return "{{name}}"
91
+
92
+ @classmethod
93
+ def get_endpoint_basename(cls) -> str:
94
+ return "wbfdm:classificationgroup"
95
+
96
+
97
+ class Classification(MPTTModel, ComplexToStringMixin):
98
+ parent = TreeForeignKey(
99
+ "self",
100
+ related_name="children",
101
+ null=True,
102
+ blank=True,
103
+ on_delete=models.CASCADE,
104
+ verbose_name="Parent Classification",
105
+ )
106
+ height = models.PositiveIntegerField(
107
+ default=0,
108
+ verbose_name="The height (leaf node have height 0)",
109
+ )
110
+ group = models.ForeignKey(
111
+ ClassificationGroup,
112
+ related_name="classifications",
113
+ on_delete=models.CASCADE,
114
+ verbose_name="Classification Group",
115
+ )
116
+
117
+ level_representation = models.CharField(max_length=256, verbose_name="Level Representation")
118
+
119
+ name = models.CharField(max_length=128, verbose_name="Name")
120
+ code_aggregated = models.CharField(max_length=64, verbose_name="Code Aggregated")
121
+
122
+ investable = models.BooleanField(default=True, help_text="Is this classification investable for us?")
123
+
124
+ description = models.TextField(
125
+ default="",
126
+ blank=True,
127
+ help_text="Give a basic definition and description",
128
+ verbose_name="Definition/Description",
129
+ )
130
+
131
+ @classmethod
132
+ def dict_to_model(cls, classification_data):
133
+ if isinstance(classification_data, int):
134
+ return cls.objects.filter(id=classification_data).first()
135
+ res = cls.objects.all()
136
+ if code_aggregated := classification_data.get("code_aggregated", None):
137
+ res = res.filter(code_aggregated=code_aggregated)
138
+ if group_id := classification_data.get("group", None):
139
+ res = res.filter(group=group_id)
140
+ if res.count() == 1:
141
+ return res.first()
142
+
143
+ def __str__(self):
144
+ if self.computed_str:
145
+ return self.computed_str
146
+ return f"{self.code_aggregated} {self.name}"
147
+
148
+ def get_classified_instruments(self, only_favorites: bool = False) -> models.QuerySet:
149
+ childs_classifications = self.get_descendants(include_self=True)
150
+ params = {"classifications__in": childs_classifications}
151
+ if only_favorites:
152
+ params["classifications_through__is_favorite"] = True
153
+
154
+ from wbfdm.models import Instrument
155
+
156
+ return Instrument.objects.filter(**params).distinct()
157
+
158
+ def save(self, *args, **kwargs):
159
+ if self.parent:
160
+ self.group = self.parent.group
161
+ if not self.code_aggregated:
162
+ self.code_aggregated = self.get_next_valid_code(self.group, self.parent)
163
+ if not self.level_representation:
164
+ self.level_representation = self.get_default_level_representation(self.group, self.parent)
165
+ return super().save(*args, **kwargs)
166
+
167
+ def compute_str(self) -> str:
168
+ if parent := self.parent:
169
+ tree_titles = parent.name
170
+ while parent and (parent := parent.parent):
171
+ tree_titles += f" - {parent.name}"
172
+ return f"{self.code_aggregated} {self.name} [{tree_titles}] ({self.group.name})"
173
+ return f"{self.code_aggregated} {self.name} ({self.group.name})"
174
+
175
+ class Meta:
176
+ verbose_name = "Classification"
177
+ verbose_name_plural = "Classifications"
178
+ constraints = [models.UniqueConstraint(fields=["group", "code_aggregated"], name="unique_classification")]
179
+
180
+ @classmethod
181
+ def get_next_valid_code(cls, group: "ClassificationGroup", parent: "Classification | None" = None) -> str:
182
+ """
183
+ Return the next valid classification code given the classification parent and its group parameters
184
+ Args:
185
+ group: The classification group the estimated code belongs to
186
+ parent: The classification parent (if any)
187
+
188
+ Returns:
189
+ The next valid and unused aggregated classification code
190
+ """
191
+ if not group:
192
+ raise ValueError("This method needs a group")
193
+ siblings_classifications = (
194
+ Classification.objects.filter(parent=parent, group=group)
195
+ .annotate(casted_code=Cast("code_aggregated", models.BigIntegerField()))
196
+ .order_by("-casted_code")
197
+ )
198
+ parent_level = parent.level + 1 if parent else 0
199
+ code_aggregated_digits = group.code_level_digits * (parent_level + 1)
200
+ if last_classification := siblings_classifications.first():
201
+ for i in range(0, 100 - last_classification.casted_code % 10**group.code_level_digits):
202
+ if not siblings_classifications.filter(casted_code=last_classification.casted_code + i).exists():
203
+ last_valid_code = last_classification.casted_code + i
204
+ return f"{last_valid_code:0{code_aggregated_digits}}"
205
+ if parent:
206
+ return parent.code_aggregated + f"{1:0{group.code_level_digits}}"
207
+ return f"{1:0{group.code_level_digits}}"
208
+
209
+ @classmethod
210
+ def get_default_level_representation(
211
+ cls, group: "ClassificationGroup", parent: Optional["Classification"] = None
212
+ ) -> str:
213
+ """
214
+ Return the default level representation, extracted from the classification siblings
215
+ Args:
216
+ group: The classification group the estimated code belongs to
217
+ parent: The classification parent (if any)
218
+
219
+ Returns:
220
+ A default level representation (e.g. Industry)
221
+ """
222
+ level = parent.level + 1 if parent else None
223
+ siblings_classifications = cls.objects.filter(level=level, group=group).order_by("id")
224
+ if siblings_classifications.exists():
225
+ return siblings_classifications.last().level_representation
226
+ level = roman.toRoman(level) if level else 0
227
+ return f"Level {level}"
228
+
229
+ @classmethod
230
+ def get_representation_endpoint(cls) -> str:
231
+ return "wbfdm:classificationrepresentation-list"
232
+
233
+ @classmethod
234
+ def get_representation_value_key(cls) -> str:
235
+ return "id"
236
+
237
+ @classmethod
238
+ def get_endpoint_basename(cls) -> str:
239
+ return "wbfdm:classification"
240
+
241
+
242
+ @receiver(post_save, sender="wbfdm.Classification")
243
+ def post_save_parent_classification(sender, instance, created, raw, **kwargs):
244
+ # Recursively call the parent save function to recompute its parameters
245
+ if not raw and instance.parent:
246
+ instance.parent.save()
247
+ if instance.level is not None:
248
+ update_fields = {"height": instance.group.max_depth - instance.level}
249
+ Classification.objects.filter(id=instance.id).update(**update_fields)
250
+ instance.refresh_from_db()
251
+ # # Ensure initial parent classifcation span the proper classification tree structure
252
+ if instance.group and not instance.get_descendants().exists() and instance.level < instance.group.max_depth:
253
+ Classification.objects.create(
254
+ parent=instance,
255
+ group=instance.group,
256
+ name=f"{instance.name} (Level {instance.level})",
257
+ )
258
+ """
259
+ If a parent classification is not investable, therefore its "children" become non investable too, by cascade.
260
+ If a child classification becomes investable and one of its parent is non investable, therefore he cannot be
261
+ investable as long as its parent is non investable. (-> in the serializer)
262
+ """
263
+ if not raw and not instance.investable and instance.get_descendants().exists():
264
+ descandants = instance.get_descendants()
265
+ descandants.update(investable=False)
@@ -0,0 +1,120 @@
1
+ from django.db import models
2
+ from slugify import slugify
3
+ from wbcore.contrib.io.mixins import ImportMixin
4
+ from wbcore.models import WBModel
5
+ from wbcore.utils.models import ComplexToStringMixin
6
+ from wbfdm.import_export.handlers.instrument_list import InstrumentListImportHandler
7
+ from wbfdm.models.instruments.instruments import Instrument
8
+
9
+
10
+ class InstrumentListThroughModel(ImportMixin, ComplexToStringMixin):
11
+ import_export_handler_class = InstrumentListImportHandler
12
+ """
13
+ This model is not a Through model from a programming point of view, however it allows to link instrument list to
14
+ instruments.
15
+ """
16
+
17
+ instrument_str = models.CharField(max_length=256)
18
+ instrument = models.ForeignKey(
19
+ to="wbfdm.Instrument",
20
+ null=True,
21
+ blank=True,
22
+ limit_choices_to=models.Q(is_security=True),
23
+ on_delete=models.SET_NULL,
24
+ )
25
+ instrument_list = models.ForeignKey(
26
+ to="wbfdm.InstrumentList",
27
+ on_delete=models.CASCADE,
28
+ )
29
+
30
+ from_date = models.DateField(null=True, blank=True)
31
+ to_date = models.DateField(null=True, blank=True)
32
+ comment = models.TextField(default="", blank=True)
33
+ validated = models.BooleanField(default=False)
34
+
35
+ def compute_str(self) -> str:
36
+ """
37
+ Method to compute the string representation of the instance. It will save the string value to the computed_str
38
+ field.
39
+
40
+ Returns:
41
+ The string representation of the instance.
42
+ """
43
+ if self.instrument and self.instrument.name_repr:
44
+ return f"{self.instrument.name_repr} - {self.instrument_list.name}"
45
+ return f"{self.instrument_str} - {self.instrument_list.name}"
46
+
47
+ class Meta:
48
+ verbose_name = "Instrument in Instrument List"
49
+ constraints = [
50
+ models.UniqueConstraint(fields=["instrument", "instrument_list"], name="unique_instrument_per_list")
51
+ ]
52
+
53
+ notification_types = [
54
+ (
55
+ "wbfdm.instrument_list_add",
56
+ "Instrument added to Instrument List",
57
+ "A notification when an instrument gets added to a list.",
58
+ True,
59
+ True,
60
+ True,
61
+ ),
62
+ ]
63
+
64
+
65
+ class InstrumentList(WBModel):
66
+ class InstrumentListType(models.TextChoices):
67
+ WATCH = "WATCH", "Watch List"
68
+ EXCLUSION = "EXCLUSION", "Exclusion List"
69
+
70
+ name = models.CharField(max_length=255)
71
+ identifier = models.CharField(max_length=255, unique=True, blank=True)
72
+ instrument_list_type = models.CharField(max_length=32, choices=InstrumentListType.choices, null=True, blank=True)
73
+
74
+ def __str__(self):
75
+ return self.name
76
+
77
+ @property
78
+ def instruments(self) -> models.QuerySet[Instrument]:
79
+ """
80
+ Returns a QuerySet of Instrument objects associated with the current instrument list.
81
+
82
+ This property filters the Instrument objects based on the related InstrumentListThroughModel
83
+ and returns only those Instruments where the foreign key is not null.
84
+
85
+ Returns:
86
+ models.QuerySet[Instrument]: A QuerySet of Instrument objects.
87
+ """
88
+ return Instrument.objects.filter(
89
+ id__in=(
90
+ InstrumentListThroughModel.objects.filter(instrument_list=self, instrument__isnull=False).values(
91
+ "instrument"
92
+ )
93
+ )
94
+ )
95
+
96
+ def save(self, *args, **kwargs):
97
+ if not self.identifier:
98
+ self.identifier = slugify(f"{self.name}-{self.id}")
99
+ super().save(*args, **kwargs)
100
+
101
+ class Meta:
102
+ verbose_name = "Instrument List"
103
+ verbose_name_plural = "Instrument Lists"
104
+ permissions = (("administrate_instrumentlist", "Can administrate Instrument List"),)
105
+
106
+ @classmethod
107
+ def get_endpoint_basename(cls):
108
+ return "wbfdm:instrumentlist"
109
+
110
+ @classmethod
111
+ def get_representation_endpoint(cls):
112
+ return "wbfdm:instrumentlistrepresentation-list"
113
+
114
+ @classmethod
115
+ def get_representation_value_key(cls):
116
+ return "id"
117
+
118
+ @classmethod
119
+ def get_representation_label_key(cls):
120
+ return "{{name}}"