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,544 @@
1
+ from contextlib import suppress
2
+ from decimal import Decimal
3
+
4
+ import pandas as pd
5
+ from celery import shared_task
6
+ from django.db import models, transaction
7
+ from django.db.models import (
8
+ Case,
9
+ DecimalField,
10
+ Exists,
11
+ ExpressionWrapper,
12
+ F,
13
+ OuterRef,
14
+ Q,
15
+ QuerySet,
16
+ Subquery,
17
+ Value,
18
+ When,
19
+ )
20
+ from django.db.models.signals import post_save
21
+ from django.dispatch import receiver
22
+ from wbcore.contrib.currency.models import CurrencyFXRates
23
+ from wbcore.contrib.io.mixins import ImportMixin
24
+ from wbcore.models import DynamicDecimalField, DynamicFloatField, DynamicModel, WBModel
25
+ from wbcore.signals import pre_merge
26
+ from wbfdm.analysis.financial_analysis.financial_statistics_analysis import (
27
+ FinancialStatistics,
28
+ )
29
+ from wbfdm.import_export.handlers.instrument_price import InstrumentPriceImportHandler
30
+
31
+ from .mixin.financials_computed import InstrumentPriceComputedMixin
32
+
33
+
34
+ class ValidPricesQueryset(QuerySet):
35
+ def filter_only_valid_prices(self) -> QuerySet:
36
+ """
37
+ Filter the queryset to remove duplicate in case calculated and non-calculated prices are present for the same date/product/type
38
+ """
39
+ return self.annotate(
40
+ real_price_exists=Exists(
41
+ self.filter(
42
+ instrument=OuterRef("instrument"),
43
+ date=OuterRef("date"),
44
+ calculated=False,
45
+ )
46
+ )
47
+ ).filter(Q(calculated=False) | (Q(real_price_exists=False) & Q(calculated=True)))
48
+
49
+ def annotate_market_data(self):
50
+ base_qs = ValidPricesQueryset(self.model, using=self._db)
51
+
52
+ return self.annotate(
53
+ currency_fx_rate_to_usd_rate=F("currency_fx_rate_to_usd__value"),
54
+ calculated_outstanding_shares=Subquery(
55
+ base_qs.filter(instrument=OuterRef("instrument"), calculated=True, date=OuterRef("date")).values(
56
+ "outstanding_shares"
57
+ )[:1]
58
+ ),
59
+ internal_outstanding_shares=ExpressionWrapper(
60
+ Case( # Annotate the parent security if exists
61
+ When(
62
+ outstanding_shares__isnull=False,
63
+ then=F("outstanding_shares"),
64
+ ),
65
+ default=F("calculated_outstanding_shares"),
66
+ ),
67
+ output_field=DecimalField(max_digits=4),
68
+ ),
69
+ internal_market_capitalization=Case(
70
+ When(
71
+ market_capitalization__isnull=True,
72
+ then=ExpressionWrapper(
73
+ F("internal_outstanding_shares") * F("net_value"),
74
+ output_field=DecimalField(max_digits=4),
75
+ ),
76
+ ),
77
+ default=ExpressionWrapper(F("market_capitalization"), output_field=DecimalField(max_digits=4)),
78
+ ),
79
+ internal_market_capitalization_usd=F("internal_market_capitalization") / F("currency_fx_rate_to_usd_rate"),
80
+ calculated_volume=Subquery(
81
+ base_qs.filter(instrument=OuterRef("instrument"), calculated=True, date=OuterRef("date")).values(
82
+ "volume"
83
+ )[:1]
84
+ ),
85
+ internal_volume=ExpressionWrapper(
86
+ Case( # Annotate the parent security if exists
87
+ When(
88
+ volume__isnull=False,
89
+ then=F("volume"),
90
+ ),
91
+ default=F("calculated_volume"),
92
+ ),
93
+ output_field=models.FloatField(),
94
+ ),
95
+ )
96
+
97
+ def annotate_base_data(self):
98
+ return self.annotate(
99
+ currency_fx_rate_to_usd_rate=F("currency_fx_rate_to_usd__value"),
100
+ market_capitalization_usd=ExpressionWrapper(
101
+ F("market_capitalization") / F("currency_fx_rate_to_usd_rate"), output_field=DecimalField(max_digits=4)
102
+ ),
103
+ net_value_usd=F("net_value") / F("currency_fx_rate_to_usd_rate"),
104
+ gross_value_usd=F("gross_value") / F("currency_fx_rate_to_usd_rate"),
105
+ volume_usd=ExpressionWrapper(
106
+ F("volume") * F("net_value") / F("currency_fx_rate_to_usd_rate"), output_field=DecimalField()
107
+ ),
108
+ volume_50d_usd=ExpressionWrapper(
109
+ F("volume_50d") * F("net_value") / F("currency_fx_rate_to_usd_rate"), output_field=DecimalField()
110
+ ),
111
+ volume_200d_usd=ExpressionWrapper(
112
+ F("volume_200d") * F("net_value") / F("currency_fx_rate_to_usd_rate"), output_field=DecimalField()
113
+ ),
114
+ )
115
+
116
+ def annotate_security_data(self):
117
+ return self.annotate(
118
+ security=Case( # Annotate the parent security if exists
119
+ When(instrument__parent__isnull=False, then=F("instrument__parent")),
120
+ default=F("instrument"),
121
+ ),
122
+ security_instrument_type_key=Case( # Annotate the parent security if exists
123
+ When(
124
+ instrument__parent__isnull=False,
125
+ then=F("instrument__parent__instrument_type__key"),
126
+ ),
127
+ default=F("instrument__instrument_type__key"),
128
+ ),
129
+ )
130
+
131
+ def annotate_all(self):
132
+ return self.annotate_market_data().annotate_security_data().annotate_base_data()
133
+
134
+
135
+ class InstrumentPriceManager(models.Manager):
136
+ def __init__(self, with_annotation: bool = False, *args, **kwargs):
137
+ self.with_annotation = with_annotation
138
+ super().__init__(*args, **kwargs)
139
+
140
+ def get_queryset(self) -> ValidPricesQueryset:
141
+ qs = ValidPricesQueryset(self.model)
142
+ if self.with_annotation:
143
+ qs = qs.annotate_all()
144
+ return qs
145
+
146
+ def filtered_by_instruments(self, instrument_queryset, *other_instruments):
147
+ return self.filter(models.Q(instrument__in=instrument_queryset) | models.Q(instrument__in=other_instruments))
148
+
149
+ def filter_only_valid_prices(self) -> QuerySet:
150
+ return self.get_queryset().filter_only_valid_prices()
151
+
152
+ def annotate_market_data(self) -> QuerySet:
153
+ return self.get_queryset().annotate_market_data()
154
+
155
+ def annotate_base_data(self) -> QuerySet:
156
+ return self.get_queryset().annotate_base_data()
157
+
158
+ def annotate_security_data(self) -> QuerySet:
159
+ return self.get_queryset().annotate_security_data()
160
+
161
+ def annotate_all(self) -> QuerySet:
162
+ return self.get_queryset().annotate_all()
163
+
164
+
165
+ class AnnotatedInstrumentPriceManager(InstrumentPriceManager):
166
+ def get_queryset(self):
167
+ return super().get_queryset().annotate_market_data().annotate_base_data().annotate_security_data()
168
+
169
+
170
+ class InstrumentPrice(
171
+ ImportMixin,
172
+ InstrumentPriceComputedMixin,
173
+ DynamicModel,
174
+ WBModel,
175
+ ):
176
+ import_export_handler_class = InstrumentPriceImportHandler
177
+ # Base fields
178
+ instrument = models.ForeignKey(
179
+ to="wbfdm.Instrument",
180
+ related_name="prices",
181
+ on_delete=models.PROTECT,
182
+ limit_choices_to=models.Q(children__isnull=True),
183
+ verbose_name="Instrument",
184
+ blank=True,
185
+ null=True,
186
+ )
187
+ date = models.DateField(verbose_name="Date")
188
+ calculated = models.BooleanField(default=False, verbose_name="Is Calculated")
189
+
190
+ net_value = models.DecimalField(max_digits=16, decimal_places=6, verbose_name="Value (Net)")
191
+ gross_value = DynamicDecimalField(
192
+ max_digits=16,
193
+ decimal_places=6,
194
+ verbose_name="Value (Gross)",
195
+ ) # TODO: I think we need to remove this field that is not really used here.
196
+
197
+ outstanding_shares = DynamicDecimalField(
198
+ decimal_places=4,
199
+ max_digits=16,
200
+ verbose_name="Outstanding Shares",
201
+ help_text="The amount of outstanding share for this instrument",
202
+ )
203
+ outstanding_shares_consolidated = DynamicDecimalField(
204
+ decimal_places=4,
205
+ max_digits=16,
206
+ verbose_name="Outstanding Shares (Consolidated)",
207
+ help_text="The amount of outstanding share for this instrument",
208
+ )
209
+ ########################################################
210
+ # ASSET STATISTICS #
211
+ ########################################################
212
+
213
+ volume = models.FloatField(
214
+ null=True,
215
+ blank=True,
216
+ verbose_name="Volume",
217
+ help_text="The Volume of the Asset on the price date of the Asset.",
218
+ )
219
+
220
+ volume_50d = DynamicFloatField(
221
+ verbose_name="Average Volume (50 Days)",
222
+ help_text="The Average Volume of the Asset over the last 50 days from the price date of the Asset.",
223
+ )
224
+
225
+ volume_200d = models.FloatField(
226
+ null=True,
227
+ blank=True,
228
+ verbose_name="Average Volume (200 Days)",
229
+ help_text="The Average Volume of the Asset over the last 200 days from the price date of the Asset.",
230
+ )
231
+ market_capitalization = models.FloatField(
232
+ null=True,
233
+ blank=True,
234
+ verbose_name="Market Capitalization",
235
+ help_text="The Market Capitalization of the Asset the price date of the Asset.",
236
+ )
237
+ market_capitalization_consolidated = models.FloatField(
238
+ null=True,
239
+ blank=True,
240
+ verbose_name="Market Capitalization (Consolidated)",
241
+ help_text="the consolidated market value of a company in local currency.",
242
+ )
243
+
244
+ currency_fx_rate_to_usd = models.ForeignKey(
245
+ to="currency.CurrencyFXRates",
246
+ related_name="instrument_prices",
247
+ on_delete=models.PROTECT,
248
+ blank=True,
249
+ null=True,
250
+ verbose_name="Instrument Currency Rate",
251
+ help_text="Rate to between instrument currency and USD",
252
+ )
253
+
254
+ # Statistics
255
+ lock_statistics = models.BooleanField(
256
+ default=False,
257
+ help_text="If True, a save will not override the beta, correlation and sharpe ratio",
258
+ )
259
+ sharpe_ratio = models.FloatField(blank=True, null=True, verbose_name="Sharpe Ratio")
260
+ correlation = models.FloatField(blank=True, null=True, verbose_name="Correlation")
261
+ beta = models.FloatField(blank=True, null=True, verbose_name="Beta")
262
+ annualized_daily_volatility = models.FloatField(blank=True, null=True, verbose_name="Annualized Volatility")
263
+
264
+ created = models.DateTimeField(auto_now_add=True, verbose_name="Created")
265
+ modified = models.DateTimeField(
266
+ verbose_name="Modified",
267
+ auto_now=True,
268
+ )
269
+ # custom_beta_180d = DynamicFloatField(verbose_name="Custom Beta (180 days)")
270
+ # custom_beta_1y = DynamicFloatField(verbose_name="Custom Beta (1 Years)")
271
+ # custom_beta_2y = DynamicFloatField(verbose_name="Custom Beta (2 Years)")
272
+ # custom_beta_3y = DynamicFloatField(verbose_name="Custom Beta (3 Years)")
273
+ # custom_beta_5y = DynamicFloatField(verbose_name="Custom Beta (4 Years)")
274
+ #
275
+ # # Performances fields
276
+ # performance_1d = DynamicDecimalField(
277
+ # verbose_name="Performance 1D", help_text="Performance 1D", max_digits=16, decimal_places=6
278
+ # )
279
+ # performance_7d = DynamicDecimalField(
280
+ # verbose_name="Performance (1W)", help_text="Performance 7 days rolling", max_digits=16, decimal_places=6
281
+ # )
282
+ # performance_30d = DynamicDecimalField(
283
+ # verbose_name="Performance (1M)", help_text="Performance 30 days rolling", max_digits=16, decimal_places=6
284
+ # )
285
+ # performance_90d = DynamicDecimalField(
286
+ # verbose_name="Performance (1Q)", help_text="Performance 90 days rolling", max_digits=16, decimal_places=6
287
+ # )
288
+ # performance_365d = DynamicDecimalField(
289
+ # verbose_name="Performance (1Y)", help_text="Performance 365 days rolling", max_digits=16, decimal_places=6
290
+ # )
291
+ # performance_ytd = DynamicDecimalField(
292
+ # verbose_name="Performance (YTD)", help_text="Performance Year-to-date", max_digits=16, decimal_places=6
293
+ # )
294
+ # performance_inception = DynamicDecimalField(
295
+ # verbose_name="Performance (Inception)",
296
+ # help_text="Performance since inception",
297
+ # max_digits=16,
298
+ # decimal_places=6,
299
+ # )
300
+
301
+ objects = InstrumentPriceManager()
302
+ annotated_objects = InstrumentPriceManager(with_annotation=True)
303
+
304
+ class Meta:
305
+ verbose_name = "Instrument Price"
306
+ verbose_name_plural = "Instrument Prices"
307
+ constraints = [
308
+ models.CheckConstraint(
309
+ check=~models.Q(date__week_day__in=[1, 7]),
310
+ name="%(app_label)s_%(class)s_weekday_constraint",
311
+ ),
312
+ models.UniqueConstraint(fields=["instrument", "date", "calculated"], name="unique_price"),
313
+ ]
314
+ indexes = [
315
+ models.Index(
316
+ name="fdm_instrumentprice_base_idx",
317
+ fields=["calculated", "date", "instrument"],
318
+ ),
319
+ models.Index(
320
+ name="fdm_instrumentprice_idx1",
321
+ fields=["calculated", "instrument"],
322
+ ),
323
+ models.Index(
324
+ name="fdm_instrumentprice_idx2",
325
+ fields=["instrument"],
326
+ ),
327
+ ]
328
+
329
+ @property
330
+ def _net_value_usd(self):
331
+ if self.currency_fx_rate_to_usd:
332
+ return getattr(self, "net_value_usd", self.net_value * self.currency_fx_rate_to_usd.value)
333
+
334
+ def save(self, *args, **kwargs):
335
+ if not self.currency_fx_rate_to_usd:
336
+ with suppress(CurrencyFXRates.DoesNotExist):
337
+ self.currency_fx_rate_to_usd = CurrencyFXRates.objects.get(
338
+ date=self.date, currency=self.instrument.currency
339
+ )
340
+
341
+ if self.market_capitalization_consolidated is None:
342
+ self.market_capitalization_consolidated = self.market_capitalization
343
+
344
+ super().save(*args, **kwargs)
345
+
346
+ def __str__(self):
347
+ return f"{self.instrument.name}: {self.net_value} {self.date:%d.%m.%Y}"
348
+
349
+ @property
350
+ def previous_price(self):
351
+ """Returns the previous instrument prices if one exists or None
352
+
353
+ Returns:
354
+ instrument.InstrumentPrice | None -- Previous InstrumentPrice
355
+ """
356
+ try:
357
+ return InstrumentPrice.objects.filter(
358
+ instrument=self.instrument, date__lt=self.date, calculated=self.calculated
359
+ ).latest("date")
360
+ except InstrumentPrice.DoesNotExist:
361
+ return None
362
+
363
+ @property
364
+ def next_price(self):
365
+ """Returns the next instrument prices if one exists or None
366
+
367
+ Returns:
368
+ instrument.InstrumentPrice | None -- Next InstrumentPrice
369
+ """
370
+ try:
371
+ return self.instrument.prices.filter(date__gt=self.date, calculated=self.calculated).earliest("date")
372
+ except InstrumentPrice.DoesNotExist:
373
+ return None
374
+
375
+ @property
376
+ def shares(self):
377
+ """Returns the number of shares of a instrument
378
+
379
+ The number of shares are the sum of all customer trades
380
+
381
+ Returns:
382
+ int -- Shares
383
+ """
384
+ return self.instrument.total_shares(self.date)
385
+
386
+ @property
387
+ def valid_outstanding_shares(self):
388
+ prices = self.instrument.prices.filter(date=self.date, outstanding_shares__isnull=False).order_by("calculated")
389
+ return prices.last().outstanding_shares if prices.exists() else self.shares
390
+
391
+ @property
392
+ def nominal_value(self):
393
+ """Returns the nominal value of a instrument
394
+
395
+ The nominal value is the number of current shares multiplied by the share price of a instrument
396
+
397
+ Returns:
398
+ int -- Nominal Value
399
+ """
400
+ return self.instrument.nominal_value(self.date)
401
+
402
+ def compute_and_update_statistics(self, min_period: int = 20):
403
+ prices_df = (
404
+ pd.DataFrame(
405
+ InstrumentPrice.objects.filter_only_valid_prices()
406
+ .filter(instrument=self.instrument, date__lte=self.date)
407
+ .values_list("date", "net_value"),
408
+ columns=["date", "net_value"],
409
+ )
410
+ .set_index("date")
411
+ .sort_index()["net_value"]
412
+ )
413
+ if not prices_df.empty and prices_df.shape[0] >= min_period:
414
+ financial_statistics = FinancialStatistics(prices_df)
415
+ min_date = prices_df.index.min()
416
+ if risk_free_rate := self.instrument.primary_risk_instrument:
417
+ risk_free_rate_df = (
418
+ pd.DataFrame(
419
+ risk_free_rate.valuations.filter(date__gte=min_date, date__lte=self.date).values_list(
420
+ "date", "net_value"
421
+ ),
422
+ columns=["date", "net_value"],
423
+ )
424
+ .set_index("date")
425
+ .sort_index()["net_value"]
426
+ )
427
+ if sharpe_ratio := financial_statistics.get_sharpe_ratio(risk_free_rate_df):
428
+ self.sharpe_ratio = sharpe_ratio
429
+ if benchmark := self.instrument.primary_benchmark:
430
+ benchmark_df = (
431
+ pd.DataFrame(
432
+ benchmark.valuations.filter(date__gte=min_date, date__lte=self.date).values_list(
433
+ "date", "net_value"
434
+ ),
435
+ columns=["date", "net_value"],
436
+ )
437
+ .set_index("date")
438
+ .sort_index()["net_value"]
439
+ )
440
+ if (beta := financial_statistics.get_beta(benchmark_df)) is not None:
441
+ self.beta = beta
442
+ if (correlation := financial_statistics.get_correlation(benchmark_df)) is not None:
443
+ self.correlation = correlation
444
+
445
+ self.annualized_daily_volatility = financial_statistics.get_annualized_daily_volatility()
446
+
447
+ @classmethod
448
+ def subquery_closest_value(
449
+ cls,
450
+ field_name,
451
+ val_date=None,
452
+ date_name="date",
453
+ instrument_pk_name="instrument__pk",
454
+ date_lookup="lte",
455
+ order_by="-date",
456
+ calculated_filter_value=False,
457
+ ):
458
+ index_filter_params = {}
459
+ if calculated_filter_value is not None:
460
+ index_filter_params["calculated"] = calculated_filter_value
461
+ if not val_date and date_name:
462
+ index_filter_params[f"date__{date_lookup}"] = models.OuterRef(date_name)
463
+ elif val_date:
464
+ index_filter_params[f"date__{date_lookup}"] = val_date
465
+ index_filter_params["instrument"] = models.OuterRef(instrument_pk_name)
466
+ qs = cls.objects.filter(**index_filter_params).filter(**{f"{field_name}__isnull": False})
467
+ return models.Subquery(qs.order_by(order_by).values(field_name)[:1])
468
+
469
+ @classmethod
470
+ def annotate_sum_shares(cls, queryset, val_date, date_key="date"):
471
+ """
472
+ Efficient way to annotate sum of shares without destroying indexing
473
+ """
474
+ return queryset.annotate(
475
+ sum_shares_calculated=InstrumentPrice.subquery_closest_value(
476
+ "outstanding_shares",
477
+ date_name=date_key,
478
+ instrument_pk_name="pk",
479
+ date_lookup="lte",
480
+ order_by="-date",
481
+ calculated_filter_value=True,
482
+ ), # We get the last price whose outstanding_shares is not none and date below date_key
483
+ sum_shares_real=InstrumentPrice.subquery_closest_value(
484
+ "outstanding_shares",
485
+ val_date=val_date,
486
+ date_name=date_key,
487
+ instrument_pk_name="pk",
488
+ date_lookup="exact",
489
+ calculated_filter_value=False,
490
+ ),
491
+ sum_shares=Case(
492
+ When(sum_shares_real__isnull=False, then=F("sum_shares_real")),
493
+ When(sum_shares_calculated__isnull=False, then=F("sum_shares_calculated")),
494
+ default=Value(Decimal(0)),
495
+ output_field=DecimalField(),
496
+ ),
497
+ )
498
+
499
+ @classmethod
500
+ def get_endpoint_basename(cls):
501
+ return "wbfdm:price"
502
+
503
+ @classmethod
504
+ def get_representation_value_key(cls):
505
+ return "id"
506
+
507
+ @classmethod
508
+ def get_representation_label_key(cls):
509
+ return "{{instrument} {{date}}"
510
+
511
+ @classmethod
512
+ def get_representation_endpoint(cls):
513
+ return "wbfdm:price-list"
514
+
515
+
516
+ @receiver(post_save, sender="currency.CurrencyFXRates")
517
+ def rate_creation(sender, instance, created, raw, **kwargs):
518
+ if not raw and created:
519
+ transaction.on_commit(lambda: update_currency_fx_rate_from_created_rate.delay(instance.id))
520
+
521
+
522
+ @receiver(pre_merge, sender="wbfdm.Instrument")
523
+ def pre_merge_instrument(sender: models.Model, merged_object, main_object, **kwargs):
524
+ """
525
+ Simply reassign the prices of the merged instrument to the main instrument if they don't already exist for that day, otherwise simply delete them
526
+ """
527
+ merged_object.prices.annotate(
528
+ already_exists=Exists(
529
+ InstrumentPrice.objects.filter(
530
+ calculated=OuterRef("calculated"), instrument=main_object, date=OuterRef("date")
531
+ )
532
+ )
533
+ ).filter(already_exists=True).delete()
534
+ merged_object.prices.update(instrument=main_object)
535
+
536
+
537
+ @shared_task(queue="portfolio")
538
+ def update_currency_fx_rate_from_created_rate(rate_id):
539
+ currency_rate = CurrencyFXRates.objects.get(id=rate_id)
540
+ for price in InstrumentPrice.objects.filter(
541
+ instrument__currency=currency_rate.currency, date=currency_rate.date, currency_fx_rate_to_usd__isnull=True
542
+ ):
543
+ price.currency_fx_rate_to_usd = currency_rate
544
+ price.save()