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,106 @@
1
+ from django.db.models import Case, Exists, F, IntegerField, OuterRef, When
2
+ from rest_framework.filters import OrderingFilter
3
+ from wbcore import viewsets
4
+ from wbcore.contrib.guardian.filters import ObjectPermissionsFilter
5
+ from wbcore.viewsets.mixins import DjangoFilterBackend
6
+ from wbfdm.contrib.metric.backends.performances import PERFORMANCE_METRIC
7
+ from wbfdm.contrib.metric.backends.statistics import STATISTICS_METRIC
8
+ from wbfdm.contrib.metric.viewsets.mixins import InstrumentMetricMixin
9
+ from wbfdm.filters import InstrumentFilterSet
10
+ from wbfdm.import_export.resources.instruments import InstrumentResource
11
+ from wbfdm.models import Instrument, InstrumentType
12
+ from wbfdm.serializers import (
13
+ InstrumentModelListSerializer,
14
+ InstrumentModelSerializer,
15
+ InstrumentRepresentationSerializer,
16
+ InstrumentTypeRepresentationSerializer,
17
+ )
18
+
19
+ from ..configs import (
20
+ ChildrenInstrumentModelViewConfig,
21
+ InstrumentButtonViewConfig,
22
+ InstrumentDisplayConfig,
23
+ InstrumentEndpointConfig,
24
+ InstrumentTitleConfig,
25
+ )
26
+ from ..mixins import InstrumentMixin
27
+ from .utils import InstrumentSearchFilter
28
+
29
+
30
+ class InstrumentTypeRepresentationViewSet(viewsets.RepresentationViewSet):
31
+ queryset = InstrumentType.objects.all()
32
+ serializer_class = InstrumentTypeRepresentationSerializer
33
+ search_fields = ("name", "key")
34
+
35
+
36
+ class InstrumentRepresentationViewSet(viewsets.RepresentationViewSet):
37
+ filter_backends = (InstrumentSearchFilter, ObjectPermissionsFilter, DjangoFilterBackend, OrderingFilter)
38
+
39
+ queryset = Instrument.objects.annotate_base_data().exclude(name="")
40
+ serializer_class = InstrumentRepresentationSerializer
41
+ search_fields = ("name", "name_repr", "isin", "ticker")
42
+
43
+ filterset_class = InstrumentFilterSet
44
+ ordering_fields = ("title", "ticker")
45
+ ordering = ["-search_rank"]
46
+
47
+
48
+ class InstrumentModelViewSet(InstrumentMetricMixin, viewsets.ModelViewSet):
49
+ METRIC_KEYS = (PERFORMANCE_METRIC, STATISTICS_METRIC)
50
+ METRIC_SHOW_AGGREGATES = False
51
+ filter_backends = (InstrumentSearchFilter, ObjectPermissionsFilter, DjangoFilterBackend, OrderingFilter)
52
+
53
+ queryset = Instrument.objects.annotate_all()
54
+ serializer_class = InstrumentModelListSerializer
55
+ ordering_fields = (
56
+ "instrument_type",
57
+ "name_repr",
58
+ "ticker",
59
+ "isin",
60
+ "country__name",
61
+ "currency__key",
62
+ )
63
+ search_fields = (
64
+ "name_repr",
65
+ "name",
66
+ "isin",
67
+ "ticker",
68
+ "computed_str",
69
+ "refinitiv_identifier_code",
70
+ "refinitiv_mnemonic_code",
71
+ )
72
+ ordering = ["-search_rank"]
73
+
74
+ def get_serializer_class(self):
75
+ if self.get_action() in ["list", "list-metadata"]:
76
+ return InstrumentModelListSerializer
77
+ return InstrumentModelSerializer
78
+
79
+ display_config_class = InstrumentDisplayConfig
80
+ title_config_class = InstrumentTitleConfig
81
+ button_config_class = InstrumentButtonViewConfig
82
+ endpoint_config_class = InstrumentEndpointConfig
83
+ filterset_class = InstrumentFilterSet
84
+
85
+ def get_resource_class(self):
86
+ return InstrumentResource
87
+
88
+ def get_queryset(self):
89
+ qs = (
90
+ super()
91
+ .get_queryset()
92
+ .select_related("currency", "country", "instrument_type", "parent", "exchange")
93
+ .prefetch_related("tags", "classifications")
94
+ ).annotate(
95
+ has_children=Exists(Instrument.objects.filter(parent=OuterRef("pk"))),
96
+ _group_key=Case(When(has_children=True, then=F("id")), default=None, output_field=IntegerField()),
97
+ currency_symbol=F("currency__symbol"),
98
+ )
99
+ return qs
100
+
101
+
102
+ class ChildrenInstrumentModelViewSet(InstrumentMixin, InstrumentModelViewSet):
103
+ button_config_class = ChildrenInstrumentModelViewConfig
104
+
105
+ def get_queryset(self):
106
+ return super().get_queryset().filter(parent=self.instrument)
@@ -0,0 +1,235 @@
1
+ from django.db.models import Prefetch, Q
2
+ from django.db.models.expressions import F
3
+ from django.db.models.query import QuerySet
4
+ from django.utils.functional import cached_property
5
+ from rest_framework import filters
6
+ from wbcore import filters as wb_filters
7
+ from wbcore import serializers as wb_serializers
8
+ from wbcore import viewsets
9
+ from wbcore.contrib.tags.serializers import TagSerializerMixin
10
+ from wbcore.permissions.permissions import InternalUserPermissionMixin
11
+ from wbfdm.filters.instruments import BaseClassifiedInstrumentFilterSet
12
+ from wbfdm.models import (
13
+ Classification,
14
+ ClassificationGroup,
15
+ Instrument,
16
+ InstrumentFavoriteGroup,
17
+ RelatedInstrumentThroughModel,
18
+ )
19
+ from wbfdm.models.instruments import (
20
+ InstrumentClassificationRelatedInstrument,
21
+ InstrumentClassificationThroughModel,
22
+ )
23
+ from wbfdm.preferences import get_default_classification_group
24
+ from wbfdm.serializers import (
25
+ ClassifiableInstrumentRepresentationSerializer,
26
+ ClassificationRepresentationSerializer,
27
+ InstrumentClassificationRelatedInstrumentModelSerializer,
28
+ InstrumentClassificationRelatedInstrumentRepresentationSerializer,
29
+ InstrumentFavoriteGroupModelSerializer,
30
+ InstrumentFavoriteGroupRepresentationSerializer,
31
+ RelatedInstrumentThroughInstrumentModelSerializer,
32
+ )
33
+ from wbfdm.viewsets.configs.display import ClassifiedInstrumentDisplayConfig
34
+ from wbfdm.viewsets.configs.titles import ClassifiedInstrumentTitleConfig
35
+
36
+ from ..configs import (
37
+ ClassificationInstrumentRelatedInstrumentDisplayConfig,
38
+ ClassificationInstrumentRelatedInstrumentEndpointConfig,
39
+ ClassifiedInstrumentEndpointConfig,
40
+ InstrumentFavoriteGroupDisplayConfig,
41
+ InstrumentFavoriteGroupTitleConfig,
42
+ RelatedInstrumentThroughInstrumentDisplayConfig,
43
+ RelatedInstrumentThroughInstrumentEndpointConfig,
44
+ )
45
+ from ..mixins import InstrumentMixin
46
+
47
+
48
+ class InstrumentClassificationRelatedInstrumentRepresentationViewSet(viewsets.RepresentationViewSet):
49
+ queryset = InstrumentClassificationRelatedInstrument.objects.all()
50
+ serializer_class = InstrumentClassificationRelatedInstrumentRepresentationSerializer
51
+
52
+ def get_queryset(self):
53
+ queryset = super().get_queryset()
54
+ if pk := self.kwargs.get("classified_instrument_id", None):
55
+ queryset = queryset.filter(classified_instrument_id=pk)
56
+ return queryset
57
+
58
+
59
+ class InstrumentClassificationRelatedInstrumentModelViewSet(viewsets.ModelViewSet):
60
+ queryset = InstrumentClassificationRelatedInstrument.objects.all()
61
+ serializer_class = InstrumentClassificationRelatedInstrumentModelSerializer
62
+ display_config_class = ClassificationInstrumentRelatedInstrumentDisplayConfig
63
+ endpoint_config_class = ClassificationInstrumentRelatedInstrumentEndpointConfig
64
+
65
+ def get_queryset(self):
66
+ queryset = super().get_queryset()
67
+ if pk := self.kwargs.get("classified_instrument_id", None):
68
+ queryset = queryset.filter(classified_instrument_id=pk)
69
+ return queryset
70
+
71
+
72
+ class InstrumentFavoriteGroupRepresentationViewSet(InternalUserPermissionMixin, viewsets.RepresentationViewSet):
73
+ IDENTIFIER = "wbfdm:favoritegroup"
74
+ filter_backends = (filters.OrderingFilter, filters.SearchFilter)
75
+ ordering_fields = ordering = ("name",)
76
+ search_fields = ("name", "instruments__name", "owner__computed_str")
77
+ queryset = InstrumentFavoriteGroup.objects.all()
78
+ serializer_class = InstrumentFavoriteGroupRepresentationSerializer
79
+
80
+
81
+ class InstrumentFavoriteGroupModelViewSet(InternalUserPermissionMixin, viewsets.ModelViewSet):
82
+ queryset = InstrumentFavoriteGroup.objects.all()
83
+ serializer_class = InstrumentFavoriteGroupModelSerializer
84
+
85
+ ordering_fields = ("name", "owner__computed_str", "public")
86
+ ordering = ("name",)
87
+ search_fields = ("name", "instruments__name", "owner__computed_str")
88
+
89
+ filterset_fields = {"instruments": ["exact"], "owner": ["exact"], "public": ["exact"]}
90
+
91
+ display_config_class = InstrumentFavoriteGroupDisplayConfig
92
+ title_config_class = InstrumentFavoriteGroupTitleConfig
93
+
94
+ def get_queryset(self):
95
+ qs = InstrumentFavoriteGroup.objects.all()
96
+ if not self.request.user.is_superuser:
97
+ qs = qs.filter(Q(owner=self.request.user.profile) | Q(public=True))
98
+ return qs.select_related("owner").prefetch_related(
99
+ Prefetch("instruments", queryset=Instrument.objects.filter(favorite_groups__isnull=False).distinct())
100
+ )
101
+
102
+
103
+ class RelatedInstrumentThroughInstrumentModelViewSet(
104
+ InstrumentMixin, InternalUserPermissionMixin, viewsets.ModelViewSet
105
+ ):
106
+ serializer_class = RelatedInstrumentThroughInstrumentModelSerializer
107
+ queryset = RelatedInstrumentThroughModel.objects.select_related(
108
+ "related_instrument",
109
+ "instrument",
110
+ )
111
+
112
+ search_fields = ("related_instrument__computed_str",)
113
+ ordering_fields = ["is_primary"]
114
+ ordering = ["-is_primary"]
115
+
116
+ filterset_fields = {"is_primary": ["exact"], "related_instrument": ["exact"], "related_type": ["exact"]}
117
+ display_config_class = RelatedInstrumentThroughInstrumentDisplayConfig
118
+ endpoint_config_class = RelatedInstrumentThroughInstrumentEndpointConfig
119
+
120
+ def get_queryset(self):
121
+ return super().get_queryset().filter(instrument=self.instrument)
122
+
123
+
124
+ class ClassifiedInstrumentModelViewSet(InternalUserPermissionMixin, viewsets.ModelViewSet):
125
+ queryset = InstrumentClassificationThroughModel.objects.all()
126
+ display_config_class = ClassifiedInstrumentDisplayConfig
127
+ title_config_class = ClassifiedInstrumentTitleConfig
128
+ endpoint_config_class = ClassifiedInstrumentEndpointConfig
129
+
130
+ search_fields = ("instrument__computed_str",)
131
+
132
+ def get_ordering_fields(self) -> list[str]:
133
+ ordering_fields = ["instrument"]
134
+ for field_name in self.classification_group.get_fields_names(sep="_"):
135
+ ordering_fields.append(f"classification_{field_name}")
136
+ return ordering_fields
137
+
138
+ def get_serializer_class(self):
139
+ """
140
+ Unwrap defined serializer class and inject the metric fields into a new class
141
+ """
142
+
143
+ group = self.classification_group
144
+
145
+ attrs = dict()
146
+ serializer_fields = [
147
+ "id",
148
+ "instrument",
149
+ "_instrument",
150
+ "classification",
151
+ "_classification",
152
+ "is_favorite",
153
+ "tags",
154
+ "_tags",
155
+ ]
156
+ for field_name in group.get_fields_names(sep="_"):
157
+ base_field_name = f"classification_{field_name}"
158
+ # representation_field_name = f"_classification_{field_name}"
159
+ attrs[base_field_name] = wb_serializers.CharField(read_only=True)
160
+ serializer_fields.append(base_field_name)
161
+
162
+ class BaseClassifiedInstrumentModelSerializer(TagSerializerMixin, wb_serializers.ModelSerializer):
163
+ _instrument = ClassifiableInstrumentRepresentationSerializer(source="instrument")
164
+ _classification = ClassificationRepresentationSerializer(source="classification", label_key="{{name}}")
165
+
166
+ class Meta:
167
+ model = InstrumentClassificationThroughModel
168
+ fields = read_only_fields = serializer_fields
169
+
170
+ serializer_class = type(
171
+ "ClassifiedInstrumentModelSerializer", (BaseClassifiedInstrumentModelSerializer,), attrs
172
+ )
173
+
174
+ return serializer_class
175
+
176
+ def get_filterset_class(self, request):
177
+ group = self.classification_group
178
+
179
+ attrs = {
180
+ "classification": wb_filters.ModelChoiceFilter(
181
+ label="Height 0",
182
+ queryset=Classification.objects.filter(group=group, height=0),
183
+ endpoint=Classification.get_representation_endpoint(),
184
+ filter_params={"height": 0, "group": group.id},
185
+ value_key=Classification.get_representation_value_key(),
186
+ label_key="{{name}}",
187
+ )
188
+ }
189
+ for index, field_name in enumerate(group.get_fields_names(sep="_"), start=1):
190
+ attrs[f"classification_{field_name}"] = wb_filters.ModelChoiceFilter(
191
+ label=f"Height {index}",
192
+ queryset=Classification.objects.filter(group=group, height=index),
193
+ endpoint=Classification.get_representation_endpoint(),
194
+ filter_params={"height": index, "group": group.id},
195
+ value_key=Classification.get_representation_value_key(),
196
+ method="query_classification",
197
+ label_key="{{name}}",
198
+ )
199
+ filter_class = type("ClassifiedInstrumentFilterSet", (BaseClassifiedInstrumentFilterSet,), attrs)
200
+
201
+ def _get_filter_class_for_remote_filter(cls):
202
+ """
203
+ Define which filterset class sender to user for remote filter registration
204
+ """
205
+ return BaseClassifiedInstrumentFilterSet
206
+
207
+ filter_class.get_filter_class_for_remote_filter = classmethod(_get_filter_class_for_remote_filter)
208
+ return filter_class
209
+
210
+ @cached_property
211
+ def classification_group(self):
212
+ try:
213
+ return ClassificationGroup.objects.get(id=self.request.GET.get("classification_group"))
214
+ except ClassificationGroup.DoesNotExist:
215
+ return get_default_classification_group()
216
+
217
+ def get_queryset(self) -> QuerySet[InstrumentClassificationThroughModel]:
218
+ return (
219
+ super()
220
+ .get_queryset()
221
+ .filter(classification__group=self.classification_group)
222
+ .annotate(
223
+ **{
224
+ f"classification_{field_name}".replace("__", "_"): F(f"classification__{field_name}__name")
225
+ for field_name in self.classification_group.get_fields_names()
226
+ }
227
+ )
228
+ .select_related(
229
+ *[f"classification__{field_name}" for field_name in self.classification_group.get_fields_names()]
230
+ )
231
+ .prefetch_related(
232
+ "tags",
233
+ Prefetch("instrument", queryset=Instrument.objects.filter(classifications_through__isnull=False)),
234
+ )
235
+ )
@@ -0,0 +1,27 @@
1
+ from django.contrib.postgres.search import SearchQuery, SearchRank
2
+ from django.db.models import F, Q
3
+ from django.db.models.expressions import Value
4
+ from django.db.models.functions import Coalesce
5
+
6
+
7
+ class InstrumentSearchFilter:
8
+ def get_min_search_rank(self, view) -> float:
9
+ return getattr(view, "SEARCH_MIN_RANK", 0.3)
10
+
11
+ def filter_queryset(self, request, queryset, view):
12
+ if search := request.GET.get("search", None):
13
+ min_search_rank = self.get_min_search_rank(view)
14
+ query = SearchQuery(search, search_type="phrase")
15
+ queryset = (
16
+ queryset.annotate(search_rank=Coalesce(SearchRank(F("search_vector"), query), Value(-1.0)))
17
+ .filter(
18
+ (Q(search_vector=query) & Q(search_rank__gte=min_search_rank))
19
+ | Q(name__icontains=search)
20
+ | Q(name_repr__icontains=search)
21
+ | Q(isin__icontains=search)
22
+ )
23
+ .order_by("-search_rank")
24
+ ).distinct()
25
+ else:
26
+ queryset = queryset.annotate(search_rank=Value(-1.0))
27
+ return queryset
@@ -0,0 +1,172 @@
1
+ from contextlib import suppress
2
+ from datetime import date
3
+ from itertools import cycle
4
+
5
+ import plotly.express as px
6
+ from django.utils.functional import cached_property
7
+ from plotly import graph_objects as go
8
+ from plotly.subplots import make_subplots
9
+ from wbcore import viewsets
10
+ from wbcore.utils.date import get_date_interval_from_request
11
+ from wbfdm.enums import Indicator, MarketDataChartType
12
+ from wbfdm.filters import MarketDataChartFilterSet
13
+ from wbfdm.models.instruments import Instrument
14
+
15
+ from .configs import MarketDataChartTitleConfig, PerformanceSummaryChartTitleConfig
16
+ from .mixins import InstrumentMixin
17
+
18
+
19
+ class PerformanceSummaryChartViewSet(InstrumentMixin, viewsets.ChartViewSet):
20
+ queryset = Instrument.objects.all()
21
+ title_config_class = PerformanceSummaryChartTitleConfig
22
+
23
+ def get_queryset(self):
24
+ return super().get_queryset().filter(id=self.instrument.id)
25
+
26
+ def get_plotly(self, queryset):
27
+ today = date.today()
28
+ trace_factory = (
29
+ queryset.first().technical_analysis(from_date=today.replace(year=today.year - 4)).trace_factory()
30
+ )
31
+
32
+ fig = go.Figure()
33
+ for trace in trace_factory.performance_summary_trace(bar_options={"color": px.colors.qualitative.T10[0]}):
34
+ fig.add_trace(trace)
35
+ fig.update_layout(
36
+ template="plotly_white",
37
+ yaxis_tickformat="%",
38
+ )
39
+ return fig
40
+
41
+
42
+ class MarketDataChartViewSet(InstrumentMixin, viewsets.ChartViewSet):
43
+ queryset = Instrument.objects.all()
44
+ filterset_class = MarketDataChartFilterSet
45
+ title_config_class = MarketDataChartTitleConfig
46
+
47
+ CHART_MAPPING = {
48
+ MarketDataChartType.CLOSE: "close_trace",
49
+ MarketDataChartType.RETURN: "return_trace",
50
+ MarketDataChartType.LOG_RETURN: "log_return_trace",
51
+ MarketDataChartType.DRAWDOWN: "drawdown_trace",
52
+ MarketDataChartType.CANDLESTICK: "candlestick_trace",
53
+ MarketDataChartType.OHLC: "ohlc_trace",
54
+ }
55
+ BENCHMARK_CHART_MAPPING = {
56
+ MarketDataChartType.CLOSE: "close_trace",
57
+ MarketDataChartType.RETURN: "return_trace",
58
+ MarketDataChartType.LOG_RETURN: "log_return_trace",
59
+ MarketDataChartType.DRAWDOWN: "drawdown_trace",
60
+ MarketDataChartType.CANDLESTICK: "close_trace",
61
+ MarketDataChartType.OHLC: "close_trace",
62
+ }
63
+ STATISTIC_MAPPING = {
64
+ MarketDataChartType.CLOSE: "close",
65
+ MarketDataChartType.RETURN: "cum-ret",
66
+ MarketDataChartType.LOG_RETURN: "cum-log-ret",
67
+ MarketDataChartType.DRAWDOWN: "drawdown",
68
+ MarketDataChartType.CANDLESTICK: "close",
69
+ MarketDataChartType.OHLC: "close",
70
+ }
71
+ INDICATOR_MAPPING = {
72
+ Indicator.SMA_50: 50,
73
+ Indicator.SMA_100: 100,
74
+ Indicator.SMA_120: 120,
75
+ Indicator.SMA_200: 200,
76
+ }
77
+
78
+ def get_queryset(self):
79
+ return super().get_queryset().filter(id=self.instrument.id)
80
+
81
+ def get_benchmark_queryset(self):
82
+ return Instrument.objects.filter(id__in=self.benchmarks_ids)
83
+
84
+ @cached_property
85
+ def benchmarks_ids(self) -> list[int]:
86
+ if benchmarks_str := self.request.GET.get("benchmarks", None):
87
+ return benchmarks_str.split(",")
88
+ return []
89
+
90
+ def get_parameters(self) -> list:
91
+ return self.request.GET.get("parameters", "").split(";")
92
+
93
+ def get_plotly(self, queryset):
94
+ # Parametrization
95
+ chart_type = MarketDataChartType(self.request.GET.get("chart_type", "close"))
96
+ volume = self.request.GET.get("volume", "false") == "true"
97
+ show_estimates = self.request.GET.get("show_estimates", "false") == "true"
98
+ from_date, to_date = get_date_interval_from_request(self.request, date_range_fieldname="period")
99
+ colors = cycle(px.colors.qualitative.T10)
100
+
101
+ # Bootstrap chart
102
+ fig = make_subplots(
103
+ rows=2 if volume else 1,
104
+ cols=1,
105
+ shared_xaxes=True,
106
+ vertical_spacing=0.03,
107
+ row_width=[0.2, 0.7] if volume else [1],
108
+ )
109
+
110
+ # Generate Chart for main timeseries
111
+ ta = queryset.first().technical_analysis(from_date, to_date)
112
+ factory = ta.trace_factory()
113
+ for trace in getattr(factory, self.CHART_MAPPING[chart_type])(
114
+ line_options=dict(color=next(colors)), show_estimates=show_estimates
115
+ ):
116
+ fig.add_trace(trace)
117
+
118
+ # Generate Charts for all added benchmarks
119
+ for benchmark in self.get_benchmark_queryset():
120
+ _factory = benchmark.technical_benchmark_analysis(ta.df.index.min(), to_date).trace_factory()
121
+ for trace in getattr(_factory, self.BENCHMARK_CHART_MAPPING[chart_type])(
122
+ base_series=ta.df[self.STATISTIC_MAPPING[chart_type]],
123
+ line_options=dict(color=next(colors)),
124
+ ):
125
+ fig.add_trace(trace)
126
+
127
+ # Generate Charts for any selected indicators
128
+ if indicators := self.request.GET.get("indicators", None):
129
+ for indicator in indicators.split(","):
130
+ for trace in factory.sma_trace(
131
+ self.INDICATOR_MAPPING[Indicator(indicator)], line_options=dict(color=next(colors))
132
+ ):
133
+ fig.add_trace(trace)
134
+
135
+ if volume:
136
+ fig.add_trace(
137
+ factory.volume_trace(),
138
+ row=2,
139
+ col=1,
140
+ )
141
+
142
+ fig.update_layout(
143
+ template="plotly_white",
144
+ legend=dict(x=0.02, y=0.98, bgcolor="rgba(0,0,0,0)"),
145
+ margin=dict(l=0, r=0, t=0, b=40),
146
+ xaxis_rangeslider_visible=False,
147
+ showlegend=True,
148
+ hovermode="x",
149
+ )
150
+ fig.update_xaxes(rangebreaks=[{"pattern": "day of week", "bounds": [6, 1]}])
151
+
152
+ for i, d in enumerate(fig.data):
153
+ with suppress(AttributeError, IndexError, ValueError): # Either Candlestick or OHCL
154
+ if (y := d.y[-1]) is not None:
155
+ text_value = (
156
+ f"{y:.2%}"
157
+ if chart_type in [MarketDataChartType.RETURN, MarketDataChartType.LOG_RETURN]
158
+ else f"{y:.1f}"
159
+ )
160
+ fig.add_scatter(
161
+ x=[d.x[-1]],
162
+ y=[y],
163
+ name=d.name,
164
+ mode="markers+text",
165
+ text=text_value,
166
+ textfont=dict(color=d.line.color),
167
+ textposition="middle right",
168
+ marker=dict(color=d.line.color, size=12, symbol="circle"),
169
+ legendgroup=d.name,
170
+ showlegend=False,
171
+ )
172
+ return fig
@@ -0,0 +1,9 @@
1
+ from django.utils.functional import cached_property
2
+ from rest_framework.generics import get_object_or_404
3
+ from wbfdm.models import Instrument
4
+
5
+
6
+ class InstrumentMixin:
7
+ @cached_property
8
+ def instrument(self) -> Instrument:
9
+ return get_object_or_404(Instrument, pk=self.kwargs["instrument_id"])
@@ -0,0 +1,27 @@
1
+ from rest_framework.response import Response
2
+ from wbcore import viewsets
3
+ from wbfdm.models.instruments import Instrument
4
+ from wbfdm.serializers import OfficerSerializer
5
+ from wbfdm.viewsets.configs.display.officers import OfficerDisplayViewConfig
6
+ from wbfdm.viewsets.configs.titles.instruments import SubviewInstrumentTitleViewConfig
7
+
8
+ from .mixins import InstrumentMixin
9
+
10
+
11
+ class OfficerViewSet(InstrumentMixin, viewsets.ViewSet):
12
+ IDENTIFIER = "wbfdm:instrument-officers"
13
+ SUBVIEW_NAME = "Officers"
14
+ display_config_class = OfficerDisplayViewConfig
15
+ title_config_class = SubviewInstrumentTitleViewConfig
16
+ serializer_class = OfficerSerializer
17
+ permission_classes = []
18
+
19
+ def list(self, request, instrument_id):
20
+ queryset = self.get_queryset()
21
+ serializer = self.get_serializer_class()
22
+ serializer = serializer(queryset, many=True)
23
+ return Response({"results": serializer.data})
24
+
25
+ def get_queryset(self):
26
+ queryset = Instrument.objects.filter(id=self.instrument.id)
27
+ return queryset.dl.officers()
@@ -0,0 +1,62 @@
1
+ from datetime import date, timedelta
2
+
3
+ import pandas as pd
4
+ from wbcore.contrib.io.viewsets import ExportPandasAPIViewSet
5
+ from wbcore.pandas import fields as pf
6
+ from wbcore.serializers.fields.types import DisplayMode
7
+ from wbcore.utils.date import get_date_interval_from_request
8
+ from wbfdm.filters.instrument_prices import FakeDateRange
9
+ from wbfdm.models.instruments import Instrument
10
+ from wbfdm.viewsets.configs.display.prices import InstrumentPriceDisplayConfig
11
+ from wbfdm.viewsets.configs.titles.prices import InstrumentPriceTitleViewConfig
12
+
13
+ from .mixins import InstrumentMixin
14
+
15
+
16
+ class InstrumentPriceViewSet(InstrumentMixin, ExportPandasAPIViewSet):
17
+ IDENTIFIER = "wbfdm:instrument-price"
18
+ display_config_class = InstrumentPriceDisplayConfig
19
+ title_config_class = InstrumentPriceTitleViewConfig
20
+ pandas_fields = pf.PandasFields(
21
+ fields=(
22
+ pf.PKField(key="id", label="ID"),
23
+ pf.DateField(
24
+ key="valuation_date",
25
+ label="valuation_date",
26
+ ),
27
+ pf.FloatField(key="open", label="open"),
28
+ pf.FloatField(key="high", label="high"),
29
+ pf.FloatField(key="low", label="low"),
30
+ pf.FloatField(key="close", label="close"),
31
+ pf.FloatField(key="volume", label="volume", display_mode=DisplayMode.SHORTENED),
32
+ pf.FloatField(key="outstanding_shares", label="outstanding_shares", display_mode=DisplayMode.SHORTENED),
33
+ pf.FloatField(
34
+ key="market_capitalization", label="market_capitalization", display_mode=DisplayMode.SHORTENED
35
+ ),
36
+ )
37
+ )
38
+ permission_classes = []
39
+ filterset_class = FakeDateRange
40
+ queryset = Instrument.objects.all()
41
+ ordering_fields = (
42
+ "valuation_date",
43
+ "open",
44
+ "high",
45
+ "low",
46
+ "close",
47
+ "volume",
48
+ "outstanding_shares",
49
+ "market_capitalization",
50
+ )
51
+ ordering = ["-valuation_date"]
52
+
53
+ def get_queryset(self):
54
+ return Instrument.objects.filter(id=self.instrument.id)
55
+
56
+ def get_dataframe(self, request, queryset, **kwargs):
57
+ start, end = get_date_interval_from_request(request, date_range_fieldname="date")
58
+ if not end:
59
+ end = date.today()
60
+ if not start:
61
+ start = end - timedelta(days=365)
62
+ return pd.DataFrame(queryset.dl.market_data(from_date=start, to_date=end))
@@ -0,0 +1 @@
1
+ from .statements import StatementPandasViewSet