finance_dev 0.9.0__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.
Files changed (486) hide show
  1. finance_dev/__init__.py +18 -0
  2. finance_dev/_hooks/__init__.py +7 -0
  3. finance_dev/_hooks/clientcredentials.py +319 -0
  4. finance_dev/_hooks/oauth2scopes.py +50 -0
  5. finance_dev/_hooks/registration.py +13 -0
  6. finance_dev/_hooks/sdkhooks.py +82 -0
  7. finance_dev/_hooks/types.py +125 -0
  8. finance_dev/_version.py +16 -0
  9. finance_dev/accounts.py +797 -0
  10. finance_dev/alternativedata.py +4830 -0
  11. finance_dev/analytics.py +3757 -0
  12. finance_dev/basesdk.py +393 -0
  13. finance_dev/calendar.py +1523 -0
  14. finance_dev/chart.py +1881 -0
  15. finance_dev/company.py +1105 -0
  16. finance_dev/corporateactions.py +1795 -0
  17. finance_dev/economy.py +1153 -0
  18. finance_dev/errors/__init__.py +94 -0
  19. finance_dev/errors/apierror.py +41 -0
  20. finance_dev/errors/authenticationerrorenvelope.py +43 -0
  21. finance_dev/errors/badrequest.py +34 -0
  22. finance_dev/errors/errorenvelope.py +42 -0
  23. finance_dev/errors/errorresponse.py +45 -0
  24. finance_dev/errors/financedevapierror.py +31 -0
  25. finance_dev/errors/forbidden.py +34 -0
  26. finance_dev/errors/internalservererror.py +35 -0
  27. finance_dev/errors/no_response_error.py +18 -0
  28. finance_dev/errors/notfound.py +34 -0
  29. finance_dev/errors/orderhistorybadrequest.py +35 -0
  30. finance_dev/errors/predorderserrorenvelope.py +42 -0
  31. finance_dev/errors/responsevalidationerror.py +28 -0
  32. finance_dev/errors/unauthorized.py +34 -0
  33. finance_dev/estimates.py +1906 -0
  34. finance_dev/etf.py +1108 -0
  35. finance_dev/filings.py +586 -0
  36. finance_dev/financials.py +4079 -0
  37. finance_dev/history.py +477 -0
  38. finance_dev/httpclient.py +126 -0
  39. finance_dev/indices.py +2308 -0
  40. finance_dev/logos.py +692 -0
  41. finance_dev/market.py +1825 -0
  42. finance_dev/markets.py +952 -0
  43. finance_dev/models/__init__.py +4568 -0
  44. finance_dev/models/accountbalancev2.py +340 -0
  45. finance_dev/models/accountitem.py +78 -0
  46. finance_dev/models/accountposition.py +147 -0
  47. finance_dev/models/aggregatetechnicalindicatorsresponse.py +192 -0
  48. finance_dev/models/analystinsightitem.py +161 -0
  49. finance_dev/models/analystinsightsresponse.py +39 -0
  50. finance_dev/models/apperror.py +123 -0
  51. finance_dev/models/authenticationerrordetail.py +58 -0
  52. finance_dev/models/bankbranchesresponse.py +96 -0
  53. finance_dev/models/basicfinancials.py +243 -0
  54. finance_dev/models/bullbearanalysisresponse.py +150 -0
  55. finance_dev/models/cancelorderop.py +40 -0
  56. finance_dev/models/cancelordrequest_union.py +287 -0
  57. finance_dev/models/cancelordresponse.py +115 -0
  58. finance_dev/models/cashflowstatement.py +290 -0
  59. finance_dev/models/cashflowstatementsresponse.py +61 -0
  60. finance_dev/models/chartbars.py +111 -0
  61. finance_dev/models/chartconfig.py +81 -0
  62. finance_dev/models/chartresolution.py +46 -0
  63. finance_dev/models/companyesgresponse.py +408 -0
  64. finance_dev/models/companyexecutive.py +70 -0
  65. finance_dev/models/companyexecutiveresponse.py +39 -0
  66. finance_dev/models/companypeersresponse.py +38 -0
  67. finance_dev/models/companypresentationresponse.py +96 -0
  68. finance_dev/models/companyprofile.py +255 -0
  69. finance_dev/models/conferencecallsresponse.py +169 -0
  70. finance_dev/models/congressionaltradingresponse.py +138 -0
  71. finance_dev/models/covid19uscasesresponse.py +79 -0
  72. finance_dev/models/createconfigwatchlistop.py +40 -0
  73. finance_dev/models/createwatchlistrequest.py +43 -0
  74. finance_dev/models/dailymarketsummaryresponse.py +73 -0
  75. finance_dev/models/deleteconfigwatchlistbyidop.py +53 -0
  76. finance_dev/models/dividenditem.py +125 -0
  77. finance_dev/models/dividendsresponse.py +61 -0
  78. finance_dev/models/earningrelease.py +291 -0
  79. finance_dev/models/earningscallliveresponse.py +103 -0
  80. finance_dev/models/earningsqualityscoreresponse.py +140 -0
  81. finance_dev/models/earningssurprisesresponse.py +125 -0
  82. finance_dev/models/ebitdaestimatesresponse.py +176 -0
  83. finance_dev/models/ebitestimatesresponse.py +176 -0
  84. finance_dev/models/economicscalendarevent.py +160 -0
  85. finance_dev/models/economicsindicator.py +87 -0
  86. finance_dev/models/epsestimatesresponse.py +176 -0
  87. finance_dev/models/equitydetailsdto.py +569 -0
  88. finance_dev/models/errorcode.py +48 -0
  89. finance_dev/models/etblistresponse.py +43 -0
  90. finance_dev/models/etbsearchrequest.py +21 -0
  91. finance_dev/models/etbsearchresponse.py +43 -0
  92. finance_dev/models/etfcountryallocationresponse.py +113 -0
  93. finance_dev/models/etfholdingsresponse.py +163 -0
  94. finance_dev/models/etfprofileresponse.py +234 -0
  95. finance_dev/models/etfsectorallocationresponse.py +113 -0
  96. finance_dev/models/exchangecode.py +90 -0
  97. finance_dev/models/fdaadvisorycommitteecalendarresponse.py +88 -0
  98. finance_dev/models/filingsimilarityrecord.py +115 -0
  99. finance_dev/models/filingssimilarityindexdatawrapper.py +52 -0
  100. finance_dev/models/filingssimilarityindexresponse.py +42 -0
  101. finance_dev/models/financialratiosresponse.py +252 -0
  102. finance_dev/models/financialsreporteddatawrapper.py +49 -0
  103. finance_dev/models/financialsreportedresponse.py +42 -0
  104. finance_dev/models/financialsrevenuebreakdowndatawrapper.py +49 -0
  105. finance_dev/models/financialsrevenuebreakdownresponse.py +42 -0
  106. finance_dev/models/financialsstatementsdatawrapper.py +44 -0
  107. finance_dev/models/financialsstatementsresponse.py +42 -0
  108. finance_dev/models/financialstatement.py +171 -0
  109. finance_dev/models/financialstatementtype.py +14 -0
  110. finance_dev/models/getaccountbalanceop.py +53 -0
  111. finance_dev/models/getaccountpositionsop.py +53 -0
  112. finance_dev/models/getaccountsresponse.py +22 -0
  113. finance_dev/models/getaggregatetechnicalindicatorsop.py +94 -0
  114. finance_dev/models/getanalystinsightsop.py +164 -0
  115. finance_dev/models/getbankbranchesop.py +53 -0
  116. finance_dev/models/getbaseeventop.py +87 -0
  117. finance_dev/models/getbullbearanalysisop.py +91 -0
  118. finance_dev/models/getcashflowstatementsop.py +564 -0
  119. finance_dev/models/getchartconfigop.py +40 -0
  120. finance_dev/models/getcharthistoryop.py +122 -0
  121. finance_dev/models/getcharttimeop.py +39 -0
  122. finance_dev/models/getcompanyesgop.py +53 -0
  123. finance_dev/models/getcompanyexecutivesop.py +56 -0
  124. finance_dev/models/getcompanymetricsop.py +68 -0
  125. finance_dev/models/getcompanypeersop.py +53 -0
  126. finance_dev/models/getcompanypresentationop.py +56 -0
  127. finance_dev/models/getcompanyprofileop.py +86 -0
  128. finance_dev/models/getconferencecallsop.py +111 -0
  129. finance_dev/models/getconfigwatchlistbyidop.py +53 -0
  130. finance_dev/models/getconfigwatchlistsop.py +40 -0
  131. finance_dev/models/getcongressionaltradingop.py +73 -0
  132. finance_dev/models/getcovid19uscasesop.py +43 -0
  133. finance_dev/models/getdailymarketsummaryop.py +92 -0
  134. finance_dev/models/getdividendsop.py +294 -0
  135. finance_dev/models/getearningscalendarop.py +87 -0
  136. finance_dev/models/getearningscallliveop.py +91 -0
  137. finance_dev/models/getearningsqualityscoreop.py +81 -0
  138. finance_dev/models/getearningssurprisesop.py +80 -0
  139. finance_dev/models/getebitdaestimatesop.py +91 -0
  140. finance_dev/models/getebitestimatesop.py +88 -0
  141. finance_dev/models/geteconomicscalendarop.py +92 -0
  142. finance_dev/models/geteconomicscalendarresponse.py +56 -0
  143. finance_dev/models/getepsestimatesop.py +88 -0
  144. finance_dev/models/getequitydetailsop.py +56 -0
  145. finance_dev/models/getequitydetailsresponse.py +45 -0
  146. finance_dev/models/getetblistop.py +40 -0
  147. finance_dev/models/getetfcountryallocationop.py +80 -0
  148. finance_dev/models/getetfholdingsop.py +96 -0
  149. finance_dev/models/getetfprofileop.py +77 -0
  150. finance_dev/models/getetfsectorallocationop.py +80 -0
  151. finance_dev/models/geteventcontractsop.py +87 -0
  152. finance_dev/models/getfdaadvisorycommitteecalendarop.py +47 -0
  153. finance_dev/models/getfilingssimilarityindexop.py +102 -0
  154. finance_dev/models/getfinancialratiosop.py +612 -0
  155. finance_dev/models/getfinancialstatementsop.py +117 -0
  156. finance_dev/models/getfundownershipop.py +77 -0
  157. finance_dev/models/gethistoricaldataop.py +61 -0
  158. finance_dev/models/gethistoricalesgop.py +53 -0
  159. finance_dev/models/gethistorybatchop.py +74 -0
  160. finance_dev/models/gethistoryop.py +123 -0
  161. finance_dev/models/getincomestatementsop.py +564 -0
  162. finance_dev/models/getindexbarop.py +140 -0
  163. finance_dev/models/getindexbarsop.py +149 -0
  164. finance_dev/models/getindexchartbarsop.py +178 -0
  165. finance_dev/models/getindexrealtimevaluesop.py +94 -0
  166. finance_dev/models/getinflationexpectationsop.py +157 -0
  167. finance_dev/models/getinflationop.py +154 -0
  168. finance_dev/models/getinsidertransactionsop.py +90 -0
  169. finance_dev/models/getinstitutionalownershipop.py +99 -0
  170. finance_dev/models/getinstitutionalportfolioop.py +75 -0
  171. finance_dev/models/getinvestmentthemeop.py +68 -0
  172. finance_dev/models/getipocalendarop.py +63 -0
  173. finance_dev/models/getlobbyingop.py +70 -0
  174. finance_dev/models/getmarketbreadthop.py +40 -0
  175. finance_dev/models/getmarketholidaysop.py +65 -0
  176. finance_dev/models/getmarketstatusop.py +65 -0
  177. finance_dev/models/getmergersandacquisitionsop.py +113 -0
  178. finance_dev/models/getnetinflowop.py +40 -0
  179. finance_dev/models/getnewsbyidop.py +73 -0
  180. finance_dev/models/getnewsop.py +98 -0
  181. finance_dev/models/getnewssentimentop.py +53 -0
  182. finance_dev/models/getoptionchainsnapshotop.py +244 -0
  183. finance_dev/models/getoptioncontractsnapshotop.py +69 -0
  184. finance_dev/models/getoptiondetailsop.py +56 -0
  185. finance_dev/models/getoptiondetailsresponse.py +45 -0
  186. finance_dev/models/getoptionlasttradeop.py +56 -0
  187. finance_dev/models/getoptionopencloseop.py +66 -0
  188. finance_dev/models/getoptionpreviousdayop.py +56 -0
  189. finance_dev/models/getoptionquotesop.py +177 -0
  190. finance_dev/models/getoptionsactivityop.py +111 -0
  191. finance_dev/models/getoptionsetfstopvolumeop.py +70 -0
  192. finance_dev/models/getoptionsstockstopgainersop.py +70 -0
  193. finance_dev/models/getoptionsstockstopvolumeop.py +70 -0
  194. finance_dev/models/getoptionsunusualactivityop.py +100 -0
  195. finance_dev/models/getoptiontradesop.py +177 -0
  196. finance_dev/models/getordersv2response.py +66 -0
  197. finance_dev/models/getownershipdataop.py +77 -0
  198. finance_dev/models/getpositionsresponse.py +39 -0
  199. finance_dev/models/getpredictionorderop.py +88 -0
  200. finance_dev/models/getpricetargetop.py +53 -0
  201. finance_dev/models/getquotesop.py +53 -0
  202. finance_dev/models/getratingsop.py +164 -0
  203. finance_dev/models/getratingsresponse.py +39 -0
  204. finance_dev/models/getreportedfinancialsop.py +127 -0
  205. finance_dev/models/getrevenuebreakdownop.py +85 -0
  206. finance_dev/models/getrevenueestimatesop.py +91 -0
  207. finance_dev/models/getsecfilingsop.py +113 -0
  208. finance_dev/models/getsectortickersop.py +124 -0
  209. finance_dev/models/getsectorwisechangeop.py +84 -0
  210. finance_dev/models/getshortinterestop.py +334 -0
  211. finance_dev/models/getshortvolumeop.py +333 -0
  212. finance_dev/models/getsocialsentimentop.py +90 -0
  213. finance_dev/models/getspinoffsop.py +112 -0
  214. finance_dev/models/getstockaggregatesop.py +174 -0
  215. finance_dev/models/getstocklastquoteop.py +56 -0
  216. finance_dev/models/getstocklasttradeop.py +56 -0
  217. finance_dev/models/getstockopencloseop.py +90 -0
  218. finance_dev/models/getstockpreviousdayop.py +80 -0
  219. finance_dev/models/getstockquotesop.py +177 -0
  220. finance_dev/models/getstocksnapshotsop.py +82 -0
  221. finance_dev/models/getstocksplitsop.py +243 -0
  222. finance_dev/models/getstocktradesop.py +177 -0
  223. finance_dev/models/getsupplychainop.py +53 -0
  224. finance_dev/models/getsupportresistancelevelsop.py +90 -0
  225. finance_dev/models/getsymbolinfoop.py +53 -0
  226. finance_dev/models/gettechnicalindicatorop.py +172 -0
  227. finance_dev/models/gettechnicalpatternsop.py +88 -0
  228. finance_dev/models/gettenderoffersop.py +112 -0
  229. finance_dev/models/gettickhistoryop.py +104 -0
  230. finance_dev/models/gettopgainersop.py +78 -0
  231. finance_dev/models/gettoplosersop.py +78 -0
  232. finance_dev/models/gettopmostactiveop.py +78 -0
  233. finance_dev/models/gettranscriptop.py +53 -0
  234. finance_dev/models/gettreasuryyieldsop.py +157 -0
  235. finance_dev/models/getunifiedsnapshotop.py +223 -0
  236. finance_dev/models/getupgradesdowngradesop.py +91 -0
  237. finance_dev/models/getusaspendingop.py +70 -0
  238. finance_dev/models/getuseraccountsop.py +40 -0
  239. finance_dev/models/getusptopatentsop.py +70 -0
  240. finance_dev/models/getvisaapplicationsop.py +73 -0
  241. finance_dev/models/historicalesgresponse.py +439 -0
  242. finance_dev/models/httpmetadata.py +24 -0
  243. finance_dev/models/incomestatement.py +296 -0
  244. finance_dev/models/incomestatementsresponse.py +61 -0
  245. finance_dev/models/indexbarresponse.py +194 -0
  246. finance_dev/models/indexbarsresponse.py +123 -0
  247. finance_dev/models/indexchartbarsresponse.py +213 -0
  248. finance_dev/models/indexgroupsresponse.py +78 -0
  249. finance_dev/models/indexlistresponse.py +95 -0
  250. finance_dev/models/indexsearchresponse.py +93 -0
  251. finance_dev/models/indicesbygroupresponse.py +93 -0
  252. finance_dev/models/indicesidentifiertype.py +28 -0
  253. finance_dev/models/indicesrealtimeindexinfo.py +67 -0
  254. finance_dev/models/indicesrealtimeindexvalue.py +157 -0
  255. finance_dev/models/indicesrealtimevaluesitem.py +80 -0
  256. finance_dev/models/indicesrealtimevaluesresponse.py +42 -0
  257. finance_dev/models/inflationexpectationsresponse.py +140 -0
  258. finance_dev/models/inflationresponse.py +132 -0
  259. finance_dev/models/insidertransaction.py +97 -0
  260. finance_dev/models/insidertransactionsresponse.py +39 -0
  261. finance_dev/models/institutionalholding.py +77 -0
  262. finance_dev/models/institutionalownershipresponse.py +39 -0
  263. finance_dev/models/institutionalportfolioresponse.py +39 -0
  264. finance_dev/models/investmentthemeid.py +35 -0
  265. finance_dev/models/investmentthemeresponse.py +80 -0
  266. finance_dev/models/ipocalendarresponse.py +39 -0
  267. finance_dev/models/ipoitem.py +93 -0
  268. finance_dev/models/listactiveordersop.py +87 -0
  269. finance_dev/models/listcategoriesop.py +79 -0
  270. finance_dev/models/listindexgroupsop.py +40 -0
  271. finance_dev/models/listindicesbygroupop.py +58 -0
  272. finance_dev/models/listindicesop.py +91 -0
  273. finance_dev/models/listoptioncontractsop.py +66 -0
  274. finance_dev/models/listoptionexpirydatesop.py +56 -0
  275. finance_dev/models/listorderhistoryop.py +139 -0
  276. finance_dev/models/listpredictionordersop.py +134 -0
  277. finance_dev/models/listtranscriptsop.py +53 -0
  278. finance_dev/models/lobbyingresponse.py +166 -0
  279. finance_dev/models/logosearchresponse.py +152 -0
  280. finance_dev/models/logosyncresponse.py +277 -0
  281. finance_dev/models/marketbreadth.py +97 -0
  282. finance_dev/models/marketbreadthresponse.py +44 -0
  283. finance_dev/models/marketholiday.py +56 -0
  284. finance_dev/models/marketholidayresponse.py +39 -0
  285. finance_dev/models/marketstatus.py +67 -0
  286. finance_dev/models/mergersandacquisitionsresponse.py +186 -0
  287. finance_dev/models/metricseriespoint.py +43 -0
  288. finance_dev/models/netinflow.py +81 -0
  289. finance_dev/models/netinflowresponse.py +44 -0
  290. finance_dev/models/newsitem.py +102 -0
  291. finance_dev/models/newslistresponse.py +128 -0
  292. finance_dev/models/newssentimentresponse.py +176 -0
  293. finance_dev/models/newstypeenum.py +16 -0
  294. finance_dev/models/optionchainsnapshotresponse.py +64 -0
  295. finance_dev/models/optioncontractsnapshotresponse.py +64 -0
  296. finance_dev/models/optioncontractsnapshotrow.py +164 -0
  297. finance_dev/models/optioncontractsresponse.py +131 -0
  298. finance_dev/models/optiondetailsdto.py +72 -0
  299. finance_dev/models/optionexpirydatesresponse.py +53 -0
  300. finance_dev/models/optionlasttraderesponse.py +169 -0
  301. finance_dev/models/optionopencloseresponse.py +97 -0
  302. finance_dev/models/optionpreviousdayresponse.py +89 -0
  303. finance_dev/models/optionquotesresponse.py +138 -0
  304. finance_dev/models/optionsactivityresponse.py +265 -0
  305. finance_dev/models/optionsnapshotcontractdetails.py +85 -0
  306. finance_dev/models/optionsnapshotdaybar.py +107 -0
  307. finance_dev/models/optionsnapshotgreeks.py +67 -0
  308. finance_dev/models/optionsnapshotlastquote.py +116 -0
  309. finance_dev/models/optionsnapshotlasttrade.py +102 -0
  310. finance_dev/models/optionsnapshotunderlying.py +86 -0
  311. finance_dev/models/optionsscreeneritem.py +151 -0
  312. finance_dev/models/optionsunusualactivityresponse.py +62 -0
  313. finance_dev/models/optionsunusualactivitytrade.py +103 -0
  314. finance_dev/models/optiontradesresponse.py +132 -0
  315. finance_dev/models/orderrequesttimeinforceenum.py +29 -0
  316. finance_dev/models/ordersideenum.py +20 -0
  317. finance_dev/models/ordertypeenum.py +20 -0
  318. finance_dev/models/ordleg.py +162 -0
  319. finance_dev/models/ordresponse.py +202 -0
  320. finance_dev/models/ownershipdataresponse.py +39 -0
  321. finance_dev/models/ownershiprecord.py +74 -0
  322. finance_dev/models/placeorderop.py +40 -0
  323. finance_dev/models/placeordrequest_union.py +335 -0
  324. finance_dev/models/placepredictionorderop.py +114 -0
  325. finance_dev/models/predmdbaseevent.py +62 -0
  326. finance_dev/models/predmdbaseeventhit.py +53 -0
  327. finance_dev/models/predmdbaseeventsearchresult.py +46 -0
  328. finance_dev/models/predmdbaseeventwithevents.py +32 -0
  329. finance_dev/models/predmdbatchhistoryrequest.py +16 -0
  330. finance_dev/models/predmdbatchhistoryresponse.py +16 -0
  331. finance_dev/models/predmdcategory.py +48 -0
  332. finance_dev/models/predmdcontract.py +177 -0
  333. finance_dev/models/predmdcontracthit.py +157 -0
  334. finance_dev/models/predmdcontractsearchresult.py +46 -0
  335. finance_dev/models/predmdevent.py +136 -0
  336. finance_dev/models/predmdeventhit.py +73 -0
  337. finance_dev/models/predmdeventsearchresult.py +46 -0
  338. finance_dev/models/predmdeventwithcontracts.py +22 -0
  339. finance_dev/models/predmdhistoryrequest.py +59 -0
  340. finance_dev/models/predmdhistoryresponse.py +53 -0
  341. finance_dev/models/predmdtick.py +53 -0
  342. finance_dev/models/predordersapperror.py +78 -0
  343. finance_dev/models/predordersorder.py +267 -0
  344. finance_dev/models/predordersorderdetail.py +26 -0
  345. finance_dev/models/predordersorderpage.py +45 -0
  346. finance_dev/models/predordersorderreport.py +88 -0
  347. finance_dev/models/predordersplaceorderrequest.py +159 -0
  348. finance_dev/models/previeworderop.py +40 -0
  349. finance_dev/models/previeworderresponse.py +127 -0
  350. finance_dev/models/pricetargetresponse.py +89 -0
  351. finance_dev/models/quotedata.py +49 -0
  352. finance_dev/models/quotesresponse.py +62 -0
  353. finance_dev/models/quotevalues.py +114 -0
  354. finance_dev/models/rating.py +193 -0
  355. finance_dev/models/ratingsaccuracy.py +242 -0
  356. finance_dev/models/replaceconfigwatchlistsop.py +40 -0
  357. finance_dev/models/replaceorderop.py +40 -0
  358. finance_dev/models/replaceordrequest_union.py +367 -0
  359. finance_dev/models/replaceordresponse.py +144 -0
  360. finance_dev/models/reportedbsitem.py +53 -0
  361. finance_dev/models/reportedfiling.py +110 -0
  362. finance_dev/models/reportedreport.py +39 -0
  363. finance_dev/models/reportingfrequency.py +16 -0
  364. finance_dev/models/revenuebreakdownaxis.py +52 -0
  365. finance_dev/models/revenuebreakdowndatapoint.py +58 -0
  366. finance_dev/models/revenuebreakdownentry.py +51 -0
  367. finance_dev/models/revenuebreakdowninner.py +75 -0
  368. finance_dev/models/revenueestimatesresponse.py +176 -0
  369. finance_dev/models/screenclause.py +70 -0
  370. finance_dev/models/screengroup.py +75 -0
  371. finance_dev/models/searchetbsymbolsop.py +40 -0
  372. finance_dev/models/searchindicesop.py +55 -0
  373. finance_dev/models/searchlogosop.py +158 -0
  374. finance_dev/models/searchmarketsymbolsop.py +80 -0
  375. finance_dev/models/searchop.py +232 -0
  376. finance_dev/models/searchsymbolitem.py +76 -0
  377. finance_dev/models/searchsymbolresponseitem.py +71 -0
  378. finance_dev/models/searchsymbolsop.py +77 -0
  379. finance_dev/models/secfiling.py +91 -0
  380. finance_dev/models/secfilingsresponse.py +39 -0
  381. finance_dev/models/sectoravgchange.py +50 -0
  382. finance_dev/models/security.py +59 -0
  383. finance_dev/models/securitytypeenum.py +16 -0
  384. finance_dev/models/shortinterestresponse.py +126 -0
  385. finance_dev/models/shortvolumeresponse.py +198 -0
  386. finance_dev/models/socialsentimentresponse.py +124 -0
  387. finance_dev/models/sortorderenum.py +13 -0
  388. finance_dev/models/spinoffsresponse.py +136 -0
  389. finance_dev/models/stockaggregatesresponse.py +92 -0
  390. finance_dev/models/stocklastquoteresponse.py +175 -0
  391. finance_dev/models/stocklasttraderesponse.py +169 -0
  392. finance_dev/models/stockohlcbar.py +85 -0
  393. finance_dev/models/stockopencloseresponse.py +109 -0
  394. finance_dev/models/stockpreviousdayresponse.py +86 -0
  395. finance_dev/models/stockquotesresponse.py +170 -0
  396. finance_dev/models/stockscreenerop.py +144 -0
  397. finance_dev/models/stockscreenerrequest.py +131 -0
  398. finance_dev/models/stockscreenerresponse.py +197 -0
  399. finance_dev/models/stocksnapshotsresponse.py +454 -0
  400. finance_dev/models/stocksplitsresponse.py +144 -0
  401. finance_dev/models/stocktradesresponse.py +164 -0
  402. finance_dev/models/supplychainresponse.py +158 -0
  403. finance_dev/models/supportresistancelevelsresponse.py +68 -0
  404. finance_dev/models/symboldetails.py +119 -0
  405. finance_dev/models/symbolerror.py +43 -0
  406. finance_dev/models/symbolinfo.py +183 -0
  407. finance_dev/models/synclogosop.py +146 -0
  408. finance_dev/models/technicalindicatorresponse.py +43 -0
  409. finance_dev/models/technicalindicatortype.py +21 -0
  410. finance_dev/models/technicalpatternsresponse.py +249 -0
  411. finance_dev/models/tenderofferidentifiertype.py +26 -0
  412. finance_dev/models/tenderoffersresponse.py +548 -0
  413. finance_dev/models/tickchartbars.py +104 -0
  414. finance_dev/models/tickresolution.py +20 -0
  415. finance_dev/models/timeinforceenum.py +26 -0
  416. finance_dev/models/tradeorderlegv2.py +164 -0
  417. finance_dev/models/tradeorderv2.py +434 -0
  418. finance_dev/models/transcriptresponse.py +162 -0
  419. finance_dev/models/transcriptsresponse.py +89 -0
  420. finance_dev/models/treasuryyieldsresponse.py +160 -0
  421. finance_dev/models/unifiedsnapshotresponse.py +308 -0
  422. finance_dev/models/updateconfigwatchlistbyidop.py +65 -0
  423. finance_dev/models/updatewatchlistrequest_union.py +134 -0
  424. finance_dev/models/upgradesdowngradesresponse.py +98 -0
  425. finance_dev/models/usaspendingresponse.py +222 -0
  426. finance_dev/models/usptopatentsresponse.py +134 -0
  427. finance_dev/models/visaapplicationsresponse.py +228 -0
  428. finance_dev/models/watchlist.py +68 -0
  429. finance_dev/models/watchlistrequest.py +43 -0
  430. finance_dev/models/watchlistresponse.py +44 -0
  431. finance_dev/news_sdk.py +829 -0
  432. finance_dev/options.py +3443 -0
  433. finance_dev/orders.py +1907 -0
  434. finance_dev/ownership.py +1386 -0
  435. finance_dev/predictions.py +29 -0
  436. finance_dev/predictions_orders.py +852 -0
  437. finance_dev/py.typed +1 -0
  438. finance_dev/sdk.py +347 -0
  439. finance_dev/sdkconfiguration.py +50 -0
  440. finance_dev/signals.py +338 -0
  441. finance_dev/stocks.py +3131 -0
  442. finance_dev/technicalanalysis_sdk.py +1137 -0
  443. finance_dev/types/__init__.py +25 -0
  444. finance_dev/types/base64fileinput.py +44 -0
  445. finance_dev/types/basemodel.py +78 -0
  446. finance_dev/utils/__init__.py +181 -0
  447. finance_dev/utils/annotations.py +80 -0
  448. finance_dev/utils/datetimes.py +42 -0
  449. finance_dev/utils/dynamic_imports.py +55 -0
  450. finance_dev/utils/enums.py +135 -0
  451. finance_dev/utils/eventstreaming.py +327 -0
  452. finance_dev/utils/forms.py +240 -0
  453. finance_dev/utils/headers.py +137 -0
  454. finance_dev/utils/logger.py +23 -0
  455. finance_dev/utils/metadata.py +120 -0
  456. finance_dev/utils/queryparams.py +218 -0
  457. finance_dev/utils/requestbodies.py +68 -0
  458. finance_dev/utils/retries.py +373 -0
  459. finance_dev/utils/security.py +199 -0
  460. finance_dev/utils/serializers.py +327 -0
  461. finance_dev/utils/unmarshal_json_response.py +39 -0
  462. finance_dev/utils/url.py +155 -0
  463. finance_dev/utils/values.py +143 -0
  464. finance_dev/watchlist_sdk.py +1583 -0
  465. finance_dev/websocket/__init__.py +57 -0
  466. finance_dev/websocket/account.py +5 -0
  467. finance_dev/websocket/auth.py +124 -0
  468. finance_dev/websocket/chart.py +5 -0
  469. finance_dev/websocket/codec.py +66 -0
  470. finance_dev/websocket/config.py +52 -0
  471. finance_dev/websocket/errors.py +54 -0
  472. finance_dev/websocket/market.py +5 -0
  473. finance_dev/websocket/models/__init__.py +52 -0
  474. finance_dev/websocket/models/account.py +60 -0
  475. finance_dev/websocket/models/chart.py +44 -0
  476. finance_dev/websocket/models/common.py +85 -0
  477. finance_dev/websocket/models/market.py +113 -0
  478. finance_dev/websocket/resources.py +327 -0
  479. finance_dev/websocket/sdk.py +13 -0
  480. finance_dev/websocket/transport/async_connection.py +493 -0
  481. finance_dev/websocket/transport/base.py +78 -0
  482. finance_dev/websocket/transport/sync_connection.py +470 -0
  483. finance_dev-0.9.0.dist-info/METADATA +913 -0
  484. finance_dev-0.9.0.dist-info/RECORD +486 -0
  485. finance_dev-0.9.0.dist-info/WHEEL +5 -0
  486. finance_dev-0.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,493 @@
1
+ """Asyncio websocket connection with a single reader task."""
2
+
3
+ import asyncio
4
+ import inspect
5
+ import random
6
+ import uuid
7
+ from typing import Any
8
+ from ..auth import AsyncTokenProvider, TokenProvider, StaticTokenProvider
9
+ from ..codec import decode_frame, encode_message
10
+ from ..config import WebSocketConfig
11
+ from ..errors import (
12
+ WebSocketAuthenticationError,
13
+ WebSocketBackpressureError,
14
+ WebSocketConnectionError,
15
+ WebSocketConnectionStateError,
16
+ WebSocketProtocolError,
17
+ WebSocketRequestError,
18
+ )
19
+ from .base import apply_unsubscribe, subscription_key
20
+
21
+
22
+ class _SessionExpired(ConnectionError):
23
+ """Internal signal to reconnect after server-side token expiry."""
24
+
25
+
26
+ class AsyncConnection:
27
+ def __init__(
28
+ self, url: str, token_provider: Any, config: WebSocketConfig | None = None
29
+ ):
30
+ self.url, self.provider, self.config = (
31
+ url,
32
+ token_provider,
33
+ config or WebSocketConfig(),
34
+ )
35
+ self._state = "DISCONNECTED"
36
+ self._socket: Any = None
37
+ self._reader: asyncio.Task | None = None
38
+ self.resource: Any = None
39
+ self._pending: dict[str, asyncio.Future] = {}
40
+ self._events: asyncio.Queue[Any] = asyncio.Queue(
41
+ self.config.incoming_queue_size
42
+ )
43
+ self._closed = False
44
+ self._auth_id: str | None = None
45
+ self._active: dict[str, dict[str, Any]] = {}
46
+ self._keepalive: asyncio.Task | None = None
47
+ self._terminal: Exception | None = None
48
+ self._terminal_reported = False
49
+
50
+ async def connect(self):
51
+ try:
52
+ from websockets.asyncio.client import connect
53
+
54
+ self._state = "CONNECTING"
55
+ self._socket = await asyncio.wait_for(
56
+ connect(
57
+ self.url,
58
+ open_timeout=self.config.connect_timeout,
59
+ close_timeout=self.config.close_timeout,
60
+ max_size=self.config.max_message_size,
61
+ ping_interval=self.config.ping_interval,
62
+ ping_timeout=self.config.pong_timeout,
63
+ ),
64
+ self.config.connect_timeout,
65
+ )
66
+ self._state = "AUTHENTICATING"
67
+ self._reader = asyncio.create_task(self._read_loop())
68
+ self._auth_id = self._id("auth")
69
+ auth_future = self._pending_future(self._auth_id)
70
+ await self._send(
71
+ {
72
+ "type": "request",
73
+ "id": self._auth_id,
74
+ "payload": {
75
+ "method": "POST",
76
+ "path": "/auth",
77
+ "body": {"token": await self._get_token()},
78
+ },
79
+ }
80
+ )
81
+ result = await asyncio.wait_for(auth_future, self.config.auth_timeout)
82
+ if result.get("payload", {}).get("action") != "authSuccess":
83
+ raise WebSocketAuthenticationError(
84
+ "WebSocket authentication was rejected"
85
+ )
86
+ self._state = "READY"
87
+ self._keepalive = asyncio.create_task(self._keepalive_loop())
88
+ return self
89
+ except asyncio.TimeoutError as exc:
90
+ await self.close()
91
+ raise WebSocketAuthenticationError(
92
+ "WebSocket authentication timed out"
93
+ ) from exc
94
+ except (WebSocketAuthenticationError, WebSocketConnectionError):
95
+ await self.close()
96
+ raise
97
+ except WebSocketRequestError as exc:
98
+ await self.close()
99
+ raise WebSocketAuthenticationError(
100
+ "WebSocket authentication was rejected"
101
+ ) from exc
102
+ except Exception as exc:
103
+ await self.close()
104
+ raise WebSocketConnectionError(
105
+ "Unable to connect or authenticate websocket"
106
+ ) from exc
107
+
108
+ @property
109
+ def state(self):
110
+ """Current read-only connection state."""
111
+ return self._state
112
+
113
+ def _id(self, prefix):
114
+ return f"{prefix}-{uuid.uuid4()}"
115
+
116
+ async def _get_token(self):
117
+ value = self.provider.get_token()
118
+ return await value if inspect.isawaitable(value) else value
119
+
120
+ def _pending_future(self, request_id):
121
+ future = asyncio.get_running_loop().create_future()
122
+ self._pending[request_id] = future
123
+ return future
124
+
125
+ async def _send(self, value):
126
+ await self._socket.send(encode_message(value))
127
+
128
+ async def send_message(self, message):
129
+ self._require_ready()
130
+ key = subscription_key(message)
131
+ if message.get("type") == "subscribe" and key in self._active:
132
+ return message.get("id")
133
+ await self._send(message)
134
+ if message.get("type") == "subscribe":
135
+ self._active[key] = dict(message)
136
+ elif message.get("type") == "unsubscribe":
137
+ apply_unsubscribe(self._active, message)
138
+ return message.get("id")
139
+
140
+ async def exchange(self, message, *, timeout=None):
141
+ self._require_ready()
142
+ ident = message.get("id") or self._id("request")
143
+ message["id"] = ident
144
+ future = self._pending_future(ident)
145
+ await self._send(message)
146
+ try:
147
+ return await asyncio.wait_for(
148
+ future, timeout or self.config.request_timeout
149
+ )
150
+ finally:
151
+ self._pending.pop(ident, None)
152
+
153
+ async def _read_loop(self):
154
+ try:
155
+ async for frame in self._socket:
156
+ for message in decode_frame(frame):
157
+ if hasattr(message, "original"):
158
+ await self._put(message)
159
+ continue
160
+ wire_message = message
161
+ message = message.model_dump(by_alias=True, exclude_none=True)
162
+ payload = message.get("payload", {})
163
+ ident = message.get("id") or payload.get("id")
164
+ action = payload.get("action")
165
+ if action == "authExpired":
166
+ raise _SessionExpired("WebSocket session expired")
167
+ if action in {"authRequired", "authTimeout"}:
168
+ raise WebSocketAuthenticationError(
169
+ str(payload.get("error", action))
170
+ )
171
+ if action == "refreshAuth":
172
+ asyncio.create_task(self._refresh_auth())
173
+ continue
174
+ if ident in self._pending and (
175
+ message.get("type") in {"response", "subscribed", "pong"}
176
+ or action in {"authSuccess", "error"}
177
+ ):
178
+ future = self._pending.pop(ident)
179
+ if payload.get("error") or payload.get("status", 200) >= 400:
180
+ if ident == self._auth_id:
181
+ future.set_exception(
182
+ WebSocketAuthenticationError(
183
+ "WebSocket authentication was rejected"
184
+ )
185
+ )
186
+ else:
187
+ future.set_exception(
188
+ WebSocketRequestError(
189
+ str(
190
+ payload.get(
191
+ "message",
192
+ payload.get("error", "request failed"),
193
+ )
194
+ ),
195
+ status=payload.get("status"),
196
+ code=payload.get("code"),
197
+ request_id=ident,
198
+ )
199
+ )
200
+ elif action == "error":
201
+ future.set_exception(
202
+ WebSocketRequestError(
203
+ str(payload.get("error", "request failed")),
204
+ status=payload.get("status"),
205
+ code=payload.get("code"),
206
+ request_id=ident,
207
+ )
208
+ )
209
+ elif not future.done():
210
+ future.set_result(message)
211
+ elif action == "authSuccess" and self._auth_id in self._pending:
212
+ self._pending.pop(self._auth_id).set_result(message)
213
+ else:
214
+ await self._put(wire_message)
215
+ except asyncio.CancelledError:
216
+ raise
217
+ except Exception as exc:
218
+ if self._closed:
219
+ return
220
+ transient = isinstance(
221
+ exc, (OSError, ConnectionError, TimeoutError)
222
+ ) or exc.__class__.__name__.startswith("ConnectionClosed")
223
+ if (
224
+ self.config.reconnect
225
+ and transient
226
+ and not isinstance(
227
+ exc,
228
+ (
229
+ WebSocketProtocolError,
230
+ WebSocketAuthenticationError,
231
+ WebSocketRequestError,
232
+ WebSocketBackpressureError,
233
+ ),
234
+ )
235
+ ):
236
+ try:
237
+ await self._recover()
238
+ except Exception as reconnect_error:
239
+ self._state = "FAILED"
240
+ self._terminal = reconnect_error
241
+ await self._put_error(reconnect_error)
242
+ else:
243
+ self._state = "FAILED"
244
+ self._terminal = exc
245
+ await self._put_error(exc)
246
+
247
+ async def _refresh_auth(self):
248
+ try:
249
+ token = await self._get_token()
250
+ ident = self._id("auth")
251
+ future = self._pending_future(ident)
252
+ await self._send(
253
+ {
254
+ "type": "request",
255
+ "id": ident,
256
+ "payload": {
257
+ "method": "POST",
258
+ "path": "/auth",
259
+ "body": {"token": token},
260
+ },
261
+ }
262
+ )
263
+ result = await asyncio.wait_for(future, self.config.auth_timeout)
264
+ if result.get("payload", {}).get("action") != "authSuccess":
265
+ raise WebSocketAuthenticationError(
266
+ "WebSocket token refresh was rejected"
267
+ )
268
+ except Exception as exc:
269
+ await self._put_error(exc)
270
+ await self.close()
271
+
272
+ async def _recover(self):
273
+ self._state = "RECONNECTING"
274
+ if self._keepalive:
275
+ self._keepalive.cancel()
276
+ if self._socket:
277
+ try:
278
+ await self._socket.close()
279
+ except Exception:
280
+ pass
281
+ attempt = 0
282
+ while not self._closed:
283
+ delay = min(
284
+ self.config.reconnect_max_delay,
285
+ self.config.reconnect_initial_delay * (2**attempt),
286
+ )
287
+ await asyncio.sleep(
288
+ delay
289
+ * random.uniform(
290
+ 1 - self.config.reconnect_jitter, 1 + self.config.reconnect_jitter
291
+ )
292
+ )
293
+ try:
294
+ from websockets.asyncio.client import connect
295
+
296
+ self._socket = await asyncio.wait_for(
297
+ connect(
298
+ self.url,
299
+ open_timeout=self.config.connect_timeout,
300
+ close_timeout=self.config.close_timeout,
301
+ max_size=self.config.max_message_size,
302
+ ping_interval=self.config.ping_interval,
303
+ ping_timeout=self.config.pong_timeout,
304
+ ),
305
+ self.config.connect_timeout,
306
+ )
307
+ self._state = "AUTHENTICATING"
308
+ self._auth_id = self._id("auth")
309
+ future = self._pending_future(self._auth_id)
310
+ self._reader = asyncio.create_task(self._read_loop())
311
+ token = await self._get_token()
312
+ await self._send(
313
+ {
314
+ "type": "request",
315
+ "id": self._auth_id,
316
+ "payload": {
317
+ "method": "POST",
318
+ "path": "/auth",
319
+ "body": {"token": token},
320
+ },
321
+ }
322
+ )
323
+ result = await asyncio.wait_for(future, self.config.auth_timeout)
324
+ if result.get("payload", {}).get("action") != "authSuccess":
325
+ raise WebSocketAuthenticationError(
326
+ "WebSocket authentication was rejected"
327
+ )
328
+ self._state = "READY"
329
+ if self.config.replay_subscriptions:
330
+ for message in self._active.values():
331
+ await self._send(message)
332
+ self._keepalive = asyncio.create_task(self._keepalive_loop())
333
+ return
334
+ except WebSocketAuthenticationError:
335
+ self._state = "FAILED"
336
+ raise
337
+ except WebSocketRequestError as exc:
338
+ self._state = "FAILED"
339
+ raise WebSocketAuthenticationError(
340
+ "WebSocket authentication was rejected"
341
+ ) from exc
342
+ except Exception as exc:
343
+ attempt += 1
344
+ if attempt >= 8:
345
+ self._state = "FAILED"
346
+ raise WebSocketConnectionError(
347
+ "WebSocket reconnect attempts exhausted"
348
+ ) from exc
349
+
350
+ async def _keepalive_loop(self):
351
+ try:
352
+ while not self._closed and self._state == "READY":
353
+ await asyncio.sleep(self.config.ping_interval)
354
+ ident = self._id("ping")
355
+ future = self._pending_future(ident)
356
+ await self._send({"type": "ping", "id": ident})
357
+ await asyncio.wait_for(future, self.config.pong_timeout)
358
+ except asyncio.CancelledError:
359
+ pass
360
+ except Exception:
361
+ if self._socket and not self._closed:
362
+ try:
363
+ await self._socket.close()
364
+ except Exception:
365
+ pass
366
+
367
+ async def _put(self, event):
368
+ try:
369
+ self._events.put_nowait(event)
370
+ except asyncio.QueueFull:
371
+ self._terminal = WebSocketBackpressureError(
372
+ "Incoming websocket queue is full; drain events faster or increase incoming_queue_size"
373
+ )
374
+ self._closed = True
375
+ self._state = "FAILED"
376
+ try:
377
+ await self._socket.close()
378
+ finally:
379
+ await self._put_error(self._terminal)
380
+
381
+ async def _put_error(self, error):
382
+ self._terminal = error
383
+ self._closed = True
384
+ try:
385
+ self._events.put_nowait(error)
386
+ except asyncio.QueueFull:
387
+ pass
388
+
389
+ async def request(self, method, path, *, query=None, body=None, timeout=None):
390
+ payload = {"method": method, "path": path}
391
+ if query is not None:
392
+ payload["query"] = query
393
+ if body is not None:
394
+ payload["body"] = body
395
+ return await self.exchange(
396
+ {"type": "request", "id": self._id("request"), "payload": payload},
397
+ timeout=timeout,
398
+ )
399
+
400
+ def _require_ready(self):
401
+ if self.state != "READY":
402
+ raise WebSocketConnectionStateError(
403
+ "WebSocket must be authenticated and READY before this operation"
404
+ )
405
+
406
+ async def subscribe(self, payload, *, data_type="quotes", symbol_type=None):
407
+ resource: Any = getattr(self, "resource", None)
408
+ if getattr(resource, "protocol", "") == "chart":
409
+ return await resource.subscribe_async(
410
+ self, payload, data_type=data_type, symbol_type=symbol_type
411
+ )
412
+ if getattr(resource, "protocol", "") == "market":
413
+ return await resource.subscribe_async(self, payload)
414
+ ident = self._id("subscribe")
415
+ await self.send_message({"type": "subscribe", "id": ident, "payload": payload})
416
+ return ident
417
+
418
+ async def unsubscribe(self, payload):
419
+ resource: Any = getattr(self, "resource", None)
420
+ if getattr(resource, "protocol", "") == "chart":
421
+ return await resource.unsubscribe_async(self, payload)
422
+ if getattr(resource, "protocol", "") == "market":
423
+ return await resource.unsubscribe_async(self, payload)
424
+ ident = self._id("unsubscribe")
425
+ await self.send_message(
426
+ {"type": "unsubscribe", "id": ident, "payload": payload}
427
+ )
428
+ return ident
429
+
430
+ async def get_event(self):
431
+ item = await self._events.get()
432
+ if isinstance(item, Exception):
433
+ self._terminal_reported = True
434
+ raise item
435
+ return item
436
+
437
+ def __aiter__(self):
438
+ return self
439
+
440
+ def __getattr__(self, name):
441
+ resource = self.__dict__.get("resource")
442
+ method: Any = getattr(resource, name, None) if resource is not None else None
443
+ if method is not None and callable(method):
444
+
445
+ async def delegated(*args, **kwargs):
446
+ result: Any = method(self, *args, **kwargs)
447
+ return await result if inspect.isawaitable(result) else result
448
+
449
+ return delegated
450
+ raise AttributeError(name)
451
+
452
+ async def __anext__(self):
453
+ if self._closed and self._events.empty():
454
+ if self._terminal and not self._terminal_reported:
455
+ self._terminal_reported = True
456
+ raise self._terminal
457
+ raise StopAsyncIteration
458
+ return await self.get_event()
459
+
460
+ async def close(self):
461
+ if self._closed and self._state == "CLOSED":
462
+ return
463
+ self._closed = True
464
+ self._state = "CLOSING"
465
+ for future in self._pending.values():
466
+ if not future.done():
467
+ future.cancel()
468
+ self._pending.clear()
469
+ if self._keepalive:
470
+ self._keepalive.cancel()
471
+ if self._keepalive is not asyncio.current_task():
472
+ try:
473
+ await self._keepalive
474
+ except BaseException:
475
+ pass
476
+ if self._socket:
477
+ try:
478
+ await asyncio.wait_for(self._socket.close(), self.config.close_timeout)
479
+ except Exception:
480
+ pass
481
+ if self._reader and self._reader is not asyncio.current_task():
482
+ self._reader.cancel()
483
+ try:
484
+ await self._reader
485
+ except BaseException:
486
+ pass
487
+ self._state = "CLOSED"
488
+
489
+ async def __aenter__(self):
490
+ return await self.connect()
491
+
492
+ async def __aexit__(self, *_):
493
+ await self.close()
@@ -0,0 +1,78 @@
1
+ """Protocol-independent subscription replay helpers."""
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+
7
+ def subscription_key(message: dict[str, Any]) -> str:
8
+ return json.dumps(
9
+ {"type": message.get("type"), "payload": message.get("payload")},
10
+ sort_keys=True,
11
+ separators=(",", ":"),
12
+ )
13
+
14
+
15
+ def apply_unsubscribe(
16
+ active: dict[str, dict[str, Any]], message: dict[str, Any]
17
+ ) -> None:
18
+ """Update canonical active subscriptions for full and field-level removals."""
19
+ payload = message.get("payload")
20
+ requested = payload if isinstance(payload, list) else [payload]
21
+ for removal in requested:
22
+ if isinstance(removal, str):
23
+ removal = {"symbol": removal}
24
+ if not isinstance(removal, dict):
25
+ continue
26
+ symbol = removal.get("symbol")
27
+ if not symbol:
28
+ continue
29
+ full = set(removal) <= {"symbol", "type"}
30
+ for key, subscription in list(active.items()):
31
+ body = subscription.get("payload")
32
+ items = body if isinstance(body, list) else [body]
33
+ kept = []
34
+ for original in items:
35
+ if not isinstance(original, dict) or original.get("symbol") != symbol:
36
+ kept.append(original)
37
+ continue
38
+ current = dict(original)
39
+ if full:
40
+ continue
41
+ for category in ("quote", "trade", "level2", "timeAndSales", "greeks"):
42
+ if removal.get(category):
43
+ if category == "quote":
44
+ current.pop("quoteFields", None)
45
+ elif category == "trade":
46
+ current.pop("tradeFields", None)
47
+ elif category == "greeks":
48
+ current.pop("greeksFields", None)
49
+ else:
50
+ current.pop(category, None)
51
+ for field in ("quoteFields", "tradeFields", "greeksFields"):
52
+ removed = removal.get(field)
53
+ existing = current.get(field)
54
+ if removed and isinstance(existing, list):
55
+ remaining = [
56
+ value for value in existing if value not in removed
57
+ ]
58
+ if remaining:
59
+ current[field] = remaining
60
+ else:
61
+ current.pop(field, None)
62
+ # Chart messages carry a single data mode in `type` and have no
63
+ # other fields to preserve after the symbol is removed.
64
+ if len(current) <= 2 and "symbol" in current and "type" in current:
65
+ continue
66
+ if len(current) == 1 and "symbol" in current:
67
+ continue
68
+ kept.append(current)
69
+ if not kept:
70
+ active.pop(key, None)
71
+ elif isinstance(body, list):
72
+ subscription["payload"] = kept
73
+ active.pop(key, None)
74
+ active[subscription_key(subscription)] = subscription
75
+ else:
76
+ subscription["payload"] = kept[0]
77
+ active.pop(key, None)
78
+ active[subscription_key(subscription)] = subscription