diff --git a/MosaicIQ/src-tauri/Cargo.lock b/MosaicIQ/src-tauri/Cargo.lock index 091cdbb..6a35d99 100644 --- a/MosaicIQ/src-tauri/Cargo.lock +++ b/MosaicIQ/src-tauri/Cargo.lock @@ -8,6 +8,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -47,6 +58,12 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "as-any" version = "0.3.2" @@ -77,6 +94,18 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-compression" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-executor" version = "1.14.0" @@ -305,6 +334,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -336,6 +377,30 @@ dependencies = [ "piper", ] +[[package]] +name = "borsh" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +dependencies = [ + "once_cell", + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "brotli" version = "8.0.2" @@ -363,6 +428,28 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "bytemuck" version = "1.25.0" @@ -509,11 +596,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-link 0.2.1", ] +[[package]] +name = "chrono-tz" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" +dependencies = [ + "chrono", + "phf 0.12.1", + "serde", +] + [[package]] name = "cmake" version = "0.1.58" @@ -533,6 +633,24 @@ dependencies = [ "memchr", ] +[[package]] +name = "compression-codecs" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" +dependencies = [ + "brotli", + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -554,10 +672,29 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ + "percent-encoding", "time", "version_check", ] +[[package]] +name = "cookie_store" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" +dependencies = [ + "cookie", + "document-features", + "idna", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -862,6 +999,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "dom_query" version = "0.27.0" @@ -913,6 +1059,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "embed-resource" version = "3.0.8" @@ -1129,6 +1281,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futf" version = "0.1.5" @@ -1587,6 +1745,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] [[package]] name = "hashbrown" @@ -1723,6 +1884,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", + "webpki-roots 1.0.6", ] [[package]] @@ -1965,6 +2127,41 @@ dependencies = [ "once_cell", ] +[[package]] +name = "isin" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19bcf2544150282ebe712b4615813c1986bb014564928b1c4a4a567402d6bf02" + +[[package]] +name = "iso_country" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20633e788d3948ea7336861fdb09ec247f5dae4267e8f0743fa97de26c28624d" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "iso_currency" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed4b3f0921193400b1df556228bfd917c57c7fa38bda904d552653c5c3b641b" +dependencies = [ + "iso_country", + "proc-macro2", + "quote", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -2105,6 +2302,12 @@ dependencies = [ "selectors 0.24.0", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "leb128fmt" version = "0.1.0" @@ -2172,6 +2375,12 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.14" @@ -2312,6 +2521,7 @@ dependencies = [ "tauri-plugin-opener", "tauri-plugin-store", "tokio", + "yfinance-rs", ] [[package]] @@ -2605,6 +2815,119 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "paft" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c8f13ab3152b3fc1d596fdc6881ab5f0499647e8f1620b393dd04df3d8abcd" +dependencies = [ + "iso_currency", + "paft-aggregates", + "paft-core", + "paft-domain", + "paft-fundamentals", + "paft-market", + "paft-money", + "paft-utils", + "thiserror 2.0.18", +] + +[[package]] +name = "paft-aggregates" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b122c5099ea444a5d3bdc70963311b22d14ee372ef407c60a84b1b1e446074e7" +dependencies = [ + "chrono", + "paft-domain", + "paft-fundamentals", + "paft-market", + "paft-money", + "serde", +] + +[[package]] +name = "paft-core" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40251d2efd21aa5615927400858c5fa000db5ae6de4cd58320e57b0d2d52336" +dependencies = [ + "chrono", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "paft-domain" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7f9a2d69d11e1324d6b3a7dc1ad62f7f909442531406b8b3052d8eea69546e" +dependencies = [ + "chrono", + "isin", + "paft-core", + "paft-utils", + "regex", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "paft-fundamentals" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d591b958232362a8121e27dc5ef570007f7e64788238b2b1b280701536cf336" +dependencies = [ + "chrono", + "iso_currency", + "paft-core", + "paft-domain", + "paft-money", + "paft-utils", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "paft-market" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4708f3a8b55a06c277b4830fae834c03d3cd75f3407be27d0dc7f6b17863700" +dependencies = [ + "bitflags 2.11.0", + "chrono", + "chrono-tz", + "iso_currency", + "paft-core", + "paft-domain", + "paft-money", + "paft-utils", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "paft-money" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078c12dfca26fe2db783d2940b1a01bcf44832ad46006fdfb54b9eee6727e13d" +dependencies = [ + "iso_currency", + "paft-utils", + "rust_decimal", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "paft-utils" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f154451a10bb55eb00f81dfdc6410237ef348ead5b096ae3f186b232a240b3d6" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "pango" version = "0.18.3" @@ -2701,6 +3024,15 @@ dependencies = [ "phf_shared 0.11.3", ] +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_shared 0.12.1", +] + [[package]] name = "phf" version = "0.13.1" @@ -2849,6 +3181,15 @@ dependencies = [ "siphasher 1.0.2", ] +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher 1.0.2", +] + [[package]] name = "phf_shared" version = "0.13.1" @@ -3049,6 +3390,65 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "publicsuffix" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" +dependencies = [ + "idna", + "psl-types", +] + [[package]] name = "quick-xml" version = "0.38.4" @@ -3135,6 +3535,12 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.7.3" @@ -3320,6 +3726,55 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "cookie", + "cookie_store", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 1.0.6", +] + [[package]] name = "reqwest" version = "0.13.2" @@ -3385,13 +3840,13 @@ dependencies = [ "nanoid", "ordered-float", "pin-project-lite", - "reqwest", + "reqwest 0.13.2", "schemars 1.2.1", "serde", "serde_json", "thiserror 2.0.18", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.23.1", "tracing", "tracing-futures", "url", @@ -3411,6 +3866,52 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rkyv" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rust_decimal" +version = "1.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand 0.8.5", + "rkyv", + "serde", + "serde_json", + "wasm-bindgen", +] + [[package]] name = "rustc-hash" version = "2.1.2" @@ -3447,6 +3948,7 @@ checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "aws-lc-rs", "once_cell", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -3520,6 +4022,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + [[package]] name = "same-file" version = "1.0.6" @@ -3608,6 +4116,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "security-framework" version = "3.7.0" @@ -3773,6 +4287,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_with" version = "3.18.0" @@ -3889,6 +4415,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "siphasher" version = "0.3.11" @@ -4174,6 +4706,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.16" @@ -4210,7 +4748,7 @@ dependencies = [ "percent-encoding", "plist", "raw-window-handle", - "reqwest", + "reqwest 0.13.2", "serde", "serde_json", "serde_repr", @@ -4627,10 +5165,26 @@ dependencies = [ "rustls-pki-types", "tokio", "tokio-rustls", - "tungstenite", + "tungstenite 0.23.0", "webpki-roots 0.26.11", ] +[[package]] +name = "tokio-tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite 0.28.0", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -4770,13 +5324,18 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ + "async-compression", "bitflags 2.11.0", "bytes", + "futures-core", "futures-util", "http", "http-body", + "http-body-util", "iri-string", "pin-project-lite", + "tokio", + "tokio-util", "tower", "tower-layer", "tower-service", @@ -4885,6 +5444,25 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.2", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 2.0.18", + "utf-8", +] + [[package]] name = "typeid" version = "1.0.3" @@ -5118,6 +5696,7 @@ dependencies = [ "cfg-if", "once_cell", "rustversion", + "serde", "wasm-bindgen-macro", "wasm-bindgen-shared", ] @@ -5970,6 +6549,15 @@ dependencies = [ "x11-dl", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x11" version = "2.21.0" @@ -5991,6 +6579,27 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "yfinance-rs" +version = "0.7.2" +dependencies = [ + "base64 0.22.1", + "chrono", + "chrono-tz", + "futures", + "futures-util", + "paft", + "prost", + "reqwest 0.12.28", + "rust_decimal", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tokio-tungstenite 0.28.0", + "url", +] + [[package]] name = "yoke" version = "0.8.1" diff --git a/MosaicIQ/src-tauri/Cargo.toml b/MosaicIQ/src-tauri/Cargo.toml index 37b79a7..46131cd 100644 --- a/MosaicIQ/src-tauri/Cargo.toml +++ b/MosaicIQ/src-tauri/Cargo.toml @@ -26,6 +26,7 @@ rig-core = "0.34.0" tauri-plugin-store = "2" tokio = { version = "1", features = ["time"] } futures = "0.3" +yfinance-rs = { path = "vendor/yfinance-rs-0.7.2" } [dev-dependencies] tauri = { version = "2", features = ["test"] } diff --git a/MosaicIQ/src-tauri/src/commands/terminal.rs b/MosaicIQ/src-tauri/src/commands/terminal.rs index c73c7f4..540c6bf 100644 --- a/MosaicIQ/src-tauri/src/commands/terminal.rs +++ b/MosaicIQ/src-tauri/src/commands/terminal.rs @@ -16,7 +16,7 @@ pub async fn execute_terminal_command( state: tauri::State<'_, AppState>, request: ExecuteTerminalCommandRequest, ) -> Result { - Ok(state.command_service.execute(request)) + Ok(state.command_service.execute(request).await) } /// Starts a streaming plain-text chat turn and emits progress over Tauri events. diff --git a/MosaicIQ/src-tauri/src/state.rs b/MosaicIQ/src-tauri/src/state.rs index db072dc..917932a 100644 --- a/MosaicIQ/src-tauri/src/state.rs +++ b/MosaicIQ/src-tauri/src/state.rs @@ -1,7 +1,7 @@ //! Shared application state managed by Tauri. use std::sync::atomic::{AtomicU64, Ordering}; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; use tauri::{AppHandle, Wry}; @@ -23,7 +23,9 @@ impl AppState { pub fn new(app_handle: &AppHandle) -> Result { Ok(Self { agent: Mutex::new(AgentService::new(app_handle)?), - command_service: TerminalCommandService::default(), + command_service: TerminalCommandService::new(Arc::new( + crate::terminal::yahoo_finance::YahooFinanceLookup::default(), + )), next_request_id: AtomicU64::new(1), }) } diff --git a/MosaicIQ/src-tauri/src/terminal/command_service.rs b/MosaicIQ/src-tauri/src/terminal/command_service.rs index 0735464..7f6a89e 100644 --- a/MosaicIQ/src-tauri/src/terminal/command_service.rs +++ b/MosaicIQ/src-tauri/src/terminal/command_service.rs @@ -1,29 +1,48 @@ +use std::sync::Arc; + use crate::terminal::mock_data::load_mock_financial_data; +use crate::terminal::yahoo_finance::{ + SecurityLookup, SecurityLookupError, SecurityMatch, YahooFinanceLookup, +}; use crate::terminal::{ - ChatCommandRequest, Company, ExecuteTerminalCommandRequest, MockFinancialData, PanelPayload, + ChatCommandRequest, ExecuteTerminalCommandRequest, MockFinancialData, PanelPayload, TerminalCommandResponse, }; -/// Executes supported slash commands against the shared mock financial dataset. +/// Executes supported slash commands against live search plus shared local fixture data. pub struct TerminalCommandService { mock_data: MockFinancialData, + security_lookup: Arc, } impl Default for TerminalCommandService { fn default() -> Self { - Self { - mock_data: load_mock_financial_data(), - } + Self::with_dependencies(load_mock_financial_data(), Arc::new(YahooFinanceLookup::default())) } } impl TerminalCommandService { + /// Creates a terminal command service with a custom security lookup backend. + pub fn new(security_lookup: Arc) -> Self { + Self::with_dependencies(load_mock_financial_data(), security_lookup) + } + + fn with_dependencies( + mock_data: MockFinancialData, + security_lookup: Arc, + ) -> Self { + Self { + mock_data, + security_lookup, + } + } + /// Resolves a slash command into either a text reply or a structured panel payload. - pub fn execute(&self, request: ExecuteTerminalCommandRequest) -> TerminalCommandResponse { + pub async fn execute(&self, request: ExecuteTerminalCommandRequest) -> TerminalCommandResponse { let command = parse_command(&request.input); match command.command.as_str() { - "/search" => self.search(command.args.join(" ").trim()), + "/search" => self.search(command.args.join(" ").trim()).await, "/portfolio" => TerminalCommandResponse::Panel { panel: PanelPayload::Portfolio { data: self.mock_data.portfolio.clone(), @@ -38,34 +57,58 @@ impl TerminalCommandService { } } - fn search(&self, query: &str) -> TerminalCommandResponse { + async fn search(&self, query: &str) -> TerminalCommandResponse { if query.is_empty() { return TerminalCommandResponse::Text { content: "Usage: /search [ticker or company name]".to_string(), }; } - if let Some(company) = self.find_company_by_symbol(query) { - return TerminalCommandResponse::Panel { - panel: PanelPayload::Company { data: company }, - }; - } + let matches = match self.security_lookup.search(query).await { + Ok(matches) => matches + .into_iter() + .filter(|security_match| security_match.kind.is_supported()) + .collect::>(), + Err(SecurityLookupError::SearchUnavailable) => { + return TerminalCommandResponse::Text { + content: format!("Live search failed for \"{query}\"."), + }; + } + Err(SecurityLookupError::DetailUnavailable { .. }) => { + return TerminalCommandResponse::Text { + content: format!("Live search failed for \"{query}\"."), + }; + } + }; - let matches = self.search_companies(query); if matches.is_empty() { return TerminalCommandResponse::Text { - content: format!("No results found for \"{query}\"."), + content: format!("No live results found for \"{query}\"."), }; } - let lines = matches - .iter() - .map(|company| format!(" {} {}", company.symbol, company.name)) - .collect::>() - .join("\n"); + if let Some(selected_match) = select_exact_symbol_match(query, &matches) { + let selected_symbol = selected_match.symbol.clone(); + return match self.security_lookup.load_company(&selected_match).await { + Ok(company) => TerminalCommandResponse::Panel { + panel: PanelPayload::Company { data: company }, + }, + Err(SecurityLookupError::DetailUnavailable { symbol }) => { + TerminalCommandResponse::Text { + content: format!("Live security data unavailable for \"{symbol}\"."), + } + } + Err(SecurityLookupError::SearchUnavailable) => TerminalCommandResponse::Text { + content: format!("Live security data unavailable for \"{selected_symbol}\"."), + }, + }; + } TerminalCommandResponse::Text { - content: format!("Multiple matches found for \"{query}\":\n{lines}"), + content: format!( + "Multiple matches found for \"{query}\":\n{}", + format_search_matches(&matches) + ), } } @@ -112,28 +155,48 @@ impl TerminalCommandService { }, } } +} - fn find_company_by_symbol(&self, query: &str) -> Option { - self.mock_data - .companies - .iter() - .find(|company| company.symbol.eq_ignore_ascii_case(query)) - .cloned() +fn select_exact_symbol_match(query: &str, matches: &[SecurityMatch]) -> Option { + matches + .iter() + .enumerate() + .filter(|(_, security_match)| security_match.symbol.eq_ignore_ascii_case(query)) + .min_by_key(|(index, security_match)| { + ( + exchange_priority(security_match.exchange.as_deref()), + *index, + ) + }) + .map(|(_, security_match)| security_match.clone()) +} + +fn exchange_priority(exchange: Option<&str>) -> usize { + match exchange { + Some("NASDAQ") => 0, + Some("NYSE") => 1, + Some("AMEX") => 2, + Some("BATS") => 3, + _ => 4, } +} - fn search_companies(&self, query: &str) -> Vec { - let normalized_query = query.to_lowercase(); - - self.mock_data - .companies - .iter() - .filter(|company| { - company.symbol.to_lowercase().contains(&normalized_query) - || company.name.to_lowercase().contains(&normalized_query) - }) - .cloned() - .collect() - } +fn format_search_matches(matches: &[SecurityMatch]) -> String { + matches + .iter() + .map(|security_match| { + let name = security_match.name.as_deref().unwrap_or("Unknown"); + let exchange = security_match.exchange.as_deref().unwrap_or("N/A"); + format!( + " {} {} {} {}", + security_match.symbol, + name, + exchange, + security_match.kind.label() + ) + }) + .collect::>() + .join("\n") } /// Parses raw slash-command input into a normalized command plus positional arguments. @@ -152,7 +215,7 @@ fn parse_command(input: &str) -> ChatCommandRequest { /// Human-readable help text returned for `/help` and unknown commands. fn help_text() -> &'static str { - "Available Commands:\n\n /search [ticker] - Search for a stock\n /portfolio - Show your portfolio\n /news [ticker?] - Show market news\n /analyze [ticker] - Get stock analysis\n /help - Show this help text\n /clear - Clear terminal locally" + "Available Commands:\n\n /search [ticker] - Search live security data\n /portfolio - Show your portfolio\n /news [ticker?] - Show market news\n /analyze [ticker] - Get stock analysis\n /help - Show this help text\n /clear - Clear terminal locally" } /// Wraps the shared help text into the terminal command response envelope. @@ -164,17 +227,94 @@ fn help_response() -> TerminalCommandResponse { #[cfg(test)] mod tests { + use std::sync::Arc; + + use futures::future::BoxFuture; + use super::TerminalCommandService; - use crate::terminal::{ExecuteTerminalCommandRequest, PanelPayload, TerminalCommandResponse}; + use crate::terminal::mock_data::load_mock_financial_data; + use crate::terminal::yahoo_finance::{ + SecurityKind, SecurityLookup, SecurityLookupError, SecurityMatch, + }; + use crate::terminal::{Company, ExecuteTerminalCommandRequest, PanelPayload, TerminalCommandResponse}; + + struct FakeSecurityLookup { + search_result: Result, SecurityLookupError>, + fail_detail: bool, + } + + impl FakeSecurityLookup { + fn successful(matches: Vec) -> Self { + Self { + search_result: Ok(matches), + fail_detail: false, + } + } + } + + impl SecurityLookup for FakeSecurityLookup { + fn search<'a>( + &'a self, + _query: &'a str, + ) -> BoxFuture<'a, Result, SecurityLookupError>> { + Box::pin(async move { self.search_result.clone() }) + } + + fn load_company<'a>( + &'a self, + security_match: &'a SecurityMatch, + ) -> BoxFuture<'a, Result> { + Box::pin(async move { + if self.fail_detail { + return Err(SecurityLookupError::DetailUnavailable { + symbol: security_match.symbol.clone(), + }); + } + + Ok(Company { + symbol: security_match.symbol.clone(), + name: security_match.exchange.clone().unwrap_or_else(|| "N/A".to_string()), + price: 100.0, + change: 1.0, + change_percent: 1.0, + market_cap: 1_000_000.0, + volume: 10_000, + pe: Some(20.0), + eps: Some(2.0), + high52_week: Some(110.0), + low52_week: Some(80.0), + }) + }) + } + } + + fn build_service(search_result: Result, SecurityLookupError>) -> TerminalCommandService { + TerminalCommandService::with_dependencies( + load_mock_financial_data(), + Arc::new(FakeSecurityLookup { + search_result, + fail_detail: false, + }), + ) + } + + fn execute(service: &TerminalCommandService, input: &str) -> TerminalCommandResponse { + futures::executor::block_on(service.execute(ExecuteTerminalCommandRequest { + workspace_id: "workspace-1".to_string(), + input: input.to_string(), + })) + } #[test] fn returns_company_panel_for_exact_search_match() { - let service = TerminalCommandService::default(); + let service = build_service(Ok(vec![SecurityMatch { + symbol: "AAPL".to_string(), + name: Some("Apple Inc.".to_string()), + exchange: Some("NASDAQ".to_string()), + kind: SecurityKind::Equity, + }])); - let response = service.execute(ExecuteTerminalCommandRequest { - workspace_id: "workspace-1".to_string(), - input: "/search AAPL".to_string(), - }); + let response = execute(&service, "/search AAPL"); match response { TerminalCommandResponse::Panel { @@ -185,13 +325,145 @@ mod tests { } #[test] - fn returns_text_for_unknown_command() { - let service = TerminalCommandService::default(); + fn returns_text_list_for_name_search() { + let service = build_service(Ok(vec![ + SecurityMatch { + symbol: "AAPL".to_string(), + name: Some("Apple Inc.".to_string()), + exchange: Some("NASDAQ".to_string()), + kind: SecurityKind::Equity, + }, + SecurityMatch { + symbol: "APLE".to_string(), + name: Some("Apple Hospitality REIT".to_string()), + exchange: Some("NYSE".to_string()), + kind: SecurityKind::Equity, + }, + ])); - let response = service.execute(ExecuteTerminalCommandRequest { - workspace_id: "workspace-1".to_string(), - input: "/wat".to_string(), - }); + let response = execute(&service, "/search apple"); + + match response { + TerminalCommandResponse::Text { content } => { + assert!(content.contains("Multiple matches found for \"apple\"")); + assert!(content.contains("AAPL")); + assert!(content.contains("NASDAQ")); + assert!(content.contains("Equity")); + } + other => panic!("expected text response, got {other:?}"), + } + } + + #[test] + fn filters_unsupported_assets() { + let service = build_service(Ok(vec![SecurityMatch { + symbol: "BTC-USD".to_string(), + name: Some("Bitcoin USD".to_string()), + exchange: None, + kind: SecurityKind::Other("Crypto".to_string()), + }])); + + let response = execute(&service, "/search bitcoin"); + + match response { + TerminalCommandResponse::Text { content } => { + assert_eq!(content, "No live results found for \"bitcoin\"."); + } + other => panic!("expected text response, got {other:?}"), + } + } + + #[test] + fn prefers_us_exchange_priority_for_exact_matches() { + let service = build_service(Ok(vec![ + SecurityMatch { + symbol: "ABC".to_string(), + name: Some("ABC Ltd".to_string()), + exchange: Some("NYSE".to_string()), + kind: SecurityKind::Equity, + }, + SecurityMatch { + symbol: "ABC".to_string(), + name: Some("ABC Ltd".to_string()), + exchange: Some("NASDAQ".to_string()), + kind: SecurityKind::Equity, + }, + ])); + + let response = execute(&service, "/search ABC"); + + match response { + TerminalCommandResponse::Panel { + panel: PanelPayload::Company { data }, + } => assert_eq!(data.name, "NASDAQ"), + other => panic!("expected company panel, got {other:?}"), + } + } + + #[test] + fn returns_live_search_error_when_provider_search_fails() { + let service = build_service(Err(SecurityLookupError::SearchUnavailable)); + + let response = execute(&service, "/search AAPL"); + + match response { + TerminalCommandResponse::Text { content } => { + assert_eq!(content, "Live search failed for \"AAPL\"."); + } + other => panic!("expected text response, got {other:?}"), + } + } + + #[test] + fn returns_live_detail_error_when_exact_resolution_fails() { + let service = TerminalCommandService::with_dependencies( + load_mock_financial_data(), + Arc::new(FakeSecurityLookup { + search_result: Ok(vec![SecurityMatch { + symbol: "AAPL".to_string(), + name: Some("Apple Inc.".to_string()), + exchange: Some("NASDAQ".to_string()), + kind: SecurityKind::Equity, + }]), + fail_detail: true, + }), + ); + + let response = execute(&service, "/search AAPL"); + + match response { + TerminalCommandResponse::Text { content } => { + assert_eq!(content, "Live security data unavailable for \"AAPL\"."); + } + other => panic!("expected text response, got {other:?}"), + } + } + + #[test] + fn returns_usage_for_search_without_query() { + let service = TerminalCommandService::with_dependencies( + load_mock_financial_data(), + Arc::new(FakeSecurityLookup::successful(vec![])), + ); + + let response = execute(&service, "/search"); + + match response { + TerminalCommandResponse::Text { content } => { + assert_eq!(content, "Usage: /search [ticker or company name]"); + } + other => panic!("expected text response, got {other:?}"), + } + } + + #[test] + fn returns_text_for_unknown_command() { + let service = TerminalCommandService::with_dependencies( + load_mock_financial_data(), + Arc::new(FakeSecurityLookup::successful(vec![])), + ); + + let response = execute(&service, "/wat"); match response { TerminalCommandResponse::Text { content } => { @@ -203,12 +475,12 @@ mod tests { #[test] fn returns_usage_for_analyze_without_ticker() { - let service = TerminalCommandService::default(); + let service = TerminalCommandService::with_dependencies( + load_mock_financial_data(), + Arc::new(FakeSecurityLookup::successful(vec![])), + ); - let response = service.execute(ExecuteTerminalCommandRequest { - workspace_id: "workspace-1".to_string(), - input: "/analyze".to_string(), - }); + let response = execute(&service, "/analyze"); match response { TerminalCommandResponse::Text { content } => { diff --git a/MosaicIQ/src-tauri/src/terminal/mod.rs b/MosaicIQ/src-tauri/src/terminal/mod.rs index 51b273b..a9704f5 100644 --- a/MosaicIQ/src-tauri/src/terminal/mod.rs +++ b/MosaicIQ/src-tauri/src/terminal/mod.rs @@ -1,6 +1,7 @@ mod command_service; mod mock_data; mod types; +pub(crate) mod yahoo_finance; pub use command_service::TerminalCommandService; pub use types::{ diff --git a/MosaicIQ/src-tauri/src/terminal/types.rs b/MosaicIQ/src-tauri/src/terminal/types.rs index 7223351..8713f2f 100644 --- a/MosaicIQ/src-tauri/src/terminal/types.rs +++ b/MosaicIQ/src-tauri/src/terminal/types.rs @@ -126,6 +126,7 @@ pub struct StockAnalysis { #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MockFinancialData { + #[allow(dead_code)] pub companies: Vec, pub portfolio: Portfolio, pub news_items: Vec, diff --git a/MosaicIQ/src-tauri/src/terminal/yahoo_finance.rs b/MosaicIQ/src-tauri/src/terminal/yahoo_finance.rs new file mode 100644 index 0000000..ff05e44 --- /dev/null +++ b/MosaicIQ/src-tauri/src/terminal/yahoo_finance.rs @@ -0,0 +1,160 @@ +use futures::future::BoxFuture; +use yfinance_rs::core::conversions::money_to_f64; +use yfinance_rs::{CacheMode, SearchBuilder, Ticker, YfClient}; + +use crate::terminal::Company; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum SecurityKind { + Equity, + Fund, + Other(String), +} + +impl SecurityKind { + #[must_use] + pub(crate) const fn is_supported(&self) -> bool { + matches!(self, Self::Equity | Self::Fund) + } + + #[must_use] + pub(crate) fn label(&self) -> &str { + match self { + Self::Equity => "Equity", + Self::Fund => "Fund", + Self::Other(label) => label.as_str(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct SecurityMatch { + pub symbol: String, + pub name: Option, + pub exchange: Option, + pub kind: SecurityKind, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum SecurityLookupError { + SearchUnavailable, + DetailUnavailable { symbol: String }, +} + +pub(crate) trait SecurityLookup: Send + Sync { + fn search<'a>( + &'a self, + query: &'a str, + ) -> BoxFuture<'a, Result, SecurityLookupError>>; + + fn load_company<'a>( + &'a self, + security_match: &'a SecurityMatch, + ) -> BoxFuture<'a, Result>; +} + +pub(crate) struct YahooFinanceLookup { + client: YfClient, +} + +impl Default for YahooFinanceLookup { + fn default() -> Self { + Self { + client: YfClient::default(), + } + } +} + +impl SecurityLookup for YahooFinanceLookup { + fn search<'a>( + &'a self, + query: &'a str, + ) -> BoxFuture<'a, Result, SecurityLookupError>> { + Box::pin(async move { + let response = SearchBuilder::new(&self.client, query) + .quotes_count(10) + .news_count(0) + .lists_count(0) + .lang("en-US") + .region("US") + .cache_mode(CacheMode::Bypass) + .fetch() + .await + .map_err(|_| SecurityLookupError::SearchUnavailable)?; + + Ok(response + .results + .into_iter() + .map(|result| { + let kind = result.kind.to_string(); + + SecurityMatch { + symbol: result.symbol.to_string(), + name: result.name, + exchange: result.exchange.map(|exchange| exchange.to_string()), + kind: match kind.as_str() { + "EQUITY" => SecurityKind::Equity, + "FUND" => SecurityKind::Fund, + _ => SecurityKind::Other(kind), + }, + } + }) + .collect()) + }) + } + + fn load_company<'a>( + &'a self, + security_match: &'a SecurityMatch, + ) -> BoxFuture<'a, Result> { + Box::pin(async move { + let ticker = Ticker::new(&self.client, security_match.symbol.clone()); + let detail_error = || SecurityLookupError::DetailUnavailable { + symbol: security_match.symbol.clone(), + }; + + let (fast_info, info) = futures::try_join!(ticker.fast_info(), ticker.info()) + .map_err(|_| detail_error())?; + + let name = info + .name + .clone() + .or_else(|| security_match.name.clone()) + .or_else(|| fast_info.name.clone()) + .filter(|value| !value.trim().is_empty()) + .ok_or_else(detail_error)?; + + let price = fast_info + .last + .as_ref() + .map(money_to_f64) + .ok_or_else(detail_error)?; + + let previous_close = fast_info + .previous_close + .as_ref() + .map(money_to_f64) + .unwrap_or(0.0); + let change = price - previous_close; + let change_percent = if previous_close > 0.0 { + (change / previous_close) * 100.0 + } else { + 0.0 + }; + + Ok(Company { + symbol: security_match.symbol.clone(), + name, + price, + change, + change_percent, + market_cap: info.market_cap.as_ref().map(money_to_f64).unwrap_or(0.0), + volume: info.volume.or(fast_info.volume).unwrap_or(0), + pe: info.pe_ttm, + eps: info.eps_ttm.as_ref().map(money_to_f64), + high52_week: info.fifty_two_week_high.as_ref().map(money_to_f64), + low52_week: info.fifty_two_week_low.as_ref().map(money_to_f64), + }) + }) + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/.cargo-ok b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/.cargo-ok new file mode 100644 index 0000000..5f8b795 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/.cargo_vcs_info.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/.cargo_vcs_info.json new file mode 100644 index 0000000..eb4285e --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "629adbef87cffa40928b356934dba62b22246502" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/.github/FUNDING.yml b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/.github/FUNDING.yml new file mode 100644 index 0000000..f936650 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [gramistella] \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/.github/workflows/ci.yml b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/.github/workflows/ci.yml new file mode 100644 index 0000000..7cdcc33 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/.github/workflows/ci.yml @@ -0,0 +1,82 @@ +name: CI + +on: + push: + branches: [ "main" ] + paths-ignore: + - '**.md' + - 'LICENSE' + pull_request: + branches: [ "main" ] + paths-ignore: + - '**.md' + - 'LICENSE' + +env: + CARGO_TERM_COLOR: always + +jobs: + checks: + name: Check Formatting & Lints + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + components: clippy, rustfmt + + - name: Install protoc + uses: arduino/setup-protoc@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Cache Cargo + uses: Swatinem/rust-cache@v2 + + - name: Install just + uses: taiki-e/install-action@just + + - name: Check formatting + run: cargo fmt --all -- --check + + - name: Run clippy linter + run: just lint + + test: + name: Run Tests + runs-on: ubuntu-latest + needs: checks + steps: + - name: Clean up runner image + run: | + df -h + rm -rf /opt/hostedtoolcache + sudo rm -rf /usr/local/lib/android + sudo rm -rf /usr/share/dotnet + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + df -h + + - name: Checkout code + uses: actions/checkout@v5 + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + components: clippy, rustfmt + + - name: Install protoc + uses: arduino/setup-protoc@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Cache Cargo + uses: Swatinem/rust-cache@v2 + + - name: Install just + uses: taiki-e/install-action@just + + - name: Run offline tests + run: just test-offline \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/.github/workflows/publish.yml b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/.github/workflows/publish.yml new file mode 100644 index 0000000..f2e568e --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/.github/workflows/publish.yml @@ -0,0 +1,85 @@ +# .github/workflows/publish.yml +name: Publish to crates.io + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+*' # Trigger on tags like v0.1.0, v1.2.3, v1.0.0-beta.1 + +jobs: + + test: + runs-on: ubuntu-latest + steps: + - name: Clean up runner image + run: | + df -h + rm -rf /opt/hostedtoolcache + sudo rm -rf /usr/local/lib/android + sudo rm -rf /usr/share/dotnet + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + df -h + + - name: Checkout code + uses: actions/checkout@v5 + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + components: clippy, rustfmt + - name: Install protoc + uses: arduino/setup-protoc@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Cache Cargo + uses: Swatinem/rust-cache@v2 + + - name: Install just + uses: taiki-e/install-action@just + + - name: Run offline tests + run: just test-offline + + - name: Run clippy linter + run: just lint + + publish: + name: Publish + needs: test + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + components: clippy, rustfmt + + - name: Install protoc + uses: arduino/setup-protoc@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish to crates.io + run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} + + create-release: + needs: test + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: read + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Create GitHub Releases based on changelog + uses: taiki-e/create-gh-release-action@v1.9.1 + with: + changelog: CHANGELOG.md + token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/.gitignore b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/.gitignore new file mode 100644 index 0000000..7ebb4a5 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/.gitignore @@ -0,0 +1,5 @@ +/target +.idea +.stitchworkspace/ +Cargo.lock +.DS_Store \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/CHANGELOG.md b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/CHANGELOG.md new file mode 100644 index 0000000..8460e93 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/CHANGELOG.md @@ -0,0 +1,336 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.7.2] - 2025-10-31 + +### Dependencies + +- Bump `paft` to `v0.7.1`. + +### Note + +Yahoo Finance appears to have removed or relocated the ESG data endpoint. As a result, `ticker.sustainability()` currently panics during normal usage and live testing. This issue is under investigation. + +## [0.7.1] - 2025-10-30 + +### Fixed + +- Format fundamentals timeseries statement row period from epoch to YYYY-MM-DD. +- Correct `calendarEvents` mapping and extraction for `exDividendDate` and `dividendDate`. +- Correct gross profit and operating income in income statement. + +## [0.7.0] - 2025-10-28 + +### Added + +- Per-update volume deltas in real-time streaming: `QuoteUpdate.volume` now reflects the delta + since the previous update for a symbol. First tick per symbol and after a detected reset/rollover + yields `None`. Applies to both WebSocket and HTTP polling streams. +- Expose intraday cumulative volume on snapshots: populate `Quote.day_volume` from v7 quotes and + surface it on convenience types (`Ticker::quote()` and `Ticker::info()` as `Info.volume`). +- SearchBuilder accessors: `lang_ref()` and `region_ref()` to inspect configured parameters. +- Populate convenience `Info` with analytics and ESG when available: `price_target`, + `recommendation_summary`, `esg_scores`. + +### Breaking Change + +- Upgrade to `paft` v0.7.0 adds a new field to `paft::market::quote::QuoteUpdate`: + `volume: Option`. If you construct or exhaustively destructure `QuoteUpdate`, update your + code to include the new field or use `..`. Stream APIs and typical consumers that only read + updates are unaffected. + +### Changed + +- Stream volume semantics: WebSocket and polling streams compute per-update volume deltas. The + low-level decoder helper remains stateless and always returns `volume = None`. +- Polling stream `diff_only` now emits when either price or volume changes. + +### Documentation + +- README: added a "Volume semantics" section for streaming; clarified delta behavior and how to + obtain cumulative volume. +- Examples: updated streaming and convenience examples to display volume; SearchBuilder example now + demonstrates `lang_ref()`/`region_ref()`. + +### Dependencies + +- Bump `paft` to `v0.7.0`. + +## [0.6.1] - 2025-10-27 + +### Fixed + +- Fixed critical timestamp interpretation bug in WebSocket stream processing: use `DateTime::from_timestamp_millis()` instead of `i64_to_datetime()` to correctly interpret millisecond timestamps, preventing incorrect date values in quote updates + +#### Notes + +- **WebSocket Stream Timestamps:** Users may occasionally observe `QuoteUpdate` messages arriving via the WebSocket stream with timestamps that are older than previously received messages ("time traveling ticks"), sometimes by significant amounts (minutes or hours). This behavior appears to originate from the **Yahoo Finance data feed itself** and is not a bug introduced by `yfinance-rs`. To provide the most direct representation of the source data, `yfinance-rs` **does not automatically filter** these out-of-order messages. Applications requiring strictly chronological quote updates should implement their own filtering logic based on the timestamp (`ts`) field of the received `QuoteUpdate`. + +## [0.6.0] - 2025-10-21 + +### Breaking Change + +- `DownloadBuilder::run()` now returns `paft::market::responses::download::DownloadResponse` with an `entries: Vec` instead of the previous `DownloadResult` maps. Access candles via `entry.history.candles` and the symbol via `entry.instrument.symbol_str()`. + +### Changed + +- Re-export `DownloadEntry` and `DownloadResponse` at the crate root for convenient imports. +- Examples and tests updated to iterate over `entries` rather than `series`. + +### Performance + +- Introduced an instrument cache in `YfClient` and populate it opportunistically from v7 quote responses to reduce symbol resolution overhead during multi-symbol downloads. + +### Documentation + +- Updated README examples to reflect the new `DownloadResponse.entries` usage. + +### Dependencies + +- Bump `paft` to `v0.6.0`. + +## [0.5.2] - 2025-10-20 + +### Added + +- Optional `tracing` feature: emits spans and key events across network I/O and major logical boundaries. Instrumented `send_with_retry`, profile fallback, quote summary fetch (including invalid crumb retry), history `fetch_full`, and `Ticker` public APIs (`info`, `quote`, `history`, etc.). Disabled by default; zero overhead when not enabled. +- Optional `tracing-subscriber` feature (dev/testing): convenience initializer `init_tracing_for_tests()` to set up a basic subscriber in examples/tests. The library itself does not configure a subscriber. + +### Dependencies + +- Bump `paft` to `v0.5.2`. + +### Docs + +- Readme now includes a "Tracing" section. + +## [0.5.1] - 2025-10-17 + +### Changed + +- Updated to paft v0.5.1 + +## [0.5.0] - 2025-10-16 + +### Breaking + +- Adopted `paft` 0.5.0 identity and money types across search, streaming, and ticker info. `Quote.symbol`, `SearchResult.symbol`, `OptionContract.contract_symbol`, and `QuoteUpdate.symbol` now use `paft::domain::Symbol`; values are uppercased and validated during construction, and invalid search results are dropped. +- `Ticker::Info` now re-exports `paft::aggregates::Info`. The previous struct with raw strings and floats has been removed, and fields such as `sector`, `industry`, analyst targets, recommendation metrics, and ESG scores are no longer populated on this convenience type. Monetary and exchange data now use `Money`, `Currency`, `Exchange`, and `MarketState`. +- Real-time streaming emits `paft::market::quote::QuoteUpdate`. `last_price` is renamed to `price` and now carries `Money` (with embedded currency metadata), the standalone `currency` string is gone, and `ts` is now a `DateTime`. Update stream consumers accordingly. +- Search now returns `paft::market::responses::search::SearchResponse` with a `results` list. Each item exposes `Symbol`, `AssetKind`, and `Exchange` enums. Replace usages of `resp.quotes` and `quote.longname/shortname` with `resp.results` and `result.name`. + +### Changed + +- Bumped `paft` to 0.5.0 via the workspace checkout and aligned with the new symbol validation. +- Updated dependencies and fixtures: `reqwest 0.12.24`, `tokio 1.48`. + +### Documentation + +- Added troubleshooting guidance for consent-related errors in `README.md` (thanks to [@hrishim](https://github.com/hrishim) for the contribution!) +- Expanded `CONTRIBUTING.md` with `just` helpers and clarified repository setup. + +### Internal + +- Added `.github/FUNDING.yml` to advertise GitHub Sponsors support. +- Removed stray `.DS_Store` files and regenerated fixtures for the new models. + +### Migration notes + +- Symbols are now uppercase-validated `paft::domain::Symbol`. Use `.as_str()` for string comparisons or construct values with `Symbol::new("AAPL")` (handle the `Result` when user input is dynamic). +- Stream updates now expose `update.price` (`Money`) and `update.ts: DateTime`. Replace direct `last_price`/`ts` usage with the new typed fields and derive primitive values as needed. +- Search responses provide `resp.results` instead of `resp.quotes`. Access display data via `result.name`, `result.kind`, and `result.exchange`. +- The convenience info snapshot no longer embeds fundamentals, analyst, or ESG data. Fetch those via `profile::load_profile`, `analysis::AnalysisBuilder`, and `esg::EsgBuilder` if you still need them. + +--- + +## [0.4.0] - 2025-10-12 + +### Added + +- Enabled `paft` facade `aggregates` feature. + - `Ticker::fast_info()` now returns `paft_aggregates::FastInfo` (typed enums and `Money`), offering a richer, consistent snapshot model. +- Options models expanded (re-exported from `paft-market`): + - `OptionContract` gains `expiration_date` (NaiveDate), `expiration_at` (Option>), `last_trade_at` (Option>), and `greeks` (Option\). +- DataFrame support for options types is available when enabling this crate’s `dataframe` feature (forwards to `paft/dataframe`). + +### Changed + +- History response alignment with `paft` 0.4.0: + - `Candle` now carries `close_unadj: Option` (original unadjusted close, when available). + - `HistoryResponse` no longer includes a top-level `unadjusted_close` vector. +- Examples and tests updated to use Money-typed values and typed enums (Exchange, MarketState, Currency). + +### Breaking + +- Fast Info return type changed: + - Old: struct with `last_price: f64`, `previous_close: Option`, string-y `currency`/`exchange`/`market_state`. + - New: `paft_aggregates::FastInfo` with `last: Option`, `previous_close: Option`, `currency: Option`, `exchange: Option`, `market_state: Option`, plus `name: Option`. +- Options contract fields changed: + - Old: `OptionContract { ..., expiration: DateTime, ... }` + - New: `OptionContract { ..., expiration_date: NaiveDate, expiration_at: Option>, last_trade_at: Option>, greeks: Option, ... }` +- History unadjusted close location changed: + - Old: `HistoryResponse { ..., unadjusted_close: Option> }` + - New: `Candle { ..., close_unadj: Option }` (per-candle). + +### Migration notes + +- Fast Info + - Price as f64: replace `fi.last_price` with `fi.last.as_ref().map(money_to_f64).or_else(|| fi.previous_close.as_ref().map(money_to_f64))`. + - Currency string: replace `fi.currency` (String) with `fi.currency.map(|c| c.to_string())`. + - Exchange/MarketState strings: `.map(|e| e.to_string())`. +- Options + - Replace usages of `contract.expiration` with `contract.expiration_at.unwrap_or_else(|| ...)`, or use `contract.expiration_date` for calendar-only logic. + - New optional fields `last_trade_at` and `greeks` are available (greeks currently not populated from Yahoo v7). +- History + - Replace `resp.unadjusted_close[i]` with `resp.candles[i].close_unadj.as_ref()`. + +### Internal + +- Tests updated for `httpmock` 0.8 API changes. +- Lints and examples adjusted for Money/typed enums. + +## [0.3.2] - 2025-10-03 + +### Changed + +- Bump `paft` to 0.3.2 (docs-only upstream release; no functional impact). + +## [0.3.1] - 2025-10-02 + +### Changed + +- Internal migration to `paft` 0.3.0 without changing the public API surface. + - Switched internal imports to `paft::domain` (domain types) and `paft::money` (money/currency). + - Updated internal `Money` construction to the new `Result`-returning API and replaced scalar ops with `try_mul` where appropriate. +- Examples and docs now import DataFrame traits from `paft::prelude::{ToDataFrame, ToDataFrameVec}`. +- Conversion helpers in `core::conversions` now document potential panics if a non-ISO currency lacks registered metadata (behavior aligned with `paft-money`). +- Profile ISIN fields now validate ISIN format using `paft::domain::Isin` - invalid ISINs are filtered out and stored as `None`. +- Updated tokio-tungstenite to version 0.28 + +## [0.3.0] - 2025-09-20 + +### Changed + +- Migrated to `paft` 0.2.0 with explicit module paths; removed all `paft::prelude` imports across the codebase, tests, and examples. +- Updated enum/string conversions to use `FromStr/TryFrom` parsing from `paft` 0.2.0 (e.g., `MarketState`, `Exchange`, `Period`, insider/transaction/recommendation types). +- Adjusted `Money` operations to use `try_*` methods and made conversions more robust against non-finite values. +- Consolidated public re-exports under `core::models` (e.g., `Interval`, `Range`, `Quote`, `Action`, `Candle`, `HistoryMeta`, `HistoryResponse`) to provide stable, explicit paths. +- Simplified the Polars example behind the `dataframe` feature to avoid prelude usage and to compile cleanly with the new APIs. + +### Fixed + +- Updated examples and tests to import `Interval`/`Range` from `yfinance_rs::core` explicitly and to avoid wildcard matches in pattern tests. + +### Notes + +- This release removes reliance on `paft` preludes and may require users to update imports to explicit module paths if depending on re-exported paft items directly. + +## [0.2.1] - 2025-09-18 + +### Added + +- Profile-based reporting currency inference with per-symbol caching. The client now inspects the profile country on first use to determine an appropriate currency and reuses that decision across fundamentals and analysis calls. +- ESG involvement exposure: `Ticker::sustainability()` now returns involvement flags (e.g., tobacco, thermal_coal) alongside component scores via `EsgSummary`. + +### Changed + +- **Breaking change:** `Ticker` convenience methods for fundamentals and analysis (and their corresponding builders) now accept an extra `Option` argument. Pass `None` to use the inferred reporting currency, or `Some(currency)` to override the heuristic explicitly. +- **Breaking change:** `Ticker::sustainability()` and `esg::EsgBuilder::fetch()` now return `EsgSummary` instead of `EsgScores`. Access component values via `summary.scores` and involvement via `summary.involvement`. + +## [0.2.0] - 2025-09-16 + +### Added + +- New optional `dataframe` feature: all `paft` data models now support `.to_dataframe()` when the feature is enabled, returning Polars `DataFrame`s. Added example `14_polars_dataframes.rs` and README section. +- Custom HTTP client support via `YfClient::builder().custom_client(...)` for full control over `reqwest` configuration. +- Proxy configuration helpers on the client builder: `.proxy()`, `.https_proxy()`, `.try_proxy()`, `.try_https_proxy()`. Added example `13_custom_client_and_proxy.rs`. +- Explicit `User-Agent` is set on all HTTP/WebSocket requests by default, with `.user_agent(...)` to customize it. +- Improved numeric precision in historical adjustments and conversions using `rust_decimal`. + +### Changed + +- **Breaking change:** All public data models (such as `Quote`, `HistoryBar`, `EarningsTrendRow`, etc.) now use types from the [`paft`](https://crates.io/crates/paft) crate instead of custom-defined structs. This unifies data structures with other financial Rust libraries and improves interoperability, but may require code changes for downstream users. +- Monetary value handling now uses `paft::Money` with currency awareness across APIs and helpers. +- Consolidated and simplified fundamentals timeseries fetching via a generic helper for consistency. +- Error handling refined: `YfError` variants and messages standardized for 404/429/5xx and unexpected statuses. +- Dependencies updated and internal structure adjusted to support the new features. + +### Fixed + +- Minor clippy findings and documentation typos. + +### Known Issues + +- Currency inference relies on company profile metadata. If Yahoo omits or mislabels the headquarters country, the inferred currency can still be incorrect—use the new override parameter to force a specific currency in that case. + +## [0.1.3] - 2025-08-31 + +### Added + +- Re-exported `CacheMode` and `RetryConfig` from the `core` module. + +### Changed + +- `Ticker::new` now takes `&YfClient` instead of taking ownership. +- `SearchBuilder` now takes `&YfClient` instead of taking ownership. + +## [0.1.2] - 2025-08-30 + +### Added + +- New examples: `10_convenience_methods.rs`, `11_builder_configuration.rs`, `12_advanced_client.rs`. +- Development tooling: `just` recipes `lint`, `lint-fix`, and `lint-strict`. +- Re-exported `YfClientBuilder` at the crate root (`use yfinance_rs::YfClientBuilder`). + +### Changed + +- Centralized raw wire types (e.g., `RawNum`) into `src/core/wire.rs`. +- Gated debug file dumps behind the `debug-dumps` feature flag. + +### Fixed + +- Analyst recommendations now read from `financialData` instead of the incorrect `recommendationMean` field. +- Fixed unnecessary mutable borrow in `StreamBuilder` `run_websocket_stream` + +## [0.1.1] - 2025-08-28 + +### Added + +- `ticker.earnings_trend()` for analyst earnings and revenue estimates. +- `ticker.shares()` and `ticker.quarterly_shares()` for historical shares outstanding. +- `ticker.capital_gains()` and inclusion of capital gains in `ticker.actions()`. +- Documentation: added doc comments for `EarningsTrendRow`, `ShareCount`, and `Action::CapitalGain`. + +## [0.1.0] - 2025-08-27 + +### Added + +- Initial release of `yfinance-rs`. +- Core functionality: `info`, `history`, `quote`, `fast_info`. +- Advanced data: `options`, `option_chain`, `news`, `income_stmt`, `balance_sheet`, `cashflow`. +- Analysis tools: `recommendations`, `sustainability`, `major_holders`, `institutional_holders`. +- Utilities: `DownloadBuilder`, `StreamBuilder`, `SearchBuilder`. + +[0.7.2]: https://github.com/gramistella/yfinance-rs/compare/v0.7.1...v0.7.2 +[0.7.1]: https://github.com/gramistella/yfinance-rs/compare/v0.7.0...v0.7.1 +[0.7.0]: https://github.com/gramistella/yfinance-rs/compare/v0.6.1...v0.7.0 +[0.6.1]: https://github.com/gramistella/yfinance-rs/compare/v0.6.0...v0.6.1 +[0.6.0]: https://github.com/gramistella/yfinance-rs/compare/v0.5.2...v0.6.0 +[0.5.2]: https://github.com/gramistella/yfinance-rs/compare/v0.5.1...v0.5.2 +[0.5.1]: https://github.com/gramistella/yfinance-rs/compare/v0.5.0...v0.5.1 +[0.5.0]: https://github.com/gramistella/yfinance-rs/compare/v0.4.0...v0.5.0 +[0.4.0]: https://github.com/gramistella/yfinance-rs/compare/v0.3.1...v0.4.0 +[0.3.2]: https://github.com/gramistella/yfinance-rs/compare/v0.3.1...v0.3.2 +[0.3.1]: https://github.com/gramistella/yfinance-rs/compare/v0.3.0...v0.3.1 +[0.3.0]: https://github.com/gramistella/yfinance-rs/compare/v0.2.1...v0.3.0 +[0.2.1]: https://github.com/gramistella/yfinance-rs/compare/v0.2.0...v0.2.1 +[0.2.0]: https://github.com/gramistella/yfinance-rs/compare/v0.1.3...v0.2.0 +[0.1.3]: https://github.com/gramistella/yfinance-rs/compare/v0.1.2...v0.1.3 +[0.1.2]: https://github.com/gramistella/yfinance-rs/compare/v0.1.1...v0.1.2 +[0.1.1]: https://github.com/gramistella/yfinance-rs/compare/v0.1.0...v0.1.1 +[0.1.0]: https://github.com/gramistella/yfinance-rs/releases/tag/v0.1.0 diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/CODE_OF_CONDUCT.md b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..5b3c61e --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/CODE_OF_CONDUCT.md @@ -0,0 +1,129 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the maintainers responsible for enforcement at +limos.seam-64@icloud.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq + + diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/CONTRIBUTING.md b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/CONTRIBUTING.md new file mode 100644 index 0000000..69a0ffb --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/CONTRIBUTING.md @@ -0,0 +1,64 @@ +# Contributing to yfinance-rs + +Thanks for considering a contribution to yfinance-rs! This guide helps you get set up and submit effective pull requests. + +## Code of Conduct + +Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md). + +## Getting Started + +### Prerequisites + +- Rust (latest stable) +- Cargo +- Git +- Just command runner + +### Setup + +```bash +git clone https://github.com/gramistella/yfinance-rs.git +cd yfinance-rs +``` + +## Development Workflow + +### Test (full test, live recording + offline) + +```bash +just test +``` + +### Offline test + +```bash +just test-offline +``` + +### Lint & Format + +```bash +just fmt +just lint +``` + +## Commit Messages + +Use [Conventional Commits](https://www.conventionalcommits.org/) for clear history. + +## Pull Requests + +1. Create a feature branch. +2. Add tests and documentation as needed. +3. Ensure CI basics pass locally (fmt, clippy, test). +4. Open a PR with a concise description and issue links. + +## Release + +- Maintainers handle releases following [Semantic Versioning](https://semver.org/). +- Update `CHANGELOG.md` with notable changes. + +## Support + +Open an issue with details, environment info, and steps to reproduce. diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/Cargo.toml b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/Cargo.toml new file mode 100644 index 0000000..0e35e4f --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/Cargo.toml @@ -0,0 +1,280 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2024" +name = "yfinance-rs" +version = "0.7.2" +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Ergonomic Rust client for Yahoo Finance, supporting historical prices, real-time streaming, options, fundamentals, and more." +homepage = "https://github.com/gramistella/yfinance-rs" +documentation = "https://docs.rs/yfinance-rs" +readme = "README.md" +keywords = [ + "yahoo", + "finance", + "stocks", + "trading", + "yfinance", +] +categories = [ + "api-bindings", + "finance", +] +license = "MIT" +repository = "https://github.com/gramistella/yfinance-rs" + +[package.metadata.docs.rs] +all-features = true + +[package.metadata.cargo-doc] +all-features = true + +[features] +dataframe = [ + "polars", + "paft/dataframe", +] +debug-dumps = [] +default = [] +test-mode = [] +tracing = ["dep:tracing"] +tracing-subscriber = [ + "tracing", + "dep:tracing-subscriber", +] + +[lib] +name = "yfinance_rs" +path = "src/lib.rs" + +[[example]] +name = "01_basic_usage" +path = "examples/01_basic_usage.rs" + +[[example]] +name = "02_fundamentals_and_search" +path = "examples/02_fundamentals_and_search.rs" + +[[example]] +name = "03_esg_and_analysis" +path = "examples/03_esg_and_analysis.rs" + +[[example]] +name = "04_historical_actions" +path = "examples/04_historical_actions.rs" + +[[example]] +name = "05_concurrent_requests" +path = "examples/05_concurrent_requests.rs" + +[[example]] +name = "06_realtime_polling" +path = "examples/06_realtime_polling.rs" + +[[example]] +name = "07_quarterly_fundamentals" +path = "examples/07_quarterly_fundamentals.rs" + +[[example]] +name = "08_advanced_analysis" +path = "examples/08_advanced_analysis.rs" + +[[example]] +name = "09_holders_and_insiders" +path = "examples/09_holders_and_insiders.rs" + +[[example]] +name = "10_convenience_methods" +path = "examples/10_convenience_methods.rs" + +[[example]] +name = "11_builder_configuration" +path = "examples/11_builder_configuration.rs" + +[[example]] +name = "12_advanced_client" +path = "examples/12_advanced_client.rs" + +[[example]] +name = "13_custom_client_and_proxy" +path = "examples/13_custom_client_and_proxy.rs" + +[[example]] +name = "14_polars_dataframes" +path = "examples/14_polars_dataframes.rs" +required-features = ["dataframe"] + +[[test]] +name = "analysis" +path = "tests/analysis.rs" + +[[test]] +name = "auth" +path = "tests/auth.rs" + +[[test]] +name = "common" +path = "tests/common.rs" + +[[test]] +name = "currency" +path = "tests/currency.rs" + +[[test]] +name = "download" +path = "tests/download.rs" + +[[test]] +name = "esg" +path = "tests/esg.rs" + +[[test]] +name = "fundamentals" +path = "tests/fundamentals.rs" + +[[test]] +name = "history" +path = "tests/history.rs" + +[[test]] +name = "holders" +path = "tests/holders.rs" + +[[test]] +name = "news" +path = "tests/news.rs" + +[[test]] +name = "profile" +path = "tests/profile.rs" + +[[test]] +name = "quotes" +path = "tests/quotes.rs" + +[[test]] +name = "quotes_status_mapping" +path = "tests/quotes_status_mapping.rs" + +[[test]] +name = "search" +path = "tests/search.rs" + +[[test]] +name = "stream" +path = "tests/stream.rs" + +[[test]] +name = "ticker" +path = "tests/ticker.rs" + +[dependencies.base64] +version = "0.22" + +[dependencies.chrono] +version = "0.4.42" +features = ["serde"] + +[dependencies.chrono-tz] +version = "0.10" +features = ["serde"] + +[dependencies.futures] +version = "0.3" + +[dependencies.futures-util] +version = "0.3" + +[dependencies.paft] +version = "0.7.1" +features = [ + "market", + "fundamentals", + "domain", + "aggregates", +] + +[dependencies.polars] +version = "0.51" +optional = true +default-features = false + +[dependencies.prost] +version = "0.14" + +[dependencies.reqwest] +version = "0.12.24" +features = [ + "json", + "rustls-tls", + "gzip", + "brotli", + "deflate", + "cookies", +] +default-features = false + +[dependencies.rust_decimal] +version = "1.36" + +[dependencies.serde] +version = "1.0.228" +features = ["derive"] + +[dependencies.serde_json] +version = "1.0.145" + +[dependencies.thiserror] +version = "2.0" + +[dependencies.tokio] +version = "1.48" +features = [ + "macros", + "rt-multi-thread", +] + +[dependencies.tokio-tungstenite] +version = "0.28" +features = ["rustls-tls-native-roots"] + +[dependencies.tracing] +version = "0.1" +optional = true + +[dependencies.tracing-subscriber] +version = "0.3" +features = [ + "fmt", + "std", + "env-filter", +] +optional = true +default-features = false + +[dependencies.url] +version = "2.5.7" + +[dev-dependencies.httpmock] +version = "0.8.2" + +[dev-dependencies.polars] +version = "0.51" +features = [ + "lazy", + "rolling_window", +] +default-features = false diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/Cargo.toml.orig b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/Cargo.toml.orig new file mode 100644 index 0000000..79126ad --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/Cargo.toml.orig @@ -0,0 +1,64 @@ +[package] +name = "yfinance-rs" +version = "0.7.2" +edition = "2024" +description = "Ergonomic Rust client for Yahoo Finance, supporting historical prices, real-time streaming, options, fundamentals, and more." +license = "MIT" +repository = "https://github.com/gramistella/yfinance-rs" +homepage = "https://github.com/gramistella/yfinance-rs" +documentation = "https://docs.rs/yfinance-rs" +readme = "README.md" +keywords = ["yahoo", "finance", "stocks", "trading", "yfinance"] +categories = ["api-bindings", "finance"] +build = "build.rs" + +[dependencies] +chrono = { version = "0.4.42", features = ["serde"] } +chrono-tz = { version = "0.10", features = ["serde"] } +reqwest = { version = "0.12.24", default-features = false, features = ["json", "rustls-tls", "gzip", "brotli", "deflate", "cookies"] } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.145" +thiserror = "2.0" +url = "2.5.7" +tokio = { version = "1.48", features = ["macros", "rt-multi-thread"] } +futures = "0.3" +tokio-tungstenite = { version = "0.28", features = ["rustls-tls-native-roots"] } +futures-util = "0.3" +prost = "0.14" +base64 = "0.22" +polars = { version = "0.51", default-features = false, optional = true } +paft = { version = "0.7.1", features = ["market", "fundamentals", "domain", "aggregates"]} +rust_decimal = "1.36" +tracing = { version = "0.1", optional = true } + +[dependencies.tracing-subscriber] +version = "0.3" +optional = true +default-features = false +features = ["fmt", "std", "env-filter"] + +[dev-dependencies] +httpmock = "0.8.2" +polars = { version = "0.51", default-features = false, features = ["lazy", "rolling_window"] } + +[build-dependencies] +prost-build = "0.14" + +[features] +default = [] +test-mode = [] +debug-dumps = [] +dataframe = ["polars", "paft/dataframe"] +tracing = ["dep:tracing"] +# Dev-only convenience to initialize a subscriber in examples/tests +tracing-subscriber = ["tracing", "dep:tracing-subscriber"] + +[package.metadata.docs.rs] +all-features = true + +[package.metadata.cargo-doc] +all-features = true + +[[example]] +name = "14_polars_dataframes" +required-features = ["dataframe"] \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/LICENSE b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/LICENSE new file mode 100644 index 0000000..0ef8d63 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Giovanni Ramistella + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/README.md b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/README.md new file mode 100644 index 0000000..6fc1aaa --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/README.md @@ -0,0 +1,571 @@ +# yfinance-rs + +[![Crates.io](https://img.shields.io/crates/v/yfinance-rs.svg)](https://crates.io/crates/yfinance-rs) +[![Docs.rs](https://docs.rs/yfinance-rs/badge.svg)](https://docs.rs/yfinance-rs) +[![CI](https://github.com/gramistella/yfinance-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/gramistella/yfinance-rs/actions/workflows/ci.yml) +[![Downloads](https://img.shields.io/crates/d/yfinance-rs)](https://crates.io/crates/yfinance-rs) +[![License](https://img.shields.io/crates/l/yfinance-rs)](LICENSE) + +## Overview + +An ergonomic, async-first Rust client for the unofficial Yahoo Finance API. It provides a simple and efficient way to fetch financial data, with a convenient, yfinance-like API, leveraging Rust's type system and async runtime for performance and safety. + +## Features + +### Core Data + +* **Historical Data**: Fetch daily, weekly, or monthly OHLCV data with automatic split/dividend adjustments. +* **Real-time Quotes**: Get live quote updates with detailed market information. +* **Fast Quotes**: Optimized quote fetching with essential data only (`fast_info`). +* **Multi-Symbol Downloads**: Concurrently download historical data for many symbols at once. +* **Batch Quotes**: Fetch quotes for multiple symbols efficiently. + +### Corporate Actions & Dividends + +* **Dividend History**: Fetch complete dividend payment history with amounts and dates. +* **Stock Splits**: Get stock split history with split ratios. +* **Capital Gains**: Retrieve capital gains distributions (especially for mutual funds). +* **All Corporate Actions**: Comprehensive access to dividends, splits, and capital gains in one call. + +### Financial Statements & Fundamentals + +* **Income Statements**: Access annual and quarterly income statements. +* **Balance Sheets**: Get annual and quarterly balance sheet data. +* **Cash Flow Statements**: Fetch annual and quarterly cash flow data. +* **Earnings Data**: Historical earnings, revenue estimates, and EPS data. +* **Shares Outstanding**: Historical data on shares outstanding (annual and quarterly). +* **Corporate Calendar**: Earnings dates, ex-dividend dates, and dividend payment dates. + +### Options & Derivatives + +* **Options Chains**: Fetch expiration dates and full option chains (calls and puts). +* **Option Contracts**: Detailed option contract information. + +### Analysis & Research + +* **Analyst Ratings**: Get price targets, recommendations, and upgrade/downgrade history. +* **Earnings Trends**: Detailed earnings and revenue estimates from analysts. +* **Recommendations Summary**: Summary of current analyst recommendations. +* **Upgrades/Downgrades**: History of analyst rating changes. + +### Ownership & Holders + +* **Major Holders**: Get major, institutional, and mutual fund holder data. +* **Institutional Holders**: Top institutional shareholders and their holdings. +* **Mutual Fund Holders**: Mutual fund ownership breakdown. +* **Insider Transactions**: Recent insider buying and selling activity. +* **Insider Roster**: Company insiders and their current holdings. +* **Net Share Activity**: Summary of insider purchase/sale activity. + +### ESG & Sustainability + +* **ESG Scores**: Fetch detailed Environmental, Social, and Governance ratings. +* **ESG Involvement**: Specific ESG involvement and controversy data. + +### News & Information + +* **Company News**: Retrieve the latest articles and press releases for a ticker. +* **Company Profiles**: Detailed information about companies, ETFs, and funds. +* **Search**: Find tickers by name or keyword. + +### Real-time Streaming (WebSocket/Polling) + +* **WebSocket Streaming**: Get live quote updates using WebSockets (preferred method). +* **HTTP Polling**: Fallback polling method for real-time data. +* **Configurable Streaming**: Customize update frequency and change-only filtering. +* **Per-update volume deltas**: `QuoteUpdate.volume` reflects the delta since the previous update for that symbol. The first observed tick (and after a reset/rollover) has `volume = None`. + +### Advanced Features + +* **Data Repair**: Automatic detection and repair of price outliers. +* **Data Rounding**: Control price precision and rounding. +* **Missing Data Handling**: Configurable handling of NA/missing values. +* **Back Adjustment**: Alternative price adjustment methods. +* **Historical Metadata**: Timezone and other metadata for historical data. +* **ISIN Lookup**: Get International Securities Identification Numbers. +* **Polars DataFrames**: Convert results to Polars DataFrames via `.to_dataframe()` (enable the `dataframe` feature). + +### Developer Experience + +* **Async API**: Built on `tokio` and `reqwest` for non-blocking I/O. +* **High-Level `Ticker` Interface**: A convenient, yfinance-like struct for accessing all data for a single symbol. +* **Builder Pattern**: Fluent builders for constructing complex queries. +* **Configurable Retries**: Automatic retries with exponential backoff for transient network errors. +* **Caching**: Configurable caching behavior for API responses. +* **Custom Timeouts**: Configurable request timeouts and connection settings. + +## Quick Start + +To get started, add `yfinance-rs` to your `Cargo.toml`: + +```toml +[dependencies] +yfinance-rs = "0.7.2" +tokio = { version = "1", features = ["full"] } +``` + +To enable DataFrame conversions backed by Polars, turn on the optional `dataframe` feature and (if you use Polars types in your code) add `polars`: + +```toml +[dependencies] +yfinance-rs = { version = "0.7.2", features = ["dataframe"] } +polars = "0.51" +``` + +Then, create a `YfClient` and use a `Ticker` to fetch data. + +```rust +use yfinance_rs::{Interval, Range, Ticker, YfClient}; +use yfinance_rs::core::conversions::money_to_f64; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClient::default(); + let ticker = Ticker::new(&client, "AAPL"); + + // Get the latest quote + let quote = ticker.quote().await?; + println!( + "Latest price for AAPL: ${:.2}", + quote.price.as_ref().map(money_to_f64).unwrap_or(0.0) + ); + + // Get historical data for the last 6 months + let history = ticker.history(Some(Range::M6), Some(Interval::D1), false).await?; + if let Some(last_bar) = history.last() { + println!( + "Last closing price: ${:.2} on {}", + money_to_f64(&last_bar.close), + last_bar.ts + ); + } + + // Get analyst recommendations + let recs = ticker.recommendations().await?; + if let Some(latest_rec) = recs.first() { + println!("Latest recommendation period: {}", latest_rec.period); + } + + // Dividends in the last year + let dividends = ticker.dividends(Some(Range::Y1)).await?; + println!("Found {} dividend payments in the last year", dividends.len()); + + // Earnings trend + let trends = ticker.earnings_trend(None).await?; + if let Some(latest) = trends.first() { + println!( + "Latest earnings estimate: ${:.2}", + latest + .earnings_estimate + .avg + .as_ref() + .map(money_to_f64) + .unwrap_or(0.0) + ); + } + + Ok(()) +} +``` + +### Troubleshooting + +**Possible network or consent issues** + +Some users [have reported](https://github.com/gramistella/yfinance-rs/issues/1) encountering errors on first use, such as: + +- `Rate limited at ...` +- `HTTP error: error sending request for url (https://fc.yahoo.com/consent)` + +These are typically **environmental** (network or regional) issues with Yahoo’s public API. +In some regions, Yahoo may require a one-time consent or session initialization. + +**Workaround:** +Open [`https://fc.yahoo.com/consent`](https://fc.yahoo.com/consent) in a web browser **from the same network** before running your code again. +This usually resolves the issue for that IP/network. + +### Tracing (optional) + +This crate can emit structured tracing spans and key events when the optional `tracing` feature is enabled. When disabled (default), all instrumentation is compiled out with zero overhead. The library does not configure a subscriber; set one up in your application. + +Spans are added at: `Ticker` public APIs (`info`, `quote`, `history`, etc.), HTTP `send_with_retry`, profile fallback, quote summary fetch (including invalid-crumb retry), and full history fetch. Key events include retry/backoff and fallback notifications. + +## Advanced Examples + +### Polars DataFrames (to_dataframe) + +Enable the `dataframe` feature to convert paft models into a Polars `DataFrame` with `.to_dataframe()`. + +```rust +use yfinance_rs::{Interval, Range, Ticker, YfClient}; +use paft::prelude::{ToDataFrame, ToDataFrameVec}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClient::default(); + let ticker = Ticker::new(&client, "AAPL"); + + // Quote → DataFrame + let quote_df = ticker.quote().await?.to_dataframe()?; + println!("Quote as DataFrame:\n{}", quote_df); + + // History (Vec) → DataFrame + let hist_df = ticker + .history(Some(Range::M1), Some(Interval::D1), false) + .await? + .to_dataframe()?; + println!("History rows: {}", hist_df.height()); + + Ok(()) +} +``` + +Works for quotes, historical candles, fundamentals, analyst data, holders, options, and more. All `paft` structs returned by this crate implement `.to_dataframe()` when the `dataframe` feature is enabled. See the full example: `examples/14_polars_dataframes.rs`. + +### Multi-Symbol Data Download + +```rust +use yfinance_rs::{DownloadBuilder, Interval, Range, YfClient}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClient::default(); + let symbols = vec!["AAPL", "GOOGL", "MSFT", "TSLA"]; + + let results = DownloadBuilder::new(&client) + .symbols(symbols) + .range(Range::M6) + .interval(Interval::D1) + .auto_adjust(true) + .actions(true) + .repair(true) + .rounding(true) + .run() + .await?; + + for entry in &results.entries { + println!("{}: {} data points", entry.instrument.symbol(), entry.history.candles.len()); + } + Ok(()) +} +``` + +### Real-time Streaming + +```rust +use yfinance_rs::{StreamBuilder, StreamMethod, YfClient}; +use std::time::Duration; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClient::default(); + let (handle, mut receiver) = StreamBuilder::new(&client) + .symbols(vec!["AAPL", "GOOGL"]) + .method(StreamMethod::WebsocketWithFallback) + .interval(Duration::from_secs(1)) + .diff_only(true) + .start()?; + + while let Some(update) = receiver.recv().await { + let vol = update.volume.map(|v| format!(" (vol Δ: {v})")).unwrap_or_default(); + println!("{}: ${:.2}{}", + update.symbol, + update.price.as_ref().map(yfinance_rs::core::conversions::money_to_f64).unwrap_or(0.0), + vol); + } +#### Volume semantics + +Yahoo’s websocket stream provides cumulative intraday volume (`day_volume`). This crate converts it to per-update deltas on the consumer-facing `QuoteUpdate`: + +- First tick per symbol and after a detected reset (current < last): `volume = None`. +- Otherwise: `volume = Some(current_day_volume - last_day_volume)`. +- The polling stream applies the same logic using the v7 `regularMarketVolume` field. +- The low-level decoder helper `stream::decode_and_map_message` is stateless and always returns `volume = None`. + +If you need cumulative volume, sum the emitted per-update `volume` values, or use `Quote.day_volume` from the quote endpoints. + + Ok(()) +} +``` + +### Financial Statements + +```rust +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClient::default(); + let ticker = Ticker::new(&client, "AAPL"); + + let income_stmt = ticker.quarterly_income_stmt(None).await?; + let balance_sheet = ticker.quarterly_balance_sheet(None).await?; + let cashflow = ticker.quarterly_cashflow(None).await?; + + println!("Found {} quarterly income statements.", income_stmt.len()); + println!("Found {} quarterly balance sheet statements.", balance_sheet.len()); + println!("Found {} quarterly cashflow statements.", cashflow.len()); + + let shares = ticker.quarterly_shares().await?; + if let Some(latest) = shares.first() { + println!("Latest shares outstanding: {}", latest.shares); + } + Ok(()) +} +``` + +> 💡 Need to force a specific reporting currency? Pass `Some(paft::money::Currency::USD)` (or another currency) instead of `None` when calling the fundamentals/analysis helpers. + +### Options Trading + +```rust +use yfinance_rs::{Ticker, YfClient}; +use yfinance_rs::core::conversions::money_to_f64; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClient::default(); + let ticker = Ticker::new(&client, "AAPL"); + + let expirations = ticker.options().await?; + + if let Some(nearest) = expirations.first() { + let chain = ticker.option_chain(Some(*nearest)).await?; + + println!("Calls: {}", chain.calls.len()); + println!("Puts: {}", chain.puts.len()); + + let fi = ticker.fast_info().await?; + let current_price = fi + .last + .as_ref() + .map(money_to_f64) + .or_else(|| fi.previous_close.as_ref().map(money_to_f64)) + .unwrap_or(0.0); + for call in &chain.calls { + if (money_to_f64(&call.strike) - current_price).abs() < 5.0 { + println!( + "ATM Call: Strike ${:.2}, Bid ${:.2}, Ask ${:.2}", + money_to_f64(&call.strike), + call.bid.as_ref().map(money_to_f64).unwrap_or(0.0), + call.ask.as_ref().map(money_to_f64).unwrap_or(0.0) + ); + } + } + } + Ok(()) +} +``` + +### Advanced Analysis + +```rust +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClient::default(); + let ticker = Ticker::new(&client, "AAPL"); + + let price_target = ticker.analyst_price_target(None).await?; + let recs_summary = ticker.recommendations_summary().await?; + let upgrades = ticker.upgrades_downgrades().await?; + let earnings_trends = ticker.earnings_trend(None).await?; + + println!( + "Price Target: ${:.2}", + price_target.mean.as_ref().map(yfinance_rs::core::conversions::money_to_f64).unwrap_or(0.0) + ); + println!( + "Recommendation: {}", + recs_summary + .mean_rating_text + .as_deref() + .unwrap_or("N/A") + ); + println!("Trend rows: {}", earnings_trends.len()); + println!("Upgrades: {}", upgrades.len()); + + Ok(()) +} +``` + +### Holder Information + +```rust +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClient::default(); + let ticker = Ticker::new(&client, "AAPL"); + + let major_holders = ticker.major_holders().await?; + let institutional = ticker.institutional_holders().await?; + let mutual_funds = ticker.mutual_fund_holders().await?; + let insider_transactions = ticker.insider_transactions().await?; + + for holder in &major_holders { + println!("{}: {}", holder.category, holder.value); + } + println!("Institutional rows: {}", institutional.len()); + println!("Mutual fund rows: {}", mutual_funds.len()); + println!("Insider transactions: {}", insider_transactions.len()); + Ok(()) +} +``` + +### ESG Scores & Involvement + +```rust +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClient::default(); + let ticker = Ticker::new(&client, "AAPL"); + + let summary = ticker.sustainability().await?; + let parts = summary + .scores + .as_ref() + .map(|s| [s.environmental, s.social, s.governance]) + .unwrap_or([None, None, None]); + let vals = parts.into_iter().flatten().collect::>(); + let total = if vals.is_empty() { 0.0 } else { vals.iter().copied().sum::() / (vals.len() as f64) }; + println!("Total ESG Score: {:.2}", total); + if let Some(scores) = summary.scores.as_ref() { + println!("Environmental Score: {:.2}", scores.environmental.unwrap_or(0.0)); + println!("Social Score: {:.2}", scores.social.unwrap_or(0.0)); + println!("Governance Score: {:.2}", scores.governance.unwrap_or(0.0)); + } + Ok(()) +} +``` + +### Advanced Client Configuration + +```rust +use yfinance_rs::{YfClientBuilder, Ticker, core::client::{Backoff, CacheMode, RetryConfig}}; +use std::time::Duration; +use yfinance_rs::core::conversions::money_to_f64; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClientBuilder::default() + .timeout(Duration::from_secs(10)) + .retry_config(RetryConfig { + max_retries: 3, + backoff: Backoff::Exponential { + base: Duration::from_millis(100), + factor: 2.0, + max: Duration::from_secs(5), + jitter: true, + }, + ..Default::default() + }) + .build()?; + + let ticker = Ticker::new(&client, "AAPL") + .cache_mode(CacheMode::Bypass) + .retry_policy(Some(RetryConfig { + max_retries: 5, + ..Default::default() + })); + + let quote = ticker.quote().await?; + println!( + "Latest price for AAPL with custom client: ${:.2}", + quote.price.as_ref().map(money_to_f64).unwrap_or(0.0) + ); + + Ok(()) +} + +``` + +#### Custom Reqwest Client + +For full control over HTTP configuration, you can provide your own reqwest client: + +```rust +use yfinance_rs::{YfClient, Ticker}; +use yfinance_rs::core::conversions::money_to_f64; +use reqwest::Client; +use std::time::Duration; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let custom_client = Client::builder() + .user_agent("yfinance-rs-playground") // Make sure to set a proper user agent + .timeout(Duration::from_secs(30)) + .connect_timeout(Duration::from_secs(10)) + .pool_idle_timeout(Duration::from_secs(90)) + .pool_max_idle_per_host(10) + .tcp_keepalive(Some(Duration::from_secs(60))) + .build()?; + + let client = YfClient::builder() + .custom_client(custom_client) + .cache_ttl(Duration::from_secs(300)) + .build()?; + + let ticker = Ticker::new(&client, "AAPL"); + let quote = ticker.quote().await?; + println!( + "Latest price for AAPL: ${:.2}", + quote.price.as_ref().map(money_to_f64).unwrap_or(0.0) + ); + + Ok(()) +} +``` + +#### Proxy Configuration + +You can configure HTTP/HTTPS proxies through the builder: + +```rust +use yfinance_rs::{YfClient, YfClientBuilder, Ticker}; +use yfinance_rs::core::conversions::money_to_f64; +use std::time::Duration; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClient::builder() + .try_proxy("http://proxy.example.com:8080")? + .timeout(Duration::from_secs(30)) + .build()?; + + let client_https = YfClient::builder() + .try_https_proxy("https://proxy.example.com:8443")? + .timeout(Duration::from_secs(30)) + .build()?; + + let client_simple = YfClient::builder() + .proxy("http://proxy.example.com:8080") + .timeout(Duration::from_secs(30)) + .build()?; + + let ticker = Ticker::new(&client, "AAPL"); + let quote = ticker.quote().await?; + println!( + "Latest price for AAPL via proxy: ${:.2}", + quote.price.as_ref().map(money_to_f64).unwrap_or(0.0) + ); + + Ok(()) +} +``` + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## Contributing + +Please see our [Contributing Guide](CONTRIBUTING.md) and our [Code of Conduct](CODE_OF_CONDUCT.md). We welcome pull requests and issues. + +## Changelog + +See **[CHANGELOG.md](https://github.com/gramistella/yfinance-rs/blob/main/CHANGELOG.md)** for release notes and breaking changes. diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/build.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/build.rs new file mode 100644 index 0000000..c2bce4f --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/01_basic_usage.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/01_basic_usage.rs new file mode 100644 index 0000000..bf0c392 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/01_basic_usage.rs @@ -0,0 +1,204 @@ +use chrono::{Duration, Utc}; +use std::time::Duration as StdDuration; +use yfinance_rs::core::Interval; +use yfinance_rs::core::conversions::money_to_f64; +use yfinance_rs::{ + DownloadBuilder, NewsTab, StreamBuilder, StreamMethod, Ticker, YfClient, YfClientBuilder, + YfError, +}; + +#[tokio::main] +async fn main() -> Result<(), YfError> { + let client = YfClientBuilder::default() + .timeout(StdDuration::from_secs(5)) + .build()?; + + section_info(&client).await?; + section_fast_info(&client).await?; + section_batch_quotes(&client).await?; + section_download(&client).await?; + section_options(&client).await?; + section_stream(&client).await?; + section_news(&client).await?; + Ok(()) +} + +async fn section_info(client: &YfClient) -> Result<(), YfError> { + let msft = Ticker::new(client, "MSFT"); + let info = msft.info().await?; + println!("--- Ticker Info for {} ---", info.symbol); + println!("Name: {}", info.name.unwrap_or_default()); + println!( + "Last Price: ${:.2}", + info.last.as_ref().map(money_to_f64).unwrap_or_default() + ); + if let Some(v) = info.volume { + println!("Volume (day): {v}"); + } + if let Some(pt) = info.price_target.as_ref() + && let Some(mean) = pt.mean.as_ref() + { + println!("Price target mean: ${:.2}", money_to_f64(mean)); + } + if let Some(rs) = info.recommendation_summary.as_ref() { + let mean = rs.mean.unwrap_or_default(); + let text = rs.mean_rating_text.as_deref().unwrap_or("N/A"); + println!("Recommendation mean: {mean:.2} ({text})"); + } + println!(); + Ok(()) +} + +async fn section_fast_info(client: &YfClient) -> Result<(), YfError> { + println!("--- Fast Info for NVDA ---"); + let nvda = Ticker::new(client, "NVDA"); + let fast_info = nvda.fast_info().await?; + let price_money = fast_info + .last + .clone() + .or_else(|| fast_info.previous_close.clone()) + .expect("last or previous_close present"); + println!( + "{} is trading at ${:.2} in {}", + fast_info.symbol, + yfinance_rs::core::conversions::money_to_f64(&price_money), + fast_info + .exchange + .map(|e| e.to_string()) + .unwrap_or_default() + ); + if let Some(v) = fast_info.volume { + println!("Day volume: {v}"); + } + println!(); + Ok(()) +} + +async fn section_batch_quotes(client: &YfClient) -> Result<(), YfError> { + println!("--- Batch Quotes for Multiple Symbols ---"); + let quotes = yfinance_rs::quotes(client, vec!["AMD", "INTC", "QCOM"]).await?; + for quote in quotes { + let vol = quote + .day_volume + .map(|v| format!(" (vol: {v})")) + .unwrap_or_default(); + println!( + " {}: ${:.2}{}", + quote.symbol, + quote.price.as_ref().map(money_to_f64).unwrap_or_default(), + vol + ); + } + println!(); + Ok(()) +} + +async fn section_download(client: &YfClient) -> Result<(), YfError> { + let symbols = vec!["AAPL", "GOOG", "TSLA"]; + let today = Utc::now(); + let three_months_ago = today - Duration::days(90); + println!("--- Historical Data for Multiple Symbols ---"); + let results = DownloadBuilder::new(client) + .symbols(symbols) + .between(three_months_ago, today) + .interval(Interval::D1) + .run() + .await?; + for entry in &results.entries { + let symbol = entry.instrument.symbol(); + let candles = &entry.history.candles; + println!("{} has {} data points.", symbol, candles.len()); + if let Some(last_candle) = candles.last() { + println!( + " Last close price: ${:.2}", + money_to_f64(&last_candle.close) + ); + } + } + println!(); + Ok(()) +} + +async fn section_options(client: &YfClient) -> Result<(), YfError> { + let aapl = Ticker::new(client, "AAPL"); + let expirations = aapl.options().await?; + if let Some(first_expiry) = expirations.first() { + println!("--- Options Chain for AAPL ({first_expiry}) ---"); + let chain = aapl.option_chain(Some(*first_expiry)).await?; + println!( + " Found {} calls and {} puts.", + chain.calls.len(), + chain.puts.len() + ); + if let Some(first_call) = chain.calls.first() { + println!( + " First call option: {} @ ${:.2}", + first_call.contract_symbol, + money_to_f64(&first_call.strike) + ); + } + } + println!(); + Ok(()) +} + +async fn section_stream(client: &YfClient) -> Result<(), YfError> { + println!("--- Streaming Real-time Quotes for MSFT and GOOG ---"); + println!("(Streaming for 10 seconds or until stopped...)"); + let (handle, mut receiver) = StreamBuilder::new(client) + .symbols(vec!["GME"]) + .method(StreamMethod::WebsocketWithFallback) + .start()?; + + let stream_task = tokio::spawn(async move { + let mut count = 0; + while let Some(update) = receiver.recv().await { + let vol = update + .volume + .map(|v| format!(" (vol Δ: {v})")) + .unwrap_or_default(); + println!( + "[{}] {} @ {:.2}{}", + update.ts, + update.symbol, + update.price.as_ref().map(money_to_f64).unwrap_or_default(), + vol + ); + count += 1; + if count >= 1000 { + break; + } + } + println!("Finished streaming after {count} updates."); + }); + + tokio::select! { + () = tokio::time::sleep(StdDuration::from_secs(1000)) => { + println!("Stopping stream due to timeout."); + handle.stop().await; + } + _ = stream_task => { + println!("Stream task completed on its own."); + } + }; + Ok(()) +} + +async fn section_news(client: &YfClient) -> Result<(), YfError> { + let tesla_news = Ticker::new(client, "TSLA"); + let articles = tesla_news + .news_builder() + .tab(NewsTab::PressReleases) + .count(5) + .fetch() + .await?; + println!("\n--- Latest 5 Press Releases for TSLA ---"); + for article in articles { + println!( + "- {} by {}", + article.title, + article.publisher.unwrap_or_else(|| "Unknown".to_string()) + ); + } + Ok(()) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/02_fundamentals_and_search.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/02_fundamentals_and_search.rs new file mode 100644 index 0000000..33b4bfe --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/02_fundamentals_and_search.rs @@ -0,0 +1,103 @@ +use yfinance_rs::core::conversions::money_to_f64; +use yfinance_rs::{FundamentalsBuilder, HoldersBuilder, SearchBuilder, YfClient}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClient::default(); + let symbol = "MSFT"; + + // --- Part 1: Fetching Fundamentals --- + println!("--- Fetching Fundamentals for {symbol} ---"); + let fundamentals = FundamentalsBuilder::new(&client, symbol); + + let annual_income_stmt = fundamentals.income_statement(false, None).await?; + println!( + "Latest Annual Income Statement ({} periods):", + annual_income_stmt.len() + ); + if let Some(stmt) = annual_income_stmt.first() { + println!( + " Period End: {} | Total Revenue: {:.2}", + stmt.period, + stmt.total_revenue + .as_ref() + .map(money_to_f64) + .unwrap_or_default() + ); + } + + let quarterly_balance_sheet = fundamentals.balance_sheet(true, None).await?; + println!( + "Latest Quarterly Balance Sheet ({} periods):", + quarterly_balance_sheet.len() + ); + if let Some(stmt) = quarterly_balance_sheet.first() { + println!( + " Period End: {} | Total Assets: {:.2}", + stmt.period, + stmt.total_assets + .as_ref() + .map(money_to_f64) + .unwrap_or_default() + ); + } + + let earnings = fundamentals.earnings(None).await?; + println!("Latest Earnings Summary:"); + if let Some(e) = earnings.quarterly.first() { + println!( + " Quarter {}: Revenue: {:.2} | Earnings: {:.2}", + e.period, + e.revenue.as_ref().map(money_to_f64).unwrap_or_default(), + e.earnings.as_ref().map(money_to_f64).unwrap_or_default() + ); + } + println!("--------------------------------------\n"); + + // --- Part 2: Fetching Holder Information --- + println!("--- Fetching Holder Info for {symbol} ---"); + let holders_builder = HoldersBuilder::new(&client, symbol); + + let major_holders = holders_builder.major_holders().await?; + println!("Major Holders Breakdown:"); + for holder in major_holders { + println!(" {}: {}", holder.category, holder.value); + } + + let inst_holders = holders_builder.institutional_holders().await?; + println!("\nTop 5 Institutional Holders:"); + for holder in inst_holders.iter().take(5) { + println!( + " - {}: {:?} shares ({:?}%)", + holder.holder, holder.shares, holder.pct_held + ); + } + + let net_activity = holders_builder.net_share_purchase_activity().await?; + if let Some(activity) = net_activity { + println!("\nNet Insider Purchase Activity ({}):", activity.period); + println!(" Net shares bought/sold: {:?}", activity.net_shares); + } + println!("--------------------------------------\n"); + + // --- Part 3: Searching for Tickers --- + let query = "S&P 500"; + println!("--- Searching for '{query}' ---"); + + let search_results = SearchBuilder::new(&client, query) + .lang("en") + .region("US") + .fetch() + .await?; + + println!("Found {} results:", search_results.results.len()); + for quote in search_results.results { + let name = quote.name.unwrap_or_default(); + let exchange = quote.exchange.map(|e| e.to_string()).unwrap_or_default(); + let kind = quote.kind.to_string(); + println!(" - {}: {} ({}) on {}", quote.symbol, name, kind, exchange); + } + println!("--------------------------------------"); + + Ok(()) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/03_esg_and_analysis.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/03_esg_and_analysis.rs new file mode 100644 index 0000000..579f43f --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/03_esg_and_analysis.rs @@ -0,0 +1,124 @@ +use chrono::Duration; +use yfinance_rs::{SearchBuilder, Ticker, YfClientBuilder}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClientBuilder::default() + .timeout(Duration::seconds(5).to_std()?) + .build()?; + + section_esg(&client).await?; + section_analysis(&client).await?; + section_search(&client).await?; + Ok(()) +} + +async fn section_esg(client: &yfinance_rs::YfClient) -> Result<(), Box> { + let msft_ticker = Ticker::new(client, "MSFT"); + let esg_scores = msft_ticker.sustainability().await; + println!("--- ESG Scores for MSFT ---"); + match esg_scores { + Ok(summary) => { + let scores = summary.scores.unwrap_or_default(); + let total_esg = [scores.environmental, scores.social, scores.governance] + .into_iter() + .flatten() + .collect::>(); + let total_esg_score = if total_esg.is_empty() { + 0.0 + } else { + let denom = u32::try_from(total_esg.len()).map(f64::from).unwrap_or(1.0); + total_esg.iter().sum::() / denom + }; + println!("Total ESG Score: {total_esg_score:.2}"); + println!( + "Environmental Score: {:.2}", + scores.environmental.unwrap_or_default() + ); + println!("Social Score: {:.2}", scores.social.unwrap_or_default()); + println!( + "Governance Score: {:.2}", + scores.governance.unwrap_or_default() + ); + if !summary.involvement.is_empty() { + println!("Involvement categories ({}):", summary.involvement.len()); + for inv in summary.involvement.iter().take(5) { + println!(" - {}", inv.category); + } + } + } + Err(e) => eprintln!("Failed to fetch ESG scores: {e}"), + } + println!("--------------------------------------\n"); + Ok(()) +} + +async fn section_analysis( + client: &yfinance_rs::YfClient, +) -> Result<(), Box> { + let tsla_ticker = Ticker::new(client, "TSLA"); + let recommendations = tsla_ticker.recommendations().await; + println!("--- Analyst Recommendations for TSLA ---"); + match recommendations { + Ok(recs) => { + if let Some(latest) = recs.first() { + println!( + "Latest Recommendation Period ({}): Strong Buy: {:?}, Buy: {:?}, Hold: {:?}, Sell: {:?}, Strong Sell: {:?}", + latest.period, + latest.strong_buy, + latest.buy, + latest.hold, + latest.sell, + latest.strong_sell + ); + } + } + Err(e) => eprintln!("Failed to fetch recommendations: {e}"), + } + let upgrades = tsla_ticker.upgrades_downgrades().await; + if let Ok(upgrades_list) = upgrades { + println!("\nRecent Upgrades/Downgrades:"); + for upgrade in upgrades_list.iter().take(3) { + println!( + " - Firm: {} | Action: {} | From: {} | To: {}", + upgrade.firm.as_deref().unwrap_or("N/A"), + upgrade + .action + .as_ref() + .map_or_else(|| "N/A".to_string(), std::string::ToString::to_string), + upgrade + .from_grade + .as_ref() + .map_or_else(|| "N/A".to_string(), std::string::ToString::to_string), + upgrade + .to_grade + .as_ref() + .map_or_else(|| "N/A".to_string(), std::string::ToString::to_string) + ); + } + } + println!("--------------------------------------\n"); + Ok(()) +} + +async fn section_search(client: &yfinance_rs::YfClient) -> Result<(), Box> { + let query = "Apple Inc."; + let search_results = SearchBuilder::new(client, query).fetch().await; + println!("--- Searching for '{query}' ---"); + match search_results { + Ok(results) => { + println!("Found {} results:", results.results.len()); + for quote in results.results.iter().take(5) { + println!( + " - {} ({}) : {}", + quote.symbol, + quote.kind, + quote.name.as_deref().unwrap_or("N/A") + ); + } + } + Err(e) => eprintln!("Search failed: {e}"), + } + println!("--------------------------------------"); + Ok(()) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/04_historical_actions.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/04_historical_actions.rs new file mode 100644 index 0000000..d7a27da --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/04_historical_actions.rs @@ -0,0 +1,68 @@ +use chrono::{Duration, Utc}; +use yfinance_rs::core::conversions::money_to_f64; +use yfinance_rs::core::{Interval, Range}; +use yfinance_rs::{DownloadBuilder, Ticker, YfClient}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClient::default(); + + // --- Part 1: Fetching Historical Dividends and Splits --- + let aapl_ticker = Ticker::new(&client, "AAPL"); + + println!("--- Fetching Historical Actions for AAPL (last 5 years) ---"); + let dividends = aapl_ticker.dividends(Some(Range::Y5)).await?; + println!("Found {} dividends in the last 5 years.", dividends.len()); + if let Some((ts, amount)) = dividends.last() { + println!(" Latest dividend: ${amount:.2} on {ts}"); + } + + let splits = aapl_ticker.splits(Some(Range::Y5)).await?; + println!("\nFound {} splits in the last 5 years.", splits.len()); + for (ts, num, den) in splits { + println!(" - Split of {num}:{den} on {ts}"); + } + println!("--------------------------------------\n"); + + // --- Part 2: Advanced Multi-Symbol Download with Customization --- + let symbols = vec!["AAPL", "GOOGL", "MSFT", "AMZN"]; + println!("--- Downloading Custom Historical Data for Multiple Symbols ---"); + println!("Fetching 1-week, auto-adjusted data for the last 30 days..."); + + let thirty_days_ago = Utc::now() - Duration::days(30); + let now = Utc::now(); + + let results = DownloadBuilder::new(&client) + .symbols(symbols) + .between(thirty_days_ago, now) + .interval(Interval::W1) + .auto_adjust(true) // default, but explicit here + .back_adjust(true) // show back-adjustment + .repair(true) // show outlier repair + .rounding(true) // show rounding + .run() + .await?; + + for entry in &results.entries { + let symbol = entry.instrument.symbol(); + let candles = &entry.history.candles; + println!("- {} ({} candles)", symbol, candles.len()); + if let Some(first_candle) = candles.first() { + println!(" First Open: ${:.2}", money_to_f64(&first_candle.open)); + } + if let Some(last_candle) = candles.last() { + println!(" Last Close: ${:.2}", money_to_f64(&last_candle.close)); + } + } + println!("--------------------------------------"); + + let meta = aapl_ticker.get_history_metadata(Some(Range::Y1)).await?; + println!("\n--- History Metadata for AAPL ---"); + if let Some(m) = meta { + println!(" Timezone: {}", m.timezone.unwrap_or_default()); + println!(" GMT Offset: {}", m.utc_offset_seconds.unwrap_or_default()); + } + println!("--------------------------------------"); + + Ok(()) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/05_concurrent_requests.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/05_concurrent_requests.rs new file mode 100644 index 0000000..631f107 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/05_concurrent_requests.rs @@ -0,0 +1,103 @@ +use futures::future::try_join_all; +use yfinance_rs::core::conversions::money_to_f64; +use yfinance_rs::{FundamentalsBuilder, SearchBuilder, Ticker, YfClient}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClient::default(); + let symbols = ["AAPL", "GOOGL", "TSLA"]; + + println!("--- Fetching a comprehensive overview for multiple tickers ---"); + let fetch_info_tasks: Vec<_> = symbols + .iter() + .map(|&s| { + let ticker = Ticker::new(&client, s); + async move { + let info = ticker.info().await?; + let vol = info + .volume + .map(|v| format!(" (vol: {v})")) + .unwrap_or_default(); + println!( + "Symbol: {}, Name: {}, Price: {:.2}{}", + info.symbol, + info.name.unwrap_or_default(), + info.last.as_ref().map_or(0.0, money_to_f64), + vol + ); + Ok::<_, yfinance_rs::YfError>(()) + } + }) + .collect(); + let _ = try_join_all(fetch_info_tasks).await?; + println!(); + + println!("--- Fetching annual fundamentals for a single ticker (AAPL) ---"); + let aapl_fundamentals = FundamentalsBuilder::new(&client, "AAPL"); + let annual_income_stmt = aapl_fundamentals.income_statement(false, None).await?; + if let Some(stmt) = annual_income_stmt.first() { + println!( + "AAPL Latest Annual Revenue: {:.2} (from {})", + stmt.total_revenue + .as_ref() + .map(money_to_f64) + .unwrap_or_default(), + stmt.period + ); + } + let annual_cashflow = aapl_fundamentals.cashflow(false, None).await?; + if let Some(cf) = annual_cashflow.first() { + println!( + "AAPL Latest Annual Free Cash Flow: {:.2}", + cf.free_cash_flow + .as_ref() + .map(money_to_f64) + .unwrap_or_default() + ); + } + println!(); + + println!("--- Fetching ESG and holder data for MSFT ---"); + let msft_ticker = Ticker::new(&client, "MSFT"); + let esg_summary = msft_ticker.sustainability().await?; + let parts = esg_summary + .scores + .map_or([None, None, None], |s| { + [s.environmental, s.social, s.governance] + }) + .into_iter() + .flatten() + .collect::>(); + let total_esg = if parts.is_empty() { + 0.0 + } else { + let denom: f64 = u32::try_from(parts.len()).map(f64::from).unwrap_or(1.0); + parts.iter().sum::() / denom + }; + println!("MSFT Total ESG Score: {total_esg:.2}"); + let institutional_holders = msft_ticker.institutional_holders().await?; + if let Some(holder) = institutional_holders.first() { + println!( + "MSFT Top institutional holder: {} with {:?} shares", + holder.holder, holder.shares + ); + } + println!(); + + println!("--- Searching for SPY and getting its ticker ---"); + let search_results = SearchBuilder::new(&client, "SPY").fetch().await?; + if let Some(sp500_quote) = search_results + .results + .iter() + .find(|q| q.symbol.as_str() == "SPY") + { + println!( + "Found: {} ({})", + sp500_quote.name.as_deref().unwrap_or("N/A"), + sp500_quote.symbol + ); + } + println!(); + + Ok(()) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/06_realtime_polling.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/06_realtime_polling.rs new file mode 100644 index 0000000..7b13a1c --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/06_realtime_polling.rs @@ -0,0 +1,54 @@ +use chrono::Duration; +use yfinance_rs::{StreamBuilder, StreamMethod, YfClient}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClient::default(); + let symbols = vec!["TSLA", "GOOG"]; + + println!("--- Polling for Real-time Quotes every 5 seconds ---"); + println!("(Polling for 20 seconds or until stopped...)"); + + // Create a StreamBuilder explicitly configured for polling. + let (handle, mut receiver) = StreamBuilder::new(&client) + .symbols(symbols) + .method(StreamMethod::Polling) + .interval(Duration::seconds(5).to_std().unwrap()) + .diff_only(false) // Get updates even if price hasn't changed + .start()?; + + let stream_task = tokio::spawn(async move { + let mut count = 0; + while let Some(update) = receiver.recv().await { + println!( + "[{}] {} @ {:.2} {}", + update.ts, + update.symbol, + update + .price + .as_ref() + .map(yfinance_rs::core::conversions::money_to_f64) + .unwrap_or_default(), + update + .volume + .map(|v| format!("({v} delta)")) + .unwrap_or_default() + ); + count += 1; + } + println!("Finished polling after {count} updates."); + }); + + // Stop the stream after 20 seconds, regardless of how many updates were received. + tokio::select! { + () = tokio::time::sleep(Duration::seconds(20).to_std()?) => { + println!("Stopping polling due to timeout."); + handle.stop().await; + } + _ = stream_task => { + println!("Polling task completed on its own."); + } + }; + + Ok(()) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/07_quarterly_fundamentals.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/07_quarterly_fundamentals.rs new file mode 100644 index 0000000..e284e7e --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/07_quarterly_fundamentals.rs @@ -0,0 +1,59 @@ +use yfinance_rs::core::conversions::money_to_f64; +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClient::default(); + let ticker = Ticker::new(&client, "MSFT"); + + println!("--- Fetching Quarterly Financial Statements for MSFT ---"); + println!("Fetching latest quarterly income statement..."); + let income_stmt = ticker.quarterly_income_stmt(None).await?; + if let Some(latest) = income_stmt.first() { + println!( + "Latest quarterly revenue: {:.2} (from {})", + latest.total_revenue.as_ref().map_or(0.0, money_to_f64), + latest.period + ); + } else { + println!("No quarterly income statement found."); + } + + println!("\nFetching latest quarterly balance sheet..."); + let balance_sheet = ticker.quarterly_balance_sheet(None).await?; + if let Some(latest) = balance_sheet.first() { + println!( + "Latest quarterly total assets: {:.2} (from {})", + latest.total_assets.as_ref().map_or(0.0, money_to_f64), + latest.period + ); + } else { + println!("No quarterly balance sheet found."); + } + + println!("\nFetching latest quarterly cash flow statement..."); + let cashflow_stmt = ticker.quarterly_cashflow(None).await?; + if let Some(latest) = cashflow_stmt.first() { + println!( + "Latest quarterly operating cash flow: {:.2} (from {})", + latest.operating_cashflow.as_ref().map_or(0.0, money_to_f64), + latest.period + ); + } else { + println!("No quarterly cash flow statement found."); + } + + println!("\nFetching latest quarterly shares outstanding..."); + let shares = ticker.quarterly_shares().await?; + if let Some(latest) = shares.first() { + println!( + "Latest quarterly shares outstanding: {} (from {})", + latest.shares, + latest.date.date_naive() + ); + } else { + println!("No quarterly shares outstanding found."); + } + + Ok(()) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/08_advanced_analysis.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/08_advanced_analysis.rs new file mode 100644 index 0000000..64186fb --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/08_advanced_analysis.rs @@ -0,0 +1,127 @@ +use yfinance_rs::core::conversions::money_to_f64; +use yfinance_rs::{Ticker, YfClient, YfError}; + +#[tokio::main] +async fn main() -> Result<(), YfError> { + let client = YfClient::default(); + + let symbol = "AAPL"; + let ticker_aapl = Ticker::new(&client, symbol); + section_earnings_and_shares(symbol, &ticker_aapl).await?; + section_capital_gains().await?; + section_price_target(symbol, &ticker_aapl).await?; + section_recommendations(symbol, &ticker_aapl).await?; + section_isin_calendar(symbol, &ticker_aapl).await?; + Ok(()) +} + +async fn section_earnings_and_shares(symbol: &str, ticker: &Ticker) -> Result<(), YfError> { + println!("--- Fetching Advanced Analysis for {symbol} ---"); + let earnings_trend = ticker.earnings_trend(None).await?; + println!("Earnings Trend ({} periods):", earnings_trend.len()); + if let Some(trend) = earnings_trend.iter().find(|t| t.period.to_string() == "0y") { + println!( + " Current Year ({}): Earnings Est. Avg: {:.2}, Revenue Est. Avg: {}", + trend.period, + trend + .earnings_estimate + .avg + .as_ref() + .map(money_to_f64) + .unwrap_or_default(), + trend + .revenue_estimate + .avg + .as_ref() + .map(money_to_f64) + .unwrap_or_default() + ); + } + println!(); + + println!("--- Fetching Historical Shares for {symbol} ---"); + let shares = ticker.shares().await?; + println!("Annual Shares Outstanding ({} periods):", shares.len()); + if let Some(share_count) = shares.first() { + println!( + " Latest Period ({}): {} shares", + share_count.date, share_count.shares + ); + } + println!(); + Ok(()) +} + +async fn section_capital_gains() -> Result<(), YfError> { + println!("--- Fetching Capital Gains for VFINX (Vanguard 500 Index Fund) ---"); + let client = YfClient::default(); + let ticker_vfinx = Ticker::new(&client, "VFINX"); + let capital_gains = ticker_vfinx.capital_gains(None).await?; + println!( + "Capital Gains Distributions ({} periods):", + capital_gains.len() + ); + if let Some((date, gain)) = capital_gains.last() { + println!(" Most Recent Gain: ${gain:.2} on {date}"); + } + Ok(()) +} + +async fn section_price_target(symbol: &str, ticker: &Ticker) -> Result<(), YfError> { + println!("--- Analyst Price Target for {symbol} ---"); + let price_target = ticker.analyst_price_target(None).await?; + println!( + " Target: avg=${:.2}, high=${:.2}, low=${:.2} (from {} analysts)", + price_target + .mean + .as_ref() + .map(money_to_f64) + .unwrap_or_default(), + price_target + .high + .as_ref() + .map(money_to_f64) + .unwrap_or_default(), + price_target + .low + .as_ref() + .map(money_to_f64) + .unwrap_or_default(), + price_target.number_of_analysts.unwrap_or_default() + ); + println!(); + Ok(()) +} + +async fn section_recommendations(symbol: &str, ticker: &Ticker) -> Result<(), YfError> { + println!("--- Recommendation Summary for {symbol} ---"); + let rec_summary = ticker.recommendations_summary().await?; + println!( + " Mean score: {:.2} ({})", + rec_summary.mean.unwrap_or_default(), + rec_summary.mean_rating_text.as_deref().unwrap_or("N/A") + ); + println!(); + Ok(()) +} + +async fn section_isin_calendar(symbol: &str, ticker: &Ticker) -> Result<(), YfError> { + println!("--- ISIN for {symbol} ---"); + let isin = ticker.isin().await?; + println!( + " ISIN: {}", + isin.unwrap_or_else(|| "Not found".to_string()) + ); + println!(); + + println!("--- Upcoming Calendar Events for {symbol} ---"); + let calendar = ticker.calendar().await?; + if let Some(date) = calendar.earnings_dates.first() { + println!(" Next earnings date (approx): {}", date.date_naive()); + } + if let Some(date) = calendar.ex_dividend_date { + println!(" Ex-dividend date: {}", date.date_naive()); + } + println!(); + Ok(()) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/09_holders_and_insiders.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/09_holders_and_insiders.rs new file mode 100644 index 0000000..254cfbd --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/09_holders_and_insiders.rs @@ -0,0 +1,48 @@ +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClient::default(); + let ticker = Ticker::new(&client, "TSLA"); + + println!("--- Fetching Holder Information for TSLA ---"); + + // Mutual Fund Holders + let mf_holders = ticker.mutual_fund_holders().await?; + println!("\nTop 5 Mutual Fund Holders:"); + for holder in mf_holders.iter().take(5) { + println!( + " - {}: {:?} shares ({:.2}%)", + holder.holder, + holder.shares, + holder.pct_held.unwrap_or(0.0) * 100.0 + ); + } + + // Insider Transactions + let insider_txns = ticker.insider_transactions().await?; + println!("\nLatest 5 Insider Transactions:"); + for txn in insider_txns.iter().take(5) { + println!( + " - {}: {} {:?} shares on {}", + txn.insider, + txn.transaction_type, + txn.shares, + txn.transaction_date.date_naive() + ); + } + + // Insider Roster + let insider_roster = ticker.insider_roster_holders().await?; + println!("\nTop 5 Insider Roster:"); + for insider in insider_roster.iter().take(5) { + println!( + " - {} ({}): {:?} shares", + insider.name, insider.position, insider.shares_owned_directly + ); + } + + println!("-----------------------------------------"); + + Ok(()) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/10_convenience_methods.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/10_convenience_methods.rs new file mode 100644 index 0000000..2aabdce --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/10_convenience_methods.rs @@ -0,0 +1,93 @@ +use yfinance_rs::core::conversions::money_to_f64; +use yfinance_rs::core::{Interval, Range}; +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClient::default(); + let ticker = Ticker::new(&client, "AAPL"); + + println!("--- Ticker Quote (Convenience) ---"); + let quote = ticker.quote().await?; + let vol = quote + .day_volume + .map(|v| format!(" (vol: {v})")) + .unwrap_or_default(); + println!( + " {}: ${:.2} (prev_close: ${:.2}){}", + quote.symbol, + quote.price.as_ref().map(money_to_f64).unwrap_or_default(), + quote + .previous_close + .as_ref() + .map(money_to_f64) + .unwrap_or_default(), + vol + ); + println!(); + + println!("--- Ticker News (Convenience, default count) ---"); + let news = ticker.news().await?; + println!(" Found {} articles with default settings.", news.len()); + if let Some(article) = news.first() { + println!(" First article: {}", article.title); + } + println!(); + + println!("--- Ticker History (Convenience, last 5 days) ---"); + let history = ticker + .history(Some(Range::D5), Some(Interval::D1), false) + .await?; + if let Some(candle) = history.last() { + println!( + " Last close on {}: ${:.2}", + candle.ts.date_naive(), + money_to_f64(&candle.close) + ); + } + println!(); + + println!("--- Ticker Actions (Convenience, YTD) ---"); + let actions = ticker.actions(Some(Range::Ytd)).await?; + println!(" Found {} actions (dividends/splits) YTD.", actions.len()); + if let Some(action) = actions.last() { + println!(" Most recent action: {action:?}"); + } + println!(); + + println!("--- Annual Financials (Convenience) ---"); + let annual_income = ticker.income_stmt(None).await?; + if let Some(stmt) = annual_income.first() { + println!( + " Latest annual revenue: {:.2}", + stmt.total_revenue + .as_ref() + .map(money_to_f64) + .unwrap_or_default() + ); + } + + let annual_balance = ticker.balance_sheet(None).await?; + if let Some(stmt) = annual_balance.first() { + println!( + " Latest annual assets: {:.2}", + stmt.total_assets + .as_ref() + .map(money_to_f64) + .unwrap_or_default() + ); + } + + let annual_cashflow = ticker.cashflow(None).await?; + if let Some(stmt) = annual_cashflow.first() { + println!( + " Latest annual free cash flow: {:.2}", + stmt.free_cash_flow + .as_ref() + .map(money_to_f64) + .unwrap_or_default() + ); + } + + Ok(()) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/11_builder_configuration.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/11_builder_configuration.rs new file mode 100644 index 0000000..2ce7b0a --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/11_builder_configuration.rs @@ -0,0 +1,95 @@ +use std::time; + +use chrono::{Duration, Utc}; +use yfinance_rs::core::Interval; +use yfinance_rs::{ + DownloadBuilder, QuotesBuilder, SearchBuilder, Ticker, YfClient, + core::client::{Backoff, CacheMode, RetryConfig}, +}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClient::default(); + + println!("--- QuotesBuilder Usage ---"); + let quotes = QuotesBuilder::new(client.clone()) + .symbols(vec!["F", "GM", "TSLA"]) + .fetch() + .await?; + println!(" Fetched {} quotes via QuotesBuilder.", quotes.len()); + println!(); + + println!("--- Per-Request Configuration: No Cache ---"); + let aapl = Ticker::new(&client, "AAPL").cache_mode(CacheMode::Bypass); + let quote_no_cache = aapl.quote().await?; + println!( + " Fetched {} quote, bypassing the client's cache.", + quote_no_cache.symbol + ); + println!(); + + println!("--- SearchBuilder Customization ---"); + let sb = SearchBuilder::new(&client, "Microsoft") + .quotes_count(2) + .region("US") + .lang("en-US"); + println!( + " Using lang={} region={}", + sb.lang_ref().unwrap_or("N/A"), + sb.region_ref().unwrap_or("N/A") + ); + let search_results = sb.fetch().await?; + println!( + " Found {} results for 'Microsoft' in US region.", + search_results.results.len() + ); + for quote in search_results.results { + println!( + " - {} ({})", + quote.symbol, + quote.name.unwrap_or_default() + ); + } + println!(); + + println!("--- DownloadBuilder with pre/post market and keepna ---"); + // Get recent data including pre/post market, which might have gaps (keepna=true) + let today = Utc::now(); + let yesterday = today - Duration::days(1); + let download = DownloadBuilder::new(&client) + .symbols(vec!["TSLA"]) + .between(yesterday, today) + .interval(Interval::I15m) + .prepost(true) + .keepna(true) + .run() + .await?; + if let Some(entry) = download + .entries + .iter() + .find(|e| e.instrument.symbol_str() == "TSLA") + { + println!( + " Fetched {} 15m candles for TSLA in the last 24h (pre/post included).", + entry.history.candles.len() + ); + } + println!(); + + println!("--- Overriding Retry Policy for a Single Ticker ---"); + let custom_retry = RetryConfig { + enabled: true, + max_retries: 1, + backoff: Backoff::Fixed(time::Duration::from_millis(100)), + ..Default::default() + }; + let goog = Ticker::new(&client, "GOOG").retry_policy(Some(custom_retry)); + // This call will now use the custom retry policy instead of the client's default + let goog_info = goog.fast_info().await?; + println!( + " Fetched fast info for {} with a custom retry policy.", + goog_info.symbol + ); + + Ok(()) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/12_advanced_client.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/12_advanced_client.rs new file mode 100644 index 0000000..2c33ada --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/12_advanced_client.rs @@ -0,0 +1,77 @@ +use std::time::Duration; +use yfinance_rs::core::conversions::money_to_f64; +use yfinance_rs::{ + Ticker, YfClientBuilder, YfError, + core::client::{Backoff, RetryConfig}, +}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // 1. --- Advanced Client Configuration --- + println!("--- Building a client with custom configuration ---"); + let custom_retry = RetryConfig { + enabled: true, + max_retries: 2, + backoff: Backoff::Fixed(Duration::from_millis(500)), + ..Default::default() + }; + let client = YfClientBuilder::default() + .retry_config(custom_retry) + .cache_ttl(Duration::from_secs(60)) // Cache responses for 60 seconds + .build()?; + println!("Client built with custom retry policy."); + println!(); + + // 2. --- Using the custom client --- + let aapl = Ticker::new(&client, "AAPL"); + let quote1 = aapl.quote().await?; + println!( + "First fetch for {}: ${:.2} (from network)", + quote1.symbol, + quote1.price.as_ref().map(money_to_f64).unwrap_or_default() + ); + let quote2 = aapl.quote().await?; + println!( + "Second fetch for {}: ${:.2} (should be from cache)", + quote2.symbol, + quote2.price.as_ref().map(money_to_f64).unwrap_or_default() + ); + println!(); + + // 3. --- Cache Management --- + println!("--- Managing the client cache ---"); + client.clear_cache().await; + println!("Client cache cleared."); + let quote3 = aapl.quote().await?; + println!( + "Third fetch for {}: ${:.2} (from network again)", + quote3.symbol, + quote3.price.as_ref().map(money_to_f64).unwrap_or_default() + ); + println!(); + + // 4. --- Demonstrating a missing data point (dividend date) --- + println!("--- Fetching Calendar Events for AAPL (including dividend date) ---"); + let calendar = aapl.calendar().await?; + if let Some(date) = calendar.ex_dividend_date { + println!(" Dividend date: {}", date.date_naive()); + } else { + println!(" No upcoming dividend date found."); + } + println!(); + + // 5. --- Error Handling Example --- + println!("--- Handling a non-existent ticker ---"); + let bad_ticker = Ticker::new(&client, "THIS-TICKER-DOES-NOT-EXIST-XYZ"); + match bad_ticker.info().await { + Ok(_) => println!("Unexpected success fetching bad ticker."), + Err(YfError::MissingData(msg)) => { + println!("Correctly failed with a missing data error: {msg}"); + } + Err(e) => { + println!("Failed with an unexpected error type: {e}"); + } + } + + Ok(()) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/13_custom_client_and_proxy.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/13_custom_client_and_proxy.rs new file mode 100644 index 0000000..696282d --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/13_custom_client_and_proxy.rs @@ -0,0 +1,141 @@ +use reqwest::Client; +use std::time::Duration; +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + println!("=== Custom Client and Proxy Configuration Examples ===\n"); + + // Example 1: Using a custom reqwest client for full control + println!("1. Custom Reqwest Client Example:"); + let custom_client = Client::builder() + // Set user agent to avoid 429 errors + .user_agent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36") + // You must enable cookie storage to avoid 403 Invalid Cookie errors + .cookie_store(true) + .timeout(Duration::from_secs(30)) + .connect_timeout(Duration::from_secs(10)) + .pool_idle_timeout(Duration::from_secs(90)) + .build()?; + + let client_with_custom = YfClient::builder().custom_client(custom_client).build()?; + + let ticker = Ticker::new(&client_with_custom, "AAPL"); + match ticker.quote().await { + Ok(quote) => println!(" Fetched quote for {} using custom client", quote.symbol), + Err(e) => println!(" Rate limited or error fetching quote: {e}"), + } + println!(); + + // Example 2: Using HTTP proxy through builder + println!("2. HTTP Proxy Configuration Example:"); + // Note: This example uses a dummy proxy URL - replace with actual proxy if needed + // let client_with_proxy = YfClient::builder() + // .proxy("http://proxy.example.com:8080") + // .timeout(Duration::from_secs(30)) + // .build()?; + + // For demonstration, we'll show the builder pattern without actually using a proxy + let client_with_timeout = YfClient::builder() + .timeout(Duration::from_secs(30)) + .connect_timeout(Duration::from_secs(10)) + .build()?; + + let ticker = Ticker::new(&client_with_timeout, "MSFT"); + match ticker.quote().await { + Ok(quote) => println!(" Fetched quote for {} with custom timeout", quote.symbol), + Err(e) => println!(" Rate limited or error fetching quote: {e}"), + } + println!(); + + // Example 3: Using HTTPS proxy with error handling + println!("3. HTTPS Proxy with Error Handling Example:"); + // Note: This example shows the pattern but uses a dummy URL + // let client_with_https_proxy = YfClient::builder() + // .try_https_proxy("https://proxy.example.com:8443")? + // .timeout(Duration::from_secs(30)) + // .build()?; + + // For demonstration, we'll show the error handling pattern + let client_with_retry = YfClient::builder() + .timeout(Duration::from_secs(30)) + .retry_enabled(true) + .build()?; + + let ticker = Ticker::new(&client_with_retry, "GOOGL"); + match ticker.quote().await { + Ok(quote) => println!(" Fetched quote for {} with retry enabled", quote.symbol), + Err(e) => println!(" Rate limited or error fetching quote: {e}"), + } + println!(); + + // Example 4: Advanced custom client configuration + println!("4. Advanced Custom Client Configuration:"); + let advanced_client = Client::builder() + .timeout(Duration::from_secs(60)) + .connect_timeout(Duration::from_secs(15)) + .pool_idle_timeout(Duration::from_secs(120)) + .pool_max_idle_per_host(10) + .tcp_keepalive(Some(Duration::from_secs(60))) + .build()?; + + let client_with_advanced = YfClient::builder() + .custom_client(advanced_client) + .cache_ttl(Duration::from_secs(300)) // 5 minutes cache + .build()?; + + let ticker = Ticker::new(&client_with_advanced, "TSLA"); + match ticker.quote().await { + Ok(quote) => println!( + " Fetched quote for {} with advanced client config", + quote.symbol + ), + Err(e) => println!(" Rate limited or error fetching quote: {e}"), + } + println!(); + + // Example 5: Error handling for invalid proxy URLs + println!("5. Error Handling for Invalid Proxy URLs:"); + match YfClient::builder().try_proxy("invalid-url") { + Ok(_) => println!(" Unexpected: Invalid proxy URL was accepted"), + Err(e) => println!(" Expected error for invalid proxy URL: {e}"), + } + + match YfClient::builder().try_https_proxy("not-a-url") { + Ok(_) => println!(" Unexpected: Invalid HTTPS proxy URL was accepted"), + Err(e) => println!(" Expected error for invalid HTTPS proxy URL: {e}"), + } + println!(); + + // Example 6: Builder pattern validation + println!("6. Builder Pattern Validation:"); + let client = YfClient::builder() + .timeout(Duration::from_secs(30)) + .connect_timeout(Duration::from_secs(10)) + .retry_enabled(true) + .cache_ttl(Duration::from_secs(60)) + .build()?; + + println!(" Successfully built client with custom configuration"); + println!(" - Retry config: {:?}", client.retry_config()); + println!(); + + // Example 7: Working HTTPS proxy example (commented out for safety) + // Uncomment and replace with your actual proxy URL: + // let client_with_https = YfClient::builder() + // .https_proxy("https://your-proxy.com:8443") + // .timeout(Duration::from_secs(30)) + // .build()?; + + println!("=== All examples completed successfully! ==="); + println!(); + println!("Key points:"); + println!("- Use .custom_client() for full reqwest control"); + println!("- Use .proxy() for HTTP proxy setup"); + println!("- Use .https_proxy() for HTTPS proxy setup"); + println!("- Use .try_proxy() or .try_https_proxy() for error handling"); + println!("- Custom client takes precedence over other HTTP settings"); + println!("- Rate limiting (429) is common with live API calls"); + + Ok(()) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/14_polars_dataframes.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/14_polars_dataframes.rs new file mode 100644 index 0000000..b165db1 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/examples/14_polars_dataframes.rs @@ -0,0 +1,193 @@ +//! Example demonstrating Polars `DataFrame` integration with yfinance-rs. +//! +//! Run with: cargo run --example `14_polars_dataframes` --features dataframe + +#[cfg(feature = "dataframe")] +use polars::prelude::*; + +#[cfg(feature = "dataframe")] +use paft::prelude::{ToDataFrame, ToDataFrameVec}; + +#[cfg(feature = "dataframe")] +use yfinance_rs::{Ticker, YfClient}; + +#[cfg(feature = "dataframe")] +use yfinance_rs::core::{Interval, Range}; + +#[cfg(feature = "dataframe")] +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = YfClient::default(); + + println!("=== Polars DataFrame Integration with yfinance-rs ===\n"); + + let ticker = Ticker::new(&client, "AAPL"); + section_history_df(&ticker).await?; + section_quote_df(&ticker).await?; + section_recommendations_df(&ticker).await?; + section_income_df(&ticker).await?; + section_esg(&ticker).await?; + section_holders_df(&ticker).await?; + section_analysis_df(&ticker).await?; + + println!("\n=== DataFrame Integration Complete ==="); + Ok(()) +} + +#[cfg(feature = "dataframe")] +async fn section_history_df(ticker: &Ticker) -> Result<(), Box> { + println!("📈 1. Historical Price Data to DataFrame"); + let history = ticker + .history(Some(Range::M6), Some(Interval::D1), false) + .await?; + + if history.is_empty() { + println!(" No history returned."); + } else { + let df = history.to_dataframe()?; + println!(" DataFrame shape: {:?}", df.shape()); + println!(" Sample data:\n{}", df.head(Some(5))); + } + println!(); + Ok(()) +} + +#[cfg(feature = "dataframe")] +async fn section_quote_df(ticker: &Ticker) -> Result<(), Box> { + println!("📊 2. Current Quote to DataFrame"); + match ticker.quote().await { + Ok(quote) => { + let df = quote.to_dataframe()?; + println!(" DataFrame shape: {:?}", df.shape()); + println!(" Quote data:\n{df}"); + } + Err(e) => println!(" Error fetching quote: {e}"), + } + println!(); + Ok(()) +} + +#[cfg(feature = "dataframe")] +async fn section_recommendations_df(ticker: &Ticker) -> Result<(), Box> { + println!("🧾 3. Analyst Recommendations to DataFrame"); + match ticker.recommendations().await { + Ok(recommendations) => { + if recommendations.is_empty() { + println!(" No recommendation data available"); + } else { + let df = recommendations.to_dataframe()?; + println!(" DataFrame shape: {:?}", df.shape()); + println!(" Recommendation data:\n{}", df.head(Some(5))); + } + } + Err(e) => println!(" Error fetching recommendations: {e}"), + } + println!(); + Ok(()) +} + +#[cfg(feature = "dataframe")] +async fn section_income_df(ticker: &Ticker) -> Result<(), Box> { + println!("💰 4. Financial Statements to DataFrame"); + match ticker.income_stmt(None).await { + Ok(financials) => { + if financials.is_empty() { + println!(" No financial data available"); + } else { + let df = financials.to_dataframe()?; + println!(" DataFrame shape: {:?}", df.shape()); + println!(" Income statement data:\n{}", df.head(Some(3))); + } + } + Err(e) => println!(" Error fetching financials: {e}"), + } + println!(); + Ok(()) +} + +#[cfg(feature = "dataframe")] +async fn section_esg(ticker: &Ticker) -> Result<(), Box> { + println!("🌱 5. ESG Scores"); + match ticker.sustainability().await { + Ok(summary) => { + if let Some(scores) = summary.scores { + println!(" Environmental: {:?}", scores.environmental); + println!(" Social: {:?}", scores.social); + println!(" Governance: {:?}", scores.governance); + } else { + println!(" No ESG component scores available"); + } + } + Err(e) => println!(" ESG data not available for this ticker: {e}"), + } + println!(); + Ok(()) +} + +#[cfg(feature = "dataframe")] +async fn section_holders_df(ticker: &Ticker) -> Result<(), Box> { + println!("🏛️ 6. Institutional Holders to DataFrame"); + match ticker.institutional_holders().await { + Ok(holders) => { + if holders.is_empty() { + println!(" No institutional holders data available"); + } else { + let df = holders.to_dataframe()?; + println!(" DataFrame shape: {:?}", df.shape()); + println!(" Top institutional holders:\n{}", df.head(Some(5))); + } + } + Err(e) => println!(" Institutional holders data not available: {e}"), + } + println!(); + Ok(()) +} + +#[cfg(feature = "dataframe")] +async fn section_analysis_df(ticker: &Ticker) -> Result<(), Box> { + println!("🔍 7. Simple Analysis with Polars"); + let history = ticker + .history(Some(Range::M6), Some(Interval::D1), false) + .await?; + if history.is_empty() { + println!(" No history for analysis."); + return Ok(()); + } + let df = history.to_dataframe()?; + + // Lazily compute a few stats + let lf = df.lazy(); + let stats = lf + .clone() + .select([ + col("close.amount").mean().alias("avg_close"), + col("close.amount").min().alias("min_close"), + col("close.amount").max().alias("max_close"), + col("volume").sum().alias("total_volume"), + ]) + .collect()?; + println!(" 6M Close/Volume Stats:\n{stats}"); + + let with_ma = lf + .sort(["ts"], SortMultipleOptions::default()) + .with_column( + col("close.amount") + .rolling_mean(RollingOptionsFixedWindow { + window_size: 5, + min_periods: 1, + ..Default::default() + }) + .alias("ma_5d"), + ) + .select([col("ts"), col("close.amount"), col("ma_5d"), col("volume")]) + .limit(10) + .collect()?; + println!(" First 10 rows with 5-day moving average:\n{with_ma}"); + Ok(()) +} + +#[cfg(not(feature = "dataframe"))] +fn main() { + println!("This example requires the 'dataframe' feature to be enabled."); + println!("Run with: cargo run --example 14_polars_dataframes --features dataframe"); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/justfile b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/justfile new file mode 100644 index 0000000..8427d2f --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/justfile @@ -0,0 +1,200 @@ +# 🧪 Test Runner Justfile +# Run `just` or `just help` to see this help. + +set shell := ["bash", "-cu"] +set dotenv-load := true +set export := true +# set quiet := true # optional: hide all command echoing + +# ---- Tunables --------------------------------------------------------------- + +FEATURES := 'test-mode,dataframe' # cargo features for tests +TEST_THREADS := '1' # default for live/record (override: just TEST_THREADS=4 live) +FIXDIR := '' # default when YF_FIXDIR isn't set in the env + +# ---- Helpers ---------------------------------------------------------------- + +banner MESSAGE: + @printf "\n\033[1m▶ %s\033[0m\n\n" "{{MESSAGE}}" + +vars: + @echo "FEATURES = {{FEATURES}}" + @echo "TEST_THREADS = {{TEST_THREADS}}" + @echo "YF_FIXDIR = ${YF_FIXDIR:-{{FIXDIR}}}" + @echo "YF_LIVE = ${YF_LIVE:-}" + @echo "YF_RECORD = ${YF_RECORD:-}" + +# ---- Recipes ---------------------------------------------------------------- + +default: help + +help: + @just --list --unsorted + +# NOTE on arg parsing: +# - If the first token looks like a test binary name (no leading `--`, no `::`), +# it's passed as `--test ` BEFORE `--`. +# - Everything else goes AFTER `--` to the harness. + +# Offline (replay cached fixtures) +test-offline +args='': + @just banner "Offline tests (cached fixtures)" + @set -euo pipefail; \ + TARGET_OPT=(); TEST_ARGS=(); \ + if [ -n "{{args}}" ]; then \ + set -- {{args}}; \ + first="${1:-}"; shift || true; \ + if [ -n "$first" ] && [[ "$first" != --* ]] && [[ "$first" != *::* ]]; then \ + TARGET_OPT=(--test "$first"); \ + TEST_ARGS=("$@"); \ + else \ + TEST_ARGS=("$first" "$@"); \ + fi; \ + fi; \ + cargo test --features {{FEATURES}} "${TARGET_OPT[@]+"${TARGET_OPT[@]}"}" -- "${TEST_ARGS[@]+"${TEST_ARGS[@]}"}" + +# Full live sweep (no writes; runs all tests including ignored) +test-live +args='': + @just banner "Live sweep (no writes, includes ignored)" + @set -euo pipefail; \ + TARGET_OPT=(); TEST_ARGS=(); \ + if [ -n "{{args}}" ]; then \ + set -- {{args}}; \ + first="${1:-}"; shift || true; \ + if [ -n "$first" ] && [[ "$first" != --* ]] && [[ "$first" != *::* ]]; then \ + TARGET_OPT=(--test "$first"); \ + TEST_ARGS=("$@"); \ + else \ + TEST_ARGS=("$first" "$@"); \ + fi; \ + fi; \ + YF_LIVE=1 cargo test --features {{FEATURES}} "${TARGET_OPT[@]+"${TARGET_OPT[@]}"}" -- --include-ignored --test-threads={{TEST_THREADS}} "${TEST_ARGS[@]+"${TEST_ARGS[@]}"}" + +# Record fixtures (live → cache) +test-record +args='': + @just banner "Recording fixtures (runs ignored tests)" + @set -euo pipefail; \ + TARGET_OPT=(); TEST_ARGS=(); \ + if [ -n "{{args}}" ]; then \ + set -- {{args}}; \ + first="${1:-}"; shift || true; \ + if [ -n "$first" ] && [[ "$first" != --* ]] && [[ "$first" != *::* ]]; then \ + TARGET_OPT=(--test "$first"); \ + TEST_ARGS=("$@"); \ + else \ + TEST_ARGS=("$first" "$@"); \ + fi; \ + fi; \ + YF_RECORD=1 cargo test --features {{FEATURES}} "${TARGET_OPT[@]+"${TARGET_OPT[@]}"}" -- --ignored --test-threads={{TEST_THREADS}} "${TEST_ARGS[@]+"${TEST_ARGS[@]}"}" + +# Use a different fixture directory, then replay +test-with-fixdir dir='/tmp/yf-fixtures' +args='': + @just banner "Recording to {{dir}} then replaying offline" + @set -euo pipefail; \ + TARGET_OPT=(); TEST_ARGS=(); \ + if [ -n "{{args}}" ]; then \ + set -- {{args}}; \ + first="${1:-}"; shift || true; \ + if [ -n "$first" ] && [[ "$first" != --* ]] && [[ "$first" != *::* ]]; then \ + TARGET_OPT=(--test "$first"); \ + TEST_ARGS=("$@"); \ + else \ + TEST_ARGS=("$first" "$@"); \ + fi; \ + fi; \ + export YF_FIXDIR="{{dir}}"; \ + YF_RECORD=1 cargo test --features {{FEATURES}} "${TARGET_OPT[@]+"${TARGET_OPT[@]}"}" -- --ignored --test-threads={{TEST_THREADS}} "${TEST_ARGS[@]+"${TEST_ARGS[@]}"}"; \ + cargo test --features {{FEATURES}} "${TARGET_OPT[@]+"${TARGET_OPT[@]}"}" -- "${TEST_ARGS[@]+"${TEST_ARGS[@]}"}" + +# Full test: clear phase markers; only run offline if live/record passes +test-full +args='': + @just banner "Full test (Phase 1: live/record → Phase 2: offline)" + @set -euo pipefail; \ + ts() { date '+%Y-%m-%d %H:%M:%S'; }; \ + TARGET_OPT=(); TEST_ARGS=(); \ + if [ -n "{{args}}" ]; then \ + set -- {{args}}; \ + first="${1:-}"; shift || true; \ + if [ -n "$first" ] && [[ "$first" != --* ]] && [[ "$first" != *::* ]]; then \ + TARGET_OPT=(--test "$first"); \ + TEST_ARGS=("$@"); \ + else \ + TEST_ARGS=("$first" "$@"); \ + fi; \ + fi; \ + echo "[$(ts)] 🟦 Phase 1/2 START — Live/Record (runs ignored, writes fixtures)"; \ + if YF_RECORD=1 cargo test --features {{FEATURES}} "${TARGET_OPT[@]+"${TARGET_OPT[@]}"}" -- --ignored --test-threads={{TEST_THREADS}} "${TEST_ARGS[@]+"${TEST_ARGS[@]}"}"; then \ + echo "[$(ts)] ✅ Phase 1/2 PASS — Live/Record passed"; \ + echo "[$(ts)] 🟩 Phase 2/2 START — Offline replay (cached fixtures)"; \ + if cargo test --features {{FEATURES}} "${TARGET_OPT[@]+"${TARGET_OPT[@]}"}" -- "${TEST_ARGS[@]+"${TEST_ARGS[@]}"}"; then \ + echo "[$(ts)] ✅ Phase 2/2 PASS — Offline replay passed"; \ + echo "[$(ts)] 🎉 Full test complete: BOTH phases passed"; \ + else \ + status=$?; \ + echo "[$(ts)] ❌ Phase 2/2 FAIL — Offline replay failed (exit $status)"; \ + echo "Tip: re-run only the offline pass with:"; \ + echo " just test-offline {{args}}"; \ + exit $status; \ + fi; \ + else \ + status=$?; \ + echo "[$(ts)] ❌ Phase 1/2 FAIL — Live/Record failed (exit $status)"; \ + echo "Skipping offline. Tip: re-run only the live/record pass with:"; \ + echo " just test-record {{args}}"; \ + exit $status; \ + fi + +test-full-debug +args='': + @just banner "Full test DEBUG (Phase 1: live/record → Phase 2: offline)" + @set -euo pipefail; \ + ts() { date '+%Y-%m-%d %H:%M:%S'; }; \ + TARGET_OPT=(); TEST_ARGS=(); \ + if [ -n "{{args}}" ]; then \ + set -- {{args}}; \ + first="${1:-}"; shift || true; \ + if [ -n "$first" ] && [[ "$first" != --* ]] && [[ "$first" != *::* ]]; then \ + TARGET_OPT=(--test "$first"); \ + TEST_ARGS=("$@"); \ + else \ + TEST_ARGS=("$first" "$@"); \ + fi; \ + fi; \ + echo "[$(ts)] 🟦 Phase 1/2 START — Live/Record DEBUG (runs ignored, writes fixtures)"; \ + if YF_DEBUG=1 YF_RECORD=1 cargo test --features {{FEATURES}} "${TARGET_OPT[@]+"${TARGET_OPT[@]}"}" -- --ignored --test-threads={{TEST_THREADS}} "${TEST_ARGS[@]+"${TEST_ARGS[@]}"}"; then \ + echo "[$(ts)] ✅ Phase 1/2 PASS — Live/Record passed"; \ + echo "[$(ts)] 🟩 Phase 2/2 START — Offline replay DEBUG (cached fixtures)"; \ + if YF_DEBUG=1 cargo test --features {{FEATURES}} "${TARGET_OPT[@]+"${TARGET_OPT[@]}"}" -- "${TEST_ARGS[@]+"${TEST_ARGS[@]}"}"; then \ + echo "[$(ts)] ✅ Phase 2/2 PASS — Offline replay passed"; \ + echo "[$(ts)] 🎉 Full debug test complete: BOTH phases passed"; \ + else \ + status=$?; \ + echo "[$(ts)] ❌ Phase 2/2 FAIL — Offline replay failed (exit $status)"; \ + echo "Tip: re-run only the offline pass with:"; \ + echo " just test-offline {{args}}"; \ + exit $status; \ + fi; \ + else \ + status=$?; \ + echo "[$(ts)] ❌ Phase 1/2 FAIL — Live/Record failed (exit $status)"; \ + echo "Skipping offline. Tip: re-run only the live/record pass with:"; \ + echo " just test-record {{args}}"; \ + exit $status; \ + fi + +test +args='': + @just banner "Alias: test → test-full" + just test-full {{args}} + +lint: + cargo clippy --workspace --all-targets --all-features -- \ + -W clippy::all -W clippy::cargo -W clippy::pedantic -W clippy::nursery -A clippy::multiple-crate-versions -D warnings + +# just lint-fix [optional flags...] +# Example: just lint-fix --allow-dirty +# just lint-fix --allow-dirty --allow-staged +lint-fix *FLAGS: + cargo clippy --workspace --all-targets --all-features --fix {{FLAGS}} -- \ + -W clippy::all -W clippy::cargo -W clippy::pedantic -W clippy::nursery -A clippy::multiple-crate-versions -D warnings + +fmt: + cargo fmt --all \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/analysis/api.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/analysis/api.rs new file mode 100644 index 0000000..14dd370 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/analysis/api.rs @@ -0,0 +1,361 @@ +use crate::{ + analysis::model::EarningsTrendRow, + core::{ + YfClient, YfError, + client::{CacheMode, RetryConfig}, + conversions::{ + f64_to_money_with_currency, i64_to_datetime, i64_to_money_with_currency, + string_to_period, string_to_recommendation_action, string_to_recommendation_grade, + }, + wire::{from_raw, from_raw_u32_round}, + }, +}; + +use super::fetch::fetch_modules; +use super::model::{PriceTarget, RecommendationRow, RecommendationSummary, UpgradeDowngradeRow}; +use chrono::DateTime; +use paft::fundamentals::analysis::{ + EarningsEstimate, EpsRevisions, EpsTrend, RevenueEstimate, RevisionPoint, TrendPoint, +}; +use paft::money::Currency; +// Period is available via prelude or directly; we use string_to_period for parsing, so import not needed + +/* ---------- Public entry points (mapping wire → public models) ---------- */ + +pub(super) async fn recommendation_trend( + client: &YfClient, + symbol: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result, YfError> { + let root = fetch_modules( + client, + symbol, + "recommendationTrend", + cache_mode, + retry_override, + ) + .await?; + + let trend = root + .recommendation_trend + .and_then(|x| x.trend) + .unwrap_or_default(); + + let rows = trend + .into_iter() + .map(|n| RecommendationRow { + period: string_to_period(&n.period.unwrap_or_default()), + strong_buy: n.strong_buy.and_then(|v| u32::try_from(v).ok()), + buy: n.buy.and_then(|v| u32::try_from(v).ok()), + hold: n.hold.and_then(|v| u32::try_from(v).ok()), + sell: n.sell.and_then(|v| u32::try_from(v).ok()), + strong_sell: n.strong_sell.and_then(|v| u32::try_from(v).ok()), + }) + .collect(); + + Ok(rows) +} + +pub(super) async fn recommendation_summary( + client: &YfClient, + symbol: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result { + let root = fetch_modules( + client, + symbol, + "recommendationTrend,financialData", + cache_mode, + retry_override, + ) + .await?; + + let trend = root + .recommendation_trend + .and_then(|x| x.trend) + .unwrap_or_default(); + + let latest = trend.first(); + + let (latest_period, sb, b, h, s, ss) = + latest.map_or((None, None, None, None, None, None), |t| { + ( + Some(string_to_period(&t.period.clone().unwrap_or_default())), + t.strong_buy.and_then(|v| u32::try_from(v).ok()), + t.buy.and_then(|v| u32::try_from(v).ok()), + t.hold.and_then(|v| u32::try_from(v).ok()), + t.sell.and_then(|v| u32::try_from(v).ok()), + t.strong_sell.and_then(|v| u32::try_from(v).ok()), + ) + }); + + let (mean, _mean_key) = root.financial_data.map_or((None, None), |fd| { + (from_raw(fd.recommendation_mean), fd.recommendation_key) + }); + + Ok(RecommendationSummary { + latest_period, + strong_buy: sb, + buy: b, + hold: h, + sell: s, + strong_sell: ss, + mean, + mean_rating_text: None, + }) +} + +pub(super) async fn upgrades_downgrades( + client: &YfClient, + symbol: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result, YfError> { + let root = fetch_modules( + client, + symbol, + "upgradeDowngradeHistory", + cache_mode, + retry_override, + ) + .await?; + + let hist = root + .upgrade_downgrade_history + .and_then(|x| x.history) + .unwrap_or_default(); + + let mut rows: Vec = hist + .into_iter() + .map(|h| UpgradeDowngradeRow { + ts: h.epoch_grade_date.map_or_else( + || DateTime::from_timestamp(0, 0).unwrap_or_default(), + i64_to_datetime, + ), + firm: h.firm, + from_grade: h.from_grade.as_deref().map(string_to_recommendation_grade), + to_grade: h.to_grade.as_deref().map(string_to_recommendation_grade), + action: h + .action + .or(h.grade_change) + .as_deref() + .map(string_to_recommendation_action), + }) + .collect(); + + rows.sort_by_key(|r| r.ts); + Ok(rows) +} + +pub(super) async fn analyst_price_target( + client: &YfClient, + symbol: &str, + currency: Currency, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result { + let root = fetch_modules(client, symbol, "financialData", cache_mode, retry_override).await?; + let fd = root + .financial_data + .ok_or_else(|| YfError::MissingData("financialData missing".into()))?; + + Ok(PriceTarget { + mean: from_raw(fd.target_mean_price) + .map(|v| f64_to_money_with_currency(v, currency.clone())), + high: from_raw(fd.target_high_price) + .map(|v| f64_to_money_with_currency(v, currency.clone())), + low: from_raw(fd.target_low_price).map(|v| f64_to_money_with_currency(v, currency.clone())), + number_of_analysts: from_raw_u32_round(fd.number_of_analyst_opinions), + }) +} + +#[allow(clippy::too_many_lines)] +pub(super) async fn earnings_trend( + client: &YfClient, + symbol: &str, + currency: Currency, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result, YfError> { + let root = fetch_modules(client, symbol, "earningsTrend", cache_mode, retry_override).await?; + + let trend = root + .earnings_trend + .and_then(|x| x.trend) + .unwrap_or_default(); + + let rows = trend + .into_iter() + .map(|n| { + let ( + earnings_estimate_avg, + earnings_estimate_low, + earnings_estimate_high, + earnings_estimate_year_ago_eps, + earnings_estimate_num_analysts, + earnings_estimate_growth, + ) = n + .earnings_estimate + .map(|e| { + ( + from_raw(e.avg), + from_raw(e.low), + from_raw(e.high), + from_raw(e.year_ago_eps), + from_raw_u32_round(e.num_analysts), + from_raw(e.growth), + ) + }) + .unwrap_or_default(); + + let ( + revenue_estimate_avg, + revenue_estimate_low, + revenue_estimate_high, + revenue_estimate_year_ago_revenue, + revenue_estimate_num_analysts, + revenue_estimate_growth, + ) = n + .revenue_estimate + .map(|e| { + ( + from_raw(e.avg), + from_raw(e.low), + from_raw(e.high), + from_raw(e.year_ago_revenue), + from_raw_u32_round(e.num_analysts), + from_raw(e.growth), + ) + }) + .unwrap_or_default(); + + let ( + eps_trend_current, + eps_trend_7_days_ago, + eps_trend_30_days_ago, + eps_trend_60_days_ago, + eps_trend_90_days_ago, + ) = n + .eps_trend + .map(|e| { + ( + from_raw(e.current), + from_raw(e.seven_days_ago), + from_raw(e.thirty_days_ago), + from_raw(e.sixty_days_ago), + from_raw(e.ninety_days_ago), + ) + }) + .unwrap_or_default(); + + let ( + eps_revisions_up_last_7_days, + eps_revisions_up_last_30_days, + eps_revisions_down_last_7_days, + eps_revisions_down_last_30_days, + ) = n + .eps_revisions + .map(|e| { + ( + from_raw_u32_round(e.up_last_7_days), + from_raw_u32_round(e.up_last_30_days), + from_raw_u32_round(e.down_last_7_days), + from_raw_u32_round(e.down_last_30_days), + ) + }) + .unwrap_or_default(); + + EarningsTrendRow { + period: string_to_period(&n.period.unwrap_or_default()), + growth: from_raw(n.growth), + earnings_estimate: EarningsEstimate { + avg: earnings_estimate_avg + .map(|v| f64_to_money_with_currency(v, currency.clone())), + low: earnings_estimate_low + .map(|v| f64_to_money_with_currency(v, currency.clone())), + high: earnings_estimate_high + .map(|v| f64_to_money_with_currency(v, currency.clone())), + year_ago_eps: earnings_estimate_year_ago_eps + .map(|v| f64_to_money_with_currency(v, currency.clone())), + num_analysts: earnings_estimate_num_analysts, + growth: earnings_estimate_growth, + }, + revenue_estimate: RevenueEstimate { + avg: revenue_estimate_avg + .map(|v| i64_to_money_with_currency(v, currency.clone())), + low: revenue_estimate_low + .map(|v| i64_to_money_with_currency(v, currency.clone())), + high: revenue_estimate_high + .map(|v| i64_to_money_with_currency(v, currency.clone())), + year_ago_revenue: revenue_estimate_year_ago_revenue + .map(|v| i64_to_money_with_currency(v, currency.clone())), + num_analysts: revenue_estimate_num_analysts, + growth: revenue_estimate_growth, + }, + eps_trend: EpsTrend { + current: eps_trend_current + .map(|v| f64_to_money_with_currency(v, currency.clone())), + historical: { + let mut hist = Vec::new(); + if let Some(v) = eps_trend_7_days_ago + && let Ok(tp) = TrendPoint::try_new_str( + "7d", + f64_to_money_with_currency(v, currency.clone()), + ) + { + hist.push(tp); + } + if let Some(v) = eps_trend_30_days_ago + && let Ok(tp) = TrendPoint::try_new_str( + "30d", + f64_to_money_with_currency(v, currency.clone()), + ) + { + hist.push(tp); + } + if let Some(v) = eps_trend_60_days_ago + && let Ok(tp) = TrendPoint::try_new_str( + "60d", + f64_to_money_with_currency(v, currency.clone()), + ) + { + hist.push(tp); + } + if let Some(v) = eps_trend_90_days_ago + && let Ok(tp) = TrendPoint::try_new_str( + "90d", + f64_to_money_with_currency(v, currency.clone()), + ) + { + hist.push(tp); + } + hist + }, + }, + eps_revisions: EpsRevisions { + historical: { + let mut hist = Vec::new(); + if let (Some(up), Some(down)) = + (eps_revisions_up_last_7_days, eps_revisions_down_last_7_days) + && let Ok(rp) = RevisionPoint::try_new_str("7d", up, down) + { + hist.push(rp); + } + if let (Some(up), Some(down)) = ( + eps_revisions_up_last_30_days, + eps_revisions_down_last_30_days, + ) && let Ok(rp) = RevisionPoint::try_new_str("30d", up, down) + { + hist.push(rp); + } + hist + }, + }, + } + }) + .collect(); + + Ok(rows) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/analysis/fetch.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/analysis/fetch.rs new file mode 100644 index 0000000..508c736 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/analysis/fetch.rs @@ -0,0 +1,24 @@ +use super::wire::V10Result; +use crate::core::{ + YfClient, YfError, + client::{CacheMode, RetryConfig}, + quotesummary, +}; + +pub(super) async fn fetch_modules( + client: &YfClient, + symbol: &str, + modules: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result { + quotesummary::fetch_module_result( + client, + symbol, + modules, + "analysis", + cache_mode, + retry_override, + ) + .await +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/analysis/mod.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/analysis/mod.rs new file mode 100644 index 0000000..c97ab7f --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/analysis/mod.rs @@ -0,0 +1,149 @@ +mod api; +mod model; + +mod fetch; +mod wire; + +pub use model::{ + EarningsTrendRow, PriceTarget, RecommendationRow, RecommendationSummary, UpgradeDowngradeRow, +}; + +use crate::core::{ + YfClient, YfError, + client::{CacheMode, RetryConfig}, +}; +use paft::money::Currency; + +/// A builder for fetching analyst-related data for a specific symbol. +pub struct AnalysisBuilder { + client: YfClient, + symbol: String, + cache_mode: CacheMode, + retry_override: Option, +} + +impl AnalysisBuilder { + /// Creates a new `AnalysisBuilder` for a given symbol. + pub fn new(client: &YfClient, symbol: impl Into) -> Self { + Self { + client: client.clone(), + symbol: symbol.into(), + cache_mode: CacheMode::Use, + retry_override: None, + } + } + + /// Sets the cache mode for this specific API call. + #[must_use] + pub const fn cache_mode(mut self, mode: CacheMode) -> Self { + self.cache_mode = mode; + self + } + + /// Overrides the default retry policy for this specific API call. + #[must_use] + pub fn retry_policy(mut self, cfg: Option) -> Self { + self.retry_override = cfg; + self + } + + /// Fetches the analyst recommendation trend over time. + /// + /// # Errors + /// + /// Returns an error if the request fails or the data is malformed. + pub async fn recommendations(self) -> Result, YfError> { + api::recommendation_trend( + &self.client, + &self.symbol, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } + + /// Fetches a summary of the latest analyst recommendations. + /// + /// # Errors + /// + /// Returns an error if the request fails or the data is malformed. + pub async fn recommendations_summary(self) -> Result { + api::recommendation_summary( + &self.client, + &self.symbol, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } + + /// Fetches the history of analyst upgrades and downgrades for the symbol. + /// + /// # Errors + /// + /// Returns an error if the request fails or the data is malformed. + pub async fn upgrades_downgrades(self) -> Result, YfError> { + api::upgrades_downgrades( + &self.client, + &self.symbol, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } + + /// Fetches the analyst price target summary. + /// + /// Provide `Some(currency)` to override the inferred reporting currency; pass `None` + /// to use the cached profile-based heuristic. + /// + /// # Errors + /// + /// Returns an error if the request fails or the data is malformed. + pub async fn analyst_price_target( + self, + override_currency: Option, + ) -> Result { + let currency = self + .client + .reporting_currency(&self.symbol, override_currency) + .await; + + api::analyst_price_target( + &self.client, + &self.symbol, + currency, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } + + /// Fetches earnings trend data. + /// + /// This includes earnings estimates, revenue estimates, EPS trends, and EPS revisions. + /// Provide `Some(currency)` to override the inferred reporting currency; pass `None` + /// to use the cached profile-based heuristic. + /// + /// # Errors + /// + /// Returns an error if the request fails or the data is malformed. + pub async fn earnings_trend( + self, + override_currency: Option, + ) -> Result, YfError> { + let currency = self + .client + .reporting_currency(&self.symbol, override_currency) + .await; + + api::earnings_trend( + &self.client, + &self.symbol, + currency, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/analysis/model.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/analysis/model.rs new file mode 100644 index 0000000..c791283 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/analysis/model.rs @@ -0,0 +1,4 @@ +// Re-export types from paft without using prelude +pub use paft::fundamentals::analysis::{ + EarningsTrendRow, PriceTarget, RecommendationRow, RecommendationSummary, UpgradeDowngradeRow, +}; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/analysis/wire.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/analysis/wire.rs new file mode 100644 index 0000000..81b86b7 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/analysis/wire.rs @@ -0,0 +1,153 @@ +use serde::Deserialize; + +use crate::core::wire::RawNum; + +/* ---------------- Serde mapping (only what we need) ---------------- */ + +#[derive(Deserialize)] +pub struct V10Result { + #[serde(rename = "recommendationTrend")] + pub(crate) recommendation_trend: Option, + + #[serde(rename = "upgradeDowngradeHistory")] + pub(crate) upgrade_downgrade_history: Option, + + #[serde(rename = "financialData")] + pub(crate) financial_data: Option, + + #[serde(rename = "earningsTrend")] + pub(crate) earnings_trend: Option, +} + +/* --- recommendation trend --- */ + +#[derive(Deserialize)] +pub struct RecommendationTrendNode { + pub(crate) trend: Option>, +} + +#[derive(Deserialize)] +pub struct RecommendationNode { + pub(crate) period: Option, + + #[serde(rename = "strongBuy")] + pub(crate) strong_buy: Option, + pub(crate) buy: Option, + pub(crate) hold: Option, + pub(crate) sell: Option, + + #[serde(rename = "strongSell")] + pub(crate) strong_sell: Option, +} + +/* --- upgrades / downgrades --- */ + +#[derive(Deserialize)] +pub struct UpgradeDowngradeHistoryNode { + pub(crate) history: Option>, +} + +#[derive(Deserialize)] +pub struct UpgradeNode { + #[serde(rename = "epochGradeDate")] + pub(crate) epoch_grade_date: Option, + + pub(crate) firm: Option, + + #[serde(rename = "toGrade")] + pub(crate) to_grade: Option, + + #[serde(rename = "fromGrade")] + pub(crate) from_grade: Option, + + pub(crate) action: Option, + #[serde(rename = "gradeChange")] + pub(crate) grade_change: Option, +} + +/* --- financial data (price targets) --- */ + +#[derive(Deserialize)] +pub struct FinancialDataNode { + #[serde(rename = "targetMeanPrice")] + pub(crate) target_mean_price: Option>, + #[serde(rename = "targetHighPrice")] + pub(crate) target_high_price: Option>, + #[serde(rename = "targetLowPrice")] + pub(crate) target_low_price: Option>, + #[serde(rename = "numberOfAnalystOpinions")] + pub(crate) number_of_analyst_opinions: Option>, + #[serde(rename = "recommendationMean")] + pub(crate) recommendation_mean: Option>, + #[serde(rename = "recommendationKey")] + pub(crate) recommendation_key: Option, +} + +#[derive(Deserialize)] +pub struct EarningsTrendNode { + pub(crate) trend: Option>, +} + +#[derive(Deserialize)] +pub struct EarningsTrendItemNode { + pub(crate) period: Option, + pub(crate) growth: Option>, + #[serde(rename = "earningsEstimate")] + pub(crate) earnings_estimate: Option, + #[serde(rename = "revenueEstimate")] + pub(crate) revenue_estimate: Option, + #[serde(rename = "epsTrend")] + pub(crate) eps_trend: Option, + #[serde(rename = "epsRevisions")] + pub(crate) eps_revisions: Option, +} + +#[derive(Deserialize)] +pub struct EarningsEstimateNode { + pub(crate) avg: Option>, + pub(crate) low: Option>, + pub(crate) high: Option>, + #[serde(rename = "yearAgoEps")] + pub(crate) year_ago_eps: Option>, + #[serde(rename = "numberOfAnalysts")] + pub(crate) num_analysts: Option>, + pub(crate) growth: Option>, +} + +#[derive(Deserialize)] +pub struct RevenueEstimateNode { + pub(crate) avg: Option>, + pub(crate) low: Option>, + pub(crate) high: Option>, + #[serde(rename = "yearAgoRevenue")] + pub(crate) year_ago_revenue: Option>, + #[serde(rename = "numberOfAnalysts")] + pub(crate) num_analysts: Option>, + pub(crate) growth: Option>, +} + +#[derive(Deserialize)] +pub struct EpsTrendNode { + pub(crate) current: Option>, + #[serde(rename = "7daysAgo")] + pub(crate) seven_days_ago: Option>, + #[serde(rename = "30daysAgo")] + pub(crate) thirty_days_ago: Option>, + #[serde(rename = "60daysAgo")] + pub(crate) sixty_days_ago: Option>, + #[serde(rename = "90daysAgo")] + pub(crate) ninety_days_ago: Option>, +} + +#[derive(Deserialize)] +#[allow(clippy::struct_field_names)] +pub struct EpsRevisionsNode { + #[serde(rename = "upLast7days")] + pub(crate) up_last_7_days: Option>, + #[serde(rename = "upLast30days")] + pub(crate) up_last_30_days: Option>, + #[serde(rename = "downLast7days")] + pub(crate) down_last_7_days: Option>, + #[serde(rename = "downLast30days")] + pub(crate) down_last_30_days: Option>, +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/client/auth.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/client/auth.rs new file mode 100644 index 0000000..92c401e --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/client/auth.rs @@ -0,0 +1,73 @@ +//! Cookie & crumb acquisition for Yahoo endpoints. + +use crate::core::error::YfError; +use reqwest::header::SET_COOKIE; + +impl super::YfClient { + pub(crate) async fn ensure_credentials(&self) -> Result<(), YfError> { + // Fast path: check if credentials exist with a read lock. + if self.state.read().await.crumb.is_some() { + return Ok(()); + } + + // Slow path: acquire the dedicated fetch lock to ensure only one task proceeds. + let _guard = self.credential_fetch_lock.lock().await; + + // Double-check: another task might have fetched credentials while this one was waiting. + if self.state.read().await.crumb.is_some() { + return Ok(()); + } + + // With the lock held, we can safely perform the network operations. + self.get_cookie().await?; + self.get_crumb_internal().await?; + + Ok(()) + } + + pub(crate) async fn clear_crumb(&self) { + let mut state = self.state.write().await; + state.crumb = None; + } + + pub(crate) async fn crumb(&self) -> Option { + let state = self.state.read().await; + state.crumb.clone() + } + + async fn get_cookie(&self) -> Result<(), YfError> { + let req = self.http.get(self.cookie_url.clone()); + let resp = self.send_with_retry(req, None).await?; + + let cookie = resp + .headers() + .get(SET_COOKIE) + .ok_or(YfError::Auth("No cookie received from fc.yahoo.com".into()))? + .to_str() + .map_err(|_| YfError::Auth("Invalid cookie header format".into()))? + .to_string(); + + self.state.write().await.cookie = Some(cookie); + Ok(()) + } + + async fn get_crumb_internal(&self) -> Result<(), YfError> { + let state = self.state.read().await; + if state.cookie.is_none() { + return Err(YfError::Auth("Cookie is missing, cannot get crumb".into())); + } + drop(state); // release read lock before making http call + + let url = self.crumb_url.clone(); + let req = self.http.get(url); + let resp = self.send_with_retry(req, None).await?; + let crumb = resp.text().await?; + + if crumb.is_empty() || crumb.contains('{') || crumb.contains('<') { + return Err(YfError::Auth(format!("Received invalid crumb: {crumb}"))); + } + + self.state.write().await.crumb = Some(crumb); + Ok(()) + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/client/constants.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/client/constants.rs new file mode 100644 index 0000000..3420f11 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/client/constants.rs @@ -0,0 +1,44 @@ +//! Centralized constants for default endpoints and UA. + +/// Default desktop UA to avoid trivial bot blocking. +pub const USER_AGENT: &str = concat!( + "Mozilla/5.0 (X11; Linux x86_64) ", + "AppleWebKit/537.36 (KHTML, like Gecko) ", + "Chrome/122.0.0.0 Safari/537.36" +); + +/// Yahoo chart API base (symbol is appended). +pub const DEFAULT_BASE_CHART: &str = "https://query1.finance.yahoo.com/v8/finance/chart/"; + +/// Yahoo quote HTML base (symbol is appended). +pub const DEFAULT_BASE_QUOTE: &str = "https://finance.yahoo.com/quote/"; + +/// Yahoo quoteSummary API base (symbol is appended). +pub const DEFAULT_BASE_QUOTE_API: &str = + "https://query1.finance.yahoo.com/v10/finance/quoteSummary/"; + +/// A URL that returns a Set-Cookie header for Yahoo domains. +pub const DEFAULT_COOKIE_URL: &str = "https://fc.yahoo.com/consent"; + +/// URL to fetch a crumb (requires cookie from `DEFAULT_COOKIE_URL`). +pub const DEFAULT_CRUMB_URL: &str = "https://query1.finance.yahoo.com/v1/test/getcrumb"; + +/// Base URL for the Yahoo Finance v7 quote API. +pub const DEFAULT_BASE_QUOTE_V7: &str = "https://query1.finance.yahoo.com/v7/finance/quote"; + +/// Base URL for the Yahoo Finance v7 options API. +pub const DEFAULT_BASE_OPTIONS_V7: &str = "https://query1.finance.yahoo.com/v7/finance/options/"; + +/// Base URL for the Yahoo Finance search API. +pub const DEFAULT_BASE_STREAM: &str = "wss://streamer.finance.yahoo.com/?version=2"; + +/// Base URL for the Business Insider search API (for ISIN lookup). +pub const DEFAULT_BASE_INSIDER_SEARCH: &str = + "https://markets.businessinsider.com/ajax/SearchController_Suggest"; + +/// Base URL for Yahoo Finance site (used for news). +pub const DEFAULT_BASE_NEWS: &str = "https://finance.yahoo.com"; + +/// Base URL for the Yahoo Finance timeseries API. +pub const DEFAULT_BASE_TIMESERIES: &str = + "https://query2.finance.yahoo.com/ws/fundamentals-timeseries/v1/finance/timeseries/"; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/client/mod.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/client/mod.rs new file mode 100644 index 0000000..edf1e94 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/client/mod.rs @@ -0,0 +1,913 @@ +//! Public client surface + builder. +//! Internals are split into `auth` (cookie/crumb) and `constants` (UA + defaults). + +mod auth; +mod constants; +mod retry; + +use crate::core::YfError; +use crate::core::client::constants::DEFAULT_BASE_INSIDER_SEARCH; +use crate::core::currency::currency_for_country; +use paft::money::{Currency, IsoCurrency}; +pub use retry::{Backoff, CacheMode, RetryConfig}; + +use constants::{ + DEFAULT_BASE_CHART, DEFAULT_BASE_QUOTE, DEFAULT_BASE_QUOTE_API, DEFAULT_COOKIE_URL, + DEFAULT_CRUMB_URL, USER_AGENT, +}; +use reqwest::Client; +use std::collections::HashMap; +use std::sync::Arc; +use std::time::{Duration, Instant}; +use tokio::sync::RwLock; +use url::Url; + +/// Defines the preferred data source for profile lookups when testing. +/// +/// This enum is always available for API compatibility, but only has effect when +/// the `test-mode` feature is enabled. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ApiPreference { + /// Try the API first, then fall back to scraping if the API fails. (Default) + ApiThenScrape, + /// Use only the `quoteSummary` API. + ApiOnly, + /// Use only the HTML scraping method. + ScrapeOnly, +} + +#[derive(Debug)] +struct CacheEntry { + body: String, + expires_at: Instant, +} + +#[derive(Debug)] +struct CacheStore { + map: RwLock>, + default_ttl: Duration, +} + +#[derive(Debug, Default)] +struct ClientState { + cookie: Option, + crumb: Option, +} + +/// The main asynchronous client for interacting with the Yahoo Finance API. +/// +/// The client manages an HTTP client, authentication (cookies and crumbs), +/// caching, and retry logic. It is cloneable and designed to be shared +/// across multiple tasks. +/// +/// Create a client using [`YfClient::builder()`] or [`YfClient::default()`]. +#[derive(Debug, Clone)] +pub struct YfClient { + http: Client, + base_chart: Url, + base_quote: Url, + base_quote_api: Url, + base_quote_v7: Url, + base_options_v7: Url, + base_stream: Url, + base_news: Url, + base_insider_search: Url, + base_timeseries: Url, + cookie_url: Url, + crumb_url: Url, + user_agent: String, + + state: Arc>, + credential_fetch_lock: Arc>, + + #[cfg(feature = "test-mode")] + api_preference: ApiPreference, + + retry: RetryConfig, + reporting_currency_cache: Arc>>, + // Cache of resolved instruments by original ticker string + instrument_cache: Arc>>, + cache: Option>, +} + +impl Default for YfClient { + fn default() -> Self { + Self::builder().build().expect("default client") + } +} + +impl YfClient { + /// Creates a new builder for a `YfClient`. + #[must_use] + pub fn builder() -> YfClientBuilder { + YfClientBuilder::default() + } + + /* -------- internal getters used by other modules -------- */ + + pub(crate) const fn http(&self) -> &Client { + &self.http + } + + pub(crate) fn user_agent(&self) -> &str { + &self.user_agent + } + + pub(crate) const fn base_chart(&self) -> &Url { + &self.base_chart + } + + pub(crate) const fn base_quote(&self) -> &Url { + &self.base_quote + } + pub(crate) const fn base_quote_api(&self) -> &Url { + &self.base_quote_api + } + + pub(crate) const fn base_quote_v7(&self) -> &Url { + &self.base_quote_v7 + } + + pub(crate) const fn base_options_v7(&self) -> &Url { + &self.base_options_v7 + } + + pub(crate) const fn base_stream(&self) -> &Url { + &self.base_stream + } + + pub(crate) const fn base_news(&self) -> &Url { + &self.base_news + } + + pub(crate) const fn base_insider_search(&self) -> &Url { + &self.base_insider_search + } + + pub(crate) const fn base_timeseries(&self) -> &Url { + &self.base_timeseries + } + + #[cfg(feature = "test-mode")] + pub(crate) const fn api_preference(&self) -> ApiPreference { + self.api_preference + } + + /// Returns `true` if in-memory caching is enabled for this client. + #[must_use] + pub const fn cache_enabled(&self) -> bool { + self.cache.is_some() + } + + pub(crate) async fn cache_get(&self, url: &Url) -> Option { + let store = self.cache.as_ref()?; + let key = url.as_str().to_string(); + if let Some(entry) = store.map.read().await.get(&key) + && Instant::now() <= entry.expires_at + { + return Some(entry.body.clone()); + } + None + } + + pub(crate) async fn cache_put(&self, url: &Url, body: &str, ttl_override: Option) { + let store = match &self.cache { + Some(s) => s.clone(), + None => return, + }; + let key = url.as_str().to_string(); + let ttl = ttl_override.unwrap_or(store.default_ttl); + let expires_at = Instant::now() + ttl; + let entry = CacheEntry { + body: body.to_string(), + expires_at, + }; + let mut guard = store.map.write().await; + guard.insert(key, entry); + } + + // -------- instrument cache (async) -------- + pub(crate) async fn cached_instrument(&self, key: &str) -> Option { + let guard = self.instrument_cache.read().await; + guard.get(key).cloned() + } + + pub(crate) async fn store_instrument(&self, key: String, inst: paft::domain::Instrument) { + let mut guard = self.instrument_cache.write().await; + guard.insert(key, inst); + } + + /// Clears the entire in-memory cache. + /// + /// This is an asynchronous operation that will acquire a write lock on the cache. + /// It does nothing if caching is disabled for the client. + pub async fn clear_cache(&self) { + if let Some(store) = &self.cache { + let mut guard = store.map.write().await; + guard.clear(); + } + } + + /// Removes a specific URL-based entry from the in-memory cache. + /// + /// This is useful if you know that the data for a specific request has become stale. + /// It does nothing if caching is disabled for the client. + pub async fn invalidate_cache_entry(&self, url: &Url) { + if let Some(store) = &self.cache { + let key = url.as_str().to_string(); + let mut guard = store.map.write().await; + guard.remove(&key); + } + } + + async fn cached_reporting_currency(&self, symbol: &str) -> Option { + let guard = self.reporting_currency_cache.read().await; + guard.get(symbol).cloned() + } + + async fn store_reporting_currency(&self, symbol: &str, currency: Currency) { + let mut guard = self.reporting_currency_cache.write().await; + guard.insert(symbol.to_string(), currency); + } + + /// Returns the cached or inferred reporting currency for a symbol. + pub(crate) async fn reporting_currency( + &self, + symbol: &str, + override_currency: Option, + ) -> Currency { + if let Some(currency) = override_currency { + self.store_reporting_currency(symbol, currency.clone()) + .await; + return currency; + } + + if let Some(currency) = self.cached_reporting_currency(symbol).await { + return currency; + } + + let mut debug_reason: Option = None; + let currency = match crate::profile::load_profile(self, symbol).await { + Ok(profile) => extract_currency_from_profile(&profile).map_or_else( + || { + debug_reason = Some("profile missing country or unsupported currency".into()); + Currency::Iso(IsoCurrency::USD) + }, + |currency| currency, + ), + Err(err) => { + debug_reason = Some(format!("failed to load profile: {err}")); + Currency::Iso(IsoCurrency::USD) + } + }; + + if let Some(reason) = + debug_reason.filter(|_| std::env::var("YF_DEBUG").ok().as_deref() == Some("1")) + { + eprintln!( + "YF_DEBUG(currency): {symbol} -> {reason}; using {}", + currency.code() + ); + } + + self.store_reporting_currency(symbol, currency.clone()) + .await; + currency + } + + #[cfg_attr( + feature = "tracing", + tracing::instrument( + skip(self, req, override_retry), + err, + fields( + url = %{ + let b = req + .try_clone() + .expect("cloneable") + .build() + .unwrap(); + b.url().clone() + } + ) + ) + )] + pub(crate) async fn send_with_retry( + &self, + mut req: reqwest::RequestBuilder, + override_retry: Option<&RetryConfig>, + ) -> Result { + // Always set User-Agent header explicitly + req = req.header("User-Agent", &self.user_agent); + + let cfg = override_retry.unwrap_or(&self.retry); + if !cfg.enabled { + return req.send().await; + } + + let mut attempt = 0u32; + loop { + let response = req.try_clone().expect("cloneable request").send().await; + + match response { + Ok(resp) => { + let code = resp.status().as_u16(); + if cfg.retry_on_status.contains(&code) && attempt < cfg.max_retries { + #[cfg(feature = "tracing")] + { + let backoff = compute_backoff_duration(&cfg.backoff, attempt); + tracing::event!( + tracing::Level::INFO, + attempt, + backoff_ms = backoff.as_secs_f64() * 1000.0, + status = code, + "retrying after status" + ); + tokio::time::sleep(backoff).await; + } + #[cfg(not(feature = "tracing"))] + { + sleep_backoff(&cfg.backoff, attempt).await; + } + attempt += 1; + continue; + } + return Ok(resp); + } + Err(e) => { + let should_retry = (cfg.retry_on_timeout && e.is_timeout()) + || (cfg.retry_on_connect && e.is_connect()); + + if should_retry && attempt < cfg.max_retries { + #[cfg(feature = "tracing")] + { + let backoff = compute_backoff_duration(&cfg.backoff, attempt); + tracing::event!( + tracing::Level::INFO, + attempt, + backoff_ms = backoff.as_secs_f64() * 1000.0, + error = %e, + timeout = e.is_timeout(), + connect = e.is_connect(), + "retrying after error" + ); + tokio::time::sleep(backoff).await; + } + #[cfg(not(feature = "tracing"))] + { + sleep_backoff(&cfg.backoff, attempt).await; + } + attempt += 1; + continue; + } + return Err(e); + } + } + } + } + + /// Returns a reference to the default `RetryConfig` for this client. + /// + /// This config is used for all requests unless overridden on a per-call basis. + #[must_use] + pub const fn retry_config(&self) -> &RetryConfig { + &self.retry + } +} + +/* ----------------------- Builder ----------------------- */ + +/// A builder for creating and configuring a [`YfClient`]. +#[derive(Default)] +pub struct YfClientBuilder { + user_agent: Option, + base_chart: Option, + base_quote: Option, + base_quote_api: Option, + base_quote_v7: Option, + base_options_v7: Option, + base_stream: Option, + base_news: Option, + base_insider_search: Option, + base_timeseries: Option, + cookie_url: Option, + crumb_url: Option, + + #[allow(dead_code)] + api_preference: Option, + #[allow(dead_code)] + preauth_cookie: Option, + #[allow(dead_code)] + preauth_crumb: Option, + + timeout: Option, + connect_timeout: Option, + retry: Option, + cache_ttl: Option, + + // New fields for custom client and proxy configuration + custom_client: Option, + proxy: Option, +} + +fn extract_currency_from_profile(profile: &crate::profile::Profile) -> Option { + match profile { + crate::profile::Profile::Company(company) => company + .address + .as_ref() + .and_then(|addr| addr.country.as_deref()) + .and_then(currency_for_country), + crate::profile::Profile::Fund(_) => None, + } +} + +impl YfClientBuilder { + /// Sets the `User-Agent` header for all HTTP requests and WebSocket connections. + /// + /// The user agent is applied consistently across all request types: + /// - HTTP requests (quotes, history, fundamentals, etc.) + /// - WebSocket streaming connections + /// - Authentication requests (cookies, crumbs) + /// + /// Defaults to a common desktop browser User-Agent to avoid being blocked. + /// This setting is applied per-request rather than at the HTTP client level. + #[must_use] + pub fn user_agent(mut self, ua: impl Into) -> Self { + self.user_agent = Some(ua.into()); + self + } + + /// Overrides the base URL for quote HTML pages (used for scraping). + /// Default: `https://finance.yahoo.com/quote/`. + #[must_use] + pub fn base_quote(mut self, url: Url) -> Self { + self.base_quote = Some(url); + self + } + + /// Overrides the base URL for the chart API (used for historical data). + /// Default: `https://query1.finance.yahoo.com/v8/finance/chart/`. + #[must_use] + pub fn base_chart(mut self, url: Url) -> Self { + self.base_chart = Some(url); + self + } + + /// Overrides the base URL for the `quoteSummary` API (used for profiles, financials, etc.). + /// Default: `https://query1.finance.yahoo.com/v10/finance/quoteSummary/`. + #[must_use] + pub fn base_quote_api(mut self, url: Url) -> Self { + self.base_quote_api = Some(url); + self + } + + /// Sets a custom base URL for the v7 quote endpoint. + /// + /// This is primarily used for testing or to target a different Yahoo Finance region. + /// If not set, a default URL (`https://query1.finance.yahoo.com/v7/finance/quote`) is used. + #[must_use] + pub fn base_quote_v7(mut self, url: Url) -> Self { + self.base_quote_v7 = Some(url); + self + } + + /// Sets a custom base URL for the v7 options endpoint. + /// + /// This is primarily used for testing or to target a different Yahoo Finance region. + /// If not set, a default URL (`https://query1.finance.yahoo.com/v7/finance/options/`) is used. + #[must_use] + pub fn base_options_v7(mut self, url: Url) -> Self { + self.base_options_v7 = Some(url); + self + } + + /// Sets a custom base URL for the streaming API. + #[must_use] + pub fn base_stream(mut self, url: Url) -> Self { + self.base_stream = Some(url); + self + } + + /// Sets a custom base URL for the news endpoint. + /// Default: `https://finance.yahoo.com`. + #[must_use] + pub fn base_news(mut self, url: Url) -> Self { + self.base_news = Some(url); + self + } + + /// Sets a custom base URL for the Business Insider search (for ISIN lookup). + #[must_use] + pub fn base_insider_search(mut self, url: Url) -> Self { + self.base_insider_search = Some(url); + self + } + + /// Sets a custom base URL for the timeseries endpoint. + #[must_use] + pub fn base_timeseries(mut self, url: Url) -> Self { + self.base_timeseries = Some(url); + self + } + + /// Overrides the URL used to acquire an initial cookie. + #[must_use] + pub fn cookie_url(mut self, url: Url) -> Self { + self.cookie_url = Some(url); + self + } + + /// Overrides the URL used to acquire a crumb for authenticated requests. + #[must_use] + pub fn crumb_url(mut self, url: Url) -> Self { + self.crumb_url = Some(url); + self + } + + /// Sets the entire retry configuration. + /// + /// Replaces the default retry settings. + #[must_use] + pub fn retry_config(mut self, cfg: RetryConfig) -> Self { + self.retry = Some(cfg); + self + } + + /// A convenience method to enable or disable the retry mechanism. + #[must_use] + pub fn retry_enabled(mut self, yes: bool) -> Self { + let mut cfg = self.retry.unwrap_or_default(); + cfg.enabled = yes; + self.retry = Some(cfg); + self + } + + /// Disables in-memory caching for this client. + #[must_use] + pub const fn no_cache(mut self) -> Self { + self.cache_ttl = None; + self + } + + /// (Internal testing only) Chooses which data source path to use for profile lookups. + /// + /// This setting only has effect when the `test-mode` feature is enabled. + /// In normal usage, this setting is ignored. + #[doc(hidden)] + #[must_use] + #[allow(unused_variables, unused_mut)] + pub const fn _api_preference(mut self, pref: ApiPreference) -> Self { + #[cfg(feature = "test-mode")] + { + self.api_preference = Some(pref); + } + self + } + + /// (Internal testing only) Provides pre-authenticated credentials to bypass the cookie/crumb fetch. + /// + /// This setting only has effect when the `test-mode` feature is enabled. + /// In normal usage, this setting is ignored. + #[doc(hidden)] + #[must_use] + #[allow(unused_variables, unused_mut)] + pub fn _preauth(mut self, cookie: impl Into, crumb: impl Into) -> Self { + #[cfg(feature = "test-mode")] + { + self.preauth_cookie = Some(cookie.into()); + self.preauth_crumb = Some(crumb.into()); + } + self + } + + /// Sets a global timeout for the entire HTTP request. + /// + /// Default: none. + #[must_use] + pub const fn timeout(mut self, dur: Duration) -> Self { + self.timeout = Some(dur); + self + } + + /// Sets a timeout for the connection phase of an HTTP request. + /// + /// Default: none. + #[must_use] + pub const fn connect_timeout(mut self, dur: Duration) -> Self { + self.connect_timeout = Some(dur); + self + } + + /// Enables in-memory caching with a default Time-To-Live (TTL) for all responses. + /// + /// If not set, caching is disabled by default. + #[must_use] + pub const fn cache_ttl(mut self, dur: Duration) -> Self { + self.cache_ttl = Some(dur); + self + } + + /// Sets a custom reqwest client for full control over HTTP configuration. + /// + /// This allows you to configure advanced features like custom TLS settings, + /// connection pooling, or other reqwest-specific options. When this is set, + /// other HTTP-related builder methods (timeout, `connect_timeout`, proxy) are ignored. + /// + /// # Example + /// + /// ```rust + /// use reqwest::Client; + /// use yfinance_rs::YfClient; + /// + /// let custom_client = Client::builder() + /// .timeout(std::time::Duration::from_secs(30)) + /// .build() + /// .unwrap(); + /// + /// let client = YfClient::builder() + /// .custom_client(custom_client) + /// .build() + /// .unwrap(); + /// ``` + #[must_use] + pub fn custom_client(mut self, client: Client) -> Self { + self.custom_client = Some(client); + self + } + + /// Sets an HTTP proxy for all requests. + /// + /// This is a convenience method for setting up proxy configuration without + /// needing to create a full custom client. If you need more advanced proxy + /// configuration, use `custom_client()` instead. + /// + /// # Example + /// + /// ```rust + /// use yfinance_rs::YfClient; + /// + /// let client = YfClient::builder() + /// .proxy("http://proxy.example.com:8080") + /// .build() + /// .unwrap(); + /// ``` + /// + /// # Errors + /// + /// This method will panic if the proxy URL is invalid. For production code, + /// consider using `try_proxy()` instead. + /// + /// # Panics + /// + /// Panics if the proxy URL format is invalid. + #[must_use] + pub fn proxy(mut self, proxy_url: &str) -> Self { + // Validate URL format before creating proxy + assert!( + url::Url::parse(proxy_url).is_ok(), + "invalid proxy URL format: {proxy_url}" + ); + self.proxy = Some(reqwest::Proxy::http(proxy_url).expect("invalid proxy URL")); + self + } + + /// Sets an HTTP proxy for all requests with error handling. + /// + /// This is a convenience method for setting up proxy configuration without + /// needing to create a full custom client. If you need more advanced proxy + /// configuration, use `custom_client()` instead. + /// + /// # Example + /// + /// ```rust + /// use yfinance_rs::YfClient; + /// + /// let client = YfClient::builder() + /// .try_proxy("http://proxy.example.com:8080")? + /// .build()?; + /// # Ok::<(), Box>(()) + /// ``` + /// + /// # Errors + /// + /// Returns an error if the proxy URL is invalid. + pub fn try_proxy(mut self, proxy_url: &str) -> Result { + // Validate URL format first + url::Url::parse(proxy_url) + .map_err(|e| YfError::InvalidParams(format!("invalid proxy URL format: {e}")))?; + + let proxy = reqwest::Proxy::http(proxy_url) + .map_err(|e| YfError::InvalidParams(format!("invalid proxy URL: {e}")))?; + self.proxy = Some(proxy); + Ok(self) + } + + /// Sets an HTTPS proxy for all requests. + /// + /// This is a convenience method for setting up HTTPS proxy configuration. + /// + /// # Example + /// + /// ```rust + /// use yfinance_rs::YfClient; + /// + /// let client = YfClient::builder() + /// .https_proxy("https://proxy.example.com:8443") + /// .build() + /// .unwrap(); + /// ``` + /// + /// # Errors + /// + /// This method will panic if the proxy URL is invalid. For production code, + /// consider using `try_https_proxy()` instead. + /// + /// # Panics + /// + /// Panics if the proxy URL format is invalid. + #[must_use] + pub fn https_proxy(mut self, proxy_url: &str) -> Self { + // Validate URL format before creating proxy + assert!( + url::Url::parse(proxy_url).is_ok(), + "invalid HTTPS proxy URL format: {proxy_url}" + ); + self.proxy = Some(reqwest::Proxy::https(proxy_url).expect("invalid HTTPS proxy URL")); + self + } + + /// Sets an HTTPS proxy for all requests with error handling. + /// + /// This is a convenience method for setting up HTTPS proxy configuration. + /// + /// # Example + /// + /// ```rust + /// use yfinance_rs::YfClient; + /// + /// let client = YfClient::builder() + /// .try_https_proxy("https://proxy.example.com:8443")? + /// .build()?; + /// # Ok::<(), Box>(()) + /// ``` + /// + /// # Errors + /// + /// Returns an error if the proxy URL is invalid. + pub fn try_https_proxy(mut self, proxy_url: &str) -> Result { + // Validate URL format first + url::Url::parse(proxy_url) + .map_err(|e| YfError::InvalidParams(format!("invalid HTTPS proxy URL format: {e}")))?; + + let proxy = reqwest::Proxy::https(proxy_url) + .map_err(|e| YfError::InvalidParams(format!("invalid HTTPS proxy URL: {e}")))?; + self.proxy = Some(proxy); + Ok(self) + } + + /// Builds the `YfClient`. + /// + /// # Errors + /// + /// Returns an error if the base URLs are invalid or the HTTP client fails to build. + pub fn build(self) -> Result { + let base_chart = self.base_chart.unwrap_or(Url::parse(DEFAULT_BASE_CHART)?); + let base_quote = self.base_quote.unwrap_or(Url::parse(DEFAULT_BASE_QUOTE)?); + let base_quote_api = self + .base_quote_api + .unwrap_or(Url::parse(DEFAULT_BASE_QUOTE_API)?); + let base_quote_v7 = self + .base_quote_v7 + .unwrap_or(Url::parse(constants::DEFAULT_BASE_QUOTE_V7)?); + let base_options_v7 = self + .base_options_v7 + .unwrap_or(Url::parse(constants::DEFAULT_BASE_OPTIONS_V7)?); + let base_stream = self + .base_stream + .unwrap_or(Url::parse(constants::DEFAULT_BASE_STREAM)?); + let base_news = self + .base_news + .unwrap_or(Url::parse(constants::DEFAULT_BASE_NEWS)?); + let base_insider_search = self + .base_insider_search + .unwrap_or(Url::parse(DEFAULT_BASE_INSIDER_SEARCH)?); + let base_timeseries = self + .base_timeseries + .unwrap_or(Url::parse(constants::DEFAULT_BASE_TIMESERIES)?); + + let cookie_url = self.cookie_url.unwrap_or(Url::parse(DEFAULT_COOKIE_URL)?); + let crumb_url = self.crumb_url.unwrap_or(Url::parse(DEFAULT_CRUMB_URL)?); + + let user_agent = self.user_agent.as_deref().unwrap_or(USER_AGENT).to_string(); + + // Use custom client if provided, otherwise build a new one + let http = if let Some(custom_client) = self.custom_client { + custom_client + } else { + let mut httpb = reqwest::Client::builder().cookie_store(true); + + if let Some(t) = self.timeout { + httpb = httpb.timeout(t); + } + if let Some(ct) = self.connect_timeout { + httpb = httpb.connect_timeout(ct); + } + if let Some(proxy) = self.proxy { + httpb = httpb.proxy(proxy); + } + + httpb.build()? + }; + + let initial_state = ClientState { + cookie: { + #[cfg(feature = "test-mode")] + { + self.preauth_cookie + } + #[cfg(not(feature = "test-mode"))] + { + None + } + }, + crumb: { + #[cfg(feature = "test-mode")] + { + self.preauth_crumb + } + #[cfg(not(feature = "test-mode"))] + { + None + } + }, + }; + + Ok(YfClient { + http, + base_chart, + base_quote, + base_quote_api, + base_quote_v7, + base_options_v7, + base_stream, + base_news, + base_insider_search, + base_timeseries, + cookie_url, + crumb_url, + user_agent, + state: Arc::new(RwLock::new(initial_state)), + credential_fetch_lock: Arc::new(tokio::sync::Mutex::new(())), + #[cfg(feature = "test-mode")] + api_preference: self.api_preference.unwrap_or(ApiPreference::ApiThenScrape), + retry: self.retry.unwrap_or_default(), + reporting_currency_cache: Arc::new(RwLock::new(HashMap::new())), + instrument_cache: Arc::new(RwLock::new(HashMap::new())), + cache: self.cache_ttl.map(|ttl| { + Arc::new(CacheStore { + map: RwLock::new(HashMap::new()), + default_ttl: ttl, + }) + }), + }) + } +} + +#[cfg(not(feature = "tracing"))] +async fn sleep_backoff(b: &Backoff, attempt: u32) { + let dur = compute_backoff_duration(b, attempt); + tokio::time::sleep(dur).await; +} + +#[inline] +fn compute_backoff_duration(b: &Backoff, attempt: u32) -> Duration { + use std::time::Duration; + match *b { + Backoff::Fixed(d) => d, + Backoff::Exponential { + base, + factor, + max, + jitter, + } => { + let pow = factor.powi(i32::try_from(attempt).unwrap()); + let mut d = Duration::from_secs_f64(base.as_secs_f64() * pow); + if d > max { + d = max; + } + if jitter { + let nanos = d.as_nanos(); + let j = u64::try_from(nanos / 2).unwrap_or(0) + * ((u64::from(attempt) % 5 + 1) * 13 % 100) + / 100; + let add = attempt.is_multiple_of(2); + d = if add { + d.saturating_add(Duration::from_nanos(j)) + } else { + d.saturating_sub(Duration::from_nanos(j)) + }; + } + d + } + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/client/retry.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/client/retry.rs new file mode 100644 index 0000000..0ef0813 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/client/retry.rs @@ -0,0 +1,65 @@ +/// Specifies the backoff strategy for retrying failed requests. +#[derive(Clone, Debug)] +pub enum Backoff { + /// Uses a fixed delay between retries. + Fixed(std::time::Duration), + /// Uses an exponential delay between retries. + /// The delay is calculated as `base * (factor ^ attempt)`. + Exponential { + /// The initial backoff duration. + base: std::time::Duration, + /// The multiplicative factor for each subsequent retry. + factor: f64, + /// The maximum duration to wait between retries. + max: std::time::Duration, + /// Whether to apply random jitter (+/- 50%) to the delay. + jitter: bool, + }, +} + +/// Configuration for the automatic retry mechanism. +#[derive(Clone, Debug)] +pub struct RetryConfig { + /// Enables or disables the retry mechanism. + pub enabled: bool, + /// The maximum number of retries to attempt. The total number of attempts will be `max_retries + 1`. + pub max_retries: u32, + /// The backoff strategy to use between retries. + pub backoff: Backoff, + /// A list of HTTP status codes that should trigger a retry. + pub retry_on_status: Vec, + /// Whether to retry on request timeouts. + pub retry_on_timeout: bool, + /// Whether to retry on connection errors. + pub retry_on_connect: bool, +} + +impl Default for RetryConfig { + fn default() -> Self { + Self { + enabled: true, + max_retries: 4, + backoff: Backoff::Exponential { + base: std::time::Duration::from_millis(200), + factor: 2.0, + max: std::time::Duration::from_secs(3), + jitter: true, + }, + retry_on_status: vec![408, 429, 500, 502, 503, 504], + retry_on_timeout: true, + retry_on_connect: true, + } + } +} + +/// Defines the behavior of the in-memory cache for an API call. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CacheMode { + /// Read from the cache if a non-expired entry is present; otherwise, fetch from the network + /// and write the response to the cache. (Default) + Use, + /// Always fetch from the network, bypassing any cached entry, and write the new response to the cache. + Refresh, + /// Always fetch from the network and do not read from or write to the cache. + Bypass, +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/conversions.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/conversions.rs new file mode 100644 index 0000000..e31c4cf --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/conversions.rs @@ -0,0 +1,222 @@ +//! Conversion utilities + +use chrono::{DateTime, Utc}; +use paft::domain::{Exchange, MarketState, Period}; +use paft::fundamentals::analysis::{RecommendationAction, RecommendationGrade}; +use paft::fundamentals::holders::{InsiderPosition, TransactionType}; +use paft::fundamentals::profile::FundKind; +use paft::money::{Currency, IsoCurrency, Money}; +use rust_decimal::prelude::ToPrimitive; +use std::str::FromStr; + +fn f64_to_decimal_safely(value: f64) -> rust_decimal::Decimal { + if !value.is_finite() { + return rust_decimal::Decimal::ZERO; + } + let formatted = format!("{value:.4}"); + rust_decimal::Decimal::from_str(&formatted).unwrap_or(rust_decimal::Decimal::ZERO) +} + +/// Convert f64 to Money with specified currency +/// +/// # Panics +/// Panics if currency metadata is not registered for non-ISO currencies. +#[must_use] +pub fn f64_to_money_with_currency(value: f64, currency: Currency) -> Money { + // Use string formatting to avoid f64 precision issues; coerce non-finite to zero + let decimal = f64_to_decimal_safely(value); + Money::new(decimal, currency).expect("currency metadata available") +} + +/// Convert i64 to Money with specified currency (no precision loss) +/// +/// # Panics +/// Panics if currency metadata is not registered for non-ISO currencies. +#[must_use] +pub fn i64_to_money_with_currency(value: i64, currency: Currency) -> Money { + let decimal = rust_decimal::Decimal::from_i128_with_scale(i128::from(value), 0); + Money::new(decimal, currency).expect("currency metadata available") +} + +/// Convert u64 to Money with specified currency (no precision loss) +/// +/// # Panics +/// Panics if currency metadata is not registered for non-ISO currencies. +#[must_use] +pub fn u64_to_money_with_currency(value: u64, currency: Currency) -> Money { + let decimal = rust_decimal::Decimal::from_i128_with_scale(i128::from(value), 0); + Money::new(decimal, currency).expect("currency metadata available") +} + +/// Convert f64 to Money with currency string (parses currency string to Currency enum) +#[must_use] +pub fn f64_to_money_with_currency_str(value: f64, currency_str: Option<&str>) -> Money { + let currency = currency_str + .and_then(|s| Currency::from_str(s).ok()) + .unwrap_or(Currency::Iso(IsoCurrency::USD)); + f64_to_money_with_currency(value, currency) +} + +/// Convert Money to f64 (loses currency information) +#[must_use] +pub fn money_to_f64(money: &Money) -> f64 { + money.amount().to_f64().unwrap_or(0.0) +} + +/// Extract currency string from Money object +#[must_use] +pub fn money_to_currency_str(money: &Money) -> Option { + Some(money.currency().to_string()) +} + +/// Convert i64 timestamp to `DateTime` +#[must_use] +pub fn i64_to_datetime(timestamp: i64) -> DateTime { + DateTime::from_timestamp(timestamp, 0).unwrap_or_default() +} + +/// Convert `DateTime` to i64 timestamp +#[must_use] +pub const fn datetime_to_i64(dt: DateTime) -> i64 { + dt.timestamp() +} + +/// Convert String to Exchange enum +#[allow(clippy::single_option_map)] +#[must_use] +pub fn string_to_exchange(s: Option) -> Option { + s.and_then(|s| { + // Map Yahoo Finance exchange names to paft Exchange values + match s.as_str() { + "NasdaqGS" | "NasdaqCM" | "NasdaqGM" => Some(Exchange::NASDAQ), + "NYSE" => Some(Exchange::NYSE), + "AMEX" => Some(Exchange::AMEX), + "BATS" => Some(Exchange::BATS), + "OTC" => Some(Exchange::OTC), + "LSE" => Some(Exchange::LSE), + "TSE" => Some(Exchange::TSE), + "HKEX" => Some(Exchange::HKEX), + "SSE" => Some(Exchange::SSE), + "SZSE" => Some(Exchange::SZSE), + "TSX" => Some(Exchange::TSX), + "ASX" => Some(Exchange::ASX), + "Euronext" => Some(Exchange::Euronext), + "XETRA" => Some(Exchange::XETRA), + "SIX" => Some(Exchange::SIX), + "BIT" => Some(Exchange::BIT), + "BME" => Some(Exchange::BME), + "AEX" => Some(Exchange::AEX), + "BRU" => Some(Exchange::BRU), + "LIS" => Some(Exchange::LIS), + "EPA" => Some(Exchange::EPA), + "OSL" => Some(Exchange::OSL), + "STO" => Some(Exchange::STO), + "CPH" => Some(Exchange::CPH), + "WSE" => Some(Exchange::WSE), + "PSE" => Some(Exchange::PSE), + "BSE" => Some(Exchange::BSE), + "MOEX" => Some(Exchange::MOEX), + "BIST" => Some(Exchange::BIST), + "JSE" => Some(Exchange::JSE), + "TASE" => Some(Exchange::TASE), + "BSE_HU" => Some(Exchange::BSE_HU), + "NSE" => Some(Exchange::NSE), + "KRX" => Some(Exchange::KRX), + "SGX" => Some(Exchange::SGX), + "SET" => Some(Exchange::SET), + "KLSE" => Some(Exchange::KLSE), + "PSE_CZ" => Some(Exchange::PSE_CZ), + "IDX" => Some(Exchange::IDX), + "HOSE" => Some(Exchange::HOSE), + _ => Exchange::try_from(s).ok(), + } + }) +} + +/// Convert Exchange to String +#[must_use] +pub fn exchange_to_string(exchange: Option) -> Option { + exchange.map(|e| e.to_string()) +} + +/// Convert String to `MarketState` enum +#[must_use] +pub fn string_to_market_state(s: Option) -> Option { + s.and_then(|s| s.parse().ok()) +} + +/// Convert `MarketState` to String +#[must_use] +pub fn market_state_to_string(state: Option) -> Option { + state.map(|s| s.to_string()) +} + +/// Convert String to `FundKind` enum +#[allow(clippy::single_option_map)] +#[must_use] +pub fn string_to_fund_kind(s: Option) -> Option { + s.and_then(|s| { + // Map Yahoo Finance legal types to paft FundKind values + match s.as_str() { + "Exchange Traded Fund" => Some(FundKind::Etf), + "Mutual Fund" => Some(FundKind::MutualFund), + "Index Fund" => Some(FundKind::IndexFund), + "Closed-End Fund" => Some(FundKind::ClosedEndFund), + "Money Market Fund" => Some(FundKind::MoneyMarketFund), + "Hedge Fund" => Some(FundKind::HedgeFund), + "Real Estate Investment Trust" => Some(FundKind::Reit), + "Unit Investment Trust" => Some(FundKind::UnitInvestmentTrust), + _ => FundKind::try_from(s).ok(), + } + }) +} + +/// Convert `FundKind` to String +#[must_use] +pub fn fund_kind_to_string(kind: Option) -> Option { + kind.map(|k| k.to_string()) +} + +/// Convert String to `InsiderPosition` enum +#[must_use] +pub fn string_to_insider_position(s: &str) -> InsiderPosition { + let token = s.trim(); + let token_nonempty = if token.is_empty() { "UNKNOWN" } else { token }; + token_nonempty.parse().unwrap_or(InsiderPosition::Officer) +} + +/// Convert String to `TransactionType` enum +#[must_use] +pub fn string_to_transaction_type(s: &str) -> TransactionType { + let token = s.trim(); + let token_nonempty = if token.is_empty() { "UNKNOWN" } else { token }; + token_nonempty.parse().unwrap_or(TransactionType::Buy) +} + +/// Convert String to Period +#[must_use] +pub fn string_to_period(s: &str) -> Period { + if s.trim().is_empty() { + return "UNKNOWN".parse().map_or(Period::Year { year: 1970 }, |p| p); + } + s.parse() + .unwrap_or_else(|_| "UNKNOWN".parse().map_or(Period::Year { year: 1970 }, |p| p)) +} + +/// Convert String to `RecommendationGrade` enum +#[must_use] +pub fn string_to_recommendation_grade(s: &str) -> RecommendationGrade { + let token = s.trim(); + let token_nonempty = if token.is_empty() { "UNKNOWN" } else { token }; + token_nonempty.parse().unwrap_or(RecommendationGrade::Hold) +} + +/// Convert String to `RecommendationAction` enum +#[must_use] +pub fn string_to_recommendation_action(s: &str) -> RecommendationAction { + let token = s.trim(); + let token_nonempty = if token.is_empty() { "UNKNOWN" } else { token }; + token_nonempty + .parse() + .unwrap_or(RecommendationAction::Maintain) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/currency.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/currency.rs new file mode 100644 index 0000000..7214027 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/currency.rs @@ -0,0 +1,659 @@ +//! Helpers for inferring currencies from country information. + +use std::{collections::HashMap, sync::LazyLock}; + +use paft::money::{Currency, IsoCurrency}; + +/// Normalized country → currency code pairs. +/// +/// Keys must be uppercase and ASCII; values are ISO 4217 currency codes. +const COUNTRY_TO_CURRENCY_RAW: &[(&str, &str)] = &[ + ("UNITED STATES", "USD"), + ("UNITED STATES OF AMERICA", "USD"), + ("US", "USD"), + ("USA", "USD"), + ("CANADA", "CAD"), + ("MEXICO", "MXN"), + ("BRAZIL", "BRL"), + ("ARGENTINA", "ARS"), + ("CHILE", "CLP"), + ("COLOMBIA", "COP"), + ("PERU", "PEN"), + ("URUGUAY", "UYU"), + ("PARAGUAY", "PYG"), + ("BOLIVIA", "BOB"), + ("ECUADOR", "USD"), + ("VENEZUELA", "VES"), + ("COSTA RICA", "CRC"), + ("GUATEMALA", "GTQ"), + ("HONDURAS", "HNL"), + ("NICARAGUA", "NIO"), + ("PANAMA", "USD"), + ("EL SALVADOR", "USD"), + ("BELIZE", "BZD"), + ("DOMINICAN REPUBLIC", "DOP"), + ("JAMAICA", "JMD"), + ("TRINIDAD AND TOBAGO", "TTD"), + ("BARBADOS", "BBD"), + ("BAHAMAS", "BSD"), + ("BERMUDA", "BMD"), + ("CAYMAN ISLANDS", "KYD"), + ("ARUBA", "AWG"), + ("CURACAO", "ANG"), + ("BRITISH VIRGIN ISLANDS", "USD"), + ("PUERTO RICO", "USD"), + ("UNITED KINGDOM", "GBP"), + ("ENGLAND", "GBP"), + ("SCOTLAND", "GBP"), + ("WALES", "GBP"), + ("NORTHERN IRELAND", "GBP"), + ("IRELAND", "EUR"), + ("FRANCE", "EUR"), + ("GERMANY", "EUR"), + ("ITALY", "EUR"), + ("SPAIN", "EUR"), + ("PORTUGAL", "EUR"), + ("NETHERLANDS", "EUR"), + ("BELGIUM", "EUR"), + ("LUXEMBOURG", "EUR"), + ("AUSTRIA", "EUR"), + ("SWITZERLAND", "CHF"), + ("SWEDEN", "SEK"), + ("NORWAY", "NOK"), + ("DENMARK", "DKK"), + ("FINLAND", "EUR"), + ("ICELAND", "ISK"), + ("POLAND", "PLN"), + ("CZECH REPUBLIC", "CZK"), + ("CZECHIA", "CZK"), + ("HUNGARY", "HUF"), + ("SLOVAKIA", "EUR"), + ("SLOVENIA", "EUR"), + ("CROATIA", "EUR"), + ("ROMANIA", "RON"), + ("BULGARIA", "BGN"), + ("GREECE", "EUR"), + ("CYPRUS", "EUR"), + ("MALTA", "EUR"), + ("ESTONIA", "EUR"), + ("LATVIA", "EUR"), + ("LITHUANIA", "EUR"), + ("UKRAINE", "UAH"), + ("BELARUS", "BYN"), + ("RUSSIA", "RUB"), + ("TURKEY", "TRY"), + ("SERBIA", "RSD"), + ("BOSNIA AND HERZEGOVINA", "BAM"), + ("NORTH MACEDONIA", "MKD"), + ("ALBANIA", "ALL"), + ("MONTENEGRO", "EUR"), + ("KOSOVO", "EUR"), + ("ARMENIA", "AMD"), + ("GEORGIA", "GEL"), + ("AZERBAIJAN", "AZN"), + ("KAZAKHSTAN", "KZT"), + ("UZBEKISTAN", "UZS"), + ("TURKMENISTAN", "TMT"), + ("KYRGYZSTAN", "KGS"), + ("TAJIKISTAN", "TJS"), + ("CHINA", "CNY"), + ("PEOPLES REPUBLIC OF CHINA", "CNY"), + ("HONG KONG", "HKD"), + ("MACAU", "MOP"), + ("TAIWAN", "TWD"), + ("JAPAN", "JPY"), + ("SOUTH KOREA", "KRW"), + ("REPUBLIC OF KOREA", "KRW"), + ("NORTH KOREA", "KPW"), + ("INDIA", "INR"), + ("PAKISTAN", "PKR"), + ("BANGLADESH", "BDT"), + ("SRI LANKA", "LKR"), + ("NEPAL", "NPR"), + ("BHUTAN", "BTN"), + ("MALDIVES", "MVR"), + ("MYANMAR", "MMK"), + ("THAILAND", "THB"), + ("VIETNAM", "VND"), + ("LAOS", "LAK"), + ("CAMBODIA", "KHR"), + ("MALAYSIA", "MYR"), + ("SINGAPORE", "SGD"), + ("INDONESIA", "IDR"), + ("PHILIPPINES", "PHP"), + ("BRUNEI", "BND"), + ("MONGOLIA", "MNT"), + ("AUSTRALIA", "AUD"), + ("NEW ZEALAND", "NZD"), + ("FIJI", "FJD"), + ("PAPUA NEW GUINEA", "PGK"), + ("NEW CALEDONIA", "XPF"), + ("FRENCH POLYNESIA", "XPF"), + ("SAMOA", "WST"), + ("TONGA", "TOP"), + ("VANUATU", "VUV"), + ("SOLOMON ISLANDS", "SBD"), + ("EAST TIMOR", "USD"), + ("TIMOR-LESTE", "USD"), + ("UNITED ARAB EMIRATES", "AED"), + ("SAUDI ARABIA", "SAR"), + ("QATAR", "QAR"), + ("KUWAIT", "KWD"), + ("BAHRAIN", "BHD"), + ("OMAN", "OMR"), + ("JORDAN", "JOD"), + ("LEBANON", "LBP"), + ("ISRAEL", "ILS"), + ("PALESTINE", "ILS"), + ("IRAQ", "IQD"), + ("IRAN", "IRR"), + ("AFGHANISTAN", "AFN"), + ("SYRIA", "SYP"), + ("YEMEN", "YER"), + ("EGYPT", "EGP"), + ("MOROCCO", "MAD"), + ("ALGERIA", "DZD"), + ("TUNISIA", "TND"), + ("LIBYA", "LYD"), + ("SUDAN", "SDG"), + ("SOUTH SUDAN", "SSP"), + ("NIGERIA", "NGN"), + ("GHANA", "GHS"), + ("COTE DIVOIRE", "XOF"), + ("COTE D IVOIRE", "XOF"), + ("COTE D'IVOIRE", "XOF"), + ("SENEGAL", "XOF"), + ("MALI", "XOF"), + ("BENIN", "XOF"), + ("BURKINA FASO", "XOF"), + ("NIGER", "XOF"), + ("TOGO", "XOF"), + ("GUINEA-BISSAU", "XOF"), + ("GUINEA BISSAU", "XOF"), + ("CAMEROON", "XAF"), + ("CHAD", "XAF"), + ("CENTRAL AFRICAN REPUBLIC", "XAF"), + ("REPUBLIC OF THE CONGO", "XAF"), + ("CONGO", "XAF"), + ("GABON", "XAF"), + ("EQUATORIAL GUINEA", "XAF"), + ("GAMBIA", "GMD"), + ("GUINEA", "GNF"), + ("SIERRA LEONE", "SLE"), + ("LIBERIA", "LRD"), + ("ETHIOPIA", "ETB"), + ("ERITREA", "ERN"), + ("DJIBOUTI", "DJF"), + ("KENYA", "KES"), + ("UGANDA", "UGX"), + ("TANZANIA", "TZS"), + ("RWANDA", "RWF"), + ("BURUNDI", "BIF"), + ("SOMALIA", "SOS"), + ("SEYCHELLES", "SCR"), + ("MADAGASCAR", "MGA"), + ("MAURITIUS", "MUR"), + ("MOZAMBIQUE", "MZN"), + ("ZIMBABWE", "ZWL"), + ("ZAMBIA", "ZMW"), + ("MALAWI", "MWK"), + ("ANGOLA", "AOA"), + ("NAMIBIA", "NAD"), + ("BOTSWANA", "BWP"), + ("SOUTH AFRICA", "ZAR"), + ("LESOTHO", "LSL"), + ("ESWATINI", "SZL"), + ("SWAZILAND", "SZL"), + ("COMOROS", "KMF"), + ("MAURITANIA", "MRU"), + ("SAO TOME AND PRINCIPE", "STN"), + ("GRENADA", "XCD"), + ("SAINT LUCIA", "XCD"), + ("SAINT VINCENT AND THE GRENADINES", "XCD"), + ("ANTIGUA AND BARBUDA", "XCD"), + ("DOMINICA", "XCD"), + ("SAINT KITTS AND NEVIS", "XCD"), +]; + +/// Precomputed lookup table using `COUNTRY_TO_CURRENCY_RAW`. +static COUNTRY_TO_CURRENCY: LazyLock> = LazyLock::new(|| { + let mut map = HashMap::new(); + for (country, code) in COUNTRY_TO_CURRENCY_RAW { + let parsed = (*code).parse().unwrap_or(Currency::Iso(IsoCurrency::USD)); + map.insert(*country, parsed); + } + map +}); + +/// Normalize a country string to an uppercase ASCII key. +fn normalize_country(country: &str) -> Option { + let trimmed = country.trim(); + if trimmed.is_empty() { + return None; + } + + let mut buf = String::with_capacity(trimmed.len()); + for ch in trimmed.chars() { + match ch { + 'A'..='Z' | '0'..='9' => buf.push(ch), + 'a'..='z' => buf.push(ch.to_ascii_uppercase()), + ' ' | '\t' | '\n' | '\r' | '\'' | '`' | '"' => buf.push(' '), + '-' | '_' | '/' | ',' | '.' | ';' | ':' | '&' | '(' | ')' | '[' | ']' | '{' | '}' => { + buf.push(' '); + } + 'á' | 'à' | 'â' | 'ä' | 'ã' | 'å' | 'Á' | 'À' | 'Â' | 'Ä' | 'Ã' | 'Å' => { + buf.push('A'); + } + 'ç' | 'Ç' => buf.push('C'), + 'é' | 'è' | 'ê' | 'ë' | 'É' | 'È' | 'Ê' | 'Ë' => buf.push('E'), + 'í' | 'ì' | 'î' | 'ï' | 'Í' | 'Ì' | 'Î' | 'Ï' => buf.push('I'), + 'ñ' | 'Ñ' => buf.push('N'), + 'ó' | 'ò' | 'ô' | 'ö' | 'õ' | 'Ó' | 'Ò' | 'Ô' | 'Ö' | 'Õ' => buf.push('O'), + 'ú' | 'ù' | 'û' | 'ü' | 'Ú' | 'Ù' | 'Û' | 'Ü' => buf.push('U'), + 'ý' | 'ÿ' | 'Ý' => buf.push('Y'), + _ => { + // Ignore other symbols to keep normalization simple. + } + } + } + + let normalized = buf + .split_whitespace() + .filter(|part| !part.is_empty()) + .collect::>() + .join(" "); + + if normalized.is_empty() { + None + } else { + Some(normalized) + } +} + +/// Attempt to infer a currency from a country string. +/// +/// Returns `None` if the country string is empty or cannot be matched. +pub fn currency_for_country(country: &str) -> Option { + let normalized = normalize_country(country)?; + + if let Some(currency) = COUNTRY_TO_CURRENCY.get(normalized.as_str()) { + return Some(currency.clone()); + } + + heuristic_currency_match(&normalized) +} + +fn heuristic_currency_match(normalized: &str) -> Option { + match_americas(normalized) + .or_else(|| match_europe(normalized)) + .or_else(|| match_asia_pacific(normalized)) + .or_else(|| match_mena(normalized)) + .or_else(|| match_caucasus_central_asia(normalized)) + .or_else(|| match_africa(normalized)) +} + +fn match_americas(s: &str) -> Option { + let c = |n| s.contains(n); + if c("UNITED STATES") { + return Some(Currency::Iso(IsoCurrency::USD)); + } + if c("CANADA") { + return Some(Currency::Iso(IsoCurrency::CAD)); + } + if c("MEXICO") { + return Some(Currency::Iso(IsoCurrency::MXN)); + } + if c("BRAZIL") { + return Some(Currency::Iso(IsoCurrency::BRL)); + } + if c("ARGENTINA") { + return "ARS".parse().ok(); + } + if c("CHILE") { + return "CLP".parse().ok(); + } + if c("COLOMBIA") { + return "COP".parse().ok(); + } + if c("PERU") { + return "PEN".parse().ok(); + } + if c("URUGUAY") { + return "UYU".parse().ok(); + } + if c("PARAGUAY") { + return "PYG".parse().ok(); + } + if c("BOLIVIA") { + return "BOB".parse().ok(); + } + if c("VENEZUELA") { + return "VES".parse().ok(); + } + if c("PANAMA") || c("ECUADOR") || c("EL SALVADOR") { + return Some(Currency::Iso(IsoCurrency::USD)); + } + if c("BAHAMAS") { + return "BSD".parse().ok(); + } + if c("CAYMAN") { + return "KYD".parse().ok(); + } + if c("BERMUDA") { + return "BMD".parse().ok(); + } + if c("TRINIDAD") { + return "TTD".parse().ok(); + } + if c("JAMAICA") { + return "JMD".parse().ok(); + } + if c("BARBADOS") { + return "BBD".parse().ok(); + } + if c("DOMINICAN") { + return "DOP".parse().ok(); + } + Some(None?).or(None) +} + +fn match_europe(s: &str) -> Option { + let c = |n| s.contains(n); + if c("UNITED KINGDOM") || c("ENGLAND") || c("SCOTLAND") { + return Some(Currency::Iso(IsoCurrency::GBP)); + } + if c("EUROPEAN UNION") || c("EURO AREA") { + return Some(Currency::Iso(IsoCurrency::EUR)); + } + if c("SWITZERLAND") { + return Some(Currency::Iso(IsoCurrency::CHF)); + } + if c("NORWAY") { + return Some(Currency::Iso(IsoCurrency::NOK)); + } + if c("SWEDEN") { + return Some(Currency::Iso(IsoCurrency::SEK)); + } + if c("DENMARK") { + return Some(Currency::Iso(IsoCurrency::DKK)); + } + if c("ICELAND") { + return "ISK".parse().ok(); + } + if c("POLAND") { + return Some(Currency::Iso(IsoCurrency::PLN)); + } + if c("CZECH") { + return Some(Currency::Iso(IsoCurrency::CZK)); + } + if c("HUNGARY") { + return Some(Currency::Iso(IsoCurrency::HUF)); + } + if c("ROMANIA") { + return "RON".parse().ok(); + } + if c("BULGARIA") { + return "BGN".parse().ok(); + } + if c("UKRAINE") { + return "UAH".parse().ok(); + } + if c("BELARUS") { + return "BYN".parse().ok(); + } + if c("SERBIA") { + return "RSD".parse().ok(); + } + if c("TURKEY") { + return Some(Currency::Iso(IsoCurrency::TRY)); + } + Some(None?).or(None) +} + +fn match_asia_pacific(s: &str) -> Option { + let c = |n| s.contains(n); + if c("HONG KONG") { + return Some(Currency::Iso(IsoCurrency::HKD)); + } + if c("MACAU") { + return "MOP".parse().ok(); + } + if c("TAIWAN") { + return "TWD".parse().ok(); + } + if c("KOREA") { + return Some(Currency::Iso(IsoCurrency::KRW)); + } + if c("JAPAN") { + return Some(Currency::Iso(IsoCurrency::JPY)); + } + if c("CHINA") { + return Some(Currency::Iso(IsoCurrency::CNY)); + } + if c("INDIA") { + return Some(Currency::Iso(IsoCurrency::INR)); + } + if c("SINGAPORE") { + return Some(Currency::Iso(IsoCurrency::SGD)); + } + if c("MALAYSIA") { + return Some(Currency::Iso(IsoCurrency::MYR)); + } + if c("INDONESIA") { + return Some(Currency::Iso(IsoCurrency::IDR)); + } + if c("PHILIPPINES") { + return Some(Currency::Iso(IsoCurrency::PHP)); + } + if c("VIETNAM") { + return Some(Currency::Iso(IsoCurrency::VND)); + } + if c("THAILAND") { + return Some(Currency::Iso(IsoCurrency::THB)); + } + if c("LAOS") { + return "LAK".parse().ok(); + } + if c("CAMBODIA") { + return "KHR".parse().ok(); + } + if c("BRUNEI") { + return "BND".parse().ok(); + } + if c("MONGOLIA") { + return "MNT".parse().ok(); + } + if c("AUSTRALIA") { + return Some(Currency::Iso(IsoCurrency::AUD)); + } + if c("NEW ZEALAND") { + return Some(Currency::Iso(IsoCurrency::NZD)); + } + if c("FIJI") { + return "FJD".parse().ok(); + } + if c("SAMOA") { + return "WST".parse().ok(); + } + if c("TONGA") { + return "TOP".parse().ok(); + } + if c("VANUATU") { + return "VUV".parse().ok(); + } + if c("SOLOMON") { + return "SBD".parse().ok(); + } + if c("PAPUA") { + return "PGK".parse().ok(); + } + Some(None?).or(None) +} + +fn match_mena(s: &str) -> Option { + let c = |n| s.contains(n); + if c("ISRAEL") { + return Some(Currency::Iso(IsoCurrency::ILS)); + } + if c("SAUDI ARABIA") { + return "SAR".parse().ok(); + } + if c("UNITED ARAB EMIRATES") { + return "AED".parse().ok(); + } + if c("QATAR") { + return "QAR".parse().ok(); + } + if c("KUWAIT") { + return "KWD".parse().ok(); + } + if c("BAHRAIN") { + return "BHD".parse().ok(); + } + if c("OMAN") { + return "OMR".parse().ok(); + } + if c("EGYPT") { + return "EGP".parse().ok(); + } + if c("JORDAN") { + return "JOD".parse().ok(); + } + if c("LEBANON") { + return "LBP".parse().ok(); + } + if c("IRAQ") { + return "IQD".parse().ok(); + } + if c("IRAN") { + return "IRR".parse().ok(); + } + if c("AFGHANISTAN") { + return "AFN".parse().ok(); + } + if c("SYRIA") { + return "SYP".parse().ok(); + } + if c("YEMEN") { + return "YER".parse().ok(); + } + Some(None?).or(None) +} + +fn match_caucasus_central_asia(s: &str) -> Option { + let c = |n| s.contains(n); + if c("GEORGIA") { + return "GEL".parse().ok(); + } + if c("ARMENIA") { + return "AMD".parse().ok(); + } + if c("AZERBAIJAN") { + return "AZN".parse().ok(); + } + if c("KAZAKHSTAN") { + return "KZT".parse().ok(); + } + if c("UZBEKISTAN") { + return "UZS".parse().ok(); + } + if c("TURKMENISTAN") { + return "TMT".parse().ok(); + } + if c("KYRGYZSTAN") { + return "KGS".parse().ok(); + } + if c("TAJIKISTAN") { + return "TJS".parse().ok(); + } + Some(None?).or(None) +} + +fn match_africa(s: &str) -> Option { + let c = |n| s.contains(n); + if c("SOUTH AFRICA") { + return Some(Currency::Iso(IsoCurrency::ZAR)); + } + if c("NIGERIA") { + return "NGN".parse().ok(); + } + if c("GHANA") { + return "GHS".parse().ok(); + } + if c("KENYA") { + return "KES".parse().ok(); + } + if c("MOROCCO") { + return "MAD".parse().ok(); + } + if c("ALGERIA") { + return "DZD".parse().ok(); + } + if c("TUNISIA") { + return "TND".parse().ok(); + } + if c("ZAMBIA") { + return "ZMW".parse().ok(); + } + if c("ZIMBABWE") { + return "ZWL".parse().ok(); + } + if c("ANGOLA") { + return "AOA".parse().ok(); + } + if c("NAMIBIA") { + return "NAD".parse().ok(); + } + if c("BOTSWANA") { + return "BWP".parse().ok(); + } + if c("LESOTHO") { + return "LSL".parse().ok(); + } + if c("ESWATINI") || c("SWAZILAND") { + return "SZL".parse().ok(); + } + if c("MOZAMBIQUE") { + return "MZN".parse().ok(); + } + if c("MADAGASCAR") { + return "MGA".parse().ok(); + } + if c("MAURITIUS") { + return "MUR".parse().ok(); + } + if c("MALAWI") { + return "MWK".parse().ok(); + } + if c("SEYCHELLES") { + return "SCR".parse().ok(); + } + if c("RWANDA") { + return "RWF".parse().ok(); + } + if c("BURUNDI") { + return "BIF".parse().ok(); + } + if c("UGANDA") { + return "UGX".parse().ok(); + } + if c("TANZANIA") { + return "TZS".parse().ok(); + } + if c("SOMALIA") { + return "SOS".parse().ok(); + } + if c("DJIBOUTI") { + return "DJF".parse().ok(); + } + if c("ERITREA") { + return "ERN".parse().ok(); + } + if c("NIGER") || c("SENEGAL") || c("IVORY COAST") || c("COTE DIVOIRE") { + return "XOF".parse().ok(); + } + if c("CAMEROON") { + return "XAF".parse().ok(); + } + Some(None?).or(None) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/dataframe.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/dataframe.rs new file mode 100644 index 0000000..3200cd9 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/dataframe.rs @@ -0,0 +1,36 @@ +use polars::prelude::*; + +/// Trait for converting financial data structures into Polars `DataFrames`. +/// +/// This trait provides a consistent interface for converting various yfinance-rs data +/// structures into Polars `DataFrames` for advanced data analysis and manipulation. +pub trait ToDataFrame { + /// Converts the object into a Polars `DataFrame`. + /// + /// # Errors + /// + /// Returns an error if `DataFrame` construction fails (e.g., invalid schema or data). + fn to_dataframe(&self) -> PolarsResult; + + /// Creates an empty `DataFrame` with the correct schema for this type. + /// + /// # Errors + /// + /// Returns an error if the schema definition cannot be represented in a `DataFrame`. + fn empty_dataframe() -> PolarsResult + where + Self: Sized; + + /// Returns the complete flattened schema for this type. + /// + /// This method provides static access to the type's schema without requiring + /// an instance, making it useful for building nested schemas and validating + /// data structures at compile time. + /// + /// # Errors + /// + /// Returns an error if the schema cannot be derived for the type. + fn schema() -> PolarsResult> + where + Self: Sized; +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/error.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/error.rs new file mode 100644 index 0000000..276a3c4 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/error.rs @@ -0,0 +1,93 @@ +use thiserror::Error; + +/// The primary error type for the `yfinance-rs` crate. +#[derive(Debug, Error)] +pub enum YfError { + /// An error originating from the underlying HTTP client (`reqwest`). + #[error("HTTP error: {0}")] + Http(#[from] reqwest::Error), + + /// An error related to WebSocket communication. + #[error("WebSocket error: {0}")] + Websocket(Box), + + /// An error during Protobuf decoding, typically from a WebSocket stream. + #[error("Protobuf decoding error: {0}")] + Protobuf(#[from] prost::DecodeError), + + /// An error during JSON serialization or deserialization. + #[error("JSON parsing error: {0}")] + Json(#[from] serde_json::Error), + + /// An error during Base64 decoding. + #[error("Base64 decoding error: {0}")] + Base64(#[from] base64::DecodeError), + + /// An error that occurs when parsing a URL. + #[error("Invalid URL: {0}")] + Url(#[from] url::ParseError), + + /// A 404 Not Found returned by Yahoo endpoints. + #[error("Not found at {url}")] + NotFound { + /// The URL that returned a 404. + url: String, + }, + + /// A 429 Too Many Requests (rate limit) returned by Yahoo endpoints. + #[error("Rate limited at {url}")] + RateLimited { + /// The URL that returned a 429. + url: String, + }, + + /// A 5xx server error returned by Yahoo endpoints. + #[error("Server error {status} at {url}")] + ServerError { + /// The HTTP status code in the 5xx range. + status: u16, + /// The URL that returned a server error. + url: String, + }, + + /// An error indicating an unexpected, non-successful HTTP status code (non-404/429/5xx). + #[error("Unexpected response status: {status} at {url}")] + Status { + /// The unexpected HTTP status code returned. + status: u16, + /// The URL that returned the status. + url: String, + }, + + /// An error returned by the Yahoo Finance API within an otherwise successful response. + /// + /// For example, a `200 OK` response might contain a JSON body with an `error` field. + #[error("Yahoo API error: {0}")] + Api(String), + + /// An error related to authentication, such as failing to retrieve a cookie or crumb. + #[error("Authentication error: {0}")] + Auth(String), + + /// An error that occurs during the web scraping process. + #[error("Web scraping error: {0}")] + Scrape(String), + + /// Indicates that an expected piece of data was missing from the API response. + #[error("Missing data in response: {0}")] + MissingData(String), + + /// An error indicating that the parameters provided by the caller were invalid. + #[error("Invalid parameters: {0}")] + InvalidParams(String), + + /// An error indicating that the provided date range is invalid (e.g., start date after end date). + #[error("Invalid date range: start date must be before end date")] + InvalidDates, +} + +impl From for YfError { + fn from(e: tokio_tungstenite::tungstenite::Error) -> Self { + Self::Websocket(Box::new(e)) + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/fixtures.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/fixtures.rs new file mode 100644 index 0000000..777e3ca --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/fixtures.rs @@ -0,0 +1,36 @@ +//! Test/recording helpers for persisting HTTP fixtures. +//! Compiled only when the `test-mode` feature is enabled. + +use std::env; +use std::fs; +use std::io::Write; +use std::path::{Path, PathBuf}; + +pub fn get_fixture_dir() -> PathBuf { + env::var("YF_FIXDIR").map_or_else( + |_| Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures"), + PathBuf::from, + ) +} + +pub fn record_fixture( + endpoint: &str, + symbol: &str, + ext: &str, + body: &str, +) -> Result<(), std::io::Error> { + let dir = get_fixture_dir(); + if !dir.exists() { + fs::create_dir_all(&dir)?; + } + let filename = format!("{endpoint}_{symbol}.{ext}"); + let path = dir.join(filename); + + let mut file = fs::File::create(&path)?; + file.write_all(body.as_bytes())?; + + if env::var("YF_DEBUG").ok().as_deref() == Some("1") { + eprintln!("YF_RECORD: wrote fixture to {}", path.display()); + } + Ok(()) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/mod.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/mod.rs new file mode 100644 index 0000000..316ab29 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/mod.rs @@ -0,0 +1,36 @@ +//! Core components of the `yfinance-rs` client. +//! +//! This module contains the foundational building blocks of the library, including: +//! - The main [`YfClient`] and its builder. +//! - The primary [`YfError`] type. +//! - Shared data models like [`Quote`] and [`Candle`]. +//! - Internal networking and authentication logic. + +/// The main client (`YfClient`), builder, and configuration. +pub mod client; +pub(crate) mod currency; +/// The primary error type (`YfError`) for the crate. +pub mod error; +/// Shared data models used across multiple API modules (e.g., `Quote`, `Candle`). +pub mod models; +pub(crate) mod quotes; +pub(crate) mod quotesummary; +/// Service traits for abstracting functionality like history fetching. +pub mod services; +pub(crate) mod wire; + +#[cfg(feature = "test-mode")] +pub(crate) mod fixtures; + +#[cfg(feature = "dataframe")] +/// `DataFrame` conversion traits. +pub mod dataframe; + +pub mod conversions; +pub(crate) mod net; + +// convenient re-exports so most code can just `use crate::core::YfClient` +pub use client::{CacheMode, RetryConfig, YfClient, YfClientBuilder}; +pub use error::YfError; +pub use models::{Action, Candle, HistoryMeta, HistoryResponse, Interval, Quote, Range}; +pub use services::{HistoryRequest, HistoryService}; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/models.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/models.rs new file mode 100644 index 0000000..95d0d23 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/models.rs @@ -0,0 +1,39 @@ +// Re-export types from paft explicitly +pub use paft::market::action::Action; +pub use paft::market::quote::Quote; +pub use paft::market::requests::history::{Interval, Range}; +pub use paft::market::responses::history::{Candle, HistoryMeta, HistoryResponse}; + +// Helper functions for converting to string representations +pub(crate) const fn range_as_str(range: Range) -> &'static str { + match range { + Range::D1 => "1d", + Range::D5 => "5d", + Range::M1 => "1mo", + Range::M3 => "3mo", + Range::M6 => "6mo", + Range::Y1 => "1y", + Range::Y2 => "2y", + Range::Y5 => "5y", + Range::Y10 => "10y", + Range::Ytd => "ytd", + Range::Max => "max", + } +} + +pub(crate) const fn interval_as_str(interval: Interval) -> &'static str { + match interval { + Interval::I1m => "1m", + Interval::I2m => "2m", + Interval::I5m => "5m", + Interval::I15m => "15m", + Interval::I30m => "30m", + Interval::I90m => "90m", + Interval::I1h => "1h", + Interval::D1 => "1d", + Interval::D5 => "5d", + Interval::W1 => "1wk", + Interval::M1 => "1mo", + Interval::M3 => "3mo", + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/net.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/net.rs new file mode 100644 index 0000000..438c2cb --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/net.rs @@ -0,0 +1,25 @@ +#[cfg(feature = "test-mode")] +use std::env; + +/// Read the response body as text. +/// In `test-mode`, if `YF_RECORD=1`, the body is saved as a fixture via `net_fixtures`. +#[allow(unused_variables)] +pub async fn get_text( + resp: reqwest::Response, + endpoint: &str, + symbol: &str, + ext: &str, +) -> Result { + let text = resp.text().await?; + + #[cfg(feature = "test-mode")] + { + if env::var("YF_RECORD").ok().as_deref() == Some("1") + && let Err(e) = crate::core::fixtures::record_fixture(endpoint, symbol, ext, &text) + { + eprintln!("YF_RECORD: failed to write fixture for {symbol}: {e}"); + } + } + + Ok(text) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/quotes.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/quotes.rs new file mode 100644 index 0000000..0a51b15 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/quotes.rs @@ -0,0 +1,219 @@ +// src/core/quotes.rs +use serde::Deserialize; +use std::str::FromStr; +use url::Url; + +use crate::{ + YfClient, YfError, + core::{ + client::{CacheMode, RetryConfig}, + conversions::f64_to_money_with_currency_str, + net, + }, +}; +use paft::domain::{AssetKind, Instrument}; +use paft::market::quote::Quote; + +// Centralized wire model for the v7 quote API +#[derive(Deserialize)] +pub struct V7Envelope { + #[serde(rename = "quoteResponse")] + pub(crate) quote_response: Option, +} + +#[derive(Deserialize)] +pub struct V7QuoteResponse { + pub(crate) result: Option>, + #[allow(dead_code)] + pub(crate) error: Option, +} + +#[derive(Deserialize, Clone)] +pub struct V7QuoteNode { + #[serde(default)] + pub(crate) symbol: Option, + #[serde(rename = "quoteType")] + pub(crate) quote_type: Option, + #[serde(rename = "shortName")] + pub(crate) short_name: Option, + #[serde(rename = "regularMarketPrice")] + pub(crate) regular_market_price: Option, + #[serde(rename = "regularMarketPreviousClose")] + pub(crate) regular_market_previous_close: Option, + #[serde(rename = "regularMarketVolume")] + pub(crate) regular_market_volume: Option, + pub(crate) currency: Option, + #[serde(rename = "fullExchangeName")] + pub(crate) full_exchange_name: Option, + pub(crate) exchange: Option, + pub(crate) market: Option, + #[serde(rename = "marketCapFigureExchange")] + pub(crate) market_cap_figure_exchange: Option, + #[serde(rename = "marketState")] + pub(crate) market_state: Option, +} + +/// Centralized function to fetch one or more quotes from the v7 API. +/// It handles caching, retries, and authentication (crumb). +#[allow(clippy::too_many_lines)] +pub async fn fetch_v7_quotes( + client: &YfClient, + symbols: &[&str], + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result, YfError> { + // Inner function to attempt the fetch, allowing for an auth retry. + async fn attempt_fetch( + client: &YfClient, + symbols: &[&str], + crumb: Option<&str>, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, + ) -> Result<(String, Url, Option), YfError> { + let mut url = client.base_quote_v7().clone(); + { + let mut qp = url.query_pairs_mut(); + qp.append_pair("symbols", &symbols.join(",")); + if let Some(c) = crumb { + qp.append_pair("crumb", c); + } + } + + if cache_mode == CacheMode::Use + && let Some(body) = client.cache_get(&url).await + { + return Ok((body, url, None)); + } + + let resp = client + .send_with_retry( + client + .http() + .get(url.clone()) + .header("accept", "application/json"), + retry_override, + ) + .await?; + + let status = resp.status(); + let body = net::get_text(resp, "quote_v7", &symbols.join("-"), "json").await?; + + if status.is_success() { + if cache_mode != CacheMode::Bypass { + client.cache_put(&url, &body, None).await; + } + Ok((body, url, None)) + } else { + Ok((body, url, Some(status.as_u16()))) + } + } + + // First attempt, without a crumb. + let (body, url, maybe_status) = + attempt_fetch(client, symbols, None, cache_mode, retry_override).await?; + + let body_to_parse = if let Some(status_code) = maybe_status { + // If unauthorized, get a crumb and retry. + if status_code == 401 || status_code == 403 { + client.ensure_credentials().await?; + let crumb = client.crumb().await.ok_or_else(|| { + YfError::Auth("Crumb is not set after ensuring credentials".into()) + })?; + + // Second attempt, with a crumb. + let (body, url, maybe_status) = + attempt_fetch(client, symbols, Some(&crumb), cache_mode, retry_override).await?; + + if let Some(status_code) = maybe_status { + let url_s = url.to_string(); + return Err(match status_code { + 404 => YfError::NotFound { url: url_s }, + 429 => YfError::RateLimited { url: url_s }, + 500..=599 => YfError::ServerError { + status: status_code, + url: url_s, + }, + _ => YfError::Status { + status: status_code, + url: url_s, + }, + }); + } + body + } else { + let url_s = url.to_string(); + return Err(match status_code { + 404 => YfError::NotFound { url: url_s }, + 429 => YfError::RateLimited { url: url_s }, + 500..=599 => YfError::ServerError { + status: status_code, + url: url_s, + }, + _ => YfError::Status { + status: status_code, + url: url_s, + }, + }); + } + } else { + body + }; + + let env: V7Envelope = serde_json::from_str(&body_to_parse)?; + let nodes = env + .quote_response + .and_then(|qr| qr.result) + .unwrap_or_default(); + + // Populate instrument cache best-effort from v7 quote nodes + for n in &nodes { + if let Some(sym) = n.symbol.as_deref() { + let exch = crate::core::conversions::string_to_exchange( + n.full_exchange_name + .clone() + .or_else(|| n.exchange.clone()) + .or_else(|| n.market.clone()) + .or_else(|| n.market_cap_figure_exchange.clone()), + ); + let kind = n + .quote_type + .as_deref() + .and_then(|s| s.parse::().ok()) + .unwrap_or(AssetKind::Equity); + + let inst = exch.map_or_else( + || Instrument::from_symbol(sym, kind), + |ex| Instrument::from_symbol_and_exchange(sym, ex, kind), + ); + if let Ok(inst) = inst { + client.store_instrument(sym.to_string(), inst).await; + } + } + } + + Ok(nodes) +} + +impl From for Quote { + fn from(n: V7QuoteNode) -> Self { + Self { + symbol: paft::domain::Symbol::from_str(&n.symbol.unwrap_or_default()) + .expect("v7 quote node had invalid/missing symbol"), + shortname: n.short_name, + price: n + .regular_market_price + .map(|price| f64_to_money_with_currency_str(price, n.currency.as_deref())), + previous_close: n + .regular_market_previous_close + .map(|price| f64_to_money_with_currency_str(price, n.currency.as_deref())), + day_volume: n.regular_market_volume, + exchange: crate::core::conversions::string_to_exchange( + n.full_exchange_name + .or(n.exchange) + .or(n.market) + .or(n.market_cap_figure_exchange), + ), + market_state: n.market_state.and_then(|s| s.parse().ok()), + } + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/quotesummary.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/quotesummary.rs new file mode 100644 index 0000000..70c03fc --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/quotesummary.rs @@ -0,0 +1,145 @@ +use crate::core::{ + YfClient, YfError, + client::{CacheMode, RetryConfig}, + net, +}; +use serde::Deserialize; + +#[cfg(feature = "debug-dumps")] +use crate::profile::debug::debug_dump_api; + +#[derive(Deserialize)] +pub struct V10Envelope { + #[serde(rename = "quoteSummary")] + pub(crate) quote_summary: Option, +} + +#[derive(Deserialize)] +pub struct V10QuoteSummary { + pub(crate) result: Option>, + pub(crate) error: Option, +} + +#[derive(Deserialize)] +pub struct V10Error { + pub(crate) description: String, +} + +#[cfg_attr( + feature = "tracing", + tracing::instrument( + skip(client, cache_mode, retry_override), + err, + fields(symbol = %symbol, modules = %modules, caller = %caller) + ) +)] +pub async fn fetch( + client: &YfClient, + symbol: &str, + modules: &str, + caller: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result { + async fn attempt_fetch( + client: &YfClient, + symbol: &str, + modules: &str, + caller: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, + ) -> Result { + client.ensure_credentials().await?; + + let crumb = client + .crumb() + .await + .ok_or_else(|| YfError::Auth("Crumb is not set".into()))?; + + let mut url = client.base_quote_api().join(symbol)?; + { + let mut qp = url.query_pairs_mut(); + qp.append_pair("modules", modules); + qp.append_pair("crumb", &crumb); + } + + if cache_mode == CacheMode::Use + && let Some(text) = client.cache_get(&url).await + { + #[cfg(feature = "debug-dumps")] + let _ = debug_dump_api(symbol, &text); + return serde_json::from_str(&text).map_err(YfError::Json); + } + + let req = client.http().get(url.clone()); + let resp = client.send_with_retry(req, retry_override).await?; + + // Create a sanitized key from module names for a unique fixture filename. + let module_key = modules + .replace(',', "-") + .replace(|c: char| !c.is_alphanumeric() && c != '-', ""); + let fixture_endpoint = format!("{caller}_api_{module_key}"); + let text = net::get_text(resp, &fixture_endpoint, symbol, "json").await?; + + #[cfg(feature = "debug-dumps")] + let _ = debug_dump_api(symbol, &text); + + if cache_mode != CacheMode::Bypass { + client.cache_put(&url, &text, None).await; + } + + serde_json::from_str(&text).map_err(YfError::Json) + } + + for attempt in 0..=1 { + let env = + attempt_fetch(client, symbol, modules, caller, cache_mode, retry_override).await?; + + if let Some(error) = env.quote_summary.as_ref().and_then(|qs| qs.error.as_ref()) { + let desc = error.description.to_ascii_lowercase(); + if desc.contains("invalid crumb") && attempt == 0 { + if std::env::var("YF_DEBUG").ok().as_deref() == Some("1") { + eprintln!("YF_DEBUG: Invalid crumb in {caller}; refreshing and retrying."); + } + #[cfg(feature = "tracing")] + tracing::event!( + tracing::Level::WARN, + "invalid crumb; refreshing and retrying" + ); + client.clear_crumb().await; + continue; + } + #[cfg(feature = "tracing")] + tracing::event!(tracing::Level::ERROR, description = %error.description, "quoteSummary error"); + return Err(YfError::Api(format!("yahoo error: {}", error.description))); + } + + return Ok(env); + } + + Err(YfError::Api(format!( + "{caller} API call failed after retry" + ))) +} + +pub async fn fetch_module_result( + client: &YfClient, + symbol: &str, + modules: &str, + caller: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result +where + T: for<'de> serde::Deserialize<'de>, +{ + let env = fetch(client, symbol, modules, caller, cache_mode, retry_override).await?; + + let result_val = env + .quote_summary + .and_then(|qs| qs.result) + .and_then(|mut v| v.pop()) + .ok_or_else(|| YfError::MissingData("empty quoteSummary result".into()))?; + + serde_json::from_value(result_val).map_err(YfError::Json) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/services.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/services.rs new file mode 100644 index 0000000..4e7318c --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/services.rs @@ -0,0 +1,49 @@ +use crate::core::{HistoryResponse, Interval, Range, YfError}; + +/// Encapsulates all parameters for a single historical data request. +/// +/// This struct is used as a generic way to request historical data, decoupling modules +/// like `download` from the specific implementation details of `history::HistoryBuilder`. +#[derive(Debug, Clone, Copy)] +#[allow(clippy::struct_excessive_bools)] +pub struct HistoryRequest { + /// A relative time range for the request (e.g., `1y`, `6mo`). + /// + /// If `Some`, this takes precedence over `period`. + pub range: Option, + /// An absolute time period for the request, specified as `(start, end)` Unix timestamps. + pub period: Option<(i64, i64)>, + /// The time interval for each data point (candle). + pub interval: Interval, + /// Whether to include pre-market and post-market data for intraday intervals. + pub include_prepost: bool, + /// Whether to include corporate actions (dividends and splits) in the response. + pub include_actions: bool, + /// Whether to automatically adjust prices for splits and dividends. + pub auto_adjust: bool, + /// Whether to keep data rows that have missing OHLC values. + pub keepna: bool, +} + +/// A trait for services that can fetch historical financial data. +/// +/// This allows for abstracting the history fetching logic, making it easier to test +/// and decoupling different parts of the crate. It is implemented by [`YfClient`]. +pub trait HistoryService: Send + Sync { + /// Asynchronously fetches the complete historical data for a given symbol and request. + /// + /// # Arguments + /// * `symbol` - The ticker symbol to fetch data for. + /// * `req` - A `HistoryRequest` struct containing all the parameters for the query. + /// + /// # Returns + /// A `Future` that resolves to a `Result` containing either a `HistoryResponse` on success + /// or a `YfError` on failure. + fn fetch_full_history<'a>( + &'a self, + symbol: &'a str, + req: HistoryRequest, + ) -> core::pin::Pin< + Box> + Send + 'a>, + >; +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/wire.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/wire.rs new file mode 100644 index 0000000..fd74d6a --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/core/wire.rs @@ -0,0 +1,65 @@ +use serde::{Deserialize, Deserializer}; + +#[derive(Deserialize, Clone, Copy)] +pub struct RawNum { + pub(crate) raw: Option, +} + +pub fn from_raw(raw: Option>) -> Option { + raw.and_then(|n| n.raw) +} + +pub fn from_raw_u32_round(r: Option>) -> Option { + r.and_then(|n| n.raw).and_then(|v| { + let rounded = v.round(); + if rounded >= 0.0 && rounded <= f64::from(u32::MAX) { + // This cast is safe as we check the bounds of rounded. + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + Some(rounded as u32) + } else { + None + } + }) +} + +#[derive(Deserialize, Clone, Copy)] +pub struct RawDate { + pub(crate) raw: Option, +} + +pub fn from_raw_date(r: Option) -> Option { + r.and_then(|d| d.raw) +} + +#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] +fn de_u64_from_any_number<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum AnyNumber { + U64(u64), + F64(f64), + } + + match Option::::deserialize(deserializer)? { + Some(AnyNumber::U64(u)) => Ok(Some(u)), + Some(AnyNumber::F64(f)) => { + if f.fract() == 0.0 && f >= 0.0 { + Ok(Some(f as u64)) + } else { + Err(serde::de::Error::custom(format!( + "cannot convert float {f} to u64" + ))) + } + } + None => Ok(None), + } +} + +#[derive(Deserialize, Clone, Copy)] +pub struct RawNumU64 { + #[serde(deserialize_with = "de_u64_from_any_number")] + pub(crate) raw: Option, +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/download/mod.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/download/mod.rs new file mode 100644 index 0000000..5588b3a --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/download/mod.rs @@ -0,0 +1,442 @@ +use futures::future::try_join_all; + +use crate::{ + core::client::{CacheMode, RetryConfig}, + core::{Candle, HistoryResponse, Interval, Range, YfClient, YfError}, + history::HistoryBuilder, +}; +use paft::domain::{AssetKind, Instrument}; +use paft::market::responses::download::{DownloadEntry, DownloadResponse}; +use paft::money::Money; +use rust_decimal::prelude::{FromPrimitive, ToPrimitive}; +type DateRange = (chrono::DateTime, chrono::DateTime); +type MaybeDateRange = Option; + +/// A builder for downloading historical data for multiple symbols concurrently. +/// +/// This provides a convenient way to fetch data for a list of tickers with the same +/// parameters in parallel, similar to `yfinance.download` in Python. +/// +/// Many of the configuration methods mirror those on [`HistoryBuilder`]. +#[allow(clippy::struct_excessive_bools)] +pub struct DownloadBuilder { + client: YfClient, + symbols: Vec, + + // date / time controls + range: Option, + period: Option<(i64, i64)>, + interval: Interval, + + // behavior flags + auto_adjust: bool, + back_adjust: bool, + include_prepost: bool, + include_actions: bool, + keepna: bool, + rounding: bool, + repair: bool, + + cache_mode: CacheMode, + retry_override: Option, +} + +impl DownloadBuilder { + fn precompute_period_dt(&self) -> Result { + if let Some((p1, p2)) = self.period { + use chrono::{TimeZone, Utc}; + let start = Utc + .timestamp_opt(p1, 0) + .single() + .ok_or_else(|| YfError::InvalidParams("invalid period1".into()))?; + let end = Utc + .timestamp_opt(p2, 0) + .single() + .ok_or_else(|| YfError::InvalidParams("invalid period2".into()))?; + Ok(Some((start, end))) + } else { + Ok(None) + } + } + + fn build_history_for_symbol( + &self, + sym: &str, + period_dt: Option<(chrono::DateTime, chrono::DateTime)>, + need_adjust_in_fetch: bool, + ) -> HistoryBuilder { + let mut hb: HistoryBuilder = HistoryBuilder::new(&self.client, sym.to_string()) + .interval(self.interval) + .auto_adjust(need_adjust_in_fetch) + .prepost(self.include_prepost) + .actions(self.include_actions) + .keepna(self.keepna) + .cache_mode(self.cache_mode) + .retry_policy(self.retry_override.clone()); + + if let Some((start, end)) = period_dt { + hb = hb.between(start, end); + } else if let Some(r) = self.range { + hb = hb.range(r); + } else { + hb = hb.range(Range::M6); + } + hb + } + + fn apply_back_adjust(&self, rows: &mut [Candle]) { + if !self.back_adjust { + return; + } + for c in rows.iter_mut() { + if let Some(rc) = c.close_unadj.as_ref() + && rc.amount().to_f64().is_some_and(f64::is_finite) + { + c.close = rc.clone(); + } + } + } + + fn apply_rounding_if_enabled(&self, rows: &mut [Candle]) { + if !self.rounding { + return; + } + for c in rows { + if c.open.amount().to_f64().is_some_and(f64::is_finite) { + c.open = Money::new( + rust_decimal::Decimal::from_f64(round2( + c.open.amount().to_f64().unwrap_or(0.0), + )) + .unwrap_or_default(), + c.open.currency().clone(), + ) + .expect("currency metadata available"); + } + if c.high.amount().to_f64().is_some_and(f64::is_finite) { + c.high = Money::new( + rust_decimal::Decimal::from_f64(round2( + c.high.amount().to_f64().unwrap_or(0.0), + )) + .unwrap_or_default(), + c.high.currency().clone(), + ) + .expect("currency metadata available"); + } + if c.low.amount().to_f64().is_some_and(f64::is_finite) { + c.low = Money::new( + rust_decimal::Decimal::from_f64(round2(c.low.amount().to_f64().unwrap_or(0.0))) + .unwrap_or_default(), + c.low.currency().clone(), + ) + .expect("currency metadata available"); + } + if c.close.amount().to_f64().is_some_and(f64::is_finite) { + c.close = Money::new( + rust_decimal::Decimal::from_f64(round2( + c.close.amount().to_f64().unwrap_or(0.0), + )) + .unwrap_or_default(), + c.close.currency().clone(), + ) + .expect("currency metadata available"); + } + } + } + + fn maybe_repair(&self, rows: &mut [Candle]) { + if self.repair { + repair_scale_outliers(rows); + } + } + + async fn process_joined_results( + &self, + joined: Vec<(String, HistoryResponse)>, + _need_adjust_in_fetch: bool, + ) -> DownloadResponse { + let mut entries: Vec = Vec::with_capacity(joined.len()); + for (sym, mut resp) in joined { + // apply transforms to candles + self.apply_back_adjust(&mut resp.candles); + self.maybe_repair(&mut resp.candles); + self.apply_rounding_if_enabled(&mut resp.candles); + + // get instrument from cache or fallback + let instrument = if let Some(inst) = self.client.cached_instrument(&sym).await { + inst + } else { + let kind = AssetKind::Equity; + let inst = Instrument::from_symbol(&sym, kind).expect("valid symbol"); + self.client + .store_instrument(sym.clone(), inst.clone()) + .await; + inst + }; + + entries.push(DownloadEntry { + instrument, + history: resp, + }); + } + DownloadResponse { entries } + } + + /// Creates a new `DownloadBuilder`. + #[must_use] + pub fn new(client: &YfClient) -> Self { + Self { + client: client.clone(), + symbols: Vec::new(), + range: Some(Range::M6), + period: None, + interval: Interval::D1, + auto_adjust: true, + back_adjust: false, + include_prepost: false, + include_actions: true, + keepna: false, + rounding: false, + repair: false, + cache_mode: CacheMode::Use, + retry_override: None, + } + } + + /// Sets the cache mode for all API calls made by this builder. + #[must_use] + pub const fn cache_mode(mut self, mode: CacheMode) -> Self { + self.cache_mode = mode; + self + } + + /// Overrides the default retry policy for all API calls made by this builder. + #[must_use] + pub fn retry_policy(mut self, cfg: Option) -> Self { + self.retry_override = cfg; + self + } + + /// Replaces the current list of symbols with a new list. + #[must_use] + pub fn symbols(mut self, syms: I) -> Self + where + I: IntoIterator, + S: Into, + { + self.symbols = syms.into_iter().map(std::convert::Into::into).collect(); + self + } + + /// Adds a single symbol to the list of symbols to download. + #[must_use] + pub fn add_symbol(mut self, sym: impl Into) -> Self { + self.symbols.push(sym.into()); + self + } + + /// Sets a relative time range for the request (e.g., `1y`, `6mo`). + #[must_use] + pub const fn range(mut self, range: Range) -> Self { + self.period = None; + self.range = Some(range); + self + } + + /// Sets an absolute time period for the request using start and end timestamps. + #[must_use] + pub const fn between( + mut self, + start: chrono::DateTime, + end: chrono::DateTime, + ) -> Self { + self.range = None; + self.period = Some((start.timestamp(), end.timestamp())); + self + } + + /// Sets the time interval for each data point (candle). + #[must_use] + pub const fn interval(mut self, interval: Interval) -> Self { + self.interval = interval; + self + } + + /// Sets whether to automatically adjust prices for splits and dividends. (Default: `true`) + #[must_use] + pub const fn auto_adjust(mut self, yes: bool) -> Self { + self.auto_adjust = yes; + self + } + + /// Sets whether to back-adjust prices. + /// + /// Back-adjustment adjusts the Open, High, and Low prices, but keeps the Close price as the + /// raw, unadjusted close. This forces an internal adjustment even if `auto_adjust` is false. + #[must_use] + pub const fn back_adjust(mut self, yes: bool) -> Self { + self.back_adjust = yes; + self + } + + /// Sets whether to include pre-market and post-market data for intraday intervals. (Default: `false`) + #[must_use] + pub const fn prepost(mut self, yes: bool) -> Self { + self.include_prepost = yes; + self + } + + /// Sets whether to include corporate actions (dividends and splits) in the result. (Default: `true`) + #[must_use] + pub const fn actions(mut self, yes: bool) -> Self { + self.include_actions = yes; + self + } + + /// Sets whether to keep data rows that have missing OHLC values. (Default: `false`) + #[must_use] + pub const fn keepna(mut self, yes: bool) -> Self { + self.keepna = yes; + self + } + + /// Sets whether to round prices to 2 decimal places. (Default: `false`) + #[must_use] + pub const fn rounding(mut self, yes: bool) -> Self { + self.rounding = yes; + self + } + + /// Sets whether to attempt to repair obvious price outliers (e.g., 100x errors). (Default: `false`) + #[must_use] + pub const fn repair(mut self, yes: bool) -> Self { + self.repair = yes; + self + } + + /// Executes the download by fetching data for all specified symbols concurrently. + /// + /// # Errors + /// + /// Returns an error if any of the underlying history requests fail. + pub async fn run(self) -> Result { + if self.symbols.is_empty() { + return Err(YfError::InvalidParams("no symbols specified".into())); + } + + let need_adjust_in_fetch = self.auto_adjust || self.back_adjust; + let period_dt = self.precompute_period_dt()?; + + let futures = self.symbols.iter().map(|sym| { + let sym = sym.clone(); + let hb = self.build_history_for_symbol(&sym, period_dt, need_adjust_in_fetch); + + async move { + let full: HistoryResponse = hb.fetch_full().await?; + Ok::<(String, HistoryResponse), YfError>((sym, full)) + } + }); + + let joined: Vec<(String, HistoryResponse)> = try_join_all(futures).await?; + Ok(self + .process_joined_results(joined, need_adjust_in_fetch) + .await) + } +} + +/* ---------------- internal helpers ---------------- */ + +fn round2(x: f64) -> f64 { + (x * 100.0).round() / 100.0 +} + +/// Very lightweight "repair" pass: +/// If a bar's close is ~100× the average of its neighbors (or ~1/100), +/// scale that entire bar's OHLC accordingly. Volumes are left unchanged. +fn repair_scale_outliers(rows: &mut [Candle]) { + if rows.len() < 3 { + return; + } + + for i in 1..rows.len() - 1 { + // Split rows at i, so left[..i] and right[i..] don't overlap. + let (left, right) = rows.split_at_mut(i); + + // prev is in the left side (immutable is fine) + let prev = &left[i - 1]; + + // Now split the right side so we can mutably borrow the “current” bar + // and (immutably) the remainder where “next” lives, without overlap. + let (cur, rem) = right.split_first_mut().expect("right has at least 1"); + let next = &rem[0]; // safe because len >= 2 overall ⇒ rem has at least one + + let p = &prev.close; + let n = &next.close; + let c = &cur.close; + + if !(p.amount().to_f64().is_some_and(f64::is_finite) + && n.amount().to_f64().is_some_and(f64::is_finite) + && c.amount().to_f64().is_some_and(f64::is_finite)) + { + continue; + } + + let p_val = p.amount().to_f64().unwrap_or(0.0); + let n_val = n.amount().to_f64().unwrap_or(0.0); + let c_val = c.amount().to_f64().unwrap_or(0.0); + + let baseline = f64::midpoint(p_val, n_val); + if baseline <= 0.0 { + continue; + } + + let ratio = c_val / baseline; + + // ~100× high + if ratio > 50.0 && ratio < 200.0 { + let scale = if (80.0..125.0).contains(&ratio) { + 0.01 + } else { + 1.0 / ratio + }; + scale_row_prices(cur, scale); + continue; + } + + // ~100× low + if ratio > 0.0 && ratio < 0.02 { + let scale = if (0.008..0.0125).contains(&ratio) { + 100.0 + } else { + 1.0 / ratio + }; + scale_row_prices(cur, scale); + } + } +} + +fn scale_row_prices(c: &mut Candle, scale: f64) { + if c.open.amount().to_f64().is_some_and(f64::is_finite) { + c.open = c + .open + .try_mul(rust_decimal::Decimal::from_f64_retain(scale).unwrap_or_default()) + .expect("currency metadata available"); + } + if c.high.amount().to_f64().is_some_and(f64::is_finite) { + c.high = c + .high + .try_mul(rust_decimal::Decimal::from_f64_retain(scale).unwrap_or_default()) + .expect("currency metadata available"); + } + if c.low.amount().to_f64().is_some_and(f64::is_finite) { + c.low = c + .low + .try_mul(rust_decimal::Decimal::from_f64_retain(scale).unwrap_or_default()) + .expect("currency metadata available"); + } + if c.close.amount().to_f64().is_some_and(f64::is_finite) { + c.close = c + .close + .try_mul(rust_decimal::Decimal::from_f64_retain(scale).unwrap_or_default()) + .expect("currency metadata available"); + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/esg/api.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/esg/api.rs new file mode 100644 index 0000000..6986a30 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/esg/api.rs @@ -0,0 +1,70 @@ +use crate::{ + core::{ + YfClient, YfError, + client::{CacheMode, RetryConfig}, + quotesummary, + wire::from_raw, + }, + esg::wire::V10Result, +}; +use paft::fundamentals::esg::{EsgInvolvement, EsgScores, EsgSummary}; + +pub(super) async fn fetch_esg_scores( + client: &YfClient, + symbol: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result { + let root: V10Result = quotesummary::fetch_module_result( + client, + symbol, + "esgScores", + "esg", + cache_mode, + retry_override, + ) + .await?; + + let esg = root + .esg_scores + .ok_or_else(|| YfError::MissingData("esgScores module missing from response".into()))?; + + // Map to paft types: paft::fundamentals::EsgScores now has only environmental/social/governance. + let scores = EsgScores { + environmental: from_raw(esg.environment_score), + social: from_raw(esg.social_score), + governance: from_raw(esg.governance_score), + }; + + // Collect involvement booleans as individual entries with simple categories. + let mut involvement: Vec = Vec::new(); + let mut push_flag = |name: &str, val: Option| { + if val.unwrap_or(false) { + involvement.push(EsgInvolvement { + category: name.to_string(), + score: None, + }); + } + }; + push_flag("adult", esg.adult); + push_flag("alcoholic", esg.alcoholic); + push_flag("animal_testing", esg.animal_testing); + push_flag("catholic", esg.catholic); + push_flag("controversial_weapons", esg.controversial_weapons); + push_flag("small_arms", esg.small_arms); + push_flag("fur_leather", esg.fur_leather); + push_flag("gambling", esg.gambling); + push_flag("gmo", esg.gmo); + push_flag("military_contract", esg.military_contract); + push_flag("nuclear", esg.nuclear); + push_flag("palm_oil", esg.palm_oil); + push_flag("pesticides", esg.pesticides); + push_flag("thermal_coal", esg.thermal_coal); + push_flag("tobacco", esg.tobacco); + + // Return scores together with involvement in a single summary + Ok(EsgSummary { + scores: Some(scores), + involvement, + }) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/esg/mod.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/esg/mod.rs new file mode 100644 index 0000000..9b803ca --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/esg/mod.rs @@ -0,0 +1,59 @@ +mod api; +mod model; +mod wire; + +pub use model::{EsgInvolvement, EsgScores, EsgSummary}; + +use crate::{ + YfClient, YfError, + core::client::{CacheMode, RetryConfig}, +}; + +/// A builder for fetching ESG (Environmental, Social, and Governance) data for a specific symbol. +pub struct EsgBuilder { + client: YfClient, + symbol: String, + cache_mode: CacheMode, + retry_override: Option, +} + +impl EsgBuilder { + /// Creates a new `EsgBuilder` for a given symbol. + pub fn new(client: &YfClient, symbol: impl Into) -> Self { + Self { + client: client.clone(), + symbol: symbol.into(), + cache_mode: CacheMode::Use, + retry_override: None, + } + } + + /// Sets the cache mode for this specific API call. + #[must_use] + pub const fn cache_mode(mut self, mode: CacheMode) -> Self { + self.cache_mode = mode; + self + } + + /// Overrides the default retry policy for this specific API call. + #[must_use] + pub fn retry_policy(mut self, cfg: Option) -> Self { + self.retry_override = cfg; + self + } + + /// Fetches the ESG scores and involvement data for the symbol. + /// + /// # Errors + /// + /// Returns a `YfError` if the network request fails or the API response cannot be parsed. + pub async fn fetch(self) -> Result { + api::fetch_esg_scores( + &self.client, + &self.symbol, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/esg/model.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/esg/model.rs new file mode 100644 index 0000000..f996f92 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/esg/model.rs @@ -0,0 +1,2 @@ +// Re-export types from paft +pub use paft::fundamentals::esg::{EsgInvolvement, EsgScores, EsgSummary}; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/esg/wire.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/esg/wire.rs new file mode 100644 index 0000000..bbf467e --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/esg/wire.rs @@ -0,0 +1,44 @@ +use crate::core::wire::RawNum; +use serde::Deserialize; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct V10Result { + #[serde(rename = "esgScores")] + pub(crate) esg_scores: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EsgScoresNode { + // These are objects: { "raw": ... } + #[allow(dead_code)] + pub(crate) total_esg: Option>, + pub(crate) environment_score: Option>, + pub(crate) social_score: Option>, + pub(crate) governance_score: Option>, + + // These are primitives + #[allow(dead_code)] + pub(crate) percentile: Option, + #[allow(dead_code)] + pub(crate) highest_controversy: Option, // Use f64 to match JSON `2.0` + + // Involvement flags + pub(crate) adult: Option, + pub(crate) alcoholic: Option, + pub(crate) animal_testing: Option, + pub(crate) catholic: Option, + pub(crate) controversial_weapons: Option, + pub(crate) small_arms: Option, + pub(crate) fur_leather: Option, + pub(crate) gambling: Option, + pub(crate) gmo: Option, + pub(crate) military_contract: Option, + pub(crate) nuclear: Option, + pub(crate) palm_oil: Option, + pub(crate) pesticides: Option, + #[serde(rename = "coal")] // JSON key is "coal", map to our more descriptive field name + pub(crate) thermal_coal: Option, + pub(crate) tobacco: Option, +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/fundamentals/api.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/fundamentals/api.rs new file mode 100644 index 0000000..ae8b4ce --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/fundamentals/api.rs @@ -0,0 +1,619 @@ +use chrono::{Duration, Utc}; +use std::collections::BTreeMap; + +use crate::{ + core::{ + YfClient, YfError, + client::{CacheMode, RetryConfig}, + conversions::{f64_to_money_with_currency, i64_to_datetime, string_to_period}, + }, + fundamentals::wire::{TimeseriesData, TimeseriesEnvelope}, +}; +use paft::fundamentals::profile::ShareCount; +use paft::money::Currency; + +use super::fetch::fetch_modules; +use super::{ + BalanceSheetRow, CashflowRow, Earnings, EarningsQuarter, EarningsQuarterEps, EarningsYear, + IncomeStatementRow, +}; + +/// Generic helper function to fetch and process timeseries data from the fundamentals API. +/// +/// This function handles the common pattern of: +/// 1. Constructing the URL for the /ws/fundamentals-timeseries endpoint +/// 2. Making the request with caching logic +/// 3. Parsing the `TimeseriesEnvelope` +/// 4. Processing the data into a `BTreeMap` +/// +/// The `process_item` closure is responsible for processing each timeseries item +/// and updating the rows map accordingly. +#[allow(clippy::too_many_arguments)] +async fn fetch_timeseries_data( + client: &YfClient, + symbol: &str, + quarterly: bool, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, + keys: &[&str], + endpoint_name: &str, + _create_default_row: fn(i64) -> T, + process_item: F, +) -> Result, YfError> +where + F: Fn(&str, &serde_json::Value, &mut BTreeMap, &[i64], &str) -> Result<(), YfError>, +{ + let prefix = if quarterly { "quarterly" } else { "annual" }; + let types: Vec = keys.iter().map(|k| format!("{prefix}{k}")).collect(); + let type_str = types.join(","); + + let end_ts = Utc::now().timestamp(); + let start_ts = Utc::now() + .checked_sub_signed(Duration::days(365 * 5)) + .map_or(0, |dt| dt.timestamp()); + + let mut url = client.base_timeseries().join(symbol)?; + url.query_pairs_mut() + .append_pair("symbol", symbol) + .append_pair("type", &type_str) + .append_pair("period1", &start_ts.to_string()) + .append_pair("period2", &end_ts.to_string()); + + client.ensure_credentials().await?; + if let Some(crumb) = client.crumb().await { + url.query_pairs_mut().append_pair("crumb", &crumb); + } + + let body = if cache_mode == CacheMode::Use { + if let Some(cached) = client.cache_get(&url).await { + cached + } else { + let resp = client + .send_with_retry(client.http().get(url.clone()), retry_override) + .await?; + let endpoint = format!("timeseries_{endpoint_name}_{prefix}"); + let text = crate::core::net::get_text(resp, &endpoint, symbol, "json").await?; + if cache_mode != CacheMode::Bypass { + client.cache_put(&url, &text, None).await; + } + text + } + } else { + let resp = client + .send_with_retry(client.http().get(url.clone()), retry_override) + .await?; + let endpoint = format!("timeseries_{endpoint_name}_{prefix}"); + let text = crate::core::net::get_text(resp, &endpoint, symbol, "json").await?; + if cache_mode != CacheMode::Bypass { + client.cache_put(&url, &text, None).await; + } + text + }; + + let envelope: TimeseriesEnvelope = serde_json::from_str(&body).map_err(YfError::Json)?; + + let result_vec = envelope + .timeseries + .and_then(|ts| ts.result) + .unwrap_or_default(); + + if result_vec.is_empty() { + return Ok(vec![]); + } + + let mut rows_map = BTreeMap::::new(); + + for item in result_vec { + if let (Some(timestamps), Some((key, values_json))) = + (item.timestamp, item.values.into_iter().next()) + { + // Process the item using the provided closure + process_item(&key, &values_json, &mut rows_map, ×tamps, prefix)?; + } + } + + Ok(rows_map.into_values().rev().collect()) +} + +pub(super) async fn income_statement( + client: &YfClient, + symbol: &str, + quarterly: bool, + currency: Currency, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result, YfError> { + use serde::Deserialize; + + use crate::core::wire::RawNum; + + #[derive(Deserialize)] + struct TimeseriesValueF64 { + #[serde(rename = "reportedValue")] + reported_value: Option>, + } + + let keys = [ + "TotalRevenue", + "GrossProfit", + "OperatingIncome", + "NetIncome", + ]; + let endpoint_name = "income_statement"; + + let create_default_row = |period_end: i64| IncomeStatementRow { + period: string_to_period(&i64_to_datetime(period_end).format("%Y-%m-%d").to_string()), + total_revenue: None, + gross_profit: None, + operating_income: None, + net_income: None, + }; + + let process_item = |key: &str, + values_json: &serde_json::Value, + rows_map: &mut BTreeMap, + timestamps: &[i64], + prefix: &str| + -> Result<(), YfError> { + if let Ok(values) = serde_json::from_value::>(values_json.clone()) { + for (i, ts) in timestamps.iter().enumerate() { + let row = rows_map + .entry(*ts) + .or_insert_with(|| create_default_row(*ts)); + + let value = values + .get(i) + .and_then(|v| v.reported_value.and_then(|rv| rv.raw)); + + if key == format!("{prefix}TotalRevenue") { + row.total_revenue = + value.map(|v| f64_to_money_with_currency(v, currency.clone())); + } else if key == format!("{prefix}GrossProfit") { + row.gross_profit = + value.map(|v| f64_to_money_with_currency(v, currency.clone())); + } else if key == format!("{prefix}OperatingIncome") { + row.operating_income = + value.map(|v| f64_to_money_with_currency(v, currency.clone())); + } else if key == format!("{prefix}NetIncome") { + row.net_income = value.map(|v| f64_to_money_with_currency(v, currency.clone())); + } + } + } + Ok(()) + }; + + let result = fetch_timeseries_data( + client, + symbol, + quarterly, + cache_mode, + retry_override, + &keys, + endpoint_name, + create_default_row, + process_item, + ) + .await?; + + Ok(result) +} + +#[allow(clippy::too_many_lines)] +#[allow(clippy::cognitive_complexity)] +pub(super) async fn balance_sheet( + client: &YfClient, + symbol: &str, + quarterly: bool, + currency: Currency, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result, YfError> { + use serde::Deserialize; + + use crate::core::wire::{RawNum, RawNumU64}; + + #[derive(Deserialize)] + struct TimeseriesValueF64 { + #[serde(rename = "reportedValue")] + reported_value: Option>, + } + #[derive(Deserialize)] + struct TimeseriesValueU64 { + #[serde(rename = "reportedValue")] + reported_value: Option, + } + + let keys = [ + "TotalAssets", + "TotalLiabilitiesNetMinorityInterest", + "StockholdersEquity", + "CashAndCashEquivalents", + "LongTermDebt", + "OrdinarySharesNumber", + ]; + let endpoint_name = "balance_sheet"; + + let create_default_row = |period_end: i64| BalanceSheetRow { + period: string_to_period(&i64_to_datetime(period_end).format("%Y-%m-%d").to_string()), + total_assets: None, + total_liabilities: None, + total_equity: None, + cash: None, + long_term_debt: None, + shares_outstanding: None, + }; + + let process_item = |key: &str, + values_json: &serde_json::Value, + rows_map: &mut BTreeMap, + timestamps: &[i64], + prefix: &str| + -> Result<(), YfError> { + if key.ends_with("OrdinarySharesNumber") { + if let Ok(values) = + serde_json::from_value::>(values_json.clone()) + { + for (i, ts) in timestamps.iter().enumerate() { + let row = rows_map + .entry(*ts) + .or_insert_with(|| create_default_row(*ts)); + row.shares_outstanding = values + .get(i) + .and_then(|v| v.reported_value.and_then(|rv| rv.raw)); + } + } + } else if let Ok(values) = + serde_json::from_value::>(values_json.clone()) + { + for (i, ts) in timestamps.iter().enumerate() { + let row = rows_map + .entry(*ts) + .or_insert_with(|| create_default_row(*ts)); + + let value = values + .get(i) + .and_then(|v| v.reported_value.and_then(|rv| rv.raw)); + + if key == format!("{prefix}TotalAssets") { + row.total_assets = + value.map(|v| f64_to_money_with_currency(v, currency.clone())); + } else if key == format!("{prefix}TotalLiabilitiesNetMinorityInterest") { + row.total_liabilities = + value.map(|v| f64_to_money_with_currency(v, currency.clone())); + } else if key == format!("{prefix}StockholdersEquity") { + row.total_equity = + value.map(|v| f64_to_money_with_currency(v, currency.clone())); + } else if key == format!("{prefix}CashAndCashEquivalents") { + row.cash = value.map(|v| f64_to_money_with_currency(v, currency.clone())); + } else if key == format!("{prefix}LongTermDebt") { + row.long_term_debt = + value.map(|v| f64_to_money_with_currency(v, currency.clone())); + } + } + } + Ok(()) + }; + + fetch_timeseries_data( + client, + symbol, + quarterly, + cache_mode, + retry_override, + &keys, + endpoint_name, + create_default_row, + process_item, + ) + .await +} + +#[allow(clippy::too_many_lines)] +pub(super) async fn cashflow( + client: &YfClient, + symbol: &str, + quarterly: bool, + currency: Currency, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result, YfError> { + use serde::Deserialize; + + use crate::core::wire::RawNum; + + #[derive(Deserialize)] + struct TimeseriesValueF64 { + #[serde(rename = "reportedValue")] + reported_value: Option>, + } + + let keys = [ + "OperatingCashFlow", + "CapitalExpenditure", + "FreeCashFlow", + "NetIncome", + ]; + let endpoint_name = "cash_flow"; + + let create_default_row = |period_end: i64| CashflowRow { + period: string_to_period(&i64_to_datetime(period_end).format("%Y-%m-%d").to_string()), + operating_cashflow: None, + capital_expenditures: None, + free_cash_flow: None, + net_income: None, + }; + + let process_item = |key: &str, + values_json: &serde_json::Value, + rows_map: &mut BTreeMap, + timestamps: &[i64], + prefix: &str| + -> Result<(), YfError> { + if let Ok(values) = serde_json::from_value::>(values_json.clone()) { + for (i, ts) in timestamps.iter().enumerate() { + let row = rows_map + .entry(*ts) + .or_insert_with(|| create_default_row(*ts)); + + let value = values + .get(i) + .and_then(|v| v.reported_value.and_then(|rv| rv.raw)); + + if key == format!("{prefix}OperatingCashFlow") { + row.operating_cashflow = + value.map(|v| f64_to_money_with_currency(v, currency.clone())); + } else if key == format!("{prefix}CapitalExpenditure") { + row.capital_expenditures = + value.map(|v| f64_to_money_with_currency(v, currency.clone())); + } else if key == format!("{prefix}FreeCashFlow") { + row.free_cash_flow = + value.map(|v| f64_to_money_with_currency(v, currency.clone())); + } else if key == format!("{prefix}NetIncome") { + row.net_income = value.map(|v| f64_to_money_with_currency(v, currency.clone())); + } + } + } + Ok(()) + }; + + let mut result = fetch_timeseries_data( + client, + symbol, + quarterly, + cache_mode, + retry_override, + &keys, + endpoint_name, + create_default_row, + process_item, + ) + .await?; + + // After filling values, calculate FCF if it's missing. + for row in &mut result { + if row.free_cash_flow.is_none() + && let (Some(ocf), Some(capex)) = ( + row.operating_cashflow.clone(), + row.capital_expenditures.clone(), + ) + { + // In timeseries API, capex is negative for cash outflow. + row.free_cash_flow = ocf.try_add(&capex).ok(); + } + } + + Ok(result) +} + +pub(super) async fn earnings( + client: &YfClient, + symbol: &str, + currency: Currency, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result { + let root = fetch_modules(client, symbol, "earnings", cache_mode, retry_override).await?; + let e = root + .earnings + .ok_or_else(|| YfError::MissingData("earnings missing".into()))?; + + let yearly = e + .financials_chart + .as_ref() + .and_then(|fc| fc.yearly.as_ref()) + .map(|v| { + v.iter() + .filter_map(|y| { + y.date.and_then(|date| { + i32::try_from(date).ok().map(|year| EarningsYear { + year, + revenue: y.revenue.as_ref().and_then(|x| { + x.raw + .map(|v| f64_to_money_with_currency(v, currency.clone())) + }), + earnings: y.earnings.as_ref().and_then(|x| { + x.raw + .map(|v| f64_to_money_with_currency(v, currency.clone())) + }), + }) + }) + }) + .collect() + }) + .unwrap_or_default(); + + let quarterly = e + .financials_chart + .as_ref() + .and_then(|fc| fc.quarterly.as_ref()) + .map(|v| { + v.iter() + .map(|q| EarningsQuarter { + period: string_to_period(&q.date.clone().unwrap_or_default()), + revenue: q.revenue.as_ref().and_then(|x| { + x.raw + .map(|v| f64_to_money_with_currency(v, currency.clone())) + }), + earnings: q.earnings.as_ref().and_then(|x| { + x.raw + .map(|v| f64_to_money_with_currency(v, currency.clone())) + }), + }) + .collect() + }) + .unwrap_or_default(); + + let quarterly_eps = e + .earnings_chart + .as_ref() + .and_then(|ec| ec.quarterly.as_ref()) + .map(|v| { + v.iter() + .map(|q| EarningsQuarterEps { + period: string_to_period(&q.date.clone().unwrap_or_default()), + actual: q.actual.as_ref().and_then(|x| { + x.raw + .map(|v| f64_to_money_with_currency(v, currency.clone())) + }), + estimate: q.estimate.as_ref().and_then(|x| { + x.raw + .map(|v| f64_to_money_with_currency(v, currency.clone())) + }), + }) + .collect() + }) + .unwrap_or_default(); + + Ok(Earnings { + yearly, + quarterly, + quarterly_eps, + }) +} + +pub(super) async fn calendar( + client: &YfClient, + symbol: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result { + let root = fetch_modules(client, symbol, "calendarEvents", cache_mode, retry_override).await?; + let calendar_events = root + .calendar_events + .ok_or_else(|| YfError::MissingData("calendarEvents missing".into()))?; + + let earnings_dates = calendar_events + .earnings + .and_then(|e| e.earnings_date) + .unwrap_or_default() + .into_iter() + .filter_map(|d| d.raw.map(i64_to_datetime)) + .collect(); + + Ok(super::Calendar { + earnings_dates, + ex_dividend_date: calendar_events + .ex_dividend_date + .and_then(|x| x.raw.map(i64_to_datetime)), + dividend_payment_date: calendar_events + .dividend_date + .and_then(|x| x.raw.map(i64_to_datetime)), + }) +} + +pub(super) async fn shares( + client: &YfClient, + symbol: &str, + start: Option>, + end: Option>, + quarterly: bool, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result, YfError> { + let end_ts = end.unwrap_or_else(Utc::now).timestamp(); + let start_ts = start + .unwrap_or_else(|| Utc::now() - Duration::days(548)) + .timestamp(); + + let type_key = if quarterly { + "quarterlyBasicAverageShares" + } else { + "annualBasicAverageShares" + }; + + let mut url = client.base_timeseries().join(symbol)?; + url.query_pairs_mut() + .append_pair("symbol", symbol) + .append_pair("type", type_key) + .append_pair("period1", &start_ts.to_string()) + .append_pair("period2", &end_ts.to_string()); + + client.ensure_credentials().await?; + if let Some(crumb) = client.crumb().await { + url.query_pairs_mut().append_pair("crumb", &crumb); + } + + let body = if cache_mode == CacheMode::Use { + if let Some(cached) = client.cache_get(&url).await { + cached + } else { + let resp = client + .send_with_retry(client.http().get(url.clone()), retry_override) + .await?; + let endpoint = format!("timeseries_{type_key}"); + let text = crate::core::net::get_text(resp, &endpoint, symbol, "json").await?; + if cache_mode != CacheMode::Bypass { + client.cache_put(&url, &text, None).await; + } + text + } + } else { + let resp = client + .send_with_retry(client.http().get(url.clone()), retry_override) + .await?; + let endpoint = format!("timeseries_{type_key}"); + let text = crate::core::net::get_text(resp, &endpoint, symbol, "json").await?; + if cache_mode != CacheMode::Bypass { + client.cache_put(&url, &text, None).await; + } + text + }; + + let envelope: TimeseriesEnvelope = serde_json::from_str(&body).map_err(YfError::Json)?; + + let result_data: Option = envelope + .timeseries + .and_then(|ts| ts.result) + .and_then(|mut v| v.pop()); + + let Some(TimeseriesData { + timestamp: Some(timestamps), + values: mut values_map, + .. + }) = result_data + else { + return Ok(vec![]); + }; + + let Some(values_json) = values_map.remove(type_key) else { + return Ok(vec![]); + }; + + let values: Vec = + serde_json::from_value(values_json).map_err(YfError::Json)?; + + let counts = timestamps + .into_iter() + .zip(values.into_iter()) + .filter_map(|(ts, val)| { + val.reported_value + .and_then(|rv| rv.raw) + .map(|shares| ShareCount { + date: i64_to_datetime(ts), + shares, + }) + }) + .collect(); + + Ok(counts) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/fundamentals/fetch.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/fundamentals/fetch.rs new file mode 100644 index 0000000..895039b --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/fundamentals/fetch.rs @@ -0,0 +1,26 @@ +use super::wire::V10Result; +use crate::core::{ + YfClient, YfError, + client::{CacheMode, RetryConfig}, + quotesummary, +}; + +/* ---------- Single focused fetch with crumb + retry ---------- */ + +pub(super) async fn fetch_modules( + client: &YfClient, + symbol: &str, + modules: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result { + quotesummary::fetch_module_result( + client, + symbol, + modules, + "fundamentals", + cache_mode, + retry_override, + ) + .await +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/fundamentals/mod.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/fundamentals/mod.rs new file mode 100644 index 0000000..9942e08 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/fundamentals/mod.rs @@ -0,0 +1,196 @@ +mod api; +mod model; + +mod fetch; +mod wire; + +pub use model::{ + BalanceSheetRow, Calendar, CashflowRow, Earnings, EarningsQuarter, EarningsQuarterEps, + EarningsYear, IncomeStatementRow, ShareCount, +}; + +use crate::core::{ + YfClient, YfError, + client::{CacheMode, RetryConfig}, +}; +use paft::money::Currency; + +/// A builder for fetching fundamental financial data (statements, earnings, etc.). +pub struct FundamentalsBuilder { + client: YfClient, + symbol: String, + cache_mode: CacheMode, + retry_override: Option, +} + +impl FundamentalsBuilder { + /// Creates a new `FundamentalsBuilder` for a given symbol. + pub fn new(client: &YfClient, symbol: impl Into) -> Self { + Self { + client: client.clone(), + symbol: symbol.into(), + cache_mode: CacheMode::Use, + retry_override: None, + } + } + + /// Sets the cache mode for this specific API call. + #[must_use] + pub const fn cache_mode(mut self, mode: CacheMode) -> Self { + self.cache_mode = mode; + self + } + + /// Overrides the default retry policy for this specific API call. + #[must_use] + pub fn retry_policy(mut self, cfg: Option) -> Self { + self.retry_override = cfg; + self + } + + /// Fetches the income statement. + /// + /// Set `quarterly` to `true` to get quarterly reports, or `false` for annual reports. + /// Provide `Some(currency)` to override the inferred reporting currency; pass `None` + /// to use the cached profile-based heuristic. + /// + /// # Errors + /// + /// Returns a `YfError` if the network request fails or the API response cannot be parsed. + pub async fn income_statement( + &self, + quarterly: bool, + override_currency: Option, + ) -> Result, YfError> { + let currency = self + .client + .reporting_currency(&self.symbol, override_currency) + .await; + + api::income_statement( + &self.client, + &self.symbol, + quarterly, + currency, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } + + /// Fetches the balance sheet. + /// + /// Set `quarterly` to `true` to get quarterly reports, or `false` for annual reports. + /// Provide `Some(currency)` to override the inferred reporting currency; pass `None` + /// to use the cached profile-based heuristic. + /// + /// # Errors + /// + /// Returns a `YfError` if the network request fails or the API response cannot be parsed. + pub async fn balance_sheet( + &self, + quarterly: bool, + override_currency: Option, + ) -> Result, YfError> { + let currency = self + .client + .reporting_currency(&self.symbol, override_currency) + .await; + + api::balance_sheet( + &self.client, + &self.symbol, + quarterly, + currency, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } + + /// Fetches the cash flow statement. + /// + /// Set `quarterly` to `true` to get quarterly reports, or `false` for annual reports. + /// Provide `Some(currency)` to override the inferred reporting currency; pass `None` + /// to use the cached profile-based heuristic. + /// + /// # Errors + /// + /// Returns a `YfError` if the network request fails or the API response cannot be parsed. + pub async fn cashflow( + &self, + quarterly: bool, + override_currency: Option, + ) -> Result, YfError> { + let currency = self + .client + .reporting_currency(&self.symbol, override_currency) + .await; + + api::cashflow( + &self.client, + &self.symbol, + quarterly, + currency, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } + + /// Fetches earnings history and estimates. + /// + /// # Errors + /// + /// Returns a `YfError` if the network request fails or the API response cannot be parsed. + pub async fn earnings(&self, override_currency: Option) -> Result { + let currency = self + .client + .reporting_currency(&self.symbol, override_currency) + .await; + + api::earnings( + &self.client, + &self.symbol, + currency, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } + + /// Fetches corporate calendar events like earnings dates. + /// + /// # Errors + /// + /// Returns a `YfError` if the network request fails or the API response cannot be parsed. + pub async fn calendar(&self) -> Result { + api::calendar( + &self.client, + &self.symbol, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } + + /// Fetches the historical number of shares outstanding. + /// + /// If `quarterly` is true, fetches quarterly data, otherwise annual data is fetched. + /// + /// # Errors + /// + /// Returns a `YfError` if the network request fails or the API response cannot be parsed. + pub async fn shares(&self, quarterly: bool) -> Result, YfError> { + api::shares( + &self.client, + &self.symbol, + None, + None, + quarterly, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/fundamentals/model.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/fundamentals/model.rs new file mode 100644 index 0000000..d6c7a72 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/fundamentals/model.rs @@ -0,0 +1,8 @@ +// Re-export types from paft without using prelude +pub use paft::fundamentals::analysis::{ + Earnings, EarningsQuarter, EarningsQuarterEps, EarningsYear, +}; +pub use paft::fundamentals::profile::ShareCount; +pub use paft::fundamentals::statements::{ + BalanceSheetRow, Calendar, CashflowRow, IncomeStatementRow, +}; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/fundamentals/wire.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/fundamentals/wire.rs new file mode 100644 index 0000000..92e0093 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/fundamentals/wire.rs @@ -0,0 +1,131 @@ +use crate::core::wire::{RawDate, RawNum, RawNumU64}; +use serde::Deserialize; + +/* ---------------- Serde mapping (only what we need) ---------------- */ + +#[derive(Deserialize)] +pub struct V10Result { + /* income */ + #[allow(dead_code)] + #[serde(rename = "incomeStatementHistory")] + pub(crate) income_statement_history: Option, + #[allow(dead_code)] + #[serde(rename = "incomeStatementHistoryQuarterly")] + pub(crate) income_statement_history_quarterly: Option, + + /* earnings + calendar */ + pub(crate) earnings: Option, + #[serde(rename = "calendarEvents")] + pub(crate) calendar_events: Option, +} + +/* --- income --- */ +#[derive(Deserialize)] +pub struct IncomeHistoryNode { + #[allow(dead_code)] + #[serde(rename = "incomeStatementHistory")] + pub(crate) income_statement_history: Option>, +} + +#[derive(Deserialize)] +pub struct IncomeRowNode { + #[allow(dead_code)] + #[serde(rename = "endDate")] + pub(crate) end_date: Option, + #[allow(dead_code)] + #[serde(rename = "totalRevenue")] + pub(crate) total_revenue: Option>, + #[allow(dead_code)] + #[serde(rename = "grossProfit")] + pub(crate) gross_profit: Option>, + #[allow(dead_code)] + #[serde(rename = "operatingIncome")] + pub(crate) operating_income: Option>, + #[allow(dead_code)] + #[serde(rename = "netIncome")] + pub(crate) net_income: Option>, +} + +/* --- earnings --- */ +#[derive(Deserialize)] +pub struct EarningsNode { + #[serde(rename = "financialsChart")] + pub(crate) financials_chart: Option, + #[serde(rename = "earningsChart")] + pub(crate) earnings_chart: Option, +} + +#[derive(Deserialize)] +pub struct FinancialsChartNode { + pub(crate) yearly: Option>, + pub(crate) quarterly: Option>, +} + +#[derive(Deserialize)] +pub struct FinancialYearNode { + pub(crate) date: Option, + pub(crate) revenue: Option>, + pub(crate) earnings: Option>, +} + +#[derive(Deserialize)] +pub struct FinancialQuarterNode { + pub(crate) date: Option, + pub(crate) revenue: Option>, + pub(crate) earnings: Option>, +} + +#[derive(Deserialize)] +pub struct EarningsChartNode { + pub(crate) quarterly: Option>, +} + +#[derive(Deserialize)] +pub struct EpsQuarterNode { + pub(crate) date: Option, + pub(crate) actual: Option>, + pub(crate) estimate: Option>, +} + +/* --- calendar --- */ +#[derive(Deserialize)] +pub struct CalendarEventsNode { + pub(crate) earnings: Option, + #[serde(rename = "exDividendDate")] + pub(crate) ex_dividend_date: Option, + #[serde(rename = "dividendDate")] + pub(crate) dividend_date: Option, +} + +#[derive(Deserialize)] +#[allow(clippy::struct_field_names)] +pub struct CalendarEarningsNode { + #[serde(rename = "earningsDate")] + pub(crate) earnings_date: Option>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TimeseriesEnvelope { + pub(crate) timeseries: Option, +} + +#[derive(Deserialize)] +pub struct TimeseriesResult { + pub(crate) result: Option>, +} + +#[derive(Deserialize)] +pub struct TimeseriesData { + pub(crate) timestamp: Option>, + #[allow(dead_code)] + meta: serde_json::Value, + #[serde(flatten)] + pub(crate) values: std::collections::HashMap, +} + +#[derive(Deserialize)] +pub struct TimeseriesValue { + #[serde(rename = "reportedValue")] + pub(crate) reported_value: Option, +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/builder.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/builder.rs new file mode 100644 index 0000000..65f87ad --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/builder.rs @@ -0,0 +1,241 @@ +mod actions; +mod adjust; +mod assemble; +mod fetch; + +use crate::core::client::{CacheMode, RetryConfig}; +// use crate::core::conversions::f64_to_money_with_currency_str; +use crate::core::{YfClient, YfError}; +use crate::history::wire::MetaNode; +use chrono_tz::Tz; +use paft::market::action::Action; +use paft::market::requests::history::{Interval, Range}; +use paft::market::responses::history::{Candle, HistoryMeta, HistoryResponse}; + +use actions::extract_actions; +use adjust::cumulative_split_after; +use assemble::assemble_candles; +use fetch::fetch_chart; + +/// A builder for fetching historical price data for a single symbol. +/// +/// This builder provides fine-grained control over the parameters for a historical +/// data request, including the time range, interval, and data adjustments. +#[derive(Clone)] +#[allow(clippy::struct_excessive_bools)] +pub struct HistoryBuilder { + #[doc(hidden)] + pub(crate) client: YfClient, + #[doc(hidden)] + pub(crate) symbol: String, + #[doc(hidden)] + pub(crate) range: Option, + #[doc(hidden)] + pub(crate) period: Option<(i64, i64)>, + #[doc(hidden)] + pub(crate) interval: Interval, + #[doc(hidden)] + pub(crate) auto_adjust: bool, + #[doc(hidden)] + pub(crate) include_prepost: bool, + #[doc(hidden)] + pub(crate) include_actions: bool, + #[doc(hidden)] + pub(crate) keepna: bool, + #[doc(hidden)] + pub(crate) cache_mode: CacheMode, + #[doc(hidden)] + pub(crate) retry_override: Option, +} + +impl HistoryBuilder { + /// Creates a new `HistoryBuilder` for a given symbol. + pub fn new(client: &YfClient, symbol: impl Into) -> Self { + Self { + client: client.clone(), + symbol: symbol.into(), + range: Some(Range::M6), + period: None, + interval: Interval::D1, + auto_adjust: true, + include_prepost: false, + include_actions: true, + keepna: false, + cache_mode: CacheMode::Use, + retry_override: None, + } + } + + /// Sets the cache mode for this specific API call. + #[must_use] + pub const fn cache_mode(mut self, mode: CacheMode) -> Self { + self.cache_mode = mode; + self + } + + /// Overrides the default retry policy for this specific API call. + #[must_use] + pub fn retry_policy(mut self, cfg: Option) -> Self { + self.retry_override = cfg; + self + } + + /// Sets a relative time range for the request (e.g., `1y`, `6mo`). + /// + /// This will override any previously set period using `between()`. + #[must_use] + pub const fn range(mut self, range: Range) -> Self { + self.period = None; + self.range = Some(range); + self + } + + /// Sets an absolute time period for the request using start and end timestamps. + /// + /// This will override any previously set range using `range()`. + #[must_use] + pub const fn between( + mut self, + start: chrono::DateTime, + end: chrono::DateTime, + ) -> Self { + self.range = None; + self.period = Some((start.timestamp(), end.timestamp())); + self + } + + /// Sets the time interval for each data point (candle). + #[must_use] + pub const fn interval(mut self, interval: Interval) -> Self { + self.interval = interval; + self + } + + /// Sets whether to automatically adjust prices for splits and dividends. (Default: `true`) + #[must_use] + pub const fn auto_adjust(mut self, yes: bool) -> Self { + self.auto_adjust = yes; + self + } + + /// Sets whether to include pre-market and post-market data for intraday intervals. (Default: `false`) + #[must_use] + pub const fn prepost(mut self, yes: bool) -> Self { + self.include_prepost = yes; + self + } + + /// Sets whether to include corporate actions (dividends and splits) in the response. (Default: `true`) + #[must_use] + pub const fn actions(mut self, yes: bool) -> Self { + self.include_actions = yes; + self + } + + /// Sets whether to keep data rows that have missing OHLC values. (Default: `false`) + /// + /// If `true`, missing values are represented as `f64::NAN`. If `false`, rows with any missing + /// OHLC values are dropped. + #[must_use] + pub const fn keepna(mut self, yes: bool) -> Self { + self.keepna = yes; + self + } + + /// Executes the request and returns only the price candles. + /// + /// # Errors + /// + /// Returns a `YfError` if the network request fails or the API response cannot be parsed. + pub async fn fetch(self) -> Result, YfError> { + let resp = self.fetch_full().await?; + Ok(resp.candles) + } + + /// Executes the request and returns the full response, including candles, actions, and metadata. + /// + /// # Errors + /// + /// Returns a `YfError` if the network request fails, the API returns an error, + /// or the response cannot be parsed. + #[cfg_attr( + feature = "tracing", + tracing::instrument( + skip(self), + err, + fields( + symbol = %self.symbol, + interval = %format!("{:?}", self.interval), + range = %self + .range + .as_ref() + .map_or_else(|| "period".into(), |r| format!("{r:?}")) + ) + ) + )] + pub async fn fetch_full(self) -> Result { + // 1) Fetch and parse the /chart payload into owned blocks + let fetched = fetch_chart( + &self.client, + &self.symbol, + self.range, + self.period, + self.interval, + self.include_actions, + self.include_prepost, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await?; + + // 2) Corporate actions & split ratios + let reporting_currency = self.client.reporting_currency(&self.symbol, None).await; + + let (mut actions_out, split_events) = + extract_actions(fetched.events.as_ref(), &reporting_currency); + + // 3) Cumulative split factors after each bar + let cum_split_after = cumulative_split_after(&fetched.ts, &split_events); + + // 4) Assemble candles (+ raw close) with/without adjustments + let currency = fetched.meta.as_ref().and_then(|m| m.currency.as_deref()); + let candles = assemble_candles( + &fetched.ts, + &fetched.quote, + &fetched.adjclose, + self.auto_adjust, + self.keepna, + &cum_split_after, + currency, + ); + + // ensure actions sorted (extract_actions already sorts, keep consistent) + actions_out.sort_by_key(|a| match a { + Action::Dividend { ts, .. } + | Action::Split { ts, .. } + | Action::CapitalGain { ts, .. } => ts.timestamp(), + }); + + // 5) Map metadata + let meta_out = map_meta(fetched.meta.as_ref()); + + Ok(HistoryResponse { + candles, + actions: actions_out, + adjusted: self.auto_adjust, + meta: meta_out, + }) + } +} + +/* --- tiny private helper --- */ + +fn map_meta(m: Option<&MetaNode>) -> Option { + m.as_ref().map(|mm| HistoryMeta { + timezone: mm + .timezone + .as_ref() + .and_then(|tz_str| tz_str.parse::().ok()), + utc_offset_seconds: mm.gmtoffset, + }) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/builder/actions.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/builder/actions.rs new file mode 100644 index 0000000..30b0c01 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/builder/actions.rs @@ -0,0 +1,79 @@ +use crate::core::conversions::{f64_to_money_with_currency, i64_to_datetime}; +use crate::history::wire::Events; +use paft::market::action::Action; +use paft::money::Currency; + +#[allow(clippy::cast_possible_truncation)] +pub fn extract_actions( + events: Option<&Events>, + currency: &Currency, +) -> (Vec, Vec<(i64, f64)>) { + let mut out: Vec = Vec::new(); + let mut split_events: Vec<(i64, f64)> = Vec::new(); + + let Some(ev) = events else { + return (out, split_events); + }; + + if let Some(divs) = ev.dividends.as_ref() { + for (k, d) in divs { + let ts = k.parse::().unwrap_or_else(|_| d.date.unwrap_or(0)); + if let Some(amount) = d.amount { + out.push(Action::Dividend { + ts: i64_to_datetime(ts), + amount: f64_to_money_with_currency(amount, currency.clone()), + }); + } + } + } + + if let Some(gains) = ev.capital_gains.as_ref() { + for (k, g) in gains { + let ts = k.parse::().unwrap_or_else(|_| g.date.unwrap_or(0)); + if let Some(gain) = g.amount { + out.push(Action::CapitalGain { + ts: i64_to_datetime(ts), + gain: f64_to_money_with_currency(gain, currency.clone()), + }); + } + } + } + + if let Some(splits) = ev.splits.as_ref() { + for (k, s) in splits { + let ts = k.parse::().unwrap_or_else(|_| s.date.unwrap_or(0)); + let (num, den) = if let (Some(n), Some(d)) = (s.numerator, s.denominator) { + (n as u32, d as u32) + } else if let Some(r) = s.split_ratio.as_deref() { + let mut it = r.split('/'); + let n = it.next().and_then(|x| x.parse::().ok()).unwrap_or(1); + let d = it.next().and_then(|x| x.parse::().ok()).unwrap_or(1); + (n, d) + } else { + (1, 1) + }; + + out.push(Action::Split { + ts: i64_to_datetime(ts), + numerator: num, + denominator: den, + }); + + let ratio = if den == 0 { + 1.0 + } else { + f64::from(num) / f64::from(den) + }; + split_events.push((ts, ratio)); + } + } + + out.sort_by_key(|a| match a { + Action::Dividend { ts, .. } | Action::Split { ts, .. } | Action::CapitalGain { ts, .. } => { + ts.timestamp() + } + }); + split_events.sort_by_key(|(ts, _)| *ts); + + (out, split_events) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/builder/adjust.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/builder/adjust.rs new file mode 100644 index 0000000..23bc7ac --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/builder/adjust.rs @@ -0,0 +1,30 @@ +pub fn cumulative_split_after(ts: &[i64], split_events: &[(i64, f64)]) -> Vec { + let mut out = vec![1.0; ts.len()]; + if split_events.is_empty() || ts.is_empty() { + return out; + } + + let mut sp_idx = split_events.len(); + let mut running: f64 = 1.0; + + for i in (0..ts.len()).rev() { + while sp_idx > 0 && split_events[sp_idx - 1].0 > ts[i] { + sp_idx -= 1; + running *= split_events[sp_idx].1; + } + out[i] = running; + } + out +} + +pub fn price_factor_for_row( + i: usize, + adjclose_i: Option, + close_i: Option, + cum_split_after: &[f64], +) -> f64 { + match (adjclose_i, close_i) { + (Some(adj), Some(c)) if c != 0.0 => adj / c, + _ => 1.0 / cum_split_after[i].max(1e-12), + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/builder/assemble.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/builder/assemble.rs new file mode 100644 index 0000000..665d6a9 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/builder/assemble.rs @@ -0,0 +1,116 @@ +use crate::core::conversions::{f64_to_money_with_currency_str, i64_to_datetime}; +use crate::history::wire::QuoteBlock; +use paft::market::responses::history::Candle; + +use super::adjust::price_factor_for_row; + +pub fn assemble_candles( + ts: &[i64], + q: &QuoteBlock, + adj: &[Option], + auto_adjust: bool, + keepna: bool, + cum_split_after: &[f64], + currency: Option<&str>, +) -> Vec { + let mut out = Vec::new(); + + for (i, &t) in ts.iter().enumerate() { + let getter_f64 = |v: &Vec>| v.get(i).and_then(|x| *x); + let mut open = getter_f64(&q.open); + let mut high = getter_f64(&q.high); + let mut low = getter_f64(&q.low); + let mut close = getter_f64(&q.close); + let volume0 = q.volume.get(i).and_then(|x| *x); + + let raw_close_val = close.unwrap_or(f64::NAN); + + if auto_adjust { + let pf = price_factor_for_row(i, adj.get(i).and_then(|x| *x), close, cum_split_after); + + if let Some(v) = open.as_mut() { + *v *= pf; + } + if let Some(v) = high.as_mut() { + *v *= pf; + } + if let Some(v) = low.as_mut() { + *v *= pf; + } + if let Some(v) = close.as_mut() { + *v *= pf; + } + + let volume_adj = volume0.map(|v| { + #[allow(clippy::cast_precision_loss)] + let v_adj = (v as f64) * cum_split_after[i]; + if v_adj.is_finite() && v_adj >= 0.0 { + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + (v_adj.round() as u64) + } else { + v + } + }); + + if let (Some(ov), Some(hv), Some(lv), Some(cv)) = (open, high, low, close) { + out.push(Candle { + ts: i64_to_datetime(t), + open: f64_to_money_with_currency_str(ov, currency), + high: f64_to_money_with_currency_str(hv, currency), + low: f64_to_money_with_currency_str(lv, currency), + close: f64_to_money_with_currency_str(cv, currency), + close_unadj: if raw_close_val.is_finite() { + Some(f64_to_money_with_currency_str(raw_close_val, currency)) + } else { + None + }, + volume: volume_adj, + }); + } else if keepna { + out.push(Candle { + ts: i64_to_datetime(t), + open: f64_to_money_with_currency_str(open.unwrap_or(f64::NAN), currency), + high: f64_to_money_with_currency_str(high.unwrap_or(f64::NAN), currency), + low: f64_to_money_with_currency_str(low.unwrap_or(f64::NAN), currency), + close: f64_to_money_with_currency_str(close.unwrap_or(f64::NAN), currency), + close_unadj: if raw_close_val.is_finite() { + Some(f64_to_money_with_currency_str(raw_close_val, currency)) + } else { + None + }, + volume: volume0, + }); + } + } else if let (Some(ov), Some(hv), Some(lv), Some(cv)) = (open, high, low, close) { + out.push(Candle { + ts: i64_to_datetime(t), + open: f64_to_money_with_currency_str(ov, currency), + high: f64_to_money_with_currency_str(hv, currency), + low: f64_to_money_with_currency_str(lv, currency), + close: f64_to_money_with_currency_str(cv, currency), + close_unadj: if raw_close_val.is_finite() { + Some(f64_to_money_with_currency_str(raw_close_val, currency)) + } else { + None + }, + volume: volume0, + }); + } else if keepna { + out.push(Candle { + ts: i64_to_datetime(t), + open: f64_to_money_with_currency_str(open.unwrap_or(f64::NAN), currency), + high: f64_to_money_with_currency_str(high.unwrap_or(f64::NAN), currency), + low: f64_to_money_with_currency_str(low.unwrap_or(f64::NAN), currency), + close: f64_to_money_with_currency_str(close.unwrap_or(f64::NAN), currency), + close_unadj: if raw_close_val.is_finite() { + Some(f64_to_money_with_currency_str(raw_close_val, currency)) + } else { + None + }, + volume: volume0, + }); + } + } + + out +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/builder/fetch.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/builder/fetch.rs new file mode 100644 index 0000000..46ca658 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/builder/fetch.rs @@ -0,0 +1,130 @@ +use crate::core::client::{CacheMode, RetryConfig}; +use crate::history::wire::{Events, MetaNode, QuoteBlock}; + +pub struct Fetched { + pub ts: Vec, + pub quote: QuoteBlock, + pub adjclose: Vec>, + pub events: Option, + pub meta: Option, +} + +#[allow(clippy::too_many_arguments)] +pub async fn fetch_chart( + client: &crate::core::YfClient, + symbol: &str, + range: Option, + period: Option<(i64, i64)>, + interval: crate::core::Interval, + include_actions: bool, + include_prepost: bool, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result { + let mut url = client.base_chart().join(symbol)?; + { + let mut qp = url.query_pairs_mut(); + + if let Some((p1, p2)) = period { + if p1 >= p2 { + return Err(crate::core::YfError::InvalidDates); + } + qp.append_pair("period1", &p1.to_string()); + qp.append_pair("period2", &p2.to_string()); + } else if let Some(r) = range { + qp.append_pair("range", crate::core::models::range_as_str(r)); + } else { + return Err(crate::core::YfError::InvalidParams( + "no range or period set".into(), + )); + } + + qp.append_pair("interval", crate::core::models::interval_as_str(interval)); + if include_actions { + qp.append_pair("events", "div|split|capitalGains"); + } + qp.append_pair( + "includePrePost", + if include_prepost { "true" } else { "false" }, + ); + } + + if cache_mode == CacheMode::Use + && let Some(body) = client.cache_get(&url).await + { + return decode_chart(&body); + } + + let resp = client + .send_with_retry(client.http().get(url.clone()), retry_override) + .await?; + if !resp.status().is_success() { + let code = resp.status().as_u16(); + let url_s = url.to_string(); + return Err(match code { + 404 => crate::core::YfError::NotFound { url: url_s }, + 429 => crate::core::YfError::RateLimited { url: url_s }, + 500..=599 => crate::core::YfError::ServerError { + status: code, + url: url_s, + }, + _ => crate::core::YfError::Status { + status: code, + url: url_s, + }, + }); + } + + let body = crate::core::net::get_text(resp, "history_chart", symbol, "json").await?; + + if cache_mode != CacheMode::Bypass { + client.cache_put(&url, &body, None).await; + } + + decode_chart(&body) +} + +// NEW helper to keep fetch_chart compact +fn decode_chart(body: &str) -> Result { + let envelope: crate::history::wire::ChartEnvelope = + serde_json::from_str(body).map_err(crate::core::YfError::Json)?; + + let chart = envelope + .chart + .ok_or_else(|| crate::core::YfError::MissingData("missing chart".into()))?; + + if let Some(error) = chart.error { + return Err(crate::core::YfError::Api(format!( + "chart error: {} - {}", + error.code, error.description + ))); + } + + let result = chart + .result + .ok_or_else(|| crate::core::YfError::MissingData("missing result".into()))?; + + let first = result + .first() + .ok_or_else(|| crate::core::YfError::MissingData("empty result".into()))?; + + let quote = first + .indicators + .quote + .first() + .ok_or_else(|| crate::core::YfError::MissingData("missing quote".into()))?; + let adjclose = first + .indicators + .adjclose + .first() + .map(|a| a.adjclose.clone()) + .unwrap_or_default(); + + Ok(Fetched { + ts: first.timestamp.clone().unwrap_or_default(), + quote: quote.clone(), + adjclose, + events: first.events.clone(), + meta: first.meta.clone(), + }) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/mod.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/mod.rs new file mode 100644 index 0000000..bef6cfd --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/mod.rs @@ -0,0 +1,47 @@ +mod builder; +mod wire; + +pub use builder::HistoryBuilder; + +use crate::core::{HistoryRequest, HistoryResponse, HistoryService, YfClient, YfError}; +use core::future::Future; +use core::pin::Pin; + +impl HistoryService for YfClient { + fn fetch_full_history<'a>( + &'a self, + symbol: &'a str, + req: HistoryRequest, + ) -> Pin> + Send + 'a>> { + // Own everything the async block needs: + let client = self.clone(); // YfClient: Clone + let symbol = symbol.to_owned(); // own the symbol + Box::pin(async move { + // HistoryBuilder::new(&YfClient, impl Into) clones internally, + // so passing &client here is fine. + let mut hb = builder::HistoryBuilder::new(&client, &symbol) + .interval(req.interval) + .auto_adjust(req.auto_adjust) + .prepost(req.include_prepost) + .actions(req.include_actions) + .keepna(req.keepna); + + if let Some((p1, p2)) = req.period { + use chrono::{TimeZone, Utc}; + let start = Utc + .timestamp_opt(p1, 0) + .single() + .ok_or(YfError::InvalidParams("invalid period1".into()))?; + let end = Utc + .timestamp_opt(p2, 0) + .single() + .ok_or(YfError::InvalidParams("invalid period2".into()))?; + hb = hb.between(start, end); + } else if let Some(r) = req.range { + hb = hb.range(r); + } + + hb.fetch_full().await + }) + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/wire.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/wire.rs new file mode 100644 index 0000000..ec86509 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/history/wire.rs @@ -0,0 +1,163 @@ +use serde::Deserialize; +use serde::Deserializer; +use std::collections::BTreeMap; + +#[derive(Deserialize)] +pub struct ChartEnvelope { + pub(crate) chart: Option, +} + +#[derive(Deserialize)] +pub struct ChartNode { + pub(crate) result: Option>, + pub(crate) error: Option, +} + +#[derive(Deserialize)] +pub struct ChartError { + pub(crate) code: String, + pub(crate) description: String, +} + +#[derive(Deserialize)] +pub struct ChartResult { + #[serde(default)] + pub(crate) meta: Option, + #[serde(default)] + pub(crate) timestamp: Option>, + pub(crate) indicators: Indicators, + #[serde(default)] + pub(crate) events: Option, +} + +#[derive(Deserialize, Clone)] +pub struct MetaNode { + #[serde(default)] + pub(crate) timezone: Option, + #[serde(default)] + pub(crate) gmtoffset: Option, + #[serde(default)] + pub(crate) currency: Option, +} + +#[derive(Deserialize)] +pub struct Indicators { + #[serde(default)] + pub(crate) quote: Vec, + #[serde(default)] + pub(crate) adjclose: Vec, +} + +#[derive(Deserialize, Clone)] +pub struct QuoteBlock { + #[serde(default)] + pub(crate) open: Vec>, + #[serde(default)] + pub(crate) high: Vec>, + #[serde(default)] + pub(crate) low: Vec>, + #[serde(default)] + pub(crate) close: Vec>, + #[serde(default)] + pub(crate) volume: Vec>, +} + +#[derive(Deserialize, Clone)] +pub struct AdjCloseBlock { + #[serde(default)] + pub(crate) adjclose: Vec>, +} + +#[derive(Deserialize, Default, Clone)] +pub struct Events { + #[serde(default)] + pub(crate) dividends: Option>, + #[serde(default)] + pub(crate) splits: Option>, + #[serde(default, rename = "capitalGains")] + pub(crate) capital_gains: Option>, +} + +#[derive(Deserialize, Clone)] +pub struct DividendEvent { + pub(crate) amount: Option, + pub(crate) date: Option, +} + +#[derive(Deserialize, Clone)] +pub struct SplitEvent { + #[serde(default, deserialize_with = "de_opt_u64_from_mixed")] + pub(crate) numerator: Option, + #[serde(default, deserialize_with = "de_opt_u64_from_mixed")] + pub(crate) denominator: Option, + #[serde(rename = "splitRatio")] + pub(crate) split_ratio: Option, + pub(crate) date: Option, +} + +#[derive(Deserialize, Clone)] +pub struct CapitalGainEvent { + pub(crate) amount: Option, + pub(crate) date: Option, +} + +/// Accepts u64, integer-like f64 (e.g., 4.0), numeric strings ("4"), or null/missing. +/// Rounds floats and rejects non-finite or clearly non-integer floats. +fn de_opt_u64_from_mixed<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + use serde::de::Error; + use serde_json::Value; + + let v = Option::::deserialize(deserializer)?; + let some = match v { + None | Some(Value::Null) => return Ok(None), + Some(Value::Number(n)) => { + if let Some(u) = n.as_u64() { + Some(u) + } else if let Some(f) = n.as_f64() { + if !f.is_finite() { + return Err(D::Error::custom("non-finite float for split field")); + } + let r = f.round(); + // Require the float to be very close to an integer + if (f - r).abs() < 1e-9 && r >= 0.0 { + #[allow( + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + clippy::cast_precision_loss + )] + Some(r as u64) + } else { + return Err(D::Error::custom(format!( + "expected integer-like float for split field, got {f}" + ))); + } + } else { + return Err(D::Error::custom("unsupported number type for split field")); + } + } + Some(Value::String(s)) => { + let s = s.trim(); + if s.is_empty() { + None + } else { + match s.parse::() { + Ok(u) => Some(u), + Err(_) => { + return Err(D::Error::custom(format!( + "invalid numeric string '{s}' for split field" + ))); + } + } + } + } + Some(other) => { + return Err(D::Error::custom(format!( + "unexpected JSON type for split field: {other}" + ))); + } + }; + Ok(some) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/holders/api.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/holders/api.rs new file mode 100644 index 0000000..ecf4330 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/holders/api.rs @@ -0,0 +1,210 @@ +use super::model::{ + InsiderRosterHolder, InsiderTransaction, InstitutionalHolder, MajorHolder, + NetSharePurchaseActivity, +}; +use super::wire::V10Result; +use crate::core::wire::{from_raw, from_raw_date}; +use crate::core::{ + YfClient, YfError, + client::{CacheMode, RetryConfig}, + conversions::{ + i64_to_datetime, string_to_insider_position, string_to_transaction_type, + u64_to_money_with_currency, + }, + quotesummary, +}; +use chrono::DateTime; +use paft::money::Currency; + +#[inline] +#[allow(clippy::cast_precision_loss)] +const fn u64_to_f64(n: u64) -> f64 { + n as f64 +} + +const MODULES: &str = "institutionOwnership,fundOwnership,majorHoldersBreakdown,insiderTransactions,insiderHolders,netSharePurchaseActivity"; + +async fn fetch_holders_modules( + client: &YfClient, + symbol: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result { + quotesummary::fetch_module_result( + client, + symbol, + MODULES, + "holders", + cache_mode, + retry_override, + ) + .await +} + +pub(super) async fn major_holders( + client: &YfClient, + symbol: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result, YfError> { + let root = fetch_holders_modules(client, symbol, cache_mode, retry_override).await?; + let breakdown = root + .major_holders_breakdown + .ok_or_else(|| YfError::MissingData("majorHoldersBreakdown missing".into()))?; + + let mut result = Vec::new(); + + if let Some(v) = from_raw(breakdown.insiders_percent_held) { + result.push(MajorHolder { + category: "% of Shares Held by All Insiders".into(), + value: v, + }); + } + if let Some(v) = from_raw(breakdown.institutions_percent_held) { + result.push(MajorHolder { + category: "% of Shares Held by Institutions".into(), + value: v, + }); + } + if let Some(v) = from_raw(breakdown.institutions_float_percent_held) { + result.push(MajorHolder { + category: "% of Float Held by Institutions".into(), + value: v, + }); + } + if let Some(v) = from_raw(breakdown.institutions_count) { + result.push(MajorHolder { + category: "Number of Institutions Holding Shares".into(), + value: u64_to_f64(v), + }); + } + + Ok(result) +} + +fn map_ownership_list( + node: Option, + currency: &Currency, +) -> Vec { + node.and_then(|n| n.ownership_list) + .unwrap_or_default() + .into_iter() + .map(|h| InstitutionalHolder { + holder: h.organization.unwrap_or_default(), + shares: from_raw(h.shares), + date_reported: from_raw_date(h.date_reported).map_or_else( + || DateTime::from_timestamp(0, 0).unwrap_or_default(), + i64_to_datetime, + ), + pct_held: from_raw(h.pct_held), + value: from_raw(h.value).map(|v| u64_to_money_with_currency(v, currency.clone())), + }) + .collect() +} + +pub(super) async fn institutional_holders( + client: &YfClient, + symbol: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result, YfError> { + let root = fetch_holders_modules(client, symbol, cache_mode, retry_override).await?; + let currency = client.reporting_currency(symbol, None).await; + Ok(map_ownership_list(root.institution_ownership, ¤cy)) +} + +pub(super) async fn mutual_fund_holders( + client: &YfClient, + symbol: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result, YfError> { + let root = fetch_holders_modules(client, symbol, cache_mode, retry_override).await?; + let currency = client.reporting_currency(symbol, None).await; + Ok(map_ownership_list(root.fund_ownership, ¤cy)) +} + +pub(super) async fn insider_transactions( + client: &YfClient, + symbol: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result, YfError> { + let root = fetch_holders_modules(client, symbol, cache_mode, retry_override).await?; + let currency = client.reporting_currency(symbol, None).await; + let transactions = root + .insider_transactions + .and_then(|it| it.transactions) + .unwrap_or_default(); + + Ok(transactions + .into_iter() + .map(|t| InsiderTransaction { + insider: t.insider.unwrap_or_default(), + position: string_to_insider_position(&t.position.unwrap_or_default()), + transaction_type: string_to_transaction_type(&t.transaction.unwrap_or_default()), + shares: from_raw(t.shares), + value: from_raw(t.value).map(|v| u64_to_money_with_currency(v, currency.clone())), + transaction_date: from_raw_date(t.start_date).map_or_else( + || DateTime::from_timestamp(0, 0).unwrap_or_default(), + i64_to_datetime, + ), + url: t.url.unwrap_or_default(), + }) + .collect()) +} + +pub(super) async fn insider_roster_holders( + client: &YfClient, + symbol: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result, YfError> { + let root = fetch_holders_modules(client, symbol, cache_mode, retry_override).await?; + let holders = root + .insider_holders + .and_then(|ih| ih.holders) + .unwrap_or_default(); + + Ok(holders + .into_iter() + .map(|h| InsiderRosterHolder { + name: h.name.unwrap_or_default(), + position: string_to_insider_position(&h.relation.unwrap_or_default()), + most_recent_transaction: string_to_transaction_type( + &h.most_recent_transaction.unwrap_or_default(), + ), + latest_transaction_date: from_raw_date(h.latest_transaction_date).map_or_else( + || DateTime::from_timestamp(0, 0).unwrap_or_default(), + i64_to_datetime, + ), + shares_owned_directly: from_raw(h.shares_owned_directly), + position_direct_date: from_raw_date(h.position_direct_date).map_or_else( + || DateTime::from_timestamp(0, 0).unwrap_or_default(), + i64_to_datetime, + ), + }) + .collect()) +} + +pub(super) async fn net_share_purchase_activity( + client: &YfClient, + symbol: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result, YfError> { + let root = fetch_holders_modules(client, symbol, cache_mode, retry_override).await?; + Ok(root + .net_share_purchase_activity + .map(|n| NetSharePurchaseActivity { + period: crate::core::conversions::string_to_period(&n.period.unwrap_or_default()), + buy_shares: from_raw(n.buy_info_shares), + buy_count: from_raw(n.buy_info_count), + sell_shares: from_raw(n.sell_info_shares), + sell_count: from_raw(n.sell_info_count), + net_shares: from_raw(n.net_info_shares), + net_count: from_raw(n.net_info_count), + total_insider_shares: from_raw(n.total_insider_shares), + net_percent_insider_shares: from_raw(n.net_percent_insider_shares), + })) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/holders/mod.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/holders/mod.rs new file mode 100644 index 0000000..dbc9019 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/holders/mod.rs @@ -0,0 +1,139 @@ +mod api; +mod model; +mod wire; + +pub use model::{ + InsiderRosterHolder, InsiderTransaction, InstitutionalHolder, MajorHolder, + NetSharePurchaseActivity, +}; + +use crate::{ + YfClient, YfError, + core::client::{CacheMode, RetryConfig}, +}; + +/// A builder for fetching holder data for a specific symbol. +pub struct HoldersBuilder { + client: YfClient, + symbol: String, + cache_mode: CacheMode, + retry_override: Option, +} + +impl HoldersBuilder { + /// Creates a new `HoldersBuilder` for a given symbol. + pub fn new(client: &YfClient, symbol: impl Into) -> Self { + Self { + client: client.clone(), + symbol: symbol.into(), + cache_mode: CacheMode::Use, + retry_override: None, + } + } + + /// Sets the cache mode for this specific API call. + #[must_use] + pub const fn cache_mode(mut self, mode: CacheMode) -> Self { + self.cache_mode = mode; + self + } + + /// Overrides the default retry policy for this specific API call. + #[must_use] + pub fn retry_policy(mut self, cfg: Option) -> Self { + self.retry_override = cfg; + self + } + + /// Fetches the major holders breakdown (e.g., % insiders, % institutions). + /// + /// # Errors + /// + /// Returns a `YfError` if the network request fails or the API response cannot be parsed. + pub async fn major_holders(&self) -> Result, YfError> { + api::major_holders( + &self.client, + &self.symbol, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } + + /// Fetches a list of the top institutional holders. + /// + /// # Errors + /// + /// Returns a `YfError` if the network request fails or the API response cannot be parsed. + pub async fn institutional_holders(&self) -> Result, YfError> { + api::institutional_holders( + &self.client, + &self.symbol, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } + + /// Fetches a list of the top mutual fund holders. + /// + /// # Errors + /// + /// Returns a `YfError` if the network request fails or the API response cannot be parsed. + pub async fn mutual_fund_holders(&self) -> Result, YfError> { + api::mutual_fund_holders( + &self.client, + &self.symbol, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } + + /// Fetches a list of recent insider transactions. + /// + /// # Errors + /// + /// Returns a `YfError` if the network request fails or the API response cannot be parsed. + pub async fn insider_transactions(&self) -> Result, YfError> { + api::insider_transactions( + &self.client, + &self.symbol, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } + + /// Fetches a roster of company insiders and their holdings. + /// + /// # Errors + /// + /// Returns a `YfError` if the network request fails or the API response cannot be parsed. + pub async fn insider_roster_holders(&self) -> Result, YfError> { + api::insider_roster_holders( + &self.client, + &self.symbol, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } + + /// Fetches a summary of net insider purchase and sale activity. + /// + /// # Errors + /// + /// Returns a `YfError` if the network request fails or the API response cannot be parsed. + pub async fn net_share_purchase_activity( + &self, + ) -> Result, YfError> { + api::net_share_purchase_activity( + &self.client, + &self.symbol, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/holders/model.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/holders/model.rs new file mode 100644 index 0000000..7891d8f --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/holders/model.rs @@ -0,0 +1,5 @@ +// Re-export types from paft without using prelude +pub use paft::fundamentals::holders::{ + InsiderRosterHolder, InsiderTransaction, InstitutionalHolder, MajorHolder, + NetSharePurchaseActivity, +}; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/holders/wire.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/holders/wire.rs new file mode 100644 index 0000000..4a5a678 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/holders/wire.rs @@ -0,0 +1,109 @@ +use crate::core::wire::{RawDate, RawNum}; +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct V10Result { + #[serde(rename = "institutionOwnership")] + pub(crate) institution_ownership: Option, + #[serde(rename = "fundOwnership")] + pub(crate) fund_ownership: Option, + #[serde(rename = "majorHoldersBreakdown")] + pub(crate) major_holders_breakdown: Option, + #[serde(rename = "insiderTransactions")] + pub(crate) insider_transactions: Option, + #[serde(rename = "insiderHolders")] + pub(crate) insider_holders: Option, + #[serde(rename = "netSharePurchaseActivity")] + pub(crate) net_share_purchase_activity: Option, +} + +#[derive(Deserialize)] +pub struct OwnershipNode { + #[serde(rename = "ownershipList")] + pub(crate) ownership_list: Option>, +} + +#[derive(Deserialize)] +pub struct InstitutionalHolderNode { + pub(crate) organization: Option, + #[serde(rename = "position")] + pub(crate) shares: Option>, + #[serde(rename = "reportDate")] + pub(crate) date_reported: Option, + #[serde(rename = "pctHeld")] + pub(crate) pct_held: Option>, + pub(crate) value: Option>, +} + +#[derive(Deserialize)] +pub struct MajorHoldersBreakdownNode { + #[serde(rename = "insidersPercentHeld")] + pub(crate) insiders_percent_held: Option>, + #[serde(rename = "institutionsPercentHeld")] + pub(crate) institutions_percent_held: Option>, + #[serde(rename = "institutionsFloatPercentHeld")] + pub(crate) institutions_float_percent_held: Option>, + #[serde(rename = "institutionsCount")] + pub(crate) institutions_count: Option>, +} + +#[derive(Deserialize)] +pub struct InsiderTransactionsNode { + pub(crate) transactions: Option>, +} + +#[derive(Deserialize)] +pub struct InsiderTransactionNode { + #[serde(rename = "filerName")] + pub(crate) insider: Option, + #[serde(rename = "filerRelation")] + pub(crate) position: Option, + #[serde(rename = "transactionText")] + pub(crate) transaction: Option, + pub(crate) shares: Option>, + pub(crate) value: Option>, + #[serde(rename = "startDate")] + pub(crate) start_date: Option, + #[serde(rename = "filerUrl")] + pub(crate) url: Option, +} + +#[derive(Deserialize)] +pub struct InsiderHoldersNode { + pub(crate) holders: Option>, +} + +#[derive(Deserialize)] +pub struct InsiderRosterHolderNode { + pub(crate) name: Option, + pub(crate) relation: Option, + #[serde(rename = "transactionDescription")] + pub(crate) most_recent_transaction: Option, + #[serde(rename = "latestTransDate")] + pub(crate) latest_transaction_date: Option, + #[serde(rename = "positionDirect")] + pub(crate) shares_owned_directly: Option>, + #[serde(rename = "positionDirectDate")] + pub(crate) position_direct_date: Option, +} + +#[derive(Deserialize)] +pub struct NetSharePurchaseActivityNode { + pub(crate) period: Option, + #[serde(rename = "buyInfoShares")] + pub(crate) buy_info_shares: Option>, + #[serde(rename = "buyInfoCount")] + pub(crate) buy_info_count: Option>, + #[serde(rename = "sellInfoShares")] + pub(crate) sell_info_shares: Option>, + #[serde(rename = "sellInfoCount")] + pub(crate) sell_info_count: Option>, + #[serde(rename = "netInfoShares")] + pub(crate) net_info_shares: Option>, + #[serde(rename = "netInfoCount")] + pub(crate) net_info_count: Option>, + #[serde(rename = "totalInsiderShares")] + pub(crate) total_insider_shares: Option>, + #[serde(rename = "netPercentInsiderShares")] + pub(crate) net_percent_insider_shares: Option>, +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/lib.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/lib.rs new file mode 100644 index 0000000..b5cc0f8 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/lib.rs @@ -0,0 +1,187 @@ +//! # yfinance-rs +//! +//! An ergonomic, async-first Rust client for the unofficial Yahoo Finance API. +//! +//! This crate provides a simple and efficient way to fetch financial data from Yahoo Finance. +//! It is designed to feel familiar to users of the popular Python `yfinance` library, but +//! leverages Rust's powerful type system and async capabilities for performance and safety. +//! +//! ## Features +//! +//! ### Core Data +//! * **Historical Data**: Fetch daily, weekly, or monthly OHLCV data with automatic split/dividend adjustments. +//! * **Real-time Quotes**: Get live quote updates with detailed market information. +//! * **Fast Quotes**: Optimized quote fetching with essential data only (`fast_info`). +//! * **Multi-Symbol Downloads**: Concurrently download historical data for many symbols at once. +//! * **Batch Quotes**: Fetch quotes for multiple symbols efficiently. +//! +//! ### Corporate Actions & Dividends +//! * **Dividend History**: Fetch complete dividend payment history with amounts and dates. +//! * **Stock Splits**: Get stock split history with split ratios. +//! * **Capital Gains**: Retrieve capital gains distributions (especially for mutual funds). +//! * **All Corporate Actions**: Comprehensive access to dividends, splits, and capital gains in one call. +//! +//! ### Financial Statements & Fundamentals +//! * **Income Statements**: Access annual and quarterly income statements. +//! * **Balance Sheets**: Get annual and quarterly balance sheet data. +//! * **Cash Flow Statements**: Fetch annual and quarterly cash flow data. +//! * **Earnings Data**: Historical earnings, revenue estimates, and EPS data. +//! * **Shares Outstanding**: Historical data on shares outstanding (annual and quarterly). +//! * **Corporate Calendar**: Earnings dates, ex-dividend dates, and dividend payment dates. +//! +//! ### Options & Derivatives +//! * **Options Chains**: Fetch expiration dates and full option chains (calls and puts). +//! * **Option Contracts**: Detailed option contract information. +//! +//! ### Analysis & Research +//! * **Analyst Ratings**: Get price targets, recommendations, and upgrade/downgrade history. +//! * **Earnings Trends**: Detailed earnings and revenue estimates from analysts. +//! * **Recommendations Summary**: Summary of current analyst recommendations. +//! * **Upgrades/Downgrades**: History of analyst rating changes. +//! +//! ### Ownership & Holders +//! * **Major Holders**: Get major, institutional, and mutual fund holder data. +//! * **Institutional Holders**: Top institutional shareholders and their holdings. +//! * **Mutual Fund Holders**: Mutual fund ownership breakdown. +//! * **Insider Transactions**: Recent insider buying and selling activity. +//! * **Insider Roster**: Company insiders and their current holdings. +//! * **Net Share Activity**: Summary of insider purchase/sale activity. +//! +//! ### ESG & Sustainability +//! * **ESG Scores**: Fetch detailed Environmental, Social, and Governance ratings. +//! * **ESG Involvement**: Specific ESG involvement and controversy data. +//! +//! ### News & Information +//! * **Company News**: Retrieve the latest articles and press releases for a ticker. +//! * **Company Profiles**: Detailed information about companies, ETFs, and funds. +//! * **Search**: Find tickers by name or keyword. +//! +//! ### Real-time Streaming +//! * **WebSocket Streaming**: Get live quote updates using `WebSockets` (preferred method). +//! * **HTTP Polling**: Fallback polling method for real-time data. +//! * **Configurable Streaming**: Customize update frequency and change-only filtering. +//! +//! ### Advanced Features +//! * **Data Repair**: Automatic detection and repair of price outliers. +//! * **Data Rounding**: Control price precision and rounding. +//! * **Missing Data Handling**: Configurable handling of NA/missing values. +//! * **Back Adjustment**: Alternative price adjustment methods. +//! * **Historical Metadata**: Timezone and other metadata for historical data. +//! * **ISIN Lookup**: Get International Securities Identification Numbers. +//! +//! ### Developer Experience +//! * **Async API**: Built on `tokio` and `reqwest` for non-blocking I/O. +//! * **High-Level `Ticker` Interface**: A convenient, yfinance-like struct for accessing all data for a single symbol. +//! * **Builder Pattern**: Fluent builders for constructing complex queries. +//! * **Configurable Retries**: Automatic retries with exponential backoff for transient network errors. +//! * **Caching**: Configurable caching behavior for API responses. +//! * **Custom Timeouts**: Configurable request timeouts and connection settings. +//! +//! ## Quick Start +//! +//! To get started, add `yfinance-rs` to your `Cargo.toml`: +//! +//! ```toml +//! [dependencies] +//! yfinance-rs = "0.7.2" +//! tokio = { version = "1", features = ["full"] } +//! ``` +//! +//! Then, create a `YfClient` and use a `Ticker` to fetch data. +//! +//! ```no_run +//! use yfinance_rs::{Interval, Range, Ticker, YfClient}; +//! +//! #[tokio::main] +//! async fn main() -> Result<(), Box> { +//! let client = YfClient::default(); +//! let ticker = Ticker::new(&client, "AAPL"); +//! +//! // Get the latest quote +//! let quote = ticker.quote().await?; +//! println!("Latest price for AAPL: ${:.2}", quote.price.as_ref().map(|p| yfinance_rs::core::conversions::money_to_f64(p)).unwrap_or(0.0)); +//! +//! // Get historical data for the last 6 months +//! let history = ticker.history(Some(Range::M6), Some(Interval::D1), false).await?; +//! if let Some(last_bar) = history.last() { +//! println!("Last closing price: ${:.2} on timestamp {}", yfinance_rs::core::conversions::money_to_f64(&last_bar.close), last_bar.ts); +//! } +//! +//! // Get analyst recommendations +//! let recs = ticker.recommendations().await?; +//! if let Some(latest_rec) = recs.first() { +//! println!("Latest recommendation period: {}", latest_rec.period); +//! } +//! +//! Ok(()) +//! } +//! ``` +#![warn(missing_docs)] + +/// Core components, including the `YfClient` and `YfError`. +pub mod core; + +// --- feature modules --- +/// Fetch analyst ratings, price targets, and upgrade/downgrade history. +pub mod analysis; +/// Download historical data for multiple symbols concurrently. +pub mod download; +/// Fetch ESG (Environmental, Social, Governance) scores and involvement data. +pub mod esg; +/// Fetch financial statements (income, balance sheet, cash flow) and earnings data. +pub mod fundamentals; +/// Fetch historical OHLCV data for a single symbol. +pub mod history; +/// Fetch holder information, including major, institutional, and insider holders. +pub mod holders; +/// Fetch news articles for a ticker. +pub mod news; +/// Retrieve company or fund profile information. +pub mod profile; +/// Fetch quotes for multiple symbols. +pub mod quote; +/// Search for tickers by name or keyword. +pub mod search; +/// Stream real-time quote updates via `WebSockets` or polling. +pub mod stream; +/// A high-level interface for a single ticker, providing access to all data types. +pub mod ticker; + +// --- re-exports (public API remains the same names as before) --- +// Core types that are provider-specific +pub use core::client::ApiPreference; +pub use core::{CacheMode, RetryConfig, YfClient, YfClientBuilder, YfError}; + +// Provider-specific builders and utilities +pub use download::DownloadBuilder; +pub use esg::EsgBuilder; +pub use fundamentals::FundamentalsBuilder; +pub use history::HistoryBuilder; +pub use holders::HoldersBuilder; +pub use news::{NewsBuilder, NewsTab}; +pub use paft::market::responses::download::{DownloadEntry, DownloadResponse}; +pub use quote::{QuotesBuilder, quotes}; +pub use search::{SearchBuilder, search}; +pub use stream::{StreamBuilder, StreamConfig, StreamHandle, StreamMethod}; +pub use ticker::{FastInfo, Info, Ticker}; + +/// Initialize a default tracing subscriber for tests/examples when the +/// `tracing-subscriber` feature is enabled. No-op otherwise. +#[cfg(feature = "tracing-subscriber")] +#[doc(hidden)] +pub fn init_tracing_for_tests() { + use tracing_subscriber::{EnvFilter, fmt}; + let filter = std::env::var("RUST_LOG") + .ok() + .and_then(|s| EnvFilter::try_new(s).ok()) + .unwrap_or_else(|| EnvFilter::new("info")); + let _ = fmt::Subscriber::builder() + .with_env_filter(filter) + .with_target(true) + .with_ansi(true) + .try_init(); +} + +// Explicitly re-export selected paft core types commonly used by users of this crate +pub use crate::core::{Action, Candle, HistoryMeta, HistoryResponse, Quote}; +pub use crate::core::{Interval, Range}; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/news/api.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/news/api.rs new file mode 100644 index 0000000..1d5262d --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/news/api.rs @@ -0,0 +1,107 @@ +use serde::Serialize; + +use crate::{ + core::{ + YfClient, YfError, + client::{CacheMode, RetryConfig}, + conversions::i64_to_datetime, + net, + }, + news::{NewsTab, model::NewsArticle, tab_as_str, wire}, +}; + +#[derive(Serialize)] +struct ServiceConfig<'a> { + #[serde(rename = "snippetCount")] + snippet_count: u32, + s: &'a [&'a str], +} + +#[derive(Serialize)] +struct NewsPayload<'a> { + #[serde(rename = "serviceConfig")] + service_config: ServiceConfig<'a>, +} + +pub(super) async fn fetch_news( + client: &YfClient, + symbol: &str, + count: u32, + tab: NewsTab, + _cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result, YfError> { + let mut url = client.base_news().join("xhr/ncp")?; + url.query_pairs_mut() + .append_pair("queryRef", tab_as_str(tab)) + .append_pair("serviceKey", "ncp_fin"); + + let payload = NewsPayload { + service_config: ServiceConfig { + snippet_count: count, + s: &[symbol], + }, + }; + + // Note: The client's built-in cache is URL-based and doesn't support POST bodies. + // Caching for this endpoint would require a more complex keying strategy. + + let req = client.http().post(url).json(&payload); + let resp = client.send_with_retry(req, retry_override).await?; + + if !resp.status().is_success() { + let code = resp.status().as_u16(); + let url_s = resp.url().to_string(); + return Err(match code { + 404 => YfError::NotFound { url: url_s }, + 429 => YfError::RateLimited { url: url_s }, + 500..=599 => YfError::ServerError { + status: code, + url: url_s, + }, + _ => YfError::Status { + status: code, + url: url_s, + }, + }); + } + + let endpoint = format!("news_{}", tab_as_str(tab)); + let body = net::get_text(resp, &endpoint, symbol, "json").await?; + let envelope: wire::NewsEnvelope = serde_json::from_str(&body).map_err(YfError::Json)?; + + let articles = envelope + .data + .and_then(|d| d.ticker_stream) + .and_then(|ts| ts.stream) + .unwrap_or_default(); + + let results = articles + .into_iter() + .filter_map(|raw_item| { + // Filter out ads or items that are not valid articles + if raw_item.ad.is_some() { + return None; + } + + let content = raw_item.content?; + let title = content.title?; + let pub_date_str = content.pub_date?; + + // Parse the RFC3339 string to a timestamp + let timestamp = chrono::DateTime::parse_from_rfc3339(&pub_date_str) + .ok()? + .timestamp(); + + Some(NewsArticle { + uuid: raw_item.id, + title, + publisher: content.provider.and_then(|p| p.display_name), + link: content.canonical_url.and_then(|u| u.url), + published_at: i64_to_datetime(timestamp), + }) + }) + .collect(); + + Ok(results) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/news/mod.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/news/mod.rs new file mode 100644 index 0000000..4b96faf --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/news/mod.rs @@ -0,0 +1,102 @@ +mod api; +mod model; +mod wire; + +use serde::{Deserialize, Serialize}; + +/// Tabs for filtering the Yahoo Finance news endpoint. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] +pub enum NewsTab { + /// Only editorial news items. + #[default] + News, + /// All items including press releases. + All, + /// Only press releases. + PressReleases, +} +pub use model::NewsArticle; + +use crate::{ + YfClient, YfError, + core::client::{CacheMode, RetryConfig}, +}; + +pub(crate) const fn tab_as_str(tab: NewsTab) -> &'static str { + match tab { + NewsTab::News => "latestNews", + NewsTab::All => "newsAll", + NewsTab::PressReleases => "pressRelease", + } +} + +/// A builder for fetching news articles for a specific symbol. +pub struct NewsBuilder { + client: YfClient, + symbol: String, + count: u32, + tab: NewsTab, + cache_mode: CacheMode, + retry_override: Option, +} + +impl NewsBuilder { + /// Creates a new `NewsBuilder` for a given symbol. + pub fn new(client: &YfClient, symbol: impl Into) -> Self { + Self { + client: client.clone(), + symbol: symbol.into(), + count: 10, + tab: NewsTab::default(), + cache_mode: CacheMode::Use, + retry_override: None, + } + } + + /// Sets the cache mode for this specific API call. + /// Note: Caching is not currently implemented for news requests. + #[must_use] + pub const fn cache_mode(mut self, mode: CacheMode) -> Self { + self.cache_mode = mode; + self + } + + /// Overrides the default retry policy for this specific API call. + #[must_use] + pub fn retry_policy(mut self, cfg: Option) -> Self { + self.retry_override = cfg; + self + } + + /// Sets the maximum number of news articles to return. + #[must_use] + pub const fn count(mut self, count: u32) -> Self { + self.count = count; + self + } + + /// Sets the category of news to fetch. + #[must_use] + pub const fn tab(mut self, tab: NewsTab) -> Self { + self.tab = tab; + self + } + + /// Executes the request and fetches the news articles. + /// + /// # Errors + /// + /// Returns a `YfError` if the request to the Yahoo Finance API fails, + /// if the response cannot be parsed, or if there's a network issue. + pub async fn fetch(self) -> Result, YfError> { + api::fetch_news( + &self.client, + &self.symbol, + self.count, + self.tab, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/news/model.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/news/model.rs new file mode 100644 index 0000000..23a8f9b --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/news/model.rs @@ -0,0 +1,2 @@ +// Re-export types from paft without using prelude +pub use paft::market::news::NewsArticle; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/news/wire.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/news/wire.rs new file mode 100644 index 0000000..3600520 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/news/wire.rs @@ -0,0 +1,46 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct NewsEnvelope { + pub(crate) data: Option, +} + +#[derive(Deserialize)] +pub struct NewsData { + #[serde(rename = "tickerStream")] + pub(crate) ticker_stream: Option, +} + +#[derive(Deserialize)] +pub struct TickerStream { + pub(crate) stream: Option>, +} + +#[derive(Deserialize)] +pub struct StreamItem { + pub(crate) id: String, + pub(crate) content: Option, + // The python 'ad' check might be for a field at this level. + pub(crate) ad: Option, +} + +#[derive(Deserialize)] +pub struct Content { + pub(crate) title: Option, + #[serde(rename = "pubDate")] + pub(crate) pub_date: Option, + pub(crate) provider: Option, + #[serde(rename = "canonicalUrl")] + pub(crate) canonical_url: Option, +} + +#[derive(Deserialize)] +pub struct Provider { + #[serde(rename = "displayName")] + pub(crate) display_name: Option, +} + +#[derive(Deserialize)] +pub struct CanonicalUrl { + pub(crate) url: Option, +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/api.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/api.rs new file mode 100644 index 0000000..8d92d12 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/api.rs @@ -0,0 +1,129 @@ +//! quoteSummary v10 API path for profiles. + +use crate::{ + YfClient, YfError, + core::{client::CacheMode, conversions::string_to_fund_kind, quotesummary}, +}; +use paft::domain::Isin; +use serde::Deserialize; + +use super::{Address, Company, Fund, Profile}; + +pub async fn load_from_quote_summary_api( + client: &YfClient, + symbol: &str, +) -> Result { + let first: V10Result = quotesummary::fetch_module_result( + client, + symbol, + "assetProfile,quoteType,fundProfile", + "profile", + CacheMode::Use, + None, + ) + .await?; + + let kind = first + .quote_type + .as_ref() + .and_then(|q| q.quote_type.as_deref()) + .unwrap_or(""); + + let name = first + .quote_type + .as_ref() + .and_then(|q| q.long_name.clone().or_else(|| q.short_name.clone())) + .unwrap_or_else(|| symbol.to_string()); + + match kind { + "EQUITY" => { + let sp = first + .asset_profile + .ok_or_else(|| YfError::MissingData("assetProfile missing".into()))?; + let address = Address { + street1: sp.address1, + street2: sp.address2, + city: sp.city, + state: sp.state, + country: sp.country, + zip: sp.zip, + }; + // Validate ISIN if present, return None if invalid + let validated_isin = sp.isin.and_then(|isin_str| Isin::new(&isin_str).ok()); + + Ok(Profile::Company(Company { + name, + sector: sp.sector, + industry: sp.industry, + website: sp.website, + summary: sp.long_business_summary, + address: Some(address), + isin: validated_isin, + })) + } + "ETF" => { + let fp = first + .fund_profile + .ok_or_else(|| YfError::MissingData("fundProfile missing".into()))?; + + // Validate ISIN if present, return None if invalid + let validated_isin = fp.isin.and_then(|isin_str| Isin::new(&isin_str).ok()); + + Ok(Profile::Fund(Fund { + name, + family: fp.family, + kind: string_to_fund_kind(fp.legal_type).unwrap_or_default(), + isin: validated_isin, + })) + } + other => Err(YfError::InvalidParams(format!( + "unsupported quoteType: {other}" + ))), + } +} + +/* --------- Minimal serde mapping for the API JSON --------- */ + +#[derive(Deserialize)] +struct V10Result { + #[serde(rename = "assetProfile")] + asset_profile: Option, + #[serde(rename = "fundProfile")] + fund_profile: Option, + #[serde(rename = "quoteType")] + quote_type: Option, +} + +#[derive(Deserialize)] +struct V10AssetProfile { + address1: Option, + address2: Option, + city: Option, + state: Option, + country: Option, + zip: Option, + sector: Option, + industry: Option, + website: Option, + #[serde(rename = "longBusinessSummary")] + long_business_summary: Option, + isin: Option, +} + +#[derive(Deserialize)] +struct V10FundProfile { + #[serde(rename = "legalType")] + legal_type: Option, + family: Option, + isin: Option, +} + +#[derive(Deserialize)] +struct V10QuoteType { + #[serde(rename = "quoteType")] + quote_type: Option, + #[serde(rename = "longName")] + long_name: Option, + #[serde(rename = "shortName")] + short_name: Option, +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/debug.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/debug.rs new file mode 100644 index 0000000..03c6b07 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/debug.rs @@ -0,0 +1,199 @@ +//! Debug dump helpers for development / troubleshooting. + +use crate::profile::scrape::utils::{escape_html, iter_json_scripts, parse_jsonish_string}; +use serde_json::Value; +use std::fmt::Write as _; +use std::io::Write; // for writing to files // for write!(..) into String + +pub fn debug_dump_extracted_json(symbol: &str, json: &str) -> std::io::Result<()> { + let path = std::env::temp_dir().join(format!("yfinance_rs-profile-{symbol}-extracted.json")); + let mut f = std::fs::File::create(&path)?; + + if let Ok(val) = serde_json::from_str::(json) + && let Ok(pretty) = serde_json::to_string_pretty(&val) + { + let _ = f.write_all(pretty.as_bytes()); + eprintln!( + "yfinance-rs(debug-dumps): wrote pretty-printed extracted JSON to {}", + path.display() + ); + return Ok(()); + } + + let _ = f.write_all(json.as_bytes()); + eprintln!( + "yfinance-rs(debug-dumps): wrote raw extracted JSON to {}", + path.display() + ); + Ok(()) +} + +#[allow(clippy::too_many_lines)] +pub fn debug_dump_html(symbol: &str, html: &str) -> std::io::Result<()> { + use std::{fs, io::Write}; + + fn pretty_limit(v: &Value, max_chars: usize) -> String { + let s = serde_json::to_string_pretty(v).unwrap_or_else(|_| format!("{v:?}")); + if s.chars().count() <= max_chars { + return s; + } + let mut out = String::new(); + for (n, ch) in s.chars().enumerate() { + if n >= max_chars { + break; + } + out.push(ch); + } + out.push_str("\n… [truncated]"); + out + } + + fn extract_title(html: &str) -> Option { + let lt = ""; + let rt = ""; + let i = html.find(lt)?; + let j = html[i + lt.len()..].find(rt)? + i + lt.len(); + Some(html[i + lt.len()..j].to_string()) + } + + fn extract_js_object_after(pattern: &str, s: &str) -> Option { + let start = s.find(pattern)? + pattern.len(); + let bytes = s.as_bytes(); + let mut i = start; + while i < bytes.len() && bytes[i].is_ascii_whitespace() { + i += 1; + } + if i >= bytes.len() || bytes[i] != b'{' { + return None; + } + let mut j = i; + let mut depth = 0i32; + while j < bytes.len() { + match bytes[j] { + b'{' => { + depth += 1; + } + b'}' => { + depth -= 1; + if depth == 0 { + j += 1; + break; + } + } + b'"' => { + j += 1; + while j < bytes.len() { + if bytes[j] == b'\\' { + j += 2; + continue; + } + if bytes[j] == b'"' { + j += 1; + break; + } + j += 1; + } + continue; + } + _ => {} + } + j += 1; + } + if j <= i { + return None; + } + Some(s[i..j].to_string()) + } + + let tmp = std::env::temp_dir(); + let base = format!("yfinance_rs-profile-{symbol}"); + + let min_path = tmp.join(format!("{base}-min.html")); + let next_path = tmp.join(format!("{base}-next.json")); + let rootapp_path = tmp.join(format!("{base}-rootapp.json")); + + let mut min_html = String::new(); + min_html.push_str("\n\n"); + if let Some(t) = extract_title(html) { + let _ = writeln!(min_html, "

title

{}
", escape_html(&t)); + } + + for (attrs, inner) in iter_json_scripts(html) { + let parsed = serde_json::from_str::(inner).ok(); + + let pretty; + if let Some(v) = parsed.as_ref() { + if let Some(u) = v + .get("body") + .and_then(|b| b.as_str()) + .and_then(parse_jsonish_string) + { + pretty = pretty_limit(&u, 5_000); + } else { + pretty = pretty_limit(v, 5_000); + } + } else if let Some(v) = parse_jsonish_string(inner) { + pretty = pretty_limit(&v, 5_000); + } else { + continue; + } + + let data_url = attrs + .split_whitespace() + .find(|p| p.starts_with("data-url=")) + .map_or("", |p| p.trim_start_matches("data-url=").trim_matches('"')); + let label = if attrs.contains("data-sveltekit-fetched") { + format!("sveltekit-fetched {data_url}",) + } else if attrs.contains("id=\"__NEXT_DATA__\"") { + "__NEXT_DATA__".to_string() + } else { + "application/json script".to_string() + }; + let _ = writeln!( + min_html, + "

{}

{}
", + escape_html(&label), + escape_html(&pretty) + ); + } + + if let Some((_, inner)) = iter_json_scripts(html) + .into_iter() + .find(|(attrs, _)| attrs.contains("id=\"__NEXT_DATA__\"")) + && let Ok(v) = serde_json::from_str::(inner) + { + let mut f = fs::File::create(&next_path)?; + let s = serde_json::to_string_pretty(&v).unwrap_or_else(|_| inner.to_string()); + f.write_all(s.as_bytes())?; + eprintln!("yfinance-rs(debug-dumps): wrote {}", next_path.display()); + } + + if let Some(js_obj) = extract_js_object_after("root.App.main =", html) { + if let Ok(v) = serde_json::from_str::(&js_obj) { + let mut f = fs::File::create(&rootapp_path)?; + let s = serde_json::to_string_pretty(&v).unwrap_or_else(|_| js_obj.clone()); + f.write_all(s.as_bytes())?; + eprintln!("yfinance-rs(debug-dumps): wrote {}", rootapp_path.display()); + } + if let Ok(v) = serde_json::from_str::(&js_obj) { + min_html.push_str("

root.App.main (snippet)

\n"); + let pretty = pretty_limit(&v, 5_000); + let _ = writeln!(min_html, "
{}
", escape_html(&pretty)); + } + } + + let mut f = std::fs::File::create(&min_path)?; + f.write_all(min_html.as_bytes())?; + eprintln!("yfinance-rs(debug-dumps): wrote {}", min_path.display()); + + Ok(()) +} + +pub fn debug_dump_api(symbol: &str, body: &str) -> std::io::Result<()> { + use std::io::Write; + let path = std::env::temp_dir().join(format!("yfinance_rs-quoteSummary-{symbol}.json")); + let mut f = std::fs::File::create(&path)?; + let _ = f.write_all(body.as_bytes()); + eprintln!("yfinance-rs(debug-dumps): wrote {}", path.display()); + Ok(()) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/mod.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/mod.rs new file mode 100644 index 0000000..6859038 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/mod.rs @@ -0,0 +1,65 @@ +//! Public profile types + loading strategy (API first, then scrape). +//! +//! Internals are split into: +//! - `api`:    quoteSummary v10 API path +//! - `scrape`: HTML scrape + JSON extraction path +//! - `internal`: common utilities for both API and scrape +//! - `debug`:  optional debug dump helpers (only in debug builds or with `debug-dumps` feature) + +mod api; +mod scrape; + +#[cfg(feature = "debug-dumps")] +pub(crate) mod debug; + +use crate::{YfClient, YfError}; + +mod model; +pub use model::{Address, Company, Fund, Profile}; + +/// Helper to contain the API->Scrape fallback logic. +#[cfg_attr(feature = "tracing", tracing::instrument(skip(client), err, fields(symbol = %symbol)))] +async fn load_with_fallback(client: &YfClient, symbol: &str) -> Result { + client.ensure_credentials().await?; + + match api::load_from_quote_summary_api(client, symbol).await { + Ok(p) => Ok(p), + Err(e) => { + if std::env::var("YF_DEBUG").ok().as_deref() == Some("1") { + eprintln!("YF_DEBUG: API call failed ({e}), falling back to scrape."); + } + #[cfg(feature = "tracing")] + tracing::event!(tracing::Level::WARN, error = %e, "profile: API failed; falling back to scrape"); + scrape::load_from_scrape(client, symbol).await + } + } +} + +/// Loads the profile for a given symbol. +/// +/// This function will try to load the profile from the quote summary API first, +/// and fall back to scraping the quote page if the API fails. +/// +/// # Errors +/// +/// Returns `YfError` if the network request fails, the response cannot be parsed, +/// or the data for the symbol is not available. +pub async fn load_profile(client: &YfClient, symbol: &str) -> Result { + #[cfg(not(feature = "test-mode"))] + { + load_with_fallback(client, symbol).await + } + + #[cfg(feature = "test-mode")] + { + use crate::core::client::ApiPreference; + match client.api_preference() { + ApiPreference::ApiThenScrape => load_with_fallback(client, symbol).await, + ApiPreference::ApiOnly => { + client.ensure_credentials().await?; + api::load_from_quote_summary_api(client, symbol).await + } + ApiPreference::ScrapeOnly => scrape::load_from_scrape(client, symbol).await, + } + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/model.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/model.rs new file mode 100644 index 0000000..be0eaac --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/model.rs @@ -0,0 +1,4 @@ +// Re-export types from paft without using prelude +pub use paft::fundamentals::profile::{ + Address, CompanyProfile as Company, FundProfile as Fund, Profile, +}; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/scrape/extract/helpers.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/scrape/extract/helpers.rs new file mode 100644 index 0000000..c3562f1 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/scrape/extract/helpers.rs @@ -0,0 +1,143 @@ +use serde_json::Value; + +pub fn truncate(s: &str, n: usize) -> String { + if s.len() <= n { + s.to_string() + } else { + let mut out = String::with_capacity(n + 16); + out.push_str(&s[..n]); + out.push_str(" …[trunc]"); + out + } +} + +pub fn extract_store_like_from_quote_summary_value(qs_val: &Value) -> Option { + let debug = std::env::var("YF_DEBUG").ok().as_deref() == Some("1"); + + // Accept either {..., quoteSummary: {...}} or a quoteSummary node directly. + let summary = qs_val.get("quoteSummary").unwrap_or(qs_val); + + // Take the first result element if present. + let result0 = summary + .get("result") + .and_then(|r| r.as_array()) + .and_then(|arr| arr.first()) + .cloned(); + + if result0.is_none() { + if debug { + eprintln!( + "YF_DEBUG [extract_store_like]: quoteSummary.result[0] missing or not an array." + ); + } + return None; + } + let result0 = result0.unwrap(); + + // Sanity checks that this looks like a profile payload. + let has_quote_type = result0.get("quoteType").is_some(); + let has_profile = + result0.get("assetProfile").is_some() || result0.get("summaryProfile").is_some(); + let has_fund = result0.get("fundProfile").is_some(); + + if debug { + eprintln!( + "YF_DEBUG [extract_store_like]: has_quoteType={has_quote_type}, has_profile={has_profile}, has_fund={has_fund}" + ); + } + if !(has_quote_type || has_profile || has_fund) { + if debug { + eprintln!("YF_DEBUG [extract_store_like]: shape not acceptable."); + } + return None; + } + + let norm = normalize_store_like(result0); + if debug { + let keys = norm + .as_object() + .map(|m| { + let mut v: Vec<_> = m.keys().cloned().collect(); + v.sort(); + v.join(",") + }) + .unwrap_or_default(); + eprintln!("YF_DEBUG [extract_store_like]: SUCCESS; normalized keys={keys}"); + } + Some(norm) +} + +pub fn find_quote_summary_store_in_value(v: &Value) -> Option<&Value> { + match v { + Value::Object(map) => { + if let Some(qss) = map.get("QuoteSummaryStore") + && qss.is_object() + { + return Some(qss); + } + if let Some(stores) = map.get("stores") + && let Some(qss) = stores.get("QuoteSummaryStore") + && qss.is_object() + { + return Some(qss); + } + for child in map.values() { + if let Some(found) = find_quote_summary_store_in_value(child) { + return Some(found); + } + } + None + } + Value::Array(arr) => { + for child in arr { + if let Some(found) = find_quote_summary_store_in_value(child) { + return Some(found); + } + } + None + } + _ => None, + } +} + +pub fn find_quote_summary_value_in_value(v: &Value) -> Option<&Value> { + match v { + Value::Object(map) => { + if let Some(qs) = map.get("quoteSummary") { + return Some(qs); + } + for child in map.values() { + if let Some(found) = find_quote_summary_value_in_value(child) { + return Some(found); + } + } + None + } + Value::Array(arr) => { + for child in arr { + if let Some(found) = find_quote_summary_value_in_value(child) { + return Some(found); + } + } + None + } + _ => None, + } +} + +pub fn normalize_store_like(mut store_like: Value) -> Value { + if let Some(obj) = store_like.as_object_mut() + && let Some(ap) = obj.remove("assetProfile") + { + // Normalize to what the rest of the code expects. + obj.insert("summaryProfile".to_string(), ap); + } + store_like +} + +pub fn wrap_store_like(store_like: &Value) -> Result { + let store_json = serde_json::to_string(&store_like).map_err(crate::YfError::Json)?; + Ok(format!( + r#"{{"context":{{"dispatcher":{{"stores":{{"QuoteSummaryStore":{store_json}}}}}}}}}"# + )) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/scrape/extract/mod.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/scrape/extract/mod.rs new file mode 100644 index 0000000..775aab8 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/scrape/extract/mod.rs @@ -0,0 +1,86 @@ +mod helpers; +mod strategies; + +use helpers::truncate; +use strategies::{ + try_generic_json_scripts, try_quote_summary_store_literal, try_root_app_main, + try_sveltekit_json, +}; + +pub fn extract_bootstrap_json(body: &str) -> Result { + let debug = std::env::var("YF_DEBUG").ok().as_deref() == Some("1"); + + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: starting, body.len()={}", + body.len() + ); + } + + /* Strategy A: legacy root.App.main = {...}; */ + if debug { + eprintln!("YF_DEBUG [extract_bootstrap_json]: Strategy A (root.App.main)..."); + } + if let Some(json_str) = try_root_app_main(body, debug) { + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: Strategy A hit; json.len={} preview=`{}`", + json_str.len(), + truncate(&json_str, 160) + ); + } + return Ok(json_str); + } + + /* Strategy B: literal "QuoteSummaryStore": { ... } object */ + if debug { + eprintln!("YF_DEBUG [extract_bootstrap_json]: Strategy B (QuoteSummaryStore literal)..."); + } + if let Some(wrapped) = try_quote_summary_store_literal(body, debug) { + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: Strategy B hit; wrapped.len={} preview=`{}`", + wrapped.len(), + truncate(&wrapped, 160) + ); + } + return Ok(wrapped); + } + + /* Strategy C: SvelteKit data-sveltekit-fetched blobs. */ + if debug { + eprintln!("YF_DEBUG [extract_bootstrap_json]: Strategy C (SvelteKit fetched JSON)..."); + } + if let Some(wrapped) = try_sveltekit_json(body, debug) { + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: Strategy C hit; wrapped.len={} preview=`{}`", + wrapped.len(), + truncate(&wrapped, 160) + ); + } + return Ok(wrapped); + } + + /* Strategy D: generic scan of all application/json scripts */ + if debug { + eprintln!("YF_DEBUG [extract_bootstrap_json]: Strategy D (generic JSON scan)..."); + } + if let Some(wrapped) = try_generic_json_scripts(body, debug) { + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: Strategy D hit; wrapped.len={} preview=`{}`", + wrapped.len(), + truncate(&wrapped, 160) + ); + } + return Ok(wrapped); + } + + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: All strategies exhausted; bootstrap not found." + ); + } + Err(crate::YfError::MissingData("bootstrap not found".into())) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/scrape/extract/strategies.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/scrape/extract/strategies.rs new file mode 100644 index 0000000..23b9e0d --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/scrape/extract/strategies.rs @@ -0,0 +1,288 @@ +use serde_json::Value; + +use super::helpers::{ + extract_store_like_from_quote_summary_value, find_quote_summary_store_in_value, + find_quote_summary_value_in_value, normalize_store_like, truncate, wrap_store_like, +}; +use crate::profile::scrape::utils::{find_matching_brace, iter_json_scripts}; + +/// Strategy A: look for `root.App.main = {...};` +pub fn try_root_app_main(body: &str, debug: bool) -> Option { + if let Some(start) = body.find("root.App.main") { + let after = &body[start..]; + if let Some(eq) = after.find('=') { + let mut payload = &after[eq + 1..]; + payload = payload.trim_start(); + let end_script = payload.find("").unwrap_or(payload.len()); + let segment = &payload[..end_script]; + if let Some(semi) = segment.rfind(';') { + let json_str = segment[..semi].trim(); + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: Strategy A preview=`{}`", + truncate(json_str, 160) + ); + } + return Some(json_str.to_string()); + } + } + } + None +} + +/// Strategy B: find literal `"QuoteSummaryStore" : { ... }` and wrap it. +pub fn try_quote_summary_store_literal(body: &str, debug: bool) -> Option { + let key = "\"QuoteSummaryStore\""; + if let Some(pos) = body.find(key) { + let after = &body[pos + key.len()..]; + if let Some(brace_rel) = after.find('{') { + let obj_start = pos + key.len() + brace_rel; + if let Some(obj_end) = find_matching_brace(body, obj_start) { + let obj = &body[obj_start..=obj_end]; + let wrapped = format!( + r#"{{"context":{{"dispatcher":{{"stores":{{"QuoteSummaryStore":{obj}}}}}}}}}"# + ); + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: Strategy B obj.len={} preview=`{}`", + obj.len(), + truncate(obj, 160) + ); + } + return Some(wrapped); + } else if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: Strategy B found start but failed to match closing brace." + ); + } + } + } + None +} + +/// Strategy C: scan `SvelteKit` `data-sveltekit-fetched` JSON blobs. +#[allow(clippy::too_many_lines)] +pub fn try_sveltekit_json(body: &str, debug: bool) -> Option { + let scripts = iter_json_scripts(body); + + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: Strategy C inspecting {} JSON scripts...", + scripts.len() + ); + } + + for (i, (tag_attrs, inner_json)) in scripts.iter().enumerate() { + let is_svelte = tag_attrs.contains("data-sveltekit-fetched"); + if !is_svelte { + continue; + } + + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: C[{}] attrs=`{}` inner.len={} preview=`{}`", + i, + truncate(tag_attrs, 160), + inner_json.len(), + truncate(inner_json, 120) + ); + } + + // Case C1: array of objects having nodes[].data + if let Ok(outer_array) = serde_json::from_str::>(inner_json) { + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: C[{}] parsed as ARRAY (len={})", + i, + outer_array.len() + ); + } + for (ai, outer_obj) in outer_array.into_iter().enumerate() { + if let Some(nodes) = outer_obj.get("nodes").and_then(|n| n.as_array()) { + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: C[{}][{}] nodes.len={}", + i, + ai, + nodes.len() + ); + } + for (ni, node) in nodes.iter().enumerate() { + if let Some(data) = node.get("data") + && let Some(store_like) = + extract_store_like_from_quote_summary_value(data) + && let Ok(wrapped) = wrap_store_like(&store_like) + { + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: C[{}][{}] SUCCESS via nodes[{}].data -> wrapped.len={}", + i, + ai, + ni, + wrapped.len() + ); + } + return Some(wrapped); + } + } + } + } + } + + // Case C2: object with "body" either JSON string or inline JSON + let parsed_obj = match serde_json::from_str::(inner_json) { + Ok(v @ Value::Object(_)) => Some(v), + Ok(_) => None, + Err(e) => { + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: C[{i}] parse-as-OBJECT failed: {e}" + ); + } + None + } + }; + + if let Some(mut outer_obj) = parsed_obj { + let body_val_opt = { outer_obj.get_mut("body").map(serde_json::Value::take) }; + + if let Some(body_val) = body_val_opt { + let payload_opt = match body_val { + Value::String(s) => serde_json::from_str::(&s).ok(), + Value::Object(_) | Value::Array(_) => Some(body_val), + _ => None, + }; + + if let Some(payload) = payload_opt { + if let Some(qss) = find_quote_summary_store_in_value(&payload) { + let store_like = normalize_store_like(qss.clone()); + if let Ok(wrapped) = wrap_store_like(&store_like) { + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: C[{}] SUCCESS via QuoteSummaryStore path; wrapped.len={}", + i, + wrapped.len() + ); + } + return Some(wrapped); + } + } + + if let Some(qs_val) = find_quote_summary_value_in_value(&payload) + && let Some(store_like) = + extract_store_like_from_quote_summary_value(qs_val) + && let Ok(wrapped) = wrap_store_like(&store_like) + { + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: C[{}] SUCCESS via quoteSummary->result; wrapped.len={}", + i, + wrapped.len() + ); + } + return Some(wrapped); + } + } + } + } + } + + None +} + +/// Strategy D: generic scan of *all* application/json scripts with multiple fallbacks. +pub fn try_generic_json_scripts(body: &str, debug: bool) -> Option { + let scripts = iter_json_scripts(body); + + for (i, (_attrs, inner_json)) in scripts.iter().enumerate() { + let val = match serde_json::from_str::(inner_json) { + Ok(v) => v, + Err(e) => { + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: D[{}] parse failed: {} (preview=`{}`)", + i, + e, + if inner_json.len() > 120 { + &inner_json[..120] + } else { + inner_json + } + ); + } + continue; + } + }; + + // D1: direct QuoteSummaryStore object + if let Some(qss) = find_quote_summary_store_in_value(&val) { + let store_like = normalize_store_like(qss.clone()); + if let Ok(wrapped) = wrap_store_like(&store_like) { + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: D[{}] SUCCESS via QuoteSummaryStore; wrapped.len={}", + i, + wrapped.len() + ); + } + return Some(wrapped); + } + } + + // D2: quoteSummary -> result[0] + if let Some(qs_val) = find_quote_summary_value_in_value(&val) + && let Some(store_like) = extract_store_like_from_quote_summary_value(qs_val) + && let Ok(wrapped) = wrap_store_like(&store_like) + { + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: D[{}] SUCCESS via quoteSummary->result; wrapped.len={}", + i, + wrapped.len() + ); + } + return Some(wrapped); + } + + // D3: value has a "body" which itself is a JSON string/object/array + if let Some(body_val) = val.get("body") { + let payload_opt = match body_val { + Value::String(s) => serde_json::from_str::(s).ok(), + Value::Object(_) | Value::Array(_) => Some(body_val.clone()), + _ => None, + }; + + if let Some(payload) = payload_opt { + if let Some(qss) = find_quote_summary_store_in_value(&payload) { + let store_like = normalize_store_like(qss.clone()); + if let Ok(wrapped) = wrap_store_like(&store_like) { + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: D[{}] SUCCESS via body->QuoteSummaryStore; wrapped.len={}", + i, + wrapped.len() + ); + } + return Some(wrapped); + } + } + + if let Some(qs_val) = find_quote_summary_value_in_value(&payload) + && let Some(store_like) = extract_store_like_from_quote_summary_value(qs_val) + && let Ok(wrapped) = wrap_store_like(&store_like) + { + if debug { + eprintln!( + "YF_DEBUG [extract_bootstrap_json]: D[{}] SUCCESS via body->quoteSummary->result; wrapped.len={}", + i, + wrapped.len() + ); + } + return Some(wrapped); + } + } + } + } + + None +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/scrape/mod.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/scrape/mod.rs new file mode 100644 index 0000000..c5c85f1 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/scrape/mod.rs @@ -0,0 +1,225 @@ +//! Scrape the Yahoo quote HTML and extract profile data. + +use crate::{YfClient, YfError}; +use paft::domain::Isin; +use serde::Deserialize; + +use super::{Address, Company, Fund, Profile}; + +#[cfg(feature = "debug-dumps")] +use crate::profile::debug::{debug_dump_extracted_json, debug_dump_html}; + +pub mod extract; +pub mod utils; +use extract::extract_bootstrap_json; + +#[allow(clippy::too_many_lines)] +pub async fn load_from_scrape(client: &YfClient, symbol: &str) -> Result { + let debug = std::env::var("YF_DEBUG").ok().as_deref() == Some("1"); + + let mut url = client.base_quote().join(symbol)?; + { + let mut qp = url.query_pairs_mut(); + qp.append_pair("p", symbol); + } + + let body = if let Some(body) = client.cache_get(&url).await { + body + } else { + let req = client.http().get(url.clone()); + let quote_page_resp = client.send_with_retry(req, None).await?; + if !quote_page_resp.status().is_success() { + return Err(YfError::Status { + status: quote_page_resp.status().as_u16(), + url: url.to_string(), + }); + } + let body = + crate::core::net::get_text(quote_page_resp, "profile_html", symbol, "html").await?; + client.cache_put(&url, &body, None).await; + body + }; + + #[cfg(feature = "debug-dumps")] + { + let _ = debug_dump_html(symbol, &body); + } + + let json_str = extract_bootstrap_json(&body)?; + #[cfg(feature = "debug-dumps")] + { + let _ = debug_dump_extracted_json(symbol, &json_str); + } + + let boot: Bootstrap = serde_json::from_str(&json_str).map_err(YfError::Json)?; + + let store = boot.context.dispatcher.stores.quote_summary_store; + + let name = store + .quote_type + .as_ref() + .and_then(|qt| qt.long_name.clone().or_else(|| qt.short_name.clone())) + .or_else(|| { + store + .price + .as_ref() + .and_then(|p| p.long_name.clone().or_else(|| p.short_name.clone())) + }) + .unwrap_or_else(|| symbol.to_string()); + + let inferred_kind = if store.fund_profile.is_some() { + Some("ETF") + } else if store.summary_profile.is_some() { + Some("EQUITY") + } else { + None + }; + let kind = store + .quote_type + .as_ref() + .and_then(|qt| qt.kind.as_deref()) + .or(inferred_kind) + .unwrap_or(""); + + if debug { + eprintln!( + "YF_DEBUG [load_from_scrape]: resolved kind=`{}`, name=`{}` (quote_type_present={}, price_present={}, has_summary_profile={}, has_fund_profile={})", + kind, + name, + store.quote_type.is_some(), + store.price.is_some(), + store.summary_profile.is_some(), + store.fund_profile.is_some() + ); + } + + match kind { + "EQUITY" => { + let sp = store + .summary_profile + .ok_or_else(|| YfError::MissingData("summaryProfile missing".into()))?; + let address = Address { + street1: sp.address1, + street2: sp.address2, + city: sp.city, + state: sp.state, + country: sp.country, + zip: sp.zip, + }; + // Validate ISIN if present, return None if invalid + let validated_isin = sp.isin.and_then(|isin_str| Isin::new(&isin_str).ok()); + + Ok(Profile::Company(Company { + name, + sector: sp.sector, + industry: sp.industry, + website: sp.website, + summary: sp.long_business_summary, + address: Some(address), + isin: validated_isin, + })) + } + "ETF" => { + let fp = store + .fund_profile + .ok_or_else(|| YfError::MissingData("fundProfile missing".into()))?; + // Validate ISIN if present, return None if invalid + let validated_isin = fp.isin.and_then(|isin_str| Isin::new(&isin_str).ok()); + + Ok(Profile::Fund(Fund { + name, + family: fp.family, + kind: crate::core::conversions::string_to_fund_kind(fp.legal_type) + .unwrap_or_default(), + isin: validated_isin, + })) + } + other => Err(YfError::InvalidParams(format!( + "unsupported or unknown quoteType: {other}" + ))), + } +} + +/* --------- Minimal serde mapping for the bootstrap JSON --------- */ + +#[derive(Deserialize)] +struct Bootstrap { + context: Ctx, +} + +#[derive(Deserialize)] +struct Ctx { + dispatcher: Dispatch, +} + +#[derive(Deserialize)] +struct Dispatch { + stores: Stores, +} + +#[derive(Deserialize)] +struct Stores { + #[serde(rename = "QuoteSummaryStore")] + quote_summary_store: QuoteSummaryStore, +} + +#[derive(Deserialize)] +struct QuoteSummaryStore { + #[serde(rename = "quoteType")] + quote_type: Option, + + #[serde(default)] + price: Option, + + #[serde(rename = "summaryProfile")] + summary_profile: Option, + + #[serde(rename = "fundProfile")] + fund_profile: Option, +} + +#[derive(Deserialize)] +struct QuoteTypeNode { + #[serde(rename = "quoteType")] + kind: Option, + + #[serde(rename = "longName")] + long_name: Option, + + #[serde(rename = "shortName")] + short_name: Option, +} + +#[derive(Deserialize)] +struct PriceNode { + #[serde(rename = "longName")] + long_name: Option, + #[serde(rename = "shortName")] + short_name: Option, +} + +#[derive(Deserialize)] +struct SummaryProfileNode { + address1: Option, + address2: Option, + city: Option, + state: Option, + country: Option, + zip: Option, + sector: Option, + industry: Option, + + #[serde(rename = "longBusinessSummary")] + long_business_summary: Option, + + website: Option, + isin: Option, +} + +#[derive(Deserialize)] +struct FundProfileNode { + #[serde(rename = "legalType")] + legal_type: Option, + family: Option, + isin: Option, +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/scrape/utils.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/scrape/utils.rs new file mode 100644 index 0000000..a61326f --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/profile/scrape/utils.rs @@ -0,0 +1,136 @@ +pub fn iter_json_scripts(html: &str) -> Vec<(&str, &str)> { + let debug = std::env::var("YF_DEBUG").ok().as_deref() == Some("1"); + if debug { + eprintln!( + "YF_DEBUG [iter_json_scripts]: html.len()={}; scanning for ") { + Some(x) => open_end + 1 + x, + None => break, + }; + let inner = &html[open_end + 1..close]; + + if is_json { + res.push((tag_open, inner)); + } + pos = close + "".len(); + } + + if debug { + eprintln!( + "YF_DEBUG [iter_json_scripts]: total_scripts={total_scripts}, total_json_scripts={total_json_scripts}, svelte_fetched={total_svelte_fetched}" + ); + if let Some((attrs, body)) = res.first() { + let a = if attrs.len() > 180 { + &attrs[..180] + } else { + attrs + }; + let b = if body.len() > 120 { &body[..120] } else { body }; + eprintln!( + "YF_DEBUG [iter_json_scripts]: first JSON script attrs[trunc]=`{a}` body[trunc]=`{b}`" + ); + } + } + res +} + +/// Exposed for debug helpers as well. +pub fn find_matching_brace(s: &str, start: usize) -> Option { + let bytes = s.as_bytes(); + let i = start; + if bytes.get(i).copied()? != b'{' { + return None; + } + + let mut depth = 0usize; + let mut in_str = false; + let mut j = i; + + while j < bytes.len() { + let c = bytes[j]; + + if in_str { + if c == b'\\' { + j += 2; + continue; + } else if c == b'"' { + in_str = false; + } + j += 1; + continue; + } + + match c { + b'"' => { + in_str = true; + } + b'{' => { + depth += 1; + } + b'}' => { + depth -= 1; + if depth == 0 { + return Some(j); + } + } + _ => {} + } + j += 1; + } + None +} + +/// Exposed for debug helpers as well. +#[cfg(feature = "debug-dumps")] +pub fn parse_jsonish_string(s: &str) -> Option { + let t = s.trim(); + if t.starts_with('{') || t.starts_with('[') { + serde_json::from_str::(t).ok() + } else { + None + } +} + +#[cfg(feature = "debug-dumps")] +pub fn escape_html(s: &str) -> String { + let mut out = String::with_capacity(s.len()); + for ch in s.chars() { + match ch { + '&' => out.push_str("&"), + '<' => out.push_str("<"), + '>' => out.push_str(">"), + '"' => out.push_str("""), + '\'' => out.push_str("'"), + _ => out.push(ch), + } + } + out +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/quote/mod.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/quote/mod.rs new file mode 100644 index 0000000..05db66a --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/quote/mod.rs @@ -0,0 +1,98 @@ +use crate::core::client::CacheMode; +use crate::core::client::RetryConfig; +use crate::core::{Quote, YfClient, YfError, quotes as core_quotes}; + +/// Fetches quotes for multiple symbols. +/// +/// # Errors +/// +/// Returns `YfError` if the network request fails, the response cannot be parsed, +/// or the data for the symbols is not available. +pub async fn quotes(client: &YfClient, symbols: I) -> Result, YfError> +where + I: IntoIterator, + S: Into, +{ + QuotesBuilder::new(client.clone()) + .symbols(symbols) + .fetch() + .await +} + +/// A builder for fetching quotes for one or more symbols. +pub struct QuotesBuilder { + client: YfClient, + symbols: Vec, + cache_mode: CacheMode, + retry_override: Option, +} + +impl QuotesBuilder { + /// Creates a new `QuotesBuilder`. + #[must_use] + pub const fn new(client: YfClient) -> Self { + Self { + client, + symbols: Vec::new(), + cache_mode: CacheMode::Use, + retry_override: None, + } + } + + /// Sets the cache mode for this specific API call. + #[must_use] + pub const fn cache_mode(mut self, mode: CacheMode) -> Self { + self.cache_mode = mode; + self + } + + /// Overrides the default retry policy for this specific API call. + #[must_use] + pub fn retry_policy(mut self, cfg: Option) -> Self { + self.retry_override = cfg; + self + } + + /// Replaces the current list of symbols with a new list. + #[must_use] + pub fn symbols(mut self, syms: I) -> Self + where + I: IntoIterator, + S: Into, + { + self.symbols = syms.into_iter().map(Into::into).collect(); + self + } + + /// Adds a single symbol to the list. + #[must_use] + pub fn add_symbol(mut self, sym: impl Into) -> Self { + self.symbols.push(sym.into()); + self + } + + /// Fetches the quotes for the configured symbols. + /// + /// # Errors + /// + /// Returns `YfError` if no symbols were provided, the network request fails, + /// the response cannot be parsed, or data for the symbols is not available. + pub async fn fetch(self) -> Result, crate::core::YfError> { + if self.symbols.is_empty() { + return Err(crate::core::YfError::InvalidParams( + "symbols list cannot be empty".into(), + )); + } + + let symbol_slices: Vec<&str> = self.symbols.iter().map(AsRef::as_ref).collect(); + let results = core_quotes::fetch_v7_quotes( + &self.client, + &symbol_slices, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await?; + + Ok(results.into_iter().map(Into::into).collect()) + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/search/mod.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/search/mod.rs new file mode 100644 index 0000000..9246191 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/search/mod.rs @@ -0,0 +1,335 @@ +use paft::domain::{AssetKind, Exchange}; +use paft::market::responses::search::{SearchResponse, SearchResult}; +use serde::Deserialize; +use url::Url; + +use crate::core::client::CacheMode; +use crate::core::client::RetryConfig; +use crate::{YfClient, YfError}; + +fn parse_search_body(body: &str) -> Result { + let env: V1SearchEnvelope = serde_json::from_str(body).map_err(YfError::Json)?; + + let quotes = env.quotes.unwrap_or_default(); + let results = quotes + .into_iter() + .filter_map(|q| { + let sym = q.symbol.unwrap_or_default(); + paft::domain::Symbol::try_from(sym) + .ok() + .map(|symbol| SearchResult { + symbol, + name: q.shortname.or(q.longname), + exchange: q.exchange.and_then(|s| s.parse::().ok()), + kind: q + .quote_type + .and_then(|s| s.parse::().ok()) + .unwrap_or_default(), + }) + }) + .collect(); + + Ok(SearchResponse { results }) +} + +/* ---------------- Public API ---------------- */ + +/// Searches for symbols matching a query. +/// +/// # Errors +/// +/// Returns `YfError` if the network request fails or the response cannot be parsed. +pub async fn search(client: &YfClient, query: &str) -> Result { + SearchBuilder::new(client, query).fetch().await +} + +/// A builder for searching for tickers and other assets on Yahoo Finance. +#[derive(Debug)] +pub struct SearchBuilder { + client: YfClient, + base: Url, + query: String, + quotes_count: Option, + news_count: Option, + lists_count: Option, + lang: Option, + region: Option, + cache_mode: CacheMode, + retry_override: Option, +} + +impl SearchBuilder { + /// Creates a new `SearchBuilder` for a given search query. + /// + /// # Panics + /// + /// This function will panic if the hardcoded `DEFAULT_BASE_SEARCH_V1` constant + /// is not a valid URL. This indicates a bug within the crate itself. + pub fn new(client: &YfClient, query: impl Into) -> Self { + Self { + client: client.clone(), + base: Url::parse(DEFAULT_BASE_SEARCH_V1).unwrap(), + query: query.into(), + quotes_count: Some(10), + news_count: Some(0), + lists_count: Some(0), + lang: None, + region: None, + cache_mode: CacheMode::Use, + retry_override: None, + } + } + + /// Sets the cache mode for this specific API call. + #[must_use] + pub const fn cache_mode(mut self, mode: CacheMode) -> Self { + self.cache_mode = mode; + self + } + + /// Overrides the default retry policy for this specific API call. + #[must_use] + pub fn retry_policy(mut self, cfg: Option) -> Self { + self.retry_override = cfg; + self + } + + /// (For testing) Overrides the base URL for the search API. + #[must_use] + pub fn search_base(mut self, base: Url) -> Self { + self.base = base; + self + } + + /// Sets the maximum number of quote results to return. + #[must_use] + pub const fn quotes_count(mut self, n: u32) -> Self { + self.quotes_count = Some(n); + self + } + + /// Sets the maximum number of news results to return. + #[must_use] + pub const fn news_count(mut self, n: u32) -> Self { + self.news_count = Some(n); + self + } + + /// Sets the maximum number of screener list results to return. + #[must_use] + pub const fn lists_count(mut self, n: u32) -> Self { + self.lists_count = Some(n); + self + } + + /// Sets the language for the search results. + #[must_use] + pub fn lang(mut self, s: impl Into) -> Self { + self.lang = Some(s.into()); + self + } + + /// Sets the region for the search results. + #[must_use] + pub fn region(mut self, s: impl Into) -> Self { + self.region = Some(s.into()); + self + } + + /// Returns the configured language parameter, if any. + #[must_use] + pub fn lang_ref(&self) -> Option<&str> { + self.lang.as_deref() + } + + /// Returns the configured region parameter, if any. + #[must_use] + pub fn region_ref(&self) -> Option<&str> { + self.region.as_deref() + } + + /// Executes the search request. + /// + /// # Errors + /// + /// This method will return an error if the network request fails, the API returns a + /// non-successful status code, or the response body cannot be parsed as a valid search result. + #[allow(clippy::too_many_lines)] + pub async fn fetch(self) -> Result { + let mut url = self.base.clone(); + Self::append_query_params( + &mut url, + &self.query, + self.quotes_count, + self.news_count, + self.lists_count, + self.lang.as_deref(), + self.region.as_deref(), + ); + + if self.cache_mode == CacheMode::Use + && let Some(body) = self.client.cache_get(&url).await + { + return parse_search_body(&body); + } + + let http = self.client.http().clone(); + let mut resp = self + .client + .send_with_retry( + http.get(url.clone()).header("accept", "application/json"), + self.retry_override.as_ref(), + ) + .await?; + + if !resp.status().is_success() { + let code = resp.status().as_u16(); + + if code == 401 || code == 403 { + self.client.ensure_credentials().await?; + let crumb = self + .client + .crumb() + .await + .ok_or_else(|| crate::core::YfError::Auth("Crumb is not set".into()))?; + + let mut url2 = self.base.clone(); + Self::append_query_params( + &mut url2, + &self.query, + self.quotes_count, + self.news_count, + self.lists_count, + self.lang.as_deref(), + self.region.as_deref(), + ); + url2.query_pairs_mut().append_pair("crumb", &crumb); + + resp = self + .client + .send_with_retry( + http.get(url2.clone()).header("accept", "application/json"), + self.retry_override.as_ref(), + ) + .await?; + + if !resp.status().is_success() { + let code = resp.status().as_u16(); + let url_s = url2.to_string(); + return Err(match code { + 404 => crate::core::YfError::NotFound { url: url_s }, + 429 => crate::core::YfError::RateLimited { url: url_s }, + 500..=599 => crate::core::YfError::ServerError { + status: code, + url: url_s, + }, + _ => crate::core::YfError::Status { + status: code, + url: url_s, + }, + }); + } + + let body = + crate::core::net::get_text(resp, "search_v1", &self.query, "json").await?; + if self.cache_mode != CacheMode::Bypass { + self.client.cache_put(&url2, &body, None).await; + } + return parse_search_body(&body); + } + + let url_s = url.to_string(); + return Err(match code { + 404 => crate::core::YfError::NotFound { url: url_s }, + 429 => crate::core::YfError::RateLimited { url: url_s }, + 500..=599 => crate::core::YfError::ServerError { + status: code, + url: url_s, + }, + _ => crate::core::YfError::Status { + status: code, + url: url_s, + }, + }); + } + + let body = crate::core::net::get_text(resp, "search_v1", &self.query, "json").await?; + if self.cache_mode != CacheMode::Bypass { + self.client.cache_put(&url, &body, None).await; + } + parse_search_body(&body) + } + + fn append_query_params( + url: &mut Url, + query: &str, + quotes_count: Option, + news_count: Option, + lists_count: Option, + lang: Option<&str>, + region: Option<&str>, + ) { + let mut qp = url.query_pairs_mut(); + qp.append_pair("q", query); + if let Some(n) = quotes_count { + qp.append_pair("quotesCount", &n.to_string()); + } + if let Some(n) = news_count { + qp.append_pair("newsCount", &n.to_string()); + } + if let Some(n) = lists_count { + qp.append_pair("listsCount", &n.to_string()); + } + if let Some(l) = lang { + qp.append_pair("lang", l); + } + if let Some(r) = region { + qp.append_pair("region", r); + } + } +} + +/* ---------------- Types returned by this module ---------------- */ +// Local types removed in favor of paft::market::responses::search::{SearchResponse, SearchResult} + +const DEFAULT_BASE_SEARCH_V1: &str = "https://query2.finance.yahoo.com/v1/finance/search"; + +/* ------------- Minimal serde mapping of /v1/finance/search ------------- */ + +#[derive(Deserialize)] +struct V1SearchEnvelope { + #[allow(dead_code)] + explains: Option, + #[allow(dead_code)] + count: Option, + quotes: Option>, + #[allow(dead_code)] + news: Option, + #[allow(dead_code)] + nav: Option, + #[allow(dead_code)] + lists: Option, +} + +#[derive(Deserialize)] +struct V1SearchQuote { + #[serde(default)] + symbol: Option, + #[serde(default)] + shortname: Option, + #[serde(default)] + longname: Option, + #[serde(rename = "quoteType")] + #[serde(default)] + quote_type: Option, + #[serde(default)] + exchange: Option, + #[allow(dead_code)] + #[serde(rename = "exchDisp")] + #[serde(default)] + exch_disp: Option, + #[allow(dead_code)] + #[serde(rename = "typeDisp")] + #[serde(default)] + type_disp: Option, +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/stream/mod.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/stream/mod.rs new file mode 100644 index 0000000..aa69f7f --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/stream/mod.rs @@ -0,0 +1,680 @@ +use base64::{Engine as _, engine::general_purpose}; +use chrono::{DateTime, Utc}; +use futures_util::{SinkExt, StreamExt}; +use prost::Message; +use serde::Serialize; +use std::time::Duration; +use tokio::{ + select, + sync::{mpsc, oneshot}, + task::JoinHandle, +}; +use tokio_tungstenite::{ + connect_async, + tungstenite::{ + handshake::client::{Request, generate_key}, + protocol::Message as WsMessage, + }, +}; + +use crate::{ + YfClient, YfError, + core::client::{CacheMode, RetryConfig}, + core::conversions::f64_to_money_with_currency_str, +}; +use paft::market::quote::QuoteUpdate; + +// Yahoo Finance websocket wire types (generated from `yaticker.proto`). +mod wire_ws { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct PricingData { + #[prost(string, tag = "1")] + pub id: String, + #[prost(float, tag = "2")] + pub price: f32, + #[prost(sint64, tag = "3")] + pub time: i64, + #[prost(string, tag = "4")] + pub currency: String, + #[prost(string, tag = "5")] + pub exchange: String, + #[prost(int32, tag = "6")] + pub quote_type: i32, + #[prost(int32, tag = "7")] + pub market_hours: i32, + #[prost(float, tag = "8")] + pub change_percent: f32, + #[prost(sint64, tag = "9")] + pub day_volume: i64, + #[prost(float, tag = "10")] + pub day_high: f32, + #[prost(float, tag = "11")] + pub day_low: f32, + #[prost(float, tag = "12")] + pub change: f32, + #[prost(string, tag = "13")] + pub short_name: String, + #[prost(sint64, tag = "14")] + pub expire_date: i64, + #[prost(float, tag = "15")] + pub open_price: f32, + #[prost(float, tag = "16")] + pub previous_close: f32, + #[prost(float, tag = "17")] + pub strike_price: f32, + #[prost(string, tag = "18")] + pub underlying_symbol: String, + #[prost(sint64, tag = "19")] + pub open_interest: i64, + #[prost(sint64, tag = "20")] + pub options_type: i64, + #[prost(sint64, tag = "21")] + pub mini_option: i64, + #[prost(sint64, tag = "22")] + pub last_size: i64, + #[prost(float, tag = "23")] + pub bid: f32, + #[prost(sint64, tag = "24")] + pub bid_size: i64, + #[prost(float, tag = "25")] + pub ask: f32, + #[prost(sint64, tag = "26")] + pub ask_size: i64, + #[prost(sint64, tag = "27")] + pub price_hint: i64, + #[prost(sint64, tag = "28")] + pub vol_24hr: i64, + #[prost(sint64, tag = "29")] + pub vol_all_currencies: i64, + #[prost(string, tag = "30")] + pub from_currency: String, + #[prost(string, tag = "31")] + pub last_market: String, + #[prost(double, tag = "32")] + pub circulating_supply: f64, + #[prost(double, tag = "33")] + pub market_cap: f64, + } +} + +// Use paft's QuoteUpdate which carries Money and DateTime +// pub use paft::market::quote::QuoteUpdate; (imported above) + +// Streaming quotes +// +// Volume semantics: +// - Yahoo sends cumulative intraday volume (`day_volume`). This crate converts it into +// per-update deltas when producing `QuoteUpdate`s. +// - For each symbol, the first observed tick (or after a detected reset where current < last) +// has `volume = None` (no delta yet). Subsequent ticks set `volume = Some(current - last)`. +// - This applies to both WebSocket and Polling streams. The JSON/base64 decoder helper +// (`decode_and_map_message`) is stateless and always returns `volume = None`. +// +// Implications: +// - If you need cumulative volume, accumulate the per-update `volume` values yourself or +// use the `day_volume` from quote endpoints. +// - Expect `None` for the first message per symbol and after rollovers. +/// Configuration for a polling-based quote stream. +#[derive(Debug, Clone)] +pub struct StreamConfig { + /// The interval at which to poll for new quote data. + pub interval: Duration, + /// If `true`, only emit updates when the price has changed. + pub diff_only: bool, +} + +impl Default for StreamConfig { + fn default() -> Self { + Self { + interval: Duration::from_secs(1), + diff_only: true, + } + } +} + +/// A handle to a running quote stream, used to stop it gracefully. +pub struct StreamHandle { + join: JoinHandle<()>, + stop_tx: Option>, +} + +impl StreamHandle { + /// Stops the stream and waits for the background task to complete. + pub async fn stop(mut self) { + if let Some(tx) = self.stop_tx.take() { + let _ = tx.send(()); + } + let _ = self.join.await; + } + + /// Aborts the background task immediately. + pub fn abort(self) { + self.join.abort(); + } +} + +/// Defines the transport method for streaming quote data. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum StreamMethod { + /// Attempt to use `WebSockets`, and fall back to polling if the connection fails. (Default) + #[default] + WebsocketWithFallback, + /// Use `WebSockets` only. This is the preferred method for real-time data. The stream will fail if a WebSocket connection cannot be established. + Websocket, + /// Use polling over HTTP. This is a less efficient fallback option. + Polling, +} + +/// Builds and starts a real-time quote stream. +pub struct StreamBuilder { + client: YfClient, + symbols: Vec, + cfg: StreamConfig, + method: StreamMethod, + cache_mode: CacheMode, + retry_override: Option, +} + +impl StreamBuilder { + /// Creates a new `StreamBuilder`. + #[must_use] + pub fn new(client: &YfClient) -> Self { + Self { + client: client.clone(), + symbols: Vec::new(), + cfg: StreamConfig::default(), + method: StreamMethod::default(), + cache_mode: CacheMode::Use, + retry_override: None, + } + } + + /// Sets the cache mode for this specific API call (only affects polling mode). + #[must_use] + pub const fn cache_mode(mut self, mode: CacheMode) -> Self { + self.cache_mode = mode; + self + } + + /// Overrides the default retry policy for this specific API call (only affects polling mode). + #[must_use] + pub fn retry_policy(mut self, cfg: Option) -> Self { + self.retry_override = cfg; + self + } + + /// Sets the symbols to stream. + #[must_use] + pub fn symbols(mut self, syms: I) -> Self + where + I: IntoIterator, + S: Into, + { + self.symbols = syms.into_iter().map(std::convert::Into::into).collect(); + self + } + + /// Adds a single symbol to the stream. + #[must_use] + pub fn add_symbol(mut self, sym: impl Into) -> Self { + self.symbols.push(sym.into()); + self + } + + /// Sets the streaming transport method. + #[must_use] + pub const fn method(mut self, method: StreamMethod) -> Self { + self.method = method; + self + } + + /// Sets the polling interval. (Only used for `Polling` and `WebsocketWithFallback` methods). + #[must_use] + pub const fn interval(mut self, dur: Duration) -> Self { + self.cfg.interval = dur; + self + } + + /// If `true`, only emit updates when the price changes. (Only used for `Polling` method). + #[must_use] + pub const fn diff_only(mut self, yes: bool) -> Self { + self.cfg.diff_only = yes; + self + } + + /// Starts the stream, returning a handle to control it and a channel receiver for quote updates. + /// + /// # Errors + /// + /// This method will return an error if no symbols have been added to the builder. + pub fn start( + self, + ) -> Result<(StreamHandle, tokio::sync::mpsc::Receiver), crate::core::YfError> + { + if self.symbols.is_empty() { + return Err(crate::core::YfError::InvalidParams( + "symbols list cannot be empty".into(), + )); + } + + let (tx, rx) = tokio::sync::mpsc::channel::(1024); + let (stop_tx, stop_rx) = tokio::sync::oneshot::channel::<()>(); + + let join = tokio::spawn({ + let client = self.client; + let symbols = self.symbols.clone(); + let cfg = self.cfg.clone(); + + let mut stop_rx = stop_rx; + + // NEW: + let cache_mode = self.cache_mode; + let retry_override = self.retry_override.clone(); + + async move { + match self.method { + StreamMethod::Websocket => { + if let Err(e) = + run_websocket_stream(&client, symbols, tx, &mut stop_rx).await + && std::env::var("YF_DEBUG").ok().as_deref() == Some("1") + { + eprintln!("YF_DEBUG(stream): websocket stream failed: {e}"); + } + } + StreamMethod::WebsocketWithFallback => { + if let Err(e) = + run_websocket_stream(&client, symbols.clone(), tx.clone(), &mut stop_rx) + .await + { + if std::env::var("YF_DEBUG").ok().as_deref() == Some("1") { + eprintln!( + "YF_DEBUG(stream): websocket failed ({e}), falling back to polling." + ); + } + run_polling_stream( + client, + symbols, + cfg, + tx, + &mut stop_rx, + cache_mode, + retry_override.as_ref(), + ) + .await; + } + } + StreamMethod::Polling => { + run_polling_stream( + client, + symbols, + cfg, + tx, + &mut stop_rx, + cache_mode, + retry_override.as_ref(), + ) + .await; + } + } + } + }); + + Ok(( + StreamHandle { + join, + stop_tx: Some(stop_tx), + }, + rx, + )) + } +} + +#[derive(Serialize)] +struct WsSubscribe<'a> { + subscribe: &'a [String], +} + +#[allow(clippy::too_many_lines)] +async fn run_websocket_stream( + client: &YfClient, + symbols: Vec, + tx: mpsc::Sender, + stop_rx: &mut oneshot::Receiver<()>, +) -> Result<(), YfError> { + let base = client.base_stream(); + let host = base + .host_str() + .ok_or_else(|| YfError::InvalidParams("URL has no host".into()))?; + + let request = Request::builder() + .uri(base.as_str()) + .header("Host", host) + .header("Origin", "https://finance.yahoo.com") + .header("User-Agent", client.user_agent()) + .header("Upgrade", "websocket") + .header("Connection", "Upgrade") + .header("Sec-WebSocket-Key", generate_key()) + .header("Sec-WebSocket-Version", "13") + .body(()) + .map_err(|e| YfError::InvalidParams(format!("Failed to build websocket request: {e}")))?; + + let (ws_stream, _) = connect_async(request).await?; + let (mut write, mut read) = ws_stream.split(); + + let sub_msg = serde_json::to_string(&WsSubscribe { + subscribe: &symbols, + }) + .map_err(YfError::Json)?; + write.send(WsMessage::Text(sub_msg.into())).await?; + + #[cfg(feature = "test-mode")] + let mut recorded = false; + + let mut last_day_volume: std::collections::HashMap = + std::collections::HashMap::new(); + let mut last_ts: std::collections::HashMap> = + std::collections::HashMap::new(); + + loop { + select! { + msg = read.next() => { + match msg { + Some(Ok(WsMessage::Text(text))) => { + #[cfg(feature = "test-mode")] + { + if !recorded && std::env::var("YF_RECORD").ok().as_deref() == Some("1") { + if let Err(e) = crate::core::fixtures::record_fixture("stream_ws", "MULTI", "b64", &text) { + eprintln!("YF_RECORD: failed to write stream fixture: {e}"); + } + recorded = true; + } + } + + match decode_ws_pricing(&text) { + Ok(ticker) => { + if let Some(update) = map_ws_pricing_to_update_with_delta(&ticker, &mut last_day_volume, &mut last_ts) + && tx.send(update).await.is_err() { break; } + }, + Err(e) => { + if std::env::var("YF_DEBUG").ok().as_deref() == Some("1") { + eprintln!("YF_DEBUG(stream): ws text decode error: {e}"); + } + // Non-price frames (acks/heartbeats) may lack "message"; ignore. + } + } + } + Some(Ok(WsMessage::Binary(bin))) => { + // Try to interpret as UTF-8 JSON-wrapped base64 first + let handled = if let Ok(as_text) = std::str::from_utf8(&bin) { + if let Ok(ticker) = decode_ws_pricing(as_text) { + if let Some(update) = map_ws_pricing_to_update_with_delta(&ticker, &mut last_day_volume, &mut last_ts) + && tx.send(update).await.is_err() { break; } + true + } else { false } + } else { false }; + // If not handled, treat as raw protobuf bytes + if !handled { + match wire_ws::PricingData::decode(&*bin) { + Ok(ticker) => { + if let Some(update) = map_ws_pricing_to_update_with_delta(&ticker, &mut last_day_volume, &mut last_ts) + && tx.send(update).await.is_err() { break; } + } + Err(e) => { + if std::env::var("YF_DEBUG").ok().as_deref() == Some("1") { + eprintln!("YF_DEBUG(stream): ws binary decode error: {e}"); + } + } + } + } + } + Some(Ok(WsMessage::Ping(_) | WsMessage::Pong(_) | _)) => { /* catch-all for variants like Frame(_) */ } + Some(Err(e)) => return Err(e.into()), + None => break, + } + }, + _ = &mut *stop_rx => { + break; + } + } + } + Ok(()) +} + +fn decode_ws_pricing(text: &str) -> Result { + let s = text.trim(); + let b64_cow: std::borrow::Cow = if s.starts_with('{') { + match serde_json::from_str::(s) { + Ok(v) => { + let msg = v.get("message").and_then(|m| m.as_str()).ok_or_else(|| { + YfError::MissingData("ws json message missing 'message' field".into()) + })?; + std::borrow::Cow::Owned(msg.to_string()) + } + Err(_) => std::borrow::Cow::Borrowed(s), + } + } else { + std::borrow::Cow::Borrowed(s) + }; + let decoded = general_purpose::STANDARD + .decode(b64_cow.as_ref()) + .map_err(YfError::Base64)?; + let ticker = wire_ws::PricingData::decode(&*decoded)?; + Ok(ticker) +} + +fn map_ws_pricing_to_update_with_delta( + ticker: &wire_ws::PricingData, + last_vol: &mut std::collections::HashMap, + last_ts: &mut std::collections::HashMap>, +) -> Option { + let currency_str = Some(ticker.currency.as_str()); + let Ok(symbol) = paft::domain::Symbol::new(&ticker.id) else { + if std::env::var("YF_DEBUG").ok().as_deref() == Some("1") { + eprintln!( + "YF_DEBUG(stream): skipping ws update with invalid symbol: {}", + ticker.id + ); + } + return None; + }; + let Some(timestamp) = DateTime::from_timestamp_millis(ticker.time) else { + if std::env::var("YF_DEBUG").ok().as_deref() == Some("1") { + eprintln!( + "YF_DEBUG(stream): skipping ws update with invalid timestamp: {}", + ticker.time + ); + } + return None; + }; + + // If out-of-order, emit but don't mutate state; volume=None + if let Some(prev_ts) = last_ts.get(&ticker.id) + && timestamp < *prev_ts + { + return Some(QuoteUpdate { + symbol, + price: Some(f64_to_money_with_currency_str( + f64::from(ticker.price), + currency_str, + )), + previous_close: Some(f64_to_money_with_currency_str( + f64::from(ticker.previous_close), + currency_str, + )), + ts: timestamp, + volume: None, + }); + } + + let cur_vol = u64::try_from(ticker.day_volume).unwrap_or(0); + let prev_vol = last_vol.get(&ticker.id).copied(); + let volume = match prev_vol { + Some(p) if cur_vol >= p => Some(cur_vol - p), + // First observation or reset forward in time → no delta + _ => None, + }; + + // Update state only for in-order ticks + last_ts.insert(ticker.id.clone(), timestamp); + last_vol.insert(ticker.id.clone(), cur_vol); + + Some(QuoteUpdate { + symbol, + price: Some(f64_to_money_with_currency_str( + f64::from(ticker.price), + currency_str, + )), + previous_close: Some(f64_to_money_with_currency_str( + f64::from(ticker.previous_close), + currency_str, + )), + ts: timestamp, + volume, + }) +} + +/// Decodes a single base64-encoded protobuf message from the Yahoo Finance WebSocket stream. +#[doc(hidden)] +pub fn decode_and_map_message(text: &str) -> Result { + // Support both: + // 1) Raw base64 string + // 2) JSON wrapper: {"message":""} (Yahoo's current format) + let s = text.trim(); + + // Use Cow to avoid borrowing from a temporary JSON value + let b64_cow: std::borrow::Cow = if s.starts_with('{') { + match serde_json::from_str::(s) { + Ok(v) => { + let msg = v.get("message").and_then(|m| m.as_str()).ok_or_else(|| { + YfError::MissingData("ws json message missing 'message' field".into()) + })?; + std::borrow::Cow::Owned(msg.to_string()) + } + // If it's not valid JSON, treat the whole thing as raw base64 + Err(_) => std::borrow::Cow::Borrowed(s), + } + } else { + std::borrow::Cow::Borrowed(s) + }; + + let decoded = general_purpose::STANDARD + .decode(b64_cow.as_ref()) + .map_err(YfError::Base64)?; + let ticker = wire_ws::PricingData::decode(&*decoded)?; + let currency_str = Some(ticker.currency.as_str()); + let symbol = paft::domain::Symbol::new(&ticker.id) + .map_err(|_| YfError::InvalidParams(format!("ws symbol invalid: {}", ticker.id)))?; + + let Some(timestamp) = DateTime::from_timestamp_millis(ticker.time) else { + // Log the error and return an error from this function + if std::env::var("YF_DEBUG").ok().as_deref() == Some("1") { + eprintln!( + "YF_DEBUG(stream): received ws update with invalid timestamp millis: {}", + ticker.time + ); + } + #[cfg(feature = "tracing")] + tracing::warn!(timestamp_millis = ticker.time, symbol = %ticker.id, "received ws update with invalid timestamp"); + // Return an error instead of default + return Err(YfError::InvalidParams(format!( + "Invalid timestamp in stream message: {}", + ticker.time + ))); + }; + + Ok(QuoteUpdate { + symbol, + price: Some(f64_to_money_with_currency_str( + f64::from(ticker.price), + currency_str, + )), + previous_close: Some(f64_to_money_with_currency_str( + f64::from(ticker.previous_close), + currency_str, + )), + ts: timestamp, + volume: None, + }) +} + +#[allow(clippy::too_many_arguments)] +async fn run_polling_stream( + client: crate::core::YfClient, + symbols: Vec, + cfg: StreamConfig, + tx: tokio::sync::mpsc::Sender, + stop_rx: &mut tokio::sync::oneshot::Receiver<()>, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) { + let mut ticker = tokio::time::interval(cfg.interval); + let mut last_price: std::collections::HashMap> = + std::collections::HashMap::new(); + let mut last_day_volume: std::collections::HashMap = + std::collections::HashMap::new(); + + let symbol_slices: Vec<&str> = symbols.iter().map(AsRef::as_ref).collect(); + + loop { + tokio::select! { + _ = ticker.tick() => { + if tx.is_closed() { break; } + let ts: DateTime = chrono::Utc::now(); + match crate::core::quotes::fetch_v7_quotes(&client, &symbol_slices, cache_mode, retry_override).await { + Ok(quotes) => { + for q in quotes { + let sym_s = q.symbol.clone().unwrap_or_default(); + let lp = q.regular_market_price.or(q.regular_market_previous_close); + + // Track price changes when diff_only is enabled + let price_changed = if cfg.diff_only { + let prev = last_price.insert(sym_s.clone(), lp); + prev != Some(lp) + } else { + true + }; + + // Compute volume delta and detect changes, including resets (cur < prev) + let (vol_delta, vol_changed) = q.regular_market_volume.map_or((None, false), |cur| { + let prev = last_day_volume.insert(sym_s.clone(), cur); + match prev { + Some(p) if cur >= p => { + let d = cur - p; + (Some(d), d > 0) + } + Some(p) if cur < p => (None, true), // reset detected + _ => (None, false), // first observation; no delta + } + }); + + // With diff_only, emit if either price OR volume changed + if cfg.diff_only && !price_changed && !vol_changed { + continue; + } + + let currency_str = q.currency.as_deref(); + let Ok(symbol) = paft::domain::Symbol::new(&sym_s) else { continue }; + if tx.send(QuoteUpdate { + symbol, + price: lp.map(|v| f64_to_money_with_currency_str(v, currency_str)), + previous_close: q.regular_market_previous_close.map(|v| f64_to_money_with_currency_str(v, currency_str)), + ts, + volume: vol_delta, + }).await.is_err() { + // Break outer loop if receiver is dropped + break; + } + } + } + Err(e) => { + if std::env::var("YF_DEBUG").ok().as_deref() == Some("1") { + eprintln!("YF_DEBUG(stream): fetch error: {e}"); + } + } + } + if tx.is_closed() { break; } + } + _ = &mut *stop_rx => { break; } + } + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/stream/yaticker.proto b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/stream/yaticker.proto new file mode 100644 index 0000000..9805251 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/stream/yaticker.proto @@ -0,0 +1,38 @@ +syntax = "proto3"; +package Yaticker; + +message PricingData { + string id = 1; + float price = 2; + sint64 time = 3; + string currency = 4; + string exchange = 5; + int32 quote_type = 6; + int32 market_hours = 7; + float change_percent = 8; + sint64 day_volume = 9; + float day_high = 10; + float day_low = 11; + float change = 12; + string short_name = 13; + sint64 expire_date = 14; + float open_price = 15; + float previous_close = 16; + float strike_price = 17; + string underlying_symbol = 18; + sint64 open_interest = 19; + sint64 options_type = 20; + sint64 mini_option = 21; + sint64 last_size = 22; + float bid = 23; + sint64 bid_size = 24; + float ask = 25; + sint64 ask_size = 26; + sint64 price_hint = 27; + sint64 vol_24hr = 28; + sint64 vol_all_currencies = 29; + string from_currency = 30; + string last_market = 31; + double circulating_supply = 32; + double market_cap = 33; +} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/ticker/info.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/ticker/info.rs new file mode 100644 index 0000000..707d900 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/ticker/info.rs @@ -0,0 +1,134 @@ +use crate::{ + YfClient, YfError, analysis, + core::client::{CacheMode, RetryConfig}, + core::conversions::i64_to_datetime, + esg, + profile::Profile, +}; +use paft::aggregates::Info; + +/// Private helper to handle optional async results, logging errors in debug mode. +fn log_err_async(res: Result, name: &str, symbol: &str) -> Option { + match res { + Ok(data) => Some(data), + Err(e) => { + if std::env::var("YF_DEBUG").ok().as_deref() == Some("1") { + eprintln!("YF_DEBUG(info): failed to fetch '{name}' for {symbol}: {e}"); + } + None + } + } +} + +pub(super) async fn fetch_info( + client: &YfClient, + symbol: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result { + let (quote, profile, price_target, rec_summary, esg_summary) = + Box::pin(fetch_info_parts(client, symbol, cache_mode, retry_override)).await?; + let isin = extract_isin(&profile); + Ok(assemble_info( + symbol, + quote.as_ref(), + isin, + price_target, + rec_summary, + esg_summary.and_then(|s| s.scores), + )) +} + +async fn fetch_info_parts( + client: &YfClient, + symbol: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result< + ( + Option, + Profile, + Option, + Option, + Option, + ), + YfError, +> { + let (quote_res, profile_res, price_target_res, rec_summary_res, esg_res) = tokio::join!( + crate::ticker::quote::fetch_quote(client, symbol, cache_mode, retry_override), + crate::profile::load_profile(client, symbol), + analysis::AnalysisBuilder::new(client, symbol) + .cache_mode(cache_mode) + .retry_policy(retry_override.cloned()) + .analyst_price_target(None), + analysis::AnalysisBuilder::new(client, symbol) + .cache_mode(cache_mode) + .retry_policy(retry_override.cloned()) + .recommendations_summary(), + esg::EsgBuilder::new(client, symbol) + .cache_mode(cache_mode) + .retry_policy(retry_override.cloned()) + .fetch() + ); + + let profile = profile_res?; + let quote = log_err_async(quote_res, "quote", symbol); + let price_target = log_err_async(price_target_res, "price_target", symbol); + let rec_summary = log_err_async(rec_summary_res, "recommendations_summary", symbol); + let esg_summary = log_err_async(esg_res, "esg_scores", symbol); + Ok((quote, profile, price_target, rec_summary, esg_summary)) +} + +fn extract_isin(profile: &Profile) -> Option { + match profile { + Profile::Company(c) => c.isin.clone(), + Profile::Fund(f) => f.isin.clone(), + } +} + +fn assemble_info( + symbol: &str, + quote: Option<&crate::Quote>, + isin: Option, + price_target: Option, + rec_summary: Option, + esg_scores: Option, +) -> Info { + Info { + symbol: quote.map_or_else( + || paft::domain::Symbol::new(symbol).expect("invalid symbol"), + |q| q.symbol.clone(), + ), + name: quote.and_then(|q| q.shortname.clone()), + isin, + exchange: quote.and_then(|q| q.exchange.clone()), + market_state: quote.and_then(|q| q.market_state), + currency: quote.and_then(|q| { + q.price + .as_ref() + .map(|m| m.currency().clone()) + .or_else(|| q.previous_close.as_ref().map(|m| m.currency().clone())) + }), + last: quote.and_then(|q| q.price.clone()), + open: None, + high: None, + low: None, + previous_close: quote.and_then(|q| q.previous_close.clone()), + day_range_low: None, + day_range_high: None, + fifty_two_week_low: None, + fifty_two_week_high: None, + volume: quote.and_then(|q| q.day_volume), + average_volume: None, + market_cap: None, + shares_outstanding: None, + eps_ttm: None, + pe_ttm: None, + dividend_yield: None, + ex_dividend_date: None, + as_of: Some(i64_to_datetime(chrono::Utc::now().timestamp())), + price_target, + recommendation_summary: rec_summary, + esg_scores, + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/ticker/isin.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/ticker/isin.rs new file mode 100644 index 0000000..e13e4db --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/ticker/isin.rs @@ -0,0 +1,274 @@ +use crate::{ + YfClient, YfError, + core::{client::RetryConfig, net}, +}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct FlatSuggest { + #[serde(alias = "Value", alias = "value")] + value: Option, + #[serde(alias = "Symbol", alias = "symbol")] + symbol: Option, + #[serde(alias = "Isin", alias = "isin", alias = "ISIN")] + isin: Option, +} + +pub(super) async fn fetch_isin( + client: &YfClient, + symbol: &str, + retry_override: Option<&RetryConfig>, +) -> Result, YfError> { + let Some(body) = fetch_isin_body(client, symbol, retry_override).await? else { + return Ok(None); + }; + + let debug = std::env::var("YF_DEBUG").ok().as_deref() == Some("1"); + let input_norm = normalize_sym(symbol); + + if let Some(isin) = parse_as_json_value(&body, &input_norm, debug) { + return Ok(Some(isin)); + } + + if let Some(isin) = parse_as_flat_suggest(&body, &input_norm) { + return Ok(Some(isin)); + } + + if let Some(isin) = scan_raw_body(&body, debug) { + return Ok(Some(isin)); + } + + if debug { + eprintln!("YF_DEBUG(isin): No matching ISIN found in any response shape."); + } + Ok(None) +} + +async fn fetch_isin_body( + client: &YfClient, + symbol: &str, + retry_override: Option<&RetryConfig>, +) -> Result, YfError> { + let mut url = client.base_insider_search().clone(); + url.query_pairs_mut() + .append_pair("max_results", "5") + .append_pair("query", symbol); + + let req = client.http().get(url.clone()); + let resp = client.send_with_retry(req, retry_override).await?; + + if !resp.status().is_success() { + return Ok(None); + } + + Ok(Some( + net::get_text(resp, "isin_search", symbol, "json").await?, + )) +} + +fn parse_as_json_value(body: &str, input_norm: &str, debug: bool) -> Option { + if let Ok(val) = serde_json::from_str::(body) { + if let Some(hit) = extract_from_json_value(&val, input_norm) { + if debug { + eprintln!("YF_DEBUG(isin): ISIN extracted from JSON structures: {hit}",); + } + return Some(hit); + } + } else if debug { + eprintln!("YF_DEBUG(isin): failed to parse JSON response for query '{input_norm}'",); + } + None +} + +fn parse_as_flat_suggest(body: &str, input_norm: &str) -> Option { + if let Ok(raw_arr) = serde_json::from_str::>(body) { + for r in &raw_arr { + if let Some(isin) = r.isin.as_deref() + && looks_like_isin(isin) + && r.symbol.as_deref().map(normalize_sym) == Some(input_norm.to_string()) + { + return Some(isin.to_uppercase()); + } + if let Some(value) = r.value.as_deref() { + let parts: Vec = value + .split('|') + .map(|p| p.trim().to_string()) + .filter(|p| !p.is_empty()) + .collect(); + if let Some(isin) = pick_from_parts(&parts, input_norm) { + return Some(isin); + } + } + } + for r in &raw_arr { + if let Some(isin) = r.isin.as_deref() + && looks_like_isin(isin) + { + return Some(isin.to_uppercase()); + } + if let Some(value) = r.value.as_deref() + && let Some(tok) = value + .split('|') + .map(str::trim) + .find(|tok| looks_like_isin(tok)) + { + return Some((*tok).to_uppercase()); + } + } + } + None +} + +fn scan_raw_body(body: &str, debug: bool) -> Option { + let mut token = String::new(); + for ch in body.chars() { + if ch.is_ascii_alphanumeric() { + token.push(ch); + if token.len() > 12 { + token.remove(0); + } + if token.len() == 12 && looks_like_isin(&token) { + if debug { + eprintln!("YF_DEBUG(isin): Fallback raw scan found ISIN: {token}"); + } + return Some(token.to_uppercase()); + } + } else { + token.clear(); + } + } + None +} + +fn extract_from_json_value(v: &serde_json::Value, target_norm: &str) -> Option { + let mut arrays: Vec<&serde_json::Value> = Vec::new(); + + match v { + serde_json::Value::Array(_) => arrays.push(v), + serde_json::Value::Object(map) => { + for key in [ + "Suggestions", + "suggestions", + "items", + "results", + "Result", + "data", + ] { + if let Some(val) = map.get(key) + && val.is_array() + { + arrays.push(val); + } + } + if arrays.is_empty() { + for (_, val) in map { + if val.is_array() { + arrays.push(val); + } else if let Some(obj) = val.as_object() { + for (_, inner) in obj { + if inner.is_array() { + arrays.push(inner); + } + } + } + } + } + } + _ => {} + } + + for arr in arrays { + if let Some(a) = arr.as_array() { + for item in a { + if let Some(obj) = item.as_object() { + for k in ["Isin", "isin", "ISIN"] { + if let Some(isin_val) = obj.get(k).and_then(|x| x.as_str()) + && looks_like_isin(isin_val) + { + let sym = obj + .get("Symbol") + .and_then(|x| x.as_str()) + .or_else(|| obj.get("symbol").and_then(|x| x.as_str())) + .unwrap_or(""); + if sym.is_empty() || normalize_sym(sym) == target_norm { + return Some(isin_val.to_uppercase()); + } + } + } + + let value_str = obj + .get("Value") + .and_then(|x| x.as_str()) + .or_else(|| obj.get("value").and_then(|x| x.as_str())) + .unwrap_or(""); + if !value_str.is_empty() { + let parts: Vec = value_str + .split('|') + .map(|p| p.trim().to_string()) + .filter(|p| !p.is_empty()) + .collect(); + if let Some(isin) = pick_from_parts(&parts, target_norm) { + return Some(isin); + } + } + + if let Some(sym) = obj + .get("Symbol") + .and_then(|x| x.as_str()) + .or_else(|| obj.get("symbol").and_then(|x| x.as_str())) + && normalize_sym(sym) == target_norm + { + for (_k, v) in obj { + if let Some(s) = v.as_str() + && looks_like_isin(s) + { + return Some(s.to_uppercase()); + } + } + } + } + } + } + } + None +} + +fn normalize_sym(s: &str) -> String { + let mut t = s.trim().replace('-', "."); + for sep in ['.', ':', ' ', '\t', '\n', '\r'] { + if let Some(idx) = t.find(sep) { + t.truncate(idx); + break; + } + } + t.to_ascii_lowercase() +} + +fn looks_like_isin(s: &str) -> bool { + let t = s.trim(); + if t.len() != 12 { + return false; + } + let b = t.as_bytes(); + if !(b[0].is_ascii_alphabetic() && b[1].is_ascii_alphabetic()) { + return false; + } + if !t[2..11].chars().all(|c| c.is_ascii_alphanumeric()) { + return false; + } + b[11].is_ascii_digit() +} + +fn pick_from_parts(parts: &[String], target_norm: &str) -> Option { + if let Some(first) = parts.first() + && normalize_sym(first) == target_norm + && let Some(isin) = parts + .iter() + .map(std::string::String::as_str) + .find(|s| looks_like_isin(s)) + { + return Some(isin.to_uppercase()); + } + + None +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/ticker/mod.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/ticker/mod.rs new file mode 100644 index 0000000..2a15cce --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/ticker/mod.rs @@ -0,0 +1,691 @@ +mod info; +mod isin; +mod model; +mod options; +mod quote; + +pub use model::{Info, OptionChain, OptionContract}; +pub use paft::aggregates::FastInfo; + +use crate::core::{Action, Candle, HistoryMeta, Interval, Quote, Range}; +use crate::fundamentals::{Calendar, ShareCount}; +use crate::holders::{ + InsiderRosterHolder, InsiderTransaction, InstitutionalHolder, MajorHolder, + NetSharePurchaseActivity, +}; +use crate::news::NewsArticle; +use crate::{ + EsgBuilder, + core::client::RetryConfig, + core::conversions::{datetime_to_i64, money_to_currency_str, money_to_f64}, + core::{CacheMode, YfClient, YfError}, + holders::HoldersBuilder, + news::NewsBuilder, +}; +use crate::{ + analysis::AnalysisBuilder, fundamentals::FundamentalsBuilder, history::HistoryBuilder, +}; +use paft::fundamentals::analysis::{ + Earnings, EarningsTrendRow, PriceTarget, RecommendationRow, RecommendationSummary, + UpgradeDowngradeRow, +}; +use paft::fundamentals::statements::{BalanceSheetRow, CashflowRow, IncomeStatementRow}; +use paft::money::Currency; + +/// A high-level interface for a single ticker symbol, providing convenient access to all available data. +/// +/// This struct is designed to be the primary entry point for users who want to fetch +/// various types of financial data for a specific security, similar to the `Ticker` +/// object in the Python `yfinance` library. +/// +/// A `Ticker` is created with a [`YfClient`] and a symbol. It then provides methods +/// to fetch quotes, historical prices, options chains, financials, and more. +/// +/// # Example +/// +/// ```no_run +/// # use yfinance_rs::{Ticker, YfClient}; +/// # #[tokio::main] +/// # async fn main() -> Result<(), Box> { +/// let client = YfClient::default(); +/// let ticker = Ticker::new(&client, "TSLA"); +/// +/// // Get the latest quote +/// let quote = ticker.quote().await?; +/// println!("Tesla's last price: {}", quote.price.as_ref().map(|p| yfinance_rs::core::conversions::money_to_f64(p)).unwrap_or(0.0)); +/// +/// // Get historical prices for the last year +/// let history = ticker.history(Some(yfinance_rs::Range::Y1), None, false).await?; +/// println!("Fetched {} days of history.", history.len()); +/// # Ok(()) +/// # } +/// ``` +pub struct Ticker { + #[doc(hidden)] + pub(crate) client: YfClient, + #[doc(hidden)] + pub(crate) symbol: String, + #[doc(hidden)] + cache_mode: CacheMode, + retry_override: Option, +} + +impl Ticker { + /// Creates a new `Ticker` for a given symbol. + /// + /// This is the standard way to create a ticker instance with default API endpoints. + pub fn new(client: &YfClient, symbol: impl Into) -> Self { + Self { + client: client.clone(), + symbol: symbol.into(), + cache_mode: CacheMode::Use, + retry_override: None, + } + } + + /// Sets the cache mode for all subsequent API calls made by this `Ticker` instance. + /// + /// This allows you to override the client's default cache behavior for a specific ticker. + #[must_use] + pub const fn cache_mode(mut self, mode: CacheMode) -> Self { + self.cache_mode = mode; + self + } + + /// Overrides the client's default retry policy for all subsequent API calls made by this `Ticker` instance. + #[must_use] + pub fn retry_policy(mut self, cfg: Option) -> Self { + self.retry_override = cfg; + self + } + + /// Fetches a comprehensive `Info` struct containing quote, profile, analysis, and ESG data. + /// + /// This method conveniently aggregates data from multiple endpoints into a single struct, + /// similar to the `.info` attribute in the Python `yfinance` library. It makes several + /// API calls concurrently to gather the data efficiently. + /// + /// If a non-essential part of the data fails to load (e.g., ESG scores), the corresponding + /// fields in the `Info` struct will be `None`. A failure to load the core profile + /// will result in an error. + /// + /// # Errors + /// + /// This method will return an error if the core profile data cannot be fetched. + #[cfg_attr(feature = "tracing", tracing::instrument(skip(self), err, fields(symbol = %self.symbol)))] + pub async fn info(&self) -> Result { + Box::pin(info::fetch_info( + &self.client, + &self.symbol, + self.cache_mode, + self.retry_override.as_ref(), + )) + .await + } + + /* ---------------- Quotes ---------------- */ + + /// Fetches a detailed quote for the ticker. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + #[cfg_attr(feature = "tracing", tracing::instrument(skip(self), err, fields(symbol = %self.symbol)))] + pub async fn quote(&self) -> Result { + quote::fetch_quote( + &self.client, + &self.symbol, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } + + /// Fetches a "fast" info quote, containing the most essential price and market data. + /// + /// # Errors + /// + /// This method will return an error if the request fails, the response cannot be parsed, + /// or if the last/previous price is not available in the quote. + pub async fn fast_info(&self) -> Result { + let q = self.quote().await?; + Ok(FastInfo { + symbol: q.symbol, + name: q.shortname.clone(), + exchange: q.exchange, + market_state: q.market_state, + currency: q + .price + .as_ref() + .and_then(money_to_currency_str) + .or_else(|| q.previous_close.as_ref().and_then(money_to_currency_str)) + .and_then(|code| code.parse().ok()), + last: q.price, + previous_close: q.previous_close, + volume: q.day_volume, + }) + } + + /* ---------------- News convenience ---------------- */ + + /// Returns a `NewsBuilder` to construct a query for news articles. + #[must_use] + pub fn news_builder(&self) -> NewsBuilder { + NewsBuilder::new(&self.client, &self.symbol) + .cache_mode(self.cache_mode) + .retry_policy(self.retry_override.clone()) + } + + /// Fetches the latest news articles for the ticker. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn news(&self) -> Result, YfError> { + self.news_builder().fetch().await + } + + /* ---------------- History helpers ---------------- */ + + /// Returns a `HistoryBuilder` to construct a detailed query for historical price data. + #[must_use] + pub fn history_builder(&self) -> HistoryBuilder { + HistoryBuilder::new(&self.client, &self.symbol) + } + + /// Fetches historical price candles with default settings. + /// + /// Prices are automatically adjusted for splits and dividends. For more control, use [`history_builder`]. + /// + /// # Arguments + /// * `range` - The relative time range for the data (e.g., `1y`, `6mo`). Defaults to `6mo` if `None`. + /// * `interval` - The time interval for each candle (e.g., `1d`, `1wk`). Defaults to `1d` if `None`. + /// * `prepost` - Whether to include pre-market and post-market data for intraday intervals. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + #[cfg_attr(feature = "tracing", tracing::instrument(skip(self), err, fields(symbol = %self.symbol)))] + pub async fn history( + &self, + range: Option, + interval: Option, + prepost: bool, + ) -> Result, crate::core::YfError> { + let mut hb = self.history_builder(); + if let Some(r) = range { + hb = hb.range(r); + } + if let Some(i) = interval { + hb = hb.interval(i); + } + hb = hb + .auto_adjust(true) + .prepost(prepost) + .actions(true) + .cache_mode(self.cache_mode) + .retry_policy(self.retry_override.clone()); + hb.fetch().await + } + + /// Fetches all corporate actions (dividends and splits) for the given range. + /// + /// Defaults to the maximum available range if `None`. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + #[cfg_attr(feature = "tracing", tracing::instrument(skip(self), err, fields(symbol = %self.symbol)))] + pub async fn actions(&self, range: Option) -> Result, YfError> { + let mut hb = self.history_builder(); + hb = hb.range(range.unwrap_or(Range::Max)); + let resp = hb.auto_adjust(true).actions(true).fetch_full().await?; + let mut actions = resp.actions; + actions.sort_by_key(|a| match *a { + Action::Dividend { ts, .. } + | Action::Split { ts, .. } + | Action::CapitalGain { ts, .. } => ts, + }); + Ok(actions) + } + + /// Fetches all dividend payments for the given range. + /// + /// Returns a `Vec` of tuples containing `(timestamp, amount)`. + /// Defaults to the maximum available range if `None`. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + #[cfg_attr(feature = "tracing", tracing::instrument(skip(self), err, fields(symbol = %self.symbol)))] + pub async fn dividends(&self, range: Option) -> Result, YfError> { + let acts = self.actions(range).await?; + Ok(acts + .into_iter() + .filter_map(|a| match a { + Action::Dividend { ts, amount } => { + Some((datetime_to_i64(ts), money_to_f64(&amount))) + } + _ => None, + }) + .collect()) + } + + /// Fetches all stock splits for the given range. + /// + /// Returns a `Vec` of tuples containing `(timestamp, numerator, denominator)`. + /// Defaults to the maximum available range if `None`. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + #[cfg_attr(feature = "tracing", tracing::instrument(skip(self), err, fields(symbol = %self.symbol)))] + pub async fn splits(&self, range: Option) -> Result, YfError> { + let acts = self.actions(range).await?; + Ok(acts + .into_iter() + .filter_map(|a| match a { + Action::Split { + ts, + numerator, + denominator, + } => Some((datetime_to_i64(ts), numerator, denominator)), + _ => None, + }) + .collect()) + } + + /// Fetches the metadata associated with the ticker's historical data, such as timezone. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + #[cfg_attr(feature = "tracing", tracing::instrument(skip(self), err, fields(symbol = %self.symbol)))] + pub async fn get_history_metadata( + &self, + range: Option, + ) -> Result, crate::core::YfError> { + let mut hb = self + .history_builder() + .cache_mode(self.cache_mode) + .retry_policy(self.retry_override.clone()); + if let Some(r) = range { + hb = hb.range(r); + } + let resp = hb.fetch_full().await?; + Ok(resp.meta) + } + + /// Fetches the ISIN for the ticker by searching on markets.businessinsider.com. + /// + /// This mimics the approach used by the Python `yfinance` library. + /// It returns `None` for assets that don't have an ISIN, such as indices. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn isin(&self) -> Result, YfError> { + if self.symbol.contains('^') { + return Ok(None); + } + + isin::fetch_isin(&self.client, &self.symbol, self.retry_override.as_ref()).await + } + + /// Retrieves historical capital gain events for the ticker (typically for mutual funds). + /// + /// A time `range` can be optionally specified. Defaults to the maximum available range. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn capital_gains(&self, range: Option) -> Result, YfError> { + let acts = self.actions(range).await?; + Ok(acts + .into_iter() + .filter_map(|a| match a { + Action::CapitalGain { ts, gain } => { + Some((datetime_to_i64(ts), money_to_f64(&gain))) + } + _ => None, + }) + .collect()) + } + + /* ---------------- Options ---------------- */ + + /// Fetches the available expiration dates for the ticker's options as Unix timestamps. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn options(&self) -> Result, YfError> { + options::expiration_dates( + &self.client, + &self.symbol, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } + + /// Fetches the full option chain (calls and puts) for a specific expiration date. + /// + /// If `date` is `None`, fetches the chain for the nearest expiration date. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn option_chain(&self, date: Option) -> Result { + options::option_chain( + &self.client, + &self.symbol, + date, + self.cache_mode, + self.retry_override.as_ref(), + ) + .await + } + + /* ---------------- Holders convenience ---------------- */ + + fn holders_builder(&self) -> HoldersBuilder { + HoldersBuilder::new(&self.client, &self.symbol) + .cache_mode(self.cache_mode) + .retry_policy(self.retry_override.clone()) + } + + /// Fetches the major holders breakdown (e.g., % insiders, % institutions). + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn major_holders(&self) -> Result, YfError> { + self.holders_builder().major_holders().await + } + + /// Fetches a list of the top institutional holders. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn institutional_holders(&self) -> Result, YfError> { + self.holders_builder().institutional_holders().await + } + + /// Fetches a list of the top mutual fund holders. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn mutual_fund_holders(&self) -> Result, YfError> { + self.holders_builder().mutual_fund_holders().await + } + + /// Fetches a list of recent insider transactions. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn insider_transactions(&self) -> Result, YfError> { + self.holders_builder().insider_transactions().await + } + + /// Fetches a roster of company insiders and their holdings. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn insider_roster_holders(&self) -> Result, YfError> { + self.holders_builder().insider_roster_holders().await + } + + /// Fetches a summary of net insider purchase and sale activity. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn net_share_purchase_activity( + &self, + ) -> Result, YfError> { + self.holders_builder().net_share_purchase_activity().await + } + + /* ---------------- Analysis convenience ---------------- */ + + fn analysis_builder(&self) -> AnalysisBuilder { + AnalysisBuilder::new(&self.client, &self.symbol) + .cache_mode(self.cache_mode) + .retry_policy(self.retry_override.clone()) + } + + /// Fetches the analyst recommendation trend. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn recommendations(&self) -> Result, YfError> { + self.analysis_builder().recommendations().await + } + + /// Fetches a summary of the latest analyst recommendations. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn recommendations_summary(&self) -> Result { + self.analysis_builder().recommendations_summary().await + } + + /// Fetches the history of analyst upgrades and downgrades. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn upgrades_downgrades(&self) -> Result, YfError> { + self.analysis_builder().upgrades_downgrades().await + } + + /// Fetches the analyst price target. + /// + /// Provide `Some(currency)` to override the inferred reporting currency; pass `None` + /// to use the cached profile-based heuristic. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn analyst_price_target( + &self, + override_currency: Option, + ) -> Result { + self.analysis_builder() + .analyst_price_target(override_currency) + .await + } + + /// Fetches earnings trend data for the ticker. + /// + /// This includes earnings estimates, revenue estimates, EPS trends, and EPS revisions for various periods. + /// + /// Provide `Some(currency)` to override the inferred reporting currency; pass `None` + /// to use the cached profile-based heuristic. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn earnings_trend( + &self, + override_currency: Option, + ) -> Result, YfError> { + self.analysis_builder() + .earnings_trend(override_currency) + .await + } + + /* ---------------- ESG / Sustainability ---------------- */ + + fn esg_builder(&self) -> EsgBuilder { + EsgBuilder::new(&self.client, &self.symbol) + .cache_mode(self.cache_mode) + .retry_policy(self.retry_override.clone()) + } + + /// Fetches the ESG (Environmental, Social, Governance) scores for the ticker. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn sustainability(&self) -> Result { + self.esg_builder().fetch().await + } + /* ---------------- Fundamentals convenience ---------------- */ + + fn fundamentals_builder(&self) -> FundamentalsBuilder { + FundamentalsBuilder::new(&self.client, &self.symbol) + .cache_mode(self.cache_mode) + .retry_policy(self.retry_override.clone()) + } + + /// Fetches the annual income statement. + /// + /// Provide `Some(currency)` to override the inferred reporting currency; pass `None` + /// to use the cached profile-based heuristic. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn income_stmt( + &self, + override_currency: Option, + ) -> Result, YfError> { + self.fundamentals_builder() + .income_statement(false, override_currency) + .await + } + + /// Fetches the quarterly income statement. + /// + /// Provide `Some(currency)` to override the inferred reporting currency; pass `None` + /// to use the cached profile-based heuristic. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn quarterly_income_stmt( + &self, + override_currency: Option, + ) -> Result, YfError> { + self.fundamentals_builder() + .income_statement(true, override_currency) + .await + } + + /// Fetches the annual balance sheet. + /// + /// Provide `Some(currency)` to override the inferred reporting currency; pass `None` + /// to use the cached profile-based heuristic. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn balance_sheet( + &self, + override_currency: Option, + ) -> Result, YfError> { + self.fundamentals_builder() + .balance_sheet(false, override_currency) + .await + } + + /// Fetches the quarterly balance sheet. + /// + /// Provide `Some(currency)` to override the inferred reporting currency; pass `None` + /// to use the cached profile-based heuristic. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn quarterly_balance_sheet( + &self, + override_currency: Option, + ) -> Result, YfError> { + self.fundamentals_builder() + .balance_sheet(true, override_currency) + .await + } + + /// Fetches the annual cash flow statement. + /// + /// Provide `Some(currency)` to override the inferred reporting currency; pass `None` + /// to use the cached profile-based heuristic. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn cashflow( + &self, + override_currency: Option, + ) -> Result, YfError> { + self.fundamentals_builder() + .cashflow(false, override_currency) + .await + } + + /// Fetches the quarterly cash flow statement. + /// + /// Provide `Some(currency)` to override the inferred reporting currency; pass `None` + /// to use the cached profile-based heuristic. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn quarterly_cashflow( + &self, + override_currency: Option, + ) -> Result, YfError> { + self.fundamentals_builder() + .cashflow(true, override_currency) + .await + } + + /// Fetches earnings history and estimates. + /// + /// Provide `Some(currency)` to override the inferred reporting currency; pass `None` + /// to use the cached profile-based heuristic. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn earnings(&self, override_currency: Option) -> Result { + self.fundamentals_builder() + .earnings(override_currency) + .await + } + + /// Fetches corporate calendar events like earnings dates. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn calendar(&self) -> Result { + self.fundamentals_builder().calendar().await + } + + /// Fetches historical annual shares outstanding. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn shares(&self) -> Result, YfError> { + self.fundamentals_builder().shares(false).await + } + + /// Fetches historical quarterly shares outstanding. + /// + /// # Errors + /// + /// This method will return an error if the request fails or the response cannot be parsed. + pub async fn quarterly_shares(&self) -> Result, YfError> { + self.fundamentals_builder().shares(true).await + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/ticker/model.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/ticker/model.rs new file mode 100644 index 0000000..fef82fa --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/ticker/model.rs @@ -0,0 +1,5 @@ +// Re-export types from paft without using prelude +pub use paft::market::options::{OptionChain, OptionContract}; + +// Align provider `Info` with paft aggregates for consistency +pub use paft::aggregates::Info; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/ticker/options.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/ticker/options.rs new file mode 100644 index 0000000..39567f5 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/ticker/options.rs @@ -0,0 +1,302 @@ +use std::str::FromStr; + +use serde::Deserialize; +use url::Url; + +use crate::{ + YfClient, YfError, + core::{ + client::{CacheMode, RetryConfig}, + conversions::{f64_to_money_with_currency, i64_to_datetime}, + net, + }, +}; +use paft::money::Currency; + +use super::model::{OptionChain, OptionContract}; +use chrono::{NaiveDate, TimeZone, Utc}; + +/* ---------------- Public: expirations + chain ---------------- */ + +pub async fn expiration_dates( + client: &YfClient, + symbol: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result, YfError> { + let (body, _used_url) = + fetch_options_raw(client, symbol, None, cache_mode, retry_override).await?; + let env: OptEnvelope = serde_json::from_str(&body).map_err(YfError::Json)?; + + let first = env + .option_chain + .and_then(|oc| oc.result) + .and_then(|mut v| v.pop()) + .ok_or_else(|| YfError::MissingData("empty options result".into()))?; + + Ok(first.expiration_dates.unwrap_or_default()) +} + +pub async fn option_chain( + client: &YfClient, + symbol: &str, + date: Option, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result { + let (body, used_url) = + fetch_options_raw(client, symbol, date, cache_mode, retry_override).await?; + let env: OptEnvelope = serde_json::from_str(&body).map_err(YfError::Json)?; + + let first = env + .option_chain + .and_then(|oc| oc.result) + .and_then(|mut v| v.pop()) + .ok_or_else(|| YfError::MissingData("empty options result".into()))?; + + let currency_from_response = currency_from_result(&first); + + let Some(od) = first.options.and_then(|mut v| v.pop()) else { + return Ok(OptionChain { + calls: vec![], + puts: vec![], + }); + }; + + let expiration = od.expiration_date.unwrap_or_else(|| { + if let Some(q) = used_url.query() { + for kv in q.split('&') { + if let Some(v) = kv.strip_prefix("date=") + && let Ok(ts) = v.parse::() + { + return ts; + } + } + } + 0 + }); + + let currency = if let Some(currency) = currency_from_response { + currency + } else { + let quote = super::quote::fetch_quote(client, symbol, cache_mode, retry_override).await?; + quote + .price + .as_ref() + .map(|m| m.currency().clone()) + .or_else(|| quote.previous_close.as_ref().map(|m| m.currency().clone())) + .ok_or_else(|| { + YfError::MissingData("unable to determine currency from options or quote".into()) + })? + }; + + let map_side = |side: Option>, + currency: &Currency| + -> Vec { + side.unwrap_or_default() + .into_iter() + .filter_map(|c| { + let sym = c.contract_symbol.as_deref().unwrap_or(""); + let Ok(contract_symbol) = paft::domain::Symbol::new(sym) else { + return None; + }; + + let exp_ts = c.expiration.unwrap_or(expiration); + let exp_dt = i64_to_datetime(exp_ts); + let exp_date: NaiveDate = exp_dt.date_naive(); + + Some(OptionContract { + contract_symbol, + strike: f64_to_money_with_currency(c.strike.unwrap_or(0.0), currency.clone()), + price: c + .last_price + .map(|v| f64_to_money_with_currency(v, currency.clone())), + bid: c + .bid + .map(|v| f64_to_money_with_currency(v, currency.clone())), + ask: c + .ask + .map(|v| f64_to_money_with_currency(v, currency.clone())), + volume: c.volume, + open_interest: c.open_interest, + implied_volatility: c.implied_volatility, + in_the_money: c.in_the_money.unwrap_or(false), + expiration_date: exp_date, + expiration_at: Some(exp_dt), + last_trade_at: c + .last_trade_date + .and_then(|ts| Utc.timestamp_opt(ts, 0).single()), + greeks: None, + }) + }) + .collect() + }; + + Ok(OptionChain { + calls: map_side(od.calls, ¤cy), + puts: map_side(od.puts, ¤cy), + }) +} + +/* ---------------- Internal: raw fetch with auth fallback ---------------- */ + +async fn fetch_options_raw( + client: &YfClient, + symbol: &str, + date: Option, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result<(String, Url), YfError> { + let http = client.http().clone(); + let base = client.base_options_v7(); + + let mut url = base.join(symbol)?; + { + let mut qp = url.query_pairs_mut(); + if let Some(d) = date { + qp.append_pair("date", &d.to_string()); + } + } + + if cache_mode == CacheMode::Use + && let Some(body) = client.cache_get(&url).await + { + return Ok((body, url)); + } + + let req = http.get(url.clone()).header("accept", "application/json"); + let mut resp = client.send_with_retry(req, retry_override).await?; + + if resp.status().is_success() { + let fixture_key = date.map_or_else(|| symbol.to_string(), |d| format!("{symbol}_{d}")); + let body = net::get_text(resp, "options_v7", &fixture_key, "json").await?; + if cache_mode != CacheMode::Bypass { + client.cache_put(&url, &body, None).await; + } + return Ok((body, url)); + } + + let code = resp.status().as_u16(); + if code != 401 && code != 403 { + let url_s = url.to_string(); + return Err(match code { + 404 => YfError::NotFound { url: url_s }, + 429 => YfError::RateLimited { url: url_s }, + 500..=599 => YfError::ServerError { + status: code, + url: url_s, + }, + _ => YfError::Status { + status: code, + url: url_s, + }, + }); + } + + client.ensure_credentials().await?; + let crumb = client.crumb().await.ok_or_else(|| YfError::Status { + status: code, + url: url.to_string(), + })?; + + let mut url2 = base.join(symbol)?; + { + let mut qp = url2.query_pairs_mut(); + if let Some(d) = date { + qp.append_pair("date", &d.to_string()); + } + qp.append_pair("crumb", &crumb); + } + + let req2 = http.get(url2.clone()).header("accept", "application/json"); + resp = client.send_with_retry(req2, retry_override).await?; + + if !resp.status().is_success() { + let code = resp.status().as_u16(); + let url_s = url2.to_string(); + return Err(match code { + 404 => YfError::NotFound { url: url_s }, + 429 => YfError::RateLimited { url: url_s }, + 500..=599 => YfError::ServerError { + status: code, + url: url_s, + }, + _ => YfError::Status { + status: code, + url: url_s, + }, + }); + } + + let fixture_key = date.map_or_else(|| symbol.to_string(), |d| format!("{symbol}_{d}")); + let body = net::get_text(resp, "options_v7", &fixture_key, "json").await?; + if cache_mode != CacheMode::Bypass { + client.cache_put(&url2, &body, None).await; + } + Ok((body, url2)) +} + +/* ---------------- Minimal serde mapping for v7 options ---------------- */ + +#[derive(Deserialize)] +struct OptEnvelope { + #[serde(rename = "optionChain")] + option_chain: Option, +} + +#[derive(Deserialize)] +struct OptChainNode { + result: Option>, + #[allow(dead_code)] + error: Option, +} + +#[derive(Deserialize)] +struct OptResultNode { + #[serde(rename = "expirationDates")] + expiration_dates: Option>, + quote: Option, + options: Option>, +} + +#[derive(Deserialize)] +struct OptQuoteNode { + currency: Option, +} + +#[derive(Deserialize)] +struct OptByDateNode { + #[serde(rename = "expirationDate")] + expiration_date: Option, + calls: Option>, + puts: Option>, +} + +#[derive(Deserialize)] +struct OptContractNode { + #[serde(rename = "contractSymbol")] + contract_symbol: Option, + #[serde(rename = "expiration")] + expiration: Option, + #[serde(rename = "lastTradeDate")] + last_trade_date: Option, + strike: Option, + #[serde(rename = "lastPrice")] + last_price: Option, + bid: Option, + ask: Option, + volume: Option, + #[serde(rename = "openInterest")] + open_interest: Option, + #[serde(rename = "impliedVolatility")] + implied_volatility: Option, + #[serde(rename = "inTheMoney")] + in_the_money: Option, +} + +fn currency_from_result(node: &OptResultNode) -> Option { + node.quote + .as_ref() + .and_then(|q| q.currency.as_deref()) + .and_then(|code| Currency::from_str(code).ok()) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/ticker/quote.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/ticker/quote.rs new file mode 100644 index 0000000..8653fae --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/src/ticker/quote.rs @@ -0,0 +1,23 @@ +use crate::core::{ + YfClient, YfError, + client::{CacheMode, RetryConfig}, + models::Quote, + quotes, +}; + +pub async fn fetch_quote( + client: &YfClient, + symbol: &str, + cache_mode: CacheMode, + retry_override: Option<&RetryConfig>, +) -> Result { + let symbols = [symbol]; + let mut results = quotes::fetch_v7_quotes(client, &symbols, cache_mode, retry_override).await?; + + let result = results.pop().ok_or_else(|| { + YfError::MissingData(format!("no quote result found for symbol {symbol}")) + })?; + + // Use the same currency-aware conversion as the batch quotes API + Ok(result.into()) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis.rs new file mode 100644 index 0000000..bb8981e --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis.rs @@ -0,0 +1,19 @@ +// tests/analysis.rs +mod common; + +#[path = "analysis/live.rs"] +mod analysis_live; +#[path = "analysis/offline.rs"] +mod analysis_offline; +#[path = "analysis/retry_synthetic.rs"] +mod analysis_retry_synth; +#[path = "analysis/earnings_trend.rs"] +mod earnings_trend; +#[path = "analysis/price_target.rs"] +mod price_target; +#[path = "analysis/price_target_live.rs"] +mod price_target_live; +#[path = "analysis/sorted_upgrades.rs"] +mod sorted_upgrades; +#[path = "analysis/yahoo_error_passthrough.rs"] +mod yahoo_error_passthrough; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/earnings_trend.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/earnings_trend.rs new file mode 100644 index 0000000..49a75eb --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/earnings_trend.rs @@ -0,0 +1,48 @@ +use httpmock::{Method::GET, MockServer}; +use url::Url; +use yfinance_rs::{ApiPreference, Ticker, YfClient}; + +fn fixture(endpoint: &str, symbol: &str) -> String { + crate::common::fixture(endpoint, symbol, "json") +} + +#[tokio::test] +async fn offline_earnings_trend_uses_recorded_fixture() { + let sym = "AAPL"; + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "earningsTrend") + .query_param("crumb", "crumb"); + then.status(200) + .header("content-type", "application/json") + .body(fixture("analysis_api_earningsTrend", sym)); + }); + + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + ._api_preference(ApiPreference::ApiOnly) + ._preauth("cookie", "crumb") + .build() + .unwrap(); + + let t = Ticker::new(&client, sym); + let rows = t.earnings_trend(None).await.unwrap(); + + mock.assert(); + assert_eq!(rows.len(), 4, "record with YF_RECORD=1 first"); + + // Find any row with earnings estimate data + let current_year = rows + .iter() + .find(|r| r.earnings_estimate.avg.is_some()) + .expect("Should find a row with earnings estimate"); + assert!(current_year.earnings_estimate.avg.is_some()); + assert!(current_year.revenue_estimate.avg.is_some()); + assert!(current_year.eps_trend.current.is_some()); + assert!(!current_year.eps_revisions.historical.is_empty()); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/live.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/live.rs new file mode 100644 index 0000000..617e72a --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/live.rs @@ -0,0 +1,39 @@ +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_analysis_smoke_and_or_record() { + if !crate::common::live_or_record_enabled() { + return; + } + + let client = yfinance_rs::YfClient::builder().build().unwrap(); + + // Trend + { + let t = yfinance_rs::Ticker::new(&client, "AAPL"); + let _ = t.recommendations().await.unwrap(); + } + // Summary + { + let t = yfinance_rs::Ticker::new(&client, "MSFT"); + let _ = t.recommendations_summary().await.unwrap(); + } + // Upgrades/Downgrades + { + let t = yfinance_rs::Ticker::new(&client, "GOOGL"); + let _ = t.upgrades_downgrades().await.unwrap(); + } + + // Earnings Trend + { + let t = yfinance_rs::Ticker::new(&client, "AAPL"); + let _ = t.earnings_trend(None).await.unwrap(); + } + + // If not recording, at least assert the calls returned something sane. + if !crate::common::is_recording() { + let t = yfinance_rs::Ticker::new(&client, "AAPL"); + let rows = t.recommendations().await.unwrap(); + // Smoke check: when hitting live, expect at least one row + assert!(!rows.is_empty()); + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/offline.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/offline.rs new file mode 100644 index 0000000..b6e9e5c --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/offline.rs @@ -0,0 +1,108 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::{ApiPreference, Ticker, YfClient}; + +fn fixture(endpoint: &str, symbol: &str) -> String { + crate::common::fixture(endpoint, symbol, "json") +} + +#[tokio::test] +async fn offline_recommendations_trend_uses_recorded_fixture() { + let sym = "AAPL"; + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "recommendationTrend") + .query_param("crumb", "crumb"); + then.status(200) + .header("content-type", "application/json") + .body(fixture("analysis_api_recommendationTrend", sym)); + }); + + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + ._api_preference(ApiPreference::ApiOnly) + ._preauth("cookie", "crumb") + .build() + .unwrap(); + + let t = Ticker::new(&client, sym); + let rows = t.recommendations().await.unwrap(); + + mock.assert(); + assert!(!rows.is_empty(), "record with YF_RECORD=1 first"); +} + +#[tokio::test] +async fn offline_recommendations_summary_uses_recorded_fixture() { + let sym = "MSFT"; + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "recommendationTrend,financialData") + .query_param("crumb", "crumb"); + then.status(200) + .header("content-type", "application/json") + .body(fixture( + "analysis_api_recommendationTrend-financialData", + sym, + )); + }); + + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + ._api_preference(ApiPreference::ApiOnly) + ._preauth("cookie", "crumb") + .build() + .unwrap(); + + let t = Ticker::new(&client, sym); + let s = t.recommendations_summary().await.unwrap(); + + mock.assert(); + let total = s.strong_buy.unwrap_or(0) + + s.buy.unwrap_or(0) + + s.hold.unwrap_or(0) + + s.sell.unwrap_or(0) + + s.strong_sell.unwrap_or(0); + assert!(total > 0, "record with YF_RECORD=1 first"); +} + +#[tokio::test] +async fn offline_upgrades_downgrades_uses_recorded_fixture() { + let sym = "GOOGL"; + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "upgradeDowngradeHistory") + .query_param("crumb", "crumb"); + then.status(200) + .header("content-type", "application/json") + .body(fixture("analysis_api_upgradeDowngradeHistory", sym)); + }); + + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + ._api_preference(ApiPreference::ApiOnly) + ._preauth("cookie", "crumb") + .build() + .unwrap(); + + let t = Ticker::new(&client, sym); + let _rows = t.upgrades_downgrades().await.unwrap(); + + mock.assert(); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/price_target.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/price_target.rs new file mode 100644 index 0000000..4958dca --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/price_target.rs @@ -0,0 +1,168 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use paft::money::{Currency, IsoCurrency}; +use url::Url; +use yfinance_rs::core::conversions::*; +use yfinance_rs::{ApiPreference, Ticker, YfClient}; + +#[tokio::test] +async fn offline_price_target_happy() { + let server = MockServer::start(); + let sym = "AAPL"; + + let body = r#"{ + "quoteSummary": { + "result": [{ + "financialData": { + "targetMeanPrice": { "raw": 200.0 }, + "targetHighPrice": { "raw": 250.0 }, + "targetLowPrice": { "raw": 150.0 }, + "numberOfAnalystOpinions": { "raw": 31 } + } + }], + "error": null + } + }"#; + + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "financialData") + .query_param("crumb", "crumb"); + then.status(200) + .header("content-type", "application/json") + .body(body); + }); + + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + ._api_preference(ApiPreference::ApiOnly) + ._preauth("cookie", "crumb") + .build() + .unwrap(); + + let t = Ticker::new(&client, sym); + let pt = t.analyst_price_target(None).await.unwrap(); + + mock.assert(); + + assert_eq!( + pt.mean, + Some(f64_to_money_with_currency( + 200.0, + Currency::Iso(IsoCurrency::USD) + )) + ); + assert_eq!( + pt.high, + Some(f64_to_money_with_currency( + 250.0, + Currency::Iso(IsoCurrency::USD) + )) + ); + assert_eq!( + pt.low, + Some(f64_to_money_with_currency( + 150.0, + Currency::Iso(IsoCurrency::USD) + )) + ); + assert_eq!(pt.number_of_analysts, Some(31)); +} + +#[tokio::test] +async fn price_target_invalid_crumb_then_retry_succeeds() { + let server = MockServer::start(); + let sym = "MSFT"; + + let first = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "financialData") + .query_param("crumb", "stale"); + then.status(200) + .header("content-type", "application/json") + .body(r#"{"quoteSummary":{"result":null,"error":{"description":"Invalid Crumb"}}}"#); + }); + + let cookie = server.mock(|when, then| { + when.method(GET).path("/consent"); + then.status(200).header( + "set-cookie", + "A=B; Max-Age=315360000; Domain=.yahoo.com; Path=/; Secure; SameSite=None", + ); + }); + + let crumb = server.mock(|when, then| { + when.method(GET).path("/v1/test/getcrumb"); + then.status(200).body("fresh"); + }); + + let ok = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "financialData") + .query_param("crumb", "fresh"); + then.status(200) + .header("content-type", "application/json") + .body( + r#"{ + "quoteSummary": { + "result": [{ + "financialData": { + "targetMeanPrice": { "raw": 123.45 }, + "targetHighPrice": { "raw": 150.0 }, + "targetLowPrice": { "raw": 100.0 }, + "numberOfAnalystOpinions": { "raw": 20 } + } + }], + "error": null + } + }"#, + ); + }); + + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + .cookie_url(Url::parse(&format!("{}/consent", server.base_url())).unwrap()) + .crumb_url(Url::parse(&format!("{}/v1/test/getcrumb", server.base_url())).unwrap()) + ._api_preference(ApiPreference::ApiOnly) + ._preauth("cookie", "stale") + .build() + .unwrap(); + + let t = Ticker::new(&client, sym); + let pt = t.analyst_price_target(None).await.unwrap(); + + first.assert(); + cookie.assert(); + crumb.assert(); + ok.assert(); + + assert_eq!( + pt.mean, + Some(f64_to_money_with_currency( + 123.45, + Currency::Iso(IsoCurrency::USD) + )) + ); + assert_eq!( + pt.high, + Some(f64_to_money_with_currency( + 150.0, + Currency::Iso(IsoCurrency::USD) + )) + ); + assert_eq!( + pt.low, + Some(f64_to_money_with_currency( + 100.0, + Currency::Iso(IsoCurrency::USD) + )) + ); + assert_eq!(pt.number_of_analysts, Some(20)); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/price_target_live.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/price_target_live.rs new file mode 100644 index 0000000..c5fecea --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/price_target_live.rs @@ -0,0 +1,26 @@ +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_price_target_smoke() { + // Only run this when explicitly enabled + if std::env::var("YF_LIVE").ok().as_deref() != Some("1") { + return; + } + + // Default client hits real Yahoo endpoints. + let client = YfClient::builder().build().unwrap(); + + // Pick a very liquid name that usually has coverage. + let t = Ticker::new(&client, "AAPL"); + let pt = t.analyst_price_target(None).await.unwrap(); + + // Basic sanity: at least one of the fields should show up. + assert!( + pt.mean.is_some() + || pt.high.is_some() + || pt.low.is_some() + || pt.number_of_analysts.is_some(), + "expected at least one price target field to be present" + ); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/retry_synthetic.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/retry_synthetic.rs new file mode 100644 index 0000000..1a851b7 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/retry_synthetic.rs @@ -0,0 +1,74 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::{ApiPreference, Ticker, YfClient}; + +#[tokio::test] +async fn analysis_invalid_crumb_then_retry_succeeds() { + let server = MockServer::start(); + let sym = "AAPL"; + + let first = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "recommendationTrend,financialData") + .query_param("crumb", "stale"); + then.status(200) + .header("content-type", "application/json") + .body(r#"{"quoteSummary":{"result":null,"error":{"description":"Invalid Crumb"}}}"#); + }); + + let cookie = server.mock(|when, then| { + when.method(GET).path("/consent"); + then.status(200).header( + "set-cookie", + "A=B; Max-Age=315360000; Domain=.yahoo.com; Path=/; Secure; SameSite=None", + ); + }); + let crumb = server.mock(|when, then| { + when.method(GET).path("/v1/test/getcrumb"); + then.status(200).body("fresh"); + }); + + let ok = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "recommendationTrend,financialData") + .query_param("crumb", "fresh"); + then.status(200) + .header("content-type","application/json") + .body(r#"{ + "quoteSummary": { + "result": [{ + "recommendationTrend": { "trend": [] }, + "financialData": { "recommendationMean": { "raw": 2.5 }, "recommendationKey": "buy" } + }], + "error": null + } + }"#); + }); + + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + .cookie_url(Url::parse(&format!("{}/consent", server.base_url())).unwrap()) + .crumb_url(Url::parse(&format!("{}/v1/test/getcrumb", server.base_url())).unwrap()) + ._api_preference(ApiPreference::ApiOnly) + ._preauth("cookie", "stale") + .build() + .unwrap(); + + let t = Ticker::new(&client, sym); + let s = t.recommendations_summary().await.unwrap(); + + first.assert(); + cookie.assert(); + crumb.assert(); + ok.assert(); + + assert_eq!(s.mean, Some(2.5)); + // The mean_rating_text field might not be populated in the test data + // Let's just check that the summary was created successfully + assert!(s.mean.is_some()); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/sorted_upgrades.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/sorted_upgrades.rs new file mode 100644 index 0000000..65a1b2e --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/sorted_upgrades.rs @@ -0,0 +1,53 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::{ApiPreference, Ticker, YfClient}; + +#[tokio::test] +async fn upgrades_downgrades_are_sorted_by_ts() { + let server = MockServer::start(); + let sym = "GOOGL"; + + let body = r#"{ + "quoteSummary": { + "result": [{ + "upgradeDowngradeHistory": { + "history": [ + { "epochGradeDate": 2000, "firm": "B", "fromGrade": "Hold", "toGrade": "Buy", "action": "up" }, + { "epochGradeDate": 1000, "firm": "A", "fromGrade": "Sell", "toGrade": "Hold", "action": "up" } + ] + } + }], + "error": null + } + }"#; + + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "upgradeDowngradeHistory") + .query_param("crumb", "crumb"); + then.status(200) + .header("content-type", "application/json") + .body(body); + }); + + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + ._api_preference(ApiPreference::ApiOnly) + ._preauth("cookie", "crumb") + .build() + .unwrap(); + + let t = Ticker::new(&client, sym); + let rows = t.upgrades_downgrades().await.unwrap(); + + mock.assert(); + + assert_eq!(rows.len(), 2); + assert!(rows[0].ts <= rows[1].ts, "rows should be sorted ascending"); + assert_eq!(rows[0].firm.as_deref(), Some("A")); + assert_eq!(rows[1].firm.as_deref(), Some("B")); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/yahoo_error_passthrough.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/yahoo_error_passthrough.rs new file mode 100644 index 0000000..a614a7c --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/analysis/yahoo_error_passthrough.rs @@ -0,0 +1,44 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::{ApiPreference, Ticker, YfClient, YfError}; + +#[tokio::test] +async fn analysis_other_yahoo_errors_are_surfaced_without_retry() { + let server = MockServer::start(); + let sym = "AAPL"; + + // Simulate a non-crumb Yahoo error response + let api_err = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "recommendationTrend") + .query_param("crumb", "crumb"); + then.status(200) + .header("content-type", "application/json") + .body(r#"{"quoteSummary":{"result":null,"error":{"description":"Something broke"}}}"#); + }); + + // Build a client that already has credentials so the call proceeds immediately. + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + ._api_preference(ApiPreference::ApiOnly) + ._preauth("cookie", "crumb") + .build() + .unwrap(); + + let t = Ticker::new(&client, sym); + let err = t.recommendations().await.unwrap_err(); + + api_err.assert(); + + match err { + YfError::Api(s) => assert!( + s.to_ascii_lowercase().contains("yahoo error:") && s.contains("Something broke"), + "expected yahoo error to be surfaced; got {s}" + ), + other => panic!("expected Api error, got {other:?}"), + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/auth.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/auth.rs new file mode 100644 index 0000000..e71c3e0 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/auth.rs @@ -0,0 +1,6 @@ +mod common; + +#[path = "auth/crumb_retry_synthetic.rs"] +mod auth_and_retry_synth; +#[path = "auth/negative_cookie_crumb.rs"] +mod negative_cookie_crumb; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/auth/crumb_retry_synthetic.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/auth/crumb_retry_synthetic.rs new file mode 100644 index 0000000..1773674 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/auth/crumb_retry_synthetic.rs @@ -0,0 +1,108 @@ +use crate::common; +use httpmock::Method::GET; +use paft::fundamentals::profile::Profile; +use url::Url; +use yfinance_rs::YfClient; + +#[tokio::test] +async fn api_fetches_cookie_and_crumb_first() { + let server = common::setup_server(); + // 1) cookie + crumb + let (cookie_mock, crumb_mock) = common::mock_cookie_crumb(&server); + + // 2) API (uses the crumb we just fetched) + let sym = "AAPL"; + let api = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "assetProfile,quoteType,fundProfile") + .query_param("crumb", "crumb-value"); + then.status(200) + .header("content-type", "application/json") + .body(common::fixture( + "profile_api_assetProfile-quoteType-fundProfile", + sym, + "json", + )); + }); + + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + .cookie_url(Url::parse(&format!("{}/consent", server.base_url())).unwrap()) + .crumb_url(Url::parse(&format!("{}/v1/test/getcrumb", server.base_url())).unwrap()) + .build() + .unwrap(); + + let p = yfinance_rs::profile::load_profile(&client, sym) + .await + .unwrap(); + api.assert(); + cookie_mock.assert(); + crumb_mock.assert(); + + match p { + Profile::Company(c) => assert_eq!(c.name, "Apple Inc."), + Profile::Fund(_) => panic!("expected Company"), + } +} + +#[tokio::test] +async fn api_retries_on_invalid_crumb_then_succeeds() { + let server = common::setup_server(); + + // start with a stale crumb so first call fails + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + .cookie_url(Url::parse(&format!("{}/consent", server.base_url())).unwrap()) + .crumb_url(Url::parse(&format!("{}/v1/test/getcrumb", server.base_url())).unwrap()) + ._preauth("cookie", "stale-crumb") + .build() + .unwrap(); + + // first API call returns "Invalid Crumb" + let sym = "AAPL"; + let invalid = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "assetProfile,quoteType,fundProfile") + .query_param("crumb", "stale-crumb"); + then.status(200) + .header("content-type", "application/json") + .body(r#"{"quoteSummary":{"result":null,"error":{"description":"Invalid Crumb"}}}"#); + }); + + // crumb refresh happens here + let (cookie_mock, crumb_mock) = common::mock_cookie_crumb(&server); + + // second API call with fresh crumb + let ok = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "assetProfile,quoteType,fundProfile") + .query_param("crumb", "crumb-value"); + then.status(200) + .header("content-type", "application/json") + .body(common::fixture( + "profile_api_assetProfile-quoteType-fundProfile", + sym, + "json", + )); + }); + + let p = yfinance_rs::profile::load_profile(&client, sym) + .await + .unwrap(); + invalid.assert(); + cookie_mock.assert(); + crumb_mock.assert(); + ok.assert(); + + match p { + Profile::Company(c) => assert_eq!(c.name, "Apple Inc."), + Profile::Fund(_) => panic!("expected Company"), + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/auth/negative_cookie_crumb.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/auth/negative_cookie_crumb.rs new file mode 100644 index 0000000..e61a680 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/auth/negative_cookie_crumb.rs @@ -0,0 +1,94 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::{YfClient, YfError}; + +#[tokio::test] +async fn missing_set_cookie_header_is_an_error() { + let server = MockServer::start(); + let sym = "AAPL"; + + // Cookie endpoint returns 200 but no Set-Cookie header. + let cookie = server.mock(|when, then| { + when.method(GET).path("/consent"); + then.status(200); // no set-cookie + }); + let crumb = server.mock(|when, then| { + // won't be reached, but good to have + when.method(GET).path("/v1/test/getcrumb"); + then.status(200).body("crumb-value"); + }); + + // Any API body (won't be reached if ensure_credentials fails early) + let api = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")); + then.status(200) + .header("content-type", "application/json") + .body(r#"{"quoteSummary":{"result":[],"error":null}}"#); + }); + + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + .cookie_url(Url::parse(&format!("{}/consent", server.base_url())).unwrap()) + .crumb_url(Url::parse(&format!("{}/v1/test/getcrumb", server.base_url())).unwrap()) + .build() + .unwrap(); + + let err = yfinance_rs::profile::load_profile(&client, sym) + .await + .unwrap_err(); + cookie.assert(); + + match err { + YfError::Auth(s) => assert!(s.contains("No cookie received"), "unexpected error: {s}"), + other => panic!("expected Auth error, got {other:?}"), + } + assert_eq!( + crumb.calls(), + 0, + "crumb endpoint should not be called if cookie fails" + ); + assert_eq!( + api.calls(), + 0, + "API should not be called if credentials fail" + ); +} + +#[tokio::test] +async fn invalid_crumb_body_is_an_error() { + let server = MockServer::start(); + let sym = "AAPL"; + + // Proper Set-Cookie + let _cookie = server.mock(|when, then| { + when.method(GET).path("/consent"); + then.status(200).header("set-cookie", "A=B; Path=/"); + }); + // Crumb endpoint returns "{}" which should be rejected + let _crumb = server.mock(|when, then| { + when.method(GET).path("/v1/test/getcrumb"); + then.status(200).body("{}"); + }); + + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + .cookie_url(Url::parse(&format!("{}/consent", server.base_url())).unwrap()) + .crumb_url(Url::parse(&format!("{}/v1/test/getcrumb", server.base_url())).unwrap()) + .build() + .unwrap(); + + let err = yfinance_rs::profile::load_profile(&client, sym) + .await + .unwrap_err(); + + match err { + YfError::Auth(s) => assert!(s.contains("Received invalid crumb"), "unexpected: {s}"), + other => panic!("expected Auth error, got {other:?}"), + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/common.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/common.rs new file mode 100644 index 0000000..b16d88a --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/common.rs @@ -0,0 +1,164 @@ +#![allow(dead_code)] + +use httpmock::{Method::GET, Mock, MockServer}; +use std::{ + fs, + path::{Path, PathBuf}, +}; + +#[must_use] +pub fn setup_server() -> MockServer { + MockServer::start() +} + +fn fixture_dir() -> PathBuf { + std::env::var("YF_FIXDIR").map_or_else( + |_| Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures"), + PathBuf::from, + ) +} + +#[must_use] +/// Returns fixture file contents for a given endpoint/symbol/extension. +/// +/// # Panics +/// +/// Panics if the fixture file cannot be read. +pub fn fixture(endpoint: &str, symbol: &str, ext: &str) -> String { + let path = fixture_path(endpoint, symbol, ext); + fs::read_to_string(&path) + .unwrap_or_else(|e| panic!("failed to read fixture {}: {}", path.display(), e)) +} + +#[must_use] +pub fn fixture_path(endpoint: &str, symbol: &str, ext: &str) -> PathBuf { + fixture_dir().join(format!("{endpoint}_{symbol}.{ext}")) +} + +#[must_use] +pub fn fixture_exists(endpoint: &str, symbol: &str, ext: &str) -> bool { + fixture_path(endpoint, symbol, ext).exists() +} + +#[must_use] +pub fn mock_cookie_crumb(server: &'_ MockServer) -> (Mock<'_>, Mock<'_>) { + let cookie_mock = server.mock(|when, then| { + when.method(GET).path("/consent"); + then.status(200).header( + "set-cookie", + "A=B; Max-Age=315360000; Domain=.yahoo.com; Path=/; Secure; SameSite=None", + ); + }); + let crumb_mock = server.mock(|when, then| { + when.method(GET).path("/v1/test/getcrumb"); + then.status(200).body("crumb-value"); + }); + (cookie_mock, crumb_mock) +} + +#[must_use] +pub fn mock_history_chart<'a>(server: &'a MockServer, symbol: &'a str) -> Mock<'a> { + server.mock(|when, then| { + when.method(GET).path(format!("/v8/finance/chart/{symbol}")); + then.status(200) + .header("content-type", "application/json") + .body(fixture("history_chart", symbol, "json")); + }) +} + +#[must_use] +pub fn mock_profile_api<'a>(server: &'a MockServer, symbol: &'a str, crumb: &'a str) -> Mock<'a> { + server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{symbol}")) + .query_param("modules", "assetProfile,quoteType,fundProfile") + .query_param("crumb", crumb); + then.status(200) + .header("content-type", "application/json") + .body(fixture("profile_api", symbol, "json")); + }) +} + +#[must_use] +pub fn mock_profile_scrape<'a>(server: &'a MockServer, symbol: &'a str) -> Mock<'a> { + server.mock(|when, then| { + when.method(GET) + .path(format!("/quote/{symbol}")) + .query_param("p", symbol); + then.status(200) + .header("content-type", "text/html") + .body(fixture("profile_html", symbol, "html")); + }) +} + +#[must_use] +pub fn mock_quote_v7<'a>(server: &'a MockServer, symbol: &'a str) -> Mock<'a> { + server.mock(|when, then| { + when.method(GET) + .path("/v7/finance/quote") + .query_param("symbols", symbol); + then.status(200) + .header("content-type", "application/json") + .body(fixture("quote_v7", symbol, "json")); + }) +} + +#[must_use] +pub fn mock_quote_v7_multi<'a>(server: &'a MockServer, symbols_csv: &'a str) -> Mock<'a> { + server.mock(|when, then| { + when.method(GET) + .path("/v7/finance/quote") + .query_param("symbols", symbols_csv); + then.status(200) + .header("content-type", "application/json") + .body(fixture("quote_v7", "MULTI", "json")); + }) +} + +#[must_use] +pub fn mock_options_v7<'a>(server: &'a MockServer, symbol: &'a str) -> Mock<'a> { + server.mock(|when, then| { + when.method(GET) + .path(format!("/v7/finance/options/{symbol}")) + .is_true(|req| { + let group = req.query_params(); + for (k, _) in group { + if k == "date" { + return false; + } + } + true + }); + then.status(200) + .header("content-type", "application/json") + .body(fixture("options_v7", symbol, "json")); + }) +} + +#[must_use] +pub fn mock_options_v7_for_date<'a>( + server: &'a MockServer, + symbol: &'a str, + date: i64, +) -> Mock<'a> { + server.mock(|when, then| { + when.method(GET) + .path(format!("/v7/finance/options/{symbol}")) + .query_param("date", date.to_string()); + then.status(200) + .header("content-type", "application/json") + .body(fixture("options_v7", &format!("{symbol}_{date}"), "json")); + }) +} + +#[must_use] +pub fn live_or_record_enabled() -> bool { + let live = std::env::var("YF_LIVE").ok().as_deref() == Some("1"); + let record = std::env::var("YF_RECORD").ok().as_deref() == Some("1"); + live || record +} + +#[must_use] +pub fn is_recording() -> bool { + std::env::var("YF_RECORD").ok().as_deref() == Some("1") +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/currency.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/currency.rs new file mode 100644 index 0000000..42ec041 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/currency.rs @@ -0,0 +1,8 @@ +mod common; + +#[path = "currency/inference_live.rs"] +mod inference_live; +#[path = "currency/inference_offline.rs"] +mod inference_offline; +#[path = "currency/verification.rs"] +mod verification; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/currency/inference_live.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/currency/inference_live.rs new file mode 100644 index 0000000..9942ace --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/currency/inference_live.rs @@ -0,0 +1,95 @@ +use paft::money::{Currency, IsoCurrency}; +use yfinance_rs::core::{Interval, Range}; +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_reporting_currency_inference() -> Result<(), Box> { + if !crate::common::live_or_record_enabled() { + return Ok(()); + } + + let client = YfClient::builder().build()?; + + let cases = vec![ + ("AAPL", Currency::Iso(IsoCurrency::USD)), + ("SAP", Currency::Iso(IsoCurrency::EUR)), + ("7203.T", Currency::Iso(IsoCurrency::JPY)), + ]; + + for (symbol, expected) in cases { + let ticker = Ticker::new(&client, symbol); + + let rows = ticker.income_stmt(None).await?; + let inferred_currency = rows + .first() + .and_then(|row| row.total_revenue.as_ref().map(|m| m.currency().clone())); + assert_eq!( + inferred_currency, + Some(expected.clone()), + "{symbol} expected {expected:?}" + ); + + let cached_before_override = ticker.income_stmt(None).await?; + let cached_before_currency = cached_before_override + .first() + .and_then(|row| row.total_revenue.as_ref().map(|m| m.currency().clone())); + assert_eq!( + cached_before_currency, + Some(expected.clone()), + "{symbol} cache should retain inferred currency before override" + ); + + let override_rows = ticker + .income_stmt(Some(Currency::Iso(IsoCurrency::USD))) + .await?; + let override_currency = override_rows + .first() + .and_then(|row| row.total_revenue.as_ref().map(|m| m.currency().clone())); + assert_eq!( + override_currency, + Some(Currency::Iso(IsoCurrency::USD)), + "{symbol} override should force USD" + ); + + let second_pass = ticker.income_stmt(None).await?; + let cached_currency = second_pass + .first() + .and_then(|row| row.total_revenue.as_ref().map(|m| m.currency().clone())); + assert_eq!( + cached_currency, + Some(Currency::Iso(IsoCurrency::USD)), + "{symbol} cache should reflect last override" + ); + } + + Ok(()) +} + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_gs2c_dual_listing_currency() -> Result<(), Box> { + if !crate::common::live_or_record_enabled() { + return Ok(()); + } + + let client = YfClient::builder().build()?; + let ticker = Ticker::new(&client, "GS2C.DE"); + + let fast = ticker.fast_info().await?; + assert_eq!(fast.currency.map(|c| c.to_string()).as_deref(), Some("EUR")); + + let history = ticker + .history(Some(Range::D5), Some(Interval::D1), false) + .await?; + let history_currency = history.first().map(|bar| bar.close.currency().clone()); + assert_eq!(history_currency, Some(Currency::Iso(IsoCurrency::EUR))); + + let fundamentals = ticker.income_stmt(None).await?; + let fundamentals_currency = fundamentals + .first() + .and_then(|row| row.total_revenue.as_ref().map(|m| m.currency().clone())); + assert_eq!(fundamentals_currency, Some(Currency::Iso(IsoCurrency::USD))); + + Ok(()) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/currency/inference_offline.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/currency/inference_offline.rs new file mode 100644 index 0000000..0370a51 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/currency/inference_offline.rs @@ -0,0 +1,214 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use paft::money::{Currency, IsoCurrency}; +use url::Url; +use yfinance_rs::core::{Interval, Range}; +use yfinance_rs::{ApiPreference, Ticker, YfClient}; + +fn fixture(endpoint: &str, symbol: &str) -> String { + crate::common::fixture(endpoint, symbol, "json") +} + +fn has_fixture(endpoint: &str, symbol: &str) -> bool { + crate::common::fixture_exists(endpoint, symbol, "json") +} + +#[tokio::test] +async fn offline_currency_inference_uses_profile_country() { + let symbol = "TSCO.L"; + + assert!( + has_fixture("profile_api_assetProfile-quoteType-fundProfile", symbol), + "missing fixture profile_api_assetProfile-quoteType-fundProfile_{symbol}.json" + ); + assert!( + has_fixture("timeseries_income_statement_annual", symbol), + "missing fixture timeseries_income_statement_annual_{symbol}.json" + ); + + let server = MockServer::start(); + + let profile_mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{symbol}")) + .query_param("modules", "assetProfile,quoteType,fundProfile") + .query_param("crumb", "crumb"); + then.status(200) + .header("content-type", "application/json") + .body(fixture( + "profile_api_assetProfile-quoteType-fundProfile", + symbol, + )); + }); + + let fundamentals_mock = server.mock(|when, then| { + when.method(GET) + .path(format!( + "/ws/fundamentals-timeseries/v1/finance/timeseries/{symbol}" + )) + .query_param_exists("type") + .query_param("crumb", "crumb"); + then.status(200) + .header("content-type", "application/json") + .body(fixture("timeseries_income_statement_annual", symbol)); + }); + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + .base_timeseries( + Url::parse(&format!( + "{}/ws/fundamentals-timeseries/v1/finance/timeseries/", + server.base_url() + )) + .unwrap(), + ) + ._preauth("cookie", "crumb") + .build() + .unwrap(); + + let ticker = Ticker::new(&client, symbol); + + let rows = ticker.income_stmt(None).await.unwrap(); + let inferred_currency = rows + .first() + .and_then(|row| row.total_revenue.as_ref().map(|m| m.currency().clone())); + assert_eq!(inferred_currency, Some(Currency::Iso(IsoCurrency::GBP))); + + let cached_before_override = ticker.income_stmt(None).await.unwrap(); + let cached_before_currency = cached_before_override + .first() + .and_then(|row| row.total_revenue.as_ref().map(|m| m.currency().clone())); + assert_eq!( + cached_before_currency, + Some(Currency::Iso(IsoCurrency::GBP)) + ); + + let rows_override = ticker + .income_stmt(Some(Currency::Iso(IsoCurrency::USD))) + .await + .unwrap(); + let override_currency = rows_override + .first() + .and_then(|row| row.total_revenue.as_ref().map(|m| m.currency().clone())); + assert_eq!(override_currency, Some(Currency::Iso(IsoCurrency::USD))); + + let rows_cached = ticker.income_stmt(None).await.unwrap(); + let cached_currency = rows_cached + .first() + .and_then(|row| row.total_revenue.as_ref().map(|m| m.currency().clone())); + assert_eq!(cached_currency, Some(Currency::Iso(IsoCurrency::USD))); + + assert_eq!(profile_mock.calls(), 1, "profile should be fetched once"); + assert_eq!( + fundamentals_mock.calls(), + 4, + "fundamentals should be fetched four times" + ); +} + +#[tokio::test] +async fn offline_gs2c_dual_listing_currency() { + let symbol = "GS2C.DE"; + + assert!( + has_fixture("quote_v7", symbol), + "missing fixture quote_v7_{symbol}.json" + ); + assert!( + has_fixture("profile_api_assetProfile-quoteType-fundProfile", symbol), + "missing fixture profile_api_assetProfile-quoteType-fundProfile_{symbol}.json" + ); + assert!( + has_fixture("timeseries_income_statement_annual", symbol), + "missing fixture timeseries_income_statement_annual_{symbol}.json" + ); + assert!( + has_fixture("history_chart", symbol), + "missing fixture history_chart_{symbol}.json" + ); + + let server = MockServer::start(); + + let quote_mock = server.mock(|when, then| { + when.method(GET) + .path("/v7/finance/quote") + .query_param("symbols", symbol); + then.status(200) + .header("content-type", "application/json") + .body(fixture("quote_v7", symbol)); + }); + + let profile_mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{symbol}")) + .query_param("modules", "assetProfile,quoteType,fundProfile") + .query_param("crumb", "crumb"); + then.status(200) + .header("content-type", "application/json") + .body(fixture( + "profile_api_assetProfile-quoteType-fundProfile", + symbol, + )); + }); + + let fundamentals_mock = server.mock(|when, then| { + when.method(GET) + .path(format!( + "/ws/fundamentals-timeseries/v1/finance/timeseries/{symbol}" + )) + .query_param_exists("type") + .query_param("crumb", "crumb"); + then.status(200) + .header("content-type", "application/json") + .body(fixture("timeseries_income_statement_annual", symbol)); + }); + + let chart_mock = server.mock(|when, then| { + when.method(GET).path(format!("/v8/finance/chart/{symbol}")); + then.status(200) + .header("content-type", "application/json") + .body(fixture("history_chart", symbol)); + }); + + let client = YfClient::builder() + .base_quote_v7(Url::parse(&format!("{}/v7/finance/quote", server.base_url())).unwrap()) + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + .base_timeseries( + Url::parse(&format!( + "{}/ws/fundamentals-timeseries/v1/finance/timeseries/", + server.base_url() + )) + .unwrap(), + ) + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + ._api_preference(ApiPreference::ApiOnly) + ._preauth("cookie", "crumb") + .build() + .unwrap(); + + let ticker = Ticker::new(&client, symbol); + + let fast = ticker.fast_info().await.unwrap(); + assert_eq!(fast.currency.map(|c| c.to_string()).as_deref(), Some("EUR")); + + let fundamentals = ticker.income_stmt(None).await.unwrap(); + let fundamentals_currency = fundamentals + .first() + .and_then(|row| row.total_revenue.as_ref().map(|m| m.currency().clone())); + assert_eq!(fundamentals_currency, Some(Currency::Iso(IsoCurrency::USD))); + + let history = ticker + .history(Some(Range::D5), Some(Interval::D1), false) + .await + .unwrap(); + let history_currency = history.first().map(|bar| bar.close.currency().clone()); + assert_eq!(history_currency, Some(Currency::Iso(IsoCurrency::EUR))); + + assert_eq!(quote_mock.calls(), 1); + assert_eq!(profile_mock.calls(), 1); + assert_eq!(fundamentals_mock.calls(), 1); + assert_eq!(chart_mock.calls(), 1); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/currency/verification.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/currency/verification.rs new file mode 100644 index 0000000..ef7337b --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/currency/verification.rs @@ -0,0 +1,311 @@ +use yfinance_rs::core::{Interval, Range, conversions::money_to_f64}; +use yfinance_rs::quote::quotes; +use yfinance_rs::ticker::Ticker; +use yfinance_rs::{YfClient, YfError}; + +async fn check_fast_info(ticker: &Ticker, expected_currency: &str) { + println!(" 📈 Quote/Fast Info:"); + match ticker.fast_info().await { + Ok(fi) => { + println!(" Symbol: {}", fi.symbol); + println!(" Last Price: {:?}", fi.last.as_ref().map(money_to_f64)); + println!( + " Currency: {:?}", + fi.currency.as_ref().map(std::string::ToString::to_string) + ); + println!( + " Exchange: {:?}", + fi.exchange.as_ref().map(std::string::ToString::to_string) + ); + let currency_correct = fi + .currency + .as_ref() + .map(std::string::ToString::to_string) + .as_deref() + == Some(expected_currency); + println!( + " {} Currency {}: {} (expected {})", + if currency_correct { "✅" } else { "❌" }, + if currency_correct { + "CORRECT" + } else { + "INCORRECT" + }, + fi.currency + .as_ref() + .map(std::string::ToString::to_string) + .as_deref() + .unwrap_or("None"), + expected_currency + ); + } + Err(e) => println!(" ❌ Error: {e}"), + } +} + +async fn check_comprehensive_info(ticker: &Ticker, expected_currency: &str) { + println!(" 📋 Comprehensive Info:"); + match ticker.info().await { + Ok(info) => { + println!(" Symbol: {}", info.symbol); + println!( + " Last Price: {:?}", + info.last + .as_ref() + .map(yfinance_rs::core::conversions::money_to_f64) + ); + println!( + " Currency: {:?}", + info.currency.as_ref().map(std::string::ToString::to_string) + ); + println!( + " Exchange: {:?}", + info.exchange.as_ref().map(std::string::ToString::to_string) + ); + let currency_correct = info + .currency + .as_ref() + .map(std::string::ToString::to_string) + .as_deref() + == Some(expected_currency); + println!( + " {} Currency {}: {} (expected {})", + if currency_correct { "✅" } else { "❌" }, + if currency_correct { + "CORRECT" + } else { + "INCORRECT" + }, + info.currency + .as_ref() + .map(std::string::ToString::to_string) + .as_deref() + .unwrap_or("None"), + expected_currency + ); + } + Err(e) => println!(" ❌ Error: {e}"), + } +} + +async fn check_history(ticker: &Ticker, expected_currency: &str) { + println!(" 📊 Historical Data:"); + match ticker + .history(Some(Range::D5), Some(Interval::D1), false) + .await + { + Ok(history) => { + if let Some(last_candle) = history.last() { + println!(" Last Close: {:?}", last_candle.close); + println!(" Currency: \"{}\"", last_candle.close.currency()); + let currency_correct = + last_candle.close.currency().to_string() == expected_currency; + println!( + " {} Currency {}: {} (expected {})", + if currency_correct { "✅" } else { "❌" }, + if currency_correct { + "CORRECT" + } else { + "INCORRECT" + }, + last_candle.close.currency(), + expected_currency + ); + } else { + println!(" ❌ No historical data available"); + } + } + Err(e) => println!(" ❌ Error: {e}"), + } +} + +async fn check_fundamentals(ticker: &Ticker) { + println!(" 💰 Fundamentals:"); + match ticker.income_stmt(None).await { + Ok(income_stmt) => { + if let Some(latest) = income_stmt.first() { + println!(" Total Revenue: {:?}", latest.total_revenue); + println!(" Net Income: {:?}", latest.net_income); + println!( + " Revenue Currency: {} (Note: Financial statements typically in USD)", + latest + .total_revenue + .as_ref() + .map_or_else(|| "None".to_string(), |m| m.currency().to_string()) + ); + println!(" ✅ Revenue Currency CORRECT: USD (financial statements standard)"); + } else { + println!(" ❌ No income statement data available"); + } + } + Err(e) => println!(" ❌ Error: {e}"), + } +} + +async fn check_analysis(ticker: &Ticker) { + println!(" 📊 Analysis:"); + match ticker.analyst_price_target(None).await { + Ok(target) => { + println!(" Mean Target: {:?}", target.mean); + println!(" High Target: {:?}", target.high); + println!(" Low Target: {:?}", target.low); + println!( + " Target Currency: {} (Note: Analyst targets typically in USD)", + target + .mean + .as_ref() + .map_or_else(|| "None".to_string(), |m| m.currency().to_string()) + ); + println!(" ✅ Target Currency CORRECT: USD (analyst targets standard)"); + } + Err(e) => println!(" ❌ Error: {e}"), + } +} + +async fn run_symbol_check( + client: &YfClient, + symbol: &str, + expected_currency: &str, + description: &str, +) -> Result<(), YfError> { + println!("\n📊 Testing: {symbol} ({description})"); + println!("Expected Currency: {expected_currency}"); + println!("{}", "-".repeat(50)); + let ticker = Ticker::new(client, symbol); + + check_fast_info(&ticker, expected_currency).await; + check_comprehensive_info(&ticker, expected_currency).await; + check_history(&ticker, expected_currency).await; + check_fundamentals(&ticker).await; + check_analysis(&ticker).await; + + Ok(()) +} + +async fn run_batch_quotes(client: &YfClient) -> Result<(), YfError> { + println!("\n📊 Batch Quotes Currency Test:"); + println!("{}", "-".repeat(50)); + let symbols = vec!["AAPL", "TSCO.L", "7203.T"]; + match quotes(client, symbols.clone()).await { + Ok(batch_quotes) => { + for (i, quote) in batch_quotes.iter().enumerate() { + let symbol = &symbols[i]; + let expected = match *symbol { + "TSCO.L" => "GBP", + "7203.T" => "JPY", + _ => "USD", + }; + let currency = quote.price.as_ref().map(|m| m.currency().to_string()); + let currency_correct = currency.as_deref() == Some(expected); + println!( + " {}: Price={:?}, Currency={:?}", + symbol, + quote.price.as_ref().map(money_to_f64), + currency + ); + println!( + " {} Currency {}: {} (expected {})", + if currency_correct { "✅" } else { "❌" }, + if currency_correct { + "CORRECT" + } else { + "INCORRECT" + }, + currency.as_deref().unwrap_or("None"), + expected + ); + } + } + Err(e) => println!(" ❌ Error: {e}"), + } + Ok(()) +} + +#[tokio::test] +async fn test_currency_verification() -> Result<(), YfError> { + println!("🔍 Currency Verification Test"); + println!("============================"); + let client = YfClient::builder().build().unwrap(); + let cases = vec![ + ("AAPL", "USD", "US Stock (NASDAQ)"), + ("TSCO.L", "GBP", "UK Stock (LSE)"), + ("7203.T", "JPY", "Japanese Stock (TSE)"), + ("ASML.AS", "EUR", "Dutch Stock (Euronext)"), + ("TSM", "USD", "Taiwanese Stock (NYSE)"), + ]; + for (symbol, expected, desc) in cases { + run_symbol_check(&client, symbol, expected, desc).await?; + } + run_batch_quotes(&client).await?; + println!("\n✅ Currency verification test completed!"); + Ok(()) +} + +#[tokio::test] +async fn test_currency_precision() -> Result<(), YfError> { + println!("\n🔍 Currency Precision Test"); + println!("=========================="); + + let client = YfClient::builder().build().unwrap(); + let ticker = Ticker::new(&client, "AAPL"); + + // Test historical data precision + match ticker + .history(Some(Range::D5), Some(Interval::D1), false) + .await + { + Ok(history) => { + if let Some(last_candle) = history.last() { + println!("📊 Historical Data Precision:"); + println!(" Open: {:?}", last_candle.open); + println!(" High: {:?}", last_candle.high); + println!(" Low: {:?}", last_candle.low); + println!(" Close: {:?}", last_candle.close); + + // Check if amounts are clean (no precision artifacts) + let amounts = [ + money_to_f64(&last_candle.open), + money_to_f64(&last_candle.high), + money_to_f64(&last_candle.low), + money_to_f64(&last_candle.close), + ]; + + let has_precision_issues = amounts.iter().any(|&amount| { + let formatted = format!("{amount:.4}"); + let parsed_back = formatted.parse::().unwrap_or(0.0); + (amount - parsed_back).abs() > 1e-10 + }); + + if has_precision_issues { + println!(" ❌ Precision issues detected!"); + } else { + println!(" ✅ Clean precision - no artifacts"); + } + } + } + Err(e) => println!("❌ Error: {e}"), + } + + // Test quote precision + match ticker.quote().await { + Ok(quote) => { + println!("\n📈 Quote Data Precision:"); + if let Some(price) = "e.price { + println!(" Price: {price:?}"); + let amount = money_to_f64(price); + let formatted = format!("{amount:.4}"); + let parsed_back = formatted.parse::().unwrap_or(0.0); + let has_precision_issues = (amount - parsed_back).abs() > 1e-10; + + if has_precision_issues { + println!(" ❌ Precision issues detected!"); + } else { + println!(" ✅ Clean precision - no artifacts"); + } + } + } + Err(e) => println!("❌ Error: {e}"), + } + + Ok(()) +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/download.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/download.rs new file mode 100644 index 0000000..26a614b --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/download.rs @@ -0,0 +1,12 @@ +mod common; + +#[path = "download/back_adjust.rs"] +mod download_back_adjust; +#[path = "download/keepna_rounding.rs"] +mod download_keepna_rounding; +#[path = "download/live.rs"] +mod download_live; +#[path = "download/offline.rs"] +mod download_offline; +#[path = "download/repair.rs"] +mod download_repair; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/download/back_adjust.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/download/back_adjust.rs new file mode 100644 index 0000000..e1586fe --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/download/back_adjust.rs @@ -0,0 +1,61 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::YfClient; +use yfinance_rs::core::conversions::*; + +#[tokio::test] +async fn download_back_adjust_sets_close_to_raw() { + // One day has adjclose=50 while raw close=100 (e.g., dividend/split adjustment) + let body = r#"{ + "chart": { + "result": [{ + "timestamp":[1000,2000], + "indicators":{ + "quote":[{ "open":[100.0,100.0], "high":[105.0,105.0], "low":[95.0,95.0], "close":[100.0,100.0], "volume":[1000,1000] }], + "adjclose":[{ "adjclose":[50.0,100.0] }] + } + }], + "error": null + } + }"#; + + let server = MockServer::start(); + let sym = "TEST"; + + let mock = server.mock(|when, then| { + when.method(GET).path(format!("/v8/finance/chart/{sym}")); + then.status(200) + .header("content-type", "application/json") + .body(body); + }); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let res = yfinance_rs::DownloadBuilder::new(&client) + .symbols([sym]) + .back_adjust(true) + .run() + .await + .unwrap(); + + mock.assert(); + + let s = &res + .entries + .iter() + .find(|e| e.instrument.symbol_str() == sym) + .expect("symbol data") + .history + .candles; + // first bar got 50% adjustment factor; OHLC adjusted => open≈50, high≈52.5, low≈47.5 + assert!((money_to_f64(&s[0].open) - 50.0).abs() < 1e-9); + // back_adjust keeps raw Close + assert!((money_to_f64(&s[0].close) - 100.0).abs() < 1e-9); + // second bar unchanged + assert!((money_to_f64(&s[1].open) - 100.0).abs() < 1e-9); + assert!((money_to_f64(&s[1].close) - 100.0).abs() < 1e-9); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/download/keepna_rounding.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/download/keepna_rounding.rs new file mode 100644 index 0000000..bd62a6f --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/download/keepna_rounding.rs @@ -0,0 +1,79 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use rust_decimal::prelude::ToPrimitive; +use url::Url; +use yfinance_rs::YfClient; +use yfinance_rs::core::conversions::*; + +#[tokio::test] +async fn download_keepna_and_rounding() { + // Well-formed JSON: adjclose belongs inside indicators, and braces are balanced. + let body = r#"{ + "chart": { + "result": [{ + "timestamp": [10, 20, 30], + "indicators": { + "quote": [{ + "open": [100.001, null, 99.994], + "high": [101.009, null, 100.006], + "low": [ 99.001, null, 98.994], + "close": [100.499, null, 99.996], + "volume":[ 1000, 2000, 3000] + }], + "adjclose": [{ + "adjclose": [100.499, null, 99.996] + }] + } + }], + "error": null + } + }"#; + + let server = MockServer::start(); + let sym = "AAPL"; + + let mock = server.mock(|when, then| { + when.method(GET).path(format!("/v8/finance/chart/{sym}")); + then.status(200) + .header("content-type", "application/json") + .body(body); + }); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let res = yfinance_rs::DownloadBuilder::new(&client) + .symbols([sym]) + .keepna(true) + .rounding(true) + .run() + .await + .unwrap(); + + mock.assert(); + + let v = &res + .entries + .iter() + .find(|e| e.instrument.symbol_str() == sym) + .unwrap() + .history + .candles; + assert_eq!(v.len(), 3, "kept NA row"); + // row 1 rounded to 2dp + assert!((money_to_f64(&v[0].open) - 100.00).abs() < 1e-9); + assert!((money_to_f64(&v[0].high) - 101.01).abs() < 1e-9); + assert!((money_to_f64(&v[0].low) - 99.00).abs() < 1e-9); + assert!((money_to_f64(&v[0].close) - 100.50).abs() < 1e-9); + // NA row should have NaN OHLC preserved (or default values if Money doesn't support NaN) + // With Money type, NaN values might be converted to 0.0 or default values + // Let's just check that the row exists and has some values + assert!(v[1].open.amount().to_f64().unwrap_or(0.0) == 0.0 || money_to_f64(&v[1].open).is_nan()); + assert!(v[1].high.amount().to_f64().unwrap_or(0.0) == 0.0 || money_to_f64(&v[1].high).is_nan()); + assert!(v[1].low.amount().to_f64().unwrap_or(0.0) == 0.0 || money_to_f64(&v[1].low).is_nan()); + assert!( + v[1].close.amount().to_f64().unwrap_or(0.0) == 0.0 || money_to_f64(&v[1].close).is_nan() + ); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/download/live.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/download/live.rs new file mode 100644 index 0000000..efb53e2 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/download/live.rs @@ -0,0 +1,48 @@ +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_download_smoke() { + if !crate::common::live_or_record_enabled() { + return; + } + + let client = yfinance_rs::YfClient::builder().build().unwrap(); + let res = yfinance_rs::DownloadBuilder::new(&client) + .symbols(["AAPL", "MSFT"]) + .run() + .await + .unwrap(); + + if !crate::common::is_recording() { + let aapl = res + .entries + .iter() + .find(|e| e.instrument.symbol_str() == "AAPL") + .unwrap(); + let msft = res + .entries + .iter() + .find(|e| e.instrument.symbol_str() == "MSFT") + .unwrap(); + assert!(!aapl.history.candles.is_empty()); + assert!(!msft.history.candles.is_empty()); + } +} + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_download_for_record() { + // Only writes fixtures when YF_RECORD=1 + if !crate::common::is_recording() { + return; + } + + let client = yfinance_rs::YfClient::builder().build().unwrap(); + + // This will hit Yahoo live and record: + // tests/fixtures/history_chart_AAPL.json + // tests/fixtures/history_chart_MSFT.json + let _ = yfinance_rs::DownloadBuilder::new(&client) + .symbols(["AAPL", "MSFT"]) + .run() + .await; +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/download/offline.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/download/offline.rs new file mode 100644 index 0000000..1e815c4 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/download/offline.rs @@ -0,0 +1,360 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; + +use crate::common; +use yfinance_rs::core::conversions::*; +use yfinance_rs::core::{Interval, Range}; +use yfinance_rs::{DownloadBuilder, YfClient}; + +fn has_more_than_two_decimals(x: f64) -> bool { + if !x.is_finite() { + return false; + } + let cents = (x * 100.0).round(); + (x - cents / 100.0).abs() > 1e-12 +} + +#[tokio::test] +async fn download_multi_symbols_happy_path() { + let server = common::setup_server(); + + let m_aapl = server.mock(|when, then| { + when.method(GET) + .path("/v8/finance/chart/AAPL") + .query_param("range", "6mo") + .query_param("interval", "1d") + .query_param("includePrePost", "false") + .query_param("events", "div|split|capitalGains"); + then.status(200) + .header("content-type", "application/json") + .body(common::fixture("history_chart", "AAPL", "json")); + }); + + let m_msft = server.mock(|when, then| { + when.method(GET) + .path("/v8/finance/chart/MSFT") + .query_param("range", "6mo") + .query_param("interval", "1d") + .query_param("includePrePost", "false") + .query_param("events", "div|split|capitalGains"); + then.status(200) + .header("content-type", "application/json") + .body(common::fixture("history_chart", "MSFT", "json")); + }); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let res = DownloadBuilder::new(&client) + .symbols(["AAPL", "MSFT"]) + .range(Range::M6) + .interval(Interval::D1) + .auto_adjust(true) + .prepost(false) + .actions(true) + .run() + .await + .unwrap(); + + m_aapl.assert(); + m_msft.assert(); + + let keys: Vec<_> = res + .entries + .iter() + .map(|e| e.instrument.symbol_str().to_string()) + .collect(); + assert!(keys.iter().any(|s| s == "AAPL")); + assert!(keys.iter().any(|s| s == "MSFT")); +} + +#[tokio::test] +async fn download_between_params_applied_to_all_symbols() { + use chrono::{TimeZone, Utc}; + let server = httpmock::MockServer::start(); + + let start = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(); + let end = Utc.with_ymd_and_hms(2024, 1, 10, 0, 0, 0).unwrap(); + + let q1 = server.mock(|when, then| { + when.method(GET) + .path("/v8/finance/chart/AAPL") + .query_param("period1", start.timestamp().to_string()) + .query_param("period2", end.timestamp().to_string()) + .query_param("interval", "1d") + .query_param("includePrePost", "false") + .query_param("events", "div|split|capitalGains"); + then.status(200) + .header("content-type", "application/json") + .body(common::fixture("history_chart", "AAPL", "json")); + }); + + let q2 = server.mock(|when, then| { + when.method(GET) + .path("/v8/finance/chart/MSFT") + .query_param("period1", start.timestamp().to_string()) + .query_param("period2", end.timestamp().to_string()) + .query_param("interval", "1d") + .query_param("includePrePost", "false") + .query_param("events", "div|split|capitalGains"); + then.status(200) + .header("content-type", "application/json") + .body(common::fixture("history_chart", "MSFT", "json")); + }); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let res = DownloadBuilder::new(&client) + .symbols(["AAPL", "MSFT"]) + .between(start, end) + .interval(Interval::D1) + .auto_adjust(true) + .prepost(false) + .actions(true) + .run() + .await + .unwrap(); + + q1.assert(); + q2.assert(); + + assert_eq!(res.entries.len(), 2); + let aapl = res + .entries + .iter() + .find(|e| e.instrument.symbol_str() == "AAPL") + .unwrap(); + let msft = res + .entries + .iter() + .find(|e| e.instrument.symbol_str() == "MSFT") + .unwrap(); + assert!(!aapl.history.candles.is_empty()); + assert!(!msft.history.candles.is_empty()); +} + +/* ---------- Parity knob checks using cached live fixtures ---------- */ + +#[tokio::test] +async fn download_back_adjust_offline() { + // Run adjusted and back-adjusted on different mock servers so each mock sees 1 hit. + let server1 = common::setup_server(); + let server2 = common::setup_server(); + + let m1_aapl = server1.mock(|when, then| { + when.method(GET) + .path("/v8/finance/chart/AAPL") + .query_param("range", "6mo") + .query_param("interval", "1d") + .query_param("includePrePost", "false") + .query_param("events", "div|split|capitalGains"); + then.status(200) + .header("content-type", "application/json") + .body(common::fixture("history_chart", "AAPL", "json")); + }); + + let m2_aapl = server2.mock(|when, then| { + when.method(GET) + .path("/v8/finance/chart/AAPL") + .query_param("range", "6mo") + .query_param("interval", "1d") + .query_param("includePrePost", "false") + .query_param("events", "div|split|capitalGains"); + then.status(200) + .header("content-type", "application/json") + .body(common::fixture("history_chart", "AAPL", "json")); + }); + + let client1 = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server1.base_url())).unwrap()) + .build() + .unwrap(); + + let client2 = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server2.base_url())).unwrap()) + .build() + .unwrap(); + + let adj = DownloadBuilder::new(&client1) + .symbols(["AAPL"]) + .auto_adjust(true) + .back_adjust(false) + .run() + .await + .unwrap(); + + let back = DownloadBuilder::new(&client2) + .symbols(["AAPL"]) + .auto_adjust(false) // ignored internally when back_adjust(true) + .back_adjust(true) + .run() + .await + .unwrap(); + + m1_aapl.assert(); // exactly 1 + m2_aapl.assert(); // exactly 1 + + let a = &adj + .entries + .iter() + .find(|e| e.instrument.symbol_str() == "AAPL") + .unwrap() + .history + .candles; + let b = &back + .entries + .iter() + .find(|e| e.instrument.symbol_str() == "AAPL") + .unwrap() + .history + .candles; + + assert_eq!(a.len(), b.len(), "same number of bars"); + for (ca, cb) in a.iter().zip(b.iter()) { + assert!((money_to_f64(&ca.open) - money_to_f64(&cb.open)).abs() < 1e-9); + assert!((money_to_f64(&ca.high) - money_to_f64(&cb.high)).abs() < 1e-9); + assert!((money_to_f64(&ca.low) - money_to_f64(&cb.low)).abs() < 1e-9); + // close may differ due to back_adjust + } + assert!(!a.is_empty(), "expected some data"); +} + +#[tokio::test] +async fn download_repair_is_noop_on_clean_data_offline() { + // Run base and repair=true on different mock servers so each mock sees 1 hit. + let server1 = common::setup_server(); + let server2 = common::setup_server(); + + let m1_aapl = server1.mock(|when, then| { + when.method(GET) + .path("/v8/finance/chart/AAPL") + .query_param("range", "6mo") + .query_param("interval", "1d") + .query_param("includePrePost", "false") + .query_param("events", "div|split|capitalGains"); + then.status(200) + .header("content-type", "application/json") + .body(common::fixture("history_chart", "AAPL", "json")); + }); + + let m2_aapl = server2.mock(|when, then| { + when.method(GET) + .path("/v8/finance/chart/AAPL") + .query_param("range", "6mo") + .query_param("interval", "1d") + .query_param("includePrePost", "false") + .query_param("events", "div|split|capitalGains"); + then.status(200) + .header("content-type", "application/json") + .body(common::fixture("history_chart", "AAPL", "json")); + }); + + let client1 = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server1.base_url())).unwrap()) + .build() + .unwrap(); + + let client2 = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server2.base_url())).unwrap()) + .build() + .unwrap(); + + let base_run = DownloadBuilder::new(&client1) + .symbols(["AAPL"]) + .run() + .await + .unwrap(); + + let repair_run = DownloadBuilder::new(&client2) + .symbols(["AAPL"]) + .repair(true) + .run() + .await + .unwrap(); + + m1_aapl.assert(); // exactly 1 + m2_aapl.assert(); // exactly 1 + + let a = &base_run + .entries + .iter() + .find(|e| e.instrument.symbol_str() == "AAPL") + .unwrap() + .history + .candles; + let b = &repair_run + .entries + .iter() + .find(|e| e.instrument.symbol_str() == "AAPL") + .unwrap() + .history + .candles; + + assert_eq!(a.len(), b.len()); + for (ca, cb) in a.iter().zip(b.iter()) { + assert!((money_to_f64(&ca.open) - money_to_f64(&cb.open)).abs() < 1e-12); + assert!((money_to_f64(&ca.high) - money_to_f64(&cb.high)).abs() < 1e-12); + assert!((money_to_f64(&ca.low) - money_to_f64(&cb.low)).abs() < 1e-12); + assert!((money_to_f64(&ca.close) - money_to_f64(&cb.close)).abs() < 1e-12); + } +} + +#[tokio::test] +async fn rounding_two_decimals() { + use yfinance_rs::core::conversions::money_to_f64; + + let server = MockServer::start(); + + let m_aapl = server.mock(|when, then| { + when.method(GET) + .path("/v8/finance/chart/AAPL") + .query_param_exists("interval") + .query_param_exists("includePrePost") + .query_param_exists("range"); + then.status(200) + .header("content-type", "application/json") + .body(crate::common::fixture("history_chart", "AAPL", "json")); + }); + + let m_msft = server.mock(|when, then| { + when.method(GET) + .path("/v8/finance/chart/MSFT") + .query_param_exists("interval") + .query_param_exists("includePrePost") + .query_param_exists("range"); + then.status(200) + .header("content-type", "application/json") + .body(crate::common::fixture("history_chart", "MSFT", "json")); + }); + + let client = yfinance_rs::YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let res = yfinance_rs::DownloadBuilder::new(&client) + .symbols(["AAPL", "MSFT"]) + .rounding(true) + .keepna(true) + .run() + .await + .unwrap(); + + m_aapl.assert(); + m_msft.assert(); + + for entry in &res.entries { + for c in &entry.history.candles { + assert!(!has_more_than_two_decimals(money_to_f64(&c.open))); + assert!(!has_more_than_two_decimals(money_to_f64(&c.high))); + assert!(!has_more_than_two_decimals(money_to_f64(&c.low))); + assert!(!has_more_than_two_decimals(money_to_f64(&c.close))); + } + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/download/repair.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/download/repair.rs new file mode 100644 index 0000000..c147868 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/download/repair.rs @@ -0,0 +1,68 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::YfClient; +use yfinance_rs::core::conversions::*; + +#[tokio::test] +async fn download_repair_simple_100x_fix() { + // Well-formed JSON: adjclose inside indicators; braces balanced. + // Middle row is 100x too high -> should be scaled down when repair=true. + let body = r#"{ + "chart": { + "result": [{ + "timestamp": [1, 2, 3], + "indicators": { + "quote": [{ + "open": [ 10.0, 1000.0, 10.5], + "high": [ 11.0, 1100.0, 11.0], + "low": [ 9.0, 900.0, 10.0], + "close": [ 10.5, 1050.0, 10.8], + "volume":[ 100, 100, 100] + }], + "adjclose": [{ + "adjclose": [10.5, 1050.0, 10.8] + }] + } + }], + "error": null + } + }"#; + + let server = MockServer::start(); + let sym = "FIX"; + + let mock = server.mock(|when, then| { + when.method(GET).path(format!("/v8/finance/chart/{sym}")); + then.status(200) + .header("content-type", "application/json") + .body(body); + }); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let res = yfinance_rs::DownloadBuilder::new(&client) + .symbols([sym]) + .repair(true) + .run() + .await + .unwrap(); + + mock.assert(); + + let v = &res + .entries + .iter() + .find(|e| e.instrument.symbol_str() == sym) + .unwrap() + .history + .candles; + // middle row scaled ~0.01 + assert!((money_to_f64(&v[1].close) - 10.5).abs() < 1e-9); + assert!((money_to_f64(&v[1].open) - 10.0).abs() < 1e-9); + assert!((money_to_f64(&v[1].high) - 11.0).abs() < 1e-9); + assert!((money_to_f64(&v[1].low) - 9.0).abs() < 1e-9); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/esg.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/esg.rs new file mode 100644 index 0000000..1cb1368 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/esg.rs @@ -0,0 +1,10 @@ +mod common; + +#[path = "esg/live.rs"] +mod esg_live; +#[path = "esg/live_involvement.rs"] +mod esg_live_involvement; +#[path = "esg/offline.rs"] +mod esg_offline; +#[path = "esg/offline_involvement.rs"] +mod esg_offline_involvement; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/esg/live.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/esg/live.rs new file mode 100644 index 0000000..f7d85a4 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/esg/live.rs @@ -0,0 +1,28 @@ +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_esg_smoke_and_or_record() { + if !crate::common::live_or_record_enabled() { + return; + } + + let client = YfClient::builder().build().unwrap(); + // Use a ticker known to have ESG data. + let ticker = Ticker::new(&client, "MSFT"); + + // This will record `tests/fixtures/esg_api_MSFT.json` when YF_RECORD=1 + let esg = ticker.sustainability().await.unwrap(); + + if !crate::common::is_recording() { + // Basic sanity checks when running in live-only mode. + // ESG data can sometimes be unavailable, so we check that at least one score is present. + let has_any = esg.scores.as_ref().is_some_and(|s| { + s.environmental.is_some() || s.social.is_some() || s.governance.is_some() + }); + assert!( + has_any, + "Expected at least one ESG score to be present for MSFT" + ); + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/esg/live_involvement.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/esg/live_involvement.rs new file mode 100644 index 0000000..3748219 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/esg/live_involvement.rs @@ -0,0 +1,34 @@ +use std::collections::HashSet; +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_esg_involvement_smoke_and_or_record() { + if !crate::common::live_or_record_enabled() { + return; + } + + let client = YfClient::builder().build().unwrap(); + let ticker = Ticker::new(&client, "MSFT"); + + let summary = ticker.sustainability().await.unwrap(); + + if !crate::common::is_recording() { + // In live mode assert that involvement categories are unique and stable type-wise + let cats: HashSet = summary + .involvement + .iter() + .map(|i| i.category.clone()) + .collect(); + assert_eq!( + cats.len(), + summary.involvement.len(), + "involvement categories should be unique" + ); + // If provider returns flags, we expect either empty or a small set; do a loose upper bound + assert!( + summary.involvement.len() <= 16, + "unexpectedly high number of involvement categories" + ); + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/esg/offline.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/esg/offline.rs new file mode 100644 index 0000000..3b55a86 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/esg/offline.rs @@ -0,0 +1,46 @@ +use httpmock::{Method::GET, MockServer}; +use url::Url; +use yfinance_rs::{Ticker, YfClient}; + +fn fixture(endpoint: &str, symbol: &str) -> String { + crate::common::fixture(endpoint, symbol, "json") +} + +#[tokio::test] +async fn offline_esg_uses_recorded_fixture() { + let sym = "MSFT"; + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "esgScores") + .query_param("crumb", "crumb"); + then.status(200) + .header("content-type", "application/json") + .body(fixture("esg_api_esgScores", sym)); + }); + + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + ._preauth("cookie", "crumb") + .build() + .unwrap(); + + let ticker = Ticker::new(&client, sym); + let esg = ticker.sustainability().await.unwrap(); + + mock.assert(); + + // Ensure at least one ESG component exists in the summary + let has_any = esg + .scores + .as_ref() + .is_some_and(|s| s.environmental.is_some() || s.social.is_some() || s.governance.is_some()); + assert!( + has_any, + "At least one ESG component score should be present. Did you run `just test-record esg`?" + ); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/esg/offline_involvement.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/esg/offline_involvement.rs new file mode 100644 index 0000000..7289d61 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/esg/offline_involvement.rs @@ -0,0 +1,97 @@ +use std::collections::HashSet; + +use httpmock::{Method::GET, MockServer}; +use serde_json::Value; +use url::Url; +use yfinance_rs::{Ticker, YfClient}; + +fn fixture(endpoint: &str, symbol: &str) -> String { + crate::common::fixture(endpoint, symbol, "json") +} + +fn expected_involvement_from_fixture(sym: &str) -> HashSet { + let body = fixture("esg_api_esgScores", sym); + let v: Value = serde_json::from_str(&body).expect("valid JSON"); + let scores = &v["quoteSummary"]["result"][0]["esgScores"]; + + // Map possible JSON keys to our category strings + let candidates: &[(&[&str], &str)] = &[ + (&["adult"], "adult"), + (&["alcoholic"], "alcoholic"), + (&["animalTesting", "animal_testing"], "animal_testing"), + (&["catholic"], "catholic"), + ( + &["controversialWeapons", "controversial_weapons"], + "controversial_weapons", + ), + (&["smallArms", "small_arms"], "small_arms"), + (&["furLeather", "fur_leather"], "fur_leather"), + (&["gambling"], "gambling"), + (&["gmo"], "gmo"), + ( + &["militaryContract", "military_contract"], + "military_contract", + ), + (&["nuclear"], "nuclear"), + (&["palmOil", "palm_oil"], "palm_oil"), + (&["pesticides"], "pesticides"), + (&["thermalCoal", "thermal_coal"], "thermal_coal"), + (&["tobacco"], "tobacco"), + ]; + + let mut out = HashSet::new(); + for (keys, category) in candidates { + let mut is_true = false; + for key in *keys { + if scores.get(*key).and_then(Value::as_bool).unwrap_or(false) { + is_true = true; + break; + } + } + if is_true { + out.insert((*category).to_string()); + } + } + out +} + +#[tokio::test] +async fn offline_esg_involvement_matches_fixture() { + let sym = "MSFT"; + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "esgScores") + .query_param("crumb", "crumb"); + then.status(200) + .header("content-type", "application/json") + .body(fixture("esg_api_esgScores", sym)); + }); + + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + ._preauth("cookie", "crumb") + .build() + .unwrap(); + + let ticker = Ticker::new(&client, sym); + let summary = ticker.sustainability().await.unwrap(); + + mock.assert(); + + let got: HashSet = summary + .involvement + .iter() + .map(|i| i.category.clone()) + .collect(); + let expected = expected_involvement_from_fixture(sym); + + assert_eq!( + got, expected, + "involvement categories should match fixture booleans" + ); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/analysis_api_earningsTrend_AAPL.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/analysis_api_earningsTrend_AAPL.json new file mode 100644 index 0000000..5003199 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/analysis_api_earningsTrend_AAPL.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"earningsTrend":{"trend":[{"maxAge":1,"period":"0q","endDate":"2025-09-30","growth":{"raw":0.0798,"fmt":"7.98%"},"earningsEstimate":{"avg":{"raw":1.76993,"fmt":"1.77"},"low":{"raw":1.59,"fmt":"1.59"},"high":{"raw":1.83,"fmt":"1.83"},"yearAgoEps":{"raw":0.97,"fmt":"0.97"},"numberOfAnalysts":{"raw":32,"fmt":"32","longFmt":"32"},"growth":{"raw":0.8247,"fmt":"82.47%"},"earningsCurrency":"USD"},"revenueEstimate":{"avg":{"raw":102250480070,"fmt":"102.25B","longFmt":"102,250,480,070"},"low":{"raw":97854000000,"fmt":"97.85B","longFmt":"97,854,000,000"},"high":{"raw":107790700000,"fmt":"107.79B","longFmt":"107,790,700,000"},"numberOfAnalysts":{"raw":31,"fmt":"31","longFmt":"31"},"yearAgoRevenue":{"raw":94930000000,"fmt":"94.93B","longFmt":"94,930,000,000"},"growth":{"raw":0.0771,"fmt":"7.71%"},"revenueCurrency":"USD"},"epsTrend":{"current":{"raw":1.76993,"fmt":"1.77"},"7daysAgo":{"raw":1.76491,"fmt":"1.76"},"30daysAgo":{"raw":1.7605,"fmt":"1.76"},"60daysAgo":{"raw":1.75957,"fmt":"1.76"},"90daysAgo":{"raw":1.65557,"fmt":"1.66"},"epsTrendCurrency":"USD"},"epsRevisions":{"upLast7days":{"raw":6,"fmt":"6","longFmt":"6"},"upLast30days":{"raw":8,"fmt":"8","longFmt":"8"},"downLast30days":{"raw":1,"fmt":"1","longFmt":"1"},"downLast7Days":{"raw":1,"fmt":"1","longFmt":"1"},"downLast90days":{},"epsRevisionsCurrency":"USD"}},{"maxAge":1,"period":"+1q","endDate":"2025-12-31","growth":{"raw":0.043,"fmt":"4.30%"},"earningsEstimate":{"avg":{"raw":2.51899,"fmt":"2.52"},"low":{"raw":2.28,"fmt":"2.28"},"high":{"raw":2.75,"fmt":"2.75"},"yearAgoEps":{"raw":2.4,"fmt":"2.4"},"numberOfAnalysts":{"raw":26,"fmt":"26","longFmt":"26"},"growth":{"raw":0.0496,"fmt":"4.96%"},"earningsCurrency":"USD"},"revenueEstimate":{"avg":{"raw":131696361930,"fmt":"131.7B","longFmt":"131,696,361,930"},"low":{"raw":115811000000,"fmt":"115.81B","longFmt":"115,811,000,000"},"high":{"raw":143560100000,"fmt":"143.56B","longFmt":"143,560,100,000"},"numberOfAnalysts":{"raw":26,"fmt":"26","longFmt":"26"},"yearAgoRevenue":{"raw":124300000000,"fmt":"124.3B","longFmt":"124,300,000,000"},"growth":{"raw":0.059499998,"fmt":"5.95%"},"revenueCurrency":"USD"},"epsTrend":{"current":{"raw":2.51899,"fmt":"2.52"},"7daysAgo":{"raw":2.50955,"fmt":"2.51"},"30daysAgo":{"raw":2.49325,"fmt":"2.49"},"60daysAgo":{"raw":2.47492,"fmt":"2.47"},"90daysAgo":{"raw":2.42337,"fmt":"2.42"},"epsTrendCurrency":"USD"},"epsRevisions":{"upLast7days":{"raw":7,"fmt":"7","longFmt":"7"},"upLast30days":{"raw":8,"fmt":"8","longFmt":"8"},"downLast30days":{"raw":0,"fmt":null,"longFmt":"0"},"downLast7Days":{"raw":0,"fmt":null,"longFmt":"0"},"downLast90days":{},"epsRevisionsCurrency":"USD"}},{"maxAge":1,"period":"0y","endDate":"2025-09-30","growth":{"raw":0.0949,"fmt":"9.49%"},"earningsEstimate":{"avg":{"raw":7.38069,"fmt":"7.38"},"low":{"raw":7.09,"fmt":"7.09"},"high":{"raw":7.48,"fmt":"7.48"},"yearAgoEps":{"raw":6.08,"fmt":"6.08"},"numberOfAnalysts":{"raw":43,"fmt":"43","longFmt":"43"},"growth":{"raw":0.2139,"fmt":"21.39%"},"earningsCurrency":"USD"},"revenueEstimate":{"avg":{"raw":415655411720,"fmt":"415.66B","longFmt":"415,655,411,720"},"low":{"raw":404529000000,"fmt":"404.53B","longFmt":"404,529,000,000"},"high":{"raw":431783000000,"fmt":"431.78B","longFmt":"431,783,000,000"},"numberOfAnalysts":{"raw":43,"fmt":"43","longFmt":"43"},"yearAgoRevenue":{"raw":391035000000,"fmt":"391.04B","longFmt":"391,035,000,000"},"growth":{"raw":0.063,"fmt":"6.30%"},"revenueCurrency":"USD"},"epsTrend":{"current":{"raw":7.38069,"fmt":"7.38"},"7daysAgo":{"raw":7.37725,"fmt":"7.38"},"30daysAgo":{"raw":7.36913,"fmt":"7.37"},"60daysAgo":{"raw":7.38146,"fmt":"7.38"},"90daysAgo":{"raw":7.16656,"fmt":"7.17"},"epsTrendCurrency":"USD"},"epsRevisions":{"upLast7days":{"raw":6,"fmt":"6","longFmt":"6"},"upLast30days":{"raw":9,"fmt":"9","longFmt":"9"},"downLast30days":{"raw":3,"fmt":"3","longFmt":"3"},"downLast7Days":{"raw":2,"fmt":"2","longFmt":"2"},"downLast90days":{},"epsRevisionsCurrency":"USD"}},{"maxAge":1,"period":"+1y","endDate":"2026-09-30","growth":{"raw":0.0883,"fmt":"8.83%"},"earningsEstimate":{"avg":{"raw":8.05414,"fmt":"8.05"},"low":{"raw":7.23,"fmt":"7.23"},"high":{"raw":9.0,"fmt":"9"},"yearAgoEps":{"raw":7.38069,"fmt":"7.38"},"numberOfAnalysts":{"raw":43,"fmt":"43","longFmt":"43"},"growth":{"raw":0.0912,"fmt":"9.12%"},"earningsCurrency":"USD"},"revenueEstimate":{"avg":{"raw":440936326340,"fmt":"440.94B","longFmt":"440,936,326,340"},"low":{"raw":416375000000,"fmt":"416.38B","longFmt":"416,375,000,000"},"high":{"raw":477463000000,"fmt":"477.46B","longFmt":"477,463,000,000"},"numberOfAnalysts":{"raw":44,"fmt":"44","longFmt":"44"},"yearAgoRevenue":{"raw":415655411720,"fmt":"415.66B","longFmt":"415,655,411,720"},"growth":{"raw":0.0608,"fmt":"6.08%"},"revenueCurrency":"USD"},"epsTrend":{"current":{"raw":8.05414,"fmt":"8.05"},"7daysAgo":{"raw":8.02737,"fmt":"8.03"},"30daysAgo":{"raw":7.99959,"fmt":"8"},"60daysAgo":{"raw":7.96417,"fmt":"7.96"},"90daysAgo":{"raw":7.79257,"fmt":"7.79"},"epsTrendCurrency":"USD"},"epsRevisions":{"upLast7days":{"raw":6,"fmt":"6","longFmt":"6"},"upLast30days":{"raw":8,"fmt":"8","longFmt":"8"},"downLast30days":{"raw":2,"fmt":"2","longFmt":"2"},"downLast7Days":{"raw":1,"fmt":"1","longFmt":"1"},"downLast90days":{},"epsRevisionsCurrency":"USD"}}],"maxAge":1}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/analysis_api_financialData_MSFT.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/analysis_api_financialData_MSFT.json new file mode 100644 index 0000000..8be2124 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/analysis_api_financialData_MSFT.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"financialData":{"maxAge":86400,"currentPrice":{"raw":541.55,"fmt":"541.55"},"targetHighPrice":{"raw":730.0,"fmt":"730.00"},"targetLowPrice":{"raw":483.0,"fmt":"483.00"},"targetMeanPrice":{"raw":621.06885,"fmt":"621.07"},"targetMedianPrice":{"raw":630.0,"fmt":"630.00"},"recommendationMean":{"raw":1.22414,"fmt":"1.22"},"recommendationKey":"strong_buy","numberOfAnalystOpinions":{"raw":52,"fmt":"52","longFmt":"52"},"totalCash":{"raw":94564999168,"fmt":"94.56B","longFmt":"94,564,999,168"},"totalCashPerShare":{"raw":12.722,"fmt":"12.72"},"ebitda":{"raw":156528001024,"fmt":"156.53B","longFmt":"156,528,001,024"},"totalDebt":{"raw":112184000512,"fmt":"112.18B","longFmt":"112,184,000,512"},"quickRatio":{"raw":1.223,"fmt":"1.22"},"currentRatio":{"raw":1.353,"fmt":"1.35"},"totalRevenue":{"raw":281723994112,"fmt":"281.72B","longFmt":"281,723,994,112"},"debtToEquity":{"raw":32.661,"fmt":"32.66%"},"revenuePerShare":{"raw":37.902,"fmt":"37.90"},"returnOnAssets":{"raw":0.14203,"fmt":"14.20%"},"returnOnEquity":{"raw":0.33280998,"fmt":"33.28%"},"grossProfits":{"raw":193893007360,"fmt":"193.89B","longFmt":"193,893,007,360"},"freeCashflow":{"raw":61070376960,"fmt":"61.07B","longFmt":"61,070,376,960"},"operatingCashflow":{"raw":136162000896,"fmt":"136.16B","longFmt":"136,162,000,896"},"earningsGrowth":{"raw":0.237,"fmt":"23.70%"},"revenueGrowth":{"raw":0.181,"fmt":"18.10%"},"grossMargins":{"raw":0.68824,"fmt":"68.82%"},"ebitdaMargins":{"raw":0.55561,"fmt":"55.56%"},"operatingMargins":{"raw":0.44901,"fmt":"44.90%"},"profitMargins":{"raw":0.36146,"fmt":"36.15%"},"financialCurrency":"USD"}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/analysis_api_recommendationTrend-financialData_MSFT.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/analysis_api_recommendationTrend-financialData_MSFT.json new file mode 100644 index 0000000..ba38b06 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/analysis_api_recommendationTrend-financialData_MSFT.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"recommendationTrend":{"trend":[{"period":"0m","strongBuy":12,"buy":45,"hold":0,"sell":0,"strongSell":0},{"period":"-1m","strongBuy":13,"buy":44,"hold":1,"sell":0,"strongSell":0},{"period":"-2m","strongBuy":12,"buy":43,"hold":1,"sell":0,"strongSell":0},{"period":"-3m","strongBuy":12,"buy":43,"hold":3,"sell":0,"strongSell":0}],"maxAge":86400},"financialData":{"maxAge":86400,"currentPrice":{"raw":541.55,"fmt":"541.55"},"targetHighPrice":{"raw":730.0,"fmt":"730.00"},"targetLowPrice":{"raw":483.0,"fmt":"483.00"},"targetMeanPrice":{"raw":621.06885,"fmt":"621.07"},"targetMedianPrice":{"raw":630.0,"fmt":"630.00"},"recommendationMean":{"raw":1.22414,"fmt":"1.22"},"recommendationKey":"strong_buy","numberOfAnalystOpinions":{"raw":52,"fmt":"52","longFmt":"52"},"totalCash":{"raw":94564999168,"fmt":"94.56B","longFmt":"94,564,999,168"},"totalCashPerShare":{"raw":12.722,"fmt":"12.72"},"ebitda":{"raw":156528001024,"fmt":"156.53B","longFmt":"156,528,001,024"},"totalDebt":{"raw":112184000512,"fmt":"112.18B","longFmt":"112,184,000,512"},"quickRatio":{"raw":1.223,"fmt":"1.22"},"currentRatio":{"raw":1.353,"fmt":"1.35"},"totalRevenue":{"raw":281723994112,"fmt":"281.72B","longFmt":"281,723,994,112"},"debtToEquity":{"raw":32.661,"fmt":"32.66%"},"revenuePerShare":{"raw":37.902,"fmt":"37.90"},"returnOnAssets":{"raw":0.14203,"fmt":"14.20%"},"returnOnEquity":{"raw":0.33280998,"fmt":"33.28%"},"grossProfits":{"raw":193893007360,"fmt":"193.89B","longFmt":"193,893,007,360"},"freeCashflow":{"raw":61070376960,"fmt":"61.07B","longFmt":"61,070,376,960"},"operatingCashflow":{"raw":136162000896,"fmt":"136.16B","longFmt":"136,162,000,896"},"earningsGrowth":{"raw":0.237,"fmt":"23.70%"},"revenueGrowth":{"raw":0.181,"fmt":"18.10%"},"grossMargins":{"raw":0.68824,"fmt":"68.82%"},"ebitdaMargins":{"raw":0.55561,"fmt":"55.56%"},"operatingMargins":{"raw":0.44901,"fmt":"44.90%"},"profitMargins":{"raw":0.36146,"fmt":"36.15%"},"financialCurrency":"USD"}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/analysis_api_recommendationTrend_AAPL.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/analysis_api_recommendationTrend_AAPL.json new file mode 100644 index 0000000..b9a68dd --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/analysis_api_recommendationTrend_AAPL.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"recommendationTrend":{"trend":[{"period":"0m","strongBuy":5,"buy":24,"hold":14,"sell":2,"strongSell":3},{"period":"-1m","strongBuy":5,"buy":23,"hold":15,"sell":1,"strongSell":3},{"period":"-2m","strongBuy":5,"buy":22,"hold":15,"sell":1,"strongSell":1},{"period":"-3m","strongBuy":5,"buy":23,"hold":15,"sell":1,"strongSell":1}],"maxAge":86400}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/analysis_api_upgradeDowngradeHistory_GOOGL.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/analysis_api_upgradeDowngradeHistory_GOOGL.json new file mode 100644 index 0000000..b764102 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/analysis_api_upgradeDowngradeHistory_GOOGL.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"upgradeDowngradeHistory":{"history":[{"epochGradeDate":1761568080,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":300.0,"priorPriceTarget":265.0},{"epochGradeDate":1761313079,"firm":"Stifel","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":292.0,"priorPriceTarget":222.0},{"epochGradeDate":1761228748,"firm":"Bernstein","toGrade":"Market Perform","fromGrade":"Market Perform","action":"main","priceTargetAction":"Raises","currentPriceTarget":260.0,"priorPriceTarget":210.0},{"epochGradeDate":1761136261,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":290.0,"priorPriceTarget":290.0},{"epochGradeDate":1760958555,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":280.0,"priorPriceTarget":252.0},{"epochGradeDate":1760702540,"firm":"Guggenheim","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":280.0,"priorPriceTarget":210.0},{"epochGradeDate":1760032797,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":294.0,"priorPriceTarget":225.0},{"epochGradeDate":1759846523,"firm":"Wells Fargo","toGrade":"Equal-Weight","fromGrade":"Equal-Weight","action":"main","priceTargetAction":"Raises","currentPriceTarget":236.0,"priorPriceTarget":187.0},{"epochGradeDate":1759839146,"firm":"HSBC","toGrade":"Buy","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":285.0,"priorPriceTarget":0.0},{"epochGradeDate":1759409388,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":270.0,"priorPriceTarget":210.0},{"epochGradeDate":1759249312,"firm":"Mizuho","toGrade":"Outperform","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":295.0,"priorPriceTarget":0.0},{"epochGradeDate":1758798659,"firm":"MoffettNathanson","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":295.0,"priorPriceTarget":230.0},{"epochGradeDate":1758717372,"firm":"Cantor Fitzgerald","toGrade":"Neutral","fromGrade":"Neutral","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":201.0,"priorPriceTarget":201.0},{"epochGradeDate":1758554196,"firm":"Baird","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":275.0,"priorPriceTarget":215.0},{"epochGradeDate":1758548977,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":285.0,"priorPriceTarget":225.0},{"epochGradeDate":1758284562,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":290.0,"priorPriceTarget":250.0},{"epochGradeDate":1758195190,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":285.0,"priorPriceTarget":220.0},{"epochGradeDate":1757953498,"firm":"Citigroup","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":280.0,"priorPriceTarget":225.0},{"epochGradeDate":1757333779,"firm":"Evercore ISI Group","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":300.0,"priorPriceTarget":240.0},{"epochGradeDate":1757092013,"firm":"Tigress Financial","toGrade":"Strong Buy","fromGrade":"Strong Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":280.0,"priorPriceTarget":240.0},{"epochGradeDate":1756986827,"firm":"Canaccord Genuity","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":270.0,"priorPriceTarget":230.0},{"epochGradeDate":1756917185,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":270.0,"priorPriceTarget":235.0},{"epochGradeDate":1756908837,"firm":"Barclays","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":250.0,"priorPriceTarget":235.0},{"epochGradeDate":1756907866,"firm":"RBC Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":260.0,"priorPriceTarget":220.0},{"epochGradeDate":1756903921,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":265.0,"priorPriceTarget":230.0},{"epochGradeDate":1756902548,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":250.0,"priorPriceTarget":225.0},{"epochGradeDate":1756899752,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":260.0,"priorPriceTarget":220.0},{"epochGradeDate":1756898422,"firm":"Rosenblatt","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Raises","currentPriceTarget":224.0,"priorPriceTarget":191.0},{"epochGradeDate":1756896452,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":245.0,"priorPriceTarget":225.0},{"epochGradeDate":1756834730,"firm":"DA Davidson","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Raises","currentPriceTarget":190.0,"priorPriceTarget":180.0},{"epochGradeDate":1755772575,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":220.0,"priorPriceTarget":220.0},{"epochGradeDate":1754404423,"firm":"Loop Capital","toGrade":"Hold","fromGrade":"Hold","action":"main","priceTargetAction":"Raises","currentPriceTarget":190.0,"priorPriceTarget":165.0},{"epochGradeDate":1753799638,"firm":"Wells Fargo","toGrade":"Equal-Weight","fromGrade":"Equal-Weight","action":"main","priceTargetAction":"Raises","currentPriceTarget":187.0,"priorPriceTarget":184.0},{"epochGradeDate":1753372742,"firm":"DA Davidson","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Raises","currentPriceTarget":180.0,"priorPriceTarget":160.0},{"epochGradeDate":1753372205,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":220.0,"priorPriceTarget":195.0},{"epochGradeDate":1753369724,"firm":"Citigroup","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":225.0,"priorPriceTarget":203.0},{"epochGradeDate":1753368963,"firm":"UBS","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Raises","currentPriceTarget":202.0,"priorPriceTarget":192.0},{"epochGradeDate":1753368086,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":225.0,"priorPriceTarget":208.0},{"epochGradeDate":1753366164,"firm":"JP Morgan","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":232.0,"priorPriceTarget":200.0},{"epochGradeDate":1753365472,"firm":"Roth Capital","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":210.0,"priorPriceTarget":205.0},{"epochGradeDate":1753365133,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":225.0,"priorPriceTarget":200.0},{"epochGradeDate":1753364511,"firm":"Wells Fargo","toGrade":"Equal-Weight","fromGrade":"Equal-Weight","action":"main","priceTargetAction":"Raises","currentPriceTarget":184.0,"priorPriceTarget":177.0},{"epochGradeDate":1753364334,"firm":"Susquehanna","toGrade":"Positive","fromGrade":"Positive","action":"main","priceTargetAction":"Raises","currentPriceTarget":225.0,"priorPriceTarget":220.0},{"epochGradeDate":1753364103,"firm":"Cantor Fitzgerald","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Raises","currentPriceTarget":201.0,"priorPriceTarget":196.0},{"epochGradeDate":1753362207,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":230.0,"priorPriceTarget":215.0},{"epochGradeDate":1753361409,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":210.0,"priorPriceTarget":205.0},{"epochGradeDate":1753361382,"firm":"Barclays","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":235.0,"priorPriceTarget":220.0},{"epochGradeDate":1753360451,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":217.0,"priorPriceTarget":210.0},{"epochGradeDate":1753357418,"firm":"Stifel","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":222.0,"priorPriceTarget":218.0},{"epochGradeDate":1753357022,"firm":"Rosenblatt","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Raises","currentPriceTarget":191.0,"priorPriceTarget":189.0},{"epochGradeDate":1753351248,"firm":"WestPark Capital","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":220.0,"priorPriceTarget":210.0},{"epochGradeDate":1753350898,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":225.0,"priorPriceTarget":220.0},{"epochGradeDate":1753350234,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":220.0,"priorPriceTarget":210.0},{"epochGradeDate":1753275295,"firm":"Guggenheim","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":210.0,"priorPriceTarget":190.0},{"epochGradeDate":1753197736,"firm":"Bernstein","toGrade":"Market Perform","fromGrade":"Market Perform","action":"main","priceTargetAction":"Raises","currentPriceTarget":195.0,"priorPriceTarget":185.0},{"epochGradeDate":1753192388,"firm":"Stifel","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":218.0,"priorPriceTarget":200.0},{"epochGradeDate":1753111553,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":205.0,"priorPriceTarget":185.0},{"epochGradeDate":1752836948,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":210.0,"priorPriceTarget":200.0},{"epochGradeDate":1752752178,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":215.0,"priorPriceTarget":195.0},{"epochGradeDate":1752679158,"firm":"UBS","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Raises","currentPriceTarget":192.0,"priorPriceTarget":186.0},{"epochGradeDate":1752669538,"firm":"Cantor Fitzgerald","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Raises","currentPriceTarget":196.0,"priorPriceTarget":171.0},{"epochGradeDate":1752665777,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":210.0,"priorPriceTarget":178.0},{"epochGradeDate":1752599410,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"reit","priceTargetAction":"Raises","currentPriceTarget":208.0,"priorPriceTarget":200.0},{"epochGradeDate":1751994044,"firm":"Roth Capital","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":205.0,"priorPriceTarget":180.0},{"epochGradeDate":1751980742,"firm":"Wells Fargo","toGrade":"Equal-Weight","fromGrade":"Equal-Weight","action":"main","priceTargetAction":"Raises","currentPriceTarget":177.0,"priorPriceTarget":175.0},{"epochGradeDate":1751478371,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":200.0,"priorPriceTarget":200.0},{"epochGradeDate":1751026423,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Perform","action":"up","priceTargetAction":"Maintains","currentPriceTarget":220.0,"priorPriceTarget":220.0},{"epochGradeDate":1750858587,"firm":"Cantor Fitzgerald","toGrade":"Neutral","fromGrade":"Neutral","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":171.0,"priorPriceTarget":171.0},{"epochGradeDate":1750779512,"firm":"Citigroup","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":203.0,"priorPriceTarget":200.0},{"epochGradeDate":1748007058,"firm":"JMP Securities","toGrade":"Market Perform","fromGrade":"Market Perform","action":"reit","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1747835114,"firm":"Rosenblatt","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Maintains","currentPriceTarget":189.0,"priorPriceTarget":189.0},{"epochGradeDate":1747831448,"firm":"JMP Securities","toGrade":"Market Perform","fromGrade":"Market Perform","action":"reit","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1746722471,"firm":"JMP Securities","toGrade":"Market Perform","fromGrade":"Market Perform","action":"reit","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1746699275,"firm":"WestPark Capital","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":210.0,"priorPriceTarget":210.0},{"epochGradeDate":1746541696,"firm":"JMP Securities","toGrade":"Market Perform","fromGrade":"Market Perform","action":"reit","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1746046875,"firm":"Tigress Financial","toGrade":"Strong Buy","fromGrade":"Strong Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":240.0,"priorPriceTarget":220.0},{"epochGradeDate":1745599266,"firm":"Susquehanna","toGrade":"Positive","fromGrade":"Positive","action":"main","priceTargetAction":"Lowers","currentPriceTarget":220.0,"priorPriceTarget":225.0},{"epochGradeDate":1745596525,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":195.0,"priorPriceTarget":185.0},{"epochGradeDate":1745594045,"firm":"UBS","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Raises","currentPriceTarget":186.0,"priorPriceTarget":173.0},{"epochGradeDate":1745591618,"firm":"Bernstein","toGrade":"Market Perform","fromGrade":"Market Perform","action":"main","priceTargetAction":"Raises","currentPriceTarget":185.0,"priorPriceTarget":165.0},{"epochGradeDate":1745591240,"firm":"Citigroup","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":200.0,"priorPriceTarget":195.0},{"epochGradeDate":1745587545,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":195.0,"priorPriceTarget":185.0},{"epochGradeDate":1745587111,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":200.0,"priorPriceTarget":185.0},{"epochGradeDate":1745586359,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":178.0,"priorPriceTarget":178.0},{"epochGradeDate":1745585368,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":200.0,"priorPriceTarget":185.0},{"epochGradeDate":1745579559,"firm":"Wells Fargo","toGrade":"Equal-Weight","fromGrade":"Equal-Weight","action":"main","priceTargetAction":"Raises","currentPriceTarget":175.0,"priorPriceTarget":167.0},{"epochGradeDate":1745579228,"firm":"Rosenblatt","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Lowers","currentPriceTarget":189.0,"priorPriceTarget":205.0},{"epochGradeDate":1745427312,"firm":"Stifel","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":192.0,"priorPriceTarget":225.0},{"epochGradeDate":1745425564,"firm":"RBC Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":200.0,"priorPriceTarget":200.0},{"epochGradeDate":1744904229,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":185.0,"priorPriceTarget":210.0},{"epochGradeDate":1744901730,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"reit","priceTargetAction":"Lowers","currentPriceTarget":200.0,"priorPriceTarget":230.0},{"epochGradeDate":1744898837,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":200.0,"priorPriceTarget":220.0},{"epochGradeDate":1744821585,"firm":"Cantor Fitzgerald","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Lowers","currentPriceTarget":159.0,"priorPriceTarget":200.0},{"epochGradeDate":1744799875,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":178.0,"priorPriceTarget":178.0},{"epochGradeDate":1744710252,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":190.0,"priorPriceTarget":220.0},{"epochGradeDate":1744653735,"firm":"DA Davidson","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Lowers","currentPriceTarget":160.0,"priorPriceTarget":200.0},{"epochGradeDate":1744377040,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":178.0,"priorPriceTarget":225.0},{"epochGradeDate":1744300009,"firm":"Citigroup","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":195.0,"priorPriceTarget":229.0},{"epochGradeDate":1744291585,"firm":"JMP Securities","toGrade":"Market Perform","fromGrade":"Market Perform","action":"reit","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1744291285,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":185.0,"priorPriceTarget":225.0},{"epochGradeDate":1744284528,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":185.0,"priorPriceTarget":208.0},{"epochGradeDate":1744199178,"firm":"Mizuho","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":205.0,"priorPriceTarget":230.0},{"epochGradeDate":1744197392,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":185.0,"priorPriceTarget":225.0},{"epochGradeDate":1744126105,"firm":"JP Morgan","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":180.0,"priorPriceTarget":220.0},{"epochGradeDate":1743437784,"firm":"Wells Fargo","toGrade":"Equal-Weight","fromGrade":"Equal-Weight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":167.0,"priorPriceTarget":184.0},{"epochGradeDate":1743426319,"firm":"Jefferies","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":200.0,"priorPriceTarget":235.0},{"epochGradeDate":1743173949,"firm":"Citizens Capital Markets","toGrade":"Market Perform","fromGrade":"Market Perform","action":"reit","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1742393893,"firm":"Cantor Fitzgerald","toGrade":"Neutral","fromGrade":"Neutral","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":200.0,"priorPriceTarget":200.0},{"epochGradeDate":1742393781,"firm":"Roth MKM","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":220.0,"priorPriceTarget":220.0},{"epochGradeDate":1742383388,"firm":"Rosenblatt","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Maintains","currentPriceTarget":205.0,"priorPriceTarget":205.0},{"epochGradeDate":1742303810,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":202.0,"priorPriceTarget":220.0},{"epochGradeDate":1738773768,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":208.0,"priorPriceTarget":210.0},{"epochGradeDate":1738771439,"firm":"JP Morgan","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":220.0,"priorPriceTarget":232.0},{"epochGradeDate":1738770912,"firm":"JMP Securities","toGrade":"Market Perform","fromGrade":"Market Perform","action":"reit","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1738765382,"firm":"Citigroup","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":229.0,"priorPriceTarget":232.0},{"epochGradeDate":1738762407,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":220.0,"priorPriceTarget":225.0},{"epochGradeDate":1738761157,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":210.0,"priorPriceTarget":215.0},{"epochGradeDate":1738760974,"firm":"Raymond James","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":205.0,"priorPriceTarget":190.0},{"epochGradeDate":1738756089,"firm":"UBS","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Lowers","currentPriceTarget":209.0,"priorPriceTarget":211.0},{"epochGradeDate":1738755538,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":220.0,"priorPriceTarget":225.0},{"epochGradeDate":1738754229,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":225.0,"priorPriceTarget":225.0},{"epochGradeDate":1738750932,"firm":"Rosenblatt","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Raises","currentPriceTarget":205.0,"priorPriceTarget":193.0},{"epochGradeDate":1738246985,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":225.0,"priorPriceTarget":215.0},{"epochGradeDate":1737998325,"firm":"JMP Securities","toGrade":"Market Perform","fromGrade":"Market Perform","action":"reit","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1737979739,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":225.0,"priorPriceTarget":210.0},{"epochGradeDate":1737729190,"firm":"DA Davidson","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Raises","currentPriceTarget":200.0,"priorPriceTarget":190.0},{"epochGradeDate":1737561670,"firm":"Cantor Fitzgerald","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Lowers","currentPriceTarget":210.0,"priorPriceTarget":215.0},{"epochGradeDate":1737376651,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":230.0,"priorPriceTarget":217.0},{"epochGradeDate":1736785036,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":215.0,"priorPriceTarget":205.0},{"epochGradeDate":1736782784,"firm":"Stifel","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":225.0,"priorPriceTarget":200.0},{"epochGradeDate":1736781738,"firm":"Wells Fargo","toGrade":"Equal-Weight","fromGrade":"Equal-Weight","action":"main","priceTargetAction":"Raises","currentPriceTarget":190.0,"priorPriceTarget":187.0},{"epochGradeDate":1736522475,"firm":"JMP Securities","toGrade":"Market Perform","fromGrade":"Market Perform","action":"reit","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1736520155,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":225.0,"priorPriceTarget":215.0},{"epochGradeDate":1736246255,"firm":"Cantor Fitzgerald","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Raises","currentPriceTarget":215.0,"priorPriceTarget":190.0},{"epochGradeDate":1736180823,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":220.0,"priorPriceTarget":210.0},{"epochGradeDate":1735915157,"firm":"Wolfe Research","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":230.0,"priorPriceTarget":220.0},{"epochGradeDate":1735825435,"firm":"JMP Securities","toGrade":"Market Perform","fromGrade":"Market Outperform","action":"down","priceTargetAction":"Maintains","currentPriceTarget":220.0,"priorPriceTarget":220.0},{"epochGradeDate":1734972991,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":210.0,"priorPriceTarget":210.0},{"epochGradeDate":1734525714,"firm":"JP Morgan","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":232.0,"priorPriceTarget":212.0},{"epochGradeDate":1733996261,"firm":"Goldman Sachs","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":210.0,"priorPriceTarget":210.0},{"epochGradeDate":1733851567,"firm":"Baird","toGrade":"Outperform","fromGrade":"Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":205.0,"priorPriceTarget":205.0},{"epochGradeDate":1730918500,"firm":"Loop Capital","toGrade":"Hold","fromGrade":"Hold","action":"main","priceTargetAction":"Raises","currentPriceTarget":185.0,"priorPriceTarget":170.0},{"epochGradeDate":1730316981,"firm":"Pivotal Research","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":225.0,"priorPriceTarget":215.0},{"epochGradeDate":1730314846,"firm":"Bernstein","toGrade":"Market Perform","fromGrade":"Market Perform","action":"main","priceTargetAction":"Raises","currentPriceTarget":185.0,"priorPriceTarget":180.0},{"epochGradeDate":1730311942,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":220.0,"priorPriceTarget":200.0},{"epochGradeDate":1730308683,"firm":"Cantor Fitzgerald","toGrade":"Neutral","fromGrade":"Neutral","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":190.0,"priorPriceTarget":190.0},{"epochGradeDate":1730308453,"firm":"Roth MKM","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":212.0,"priorPriceTarget":206.0},{"epochGradeDate":1730307424,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":215.0,"priorPriceTarget":200.0},{"epochGradeDate":1730305185,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"Overweight","action":"reit","priceTargetAction":"Raises","currentPriceTarget":210.0,"priorPriceTarget":200.0},{"epochGradeDate":1730301775,"firm":"RBC Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":210.0,"priorPriceTarget":204.0},{"epochGradeDate":1730301577,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":225.0,"priorPriceTarget":220.0},{"epochGradeDate":1730300930,"firm":"Wells Fargo","toGrade":"Equal-Weight","fromGrade":"Equal-Weight","action":"main","priceTargetAction":"Raises","currentPriceTarget":187.0,"priorPriceTarget":182.0},{"epochGradeDate":1730300113,"firm":"Evercore ISI Group","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":205.0,"priorPriceTarget":200.0},{"epochGradeDate":1730296636,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"reit","priceTargetAction":"Raises","currentPriceTarget":217.0,"priorPriceTarget":215.0},{"epochGradeDate":1730292562,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":205.0,"priorPriceTarget":190.0},{"epochGradeDate":1730290689,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":210.0,"priorPriceTarget":206.0},{"epochGradeDate":1730287704,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":210.0,"priorPriceTarget":210.0},{"epochGradeDate":1729777597,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":205.0,"priorPriceTarget":205.0},{"epochGradeDate":1729088199,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":220.0,"priorPriceTarget":196.0},{"epochGradeDate":1728988384,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":210.0,"priorPriceTarget":210.0},{"epochGradeDate":1728488866,"firm":"Cantor Fitzgerald","toGrade":"Neutral","fromGrade":"Neutral","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":190.0,"priorPriceTarget":190.0},{"epochGradeDate":1728482670,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"reit","priceTargetAction":"Lowers","currentPriceTarget":215.0,"priorPriceTarget":222.0},{"epochGradeDate":1727879240,"firm":"Wells Fargo","toGrade":"Equal-Weight","fromGrade":"Equal-Weight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":182.0,"priorPriceTarget":190.0},{"epochGradeDate":1727719082,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"Overweight","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":200.0,"priorPriceTarget":200.0},{"epochGradeDate":1727712869,"firm":"Cantor Fitzgerald","toGrade":"Neutral","fromGrade":"Neutral","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":190.0,"priorPriceTarget":190.0},{"epochGradeDate":1727367648,"firm":"Tigress Financial","toGrade":"Strong Buy","fromGrade":"Strong Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":220.0,"priorPriceTarget":210.0},{"epochGradeDate":1727284085,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":200.0,"priorPriceTarget":200.0},{"epochGradeDate":1727101433,"firm":"Cantor Fitzgerald","toGrade":"Neutral","fromGrade":"Neutral","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":190.0,"priorPriceTarget":190.0},{"epochGradeDate":1726509236,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":196.0,"priorPriceTarget":196.0},{"epochGradeDate":1726495472,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":200.0,"priorPriceTarget":200.0},{"epochGradeDate":1726491572,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Maintains","currentPriceTarget":222.0,"priorPriceTarget":222.0},{"epochGradeDate":1726483247,"firm":"Evercore ISI Group","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":200.0,"priorPriceTarget":225.0},{"epochGradeDate":1725982475,"firm":"DA Davidson","toGrade":"Neutral","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":170.0,"priorPriceTarget":0.0},{"epochGradeDate":1725978049,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"Overweight","action":"reit","priceTargetAction":"Lowers","currentPriceTarget":200.0,"priorPriceTarget":206.0},{"epochGradeDate":1725541297,"firm":"Cantor Fitzgerald","toGrade":"Neutral","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":190.0,"priorPriceTarget":0.0},{"epochGradeDate":1725538728,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":205.0,"priorPriceTarget":205.0},{"epochGradeDate":1725364004,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":190.0,"priorPriceTarget":205.0},{"epochGradeDate":1725030408,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":210.0,"priorPriceTarget":210.0},{"epochGradeDate":1724945130,"firm":"Roth MKM","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":206.0,"priorPriceTarget":206.0},{"epochGradeDate":1724063135,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":210.0,"priorPriceTarget":210.0},{"epochGradeDate":1723631962,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":210.0,"priorPriceTarget":210.0},{"epochGradeDate":1722952188,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Maintains","currentPriceTarget":222.0,"priorPriceTarget":222.0},{"epochGradeDate":1722941721,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":210.0,"priorPriceTarget":210.0},{"epochGradeDate":1722422116,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":210.0,"priorPriceTarget":210.0},{"epochGradeDate":1722361759,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":200.0,"priorPriceTarget":200.0},{"epochGradeDate":1721848320,"firm":"Guggenheim","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":205.0,"priorPriceTarget":195.0},{"epochGradeDate":1721846964,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":200.0,"priorPriceTarget":200.0},{"epochGradeDate":1721845686,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":205.0,"priorPriceTarget":205.0},{"epochGradeDate":1721844656,"firm":"Citigroup","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":212.0,"priorPriceTarget":190.0},{"epochGradeDate":1721838622,"firm":"RBC Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":204.0,"priorPriceTarget":200.0},{"epochGradeDate":1721835543,"firm":"UBS","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Raises","currentPriceTarget":204.0,"priorPriceTarget":200.0},{"epochGradeDate":1721834557,"firm":"Roth MKM","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":206.0,"priorPriceTarget":202.0},{"epochGradeDate":1721828806,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":196.0,"priorPriceTarget":190.0},{"epochGradeDate":1721823734,"firm":"JP Morgan","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":208.0,"priorPriceTarget":200.0},{"epochGradeDate":1721823056,"firm":"Wells Fargo","toGrade":"Equal-Weight","fromGrade":"Equal-Weight","action":"main","priceTargetAction":"Raises","currentPriceTarget":190.0,"priorPriceTarget":187.0},{"epochGradeDate":1721822174,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":206.0,"priorPriceTarget":200.0},{"epochGradeDate":1721821400,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":205.0,"priorPriceTarget":210.0},{"epochGradeDate":1721820075,"firm":"Rosenblatt","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Maintains","currentPriceTarget":181.0,"priorPriceTarget":181.0},{"epochGradeDate":1721818775,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":210.0,"priorPriceTarget":210.0},{"epochGradeDate":1721755315,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":210.0,"priorPriceTarget":195.0},{"epochGradeDate":1721731123,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":210.0,"priorPriceTarget":210.0},{"epochGradeDate":1721651579,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":205.0,"priorPriceTarget":205.0},{"epochGradeDate":1721227789,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"reit","priceTargetAction":"Raises","currentPriceTarget":222.0,"priorPriceTarget":215.0},{"epochGradeDate":1721220103,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":206.0,"priorPriceTarget":200.0},{"epochGradeDate":1721139749,"firm":"Argus Research","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":215.0,"priorPriceTarget":200.0},{"epochGradeDate":1721123082,"firm":"Wolfe Research","toGrade":"Outperform","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":240.0,"priorPriceTarget":0.0},{"epochGradeDate":1720446328,"firm":"Wells Fargo","toGrade":"Equal-Weight","fromGrade":"Equal-Weight","action":"main","priceTargetAction":"Raises","currentPriceTarget":187.0,"priorPriceTarget":168.0},{"epochGradeDate":1720001527,"firm":"Loop Capital","toGrade":"Hold","fromGrade":"Hold","action":"main","priceTargetAction":"Maintains","currentPriceTarget":170.0,"priorPriceTarget":170.0},{"epochGradeDate":1719999744,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":210.0,"priorPriceTarget":210.0},{"epochGradeDate":1719587213,"firm":"Rosenblatt","toGrade":"Neutral","fromGrade":"Buy","action":"down","priceTargetAction":"Lowers","currentPriceTarget":181.0,"priorPriceTarget":182.0},{"epochGradeDate":1719570718,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":210.0,"priorPriceTarget":210.0},{"epochGradeDate":1719405640,"firm":"Jefferies","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":215.0,"priorPriceTarget":200.0},{"epochGradeDate":1718111368,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":200.0,"priorPriceTarget":200.0},{"epochGradeDate":1718109718,"firm":"Evercore ISI Group","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":225.0,"priorPriceTarget":220.0},{"epochGradeDate":1716564830,"firm":"Tigress Financial","toGrade":"Strong Buy","fromGrade":"Strong Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":210.0,"priorPriceTarget":176.0},{"epochGradeDate":1715783893,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Maintains","currentPriceTarget":195.0,"priorPriceTarget":195.0},{"epochGradeDate":1715777999,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":215.0,"priorPriceTarget":215.0},{"epochGradeDate":1715777648,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":200.0,"priorPriceTarget":200.0},{"epochGradeDate":1715773141,"firm":"Stifel","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":196.0,"priorPriceTarget":196.0},{"epochGradeDate":1714592241,"firm":"Loop Capital","toGrade":"Hold","fromGrade":"Hold","action":"main","priceTargetAction":"Raises","currentPriceTarget":170.0,"priorPriceTarget":155.0},{"epochGradeDate":1714493387,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":200.0,"priorPriceTarget":200.0},{"epochGradeDate":1714487789,"firm":"Argus Research","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":200.0,"priorPriceTarget":170.0},{"epochGradeDate":1714394695,"firm":"Susquehanna","toGrade":"Positive","fromGrade":"Positive","action":"main","priceTargetAction":"Raises","currentPriceTarget":225.0,"priorPriceTarget":170.0},{"epochGradeDate":1714154737,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":215.0,"priorPriceTarget":185.0},{"epochGradeDate":1714153754,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":205.0,"priorPriceTarget":185.0},{"epochGradeDate":1714152645,"firm":"Raymond James","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":200.0,"priorPriceTarget":160.0},{"epochGradeDate":1714151928,"firm":"TD Cowen","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":200.0,"priorPriceTarget":170.0},{"epochGradeDate":1714148204,"firm":"Roth MKM","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":202.0,"priorPriceTarget":164.0},{"epochGradeDate":1714145934,"firm":"RBC Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":200.0,"priorPriceTarget":155.0},{"epochGradeDate":1714145870,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":190.0,"priorPriceTarget":170.0},{"epochGradeDate":1714144253,"firm":"Stifel","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":196.0,"priorPriceTarget":174.0},{"epochGradeDate":1714141603,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"Overweight","action":"reit","priceTargetAction":"Raises","currentPriceTarget":200.0,"priorPriceTarget":160.0},{"epochGradeDate":1714137094,"firm":"JP Morgan","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":200.0,"priorPriceTarget":165.0},{"epochGradeDate":1714136935,"firm":"Jefferies","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":200.0,"priorPriceTarget":180.0},{"epochGradeDate":1714136238,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":205.0,"priorPriceTarget":175.0},{"epochGradeDate":1714135265,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":195.0,"priorPriceTarget":165.0},{"epochGradeDate":1714135248,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":200.0,"priorPriceTarget":160.0},{"epochGradeDate":1714134934,"firm":"Evercore ISI Group","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":220.0,"priorPriceTarget":160.0},{"epochGradeDate":1714134560,"firm":"Baird","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":200.0,"priorPriceTarget":160.0},{"epochGradeDate":1714134344,"firm":"Canaccord Genuity","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":210.0,"priorPriceTarget":190.0},{"epochGradeDate":1714134138,"firm":"Wells Fargo","toGrade":"Equal-Weight","fromGrade":"Equal-Weight","action":"main","priceTargetAction":"Raises","currentPriceTarget":168.0,"priorPriceTarget":141.0},{"epochGradeDate":1714132640,"firm":"Rosenblatt","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":182.0,"priorPriceTarget":172.0},{"epochGradeDate":1714131220,"firm":"Mizuho","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":190.0,"priorPriceTarget":170.0},{"epochGradeDate":1714129079,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":210.0,"priorPriceTarget":160.0},{"epochGradeDate":1714128111,"firm":"Citigroup","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":190.0,"priorPriceTarget":168.0},{"epochGradeDate":1714127981,"firm":"Barclays","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":200.0,"priorPriceTarget":173.0},{"epochGradeDate":1714126489,"firm":"Wolfe Research","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":200.0,"priorPriceTarget":180.0},{"epochGradeDate":1714126057,"firm":"Bernstein","toGrade":"Market Perform","fromGrade":"Market Perform","action":"main","priceTargetAction":"Raises","currentPriceTarget":180.0,"priorPriceTarget":165.0},{"epochGradeDate":1713786324,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":175.0,"priorPriceTarget":165.0},{"epochGradeDate":1713530436,"firm":"Jefferies","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":180.0,"priorPriceTarget":175.0},{"epochGradeDate":1713358288,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":170.0,"priorPriceTarget":158.0},{"epochGradeDate":1713349915,"firm":"Canaccord Genuity","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":190.0,"priorPriceTarget":180.0},{"epochGradeDate":1713271555,"firm":"UBS","toGrade":"Neutral","fromGrade":"Neutral","action":"main","priceTargetAction":"Raises","currentPriceTarget":166.0,"priorPriceTarget":150.0},{"epochGradeDate":1712772719,"firm":"Citigroup","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":168.0,"priorPriceTarget":168.0},{"epochGradeDate":1712761775,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":160.0,"priorPriceTarget":160.0},{"epochGradeDate":1712756643,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"reit","priceTargetAction":"Raises","currentPriceTarget":185.0,"priorPriceTarget":178.0},{"epochGradeDate":1712751217,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":175.0,"priorPriceTarget":175.0},{"epochGradeDate":1712147435,"firm":"Wells Fargo","toGrade":"Equal-Weight","fromGrade":"Equal-Weight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":141.0,"priorPriceTarget":144.0},{"epochGradeDate":1711117066,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":175.0,"priorPriceTarget":160.0},{"epochGradeDate":1710772126,"firm":"Wells Fargo","toGrade":"Equal-Weight","fromGrade":"Equal-Weight","action":"main","priceTargetAction":"Raises","currentPriceTarget":144.0,"priorPriceTarget":141.0},{"epochGradeDate":1706728773,"firm":"Citigroup","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":168.0,"priorPriceTarget":153.0},{"epochGradeDate":1706728250,"firm":"Rosenblatt","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":172.0,"priorPriceTarget":174.0},{"epochGradeDate":1706726258,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":158.0,"priorPriceTarget":160.0},{"epochGradeDate":1706725621,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":160.0,"priorPriceTarget":150.0},{"epochGradeDate":1706722016,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":178.0,"priorPriceTarget":170.0},{"epochGradeDate":1706718267,"firm":"Roth MKM","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":164.0,"priorPriceTarget":166.0},{"epochGradeDate":1706715823,"firm":"Susquehanna","toGrade":"Positive","fromGrade":"Positive","action":"main","priceTargetAction":"Raises","currentPriceTarget":170.0,"priorPriceTarget":150.0},{"epochGradeDate":1706715642,"firm":"RBC Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":155.0,"priorPriceTarget":0.0},{"epochGradeDate":1706709639,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":160.0,"priorPriceTarget":0.0},{"epochGradeDate":1706699943,"firm":"Redburn Atlantic","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":165.0,"priorPriceTarget":150.0},{"epochGradeDate":1706699819,"firm":"Wolfe Research","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":180.0,"priorPriceTarget":170.0},{"epochGradeDate":1706698449,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":160.0,"priorPriceTarget":0.0},{"epochGradeDate":1706696704,"firm":"Barclays","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":173.0,"priorPriceTarget":180.0},{"epochGradeDate":1706524023,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":165.0,"priorPriceTarget":153.0},{"epochGradeDate":1705682535,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":150.0,"priorPriceTarget":140.0},{"epochGradeDate":1705511299,"firm":"Mizuho","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":170.0,"priorPriceTarget":155.0},{"epochGradeDate":1704798294,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":170.0,"priorPriceTarget":0.0},{"epochGradeDate":1701963618,"firm":"Roth MKM","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":166.0,"priorPriceTarget":152.0},{"epochGradeDate":1700595322,"firm":"Tigress Financial","toGrade":"Strong Buy","fromGrade":"Strong Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":176.0,"priorPriceTarget":172.0},{"epochGradeDate":1700143299,"firm":"Wells Fargo","toGrade":"Equal-Weight","fromGrade":"Equal-Weight","action":"main","priceTargetAction":"Raises","currentPriceTarget":129.0,"priorPriceTarget":126.0},{"epochGradeDate":1698256056,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":150.0,"priorPriceTarget":147.0},{"epochGradeDate":1698253610,"firm":"Monness, Crespi, Hardt","toGrade":"Neutral","fromGrade":"Buy","action":"down","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1698250415,"firm":"RBC Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":155.0,"priorPriceTarget":0.0},{"epochGradeDate":1698245372,"firm":"Roth MKM","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":152.0,"priorPriceTarget":146.0},{"epochGradeDate":1698244549,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":140.0,"priorPriceTarget":138.0},{"epochGradeDate":1698242579,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":160.0,"priorPriceTarget":0.0},{"epochGradeDate":1698242313,"firm":"Barclays","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":180.0,"priorPriceTarget":200.0},{"epochGradeDate":1698235099,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":150.0,"priorPriceTarget":155.0},{"epochGradeDate":1698234473,"firm":"Rosenblatt","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":174.0,"priorPriceTarget":163.0},{"epochGradeDate":1698230732,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":160.0,"priorPriceTarget":140.0},{"epochGradeDate":1698225453,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":153.0,"priorPriceTarget":155.0},{"epochGradeDate":1698151063,"firm":"Seaport Global","toGrade":"Neutral","fromGrade":"","action":"init","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1697184917,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":155.0,"priorPriceTarget":145.0},{"epochGradeDate":1696936564,"firm":"Wells Fargo","toGrade":"Equal-Weight","fromGrade":"Equal-Weight","action":"main","priceTargetAction":"Raises","currentPriceTarget":126.0,"priorPriceTarget":121.0},{"epochGradeDate":1696417755,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":146.0,"priorPriceTarget":142.0},{"epochGradeDate":1696359060,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":147.0,"priorPriceTarget":148.0},{"epochGradeDate":1694601297,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":140.0,"priorPriceTarget":0.0},{"epochGradeDate":1693490463,"firm":"Susquehanna","toGrade":"Positive","fromGrade":"Positive","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":150.0,"priorPriceTarget":0.0},{"epochGradeDate":1693407383,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":146.0,"priorPriceTarget":142.0},{"epochGradeDate":1693402809,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":138.0,"priorPriceTarget":0.0},{"epochGradeDate":1692691468,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":160.0,"priorPriceTarget":0.0},{"epochGradeDate":1692374659,"firm":"Loop Capital","toGrade":"Hold","fromGrade":"Hold","action":"main","priceTargetAction":"Raises","currentPriceTarget":140.0,"priorPriceTarget":125.0},{"epochGradeDate":1691750531,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":140.0,"priorPriceTarget":0.0},{"epochGradeDate":1690390144,"firm":"Evercore ISI Group","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":160.0,"priorPriceTarget":130.0},{"epochGradeDate":1690382760,"firm":"Susquehanna","toGrade":"Positive","fromGrade":"Positive","action":"main","priceTargetAction":"Raises","currentPriceTarget":150.0,"priorPriceTarget":120.0},{"epochGradeDate":1690382477,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":155.0,"priorPriceTarget":150.0},{"epochGradeDate":1690380506,"firm":"Citigroup","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":153.0,"priorPriceTarget":130.0},{"epochGradeDate":1690379537,"firm":"RBC Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":155.0,"priorPriceTarget":145.0},{"epochGradeDate":1690377498,"firm":"Wells Fargo","toGrade":"Equal-Weight","fromGrade":"Equal-Weight","action":"main","priceTargetAction":"Raises","currentPriceTarget":121.0,"priorPriceTarget":116.0},{"epochGradeDate":1690376408,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":151.0,"priorPriceTarget":150.0},{"epochGradeDate":1690374502,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":138.0,"priorPriceTarget":132.0},{"epochGradeDate":1690370660,"firm":"Rosenblatt","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":163.0,"priorPriceTarget":132.0},{"epochGradeDate":1690366660,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":140.0,"priorPriceTarget":115.0},{"epochGradeDate":1690358668,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":145.0,"priorPriceTarget":140.0},{"epochGradeDate":1689955942,"firm":"Stifel","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":135.0,"priorPriceTarget":130.0},{"epochGradeDate":1689853169,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":142.0,"priorPriceTarget":128.0},{"epochGradeDate":1689668859,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":140.0,"priorPriceTarget":122.0},{"epochGradeDate":1689594796,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":150.0,"priorPriceTarget":135.0},{"epochGradeDate":1689351306,"firm":"Tigress Financial","toGrade":"Strong Buy","fromGrade":"Strong Buy","action":"reit","priceTargetAction":"Raises","currentPriceTarget":172.0,"priorPriceTarget":160.0},{"epochGradeDate":1689254715,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":150.0,"priorPriceTarget":140.0},{"epochGradeDate":1688563013,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":128.0,"priorPriceTarget":128.0},{"epochGradeDate":1688553475,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":140.0,"priorPriceTarget":128.0},{"epochGradeDate":1687854300,"firm":"Bernstein","toGrade":"Market Perform","fromGrade":"Outperform","action":"down","priceTargetAction":"Announces","currentPriceTarget":125.0,"priorPriceTarget":0.0},{"epochGradeDate":1687794565,"firm":"UBS","toGrade":"Neutral","fromGrade":"Buy","action":"down","priceTargetAction":"Raises","currentPriceTarget":132.0,"priorPriceTarget":123.0},{"epochGradeDate":1686827446,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":128.0,"priorPriceTarget":128.0},{"epochGradeDate":1686239939,"firm":"Wells Fargo","toGrade":"Equal-Weight","fromGrade":"Equal-Weight","action":"init","priceTargetAction":"Announces","currentPriceTarget":117.0,"priorPriceTarget":0.0},{"epochGradeDate":1685966746,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":132.0,"priorPriceTarget":0.0},{"epochGradeDate":1684932416,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"","action":"main","priceTargetAction":"Maintains","currentPriceTarget":132.0,"priorPriceTarget":0.0},{"epochGradeDate":1684835203,"firm":"Jefferies","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":150.0,"priorPriceTarget":130.0},{"epochGradeDate":1684151663,"firm":"Loop Capital","toGrade":"Hold","fromGrade":"Buy","action":"down","priceTargetAction":"Announces","currentPriceTarget":125.0,"priorPriceTarget":0.0},{"epochGradeDate":1683832164,"firm":"Goldman Sachs","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Announces","currentPriceTarget":132.0,"priorPriceTarget":0.0},{"epochGradeDate":1683819677,"firm":"RBC Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Maintains","currentPriceTarget":132.0,"priorPriceTarget":132.0},{"epochGradeDate":1683806274,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":132.0,"priorPriceTarget":0.0},{"epochGradeDate":1683656264,"firm":"Bernstein","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Maintains","currentPriceTarget":125.0,"priorPriceTarget":125.0},{"epochGradeDate":1682546708,"firm":"Wolfe Research","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":130.0,"priorPriceTarget":120.0},{"epochGradeDate":1682535433,"firm":"Loop Capital","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":125.0,"priorPriceTarget":125.0},{"epochGradeDate":1682534211,"firm":"JP Morgan","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Announces","currentPriceTarget":121.0,"priorPriceTarget":0.0},{"epochGradeDate":1682522032,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":128.0,"priorPriceTarget":125.0},{"epochGradeDate":1682521291,"firm":"TD Cowen","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":130.0,"priorPriceTarget":125.0},{"epochGradeDate":1682520500,"firm":"Citigroup","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":130.0,"priorPriceTarget":120.0},{"epochGradeDate":1682519428,"firm":"Deutsche Bank","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":125.0,"priorPriceTarget":120.0},{"epochGradeDate":1682518179,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":122.0,"priorPriceTarget":120.0},{"epochGradeDate":1682517812,"firm":"Raymond James","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":130.0,"priorPriceTarget":119.0},{"epochGradeDate":1682517457,"firm":"Roth MKM","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":134.0,"priorPriceTarget":126.0},{"epochGradeDate":1682516999,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":128.0,"priorPriceTarget":117.0},{"epochGradeDate":1682514330,"firm":"RBC Capital","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":132.0,"priorPriceTarget":130.0},{"epochGradeDate":1682511794,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":145.0,"priorPriceTarget":135.0},{"epochGradeDate":1682509749,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":132.0,"priorPriceTarget":0.0},{"epochGradeDate":1682508953,"firm":"Baird","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":123.0,"priorPriceTarget":120.0},{"epochGradeDate":1682508259,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":140.0,"priorPriceTarget":135.0},{"epochGradeDate":1682507172,"firm":"Rosenblatt","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":132.0,"priorPriceTarget":128.0},{"epochGradeDate":1682506779,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":135.0,"priorPriceTarget":136.0},{"epochGradeDate":1682505144,"firm":"Evercore ISI Group","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":130.0,"priorPriceTarget":125.0},{"epochGradeDate":1682502736,"firm":"Needham","toGrade":"Buy","fromGrade":"","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":115.0,"priorPriceTarget":0.0},{"epochGradeDate":1682498425,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":122.0,"priorPriceTarget":117.0},{"epochGradeDate":1682338944,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":135.0,"priorPriceTarget":155.0},{"epochGradeDate":1682014925,"firm":"Bernstein","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":125.0,"priorPriceTarget":130.0},{"epochGradeDate":1680808396,"firm":"Goldman Sachs","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":128.0,"priorPriceTarget":128.0},{"epochGradeDate":1680796060,"firm":"UBS","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":123.0,"priorPriceTarget":120.0},{"epochGradeDate":1680514643,"firm":"Needham","toGrade":"Buy","fromGrade":"","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":115.0,"priorPriceTarget":0.0},{"epochGradeDate":1680268292,"firm":"Piper Sandler","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":117.0,"priorPriceTarget":120.0},{"epochGradeDate":1679050566,"firm":"Exane BNP Paribas","toGrade":"Outperform","fromGrade":"Neutral","action":"up","priceTargetAction":"Announces","currentPriceTarget":123.0,"priorPriceTarget":0.0},{"epochGradeDate":1678967847,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":132.0,"priorPriceTarget":0.0},{"epochGradeDate":1678271453,"firm":"Needham","toGrade":"Buy","fromGrade":"","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":115.0,"priorPriceTarget":0.0},{"epochGradeDate":1676037397,"firm":"Jefferies","toGrade":"Buy","fromGrade":"","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":130.0,"priorPriceTarget":0.0},{"epochGradeDate":1675971761,"firm":"JP Morgan","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Maintains","currentPriceTarget":118.0,"priorPriceTarget":118.0},{"epochGradeDate":1675864784,"firm":"Loop Capital","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":125.0,"priorPriceTarget":120.0},{"epochGradeDate":1675767881,"firm":"New Street Research","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":130.0,"priorPriceTarget":118.0},{"epochGradeDate":1675711818,"firm":"Bernstein","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Announces","currentPriceTarget":130.0,"priorPriceTarget":0.0},{"epochGradeDate":1675465359,"firm":"Wolfe Research","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":120.0,"priorPriceTarget":110.0},{"epochGradeDate":1675443978,"firm":"Goldman Sachs","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Announces","currentPriceTarget":128.0,"priorPriceTarget":0.0},{"epochGradeDate":1675437446,"firm":"Wells Fargo","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":150.0,"priorPriceTarget":145.0},{"epochGradeDate":1675436548,"firm":"Raymond James","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":119.0,"priorPriceTarget":116.0},{"epochGradeDate":1675436429,"firm":"Roth MKM","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":126.0,"priorPriceTarget":120.0},{"epochGradeDate":1675433635,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":132.0,"priorPriceTarget":0.0},{"epochGradeDate":1675433291,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":120.0,"priorPriceTarget":130.0},{"epochGradeDate":1675431958,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":120.0,"priorPriceTarget":122.0},{"epochGradeDate":1675430436,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":155.0,"priorPriceTarget":130.0},{"epochGradeDate":1675429305,"firm":"Rosenblatt","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":128.0,"priorPriceTarget":130.0},{"epochGradeDate":1675428501,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":136.0,"priorPriceTarget":145.0},{"epochGradeDate":1675427970,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":135.0,"priorPriceTarget":125.0},{"epochGradeDate":1675427709,"firm":"JP Morgan","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":118.0,"priorPriceTarget":115.0},{"epochGradeDate":1675426577,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":125.0,"priorPriceTarget":119.0},{"epochGradeDate":1675425351,"firm":"Barclays","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":160.0,"priorPriceTarget":150.0},{"epochGradeDate":1675425033,"firm":"Needham","toGrade":"Buy","fromGrade":"","action":"reit","priceTargetAction":"Maintains","currentPriceTarget":115.0,"priorPriceTarget":0.0},{"epochGradeDate":1675172159,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":119.0,"priorPriceTarget":116.0},{"epochGradeDate":1675071083,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":117.0,"priorPriceTarget":118.0},{"epochGradeDate":1674739180,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":130.0,"priorPriceTarget":135.0},{"epochGradeDate":1674655755,"firm":"MKM Partners","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":120.0,"priorPriceTarget":130.0},{"epochGradeDate":1674649820,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":145.0,"priorPriceTarget":128.0},{"epochGradeDate":1674572322,"firm":"Raymond James","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":116.0,"priorPriceTarget":120.0},{"epochGradeDate":1674046992,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":125.0,"priorPriceTarget":120.0},{"epochGradeDate":1673446571,"firm":"TD Cowen","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":125.0,"priorPriceTarget":135.0},{"epochGradeDate":1673024882,"firm":"Tigress Financial","toGrade":"Strong Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":160.0,"priorPriceTarget":186.0},{"epochGradeDate":1672959586,"firm":"Wolfe Research","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":110.0,"priorPriceTarget":116.0},{"epochGradeDate":1672825528,"firm":"New Street Research","toGrade":"Buy","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":118.0,"priorPriceTarget":0.0},{"epochGradeDate":1671718708,"firm":"Baird","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":115.0,"priorPriceTarget":120.0},{"epochGradeDate":1671701247,"firm":"Needham","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":115.0,"priorPriceTarget":160.0},{"epochGradeDate":1671632618,"firm":"Evercore ISI Group","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":120.0,"priorPriceTarget":130.0},{"epochGradeDate":1669822848,"firm":"Societe Generale","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":132.0,"priorPriceTarget":147.0},{"epochGradeDate":1669808517,"firm":"Mizuho","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":135.0,"priorPriceTarget":140.0},{"epochGradeDate":1669218872,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":132.0,"priorPriceTarget":145.0},{"epochGradeDate":1668791021,"firm":"Goldman Sachs","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":135.0,"priorPriceTarget":135.0},{"epochGradeDate":1668517874,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":120.0,"priorPriceTarget":125.0},{"epochGradeDate":1666806448,"firm":"Jefferies","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":130.0,"priorPriceTarget":130.0},{"epochGradeDate":1666799789,"firm":"Goldman Sachs","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":135.0,"priorPriceTarget":146.0},{"epochGradeDate":1666794733,"firm":"Deutsche Bank","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":120.0,"priorPriceTarget":130.0},{"epochGradeDate":1666792661,"firm":"Raymond James","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":120.0,"priorPriceTarget":143.0},{"epochGradeDate":1666792020,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":125.0,"priorPriceTarget":135.0},{"epochGradeDate":1666790411,"firm":"TD Cowen","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":135.0,"priorPriceTarget":150.0},{"epochGradeDate":1666789281,"firm":"Evercore ISI Group","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":130.0,"priorPriceTarget":140.0},{"epochGradeDate":1666789027,"firm":"Susquehanna","toGrade":"Positive","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":120.0,"priorPriceTarget":150.0},{"epochGradeDate":1666788289,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":122.0,"priorPriceTarget":135.0},{"epochGradeDate":1666786711,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":135.0,"priorPriceTarget":155.0},{"epochGradeDate":1666786081,"firm":"Wells Fargo","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":145.0,"priorPriceTarget":160.0},{"epochGradeDate":1666785453,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":130.0,"priorPriceTarget":136.0},{"epochGradeDate":1666784377,"firm":"JP Morgan","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":115.0,"priorPriceTarget":136.0},{"epochGradeDate":1666784117,"firm":"Rosenblatt","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":130.0,"priorPriceTarget":156.0},{"epochGradeDate":1666783690,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":145.0,"priorPriceTarget":160.0},{"epochGradeDate":1666783393,"firm":"Mizuho","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":140.0,"priorPriceTarget":150.0},{"epochGradeDate":1666781654,"firm":"RBC Capital","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":130.0,"priorPriceTarget":135.0},{"epochGradeDate":1666781146,"firm":"Citigroup","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":120.0,"priorPriceTarget":140.0},{"epochGradeDate":1666780987,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":128.0,"priorPriceTarget":134.0},{"epochGradeDate":1666780714,"firm":"Bernstein","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":120.0,"priorPriceTarget":130.0},{"epochGradeDate":1666772857,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":118.0,"priorPriceTarget":120.0},{"epochGradeDate":1666704583,"firm":"Baird","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Announces","currentPriceTarget":120.0,"priorPriceTarget":0.0},{"epochGradeDate":1666685943,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":120.0,"priorPriceTarget":125.0},{"epochGradeDate":1666362788,"firm":"Deutsche Bank","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":130.0,"priorPriceTarget":135.0},{"epochGradeDate":1666352483,"firm":"JP Morgan","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":136.0,"priorPriceTarget":140.0},{"epochGradeDate":1666188190,"firm":"MKM Partners","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":134.0,"priorPriceTarget":140.0},{"epochGradeDate":1665485946,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":134.0,"priorPriceTarget":140.0},{"epochGradeDate":1665405961,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":135.0,"priorPriceTarget":145.0},{"epochGradeDate":1665071623,"firm":"Goldman Sachs","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":146.0,"priorPriceTarget":150.0},{"epochGradeDate":1664881550,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":114.0,"priorPriceTarget":125.0},{"epochGradeDate":1664370005,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":136.0,"priorPriceTarget":145.0},{"epochGradeDate":1663170523,"firm":"Goldman Sachs","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":150.0,"priorPriceTarget":150.0},{"epochGradeDate":1662552050,"firm":"Rosenblatt","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":156.0,"priorPriceTarget":154.0},{"epochGradeDate":1659533001,"firm":"Tigress Financial","toGrade":"Strong Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":186.0,"priorPriceTarget":183.0},{"epochGradeDate":1659474107,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Announces","currentPriceTarget":145.0,"priorPriceTarget":0.0},{"epochGradeDate":1658959046,"firm":"Wolfe Research","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Adjusts","currentPriceTarget":130.0,"priorPriceTarget":2800.0},{"epochGradeDate":1658953809,"firm":"Susquehanna","toGrade":"Positive","fromGrade":"Positive","action":"main","priceTargetAction":"Lowers","currentPriceTarget":150.0,"priorPriceTarget":187.5},{"epochGradeDate":1658945322,"firm":"Barclays","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Adjusts","currentPriceTarget":150.0,"priorPriceTarget":3000.0},{"epochGradeDate":1658936788,"firm":"Goldman Sachs","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Announces","currentPriceTarget":150.0,"priorPriceTarget":0.0},{"epochGradeDate":1658932863,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Announces","currentPriceTarget":160.0,"priorPriceTarget":0.0},{"epochGradeDate":1658930207,"firm":"Raymond James","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":143.0,"priorPriceTarget":159.0},{"epochGradeDate":1658926060,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":125.0,"priorPriceTarget":132.0},{"epochGradeDate":1658925661,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":135.0,"priorPriceTarget":139.0},{"epochGradeDate":1658924659,"firm":"UBS","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":132.0,"priorPriceTarget":133.0},{"epochGradeDate":1658922664,"firm":"Jefferies","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":130.0,"priorPriceTarget":155.0},{"epochGradeDate":1658922405,"firm":"Evercore ISI Group","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":140.0,"priorPriceTarget":155.5},{"epochGradeDate":1658920612,"firm":"Wells Fargo","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":160.0,"priorPriceTarget":170.0},{"epochGradeDate":1658920444,"firm":"Rosenblatt","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":154.0,"priorPriceTarget":205.0},{"epochGradeDate":1658919909,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":145.0,"priorPriceTarget":140.0},{"epochGradeDate":1658919754,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":140.0,"priorPriceTarget":143.0},{"epochGradeDate":1658917817,"firm":"Citigroup","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":140.0,"priorPriceTarget":145.0},{"epochGradeDate":1658845607,"firm":"Itau BBA","toGrade":"Market Perform","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":113.0,"priorPriceTarget":0.0},{"epochGradeDate":1658824097,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":125.0,"priorPriceTarget":153.0},{"epochGradeDate":1658748372,"firm":"Rosenblatt","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Adjusts","currentPriceTarget":205.0,"priorPriceTarget":4118.0},{"epochGradeDate":1658499443,"firm":"Stifel","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":145.0,"priorPriceTarget":155.0},{"epochGradeDate":1658490262,"firm":"Mizuho","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":150.0,"priorPriceTarget":175.0},{"epochGradeDate":1658414047,"firm":"Deutsche Bank","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Adjusts","currentPriceTarget":135.0,"priorPriceTarget":2900.0},{"epochGradeDate":1658313024,"firm":"Exane BNP Paribas","toGrade":"Neutral","fromGrade":"Outperform","action":"down","priceTargetAction":"Announces","currentPriceTarget":118.0,"priorPriceTarget":0.0},{"epochGradeDate":1658262682,"firm":"RBC Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Announces","currentPriceTarget":135.0,"priorPriceTarget":0.0},{"epochGradeDate":1658237862,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":150.0,"priorPriceTarget":175.0},{"epochGradeDate":1658232784,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Adjusts","currentPriceTarget":165.0,"priorPriceTarget":3290.0},{"epochGradeDate":1658231907,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":140.0,"priorPriceTarget":150.0},{"epochGradeDate":1658228623,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":143.0,"priorPriceTarget":170.0},{"epochGradeDate":1658153388,"firm":"Stifel","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Adjusts","currentPriceTarget":155.0,"priorPriceTarget":3100.0},{"epochGradeDate":1658145659,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Adjusts","currentPriceTarget":150.0,"priorPriceTarget":3000.0},{"epochGradeDate":1657802845,"firm":"Citigroup","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":2900.0,"priorPriceTarget":3175.0},{"epochGradeDate":1657718479,"firm":"TD Cowen","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3000.0,"priorPriceTarget":3200.0},{"epochGradeDate":1657205944,"firm":"Evercore ISI Group","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3110.0,"priorPriceTarget":3300.0},{"epochGradeDate":1657017981,"firm":"Barclays","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3000.0,"priorPriceTarget":3200.0},{"epochGradeDate":1656585780,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3200.0,"priorPriceTarget":3300.0},{"epochGradeDate":1656508459,"firm":"JP Morgan","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":2800.0,"priorPriceTarget":3200.0},{"epochGradeDate":1656016064,"firm":"RBC Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Announces","currentPriceTarget":2700.0,"priorPriceTarget":0.0},{"epochGradeDate":1655470327,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":2636.0,"priorPriceTarget":2940.0},{"epochGradeDate":1655381473,"firm":"UBS","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":2650.0,"priorPriceTarget":3600.0},{"epochGradeDate":1654875821,"firm":"Goldman Sachs","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":3000.0,"priorPriceTarget":3000.0},{"epochGradeDate":1654206856,"firm":"Wolfe Research","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":2800.0,"priorPriceTarget":2900.0},{"epochGradeDate":1654164951,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":2775.0,"priorPriceTarget":2900.0},{"epochGradeDate":1654079893,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3000.0,"priorPriceTarget":3270.0},{"epochGradeDate":1653397575,"firm":"Jefferies","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3100.0,"priorPriceTarget":3400.0},{"epochGradeDate":1651084063,"firm":"Citigroup","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3175.0,"priorPriceTarget":3500.0},{"epochGradeDate":1651083969,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"main","priceTargetAction":"Maintains","currentPriceTarget":3300.0,"priorPriceTarget":3300.0},{"epochGradeDate":1651074021,"firm":"Goldman Sachs","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Announces","currentPriceTarget":3000.0,"priorPriceTarget":0.0},{"epochGradeDate":1651070214,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":3200.0,"priorPriceTarget":3200.0},{"epochGradeDate":1651069532,"firm":"Deutsche Bank","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":2900.0,"priorPriceTarget":3150.0},{"epochGradeDate":1651069499,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":2940.0,"priorPriceTarget":3173.0},{"epochGradeDate":1651067962,"firm":"Canaccord Genuity","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3300.0,"priorPriceTarget":3500.0},{"epochGradeDate":1651067735,"firm":"Raymond James","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3180.0,"priorPriceTarget":3630.0},{"epochGradeDate":1651064817,"firm":"Jefferies","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3400.0,"priorPriceTarget":3600.0},{"epochGradeDate":1651062684,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3500.0,"priorPriceTarget":3600.0},{"epochGradeDate":1651060718,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3270.0,"priorPriceTarget":3450.0},{"epochGradeDate":1651060385,"firm":"Stifel","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3100.0,"priorPriceTarget":3500.0},{"epochGradeDate":1651059780,"firm":"Wells Fargo","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3400.0,"priorPriceTarget":3600.0},{"epochGradeDate":1651058990,"firm":"Rosenblatt","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":4118.0,"priorPriceTarget":4183.0},{"epochGradeDate":1651057662,"firm":"Barclays","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3200.0,"priorPriceTarget":3300.0},{"epochGradeDate":1651056850,"firm":"UBS","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3600.0,"priorPriceTarget":3850.0},{"epochGradeDate":1651056550,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3000.0,"priorPriceTarget":3300.0},{"epochGradeDate":1651055242,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3400.0,"priorPriceTarget":3450.0},{"epochGradeDate":1651055006,"firm":"Wolfe Research","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":2900.0,"priorPriceTarget":3500.0},{"epochGradeDate":1651054830,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":2900.0,"priorPriceTarget":3475.0},{"epochGradeDate":1651054641,"firm":"Mizuho","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3500.0,"priorPriceTarget":3600.0},{"epochGradeDate":1650986885,"firm":"Baird","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Announces","currentPriceTarget":3000.0,"priorPriceTarget":0.0},{"epochGradeDate":1650537773,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3450.0,"priorPriceTarget":3500.0},{"epochGradeDate":1650444912,"firm":"Citigroup","toGrade":"Buy","fromGrade":"Neutral","action":"up","priceTargetAction":"Raises","currentPriceTarget":3500.0,"priorPriceTarget":2965.0},{"epochGradeDate":1650443876,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3075.0,"priorPriceTarget":3400.0},{"epochGradeDate":1650384065,"firm":"RBC Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Announces","currentPriceTarget":3420.0,"priorPriceTarget":0.0},{"epochGradeDate":1650372247,"firm":"Rosenblatt","toGrade":"Buy","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":4183.0,"priorPriceTarget":0.0},{"epochGradeDate":1649963932,"firm":"MKM Partners","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3300.0,"priorPriceTarget":3375.0},{"epochGradeDate":1647624834,"firm":"Tigress Financial","toGrade":"Strong Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3670.0,"priorPriceTarget":3540.0},{"epochGradeDate":1647012908,"firm":"Deutsche Bank","toGrade":"Buy","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":3150.0,"priorPriceTarget":0.0},{"epochGradeDate":1646240292,"firm":"RBC Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Announces","currentPriceTarget":3550.0,"priorPriceTarget":0.0},{"epochGradeDate":1643842378,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":3600.0,"priorPriceTarget":3400.0},{"epochGradeDate":1643842274,"firm":"Wolfe Research","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":3600.0,"priorPriceTarget":3400.0},{"epochGradeDate":1643841646,"firm":"Wells Fargo","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Announces","currentPriceTarget":3600.0,"priorPriceTarget":0.0},{"epochGradeDate":1643831854,"firm":"Bernstein","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":3500.0,"priorPriceTarget":3250.0},{"epochGradeDate":1643831750,"firm":"MKM Partners","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3375.0,"priorPriceTarget":3150.0},{"epochGradeDate":1643829147,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Announces","currentPriceTarget":3300.0,"priorPriceTarget":0.0},{"epochGradeDate":1643819502,"firm":"Goldman Sachs","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":3400.0,"priorPriceTarget":3350.0},{"epochGradeDate":1643819344,"firm":"Mizuho","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3600.0,"priorPriceTarget":3350.0},{"epochGradeDate":1643816069,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":3200.0,"priorPriceTarget":3200.0},{"epochGradeDate":1643814626,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3475.0,"priorPriceTarget":3150.0},{"epochGradeDate":1643813645,"firm":"Raymond James","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":3630.0,"priorPriceTarget":3400.0},{"epochGradeDate":1643812241,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":3500.0,"priorPriceTarget":3400.0},{"epochGradeDate":1643809346,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":3510.0,"priorPriceTarget":3470.0},{"epochGradeDate":1643806121,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3450.0,"priorPriceTarget":3430.0},{"epochGradeDate":1643805738,"firm":"JP Morgan","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":3450.0,"priorPriceTarget":3250.0},{"epochGradeDate":1643800296,"firm":"Jefferies","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":3600.0,"priorPriceTarget":3500.0},{"epochGradeDate":1643794295,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3400.0,"priorPriceTarget":3090.0},{"epochGradeDate":1643753540,"firm":"Stifel","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Announces","currentPriceTarget":3500.0,"priorPriceTarget":0.0},{"epochGradeDate":1643734489,"firm":"RBC Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Announces","currentPriceTarget":3500.0,"priorPriceTarget":0.0},{"epochGradeDate":1643733097,"firm":"Baird","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Announces","currentPriceTarget":3200.0,"priorPriceTarget":0.0},{"epochGradeDate":1643377574,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3400.0,"priorPriceTarget":3450.0},{"epochGradeDate":1643313208,"firm":"Bernstein","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":3250.0,"priorPriceTarget":3350.0},{"epochGradeDate":1643137167,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Maintains","currentPriceTarget":3200.0,"priorPriceTarget":3200.0},{"epochGradeDate":1643041650,"firm":"Goldman Sachs","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":3350.0,"priorPriceTarget":3350.0},{"epochGradeDate":1642599897,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3430.0,"priorPriceTarget":3200.0},{"epochGradeDate":1642599597,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":3470.0,"priorPriceTarget":3210.0},{"epochGradeDate":1638567731,"firm":"Tigress Financial","toGrade":"Strong Buy","fromGrade":"Strong Buy","action":"main","priceTargetAction":"Announces","currentPriceTarget":3540.0,"priorPriceTarget":0.0},{"epochGradeDate":1637166249,"firm":"Goldman Sachs","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":3350.0,"priorPriceTarget":3350.0},{"epochGradeDate":1635858218,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3200.0,"priorPriceTarget":3000.0},{"epochGradeDate":1635432482,"firm":"Citigroup","toGrade":"Neutral","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2965.0,"priorPriceTarget":2825.0},{"epochGradeDate":1635360601,"firm":"Bernstein","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":3350.0,"priorPriceTarget":3250.0},{"epochGradeDate":1635348652,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3400.0,"priorPriceTarget":3100.0},{"epochGradeDate":1635348102,"firm":"Goldman Sachs","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Announces","currentPriceTarget":3350.0,"priorPriceTarget":0.0},{"epochGradeDate":1635347699,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":3150.0,"priorPriceTarget":3034.0},{"epochGradeDate":1635346153,"firm":"Monness, Crespi, Hardt","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3660.0,"priorPriceTarget":3500.0},{"epochGradeDate":1635346026,"firm":"Wells Fargo","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3400.0,"priorPriceTarget":3100.0},{"epochGradeDate":1635345884,"firm":"Jefferies","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3500.0,"priorPriceTarget":3325.0},{"epochGradeDate":1635345678,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3200.0,"priorPriceTarget":3000.0},{"epochGradeDate":1635345023,"firm":"Needham","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":3200.0,"priorPriceTarget":3200.0},{"epochGradeDate":1635344122,"firm":"JMP Securities","toGrade":"Market Perform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3300.0,"priorPriceTarget":3100.0},{"epochGradeDate":1635342782,"firm":"Raymond James","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":3400.0,"priorPriceTarget":3200.0},{"epochGradeDate":1635342330,"firm":"Barclays","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3300.0,"priorPriceTarget":3200.0},{"epochGradeDate":1635341838,"firm":"Canaccord Genuity","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3350.0,"priorPriceTarget":3100.0},{"epochGradeDate":1635338220,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":3210.0,"priorPriceTarget":3150.0},{"epochGradeDate":1635337440,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3450.0,"priorPriceTarget":3400.0},{"epochGradeDate":1635336724,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":3500.0,"priorPriceTarget":3000.0},{"epochGradeDate":1635323459,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3090.0,"priorPriceTarget":3071.0},{"epochGradeDate":1635262616,"firm":"Baird","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Announces","currentPriceTarget":3175.0,"priorPriceTarget":0.0},{"epochGradeDate":1635196111,"firm":"Stifel","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Announces","currentPriceTarget":3200.0,"priorPriceTarget":0.0},{"epochGradeDate":1634820684,"firm":"Mizuho","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3100.0,"priorPriceTarget":3000.0},{"epochGradeDate":1633078845,"firm":"RBC Capital","toGrade":"Outperform","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":3400.0,"priorPriceTarget":0.0},{"epochGradeDate":1631528477,"firm":"Goldman Sachs","toGrade":"Buy","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":3350.0,"priorPriceTarget":0.0},{"epochGradeDate":1627555987,"firm":"Argus Research","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":3100.0,"priorPriceTarget":2800.0},{"epochGradeDate":1627508854,"firm":"Wolfe Research","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":3400.0,"priorPriceTarget":2900.0},{"epochGradeDate":1627499705,"firm":"MKM Partners","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":3150.0,"priorPriceTarget":2500.0},{"epochGradeDate":1627497815,"firm":"Bernstein","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":3200.0,"priorPriceTarget":3000.0},{"epochGradeDate":1627495639,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":3100.0,"priorPriceTarget":2850.0},{"epochGradeDate":1627485337,"firm":"Canaccord Genuity","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3100.0,"priorPriceTarget":2800.0},{"epochGradeDate":1627484999,"firm":"JP Morgan","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3250.0,"priorPriceTarget":2875.0},{"epochGradeDate":1627484787,"firm":"Jefferies","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3150.0,"priorPriceTarget":2950.0},{"epochGradeDate":1627482572,"firm":"Mizuho","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3000.0,"priorPriceTarget":2800.0},{"epochGradeDate":1627480325,"firm":"Raymond James","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":3200.0,"priorPriceTarget":2750.0},{"epochGradeDate":1627478313,"firm":"Stifel","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3000.0,"priorPriceTarget":2700.0},{"epochGradeDate":1627475991,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":3034.0,"priorPriceTarget":2635.0},{"epochGradeDate":1627475773,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3400.0,"priorPriceTarget":3350.0},{"epochGradeDate":1627475457,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3000.0,"priorPriceTarget":2575.0},{"epochGradeDate":1627475227,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3100.0,"priorPriceTarget":2800.0},{"epochGradeDate":1627474894,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":3150.0,"priorPriceTarget":2755.0},{"epochGradeDate":1627474275,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":3000.0,"priorPriceTarget":2510.0},{"epochGradeDate":1627474092,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":3000.0,"priorPriceTarget":2700.0},{"epochGradeDate":1627473859,"firm":"Susquehanna","toGrade":"Positive","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3600.0,"priorPriceTarget":3100.0},{"epochGradeDate":1627470373,"firm":"Barclays","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3200.0,"priorPriceTarget":3000.0},{"epochGradeDate":1627469058,"firm":"Needham","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3200.0,"priorPriceTarget":2700.0},{"epochGradeDate":1627461362,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3071.0,"priorPriceTarget":2681.0},{"epochGradeDate":1627039534,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3350.0,"priorPriceTarget":2755.0},{"epochGradeDate":1626785077,"firm":"Jefferies","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2950.0,"priorPriceTarget":2850.0},{"epochGradeDate":1623443858,"firm":"Tigress Financial","toGrade":"Strong Buy","fromGrade":"Strong Buy","action":"main","priceTargetAction":"Announces","currentPriceTarget":3185.0,"priorPriceTarget":0.0},{"epochGradeDate":1622636846,"firm":"KGI Securities","toGrade":"Outperform","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":3000.0,"priorPriceTarget":0.0},{"epochGradeDate":1620650120,"firm":"Citigroup","toGrade":"Neutral","fromGrade":"Buy","action":"down","priceTargetAction":"Announces","currentPriceTarget":2415.0,"priorPriceTarget":0.0},{"epochGradeDate":1620049436,"firm":"China Renaissance","toGrade":"Buy","fromGrade":"Hold","action":"up","priceTargetAction":"Raises","currentPriceTarget":3000.0,"priorPriceTarget":1477.0},{"epochGradeDate":1619705276,"firm":"Argus Research","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2800.0,"priorPriceTarget":2500.0},{"epochGradeDate":1619643665,"firm":"Stifel","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":2700.0,"priorPriceTarget":2350.0},{"epochGradeDate":1619636535,"firm":"Evercore ISI Group","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":2825.0,"priorPriceTarget":2525.0},{"epochGradeDate":1619632311,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":2700.0,"priorPriceTarget":2600.0},{"epochGradeDate":1619625335,"firm":"Susquehanna","toGrade":"Positive","fromGrade":"Positive","action":"main","priceTargetAction":"Raises","currentPriceTarget":3100.0,"priorPriceTarget":3000.0},{"epochGradeDate":1619625005,"firm":"Jefferies","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2850.0,"priorPriceTarget":2700.0},{"epochGradeDate":1619618611,"firm":"Canaccord Genuity","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2800.0,"priorPriceTarget":2600.0},{"epochGradeDate":1619617866,"firm":"Raymond James","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":2750.0,"priorPriceTarget":2440.0},{"epochGradeDate":1619616717,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":2755.0,"priorPriceTarget":2500.0},{"epochGradeDate":1619614854,"firm":"Mizuho","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2800.0,"priorPriceTarget":2600.0},{"epochGradeDate":1619614748,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2575.0,"priorPriceTarget":2350.0},{"epochGradeDate":1619613478,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":2635.0,"priorPriceTarget":2250.0},{"epochGradeDate":1619612331,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":2755.0,"priorPriceTarget":2440.0},{"epochGradeDate":1619611842,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2850.0,"priorPriceTarget":2400.0},{"epochGradeDate":1619611820,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":2510.0,"priorPriceTarget":2350.0},{"epochGradeDate":1619611760,"firm":"Wells Fargo","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2850.0,"priorPriceTarget":2650.0},{"epochGradeDate":1619608147,"firm":"Barclays","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":3000.0,"priorPriceTarget":2500.0},{"epochGradeDate":1619606030,"firm":"Needham","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2700.0,"priorPriceTarget":2500.0},{"epochGradeDate":1619603162,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2681.0,"priorPriceTarget":2625.0},{"epochGradeDate":1619447553,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2400.0,"priorPriceTarget":2250.0},{"epochGradeDate":1619203103,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":2500.0,"priorPriceTarget":2360.0},{"epochGradeDate":1619190608,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2600.0,"priorPriceTarget":2350.0},{"epochGradeDate":1619101796,"firm":"Jefferies","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2700.0,"priorPriceTarget":2400.0},{"epochGradeDate":1618913716,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2625.0,"priorPriceTarget":2353.0},{"epochGradeDate":1618850680,"firm":"Mizuho","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2600.0,"priorPriceTarget":2350.0},{"epochGradeDate":1618835237,"firm":"Canaccord Genuity","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":2600.0,"priorPriceTarget":2400.0},{"epochGradeDate":1618834150,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":2350.0,"priorPriceTarget":2250.0},{"epochGradeDate":1618492953,"firm":"TD Cowen","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":2600.0,"priorPriceTarget":2400.0},{"epochGradeDate":1617973193,"firm":"Argus Research","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2500.0,"priorPriceTarget":2400.0},{"epochGradeDate":1617276326,"firm":"Wolfe Research","toGrade":"Outperform","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":2450.0,"priorPriceTarget":0.0},{"epochGradeDate":1617099460,"firm":"Stifel","toGrade":"Buy","fromGrade":"Hold","action":"up","priceTargetAction":"Raises","currentPriceTarget":2350.0,"priorPriceTarget":2025.0},{"epochGradeDate":1616672788,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2350.0,"priorPriceTarget":2200.0},{"epochGradeDate":1616156181,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":2440.0,"priorPriceTarget":2440.0},{"epochGradeDate":1613493698,"firm":"Loop Capital","toGrade":"Buy","fromGrade":"Hold","action":"up","priceTargetAction":"Raises","currentPriceTarget":2525.0,"priorPriceTarget":1895.0},{"epochGradeDate":1613137752,"firm":"Citigroup","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":2415.0,"priorPriceTarget":2000.0},{"epochGradeDate":1612389275,"firm":"Stifel","toGrade":"Hold","fromGrade":"Hold","action":"main","priceTargetAction":"Raises","currentPriceTarget":2025.0,"priorPriceTarget":1700.0},{"epochGradeDate":1612381202,"firm":"Bernstein","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":2500.0,"priorPriceTarget":2200.0},{"epochGradeDate":1612377393,"firm":"Pivotal Research","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2750.0,"priorPriceTarget":2100.0},{"epochGradeDate":1612377213,"firm":"Jefferies","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2400.0,"priorPriceTarget":2150.0},{"epochGradeDate":1612376310,"firm":"UBS","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2300.0,"priorPriceTarget":2050.0},{"epochGradeDate":1612375584,"firm":"Barclays","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2500.0,"priorPriceTarget":1900.0},{"epochGradeDate":1612375134,"firm":"Deutsche Bank","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2600.0,"priorPriceTarget":2250.0},{"epochGradeDate":1612374575,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2250.0,"priorPriceTarget":2100.0},{"epochGradeDate":1612373885,"firm":"Cowen & Co.","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2400.0,"priorPriceTarget":2200.0},{"epochGradeDate":1612371764,"firm":"Monness, Crespi, Hardt","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2500.0,"priorPriceTarget":2000.0},{"epochGradeDate":1612371449,"firm":"Evercore ISI Group","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2400.0,"priorPriceTarget":1875.0},{"epochGradeDate":1612371380,"firm":"MKM Partners","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2200.0,"priorPriceTarget":1950.0},{"epochGradeDate":1612369379,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2470.0,"priorPriceTarget":2150.0},{"epochGradeDate":1612364881,"firm":"Canaccord Genuity","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2400.0,"priorPriceTarget":2250.0},{"epochGradeDate":1612363796,"firm":"Raymond James","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":2440.0,"priorPriceTarget":1800.0},{"epochGradeDate":1612360569,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2360.0,"priorPriceTarget":2000.0},{"epochGradeDate":1612358365,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2200.0,"priorPriceTarget":2050.0},{"epochGradeDate":1612357234,"firm":"JP Morgan","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":2390.0,"priorPriceTarget":2050.0},{"epochGradeDate":1612352241,"firm":"Needham","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2500.0,"priorPriceTarget":1800.0},{"epochGradeDate":1612348217,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2353.0,"priorPriceTarget":2060.0},{"epochGradeDate":1612199092,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2100.0,"priorPriceTarget":2000.0},{"epochGradeDate":1611861933,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2150.0,"priorPriceTarget":1850.0},{"epochGradeDate":1611857109,"firm":"Wells Fargo","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2100.0,"priorPriceTarget":1800.0},{"epochGradeDate":1611590626,"firm":"Canaccord Genuity","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2250.0,"priorPriceTarget":2050.0},{"epochGradeDate":1611589756,"firm":"Mizuho","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2100.0,"priorPriceTarget":1810.0},{"epochGradeDate":1611326038,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":2000.0,"priorPriceTarget":1950.0},{"epochGradeDate":1611227546,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":2056.0,"priorPriceTarget":0.0},{"epochGradeDate":1611052038,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2060.0,"priorPriceTarget":1970.0},{"epochGradeDate":1610389849,"firm":"Jefferies","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":2150.0,"priorPriceTarget":2000.0},{"epochGradeDate":1609855646,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2050.0,"priorPriceTarget":1880.0},{"epochGradeDate":1609432880,"firm":"Baird","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Announces","currentPriceTarget":2000.0,"priorPriceTarget":0.0},{"epochGradeDate":1608297051,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":2000.0,"priorPriceTarget":2000.0},{"epochGradeDate":1604420007,"firm":"Citigroup","toGrade":"Neutral","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2000.0,"priorPriceTarget":1600.0},{"epochGradeDate":1604335860,"firm":"Canaccord Genuity","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1900.0,"priorPriceTarget":1800.0},{"epochGradeDate":1604318325,"firm":"Argus Research","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":1850.0,"priorPriceTarget":1620.0},{"epochGradeDate":1604093977,"firm":"Truist Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":2000.0,"priorPriceTarget":1850.0},{"epochGradeDate":1604092959,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1850.0,"priorPriceTarget":1700.0},{"epochGradeDate":1604090907,"firm":"Stifel","toGrade":"Hold","fromGrade":"Hold","action":"main","priceTargetAction":"Raises","currentPriceTarget":1700.0,"priorPriceTarget":1600.0},{"epochGradeDate":1604085141,"firm":"MKM Partners","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":1950.0,"priorPriceTarget":1700.0},{"epochGradeDate":1604081071,"firm":"Barclays","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":1900.0,"priorPriceTarget":1800.0},{"epochGradeDate":1604080109,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1950.0,"priorPriceTarget":1900.0},{"epochGradeDate":1604077796,"firm":"Deutsche Bank","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2250.0,"priorPriceTarget":2020.0},{"epochGradeDate":1604072958,"firm":"Susquehanna","toGrade":"Positive","fromGrade":"Positive","action":"main","priceTargetAction":"Raises","currentPriceTarget":2000.0,"priorPriceTarget":1850.0},{"epochGradeDate":1604064910,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1950.0,"priorPriceTarget":1850.0},{"epochGradeDate":1604064536,"firm":"Mizuho","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":1810.0,"priorPriceTarget":1750.0},{"epochGradeDate":1604064429,"firm":"Raymond James","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1800.0,"priorPriceTarget":1700.0},{"epochGradeDate":1604060170,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1880.0,"priorPriceTarget":1800.0},{"epochGradeDate":1604059195,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":2000.0,"priorPriceTarget":1850.0},{"epochGradeDate":1604047530,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1970.0,"priorPriceTarget":1955.0},{"epochGradeDate":1603475063,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Announces","currentPriceTarget":1900.0,"priorPriceTarget":0.0},{"epochGradeDate":1603196702,"firm":"Jefferies","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1850.0,"priorPriceTarget":1800.0},{"epochGradeDate":1602873438,"firm":"Bernstein","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Maintains","currentPriceTarget":1800.0,"priorPriceTarget":1800.0},{"epochGradeDate":1602505546,"firm":"Deutsche Bank","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":2020.0,"priorPriceTarget":1975.0},{"epochGradeDate":1600947332,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1800.0,"priorPriceTarget":1760.0},{"epochGradeDate":1600861944,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":1850.0,"priorPriceTarget":1850.0},{"epochGradeDate":1600159906,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":1955.0,"priorPriceTarget":0.0},{"epochGradeDate":1598370100,"firm":"UBS","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":1970.0,"priorPriceTarget":1600.0},{"epochGradeDate":1596470029,"firm":"JMP Securities","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1700.0,"priorPriceTarget":1500.0},{"epochGradeDate":1596230384,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1700.0,"priorPriceTarget":1550.0},{"epochGradeDate":1596210385,"firm":"Baird","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1675.0,"priorPriceTarget":1650.0},{"epochGradeDate":1596210293,"firm":"Susquehanna","toGrade":"Positive","fromGrade":"Positive","action":"main","priceTargetAction":"Raises","currentPriceTarget":1850.0,"priorPriceTarget":1550.0},{"epochGradeDate":1596204559,"firm":"Canaccord Genuity","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1800.0,"priorPriceTarget":1700.0},{"epochGradeDate":1596200118,"firm":"Raymond James","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1700.0,"priorPriceTarget":1425.0},{"epochGradeDate":1596196168,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Announces","currentPriceTarget":1730.0,"priorPriceTarget":0.0},{"epochGradeDate":1596195620,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1760.0,"priorPriceTarget":1700.0},{"epochGradeDate":1596044440,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1750.0,"priorPriceTarget":1550.0},{"epochGradeDate":1595946988,"firm":"Deutsche Bank","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":1975.0,"priorPriceTarget":1700.0},{"epochGradeDate":1595876959,"firm":"MKM Partners","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":1700.0,"priorPriceTarget":1500.0},{"epochGradeDate":1595330259,"firm":"Mizuho","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1750.0,"priorPriceTarget":1650.0},{"epochGradeDate":1595248233,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1850.0,"priorPriceTarget":1600.0},{"epochGradeDate":1595245542,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":1610.0,"priorPriceTarget":1610.0},{"epochGradeDate":1594935192,"firm":"SunTrust Robinson Humphrey","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":1805.0,"priorPriceTarget":1550.0},{"epochGradeDate":1594924381,"firm":"Bernstein","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1750.0,"priorPriceTarget":1375.0},{"epochGradeDate":1594663261,"firm":"Barclays","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":1600.0,"priorPriceTarget":1400.0},{"epochGradeDate":1594657040,"firm":"Jefferies","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":1800.0,"priorPriceTarget":1450.0},{"epochGradeDate":1594637097,"firm":"Mizuho","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1650.0,"priorPriceTarget":1560.0},{"epochGradeDate":1594297468,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1700.0,"priorPriceTarget":1400.0},{"epochGradeDate":1593007168,"firm":"Goldman Sachs","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1775.0,"priorPriceTarget":1425.0},{"epochGradeDate":1592826054,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":1610.0,"priorPriceTarget":1610.0},{"epochGradeDate":1591789063,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":1610.0,"priorPriceTarget":1420.0},{"epochGradeDate":1589299919,"firm":"Citigroup","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1600.0,"priorPriceTarget":1400.0},{"epochGradeDate":1588689464,"firm":"Deutsche Bank","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":1700.0,"priorPriceTarget":1625.0},{"epochGradeDate":1588177649,"firm":"SunTrust Robinson Humphrey","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1550.0,"priorPriceTarget":1350.0},{"epochGradeDate":1588177302,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1420.0,"priorPriceTarget":1372.0},{"epochGradeDate":1588175886,"firm":"Mizuho","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1560.0,"priorPriceTarget":1500.0},{"epochGradeDate":1588172157,"firm":"Nomura Instinet","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":1700.0,"priorPriceTarget":1680.0},{"epochGradeDate":1588172038,"firm":"Barclays","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1400.0,"priorPriceTarget":1300.0},{"epochGradeDate":1588171651,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1550.0,"priorPriceTarget":1400.0},{"epochGradeDate":1588166882,"firm":"Stifel","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1400.0,"priorPriceTarget":1300.0},{"epochGradeDate":1588166822,"firm":"RBC Capital","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1500.0,"priorPriceTarget":1350.0},{"epochGradeDate":1588162390,"firm":"MKM Partners","toGrade":"Buy","fromGrade":"","action":"reit","priceTargetAction":"Raises","currentPriceTarget":1500.0,"priorPriceTarget":1400.0},{"epochGradeDate":1588160455,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1400.0,"priorPriceTarget":1310.0},{"epochGradeDate":1588157741,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1600.0,"priorPriceTarget":1500.0},{"epochGradeDate":1588157479,"firm":"Nomura","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1700.0,"priorPriceTarget":1680.0},{"epochGradeDate":1588157021,"firm":"JP Morgan","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1505.0,"priorPriceTarget":1340.0},{"epochGradeDate":1588087283,"firm":"Pivotal Research","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":1575.0,"priorPriceTarget":1425.0},{"epochGradeDate":1587568222,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1375.0,"priorPriceTarget":1625.0},{"epochGradeDate":1587494820,"firm":"Deutsche Bank","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1515.0,"priorPriceTarget":1700.0},{"epochGradeDate":1587483031,"firm":"Raymond James","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1425.0,"priorPriceTarget":1580.0},{"epochGradeDate":1587389835,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1500.0,"priorPriceTarget":1700.0},{"epochGradeDate":1587137154,"firm":"Mizuho","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1500.0,"priorPriceTarget":1620.0},{"epochGradeDate":1587125272,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1445.0,"priorPriceTarget":1465.0},{"epochGradeDate":1586281561,"firm":"Barclays","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1300.0,"priorPriceTarget":1635.0},{"epochGradeDate":1585591234,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1350.0,"priorPriceTarget":1550.0},{"epochGradeDate":1585578613,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1310.0,"priorPriceTarget":1650.0},{"epochGradeDate":1585574828,"firm":"UBS","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1530.0,"priorPriceTarget":1675.0},{"epochGradeDate":1585566004,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Market Perform","action":"up","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1585227929,"firm":"SunTrust Robinson Humphrey","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1350.0,"priorPriceTarget":1600.0},{"epochGradeDate":1584532917,"firm":"JP Morgan","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1340.0,"priorPriceTarget":1535.0},{"epochGradeDate":1583411357,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Announces","currentPriceTarget":1600.0,"priorPriceTarget":0.0},{"epochGradeDate":1582641233,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1650.0,"priorPriceTarget":1560.0},{"epochGradeDate":1581091294,"firm":"Citigroup","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1700.0,"priorPriceTarget":1500.0},{"epochGradeDate":1580996530,"firm":"CFRA","toGrade":"Strong Buy","fromGrade":"Strong Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":1761.0,"priorPriceTarget":1737.0},{"epochGradeDate":1580906746,"firm":"Argus Research","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1620.0,"priorPriceTarget":1450.0},{"epochGradeDate":1580853051,"firm":"SunTrust Robinson Humphrey","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":1600.0,"priorPriceTarget":1500.0},{"epochGradeDate":1580842538,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1550.0,"priorPriceTarget":1450.0},{"epochGradeDate":1580841400,"firm":"BMO Capital","toGrade":"Market Perform","fromGrade":"Market Perform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1400.0,"priorPriceTarget":1350.0},{"epochGradeDate":1580840711,"firm":"Baird","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1600.0,"priorPriceTarget":1400.0},{"epochGradeDate":1580834235,"firm":"Susquehanna","toGrade":"Positive","fromGrade":"Positive","action":"main","priceTargetAction":"Raises","currentPriceTarget":1800.0,"priorPriceTarget":1550.0},{"epochGradeDate":1580832415,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1620.0,"priorPriceTarget":1530.0},{"epochGradeDate":1580831757,"firm":"Nomura Instinet","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":1680.0,"priorPriceTarget":1560.0},{"epochGradeDate":1580830783,"firm":"Deutsche Bank","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1700.0,"priorPriceTarget":1735.0},{"epochGradeDate":1580824424,"firm":"Raymond James","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1580.0,"priorPriceTarget":1475.0},{"epochGradeDate":1580823328,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":1600.0,"priorPriceTarget":1500.0},{"epochGradeDate":1580819153,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Maintains","currentPriceTarget":1620.0,"priorPriceTarget":1620.0},{"epochGradeDate":1580816508,"firm":"Needham","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1800.0,"priorPriceTarget":1350.0},{"epochGradeDate":1580815372,"firm":"Nomura","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1680.0,"priorPriceTarget":1560.0},{"epochGradeDate":1580815098,"firm":"Pivotal Research","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1700.0,"priorPriceTarget":1650.0},{"epochGradeDate":1580489872,"firm":"BMO Capital","toGrade":"Market Perform","fromGrade":"Market Perform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1350.0,"priorPriceTarget":1250.0},{"epochGradeDate":1580422506,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1625.0,"priorPriceTarget":1500.0},{"epochGradeDate":1580127605,"firm":"Mizuho","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1650.0,"priorPriceTarget":1450.0},{"epochGradeDate":1579864168,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1769.0,"priorPriceTarget":1546.0},{"epochGradeDate":1579612909,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1560.0,"priorPriceTarget":1450.0},{"epochGradeDate":1579266164,"firm":"UBS","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1675.0,"priorPriceTarget":1460.0},{"epochGradeDate":1578926995,"firm":"Evercore ISI Group","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1600.0,"priorPriceTarget":1350.0},{"epochGradeDate":1578660110,"firm":"Bernstein","toGrade":"Outperform","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":1600.0,"priorPriceTarget":0.0},{"epochGradeDate":1578318515,"firm":"Pivotal Research","toGrade":"Buy","fromGrade":"Hold","action":"up","priceTargetAction":"Raises","currentPriceTarget":1650.0,"priorPriceTarget":1445.0},{"epochGradeDate":1575548545,"firm":"Stifel","toGrade":"Buy","fromGrade":"Hold","action":"up","priceTargetAction":"Raises","currentPriceTarget":1525.0,"priorPriceTarget":1325.0},{"epochGradeDate":1575372532,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":1500.0,"priorPriceTarget":0.0},{"epochGradeDate":1572360058,"firm":"BMO Capital","toGrade":"Market Perform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1250.0,"priorPriceTarget":1245.0},{"epochGradeDate":1572359371,"firm":"Barclays","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1400.0,"priorPriceTarget":1385.0},{"epochGradeDate":1572358923,"firm":"Canaccord Genuity","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1450.0,"priorPriceTarget":1350.0},{"epochGradeDate":1572343495,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1546.0,"priorPriceTarget":1516.0},{"epochGradeDate":1572006717,"firm":"BMO Capital","toGrade":"Market Perform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1245.0,"priorPriceTarget":1225.0},{"epochGradeDate":1564146288,"firm":"Stifel","toGrade":"Hold","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1299.0,"priorPriceTarget":1287.0},{"epochGradeDate":1564146033,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1500.0,"priorPriceTarget":1350.0},{"epochGradeDate":1564145863,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1500.0,"priorPriceTarget":1400.0},{"epochGradeDate":1564142673,"firm":"Mizuho","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1400.0,"priorPriceTarget":1350.0},{"epochGradeDate":1564141688,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1450.0,"priorPriceTarget":1400.0},{"epochGradeDate":1564141523,"firm":"Canaccord Genuity","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1350.0,"priorPriceTarget":1250.0},{"epochGradeDate":1564139283,"firm":"Citigroup","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1450.0,"priorPriceTarget":1325.0},{"epochGradeDate":1564139061,"firm":"Nomura","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1400.0,"priorPriceTarget":1300.0},{"epochGradeDate":1564135622,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1516.0,"priorPriceTarget":1430.0},{"epochGradeDate":1563194153,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1400.0,"priorPriceTarget":1425.0},{"epochGradeDate":1559645972,"firm":"Loop Capital","toGrade":"Hold","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":1250.0,"priorPriceTarget":0.0},{"epochGradeDate":1559137370,"firm":"China Renaissance","toGrade":"Hold","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":1270.0,"priorPriceTarget":0.0},{"epochGradeDate":1559132173,"firm":"Pivotal Research","toGrade":"Hold","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":1250.0,"priorPriceTarget":0.0},{"epochGradeDate":1556706016,"firm":"DZ Bank","toGrade":"Hold","fromGrade":"Buy","action":"down","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1556625672,"firm":"Nomura","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1300.0,"priorPriceTarget":1310.0},{"epochGradeDate":1556625522,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1425.0,"priorPriceTarget":1500.0},{"epochGradeDate":1556620198,"firm":"Stifel","toGrade":"Hold","fromGrade":"Buy","action":"down","priceTargetAction":"Announces","currentPriceTarget":1287.0,"priorPriceTarget":0.0},{"epochGradeDate":1556203582,"firm":"BMO Capital","toGrade":"Market Perform","fromGrade":"Market Perform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1200.0,"priorPriceTarget":1100.0},{"epochGradeDate":1551789628,"firm":"Needham","toGrade":"Buy","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":1350.0,"priorPriceTarget":0.0},{"epochGradeDate":1549371872,"firm":"Citigroup","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1325.0,"priorPriceTarget":1350.0},{"epochGradeDate":1549370976,"firm":"BMO Capital","toGrade":"Market Perform","fromGrade":"Market Perform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1100.0,"priorPriceTarget":1135.0},{"epochGradeDate":1549369905,"firm":"Nomura","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1310.0,"priorPriceTarget":1350.0},{"epochGradeDate":1549369733,"firm":"Barclays","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1350.0,"priorPriceTarget":1400.0},{"epochGradeDate":1546861373,"firm":"Pivotal Research","toGrade":"Buy","fromGrade":"Hold","action":"up","priceTargetAction":"Raises","currentPriceTarget":1240.0,"priorPriceTarget":1010.0},{"epochGradeDate":1546526564,"firm":"Canaccord Genuity","toGrade":"Buy","fromGrade":"Hold","action":"up","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1544100719,"firm":"Guggenheim","toGrade":"Buy","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":1330.0,"priorPriceTarget":0.0},{"epochGradeDate":1542199412,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1500.0,"priorPriceTarget":1515.0},{"epochGradeDate":1541168326,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1375.0,"priorPriceTarget":1390.0},{"epochGradeDate":1540563422,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1350.0,"priorPriceTarget":1390.0},{"epochGradeDate":1540561778,"firm":"Barclays","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1400.0,"priorPriceTarget":1415.0},{"epochGradeDate":1540561777,"firm":"Canaccord Genuity","toGrade":"Hold","fromGrade":"Hold","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1140.0,"priorPriceTarget":1170.0},{"epochGradeDate":1539859498,"firm":"Wedbush","toGrade":"Outperform","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":1350.0,"priorPriceTarget":0.0},{"epochGradeDate":1535541426,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":1515.0,"priorPriceTarget":1325.0},{"epochGradeDate":1532444162,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":1390.0,"priorPriceTarget":1270.0},{"epochGradeDate":1532439844,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1375.0,"priorPriceTarget":1330.0},{"epochGradeDate":1532438638,"firm":"BMO Capital","toGrade":"Market Perform","fromGrade":"Market Perform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1150.0,"priorPriceTarget":1100.0},{"epochGradeDate":1532434098,"firm":"B. Riley Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":1475.0,"priorPriceTarget":1350.0},{"epochGradeDate":1532433363,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":1325.0,"priorPriceTarget":1250.0},{"epochGradeDate":1532433362,"firm":"Barclays","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":1415.0,"priorPriceTarget":1350.0},{"epochGradeDate":1532432628,"firm":"Canaccord Genuity","toGrade":"Hold","fromGrade":"Hold","action":"main","priceTargetAction":"Raises","currentPriceTarget":1170.0,"priorPriceTarget":1050.0},{"epochGradeDate":1532431718,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"Market Outperform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1390.0,"priorPriceTarget":1235.0},{"epochGradeDate":1532431717,"firm":"Stifel","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":1456.0,"priorPriceTarget":1234.0},{"epochGradeDate":1531744799,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1330.0,"priorPriceTarget":1350.0},{"epochGradeDate":1531499640,"firm":"Barclays","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":1350.0,"priorPriceTarget":1250.0},{"epochGradeDate":1531309613,"firm":"Nomura","toGrade":"Buy","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":1400.0,"priorPriceTarget":0.0},{"epochGradeDate":1530202399,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":1250.0,"priorPriceTarget":1200.0},{"epochGradeDate":1524654480,"firm":"Stifel","toGrade":"Buy","fromGrade":"Hold","action":"up","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":1234.0},{"epochGradeDate":1524585318,"firm":"Barclays","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1250.0,"priorPriceTarget":1330.0},{"epochGradeDate":1524574733,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":1200.0,"priorPriceTarget":1175.0},{"epochGradeDate":1524568236,"firm":"Stifel","toGrade":"Hold","fromGrade":"Hold","action":"main","priceTargetAction":"Raises","currentPriceTarget":1234.0,"priorPriceTarget":1150.0},{"epochGradeDate":1524560673,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1230.0,"priorPriceTarget":1280.0},{"epochGradeDate":1524489368,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1350.0,"priorPriceTarget":1400.0},{"epochGradeDate":1524227281,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"Outperform","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1350.0,"priorPriceTarget":1400.0},{"epochGradeDate":1523369166,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1175.0,"priorPriceTarget":1200.0},{"epochGradeDate":1517582492,"firm":"Barclays","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":1330.0,"priorPriceTarget":1260.0},{"epochGradeDate":1517582491,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Buy","action":"main","priceTargetAction":"Raises","currentPriceTarget":1360.0,"priorPriceTarget":1340.0},{"epochGradeDate":1517582490,"firm":"BMO Capital","toGrade":"Market Perform","fromGrade":"Market Perform","action":"main","priceTargetAction":"Raises","currentPriceTarget":1100.0,"priorPriceTarget":1080.0},{"epochGradeDate":1517580465,"firm":"Morgan Stanley","toGrade":"Outperform","fromGrade":"Overweight","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1200.0,"priorPriceTarget":1215.0},{"epochGradeDate":1517572236,"firm":"Stifel","toGrade":"Hold","fromGrade":"Buy","action":"down","priceTargetAction":"Maintains","currentPriceTarget":1150.0,"priorPriceTarget":1150.0},{"epochGradeDate":1516995723,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"Overweight","action":"main","priceTargetAction":"Raises","currentPriceTarget":1215.0,"priorPriceTarget":1210.0},{"epochGradeDate":1512568798,"firm":"Evercore ISI Group","toGrade":"Outperform","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":1230.0,"priorPriceTarget":0.0},{"epochGradeDate":1509377832,"firm":"Citigroup","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1200.0,"priorPriceTarget":1180.0},{"epochGradeDate":1509127671,"firm":"Canaccord Genuity","toGrade":"Hold","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1050.0,"priorPriceTarget":1000.0},{"epochGradeDate":1509124370,"firm":"BMO Capital","toGrade":"Market Perform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1000.0,"priorPriceTarget":970.0},{"epochGradeDate":1509108854,"firm":"Citigroup","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1200.0,"priorPriceTarget":1180.0},{"epochGradeDate":1509106019,"firm":"Stifel","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1150.0,"priorPriceTarget":1075.0},{"epochGradeDate":1507732398,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1350.0,"priorPriceTarget":1100.0},{"epochGradeDate":1506523528,"firm":"Wells Fargo","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1250.0,"priorPriceTarget":1150.0},{"epochGradeDate":1502879285,"firm":"SunTrust Robinson Humphrey","toGrade":"Buy","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":1100.0,"priorPriceTarget":0.0},{"epochGradeDate":1500996631,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1040.0,"priorPriceTarget":1050.0},{"epochGradeDate":1500996110,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":1100.0,"priorPriceTarget":1035.0},{"epochGradeDate":1497522624,"firm":"Canaccord Genuity","toGrade":"Hold","fromGrade":"Buy","action":"down","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1491302768,"firm":"BMO Capital","toGrade":"Market Perform","fromGrade":"Outperform","action":"down","priceTargetAction":"Lowers","currentPriceTarget":880.0,"priorPriceTarget":1005.0},{"epochGradeDate":1490958959,"firm":"Loop Capital","toGrade":"Hold","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":800.0,"priorPriceTarget":0.0},{"epochGradeDate":1490736011,"firm":"Barclays","toGrade":"Overweight","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":1065.0,"priorPriceTarget":0.0},{"epochGradeDate":1490610020,"firm":"Nomura","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":925.0,"priorPriceTarget":950.0},{"epochGradeDate":1490015851,"firm":"Pivotal Research","toGrade":"Hold","fromGrade":"Buy","action":"down","priceTargetAction":"Lowers","currentPriceTarget":950.0,"priorPriceTarget":970.0},{"epochGradeDate":1482324533,"firm":"Aegis Capital","toGrade":"Buy","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":980.0,"priorPriceTarget":0.0},{"epochGradeDate":1476676800,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1475007249,"firm":"Wedbush","toGrade":"Underperform","fromGrade":"Neutral","action":"down","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1469803392,"firm":"Citigroup","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":910.0,"priorPriceTarget":900.0},{"epochGradeDate":1469792420,"firm":"Jefferies","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1469792098,"firm":"CLSA","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":990.0,"priorPriceTarget":970.0},{"epochGradeDate":1469791303,"firm":"Deutsche Bank","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":1050.0,"priorPriceTarget":1100.0},{"epochGradeDate":1469790990,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":928.0,"priorPriceTarget":904.0},{"epochGradeDate":1469790306,"firm":"SunTrust Robinson Humphrey","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":900.0,"priorPriceTarget":850.0},{"epochGradeDate":1469788972,"firm":"UBS","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":925.0,"priorPriceTarget":880.0},{"epochGradeDate":1469780203,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":940.0,"priorPriceTarget":920.0},{"epochGradeDate":1467203741,"firm":"Goldman Sachs","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":810.0,"priorPriceTarget":850.0},{"epochGradeDate":1465373967,"firm":"Maxim Group","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":850.0,"priorPriceTarget":865.0},{"epochGradeDate":1461330845,"firm":"Baird","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":860.0,"priorPriceTarget":880.0},{"epochGradeDate":1461329653,"firm":"Macquarie","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":890.0,"priorPriceTarget":870.0},{"epochGradeDate":1461327751,"firm":"B. Riley Securities","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1461327274,"firm":"Jefferies","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":925.0,"priorPriceTarget":950.0},{"epochGradeDate":1461327237,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":904.0,"priorPriceTarget":916.0},{"epochGradeDate":1461326485,"firm":"Cantor Fitzgerald","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":940.0,"priorPriceTarget":975.0},{"epochGradeDate":1461326304,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":958.0,"priorPriceTarget":965.0},{"epochGradeDate":1461325763,"firm":"Stifel","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":888.0,"priorPriceTarget":930.0},{"epochGradeDate":1461322928,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":850.0,"priorPriceTarget":900.0},{"epochGradeDate":1461322777,"firm":"SunTrust Robinson Humphrey","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":850.0,"priorPriceTarget":875.0},{"epochGradeDate":1460973466,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":920.0,"priorPriceTarget":930.0},{"epochGradeDate":1460368781,"firm":"Pivotal Research","toGrade":"Buy","fromGrade":"Hold","action":"up","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1460018441,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":900.0,"priorPriceTarget":880.0},{"epochGradeDate":1459507158,"firm":"Citigroup","toGrade":"Neutral","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":900.0,"priorPriceTarget":924.0},{"epochGradeDate":1454440278,"firm":"Nomura","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":925.0,"priorPriceTarget":900.0},{"epochGradeDate":1454439994,"firm":"Keybanc","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":910.0,"priorPriceTarget":850.0},{"epochGradeDate":1454439803,"firm":"Cantor Fitzgerald","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":975.0,"priorPriceTarget":880.0},{"epochGradeDate":1454432493,"firm":"Stifel","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":930.0,"priorPriceTarget":900.0},{"epochGradeDate":1454428978,"firm":"Barclays","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":900.0,"priorPriceTarget":800.0},{"epochGradeDate":1452590568,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":900.0,"priorPriceTarget":850.0},{"epochGradeDate":1445621882,"firm":"Macquarie","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":770.0,"priorPriceTarget":750.0},{"epochGradeDate":1444379707,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":815.0,"priorPriceTarget":750.0},{"epochGradeDate":1442831194,"firm":"JMP Securities","toGrade":"Market Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":847.0,"priorPriceTarget":720.0},{"epochGradeDate":1440576122,"firm":"Goldman Sachs","toGrade":"Buy","fromGrade":"Neutral","action":"up","priceTargetAction":"Raises","currentPriceTarget":800.0,"priorPriceTarget":660.0},{"epochGradeDate":1439374709,"firm":"Morgan Stanley","toGrade":"Overweight","fromGrade":"","action":"up","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1437398658,"firm":"Argus Research","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1437131733,"firm":"Bernstein","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":800.0,"priorPriceTarget":700.0},{"epochGradeDate":1437128589,"firm":"JMP Securities","toGrade":"Market Perform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":720.0,"priorPriceTarget":625.0},{"epochGradeDate":1437128223,"firm":"Jefferies","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":800.0,"priorPriceTarget":700.0},{"epochGradeDate":1437127885,"firm":"Mizuho","toGrade":"Neutral","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":715.0,"priorPriceTarget":595.0},{"epochGradeDate":1437126955,"firm":"MKM Partners","toGrade":"Buy","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":805.0,"priorPriceTarget":0.0},{"epochGradeDate":1437041909,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"Market Perform","action":"up","priceTargetAction":"Raises","currentPriceTarget":670.0,"priorPriceTarget":570.0},{"epochGradeDate":1436791226,"firm":"Pivotal Research","toGrade":"Hold","fromGrade":"Buy","action":"down","priceTargetAction":"Lowers","currentPriceTarget":570.0,"priorPriceTarget":640.0},{"epochGradeDate":1429880400,"firm":"CRT Capital","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":650.0,"priorPriceTarget":582.0},{"epochGradeDate":1429621200,"firm":"SunTrust Robinson Humphrey","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":650.0,"priorPriceTarget":675.0},{"epochGradeDate":1425647231,"firm":"Citigroup","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":682.0,"priorPriceTarget":629.0},{"epochGradeDate":1425323046,"firm":"B of A Securities","toGrade":"Buy","fromGrade":"Neutral","action":"up","priceTargetAction":"Raises","currentPriceTarget":650.0,"priorPriceTarget":580.0},{"epochGradeDate":1422626400,"firm":"Citigroup","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":629.0,"priorPriceTarget":652.0},{"epochGradeDate":1421244000,"firm":"Credit Suisse","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":700.0,"priorPriceTarget":722.0},{"epochGradeDate":1420714987,"firm":"Stifel","toGrade":"Hold","fromGrade":"Buy","action":"down","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1418792400,"firm":"Pivotal Research","toGrade":"Buy","fromGrade":"Hold","action":"up","priceTargetAction":"","currentPriceTarget":0.0,"priorPriceTarget":0.0},{"epochGradeDate":1338980160,"firm":"JP Morgan","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":670.0,"priorPriceTarget":730.0},{"epochGradeDate":1338977820,"firm":"Deutsche Bank","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Lowers","currentPriceTarget":662.0,"priorPriceTarget":675.0},{"epochGradeDate":1334573520,"firm":"Hilliard Lyons","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":725.0,"priorPriceTarget":685.0},{"epochGradeDate":1334321160,"firm":"BMO Capital","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":755.0,"priorPriceTarget":730.0},{"epochGradeDate":1334315580,"firm":"Benchmark","toGrade":"Hold","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":650.0,"priorPriceTarget":625.0},{"epochGradeDate":1334314260,"firm":"Oppenheimer","toGrade":"Outperform","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":733.0,"priorPriceTarget":725.0},{"epochGradeDate":1334313660,"firm":"JP Morgan","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":730.0,"priorPriceTarget":686.0},{"epochGradeDate":1334313480,"firm":"Goldman Sachs","toGrade":"Neutral","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":640.0,"priorPriceTarget":600.0},{"epochGradeDate":1334313420,"firm":"Piper Sandler","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":730.0,"priorPriceTarget":675.0},{"epochGradeDate":1334312280,"firm":"Jefferies","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":850.0,"priorPriceTarget":825.0},{"epochGradeDate":1334312160,"firm":"Deutsche Bank","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":675.0,"priorPriceTarget":649.0},{"epochGradeDate":1334071920,"firm":"UBS","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":825.0,"priorPriceTarget":800.0},{"epochGradeDate":1333965780,"firm":"Pivotal Research","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":820.0,"priorPriceTarget":806.0},{"epochGradeDate":1333622040,"firm":"Deutsche Bank","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":649.0,"priorPriceTarget":645.0},{"epochGradeDate":1333457100,"firm":"Global Equities Research","toGrade":"Overweight","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":765.0,"priorPriceTarget":700.0},{"epochGradeDate":1332930540,"firm":"Citigroup","toGrade":"Buy","fromGrade":"","action":"main","priceTargetAction":"Raises","currentPriceTarget":750.0,"priorPriceTarget":680.0},{"epochGradeDate":1331753280,"firm":"Oxen Group","toGrade":"Hold","fromGrade":"","action":"init","priceTargetAction":"Announces","currentPriceTarget":618.0,"priorPriceTarget":0.0}],"maxAge":86400}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/esg_api_esgScores_MSFT.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/esg_api_esgScores_MSFT.json new file mode 100644 index 0000000..f6826c2 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/esg_api_esgScores_MSFT.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"esgScores":{"maxAge":86400,"totalEsg":{"raw":13.43,"fmt":"13.4"},"environmentScore":{"raw":3.25,"fmt":"3.3"},"socialScore":{"raw":6.99,"fmt":"7.0"},"governanceScore":{"raw":3.18,"fmt":"3.2"},"ratingYear":2025,"ratingMonth":10,"highestControversy":2.0,"peerCount":582,"esgPerformance":"LAG_PERF","peerGroup":"Software & Services","peerEsgScorePerformance":{"min":6.66,"avg":20.873745704467357,"max":34.15},"peerGovernancePerformance":{"min":0.81,"avg":5.223805668016193,"max":9.97},"peerSocialPerformance":{"min":3.56,"avg":9.788178137651823,"max":16.68},"peerEnvironmentPerformance":{"min":0.05,"avg":3.2981781376518224,"max":7.05},"peerHighestControversyPerformance":{"min":0.0,"avg":0.7336769759450171,"max":4.0},"percentile":null,"environmentPercentile":null,"socialPercentile":null,"governancePercentile":null,"adult":false,"alcoholic":false,"animalTesting":false,"catholic":null,"controversialWeapons":false,"smallArms":false,"furLeather":false,"gambling":false,"gmo":false,"militaryContract":false,"nuclear":false,"pesticides":false,"palmOil":false,"coal":false,"tobacco":false}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_calendarEvents_META.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_calendarEvents_META.json new file mode 100644 index 0000000..9adce0a --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_calendarEvents_META.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"calendarEvents":{"maxAge":1,"earnings":{"earningsDate":[{"raw":1761768000,"fmt":"2025-10-29"}],"earningsCallDate":[{"raw":1761769800,"fmt":"2025-10-29"}],"isEarningsDateEstimate":false,"earningsAverage":{"raw":8.03834,"fmt":"8.04"},"earningsLow":{"raw":7.18,"fmt":"7.18"},"earningsHigh":{"raw":9.0,"fmt":"9.00"},"revenueAverage":{"raw":57577462060,"fmt":"57.58B","longFmt":"57,577,462,060"},"revenueLow":{"raw":56000000000,"fmt":"56B","longFmt":"56,000,000,000"},"revenueHigh":{"raw":59134000000,"fmt":"59.13B","longFmt":"59,134,000,000"}},"exDividendDate":{"raw":1758499200,"fmt":"2025-09-22"},"dividendDate":{"raw":1759104000,"fmt":"2025-09-29"}}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_earnings_AMZN.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_earnings_AMZN.json new file mode 100644 index 0000000..85cf5be --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_earnings_AMZN.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"earnings":{"maxAge":86400,"earningsChart":{"quarterly":[{"date":"3Q2024","actual":{"raw":1.43,"fmt":"1.43"},"estimate":{"raw":1.14242,"fmt":"1.14"},"fiscalQuarter":"3Q2024","calendarQuarter":"3Q2024","difference":"0.29","surprisePct":"25.17"},{"date":"4Q2024","actual":{"raw":1.86,"fmt":"1.86"},"estimate":{"raw":1.48369,"fmt":"1.48"},"fiscalQuarter":"4Q2024","calendarQuarter":"4Q2024","difference":"0.38","surprisePct":"25.36"},{"date":"1Q2025","actual":{"raw":1.59,"fmt":"1.59"},"estimate":{"raw":1.358,"fmt":"1.36"},"fiscalQuarter":"1Q2025","calendarQuarter":"1Q2025","difference":"0.23","surprisePct":"17.08"},{"date":"2Q2025","actual":{"raw":1.68,"fmt":"1.68"},"estimate":{"raw":1.33192,"fmt":"1.33"},"fiscalQuarter":"2Q2025","calendarQuarter":"2Q2025","difference":"0.35","surprisePct":"26.13"}],"currentQuarterEstimate":{"raw":1.55757,"fmt":"1.56"},"currentQuarterEstimateDate":"3Q","currentCalendarQuarter":"3Q2025","currentQuarterEstimateYear":2025,"currentFiscalQuarter":"3Q2025","earningsDate":[{"raw":1761854400,"fmt":"2025-10-30"}],"isEarningsDateEstimate":false},"financialsChart":{"yearly":[{"date":2021,"revenue":{"raw":469822000000,"fmt":"469.82B","longFmt":"469,822,000,000"},"earnings":{"raw":33364000000,"fmt":"33.36B","longFmt":"33,364,000,000"}},{"date":2022,"revenue":{"raw":513983000000,"fmt":"513.98B","longFmt":"513,983,000,000"},"earnings":{"raw":-2722000000,"fmt":"-2.72B","longFmt":"-2,722,000,000"}},{"date":2023,"revenue":{"raw":574785000000,"fmt":"574.78B","longFmt":"574,785,000,000"},"earnings":{"raw":30425000000,"fmt":"30.43B","longFmt":"30,425,000,000"}},{"date":2024,"revenue":{"raw":637959000000,"fmt":"637.96B","longFmt":"637,959,000,000"},"earnings":{"raw":59248000000,"fmt":"59.25B","longFmt":"59,248,000,000"}}],"quarterly":[{"date":"3Q2024","fiscalQuarter":"3Q2024","revenue":{"raw":158877000000,"fmt":"158.88B","longFmt":"158,877,000,000"},"earnings":{"raw":15328000000,"fmt":"15.33B","longFmt":"15,328,000,000"}},{"date":"4Q2024","fiscalQuarter":"4Q2024","revenue":{"raw":187792000000,"fmt":"187.79B","longFmt":"187,792,000,000"},"earnings":{"raw":20004000000,"fmt":"20B","longFmt":"20,004,000,000"}},{"date":"1Q2025","fiscalQuarter":"1Q2025","revenue":{"raw":155667000000,"fmt":"155.67B","longFmt":"155,667,000,000"},"earnings":{"raw":17127000000,"fmt":"17.13B","longFmt":"17,127,000,000"}},{"date":"2Q2025","fiscalQuarter":"2Q2025","revenue":{"raw":167702000000,"fmt":"167.7B","longFmt":"167,702,000,000"},"earnings":{"raw":18164000000,"fmt":"18.16B","longFmt":"18,164,000,000"}}]},"financialCurrency":"USD","defaultMethodology":"gaap"}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_incomeStatementHistoryQuarterly_AAPL.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_incomeStatementHistoryQuarterly_AAPL.json new file mode 100644 index 0000000..4754acc --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_incomeStatementHistoryQuarterly_AAPL.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"incomeStatementHistoryQuarterly":{"incomeStatementHistory":[{"maxAge":1,"endDate":{"raw":1751241600,"fmt":"2025-06-30"},"totalRevenue":{"raw":94036000000,"fmt":"94.04B","longFmt":"94,036,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":23434000000,"fmt":"23.43B","longFmt":"23,434,000,000"},"netIncomeApplicableToCommonShares":{}},{"maxAge":1,"endDate":{"raw":1743379200,"fmt":"2025-03-31"},"totalRevenue":{"raw":95359000000,"fmt":"95.36B","longFmt":"95,359,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":24780000000,"fmt":"24.78B","longFmt":"24,780,000,000"},"netIncomeApplicableToCommonShares":{}},{"maxAge":1,"endDate":{"raw":1735603200,"fmt":"2024-12-31"},"totalRevenue":{"raw":124300000000,"fmt":"124.3B","longFmt":"124,300,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":36330000000,"fmt":"36.33B","longFmt":"36,330,000,000"},"netIncomeApplicableToCommonShares":{}},{"maxAge":1,"endDate":{"raw":1727654400,"fmt":"2024-09-30"},"totalRevenue":{"raw":94930000000,"fmt":"94.93B","longFmt":"94,930,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":14736000000,"fmt":"14.74B","longFmt":"14,736,000,000"},"netIncomeApplicableToCommonShares":{}}],"maxAge":86400}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_incomeStatementHistory_7203.T.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_incomeStatementHistory_7203.T.json new file mode 100644 index 0000000..7d4eb89 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_incomeStatementHistory_7203.T.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"incomeStatementHistory":{"incomeStatementHistory":[{"maxAge":1,"endDate":{"raw":1743379200,"fmt":"2025-03-31"},"totalRevenue":{"raw":48036704000000,"fmt":"48.04T","longFmt":"48,036,704,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":4765086000000,"fmt":"4.77T","longFmt":"4,765,086,000,000"},"netIncomeApplicableToCommonShares":{}},{"maxAge":1,"endDate":{"raw":1711843200,"fmt":"2024-03-31"},"totalRevenue":{"raw":45095325000000,"fmt":"45.1T","longFmt":"45,095,325,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":4944933000000,"fmt":"4.94T","longFmt":"4,944,933,000,000"},"netIncomeApplicableToCommonShares":{}},{"maxAge":1,"endDate":{"raw":1680220800,"fmt":"2023-03-31"},"totalRevenue":{"raw":37154298000000,"fmt":"37.15T","longFmt":"37,154,298,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":2936379000000,"fmt":"2.94T","longFmt":"2,936,379,000,000"},"netIncomeApplicableToCommonShares":{}},{"maxAge":1,"endDate":{"raw":1648684800,"fmt":"2022-03-31"},"totalRevenue":{"raw":31379507000000,"fmt":"31.38T","longFmt":"31,379,507,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":2850110000000,"fmt":"2.85T","longFmt":"2,850,110,000,000"},"netIncomeApplicableToCommonShares":{}}],"maxAge":86400}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_incomeStatementHistory_AAPL.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_incomeStatementHistory_AAPL.json new file mode 100644 index 0000000..e7c606b --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_incomeStatementHistory_AAPL.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"incomeStatementHistory":{"incomeStatementHistory":[{"maxAge":1,"endDate":{"raw":1727654400,"fmt":"2024-09-30"},"totalRevenue":{"raw":391035000000,"fmt":"391.04B","longFmt":"391,035,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":93736000000,"fmt":"93.74B","longFmt":"93,736,000,000"},"netIncomeApplicableToCommonShares":{}},{"maxAge":1,"endDate":{"raw":1696032000,"fmt":"2023-09-30"},"totalRevenue":{"raw":383285000000,"fmt":"383.29B","longFmt":"383,285,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":96995000000,"fmt":"97B","longFmt":"96,995,000,000"},"netIncomeApplicableToCommonShares":{}},{"maxAge":1,"endDate":{"raw":1664496000,"fmt":"2022-09-30"},"totalRevenue":{"raw":394328000000,"fmt":"394.33B","longFmt":"394,328,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":99803000000,"fmt":"99.8B","longFmt":"99,803,000,000"},"netIncomeApplicableToCommonShares":{}},{"maxAge":1,"endDate":{"raw":1632960000,"fmt":"2021-09-30"},"totalRevenue":{"raw":365817000000,"fmt":"365.82B","longFmt":"365,817,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":94680000000,"fmt":"94.68B","longFmt":"94,680,000,000"},"netIncomeApplicableToCommonShares":{}}],"maxAge":86400}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_incomeStatementHistory_GS2C.DE.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_incomeStatementHistory_GS2C.DE.json new file mode 100644 index 0000000..504fd33 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_incomeStatementHistory_GS2C.DE.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"incomeStatementHistory":{"incomeStatementHistory":[{"maxAge":1,"endDate":{"raw":1738281600,"fmt":"2025-01-31"},"totalRevenue":{"raw":3823000000,"fmt":"3.82B","longFmt":"3,823,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":131300000,"fmt":"131.3M","longFmt":"131,300,000"},"netIncomeApplicableToCommonShares":{}},{"maxAge":1,"endDate":{"raw":1706659200,"fmt":"2024-01-31"},"totalRevenue":{"raw":5272800000,"fmt":"5.27B","longFmt":"5,272,800,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":6700000,"fmt":"6.7M","longFmt":"6,700,000"},"netIncomeApplicableToCommonShares":{}},{"maxAge":1,"endDate":{"raw":1675123200,"fmt":"2023-01-31"},"totalRevenue":{"raw":5927200000,"fmt":"5.93B","longFmt":"5,927,200,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":-313100000,"fmt":"-313.1M","longFmt":"-313,100,000"},"netIncomeApplicableToCommonShares":{}},{"maxAge":1,"endDate":{"raw":1643587200,"fmt":"2022-01-31"},"totalRevenue":{"raw":6010700000,"fmt":"6.01B","longFmt":"6,010,700,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":-381300000,"fmt":"-381.3M","longFmt":"-381,300,000"},"netIncomeApplicableToCommonShares":{}}],"maxAge":86400}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_incomeStatementHistory_SAP.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_incomeStatementHistory_SAP.json new file mode 100644 index 0000000..7a4eb70 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_incomeStatementHistory_SAP.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"incomeStatementHistory":{"incomeStatementHistory":[{"maxAge":1,"endDate":{"raw":1735603200,"fmt":"2024-12-31"},"totalRevenue":{"raw":34176000000,"fmt":"34.18B","longFmt":"34,176,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":3124000000,"fmt":"3.12B","longFmt":"3,124,000,000"},"netIncomeApplicableToCommonShares":{}},{"maxAge":1,"endDate":{"raw":1703980800,"fmt":"2023-12-31"},"totalRevenue":{"raw":31207000000,"fmt":"31.21B","longFmt":"31,207,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":3564000000,"fmt":"3.56B","longFmt":"3,564,000,000"},"netIncomeApplicableToCommonShares":{}},{"maxAge":1,"endDate":{"raw":1672444800,"fmt":"2022-12-31"},"totalRevenue":{"raw":30871000000,"fmt":"30.87B","longFmt":"30,871,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":1714000000,"fmt":"1.71B","longFmt":"1,714,000,000"},"netIncomeApplicableToCommonShares":{}},{"maxAge":1,"endDate":{"raw":1640908800,"fmt":"2021-12-31"},"totalRevenue":{"raw":27842000000,"fmt":"27.84B","longFmt":"27,842,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":5383000000,"fmt":"5.38B","longFmt":"5,383,000,000"},"netIncomeApplicableToCommonShares":{}}],"maxAge":86400}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_incomeStatementHistory_TSCO.L.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_incomeStatementHistory_TSCO.L.json new file mode 100644 index 0000000..d00efd6 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/fundamentals_api_incomeStatementHistory_TSCO.L.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"incomeStatementHistory":{"incomeStatementHistory":[{"maxAge":1,"endDate":{"raw":1740700800,"fmt":"2025-02-28"},"totalRevenue":{"raw":69916000000,"fmt":"69.92B","longFmt":"69,916,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":1626000000,"fmt":"1.63B","longFmt":"1,626,000,000"},"netIncomeApplicableToCommonShares":{}},{"maxAge":1,"endDate":{"raw":1709164800,"fmt":"2024-02-29"},"totalRevenue":{"raw":68187000000,"fmt":"68.19B","longFmt":"68,187,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":1736000000,"fmt":"1.74B","longFmt":"1,736,000,000"},"netIncomeApplicableToCommonShares":{}},{"maxAge":1,"endDate":{"raw":1677542400,"fmt":"2023-02-28"},"totalRevenue":{"raw":65762000000,"fmt":"65.76B","longFmt":"65,762,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":745000000,"fmt":"745M","longFmt":"745,000,000"},"netIncomeApplicableToCommonShares":{}},{"maxAge":1,"endDate":{"raw":1646006400,"fmt":"2022-02-28"},"totalRevenue":{"raw":61344000000,"fmt":"61.34B","longFmt":"61,344,000,000"},"costOfRevenue":{"raw":0,"fmt":null,"longFmt":"0"},"grossProfit":{"raw":0,"fmt":null,"longFmt":"0"},"researchDevelopment":{},"sellingGeneralAdministrative":{},"nonRecurring":{},"otherOperatingExpenses":{},"totalOperatingExpenses":{"raw":0,"fmt":null,"longFmt":"0"},"operatingIncome":{},"totalOtherIncomeExpenseNet":{},"ebit":{"raw":0,"fmt":null,"longFmt":"0"},"interestExpense":{},"incomeBeforeTax":{},"incomeTaxExpense":{"raw":0,"fmt":null,"longFmt":"0"},"minorityInterest":{},"netIncomeFromContinuingOps":{},"discontinuedOperations":{},"extraordinaryItems":{},"effectOfAccountingCharges":{},"otherItems":{},"netIncome":{"raw":1481000000,"fmt":"1.48B","longFmt":"1,481,000,000"},"netIncomeApplicableToCommonShares":{}}],"maxAge":86400}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/history_chart_AAPL.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/history_chart_AAPL.json new file mode 100644 index 0000000..f5a63ea --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/history_chart_AAPL.json @@ -0,0 +1 @@ +{"chart":{"result":[{"meta":{"currency":"USD","symbol":"AAPL","exchangeName":"NMS","fullExchangeName":"NasdaqGS","instrumentType":"EQUITY","firstTradeDate":345479400,"regularMarketTime":1761768001,"hasPrePostMarketData":true,"gmtoffset":-14400,"timezone":"EDT","exchangeTimezoneName":"America/New_York","regularMarketPrice":269.7,"fiftyTwoWeekHigh":271.41,"fiftyTwoWeekLow":169.21,"regularMarketDayHigh":271.41,"regularMarketDayLow":267.11,"regularMarketVolume":43332154,"longName":"Apple Inc.","shortName":"Apple Inc.","chartPreviousClose":211.21,"priceHint":2,"currentTradingPeriod":{"pre":{"timezone":"EDT","start":1761724800,"end":1761744600,"gmtoffset":-14400},"regular":{"timezone":"EDT","start":1761744600,"end":1761768000,"gmtoffset":-14400},"post":{"timezone":"EDT","start":1761768000,"end":1761782400,"gmtoffset":-14400}},"dataGranularity":"1d","range":"6mo","validRanges":["1d","5d","1mo","3mo","6mo","1y","2y","5y","10y","ytd","max"]},"timestamp":[1746019800,1746106200,1746192600,1746451800,1746538200,1746624600,1746711000,1746797400,1747056600,1747143000,1747229400,1747315800,1747402200,1747661400,1747747800,1747834200,1747920600,1748007000,1748352600,1748439000,1748525400,1748611800,1748871000,1748957400,1749043800,1749130200,1749216600,1749475800,1749562200,1749648600,1749735000,1749821400,1750080600,1750167000,1750253400,1750426200,1750685400,1750771800,1750858200,1750944600,1751031000,1751290200,1751376600,1751463000,1751549400,1751895000,1751981400,1752067800,1752154200,1752240600,1752499800,1752586200,1752672600,1752759000,1752845400,1753104600,1753191000,1753277400,1753363800,1753450200,1753709400,1753795800,1753882200,1753968600,1754055000,1754314200,1754400600,1754487000,1754573400,1754659800,1754919000,1755005400,1755091800,1755178200,1755264600,1755523800,1755610200,1755696600,1755783000,1755869400,1756128600,1756215000,1756301400,1756387800,1756474200,1756819800,1756906200,1756992600,1757079000,1757338200,1757424600,1757511000,1757597400,1757683800,1757943000,1758029400,1758115800,1758202200,1758288600,1758547800,1758634200,1758720600,1758807000,1758893400,1759152600,1759239000,1759325400,1759411800,1759498200,1759757400,1759843800,1759930200,1760016600,1760103000,1760362200,1760448600,1760535000,1760621400,1760707800,1760967000,1761053400,1761139800,1761226200,1761312600,1761571800,1761658200,1761768001],"events":{"dividends":{"1747056600":{"amount":0.26,"date":1747056600},"1754919000":{"amount":0.26,"date":1754919000}}},"indicators":{"quote":[{"volume":[52286500,57365700,101010600,69018500,51216500,68536700,50478900,36453900,63775800,51909300,49325800,45029500,54737900,46140500,42496600,59211800,46742400,78432900,56288500,45339700,51396800,70819900,35423300,46381600,43604000,55126100,46607700,72862600,54672600,60989900,43904600,51447300,43020700,38856200,45394700,96813500,55814300,54064000,39525700,50799100,73188600,91912800,78788900,67941800,34955800,50229000,42848900,48749400,44443600,39765800,38840100,42296300,47490500,48068100,48974600,51377400,46404100,46989300,46022600,40268800,37858000,51411700,45512500,80698400,104434500,75109300,44155100,108483100,90224800,113854000,61806100,55626200,69878500,51916300,56038700,37476200,39402600,42263900,30621200,42477800,30983100,54575100,31259500,38074700,39418400,44075600,66427800,47549400,54870400,48999500,66313900,83440800,50208600,55824200,42699500,63421100,46508000,44249600,163741300,105517400,60275200,42303700,55202100,46076300,40127700,37704300,48713900,42630200,49155600,44664100,31955800,36496900,38322000,61999100,38142900,35478000,33893600,39777000,49147000,90483000,46695900,45015300,32754900,38253700,44888200,41534800,43332154],"close":[212.5,213.32000732421875,205.35000610351562,198.88999938964844,198.50999450683594,196.25,197.49000549316406,198.52999877929688,210.7899932861328,212.92999267578125,212.3300018310547,211.4499969482422,211.25999450683594,208.77999877929688,206.86000061035156,202.08999633789062,201.36000061035156,195.27000427246094,200.2100067138672,200.4199981689453,199.9499969482422,200.85000610351562,201.6999969482422,203.27000427246094,202.82000732421875,200.6300048828125,203.9199981689453,201.4499969482422,202.6699981689453,198.77999877929688,199.1999969482422,196.4499969482422,198.4199981689453,195.63999938964844,196.5800018310547,201.0,201.5,200.3000030517578,201.55999755859375,201.0,201.0800018310547,205.1699981689453,207.82000732421875,212.44000244140625,213.5500030517578,209.9499969482422,210.00999450683594,211.13999938964844,212.41000366210938,211.16000366210938,208.6199951171875,209.11000061035156,210.16000366210938,210.02000427246094,211.17999267578125,212.47999572753906,214.39999389648438,214.14999389648438,213.75999450683594,213.8800048828125,214.0500030517578,211.27000427246094,209.0500030517578,207.57000732421875,202.3800048828125,203.35000610351562,202.9199981689453,213.25,220.02999877929688,229.35000610351562,227.17999267578125,229.64999389648438,233.3300018310547,232.77999877929688,231.58999633789062,230.88999938964844,230.55999755859375,226.00999450683594,224.89999389648438,227.75999450683594,227.16000366210938,229.30999755859375,230.49000549316406,232.55999755859375,232.13999938964844,229.72000122070312,238.47000122070312,239.77999877929688,239.69000244140625,237.8800048828125,234.35000610351562,226.7899932861328,230.02999877929688,234.07000732421875,236.6999969482422,238.14999389648438,238.99000549316406,237.8800048828125,245.5,256.0799865722656,254.42999267578125,252.30999755859375,256.8699951171875,255.4600067138672,254.42999267578125,254.6300048828125,255.4499969482422,257.1300048828125,258.0199890136719,256.69000244140625,256.4800109863281,258.05999755859375,254.0399932861328,245.27000427246094,247.66000366210938,247.77000427246094,249.33999633789062,247.4499969482422,252.2899932861328,262.239990234375,262.7699890136719,258.45001220703125,259.5799865722656,262.82000732421875,268.80999755859375,269.0,269.70001220703125],"open":[209.3000030517578,209.0800018310547,206.08999633789062,203.10000610351562,198.2100067138672,199.1699981689453,197.72000122070312,199.0,210.97000122070312,210.42999267578125,212.42999267578125,210.9499969482422,212.36000061035156,207.91000366210938,207.6699981689453,205.1699981689453,200.7100067138672,193.6699981689453,198.3000030517578,200.58999633789062,203.5800018310547,199.3699951171875,200.27999877929688,201.35000610351562,202.91000366210938,203.5,203.0,204.38999938964844,200.60000610351562,203.5,199.0800018310547,199.72999572753906,197.3000030517578,197.1999969482422,195.94000244140625,198.24000549316406,201.6300048828125,202.58999633789062,201.4499969482422,201.42999267578125,201.88999938964844,202.00999450683594,206.6699981689453,208.91000366210938,212.14999389648438,212.67999267578125,210.10000610351562,209.52999877929688,210.50999450683594,210.57000732421875,209.92999267578125,209.22000122070312,210.3000030517578,210.57000732421875,210.8699951171875,212.10000610351562,213.13999938964844,215.0,213.89999389648438,214.6999969482422,214.02999877929688,214.17999267578125,211.89999389648438,208.49000549316406,210.8699951171875,204.50999450683594,203.39999389648438,205.6300048828125,218.8800048828125,220.8300018310547,227.9199981689453,228.00999450683594,231.07000732421875,234.05999755859375,234.0,231.6999969482422,231.27999877929688,229.97999572753906,226.27000427246094,226.1699981689453,226.47999572753906,226.8699951171875,228.61000061035156,230.82000732421875,232.50999450683594,229.25,237.2100067138672,238.4499969482422,240.0,239.3000030517578,237.0,232.19000244140625,226.8800048828125,229.22000122070312,237.0,237.17999267578125,238.97000122070312,239.97000122070312,241.22999572753906,248.3000030517578,255.8800048828125,255.22000122070312,253.2100067138672,254.10000610351562,254.55999755859375,254.86000061035156,255.0399932861328,256.5799865722656,254.6699981689453,257.989990234375,256.80999755859375,256.5199890136719,257.80999755859375,254.94000244140625,249.3800048828125,246.60000610351562,249.49000549316406,248.25,248.02000427246094,255.88999938964844,261.8800048828125,262.6499938964844,259.94000244140625,261.19000244140625,264.8800048828125,268.989990234375,269.2749938964844],"low":[206.6699981689453,208.89999389648438,202.16000366210938,198.2100067138672,197.02000427246094,193.25,194.67999267578125,197.5399932861328,206.75,209.0,210.5800018310547,209.5399932861328,209.77000427246094,204.25999450683594,205.02999877929688,200.7100067138672,199.6999969482422,193.4600067138672,197.42999267578125,199.89999389648438,198.50999450683594,196.77999877929688,200.1199951171875,200.9600067138672,202.10000610351562,200.14999389648438,202.0500030517578,200.02000427246094,200.57000732421875,198.41000366210938,197.36000061035156,195.6999969482422,196.55999755859375,195.2100067138672,195.07000732421875,196.86000061035156,198.9600067138672,200.1999969482422,200.6199951171875,199.4600067138672,200.0,199.25999450683594,206.13999938964844,208.13999938964844,211.80999755859375,208.8000030517578,208.4499969482422,207.22000122070312,210.02999877929688,209.86000061035156,207.5399932861328,208.9199981689453,208.63999938964844,209.58999633789062,209.6999969482422,211.6300048828125,212.22999572753906,212.41000366210938,213.52999877929688,213.39999389648438,213.05999755859375,210.82000732421875,207.72000122070312,207.16000366210938,201.5,201.67999267578125,202.16000366210938,205.58999633789062,216.5800018310547,219.25,224.75999450683594,227.07000732421875,230.42999267578125,230.85000610351562,229.33999633789062,230.11000061035156,229.35000610351562,225.77000427246094,223.77999877929688,225.41000366210938,226.22999572753906,224.69000244140625,228.25999450683594,229.33999633789062,231.3699951171875,226.97000122070312,234.36000061035156,236.74000549316406,238.49000549316406,236.33999633789062,233.36000061035156,225.9499969482422,226.64999389648438,229.02000427246094,235.02999877929688,236.32000732421875,237.72999572753906,236.64999389648438,240.2100067138672,248.1199951171875,253.5800018310547,251.0399932861328,251.7100067138672,253.77999877929688,253.00999450683594,253.11000061035156,254.92999267578125,254.14999389648438,253.9499969482422,255.0500030517578,255.42999267578125,256.1099853515625,253.13999938964844,244.0,245.55999755859375,244.6999969482422,247.47000122070312,245.1300048828125,247.27000427246094,255.6300048828125,261.8299865722656,255.42999267578125,258.010009765625,259.17999267578125,264.6499938964844,268.1499938964844,267.1099853515625],"high":[213.5800018310547,214.55999755859375,206.99000549316406,204.10000610351562,200.64999389648438,199.44000244140625,200.0500030517578,200.5399932861328,211.27000427246094,213.39999389648438,213.94000244140625,212.9600067138672,212.57000732421875,209.47999572753906,208.47000122070312,207.0399932861328,202.75,197.6999969482422,200.74000549316406,202.72999572753906,203.80999755859375,201.9600067138672,202.1300048828125,203.77000427246094,206.24000549316406,204.75,205.6999969482422,206.0,204.35000610351562,204.5,199.67999267578125,200.3699951171875,198.69000244140625,198.38999938964844,197.57000732421875,201.6999969482422,202.3000030517578,203.44000244140625,203.6699981689453,202.63999938964844,203.22000122070312,207.38999938964844,210.19000244140625,213.33999633789062,214.64999389648438,216.22999572753906,211.42999267578125,211.3300018310547,213.47999572753906,212.1300048828125,210.91000366210938,211.88999938964844,212.39999389648438,211.8000030517578,211.7899932861328,215.77999877929688,214.9499969482422,215.14999389648438,215.69000244140625,215.24000549316406,214.85000610351562,214.80999755859375,212.38999938964844,209.83999633789062,213.5800018310547,207.8800048828125,205.33999633789062,215.3800048828125,220.85000610351562,231.0,229.55999755859375,230.8000030517578,235.0,235.1199951171875,234.27999877929688,233.1199951171875,232.8699951171875,230.47000122070312,226.52000427246094,229.08999633789062,229.3000030517578,229.49000549316406,230.89999389648438,233.41000366210938,233.3800048828125,230.85000610351562,238.85000610351562,239.89999389648438,241.32000732421875,240.14999389648438,238.77999877929688,232.4199981689453,230.4499969482422,234.50999450683594,238.19000244140625,241.22000122070312,240.10000610351562,241.1999969482422,246.3000030517578,256.6400146484375,257.3399963378906,255.74000549316406,257.1700134277344,257.6000061035156,255.0,255.9199981689453,258.7900085449219,258.17999267578125,259.239990234375,259.07000732421875,257.3999938964844,258.5199890136719,258.0,256.3800048828125,249.69000244140625,248.85000610351562,251.82000732421875,249.0399932861328,253.3800048828125,264.3800048828125,265.2900085449219,262.8500061035156,260.6199951171875,264.1300048828125,269.1199951171875,269.8900146484375,271.4100036621094]}],"adjclose":[{"adjclose":[211.9811248779297,212.79913330078125,204.8485870361328,198.40435791015625,198.0252685546875,195.7707977294922,197.00778198242188,198.0452423095703,210.55104064941406,212.68861389160156,212.08929443359375,211.21029663085938,211.0205078125,208.54331970214844,206.62550354003906,201.86090087890625,201.13172912597656,195.04864501953125,199.98304748535156,200.1927947998047,199.72332763671875,200.622314453125,201.47134399414062,203.03956604003906,202.590087890625,200.40257263183594,203.68882751464844,201.2216339111328,202.44024658203125,198.55465698242188,198.97418212890625,196.227294921875,198.195068359375,195.418212890625,196.35714721679688,200.7721405029297,201.27157592773438,200.07293701171875,201.33151245117188,200.7721405029297,200.85205078125,204.93740844726562,207.58441162109375,212.1991729736328,213.30792236328125,209.7119903564453,209.7719268798828,210.90065002441406,212.16920471191406,210.92062377929688,208.3834991455078,208.87295532226562,209.92176818847656,209.78192138671875,210.9405975341797,212.23912048339844,214.15695190429688,213.9072265625,213.51766967773438,213.63754272460938,213.80735778808594,211.03050231933594,208.81301879882812,207.33470153808594,202.1505889892578,203.11949157714844,202.68995666503906,213.0082550048828,219.7805633544922,229.0900115966797,227.17999267578125,229.64999389648438,233.3300018310547,232.77999877929688,231.58999633789062,230.88999938964844,230.55999755859375,226.00999450683594,224.89999389648438,227.75999450683594,227.16000366210938,229.30999755859375,230.49000549316406,232.55999755859375,232.13999938964844,229.72000122070312,238.47000122070312,239.77999877929688,239.69000244140625,237.8800048828125,234.35000610351562,226.7899932861328,230.02999877929688,234.07000732421875,236.6999969482422,238.14999389648438,238.99000549316406,237.8800048828125,245.5,256.0799865722656,254.42999267578125,252.30999755859375,256.8699951171875,255.4600067138672,254.42999267578125,254.6300048828125,255.4499969482422,257.1300048828125,258.0199890136719,256.69000244140625,256.4800109863281,258.05999755859375,254.0399932861328,245.27000427246094,247.66000366210938,247.77000427246094,249.33999633789062,247.4499969482422,252.2899932861328,262.239990234375,262.7699890136719,258.45001220703125,259.5799865722656,262.82000732421875,268.80999755859375,269.0,269.70001220703125]}]}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/history_chart_GS2C.DE.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/history_chart_GS2C.DE.json new file mode 100644 index 0000000..e135786 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/history_chart_GS2C.DE.json @@ -0,0 +1 @@ +{"chart":{"result":[{"meta":{"currency":"EUR","symbol":"GS2C.DE","exchangeName":"GER","fullExchangeName":"XETRA","instrumentType":"EQUITY","firstTradeDate":1198742400,"regularMarketTime":1761755744,"hasPrePostMarketData":false,"gmtoffset":3600,"timezone":"CET","exchangeTimezoneName":"Europe/Berlin","regularMarketPrice":19.948,"fiftyTwoWeekHigh":32.895,"fiftyTwoWeekLow":18.6,"regularMarketDayHigh":20.15,"regularMarketDayLow":19.786,"regularMarketVolume":9179,"longName":"GameStop Corp.","shortName":"Gamestop Corp. R","chartPreviousClose":19.412,"priceHint":2,"currentTradingPeriod":{"pre":{"timezone":"CET","start":1761804000,"end":1761811200,"gmtoffset":3600},"regular":{"timezone":"CET","start":1761811200,"end":1761841800,"gmtoffset":3600},"post":{"timezone":"CET","start":1761841800,"end":1761852600,"gmtoffset":3600}},"dataGranularity":"1d","range":"5d","validRanges":["1d","5d","1mo","3mo","6mo","1y","2y","5y","10y","ytd","max"]},"timestamp":[1761202800,1761289200,1761552000,1761638400,1761755744],"indicators":{"quote":[{"close":[19.738000869750977,20.170000076293945,20.399999618530273,20.079999923706055,19.947999954223633],"low":[19.299999237060547,20.0,20.170000076293945,19.93000030517578,19.785999298095703],"volume":[4549,11698,32659,28989,9179],"open":[19.579999923706055,20.389999389648438,21.639999389648438,20.1200008392334,20.1299991607666],"high":[19.799999237060547,20.524999618530273,21.684999465942383,20.2450008392334,20.149999618530273]}],"adjclose":[{"adjclose":[19.738000869750977,20.170000076293945,20.399999618530273,20.079999923706055,19.947999954223633]}]}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/history_chart_MSFT.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/history_chart_MSFT.json new file mode 100644 index 0000000..2e91ce6 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/history_chart_MSFT.json @@ -0,0 +1 @@ +{"chart":{"result":[{"meta":{"currency":"USD","symbol":"MSFT","exchangeName":"NMS","fullExchangeName":"NasdaqGS","instrumentType":"EQUITY","firstTradeDate":511108200,"regularMarketTime":1761768001,"hasPrePostMarketData":true,"gmtoffset":-14400,"timezone":"EDT","exchangeTimezoneName":"America/New_York","regularMarketPrice":541.55,"fiftyTwoWeekHigh":555.45,"fiftyTwoWeekLow":344.79,"regularMarketDayHigh":546.27,"regularMarketDayLow":536.729,"regularMarketVolume":33295714,"longName":"Microsoft Corporation","shortName":"Microsoft Corporation","chartPreviousClose":394.04,"priceHint":2,"currentTradingPeriod":{"pre":{"timezone":"EDT","start":1761724800,"end":1761744600,"gmtoffset":-14400},"regular":{"timezone":"EDT","start":1761744600,"end":1761768000,"gmtoffset":-14400},"post":{"timezone":"EDT","start":1761768000,"end":1761782400,"gmtoffset":-14400}},"dataGranularity":"1d","range":"6mo","validRanges":["1d","5d","1mo","3mo","6mo","1y","2y","5y","10y","ytd","max"]},"timestamp":[1746019800,1746106200,1746192600,1746451800,1746538200,1746624600,1746711000,1746797400,1747056600,1747143000,1747229400,1747315800,1747402200,1747661400,1747747800,1747834200,1747920600,1748007000,1748352600,1748439000,1748525400,1748611800,1748871000,1748957400,1749043800,1749130200,1749216600,1749475800,1749562200,1749648600,1749735000,1749821400,1750080600,1750167000,1750253400,1750426200,1750685400,1750771800,1750858200,1750944600,1751031000,1751290200,1751376600,1751463000,1751549400,1751895000,1751981400,1752067800,1752154200,1752240600,1752499800,1752586200,1752672600,1752759000,1752845400,1753104600,1753191000,1753277400,1753363800,1753450200,1753709400,1753795800,1753882200,1753968600,1754055000,1754314200,1754400600,1754487000,1754573400,1754659800,1754919000,1755005400,1755091800,1755178200,1755264600,1755523800,1755610200,1755696600,1755783000,1755869400,1756128600,1756215000,1756301400,1756387800,1756474200,1756819800,1756906200,1756992600,1757079000,1757338200,1757424600,1757511000,1757597400,1757683800,1757943000,1758029400,1758115800,1758202200,1758288600,1758547800,1758634200,1758720600,1758807000,1758893400,1759152600,1759239000,1759325400,1759411800,1759498200,1759757400,1759843800,1759930200,1760016600,1760103000,1760362200,1760448600,1760535000,1760621400,1760707800,1760967000,1761053400,1761139800,1761226200,1761312600,1761571800,1761658200,1761768001],"events":{"dividends":{"1747315800":{"amount":0.83,"date":1747315800},"1755783000":{"amount":0.83,"date":1755783000}}},"indicators":{"quote":[{"low":[384.44000244140625,424.8999938964844,429.989990234375,432.1099853515625,431.1700134277344,431.1099853515625,435.6600036621094,435.8800048828125,439.7799987792969,445.3599853515625,448.1400146484375,450.42999267578125,448.7300109863281,450.79998779296875,454.32000732421875,451.80999755859375,453.8999938964844,448.9100036621094,456.1199951171875,456.92999267578125,455.30999755859375,455.5400085449219,456.8900146484375,460.8599853515625,463.0199890136719,464.0299987792969,468.7799987792969,468.6199951171875,466.9599914550781,469.6600036621094,473.5199890136719,472.760009765625,475.0,474.0799865722656,474.4599914550781,476.8699951171875,472.510009765625,486.79998779296875,489.3900146484375,492.80999755859375,493.0299987792969,495.3299865722656,490.9800109863281,488.70001220703125,493.44000244140625,495.2300109863281,494.1099853515625,499.739990234375,497.75,497.79998779296875,501.0299987792969,502.7900085449219,501.8900146484375,505.6199951171875,507.42999267578125,505.54998779296875,505.2699890136719,500.70001220703125,507.29998779296875,510.3599853515625,510.1199951171875,511.55999755859375,509.44000244140625,531.9000244140625,520.8599853515625,528.1300048828125,527.239990234375,524.030029296875,517.5499877929688,519.4099731445312,519.719970703125,522.7000122070312,519.3699951171875,520.1400146484375,519.0800170898438,514.02001953125,508.54998779296875,504.44000244140625,502.7200012207031,502.4100036621094,504.1199951171875,498.510009765625,499.8999938964844,505.5,504.489990234375,496.80999755859375,502.32000732421875,503.1499938964844,492.3699951171875,495.0299987792969,497.70001220703125,496.7200012207031,497.8800048828125,503.8500061035156,507.0,508.6000061035156,505.92999267578125,507.6600036621094,510.30999755859375,512.5399780273438,507.30999755859375,506.9200134277344,505.0400085449219,506.6199951171875,508.8800048828125,509.6600036621094,511.69000244140625,510.67999267578125,515.0,518.2000122070312,521.4400024414062,523.0900268554688,517.4000244140625,509.6300048828125,511.67999267578125,506.0,510.0,508.1300048828125,507.30999755859375,513.4299926757812,513.0399780273438,517.7100219726562,518.6099853515625,520.7100219726562,529.010009765625,540.77001953125,536.7286987304688],"high":[396.6600036621094,436.989990234375,439.44000244140625,439.5,437.7300109863281,438.1199951171875,443.6700134277344,440.739990234375,449.3699951171875,450.6700134277344,453.8999938964844,456.19000244140625,454.3599853515625,459.5899963378906,458.3399963378906,457.7799987792969,460.25,453.69000244140625,460.95001220703125,462.5199890136719,461.7200012207031,461.67999267578125,462.1099853515625,464.1400146484375,465.69000244140625,469.6499938964844,473.3399963378906,473.42999267578125,472.79998779296875,475.4700012207031,480.4200134277344,479.17999267578125,480.69000244140625,478.739990234375,481.0,483.4599914550781,487.75,491.8500061035156,494.55999755859375,498.0400085449219,499.29998779296875,500.760009765625,498.04998779296875,493.5,500.1300048828125,498.75,498.20001220703125,506.7799987792969,504.44000244140625,505.0299987792969,503.9700012207031,508.29998779296875,506.7200012207031,513.3699951171875,514.6400146484375,512.0900268554688,511.20001220703125,506.7900085449219,513.6699829101562,518.2899780273438,515.0,517.6199951171875,515.9500122070312,555.4500122070312,535.7999877929688,538.25,537.2999877929688,531.7000122070312,528.0900268554688,524.6599731445312,527.5900268554688,530.97998046875,532.7000122070312,525.9500122070312,526.0999755859375,522.8200073242188,515.1599731445312,511.0,507.6300048828125,510.7300109863281,508.19000244140625,504.9800109863281,507.2900085449219,511.0899963378906,509.6000061035156,506.0,507.7900085449219,508.1499938964844,511.9700012207031,501.20001220703125,502.25,503.2300109863281,503.1700134277344,512.5499877929688,515.469970703125,517.22998046875,511.2900085449219,513.0700073242188,519.2999877929688,517.739990234375,514.5900268554688,512.47998046875,510.010009765625,513.9400024414062,516.8499755859375,518.1599731445312,520.510009765625,521.5999755859375,520.489990234375,531.030029296875,529.7999877929688,526.9500122070312,524.3300170898438,523.5800170898438,516.4099731445312,515.280029296875,517.1900024414062,516.8499755859375,515.47998046875,518.7000122070312,518.6900024414062,525.22998046875,523.9500122070312,525.3499755859375,534.5800170898438,553.719970703125,546.27001953125],"volume":[36461100,58938100,30757400,20136100,15104200,23295300,23491300,15324200,22821900,23618800,19902800,21992300,23849800,21336500,15441800,19216900,18025600,16883500,20974300,17086300,13974800,34770500,16626500,15743800,14162700,20131700,15285600,16469900,15375900,16399200,18950600,16814500,15626100,15414100,17526500,37576200,24864000,22305600,17495100,21578900,34539200,28369000,19945400,16319600,13984800,13981600,11846600,18659500,16492100,16459500,12058800,14927200,15154400,17503100,21209700,14066800,13868600,16396600,16107000,19125700,14308000,16469200,26380400,51617300,28977600,25349000,19171600,21355700,16079100,15531000,20194400,18667000,19619200,20269100,25213300,23760600,21481000,27723000,18443300,24324200,21638600,30835700,17277900,18015600,20961600,18128000,16345100,15509500,31994800,16771000,14410500,21611800,18881600,23624900,17143800,19711900,15816600,18913700,52474100,20009300,19799600,13533700,15786500,16213100,17617800,19728200,22632300,21222900,15112300,21388600,14615200,13363400,18343600,24133800,14284200,14684300,14694700,15559600,19867800,14665600,15586200,18962700,14023500,15532400,18734700,29986700,33295714],"open":[390.29998779296875,431.1099853515625,431.739990234375,432.8699951171875,432.20001220703125,433.8399963378906,437.92999267578125,440.0,445.94000244140625,447.7799987792969,448.1400146484375,450.7699890136719,452.04998779296875,450.8800048828125,455.5899963378906,454.57000732421875,454.95001220703125,449.9800109863281,456.4800109863281,461.2200012207031,461.54998779296875,459.7200012207031,457.1400146484375,461.4700012207031,464.0,464.9599914550781,470.0899963378906,469.70001220703125,471.19000244140625,470.0199890136719,475.0199890136719,476.4100036621094,475.2099914550781,475.3999938964844,478.0,482.2300109863281,478.2099914550781,488.95001220703125,492.0400085449219,492.9800109863281,497.54998779296875,497.0400085449219,496.4700012207031,489.989990234375,493.80999755859375,497.3800048828125,497.239990234375,500.29998779296875,503.04998779296875,498.4700012207031,501.5199890136719,503.0199890136719,505.17999267578125,505.67999267578125,514.47998046875,506.7099914550781,510.9700012207031,506.75,508.7699890136719,512.469970703125,514.0800170898438,515.530029296875,515.1699829101562,555.22998046875,535.0,528.27001953125,537.1799926757812,530.9000244140625,526.7999877929688,522.5999755859375,522.2999877929688,523.75,532.1099853515625,522.5599975585938,522.77001953125,521.5900268554688,515.0,509.8699951171875,503.69000244140625,504.25,506.6300048828125,504.3599853515625,502.0,507.0899963378906,508.6600036621094,500.4700012207031,503.7900085449219,504.29998779296875,509.07000732421875,498.1099853515625,501.42999267578125,502.9800109863281,502.25,506.6499938964844,508.7900085449219,516.8800048828125,510.6199951171875,511.489990234375,510.55999755859375,515.5900268554688,513.7999877929688,510.3800048828125,508.29998779296875,510.05999755859375,511.5,513.239990234375,514.7999877929688,517.6400146484375,517.0999755859375,518.6099853515625,528.2899780273438,523.280029296875,522.3400268554688,519.6400146484375,516.4099731445312,510.2300109863281,514.9600219726562,512.5800170898438,509.0400085449219,514.6099853515625,517.5,521.1500244140625,522.4600219726562,522.7899780273438,531.780029296875,550.0,544.9400024414062],"close":[395.260009765625,425.3999938964844,435.2799987792969,436.1700134277344,433.30999755859375,433.3500061035156,438.1700134277344,438.7300109863281,449.260009765625,449.1400146484375,452.94000244140625,453.1300048828125,454.2699890136719,458.8699951171875,458.1700134277344,452.57000732421875,454.8599853515625,450.17999267578125,460.69000244140625,457.3599853515625,458.67999267578125,460.3599853515625,461.9700012207031,462.9700012207031,463.8699951171875,467.67999267578125,470.3800048828125,472.75,470.9200134277344,472.6199951171875,478.8699951171875,474.9599914550781,479.1400146484375,478.0400085449219,480.239990234375,477.3999938964844,486.0,490.1099853515625,492.2699890136719,497.45001220703125,495.94000244140625,497.4100036621094,492.04998779296875,491.0899963378906,498.8399963378906,497.7200012207031,496.6199951171875,503.510009765625,501.4800109863281,503.32000732421875,503.0199890136719,505.82000732421875,505.6199951171875,511.70001220703125,510.04998779296875,510.05999755859375,505.2699890136719,505.8699951171875,510.8800048828125,513.7100219726562,512.5,512.5700073242188,513.239990234375,533.5,524.1099853515625,535.6400146484375,527.75,524.9400024414062,520.8400268554688,522.0399780273438,521.77001953125,529.239990234375,520.5800170898438,522.47998046875,520.1699829101562,517.0999755859375,509.7699890136719,505.7200012207031,504.239990234375,507.2300109863281,504.260009765625,502.0400085449219,506.739990234375,509.6400146484375,506.69000244140625,505.1199951171875,505.3500061035156,507.9700012207031,495.0,498.20001220703125,498.4100036621094,500.3699951171875,501.010009765625,509.8999938964844,515.3599853515625,509.0400085449219,510.0199890136719,508.45001220703125,517.9299926757812,514.4500122070312,509.2300109863281,510.1499938964844,507.0299987792969,511.4599914550781,514.5999755859375,517.9500122070312,519.7100219726562,515.739990234375,517.3499755859375,528.5700073242188,523.97998046875,524.8499755859375,522.4000244140625,510.9599914550781,514.0499877929688,513.5700073242188,513.4299926757812,511.6099853515625,513.5800170898438,516.7899780273438,517.6599731445312,520.5399780273438,520.5599975585938,523.6099853515625,531.52001953125,542.0700073242188,541.5499877929688]}],"adjclose":[{"adjclose":[393.88818359375,423.9235534667969,433.769287109375,434.65618896484375,431.8061218261719,431.8459777832031,436.6492614746094,437.20733642578125,447.70074462890625,447.5812072753906,451.36798095703125,452.3863220214844,453.5244445800781,458.11688232421875,457.4180603027344,451.8272399902344,454.11346435546875,449.441162109375,459.93389892578125,456.609375,457.9272155761719,459.60443115234375,461.2118225097656,462.2101745605469,463.1086730957031,466.91241455078125,469.6080017089844,471.97412109375,470.1471252441406,471.8443298339844,478.0840759277344,474.18048095703125,478.3536376953125,477.25543212890625,479.4518127441406,476.6164855957031,485.2023620605469,489.30560302734375,491.4620666503906,496.6335754394531,495.1260681152344,496.5936584472656,491.242431640625,490.2840270996094,498.02130126953125,496.90313720703125,495.804931640625,502.68365478515625,500.656982421875,502.49395751953125,502.1944274902344,504.9898376464844,504.7901611328125,510.8601989746094,509.212890625,509.2228698730469,504.44073486328125,505.0397644042969,510.0415344238281,512.866943359375,511.65887451171875,511.728759765625,512.3976440429688,532.6243896484375,523.2498168945312,534.7609252929688,526.8838500976562,524.0784912109375,519.9852294921875,521.1832275390625,520.9136962890625,528.3713989257812,519.7256469726562,521.6224975585938,519.3162841796875,516.2512817382812,508.933349609375,504.8900146484375,504.239990234375,507.2300109863281,504.260009765625,502.0400085449219,506.739990234375,509.6400146484375,506.69000244140625,505.1199951171875,505.3500061035156,507.9700012207031,495.0,498.20001220703125,498.4100036621094,500.3699951171875,501.010009765625,509.8999938964844,515.3599853515625,509.0400085449219,510.0199890136719,508.45001220703125,517.9299926757812,514.4500122070312,509.2300109863281,510.1499938964844,507.0299987792969,511.4599914550781,514.5999755859375,517.9500122070312,519.7100219726562,515.739990234375,517.3499755859375,528.5700073242188,523.97998046875,524.8499755859375,522.4000244140625,510.9599914550781,514.0499877929688,513.5700073242188,513.4299926757812,511.6099853515625,513.5800170898438,516.7899780273438,517.6599731445312,520.5399780273438,520.5599975585938,523.6099853515625,531.52001953125,542.0700073242188,541.5499877929688]}]}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/history_chart_VFINX.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/history_chart_VFINX.json new file mode 100644 index 0000000..c2bcd51 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/history_chart_VFINX.json @@ -0,0 +1 @@ +{"chart":{"result":[{"meta":{"currency":"USD","symbol":"VFINX","exchangeName":"NAS","fullExchangeName":"Nasdaq","instrumentType":"MUTUALFUND","firstTradeDate":315671400,"regularMarketTime":1761782703,"hasPrePostMarketData":false,"gmtoffset":-14400,"timezone":"EDT","exchangeTimezoneName":"America/New_York","regularMarketPrice":636.38,"fiftyTwoWeekHigh":636.41,"fiftyTwoWeekLow":459.88,"longName":"Vanguard 500 Index Fund","shortName":"Vanguard Index Trust 500 Index ","chartPreviousClose":14.34,"priceHint":2,"currentTradingPeriod":{"pre":{"timezone":"EDT","start":1761724800,"end":1761744600,"gmtoffset":-14400},"regular":{"timezone":"EDT","start":1761744600,"end":1761768000,"gmtoffset":-14400},"post":{"timezone":"EDT","start":1761768000,"end":1761782400,"gmtoffset":-14400}},"dataGranularity":"3mo","range":"max","validRanges":["1mo","3mo","6mo","ytd","1y","2y","5y","10y","max"]},"timestamp":[473403600,481179600,489038400,496987200,504939600,512715600,520574400,528523200,536475600,544251600,552110400,560059200,568011600,575874000,583732800,591681600,599634000,607410000,615268800,623217600,631170000,638946000,646804800,654753600,662706000,670482000,678340800,686289600,694242000,702104400,709963200,717912000,725864400,733640400,741499200,749448000,757400400,765176400,773035200,780984000,788936400,796712400,804571200,812520000,820472400,828334800,836193600,844142400,852094800,859870800,867729600,875678400,883630800,891406800,899265600,907214400,915166800,922942800,930801600,938750400,946702800,954565200,962424000,970372800,978325200,986101200,993960000,1001908800,1009861200,1017637200,1025496000,1033444800,1041397200,1049173200,1057032000,1064980800,1072933200,1080795600,1088654400,1096603200,1104555600,1112331600,1120190400,1128139200,1136091600,1143867600,1151726400,1159675200,1167627600,1175400000,1183262400,1191211200,1199163600,1207022400,1214884800,1222833600,1230786000,1238558400,1246420800,1254369600,1262322000,1270094400,1277956800,1285905600,1293858000,1301630400,1309492800,1317441600,1325394000,1333252800,1341115200,1349064000,1357016400,1364788800,1372651200,1380600000,1388552400,1396324800,1404187200,1412136000,1420088400,1427860800,1435723200,1443672000,1451624400,1459483200,1467345600,1475294400,1483246800,1491019200,1498881600,1506830400,1514782800,1522555200,1530417600,1538366400,1546318800,1554091200,1561953600,1569902400,1577854800,1585713600,1593576000,1601524800,1609477200,1617249600,1625112000,1633060800,1641013200,1648785600,1656648000,1664596800,1672549200,1680321600,1688184000,1696132800,1704085200,1711944000,1719806400,1727755200,1735707600,1743480000,1751342400,1759291200],"events":{"dividends":{"473403600":{"amount":0.16,"date":330960600},"481179600":{"amount":0.16,"date":339168600},"489038400":{"amount":0.35,"date":347034600},"496987200":{"amount":0.18,"date":354551400},"504939600":{"amount":0.18,"date":362755800},"512715600":{"amount":0.18,"date":370704600},"520574400":{"amount":0.29,"date":378484200},"528523200":{"amount":0.18,"date":386346600},"536475600":{"amount":0.18,"date":394205400},"544251600":{"amount":0.18,"date":402067800},"552110400":{"amount":0.29,"date":409933800},"560059200":{"amount":0.18,"date":417796200},"568011600":{"amount":0.18,"date":425655000},"575874000":{"amount":0.18,"date":433603800},"583732800":{"amount":0.33,"date":441469800},"591681600":{"amount":0.18,"date":449332200},"599634000":{"amount":0.18,"date":457277400},"607410000":{"amount":0.18,"date":465226200},"615268800":{"amount":0.34,"date":473092200},"623217600":{"amount":0.18,"date":480868200},"631170000":{"amount":0.18,"date":488813400},"638946000":{"amount":0.18,"date":496935000},"646804800":{"amount":0.37,"date":504541800},"654753600":{"amount":0.18,"date":512317800},"662706000":{"amount":0.18,"date":520263000},"670482000":{"amount":0.18,"date":528471000},"678340800":{"amount":0.35,"date":536337000},"686289600":{"amount":0.18,"date":543853800},"694242000":{"amount":0.18,"date":551971800},"702104400":{"amount":0.18,"date":559834200},"709963200":{"amount":0.15,"date":567786600},"717912000":{"amount":0.19,"date":568391400},"725864400":{"amount":0.18,"date":575649000},"733640400":{"amount":0.18,"date":583507800},"741499200":{"amount":0.18,"date":591456600},"749448000":{"amount":0.37,"date":599409000},"757400400":{"amount":0.18,"date":607185000},"765176400":{"amount":0.18,"date":615043800},"773035200":{"amount":0.18,"date":622906200},"780984000":{"amount":0.66,"date":630858600},"788936400":{"amount":0.18,"date":638634600},"796712400":{"amount":0.18,"date":646407000},"804571200":{"amount":0.18,"date":654355800},"812520000":{"amount":0.63,"date":662308200},"820472400":{"amount":0.18,"date":670084200},"828334800":{"amount":0.18,"date":678029400},"836193600":{"amount":0.18,"date":685891800},"844142400":{"amount":0.61,"date":693844200},"852094800":{"amount":0.22,"date":701620200},"859870800":{"amount":0.22,"date":709392600},"867729600":{"amount":0.22,"date":717341400},"875678400":{"amount":0.46,"date":725639400},"883630800":{"amount":0.22,"date":732983400},"891406800":{"amount":0.22,"date":740842200},"899265600":{"amount":0.22,"date":748704600},"907214400":{"amount":0.47,"date":757175400},"915166800":{"amount":0.22,"date":764433000},"922942800":{"amount":0.22,"date":772291800},"930801600":{"amount":0.22,"date":780154200},"938750400":{"amount":0.51,"date":788625000},"946702800":{"amount":0.22,"date":795882600},"954565200":{"amount":0.22,"date":803741400},"962424000":{"amount":0.22,"date":812122200},"970372800":{"amount":0.56,"date":819642600},"978325200":{"amount":0.22,"date":827850600},"986101200":{"amount":0.22,"date":835709400},"993960000":{"amount":0.22,"date":843658200},"1001908800":{"amount":0.62,"date":851092200},"1009861200":{"amount":0.27,"date":859300200},"1017637200":{"amount":0.27,"date":867245400},"1025496000":{"amount":0.27,"date":875107800},"1033444800":{"amount":0.51,"date":882887400},"1041397200":{"amount":0.27,"date":891009000},"1049173200":{"amount":0.27,"date":898867800},"1057032000":{"amount":0.27,"date":906730200},"1064980800":{"amount":0.52,"date":914337000},"1072933200":{"amount":0.27,"date":922458600},"1080795600":{"amount":0.38,"date":930317400},"1088654400":{"amount":0.35,"date":938179800},"1096603200":{"amount":0.41,"date":945959400},"1104555600":{"amount":0.3,"date":953908200},"1112331600":{"amount":0.31,"date":961767000},"1120190400":{"amount":0.32,"date":969629400},"1128139200":{"amount":0.37,"date":977495400},"1136091600":{"amount":0.29,"date":984753000},"1143867600":{"amount":0.28,"date":993216600},"1151726400":{"amount":0.32,"date":1001079000},"1159675200":{"amount":0.385,"date":1009549800},"1167627600":{"amount":0.29,"date":1016807400},"1175400000":{"amount":0.3,"date":1024666200},"1183262400":{"amount":0.35,"date":1033133400},"1191211200":{"amount":0.42,"date":1040999400},"1199163600":{"amount":0.3,"date":1048861800},"1207022400":{"amount":0.3,"date":1056115800},"1214884800":{"amount":0.36,"date":1064583000},"1222833600":{"amount":0.47,"date":1072449000},"1230786000":{"amount":0.36,"date":1080311400},"1238558400":{"amount":0.35,"date":1088170200},"1246420800":{"amount":0.41,"date":1096032600},"1254369600":{"amount":0.83,"date":1103812200},"1262322000":{"amount":0.43,"date":1111588200},"1270094400":{"amount":0.42,"date":1119619800},"1277956800":{"amount":0.53,"date":1127482200},"1285905600":{"amount":0.6,"date":1135780200},"1293858000":{"amount":0.49,"date":1142605800},"1301630400":{"amount":0.48,"date":1151069400},"1309492800":{"amount":0.52,"date":1158931800},"1317441600":{"amount":0.65,"date":1167143400},"1325394000":{"amount":0.55,"date":1174656600},"1333252800":{"amount":0.57,"date":1182519000},"1341115200":{"amount":0.62,"date":1190381400},"1349064000":{"amount":0.75,"date":1198247400},"1357016400":{"amount":0.6,"date":1206624600},"1364788800":{"amount":0.57,"date":1214487000},"1372651200":{"amount":0.635,"date":1222349400},"1380600000":{"amount":0.7,"date":1230301800},"1388552400":{"amount":0.528,"date":1238074200},"1396324800":{"amount":0.435,"date":1245763800},"1404187200":{"amount":0.482,"date":1254144600},"1412136000":{"amount":0.659,"date":1262010600},"1420088400":{"amount":0.437,"date":1269869400},"1427860800":{"amount":0.506,"date":1277731800},"1435723200":{"amount":0.529,"date":1285248600},"1443672000":{"amount":0.494,"date":1293114600},"1451624400":{"amount":0.505,"date":1300973400},"1459483200":{"amount":0.536,"date":1308835800},"1467345600":{"amount":0.554,"date":1316698200},"1475294400":{"amount":0.651,"date":1324564200},"1483246800":{"amount":0.536,"date":1332509400},"1491019200":{"amount":0.607,"date":1340371800},"1498881600":{"amount":0.651,"date":1348234200},"1506830400":{"amount":0.905,"date":1356100200},"1514782800":{"amount":0.635,"date":1363872600},"1522555200":{"amount":0.697,"date":1371821400},"1530417600":{"amount":0.747,"date":1379683800},"1538366400":{"amount":0.869,"date":1387809000},"1546318800":{"amount":0.734,"date":1395408600},"1554091200":{"amount":0.762,"date":1403271000},"1561953600":{"amount":0.826,"date":1411133400},"1569902400":{"amount":0.981,"date":1418826600},"1577854800":{"amount":0.934,"date":1426858200},"1585713600":{"amount":0.86,"date":1434720600},"1593576000":{"amount":0.911,"date":1442583000},"1601524800":{"amount":1.042,"date":1450449000},"1609477200":{"amount":0.962,"date":1458307800},"1617249600":{"amount":0.908,"date":1466429400},"1625112000":{"amount":0.852,"date":1473687000},"1633060800":{"amount":1.254,"date":1482330600},"1641013200":{"amount":0.961,"date":1490103000},"1648785600":{"amount":0.96,"date":1498138200},"1656648000":{"amount":1.129,"date":1505827800},"1664596800":{"amount":1.126,"date":1513953000},"1672549200":{"amount":1.028,"date":1521811800},"1680321600":{"amount":1.098,"date":1530106200},"1688184000":{"amount":1.15,"date":1537882200},"1696132800":{"amount":1.224,"date":1544797800},"1704085200":{"amount":1.39,"date":1553088600},"1711944000":{"amount":1.308,"date":1561555800},"1719806400":{"amount":1.229,"date":1569418200},"1727755200":{"amount":1.365,"date":1576852200},"1735707600":{"amount":1.113,"date":1583760600},"1743480000":{"amount":1.355,"date":1593178200},"1751342400":{"amount":1.233,"date":1601299800},"1759291200":{"amount":1.588,"date":1759152600}},"capitalGains":{"560059200":{"amount":0.17,"date":567786600},"591681600":{"amount":0.32,"date":599409000},"599634000":{"amount":0.17,"date":607185000},"623217600":{"amount":0.58,"date":630858600},"631170000":{"amount":0.05,"date":638634600},"654753600":{"amount":0.05,"date":662308200},"662706000":{"amount":0.07,"date":670084200},"686289600":{"amount":0.05,"date":693844200},"717912000":{"amount":0.1,"date":725639400},"725864400":{"amount":0.03,"date":732983400},"757400400":{"amount":0.08,"date":764433000},"780984000":{"amount":0.12,"date":788625000},"812520000":{"amount":0.13,"date":819642600},"820472400":{"amount":0.04,"date":827850600},"844142400":{"amount":0.21,"date":851092200},"852094800":{"amount":0.04,"date":859300200},"875678400":{"amount":0.55,"date":882887400},"883630800":{"amount":0.12,"date":891009000},"907214400":{"amount":0.3,"date":914337000},"915166800":{"amount":0.455,"date":922458600},"938750400":{"amount":0.54,"date":945959400}}},"indicators":{"quote":[{"low":[19.110000610351562,20.81999969482422,21.3799991607666,21.3700008392334,22.170000076293945,24.90999984741211,25.209999084472656,24.270000457763672,24.709999084472656,27.989999771118164,30.40999984741211,22.579999923706055,24.049999237060547,24.989999771118164,25.649999618530273,26.389999389648438,26.889999389648438,28.809999465942383,31.219999313354492,32.66999816894531,30.799999237060547,31.399999618530273,28.84000015258789,28.34000015258789,29.5,35.029998779296875,35.45000076293945,35.880001068115234,38.02000045776367,37.2400016784668,38.689998626708984,38.13999938964844,40.36000061035156,40.81999969482422,41.619998931884766,43.25,41.84000015258789,41.22999954223633,41.9900016784668,42.209999084472656,42.959999084472656,47.029998779296875,51.380001068115234,54.31999969482422,56.0,59.130001068115234,58.81999969482422,64.76000213623047,68.83000183105469,68.91000366210938,83.30999755859375,82.08999633789062,86.12999725341797,100.2300033569336,89.11000061035156,89.27999877929688,112.44000244140625,118.6500015258789,117.22000122070312,115.31999969482422,123.0,125.06999969482422,131.13999938964844,117.06999969482422,103.12000274658203,101.83000183105469,89.08000183105469,95.81999969482422,99.73999786376953,89.81999969482422,73.7300033569336,71.75,74.12000274658203,79.25,89.29000091552734,94.05999755859375,101.11000061035156,100.33000183105469,98.44000244140625,101.2300033569336,107.2699966430664,104.87000274658203,110.11000061035156,108.4800033569336,115.68000030517578,113.12000274658203,113.7699966430664,122.62000274658203,126.91999816894531,131.1699981689453,129.8699951171875,129.97000122070312,117.69000244140625,117.68000030517578,101.8499984741211,69.56999969482422,62.650001525878906,74.66999816894531,81.08000183105469,94.5199966430664,97.44999694824219,94.91000366210938,94.16999816894531,104.68000030517578,116.20999908447266,116.80000305175781,103.27999877929688,101.22000122070312,117.58999633789062,118.20999908447266,123.11000061035156,125.16000366210938,134.3000030517578,142.17999267578125,148.82000732421875,152.6999969482422,160.69000244140625,167.5399932861328,176.47999572753906,171.97999572753906,183.89999389648438,189.83999633789062,172.8300018310547,177.5,169.08999633789062,184.52999877929688,192.75999450683594,192.86000061035156,208.3300018310547,215.11000061035156,222.6300048828125,233.47000122070312,238.61000061035156,238.36000061035156,250.52000427246094,216.97999572753906,226.0500030517578,254.11000061035156,262.70001220703125,266.5,206.44000244140625,228.0399932861328,287.55999755859375,302.0400085449219,341.5,371.05999755859375,393.30999755859375,397.0400085449219,385.82000732421875,339.6099853515625,330.8900146484375,330.25,351.3500061035156,374.4800109863281,394.3599853515625,380.19000244140625,432.6499938964844,458.67999267578125,478.94000244140625,525.8099975585938,510.79998779296875,0.0,574.7000122070312,605.0700073242188],"high":[21.530000686645508,22.579999923706055,22.940000534057617,25.040000915527344,26.020000457763672,27.34000015258789,27.799999237060547,27.850000381469727,30.3799991607666,31.25,33.93000030517578,32.95000076293945,27.040000915527344,27.520000457763672,27.399999618530273,28.25,29.520000457763672,32.290000915527344,34.7599983215332,35.2599983215332,34.2400016784668,35.2400016784668,35.290000915527344,32.040000915527344,35.86000061035156,37.099998474121094,37.83000183105469,39.310001373291016,39.689998626708984,39.61000061035156,40.41999816894531,42.029998779296875,43.13999938964844,42.91999816894531,43.869998931884766,44.709999084472656,45.36000061035156,43.689998626708984,45.0,44.72999954223633,47.209999084472656,51.709999084472656,55.380001068115234,58.779998779296875,61.9900016784668,63.779998779296875,64.76000213623047,71.38999938964844,76.38999938964844,84.26000213623047,89.9000015258789,92.31999969482422,102.94999694824219,105.72000122070312,110.27999877929688,115.08999633789062,122.41000366210938,126.83000183105469,131.14999389648438,135.3300018310547,141.02999877929688,139.77000427246094,140.6199951171875,132.61000061035156,126.87999725341797,121.36000061035156,114.1500015258789,108.25,108.23999786376953,105.7699966430664,91.27999877929688,86.91000366210938,85.9800033569336,93.70999908447266,96.36000061035156,102.66999816894531,107.12000274658203,106.2699966430664,104.72000122070312,112.2300033569336,113.2300033569336,112.55000305175781,114.91000366210938,117.69999694824219,120.66999816894531,122.25,123.3499984741211,131.9600067138672,134.6999969482422,142.14999389648438,143.14999389648438,144.22000122070312,133.22000122070312,131.6699981689453,120.45999908447266,106.9000015258789,86.0199966430664,87.58000183105469,99.23999786376953,104.33999633789062,108.58000183105469,112.22000122070312,105.70999908447266,116.4000015258789,123.98999786376953,125.72000122070312,124.72000122070312,118.4800033569336,130.47999572753906,130.75999450683594,135.72000122070312,134.8300018310547,144.64999389648438,154.25999450683594,159.74000549316406,170.36000061035156,173.75,181.4600067138672,186.35000610351562,192.7899932861328,195.92999267578125,197.07000732421875,196.5,194.8699951171875,190.3699951171875,196.25,202.58999633789062,210.6999969482422,221.8300018310547,227.41000366210938,232.57000732421875,249.36000061035156,265.4200134277344,258.239990234375,271.7200012207031,270.1600036621094,263.3699951171875,273.8299865722656,279.45001220703125,298.9599914550781,313.25,299.4700012207031,331.3999938964844,346.6000061035156,367.8800048828125,396.70001220703125,419.7799987792969,442.2900085449219,442.6700134277344,423.0199890136719,398.07000732421875,377.57000732421875,385.9200134277344,410.6300048828125,423.7799987792969,441.2799987792969,485.07000732421875,507.8500061035156,531.7999877929688,563.3200073242188,567.8900146484375,523.2999877929688,619.4000244140625,636.4099731445312],"volume":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"open":[19.309999465942383,21.190000534057617,22.540000915527344,21.739999771118164,22.799999237060547,25.610000610351562,27.469999313354492,25.459999084472656,24.709999084472656,29.290000915527344,30.40999984741211,32.869998931884766,25.530000686645508,25.399999618530273,26.979999542236328,27.020000457763672,26.889999389648438,28.899999618530273,31.219999313354492,34.369998931884766,34.2400016784668,32.27000045776367,34.36000061035156,30.190000534057617,30.889999389648438,35.150001525878906,35.869998931884766,37.029998779296875,39.33000183105469,38.13999938964844,39.02000045776367,39.40999984741211,40.95000076293945,42.36000061035156,42.310001373291016,43.529998779296875,43.7400016784668,41.22999954223633,41.9900016784668,43.54999923706055,42.959999084472656,47.029998779296875,51.380001068115234,54.7400016784668,58.04999923706055,61.20000076293945,63.38999938964844,64.76000213623047,68.83000183105469,70.93000030517578,83.30999755859375,89.41999816894531,90.5,102.81999969482422,106.68000030517578,91.7300033569336,113.87999725341797,119.58000183105469,127.5999984741211,118.55999755859375,134.0399932861328,138.77000427246094,135.5500030517578,132.55999755859375,118.44000244140625,105.73999786376953,114.1500015258789,95.81999969482422,106.51000213623047,105.7699966430664,89.37000274658203,78.2699966430664,83.86000061035156,79.25,90.75,94.05999755859375,102.36000061035156,104.55999755859375,104.33000183105469,104.55999755859375,110.7300033569336,108.08000183105469,110.11000061035156,113.0,116.81999969482422,119.5199966430664,117.93000030517578,122.62000274658203,130.4499969482422,131.1699981689453,139.91000366210938,142.47999572753906,133.22000122070312,126.12000274658203,118.30999755859375,106.9000015258789,85.75,74.66999816894531,85.0999984741211,94.94000244140625,104.31999969482422,108.54000091552734,94.61000061035156,105.5199966430664,117.12999725341797,122.7300033569336,123.41000366210938,101.22000122070312,117.58999633789062,130.75999450683594,125.86000061035156,133.17999267578125,134.74000549316406,143.97000122070312,148.8699951171875,156.27000427246094,168.8800048828125,173.86000061035156,182.07000732421875,179.58999633789062,189.85000610351562,189.97999572753906,191.72999572753906,177.5,185.63999938964844,191.19000244140625,194.0800018310547,199.5800018310547,208.3300018310547,217.6999969482422,224.27999877929688,233.47000122070312,248.8800048828125,238.36000061035156,251.75999450683594,270.07000732421875,231.72999572753906,264.5899963378906,273.5,271.3599853515625,300.69000244140625,228.0399932861328,287.55999755859375,312.0,341.5,371.05999755859375,398.79998779296875,402.239990234375,442.6700134277344,419.6199951171875,353.1099853515625,339.45001220703125,352.760009765625,380.5299987792969,411.1300048828125,395.70001220703125,437.57000732421875,483.92999267578125,505.2099914550781,526.8599853515625,541.6400146484375,519.7899780273438,572.0599975585938,619.52001953125],"close":[21.110000610351562,22.459999084472656,21.3799991607666,22.989999771118164,26.020000457763672,27.34000015258789,25.209999084472656,24.270000457763672,29.219999313354492,30.520000457763672,32.310001373291016,24.649999618530273,25.670000076293945,27.149999618530273,27.059999465942383,27.18000030517578,28.739999771118164,31.09000015258789,34.20000076293945,33.63999938964844,32.380001068115234,34.220001220703125,29.31999969482422,31.239999771118164,35.5099983215332,35.22999954223633,36.90999984741211,39.310001373291016,38.09000015258789,38.58000183105469,39.54999923706055,41.2599983215332,42.4900016784668,42.45000076293945,43.29999923706055,43.83000183105469,41.86000061035156,41.810001373291016,43.619998931884766,42.970001220703125,46.91999816894531,51.150001525878906,54.9900016784668,57.599998474121094,60.43000030517578,62.88999938964844,64.58999633789062,69.16000366210938,70.69000244140625,82.7300033569336,88.6500015258789,90.06999969482422,102.20999908447266,105.30000305175781,94.55999755859375,113.94999694824219,118.9000015258789,126.83000183105469,118.55000305175781,135.3300018310547,138.0800018310547,134.14999389648438,132.58999633789062,121.86000061035156,107.06999969482422,113.0199966430664,96.04000091552734,105.88999938964844,105.8499984741211,91.33000183105469,75.26000213623047,81.1500015258789,78.2699966430664,90.0199966430664,92.0,102.66999816894531,104.01000213623047,105.41000366210938,102.98999786376953,111.63999938964844,108.79000091552734,109.80999755859375,113.19999694824219,114.91999816894531,119.23999786376953,116.98999786376953,123.04000091552734,130.58999633789062,130.8300018310547,138.42999267578125,140.61000061035156,135.14999389648438,121.75,117.83000183105469,107.37000274658203,83.08999633789062,73.44000244140625,84.72000122070312,97.44999694824219,102.66999816894531,107.7300033569336,94.91000366210938,105.05999755859375,115.81999969482422,122.12000274658203,121.6500015258789,104.18000030517578,115.80000305175781,129.77999877929688,125.55000305175781,132.8300018310547,131.3699951171875,144.61000061035156,148.05999755859375,155.02000427246094,170.36000061035156,172.6300048828125,180.8300018310547,181.99000549316406,189.88999938964844,190.7100067138672,190.36000061035156,177.13999938964844,188.47999572753906,189.99000549316406,193.6699981689453,200.2100067138672,206.57000732421875,218.0500030517578,223.75,232.57000732421875,246.82000732421875,243.80999755859375,250.99000549316406,269.0899963378906,231.44000244140625,261.55999755859375,271.4100036621094,274.7099914550781,298.1600036621094,238.57000732421875,286.1199951171875,310.3299865722656,346.6000061035156,366.7300109863281,396.70001220703125,397.6700134277344,439.8699951171875,418.20001220703125,349.4200134277344,330.8900146484375,354.1700134277344,379.1300048828125,410.6300048828125,395.6700134277344,440.05999755859375,484.9100036621094,503.8500061035156,531.7999877929688,542.8400268554688,517.8200073242188,572.6900024414062,617.3900146484375,636.4099731445312]}],"adjclose":[{"adjclose":[8.32298755645752,8.931220054626465,8.570080757141113,9.29338550567627,10.679168701171875,11.29909610748291,10.488005638122559,10.1694974899292,12.4162015914917,13.046053886413574,13.891845703125,10.657750129699707,11.24372386932373,12.065445899963379,12.106531143188477,12.242218017578125,13.274335861206055,14.53659439086914,16.080352783203125,15.901507377624512,15.877748489379883,16.898649215698242,14.556342124938965,15.604056358337402,18.12221336364746,18.10554313659668,19.065780639648438,20.4047794342041,20.11356544494629,20.4888973236084,21.125171661376953,22.16135025024414,23.13153648376465,23.246736526489258,23.836483001708984,24.252519607543945,23.408536911010742,23.53997230529785,24.68636703491211,24.441099166870117,27.07647705078125,29.657625198364258,32.02126693725586,33.675994873046875,35.75786590576172,37.372589111328125,38.51747512817383,41.3836555480957,42.80373764038086,50.3044548034668,54.07786560058594,55.11066436767578,63.287906646728516,65.45027160644531,58.92605972290039,71.20695495605469,74.84698486328125,80.3244400024414,75.31525421142578,86.23001861572266,88.61433410644531,86.27568817138672,85.46988677978516,78.74110412597656,69.40200805664062,73.45497131347656,62.57225799560547,69.23285675048828,69.45639038085938,60.09198760986328,49.67857360839844,53.80402755737305,52.160247802734375,60.21516036987305,61.740623474121094,69.16889190673828,70.39777374267578,71.59611511230469,70.18480682373047,76.38420867919922,74.98882293701172,75.9935073852539,78.63690185546875,80.20995330810547,83.65692901611328,82.41303253173828,87.03762817382812,92.77420806884766,93.40992736816406,99.24781036376953,101.22080993652344,97.72164154052734,88.524169921875,86.09046173095703,78.81550598144531,61.34762191772461,54.69839096069336,63.54470443725586,73.47935485839844,77.80303192138672,82.15636444091797,72.67402648925781,80.85675048828125,89.58963012695312,94.8654556274414,94.89974212646484,81.63887023925781,91.21275329589844,102.80589294433594,99.87061309814453,106.18683624267578,105.52758026123047,116.95392608642578,120.27367401123047,126.52731323242188,139.7021942138672,142.29808044433594,149.69137573242188,151.2869110107422,158.55694580078125,160.1008758544922,160.5821075439453,150.0870361328125,160.4878387451172,162.669189453125,166.66786193847656,173.11500549316406,179.38963317871094,190.49301147460938,196.3304443359375,204.94107055664062,218.56089782714844,216.8767852783203,224.2040557861328,241.42239379882812,208.52943420410156,236.84742736816406,247.07424926757812,251.2937469482422,273.96966552734375,220.22622680664062,265.19256591796875,289.0013732910156,324.08636474609375,344.2227478027344,373.56304931640625,375.6450500488281,416.7265930175781,397.53131103515625,333.15582275390625,316.65399169921875,340.3175354003906,365.93609619140625,397.8581237792969,384.76239013671875,429.4373474121094,475.06109619140625,495.0646057128906,524.232666015625,536.638671875,513.4124145507812,569.60595703125,615.7952880859375,636.4099731445312]}]}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/holders_api_institutionOwnership-fundOwnership-majorHoldersBreakdown-insiderTransactions-insiderHolders-netSharePurchaseActivity_AAPL.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/holders_api_institutionOwnership-fundOwnership-majorHoldersBreakdown-insiderTransactions-insiderHolders-netSharePurchaseActivity_AAPL.json new file mode 100644 index 0000000..ba1be2c --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/holders_api_institutionOwnership-fundOwnership-majorHoldersBreakdown-insiderTransactions-insiderHolders-netSharePurchaseActivity_AAPL.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"institutionOwnership":{"maxAge":1,"ownershipList":[{"maxAge":1,"reportDate":{"raw":1751241600,"fmt":"2025-06-30"},"organization":"Vanguard Group Inc","pctHeld":{"raw":0.0954,"fmt":"9.54%"},"position":{"raw":1415932804,"fmt":"1.42B","longFmt":"1,415,932,804"},"value":{"raw":381877094523,"fmt":"381.88B","longFmt":"381,877,094,523"},"pctChange":{"raw":0.0108,"fmt":"1.08%"}},{"maxAge":1,"reportDate":{"raw":1751241600,"fmt":"2025-06-30"},"organization":"Blackrock Inc.","pctHeld":{"raw":0.0774,"fmt":"7.74%"},"position":{"raw":1148838990,"fmt":"1.15B","longFmt":"1,148,838,990"},"value":{"raw":309841889626,"fmt":"309.84B","longFmt":"309,841,889,626"},"pctChange":{"raw":0.0076,"fmt":"0.76%"}},{"maxAge":1,"reportDate":{"raw":1751241600,"fmt":"2025-06-30"},"organization":"State Street Corporation","pctHeld":{"raw":0.0405,"fmt":"4.05%"},"position":{"raw":601249995,"fmt":"601.25M","longFmt":"601,249,995"},"value":{"raw":162157130990,"fmt":"162.16B","longFmt":"162,157,130,990"},"pctChange":{"raw":0.0088,"fmt":"0.88%"}},{"maxAge":1,"reportDate":{"raw":1751241600,"fmt":"2025-06-30"},"organization":"Geode Capital Management, LLC","pctHeld":{"raw":0.0239,"fmt":"2.39%"},"position":{"raw":354749794,"fmt":"354.75M","longFmt":"354,749,794"},"value":{"raw":95676023772,"fmt":"95.68B","longFmt":"95,676,023,772"},"pctChange":{"raw":0.014099999,"fmt":"1.41%"}},{"maxAge":1,"reportDate":{"raw":1751241600,"fmt":"2025-06-30"},"organization":"FMR, LLC","pctHeld":{"raw":0.0207,"fmt":"2.07%"},"position":{"raw":306758594,"fmt":"306.76M","longFmt":"306,758,594"},"value":{"raw":82732796546,"fmt":"82.73B","longFmt":"82,732,796,546"},"pctChange":{"raw":-0.065,"fmt":"-6.50%"}},{"maxAge":1,"reportDate":{"raw":1751241600,"fmt":"2025-06-30"},"organization":"Berkshire Hathaway, Inc","pctHeld":{"raw":0.0189,"fmt":"1.89%"},"position":{"raw":280000000,"fmt":"280M","longFmt":"280,000,000"},"value":{"raw":75516003417,"fmt":"75.52B","longFmt":"75,516,003,417"},"pctChange":{"raw":-0.066700004,"fmt":"-6.67%"}},{"maxAge":1,"reportDate":{"raw":1751241600,"fmt":"2025-06-30"},"organization":"Morgan Stanley","pctHeld":{"raw":0.015700001,"fmt":"1.57%"},"position":{"raw":233198646,"fmt":"233.2M","longFmt":"233,198,646"},"value":{"raw":62893677672,"fmt":"62.89B","longFmt":"62,893,677,672"},"pctChange":{"raw":-0.033299997,"fmt":"-3.33%"}},{"maxAge":1,"reportDate":{"raw":1751241600,"fmt":"2025-06-30"},"organization":"JPMORGAN CHASE & CO","pctHeld":{"raw":0.0145000005,"fmt":"1.45%"},"position":{"raw":214606399,"fmt":"214.61M","longFmt":"214,606,399"},"value":{"raw":57879348430,"fmt":"57.88B","longFmt":"57,879,348,430"},"pctChange":{"raw":0.0798,"fmt":"7.98%"}},{"maxAge":1,"reportDate":{"raw":1751241600,"fmt":"2025-06-30"},"organization":"Price (T.Rowe) Associates Inc","pctHeld":{"raw":0.0137,"fmt":"1.37%"},"position":{"raw":202720404,"fmt":"202.72M","longFmt":"202,720,404"},"value":{"raw":54673695433,"fmt":"54.67B","longFmt":"54,673,695,433"},"pctChange":{"raw":-0.0558,"fmt":"-5.58%"}},{"maxAge":1,"reportDate":{"raw":1751241600,"fmt":"2025-06-30"},"organization":"NORGES BANK","pctHeld":{"raw":0.0128,"fmt":"1.28%"},"position":{"raw":189804820,"fmt":"189.8M","longFmt":"189,804,820"},"value":{"raw":51190362270,"fmt":"51.19B","longFmt":"51,190,362,270"},"pctChange":{"raw":0.014099999,"fmt":"1.41%"}}]},"majorHoldersBreakdown":{"maxAge":1,"insidersPercentHeld":{"raw":0.0197,"fmt":"1.97%"},"institutionsPercentHeld":{"raw":0.63572,"fmt":"63.57%"},"institutionsFloatPercentHeld":{"raw":0.64849,"fmt":"64.85%"},"institutionsCount":{"raw":6978,"fmt":"6.98k","longFmt":"6,978"}},"insiderHolders":{"holders":[{"maxAge":1,"name":"ADAMS KATHERINE L","relation":"General Counsel","url":"","transactionDescription":"Sale","latestTransDate":{"raw":1759363200,"fmt":"2025-10-02"},"positionDirect":{"raw":179158,"fmt":"179.16k","longFmt":"179,158"},"positionDirectDate":{"raw":1759363200,"fmt":"2025-10-02"}},{"maxAge":1,"name":"AUSTIN WANDA M","relation":"Director","url":"","transactionDescription":"Conversion of Exercise of derivative security","latestTransDate":{"raw":1738281600,"fmt":"2025-01-31"},"positionDirect":{"raw":1588,"fmt":"1.59k","longFmt":"1,588"},"positionDirectDate":{"raw":1738281600,"fmt":"2025-01-31"}},{"maxAge":1,"name":"COOK TIMOTHY D","relation":"Chief Executive Officer","url":"","transactionDescription":"Sale","latestTransDate":{"raw":1759363200,"fmt":"2025-10-02"},"positionDirect":{"raw":3280300,"fmt":"3.28M","longFmt":"3,280,300"},"positionDirectDate":{"raw":1759363200,"fmt":"2025-10-02"}},{"maxAge":1,"name":"KHAN SABIH","relation":"Chief Operating Officer","url":"","transactionDescription":"Conversion of Exercise of derivative security","latestTransDate":{"raw":1759276800,"fmt":"2025-10-01"},"positionDirect":{"raw":1074400,"fmt":"1.07M","longFmt":"1,074,400"},"positionDirectDate":{"raw":1759276800,"fmt":"2025-10-01"}},{"maxAge":1,"name":"KONDO CHRISTOPHER","relation":"Officer","url":"","transactionDescription":"Conversion of Exercise of derivative security","latestTransDate":{"raw":1760486400,"fmt":"2025-10-15"},"positionDirect":{"raw":18850,"fmt":"18.85k","longFmt":"18,850"},"positionDirectDate":{"raw":1760486400,"fmt":"2025-10-15"}},{"maxAge":1,"name":"LEVINSON ARTHUR D","relation":"Director","url":"","transactionDescription":"Sale","latestTransDate":{"raw":1756339200,"fmt":"2025-08-28"},"positionDirect":{"raw":4125580,"fmt":"4.13M","longFmt":"4,125,580"},"positionDirectDate":{"raw":1756339200,"fmt":"2025-08-28"}},{"maxAge":1,"name":"O'BRIEN DEIRDRE","relation":"Officer","url":"","transactionDescription":"Sale","latestTransDate":{"raw":1759363200,"fmt":"2025-10-02"},"positionDirect":{"raw":136687,"fmt":"136.69k","longFmt":"136,687"},"positionDirectDate":{"raw":1759363200,"fmt":"2025-10-02"}},{"maxAge":1,"name":"PAREKH KEVAN","relation":"Chief Financial Officer","url":"","transactionDescription":"Sale","latestTransDate":{"raw":1760572800,"fmt":"2025-10-16"},"positionDirect":{"raw":8765,"fmt":"8.77k","longFmt":"8,765"},"positionDirectDate":{"raw":1760572800,"fmt":"2025-10-16"}},{"maxAge":1,"name":"SUGAR RONALD D","relation":"Director","url":"","transactionDescription":"Conversion of Exercise of derivative security","latestTransDate":{"raw":1738281600,"fmt":"2025-01-31"},"positionDirect":{"raw":109311,"fmt":"109.31k","longFmt":"109,311"},"positionDirectDate":{"raw":1738281600,"fmt":"2025-01-31"}},{"maxAge":1,"name":"WILLIAMS JEFFREY E","relation":"Chief Operating Officer","url":"","transactionDescription":"Sale","latestTransDate":{"raw":1743552000,"fmt":"2025-04-02"},"positionDirect":{"raw":390059,"fmt":"390.06k","longFmt":"390,059"},"positionDirectDate":{"raw":1743552000,"fmt":"2025-04-02"}}],"maxAge":1},"netSharePurchaseActivity":{"maxAge":1,"period":"6m","buyInfoCount":{"raw":7,"fmt":"7","longFmt":"7"},"buyInfoShares":{"raw":578678,"fmt":"578.68k","longFmt":"578,678"},"buyPercentInsiderShares":{"raw":0.002,"fmt":"0.20%"},"sellInfoCount":{"raw":7,"fmt":"7","longFmt":"7"},"sellInfoShares":{"raw":353607,"fmt":"353.61k","longFmt":"353,607"},"sellPercentInsiderShares":{"raw":0.001,"fmt":"0.10%"},"netInfoCount":{"raw":14,"fmt":"14","longFmt":"14"},"netInfoShares":{"raw":225071,"fmt":"225.07k","longFmt":"225,071"},"netPercentInsiderShares":{"raw":0.001,"fmt":"0.10%"},"totalInsiderShares":{"raw":292355680,"fmt":"292.36M","longFmt":"292,355,680"}},"insiderTransactions":{"transactions":[{"maxAge":1,"shares":{"raw":4199,"fmt":"4.2k","longFmt":"4,199"},"value":{"raw":1038787,"fmt":"1.04M","longFmt":"1,038,787"},"filerUrl":"","transactionText":"Sale at price 245.89 - 248.73 per share.","filerName":"PAREKH KEVAN","filerRelation":"Chief Financial Officer","moneyText":"","startDate":{"raw":1760572800,"fmt":"2025-10-16"},"ownership":"D"},{"maxAge":1,"shares":{"raw":16457,"fmt":"16.46k","longFmt":"16,457"},"filerUrl":"","transactionText":"","filerName":"PAREKH KEVAN","filerRelation":"Chief Financial Officer","moneyText":"","startDate":{"raw":1760486400,"fmt":"2025-10-15"},"ownership":"D"},{"maxAge":1,"shares":{"raw":7371,"fmt":"7.37k","longFmt":"7,371"},"filerUrl":"","transactionText":"","filerName":"KONDO CHRISTOPHER","filerRelation":"Officer","moneyText":"","startDate":{"raw":1760486400,"fmt":"2025-10-15"},"ownership":"D"},{"maxAge":1,"shares":{"raw":43013,"fmt":"43.01k","longFmt":"43,013"},"value":{"raw":11071078,"fmt":"11.07M","longFmt":"11,071,078"},"filerUrl":"","transactionText":"Sale at price 257.36 - 258.08 per share.","filerName":"O'BRIEN DEIRDRE","filerRelation":"Officer","moneyText":"","startDate":{"raw":1759363200,"fmt":"2025-10-02"},"ownership":"D"},{"maxAge":1,"shares":{"raw":47125,"fmt":"47.12k","longFmt":"47,125"},"value":{"raw":12101154,"fmt":"12.1M","longFmt":"12,101,154"},"filerUrl":"","transactionText":"Sale at price 254.83 - 257.54 per share.","filerName":"ADAMS KATHERINE L","filerRelation":"General Counsel","moneyText":"","startDate":{"raw":1759363200,"fmt":"2025-10-02"},"ownership":"D"},{"maxAge":1,"shares":{"raw":129963,"fmt":"129.96k","longFmt":"129,963"},"value":{"raw":33375723,"fmt":"33.38M","longFmt":"33,375,723"},"filerUrl":"","transactionText":"Sale at price 254.83 - 257.57 per share.","filerName":"COOK TIMOTHY D","filerRelation":"Chief Executive Officer","moneyText":"","startDate":{"raw":1759363200,"fmt":"2025-10-02"},"ownership":"D"},{"maxAge":1,"shares":{"raw":92403,"fmt":"92.4k","longFmt":"92,403"},"filerUrl":"","transactionText":"","filerName":"KHAN SABIH","filerRelation":"Chief Operating Officer","moneyText":"","startDate":{"raw":1759276800,"fmt":"2025-10-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":92403,"fmt":"92.4k","longFmt":"92,403"},"filerUrl":"","transactionText":"","filerName":"O'BRIEN DEIRDRE","filerRelation":"Officer","moneyText":"","startDate":{"raw":1759276800,"fmt":"2025-10-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":277206,"fmt":"277.21k","longFmt":"277,206"},"filerUrl":"","transactionText":"","filerName":"COOK TIMOTHY D","filerRelation":"Chief Executive Officer","moneyText":"","startDate":{"raw":1759276800,"fmt":"2025-10-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":92403,"fmt":"92.4k","longFmt":"92,403"},"filerUrl":"","transactionText":"","filerName":"ADAMS KATHERINE L","filerRelation":"General Counsel","moneyText":"","startDate":{"raw":1759276800,"fmt":"2025-10-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":90000,"fmt":"90k","longFmt":"90,000"},"value":{"raw":20886300,"fmt":"20.89M","longFmt":"20,886,300"},"filerUrl":"","transactionText":"Sale at price 232.07 per share.","filerName":"LEVINSON ARTHUR D","filerRelation":"Director","moneyText":"","startDate":{"raw":1756339200,"fmt":"2025-08-28"},"ownership":"D"},{"maxAge":1,"shares":{"raw":435,"fmt":"435","longFmt":"435"},"value":{"raw":0,"fmt":null,"longFmt":"0"},"filerUrl":"","transactionText":"Stock Gift at price 0.00 per share.","filerName":"KONDO CHRISTOPHER","filerRelation":"Officer","moneyText":"","startDate":{"raw":1756080000,"fmt":"2025-08-25"},"ownership":"D"},{"maxAge":1,"shares":{"raw":34821,"fmt":"34.82k","longFmt":"34,821"},"value":{"raw":7772047,"fmt":"7.77M","longFmt":"7,772,047"},"filerUrl":"","transactionText":"Sale at price 223.20 per share.","filerName":"O'BRIEN DEIRDRE","filerRelation":"Officer","moneyText":"","startDate":{"raw":1754611200,"fmt":"2025-08-08"},"ownership":"D"},{"maxAge":1,"shares":{"raw":4486,"fmt":"4.49k","longFmt":"4,486"},"value":{"raw":933955,"fmt":"933.96k","longFmt":"933,955"},"filerUrl":"","transactionText":"Sale at price 208.19 per share.","filerName":"KONDO CHRISTOPHER","filerRelation":"Officer","moneyText":"","startDate":{"raw":1747008000,"fmt":"2025-05-12"},"ownership":"D"},{"maxAge":1,"shares":{"raw":4570,"fmt":"4.57k","longFmt":"4,570"},"value":{"raw":941420,"fmt":"941.42k","longFmt":"941,420"},"filerUrl":"","transactionText":"Sale at price 206.00 per share.","filerName":"PAREKH KEVAN","filerRelation":"Chief Financial Officer","moneyText":"","startDate":{"raw":1745366400,"fmt":"2025-04-23"},"ownership":"D"},{"maxAge":1,"shares":{"raw":16458,"fmt":"16.46k","longFmt":"16,458"},"filerUrl":"","transactionText":"","filerName":"PAREKH KEVAN","filerRelation":"Chief Financial Officer","moneyText":"","startDate":{"raw":1744675200,"fmt":"2025-04-15"},"ownership":"D"},{"maxAge":1,"shares":{"raw":7373,"fmt":"7.37k","longFmt":"7,373"},"filerUrl":"","transactionText":"","filerName":"KONDO CHRISTOPHER","filerRelation":"Officer","moneyText":"","startDate":{"raw":1744675200,"fmt":"2025-04-15"},"ownership":"D"},{"maxAge":1,"shares":{"raw":35493,"fmt":"35.49k","longFmt":"35,493"},"value":{"raw":7950691,"fmt":"7.95M","longFmt":"7,950,691"},"filerUrl":"","transactionText":"Sale at price 223.48 - 225.03 per share.","filerName":"WILLIAMS JEFFREY E","filerRelation":"Chief Operating Officer","moneyText":"","startDate":{"raw":1743552000,"fmt":"2025-04-02"},"ownership":"D"},{"maxAge":1,"shares":{"raw":38822,"fmt":"38.82k","longFmt":"38,822"},"value":{"raw":8683252,"fmt":"8.68M","longFmt":"8,683,252"},"filerUrl":"","transactionText":"Sale at price 221.68 - 224.62 per share.","filerName":"ADAMS KATHERINE L","filerRelation":"General Counsel","moneyText":"","startDate":{"raw":1743552000,"fmt":"2025-04-02"},"ownership":"D"},{"maxAge":1,"shares":{"raw":108136,"fmt":"108.14k","longFmt":"108,136"},"value":{"raw":24184658,"fmt":"24.18M","longFmt":"24,184,658"},"filerUrl":"","transactionText":"Sale at price 221.77 - 224.76 per share.","filerName":"COOK TIMOTHY D","filerRelation":"Chief Executive Officer","moneyText":"","startDate":{"raw":1743552000,"fmt":"2025-04-02"},"ownership":"D"},{"maxAge":1,"shares":{"raw":74535,"fmt":"74.53k","longFmt":"74,535"},"filerUrl":"","transactionText":"","filerName":"O'BRIEN DEIRDRE","filerRelation":"Officer","moneyText":"","startDate":{"raw":1743465600,"fmt":"2025-04-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":74535,"fmt":"74.53k","longFmt":"74,535"},"filerUrl":"","transactionText":"","filerName":"WILLIAMS JEFFREY E","filerRelation":"Chief Operating Officer","moneyText":"","startDate":{"raw":1743465600,"fmt":"2025-04-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":218568,"fmt":"218.57k","longFmt":"218,568"},"filerUrl":"","transactionText":"","filerName":"COOK TIMOTHY D","filerRelation":"Chief Executive Officer","moneyText":"","startDate":{"raw":1743465600,"fmt":"2025-04-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":74535,"fmt":"74.53k","longFmt":"74,535"},"filerUrl":"","transactionText":"","filerName":"ADAMS KATHERINE L","filerRelation":"General Counsel","moneyText":"","startDate":{"raw":1743465600,"fmt":"2025-04-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":1516,"fmt":"1.52k","longFmt":"1,516"},"value":{"raw":343147,"fmt":"343.15k","longFmt":"343,147"},"filerUrl":"","transactionText":"Sale at price 226.35 per share.","filerName":"LEVINSON ARTHUR D","filerRelation":"Director","moneyText":"","startDate":{"raw":1738540800,"fmt":"2025-02-03"},"ownership":"D"},{"maxAge":1,"shares":{"raw":1516,"fmt":"1.52k","longFmt":"1,516"},"filerUrl":"","transactionText":"","filerName":"SUGAR RONALD D","filerRelation":"Director","moneyText":"","startDate":{"raw":1738281600,"fmt":"2025-01-31"},"ownership":"D"},{"maxAge":1,"shares":{"raw":1516,"fmt":"1.52k","longFmt":"1,516"},"filerUrl":"","transactionText":"","filerName":"LEVINSON ARTHUR D","filerRelation":"Director","moneyText":"","startDate":{"raw":1738281600,"fmt":"2025-01-31"},"ownership":"D"},{"maxAge":1,"shares":{"raw":1516,"fmt":"1.52k","longFmt":"1,516"},"filerUrl":"","transactionText":"","filerName":"AUSTIN WANDA M","filerRelation":"Director","moneyText":"","startDate":{"raw":1738281600,"fmt":"2025-01-31"},"ownership":"D"},{"maxAge":1,"shares":{"raw":1516,"fmt":"1.52k","longFmt":"1,516"},"filerUrl":"","transactionText":"","filerName":"LOZANO MONICA C.","filerRelation":"Director","moneyText":"","startDate":{"raw":1738281600,"fmt":"2025-01-31"},"ownership":"D"},{"maxAge":1,"shares":{"raw":1516,"fmt":"1.52k","longFmt":"1,516"},"filerUrl":"","transactionText":"","filerName":"WAGNER SUSAN L","filerRelation":"Director","moneyText":"","startDate":{"raw":1738281600,"fmt":"2025-01-31"},"ownership":"D"},{"maxAge":1,"shares":{"raw":1516,"fmt":"1.52k","longFmt":"1,516"},"filerUrl":"","transactionText":"","filerName":"JUNG ANDREA","filerRelation":"Director","moneyText":"","startDate":{"raw":1738281600,"fmt":"2025-01-31"},"ownership":"D"},{"maxAge":1,"shares":{"raw":1516,"fmt":"1.52k","longFmt":"1,516"},"filerUrl":"","transactionText":"","filerName":"GORSKY ALEX","filerRelation":"Director","moneyText":"","startDate":{"raw":1738281600,"fmt":"2025-01-31"},"ownership":"D"},{"maxAge":1,"shares":{"raw":100000,"fmt":"100k","longFmt":"100,000"},"value":{"raw":24997395,"fmt":"25M","longFmt":"24,997,395"},"filerUrl":"","transactionText":"Sale at price 248.61 - 251.10 per share.","filerName":"WILLIAMS JEFFREY E","filerRelation":"Chief Operating Officer","moneyText":"","startDate":{"raw":1734307200,"fmt":"2024-12-16"},"ownership":"I"},{"maxAge":1,"shares":{"raw":200000,"fmt":"200k","longFmt":"200,000"},"value":{"raw":45464500,"fmt":"45.46M","longFmt":"45,464,500"},"filerUrl":"","transactionText":"Sale at price 224.68 - 229.28 per share.","filerName":"LEVINSON ARTHUR D","filerRelation":"Director","moneyText":"","startDate":{"raw":1731974400,"fmt":"2024-11-19"},"ownership":"D"},{"maxAge":1,"shares":{"raw":4130,"fmt":"4.13k","longFmt":"4,130"},"value":{"raw":945233,"fmt":"945.23k","longFmt":"945,233"},"filerUrl":"","transactionText":"Sale at price 228.87 per share.","filerName":"KONDO CHRISTOPHER","filerRelation":"Officer","moneyText":"","startDate":{"raw":1731888000,"fmt":"2024-11-18"},"ownership":"D"},{"maxAge":1,"shares":{"raw":8000,"fmt":"8k","longFmt":"8,000"},"value":{"raw":0,"fmt":null,"longFmt":"0"},"filerUrl":"","transactionText":"Stock Gift at price 0.00 per share.","filerName":"ADAMS KATHERINE L","filerRelation":"General Counsel","moneyText":"","startDate":{"raw":1730764800,"fmt":"2024-11-05"},"ownership":"D"},{"maxAge":1,"shares":{"raw":8115,"fmt":"8.12k","longFmt":"8,115"},"filerUrl":"","transactionText":"","filerName":"KONDO CHRISTOPHER","filerRelation":"Officer","moneyText":"","startDate":{"raw":1728950400,"fmt":"2024-10-15"},"ownership":"D"},{"maxAge":1,"shares":{"raw":59305,"fmt":"59.3k","longFmt":"59,305"},"value":{"raw":13433769,"fmt":"13.43M","longFmt":"13,433,769"},"filerUrl":"","transactionText":"Sale at price 226.52 per share.","filerName":"MAESTRI LUCA","filerRelation":"Chief Financial Officer","moneyText":"","startDate":{"raw":1728000000,"fmt":"2024-10-04"},"ownership":"D"},{"maxAge":1,"shares":{"raw":61019,"fmt":"61.02k","longFmt":"61,019"},"value":{"raw":13843382,"fmt":"13.84M","longFmt":"13,843,382"},"filerUrl":"","transactionText":"Sale at price 226.72 - 227.13 per share.","filerName":"O'BRIEN DEIRDRE","filerRelation":"Officer","moneyText":"","startDate":{"raw":1727827200,"fmt":"2024-10-02"},"ownership":"D"},{"maxAge":1,"shares":{"raw":59730,"fmt":"59.73k","longFmt":"59,730"},"value":{"raw":13550148,"fmt":"13.55M","longFmt":"13,550,148"},"filerUrl":"","transactionText":"Sale at price 226.80 - 227.22 per share.","filerName":"WILLIAMS JEFFREY E","filerRelation":"Chief Operating Officer","moneyText":"","startDate":{"raw":1727827200,"fmt":"2024-10-02"},"ownership":"D"},{"maxAge":1,"shares":{"raw":61019,"fmt":"61.02k","longFmt":"61,019"},"value":{"raw":13802297,"fmt":"13.8M","longFmt":"13,802,297"},"filerUrl":"","transactionText":"Sale at price 223.79 - 227.24 per share.","filerName":"ADAMS KATHERINE L","filerRelation":"General Counsel","moneyText":"","startDate":{"raw":1727827200,"fmt":"2024-10-02"},"ownership":"D"},{"maxAge":1,"shares":{"raw":223986,"fmt":"223.99k","longFmt":"223,986"},"value":{"raw":50276355,"fmt":"50.28M","longFmt":"50,276,355"},"filerUrl":"","transactionText":"Sale at price 223.75 - 226.57 per share.","filerName":"COOK TIMOTHY D","filerRelation":"Chief Executive Officer","moneyText":"","startDate":{"raw":1727827200,"fmt":"2024-10-02"},"ownership":"D"},{"maxAge":1,"shares":{"raw":127282,"fmt":"127.28k","longFmt":"127,282"},"filerUrl":"","transactionText":"","filerName":"O'BRIEN DEIRDRE","filerRelation":"Officer","moneyText":"","startDate":{"raw":1727740800,"fmt":"2024-10-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":127282,"fmt":"127.28k","longFmt":"127,282"},"filerUrl":"","transactionText":"","filerName":"MAESTRI LUCA","filerRelation":"Chief Financial Officer","moneyText":"","startDate":{"raw":1727740800,"fmt":"2024-10-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":127282,"fmt":"127.28k","longFmt":"127,282"},"filerUrl":"","transactionText":"","filerName":"WILLIAMS JEFFREY E","filerRelation":"Chief Operating Officer","moneyText":"","startDate":{"raw":1727740800,"fmt":"2024-10-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":477301,"fmt":"477.3k","longFmt":"477,301"},"filerUrl":"","transactionText":"","filerName":"COOK TIMOTHY D","filerRelation":"Chief Executive Officer","moneyText":"","startDate":{"raw":1727740800,"fmt":"2024-10-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":127282,"fmt":"127.28k","longFmt":"127,282"},"filerUrl":"","transactionText":"","filerName":"ADAMS KATHERINE L","filerRelation":"General Counsel","moneyText":"","startDate":{"raw":1727740800,"fmt":"2024-10-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":8706,"fmt":"8.71k","longFmt":"8,706"},"value":{"raw":1958850,"fmt":"1.96M","longFmt":"1,958,850"},"filerUrl":"","transactionText":"Sale at price 225.00 per share.","filerName":"KONDO CHRISTOPHER","filerRelation":"Officer","moneyText":"","startDate":{"raw":1723680000,"fmt":"2024-08-15"},"ownership":"D"},{"maxAge":1,"shares":{"raw":5178,"fmt":"5.18k","longFmt":"5,178"},"value":{"raw":1121037,"fmt":"1.12M","longFmt":"1,121,037"},"filerUrl":"","transactionText":"Sale at price 216.50 per share.","filerName":"KONDO CHRISTOPHER","filerRelation":"Officer","moneyText":"","startDate":{"raw":1723161600,"fmt":"2024-08-09"},"ownership":"D"},{"maxAge":1,"shares":{"raw":4500,"fmt":"4.5k","longFmt":"4,500"},"value":{"raw":0,"fmt":null,"longFmt":"0"},"filerUrl":"","transactionText":"Stock Gift at price 0.00 per share.","filerName":"ADAMS KATHERINE L","filerRelation":"General Counsel","moneyText":"","startDate":{"raw":1722988800,"fmt":"2024-08-07"},"ownership":"D"},{"maxAge":1,"shares":{"raw":100000,"fmt":"100k","longFmt":"100,000"},"value":{"raw":20643512,"fmt":"20.64M","longFmt":"20,643,512"},"filerUrl":"","transactionText":"Sale at price 206.42 - 207.05 per share.","filerName":"ADAMS KATHERINE L","filerRelation":"General Counsel","moneyText":"","startDate":{"raw":1722816000,"fmt":"2024-08-05"},"ownership":"D"},{"maxAge":1,"shares":{"raw":75000,"fmt":"75k","longFmt":"75,000"},"value":{"raw":14368500,"fmt":"14.37M","longFmt":"14,368,500"},"filerUrl":"","transactionText":"Sale at price 191.58 per share.","filerName":"LEVINSON ARTHUR D","filerRelation":"Director","moneyText":"","startDate":{"raw":1717027200,"fmt":"2024-05-30"},"ownership":"D"},{"maxAge":1,"shares":{"raw":4999,"fmt":"5k","longFmt":"4,999"},"value":{"raw":951785,"fmt":"951.78k","longFmt":"951,785"},"filerUrl":"","transactionText":"Sale at price 190.40 per share.","filerName":"KONDO CHRISTOPHER","filerRelation":"Officer","moneyText":"","startDate":{"raw":1715731200,"fmt":"2024-05-15"},"ownership":"D"},{"maxAge":1,"shares":{"raw":1850,"fmt":"1.85k","longFmt":"1,850"},"value":{"raw":0,"fmt":null,"longFmt":"0"},"filerUrl":"","transactionText":"Stock Gift at price 0.00 per share.","filerName":"ADAMS KATHERINE L","filerRelation":"General Counsel","moneyText":"","startDate":{"raw":1715299200,"fmt":"2024-05-10"},"ownership":"D"},{"maxAge":1,"shares":{"raw":8119,"fmt":"8.12k","longFmt":"8,119"},"filerUrl":"","transactionText":"","filerName":"KONDO CHRISTOPHER","filerRelation":"Officer","moneyText":"","startDate":{"raw":1713139200,"fmt":"2024-04-15"},"ownership":"D"},{"maxAge":1,"shares":{"raw":53194,"fmt":"53.19k","longFmt":"53,194"},"value":{"raw":9261933,"fmt":"9.26M","longFmt":"9,261,933"},"filerUrl":"","transactionText":"Sale at price 173.19 - 175.02 per share.","filerName":"MAESTRI LUCA","filerRelation":"Chief Financial Officer","moneyText":"","startDate":{"raw":1712793600,"fmt":"2024-04-11"},"ownership":"D"},{"maxAge":1,"shares":{"raw":59162,"fmt":"59.16k","longFmt":"59,162"},"value":{"raw":10188880,"fmt":"10.19M","longFmt":"10,188,880"},"filerUrl":"","transactionText":"Sale at price 172.22 per share.","filerName":"WILLIAMS JEFFREY E","filerRelation":"Chief Operating Officer","moneyText":"","startDate":{"raw":1712793600,"fmt":"2024-04-11"},"ownership":"I"},{"maxAge":1,"shares":{"raw":54732,"fmt":"54.73k","longFmt":"54,732"},"value":{"raw":9244782,"fmt":"9.24M","longFmt":"9,244,782"},"filerUrl":"","transactionText":"Sale at price 168.91 per share.","filerName":"O'BRIEN DEIRDRE","filerRelation":"Officer","moneyText":"","startDate":{"raw":1712016000,"fmt":"2024-04-02"},"ownership":"D"},{"maxAge":1,"shares":{"raw":54732,"fmt":"54.73k","longFmt":"54,732"},"value":{"raw":9244235,"fmt":"9.24M","longFmt":"9,244,235"},"filerUrl":"","transactionText":"Sale at price 168.90 per share.","filerName":"ADAMS KATHERINE L","filerRelation":"General Counsel","moneyText":"","startDate":{"raw":1712016000,"fmt":"2024-04-02"},"ownership":"D"},{"maxAge":1,"shares":{"raw":196410,"fmt":"196.41k","longFmt":"196,410"},"value":{"raw":33258614,"fmt":"33.26M","longFmt":"33,258,614"},"filerUrl":"","transactionText":"Sale at price 168.62 - 170.03 per share.","filerName":"COOK TIMOTHY D","filerRelation":"Chief Executive Officer","moneyText":"","startDate":{"raw":1712016000,"fmt":"2024-04-02"},"ownership":"D"},{"maxAge":1,"shares":{"raw":113309,"fmt":"113.31k","longFmt":"113,309"},"filerUrl":"","transactionText":"","filerName":"O'BRIEN DEIRDRE","filerRelation":"Officer","moneyText":"","startDate":{"raw":1711929600,"fmt":"2024-04-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":113309,"fmt":"113.31k","longFmt":"113,309"},"filerUrl":"","transactionText":"","filerName":"MAESTRI LUCA","filerRelation":"Chief Financial Officer","moneyText":"","startDate":{"raw":1711929600,"fmt":"2024-04-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":113309,"fmt":"113.31k","longFmt":"113,309"},"filerUrl":"","transactionText":"","filerName":"WILLIAMS JEFFREY E","filerRelation":"Chief Operating Officer","moneyText":"","startDate":{"raw":1711929600,"fmt":"2024-04-01"},"ownership":"I"},{"maxAge":1,"shares":{"raw":196410,"fmt":"196.41k","longFmt":"196,410"},"filerUrl":"","transactionText":"","filerName":"COOK TIMOTHY D","filerRelation":"Chief Executive Officer","moneyText":"","startDate":{"raw":1711929600,"fmt":"2024-04-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":113309,"fmt":"113.31k","longFmt":"113,309"},"filerUrl":"","transactionText":"","filerName":"ADAMS KATHERINE L","filerRelation":"General Counsel","moneyText":"","startDate":{"raw":1711929600,"fmt":"2024-04-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":100000,"fmt":"100k","longFmt":"100,000"},"value":{"raw":18094000,"fmt":"18.09M","longFmt":"18,094,000"},"filerUrl":"","transactionText":"Sale at price 180.94 per share.","filerName":"LEVINSON ARTHUR D","filerRelation":"Director","moneyText":"","startDate":{"raw":1709164800,"fmt":"2024-02-29"},"ownership":"D"},{"maxAge":1,"shares":{"raw":1852,"fmt":"1.85k","longFmt":"1,852"},"filerUrl":"","transactionText":"","filerName":"SUGAR RONALD D","filerRelation":"Director","moneyText":"","startDate":{"raw":1706745600,"fmt":"2024-02-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":1852,"fmt":"1.85k","longFmt":"1,852"},"filerUrl":"","transactionText":"","filerName":"LEVINSON ARTHUR D","filerRelation":"Director","moneyText":"","startDate":{"raw":1706745600,"fmt":"2024-02-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":1852,"fmt":"1.85k","longFmt":"1,852"},"filerUrl":"","transactionText":"","filerName":"BELL JAMES A","filerRelation":"Director","moneyText":"","startDate":{"raw":1706745600,"fmt":"2024-02-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":1852,"fmt":"1.85k","longFmt":"1,852"},"filerUrl":"","transactionText":"","filerName":"LOZANO MONICA C.","filerRelation":"Director","moneyText":"","startDate":{"raw":1706745600,"fmt":"2024-02-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":1852,"fmt":"1.85k","longFmt":"1,852"},"filerUrl":"","transactionText":"","filerName":"WAGNER SUSAN L","filerRelation":"Director","moneyText":"","startDate":{"raw":1706745600,"fmt":"2024-02-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":1852,"fmt":"1.85k","longFmt":"1,852"},"filerUrl":"","transactionText":"","filerName":"JUNG ANDREA","filerRelation":"Director","moneyText":"","startDate":{"raw":1706745600,"fmt":"2024-02-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":1852,"fmt":"1.85k","longFmt":"1,852"},"filerUrl":"","transactionText":"","filerName":"GORSKY ALEX","filerRelation":"Director","moneyText":"","startDate":{"raw":1706745600,"fmt":"2024-02-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":1852,"fmt":"1.85k","longFmt":"1,852"},"filerUrl":"","transactionText":"","filerName":"GORE ALBERT A JR","filerRelation":"Director","moneyText":"","startDate":{"raw":1706745600,"fmt":"2024-02-01"},"ownership":"D"},{"maxAge":1,"shares":{"raw":5513,"fmt":"5.51k","longFmt":"5,513"},"value":{"raw":1058496,"fmt":"1.06M","longFmt":"1,058,496"},"filerUrl":"","transactionText":"Sale at price 192.00 per share.","filerName":"KONDO CHRISTOPHER","filerRelation":"Officer","moneyText":"","startDate":{"raw":1701216000,"fmt":"2023-11-29"},"ownership":"D"},{"maxAge":1,"shares":{"raw":123448,"fmt":"123.45k","longFmt":"123,448"},"value":{"raw":23305748,"fmt":"23.31M","longFmt":"23,305,748"},"filerUrl":"","transactionText":"Sale at price 188.79 per share.","filerName":"ADAMS KATHERINE L","filerRelation":"General Counsel","moneyText":"","startDate":{"raw":1700179200,"fmt":"2023-11-17"},"ownership":"I"},{"maxAge":1,"shares":{"raw":123448,"fmt":"123.45k","longFmt":"123,448"},"value":{"raw":0,"fmt":null,"longFmt":"0"},"filerUrl":"","transactionText":"Stock Gift at price 0.00 per share.","filerName":"ADAMS KATHERINE L","filerRelation":"General Counsel","moneyText":"","startDate":{"raw":1700092800,"fmt":"2023-11-16"},"ownership":"D"},{"maxAge":1,"shares":{"raw":4806,"fmt":"4.81k","longFmt":"4,806"},"value":{"raw":884496,"fmt":"884.5k","longFmt":"884,496"},"filerUrl":"","transactionText":"Sale at price 184.04 per share.","filerName":"KONDO CHRISTOPHER","filerRelation":"Officer","moneyText":"","startDate":{"raw":1699574400,"fmt":"2023-11-10"},"ownership":"D"}],"maxAge":1},"fundOwnership":{"maxAge":1,"ownershipList":[{"maxAge":1,"reportDate":{"raw":1751241600,"fmt":"2025-06-30"},"organization":"VANGUARD INDEX FUNDS-Vanguard Total Stock Market Index Fund","pctHeld":{"raw":0.0324,"fmt":"3.24%"},"position":{"raw":480283704,"fmt":"480.28M","longFmt":"480,283,704"},"value":{"raw":129532520831,"fmt":"129.53B","longFmt":"129,532,520,831"},"pctChange":{"raw":0.012,"fmt":"1.20%"}},{"maxAge":1,"reportDate":{"raw":1751241600,"fmt":"2025-06-30"},"organization":"VANGUARD INDEX FUNDS-Vanguard 500 Index Fund","pctHeld":{"raw":0.0286,"fmt":"2.86%"},"position":{"raw":423950930,"fmt":"423.95M","longFmt":"423,950,930"},"value":{"raw":114339570996,"fmt":"114.34B","longFmt":"114,339,570,996"},"pctChange":{"raw":0.015700001,"fmt":"1.57%"}},{"maxAge":1,"reportDate":{"raw":1756598400,"fmt":"2025-08-31"},"organization":"Fidelity Concord Street Trust-Fidelity 500 Index Fund","pctHeld":{"raw":0.0128,"fmt":"1.28%"},"position":{"raw":189643332,"fmt":"189.64M","longFmt":"189,643,332"},"value":{"raw":51146808955,"fmt":"51.15B","longFmt":"51,146,808,955"},"pctChange":{"raw":0.0017,"fmt":"0.17%"}},{"maxAge":1,"reportDate":{"raw":1759190400,"fmt":"2025-09-30"},"organization":"iShares Trust-iShares Core S&P 500 ETF","pctHeld":{"raw":0.0123000005,"fmt":"1.23%"},"position":{"raw":182023835,"fmt":"182.02M","longFmt":"182,023,835"},"value":{"raw":49091830521,"fmt":"49.09B","longFmt":"49,091,830,521"},"pctChange":{"raw":0.0128,"fmt":"1.28%"}},{"maxAge":1,"reportDate":{"raw":1759190400,"fmt":"2025-09-30"},"organization":"SPDR S&P 500 ETF TRUST","pctHeld":{"raw":0.0119,"fmt":"1.19%"},"position":{"raw":176953084,"fmt":"176.95M","longFmt":"176,953,084"},"value":{"raw":47724248914,"fmt":"47.72B","longFmt":"47,724,248,914"},"pctChange":{"raw":-0.019,"fmt":"-1.90%"}},{"maxAge":1,"reportDate":{"raw":1751241600,"fmt":"2025-06-30"},"organization":"VANGUARD INDEX FUNDS-Vanguard Growth Index Fund","pctHeld":{"raw":0.01,"fmt":"1.00%"},"position":{"raw":147872804,"fmt":"147.87M","longFmt":"147,872,804"},"value":{"raw":39881297043,"fmt":"39.88B","longFmt":"39,881,297,043"},"pctChange":{"raw":0.0145000005,"fmt":"1.45%"}},{"maxAge":1,"reportDate":{"raw":1759190400,"fmt":"2025-09-30"},"organization":"Invesco QQQ Trust, Series 1","pctHeld":{"raw":0.0084,"fmt":"0.84%"},"position":{"raw":124618475,"fmt":"124.62M","longFmt":"124,618,475"},"value":{"raw":33609604228,"fmt":"33.61B","longFmt":"33,609,604,228"},"pctChange":{"raw":0.0029,"fmt":"0.29%"}},{"maxAge":1,"reportDate":{"raw":1751241600,"fmt":"2025-06-30"},"organization":"VANGUARD INSTITUTIONAL INDEX FUNDS-Vanguard Institutional Index Fund","pctHeld":{"raw":0.0062,"fmt":"0.62%"},"position":{"raw":91543381,"fmt":"91.54M","longFmt":"91,543,381"},"value":{"raw":24689250973,"fmt":"24.69B","longFmt":"24,689,250,973"},"pctChange":{"raw":-0.0115,"fmt":"-1.15%"}},{"maxAge":1,"reportDate":{"raw":1748649600,"fmt":"2025-05-31"},"organization":"VANGUARD WORLD FUND-Vanguard Information Technology Index Fund","pctHeld":{"raw":0.005,"fmt":"0.50%"},"position":{"raw":74018868,"fmt":"74.02M","longFmt":"74,018,868"},"value":{"raw":19962889603,"fmt":"19.96B","longFmt":"19,962,889,603"},"pctChange":{"raw":0.0272,"fmt":"2.72%"}},{"maxAge":1,"reportDate":{"raw":1759190400,"fmt":"2025-09-30"},"organization":"iShares Trust-iShares Russell 1000 Growth ETF","pctHeld":{"raw":0.0036000002,"fmt":"0.36%"},"position":{"raw":53571651,"fmt":"53.57M","longFmt":"53,571,651"},"value":{"raw":14448274928,"fmt":"14.45B","longFmt":"14,448,274,928"},"pctChange":{"raw":-0.0345,"fmt":"-3.45%"}}]}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/isin_search_AAPL.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/isin_search_AAPL.json new file mode 100644 index 0000000..b1609b0 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/isin_search_AAPL.json @@ -0,0 +1 @@ +mmSuggestDeliver(0, new Array("Name", "Category", "Keywords", "Bias", "Extension", "IDs"), new Array(new Array("Apple Inc.", "Stocks", "AAPL|US0378331005|AAPL||AAPL", "75", "", "aapl|AAPL|1|869"),new Array("Aallon Group Oyj Registered Shs", "Stocks", "|FI4000369608|||", "75", "", "aallon_group||1|576173391"),new Array("AAPKI Ventures Inc. Registered Shs", "Stocks", "PUSOF|CA00036H1001|PUSOF||", "75", "", "aapki_ventures|PUSOF|1|626836335"),new Array("Amplitude Surgical", "Stocks", "|FR0012789667|||", "75", "", "amplitude_surgical||1|7039064"),new Array("AUPLATA", "Stocks", "|FR0013410370|||", "75", "", "auplata_1||1|23405019")), 5, 0); \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/isin_search_QQQ.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/isin_search_QQQ.json new file mode 100644 index 0000000..71d74e0 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/isin_search_QQQ.json @@ -0,0 +1 @@ +mmSuggestDeliver(0, new Array("Name", "Category", "Keywords", "Bias", "Extension", "IDs"), new Array(new Array("ProShares Short QQQ Cert Deposito Arg Repr 0.125 Sh", "Stocks", "|AR0734794420|||", "75", "", "proshares_short_qqq_cert_deposito_arg_repr_0125_sh||1|627747520"),new Array("ProShares UltraPro QQQ Cert Deposito Arg Repr 0.04 Sh", "Stocks", "|AR0866630731|||", "75", "", "proshares_ultrapro_qqq_cert_deposito_arg_repr_004_sh||1|645933719"),new Array("Invesco QQQ Trust Series I Cert Deposito Arg Repr 0.05 Sh", "Stocks", "|ARCAVA4600V8|||", "75", "", "invesco_qqq_trust_series_i_cert_deposito_arg_repr_005_sh||1|576172969"),new Array("Quizam Media Corp Registered Shs", "Stocks", "QQQFF|CA7490575015|QQQFF||", "75", "", "qqqff|QQQFF|1|29741749"),new Array("Bank of America Corp Depositary Shs Repr 1-1000th Non-Cum Red Perp Pfd Rg Shs Series -QQ-", "Stocks", "BAC.PQ|US06055H8060|BAC.PQ||", "75", "", "bac-pq|BAC.PQ|1|339985776")), 5, 0); \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/news_latestNews_AAPL.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/news_latestNews_AAPL.json new file mode 100644 index 0000000..5a7dff5 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/news_latestNews_AAPL.json @@ -0,0 +1 @@ +{"data":{"tickerStream":{"pagination":{"requestedCount":10,"contentOverrides":{"564962c4-18de-35e4-8fdd-7046acbf4a81":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"7d28978a-f863-3901-bc4a-d1996e3e1bff":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"a3315544-794c-3d77-a220-8d7f7deeecec":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"ed6c67b3-cb69-3a3e-b920-031a8c24f5a2":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"e6f21cef-9329-3e71-ab62-66c3100b54d5":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"b951e340-2cef-3d13-bb4b-52d66030041a":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"44a962ac-a5cc-368a-b8ec-a8d1bd238903":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"fff23066-fdba-3f2a-9fc4-76a958a7d77a":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"0ce93ba2-3970-388a-b780-f8d9260aa9bf":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"236fda6e-72f7-39fa-ac03-8ab3f4c92600":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"18870763-181b-354a-9f36-cb800ae9d697":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"cf068a58-938b-3624-92fd-941617dfd7b3":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"5903b446-264f-3b3e-9047-6b452d33fa63":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"4f4ecf76-20de-319c-b1c6-d88d8fca7624":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"09db2702-0e5b-3f6d-86f8-bcadccbd6076":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"782ce416-9622-3063-8f77-4d89cd12e0fa":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"298b3a01-f939-3db3-9b66-3dfb254c6b3f":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"bd81e923-2fe4-33bb-8c46-78f845da93e3":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"8e9d7d4a-081e-34dc-a295-edeb61d64975":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"5e3f9a94-c246-316d-ac43-4f7e52144629":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"8b919cb4-5388-325c-8b2e-4d18980be780":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"aa67fdb0-6c4f-3ccf-bf1c-0e8f7dc5fd80":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"2d547cec-be00-3904-b1f0-58c54b12f68b":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"bafedcde-1688-3134-a50c-56d94202d1d8":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"6e128ead-bad1-3fdd-9839-9e83900d60f5":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"f7503682-7363-3ad0-83b8-4c60466fd1e7":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"f5d41a14-0f9a-319a-9ef6-04b21bfaa843":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"663fd04f-9aa8-3a61-9b27-8f637931e1c3":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"906f7cb0-bc66-3251-8e4f-da35b8cf48c4":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"0f5ca4d8-e369-31d9-879e-a2e6767ad8f4":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"2ebdbf49-d6ed-38ce-9f2b-c411f020ed80":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"5554fdb9-ef46-3b9f-995c-7c508afc4e88":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"0a777407-6b84-3d43-a9f3-a8088c920dfb":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"257956e6-2e05-3e01-b74d-26fe36fe1869":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"99e361fc-6264-3a1e-8c5c-25585e2c3209":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"6e533532-c0ef-34f6-a3aa-b6f8645d9d8e":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"b0d87a23-1cf1-3bc0-ae99-45a2fdd134d3":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"ac0aceb5-8880-397b-87fc-6b6113ba3620":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"201cd30c-e14f-3e8d-9d82-6a7b3c6304db":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"3f3c77ee-768c-3861-aa33-768a1b07116d":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"fd4356f2-7ecd-30ca-ae27-e2487ee871db":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"7abe84ab-bd78-3e5d-b7d3-213d3595fc2b":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"b3675399-fd93-3b4a-87d5-866377dfc039":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"50e8edb0-eaa2-385f-8024-aa36658e45ee":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"d12becbf-b038-4733-90aa-cc18020ae8f4":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"e6640730-36a6-3777-9490-de715fecaffd":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"f700da13-5920-3de5-bd00-91231eeade84":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"7317ebe9-197e-31f4-8fd7-4240fe803feb":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"971129fb-d802-3fa2-b726-982c67c3b803":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"bd35c64c-dff9-3bab-b7c7-c227cb2ad247":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"20f846c3-a906-3c04-96ad-620b8b9d5a06":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"8c0f2a09-f3a2-3a4b-aafd-c139836fa0b7":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"77c1073d-2724-3393-8c1c-cfa3a70ce6d6":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"6f4a62b5-1a0e-3d98-a0cb-16c587ee07cc":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"2e802ed0-3b9f-3481-aa46-a985da10f78c":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"f9bd712e-68a8-3860-b59b-de95b2ec0c5f":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"2ceecca1-540a-383f-be72-13c9c0c07406":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"9b570ac6-5908-32e5-9e53-b87c4f57bebc":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"0fc6d7b2-dea8-36fd-afac-befc2ab1992d":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"a2abd904-3d26-3470-9572-6cbd3ca6845e":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"ab0e112a-a702-334f-b2f2-c8177136ed7a":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"b266874b-d346-315f-aa3b-b114885b9ff3":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"1a2d73f7-ae2c-3f58-a614-9a0fffd77fac":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"04a3bbd8-ade1-3003-bb16-8cdbb654a414":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"3d0af149-d14e-3688-9e23-c24bf6568f37":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"87ad1952-d672-3693-8c78-40eb3af15210":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"6741acda-b13e-3e1d-88a5-f20cd1b3ac1e":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"d7d49b32-d847-34d8-af75-c41ba227be4e":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"7f0eaad7-9ee2-3a49-a22a-1533bb355ed0":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"34e31eb6-4ee4-3b20-bf1b-a1c21924f5b8":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"a817da42-89bb-33ab-b6b1-198b9f36c4a9":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"948a81d4-20bf-3c66-bcf2-b75e9f204885":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"e8b0d465-22b3-3df3-9990-e4dc4d6fcf8c":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"cce2e09c-1e61-35fb-be01-1f9567123aa1":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"1b1d0ed5-e338-3963-b19f-f29a7c60d46f":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"882041fe-e681-3612-92b3-e4622ff8ba03":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"c6aca2b1-98b6-3e9b-9780-554ef5876894":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"20ac6dac-81f9-38b1-bbe8-fb940adc0c4c":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"266a3126-afd5-3bdf-9da2-dc0da4010016":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"746eb31f-7251-3611-85aa-50e7b69db4f0":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"5a39882e-3a26-392e-9b45-7e3f510ef5f2":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"8d1c2b17-d43a-341c-818d-aa51170c0704":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"48bfb7f5-7994-30a1-aaca-1e1c4d3b2d77":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"1ce45e00-e6f3-4b15-ad6d-87f6550f1064":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"d40cb837-380d-3549-a4a3-d48ce64ade83":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"66f0ed43-2bc1-311e-b326-076e9e3c330e":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"d236c292-1c76-3f52-8cbe-87234fd26a70":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"8fb60bec-18a1-3929-b744-bd7e261a7910":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"f35ccad8-0bbf-4d46-a266-489efc2f0d56":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"c85be9fb-feb1-3f25-801a-6ffd03c19cb0":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"c8590890-95f4-3935-81b0-8a5004b0c4ef":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"41096cf4-443e-3249-938a-594d42ceffcd":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"6fa2d5dd-c74c-3edf-afc1-033002f7d725":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"861a0780-4e50-3458-b92e-38f9211e1887":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"ed9b5d67-1a41-34df-9678-e6875eadbe36":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"80ac2dae-4156-3a0c-b92c-506ad7576601":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"2c40bc85-e367-39f5-a36e-22b1c40c9140":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"0f38486a-1c17-345f-92fb-acf1c0dcaec8":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"4b479169-e7dc-3a8e-9b45-104e14482f87":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"50fa0fa3-13fb-3503-b5fd-fb0b3d7d996c":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"6b824190-2d03-3098-8bd6-070dac87c97f":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"c5eaa9a4-5555-3cea-bac6-c2f607f63893":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"d3771417-1a88-39fe-9803-14c1b6050723":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"c60f8b32-606f-39cb-8ced-e5722254947c":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"2c835e0f-30f4-39fe-9637-9267f992b5c9":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"8a7e238e-1a3c-318c-a802-700c2b47726f":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"18fe0e7b-74b3-3d67-964f-20a0e6081804":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"3c59db98-45a6-3e7e-844e-36edc70c6f89":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"243426b0-55ca-347b-a312-8f1862f4e8f0":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"96fac3a4-c2f7-36ad-86a8-75b4ba8c518c":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"50c98088-8103-3c4f-acf5-e544fda5cb99":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"f4df0b4d-83e3-30c7-9922-1d1f17704025":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"9a604f01-d391-36fb-8b83-a2f63c424872":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"883867cc-6959-33c1-87c5-ef4a8bb75564":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"27b59a19-4c8a-363a-b430-b5d058adae61":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"ebfb113a-f408-42cf-9255-070f0d5d0d27":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"49ceefe3-4c4b-4272-aa7d-86f2892bfa33":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"fee8ea7a-89f4-3b2a-8b14-7bc1f252db5f":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"a1e74fdb-ea6e-38fc-84c4-666fe9253f11":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"41bd442b-ad6e-334c-ab61-6b4dc91bd89c":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"dc92d84e-f371-3cce-810b-55e5a76a8e51":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"f626e40e-bec1-3445-ad54-5b8560a4e40a":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"665f2d12-41cd-3575-bdb9-198ee05fbe68":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"9ce66efa-2050-3679-b1c2-0eb06b54b1ad":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"6bfa46dc-166e-3b1b-bfab-f52d90b46a33":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"5614537b-fac2-366f-9f4d-359ab0495c88":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"241bc846-3386-3efe-a97e-f2cf535e1ba1":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"74745352-3430-3781-8afd-4d74656741d8":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"c3a1d530-8225-3556-8f5b-f886d6913b58":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"22247226-db97-3406-adc1-3e5ae8a8cd03":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"f5c959aa-750b-3749-982f-de226c12ec79":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"0164b3bd-8349-3049-85a3-0553e9c8deb4":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"643c4794-b06a-3c0e-b629-135ff053da06":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"e994fa6d-2a92-3765-a643-c7ccb83f9a9d":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"a0e66c61-1578-3e48-be66-3f9a5828824a":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"d80d67cc-bb65-3a26-b039-ca30fffecfa8":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"51542b62-3301-3b05-b8e5-5216c3ba35a7":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"7f40c9ff-bf06-40ef-9359-bac6351255a2":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"5c0dff72-e835-48ac-bfe9-0e62e2647010":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"62b120dd-f758-340c-b665-e5724a5daf3d":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"10b7cea3-5c7d-4669-ad46-22092261fe02":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"95f3eeca-6cfb-3fd8-9369-efbbb792e214":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"0af77da3-3379-3e4f-9199-048753558d6c":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"79c5f4e3-0d09-33a8-a509-f7e2e9282b24":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"27951481-bc57-380a-b674-12a4af1c6972":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"6192d306-f5ef-3e31-bf58-995659063424":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"3d3c1fe0-456f-3f6e-bddb-73c5d587753f":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"51a017d2-5b86-37e7-9523-b978db9aa2ac":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"3428a358-99db-328a-a28f-fd864ed4ccd3":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"e2876681-214d-3b1a-920d-a151aa0e1531":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"9f0c2712-0559-3883-8d1b-17b24ac9227d":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"bd294248-fd74-32f9-8a97-6d055375fa7e":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"5088681e-7a3a-3c4e-843a-83dd95aca79c":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"0ac51e65-7a3c-3ccb-8b92-66eb379646e9":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"c47a7f6c-623c-3d62-b5db-105f53c4d26c":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"c8e6907f-219d-4391-8413-2de28ea6b107":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"584d99f7-4e2e-3468-a563-193ad8eba866":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"fbaa1075-f16f-33b2-a75a-29ae6be2e95e":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"19b289c6-4cdf-3be8-9599-0a6a78f53384":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"1a23ebac-5a0d-3593-8aa3-9ffe0f4dfea5":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"144c5e84-a89f-30a5-9a0d-432a64269255":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"de9a0431-f71e-3c3e-aab2-1c593adf32e8":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"d0cd5dc9-d8ae-3754-a090-073b4fa82fd5":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"c0a3deaf-f214-3ea4-960d-53846289e663":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"7225300b-97d3-3f80-9862-c4cfca619e16":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"0ea5665e-835a-3e21-b94f-4ce81fd697f0":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"1897485b-4733-35ad-b97e-464dda582194":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"a33c5dc0-c9e5-35ac-a6b8-f60a3e88d1be":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"4bd97e5b-7e4d-3eb5-ab80-a18b234b5e52":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"ca484f9b-452f-3777-8413-5810bc4bf8a4":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"695ce02d-c5c0-3938-9e29-5b12c9282b02":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"7c342136-48df-342e-847c-d7fb00ff0c35":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"3aef47c4-d603-3821-9924-445e63f2ad2c":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"0a3f4334-b16b-3546-94ae-dc6584c6276e":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"2fb34824-9948-342d-9260-641ffc37b9ee":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"a4b86ebc-54a3-3da8-acfd-70659fe4d776":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"406b873f-d2f2-3eca-a9e6-f4976d6064eb":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"1e800594-3425-3e37-8af1-623d57726872":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"ee721bcf-2ecc-4ea4-a037-e7d4958e5b29":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"00f4928e-916c-4cc3-9be6-9ca6b3122609":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"c4c90a4a-57b7-338e-beb2-ec4f30423cc8":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false},"1f7d1752-3308-3f91-ae19-c8322bea7c8e":{"list":"9815e400-c5fd-11e9-9fff-40d60c11b0a1","editorsPick":false}},"remainingCount":182,"uuids":"ac0aceb5-8880-397b-87fc-6b6113ba3620:STORY,882041fe-e681-3612-92b3-e4622ff8ba03:STORY,d80d67cc-bb65-3a26-b039-ca30fffecfa8:STORY,d236c292-1c76-3f52-8cbe-87234fd26a70:STORY,e6640730-36a6-3777-9490-de715fecaffd:STORY,406b873f-d2f2-3eca-a9e6-f4976d6064eb:STORY,c85be9fb-feb1-3f25-801a-6ffd03c19cb0:STORY,04a3bbd8-ade1-3003-bb16-8cdbb654a414:STORY,cf068a58-938b-3624-92fd-941617dfd7b3:STORY,861a0780-4e50-3458-b92e-38f9211e1887:STORY,b266874b-d346-315f-aa3b-b114885b9ff3:STORY,5903b446-264f-3b3e-9047-6b452d33fa63:STORY,241bc846-3386-3efe-a97e-f2cf535e1ba1:STORY,3f3c77ee-768c-3861-aa33-768a1b07116d:STORY,695ce02d-c5c0-3938-9e29-5b12c9282b02:STORY,2c835e0f-30f4-39fe-9637-9267f992b5c9:STORY,0af77da3-3379-3e4f-9199-048753558d6c:STORY,3c59db98-45a6-3e7e-844e-36edc70c6f89:STORY,1897485b-4733-35ad-b97e-464dda582194:STORY,44a962ac-a5cc-368a-b8ec-a8d1bd238903:STORY,d7d49b32-d847-34d8-af75-c41ba227be4e:STORY,6192d306-f5ef-3e31-bf58-995659063424:STORY,9ce66efa-2050-3679-b1c2-0eb06b54b1ad:STORY,6e533532-c0ef-34f6-a3aa-b6f8645d9d8e:STORY,564962c4-18de-35e4-8fdd-7046acbf4a81:STORY,fff23066-fdba-3f2a-9fc4-76a958a7d77a:STORY,7225300b-97d3-3f80-9862-c4cfca619e16:STORY,f5d41a14-0f9a-319a-9ef6-04b21bfaa843:STORY,243426b0-55ca-347b-a312-8f1862f4e8f0:STORY,3aef47c4-d603-3821-9924-445e63f2ad2c:STORY,c6aca2b1-98b6-3e9b-9780-554ef5876894:STORY,96fac3a4-c2f7-36ad-86a8-75b4ba8c518c:STORY,bd294248-fd74-32f9-8a97-6d055375fa7e:STORY,00f4928e-916c-4cc3-9be6-9ca6b3122609:STORY,d0cd5dc9-d8ae-3754-a090-073b4fa82fd5:STORY,a1e74fdb-ea6e-38fc-84c4-666fe9253f11:STORY,663fd04f-9aa8-3a61-9b27-8f637931e1c3:STORY,0a3f4334-b16b-3546-94ae-dc6584c6276e:STORY,5554fdb9-ef46-3b9f-995c-7c508afc4e88:STORY,3d3c1fe0-456f-3f6e-bddb-73c5d587753f:STORY,665f2d12-41cd-3575-bdb9-198ee05fbe68:STORY,6b824190-2d03-3098-8bd6-070dac87c97f:STORY,1f7d1752-3308-3f91-ae19-c8322bea7c8e:STORY,c8590890-95f4-3935-81b0-8a5004b0c4ef:STORY,236fda6e-72f7-39fa-ac03-8ab3f4c92600:STORY,aa67fdb0-6c4f-3ccf-bf1c-0e8f7dc5fd80:STORY,c0a3deaf-f214-3ea4-960d-53846289e663:STORY,8d1c2b17-d43a-341c-818d-aa51170c0704:STORY,5614537b-fac2-366f-9f4d-359ab0495c88:STORY,c47a7f6c-623c-3d62-b5db-105f53c4d26c:STORY,6f4a62b5-1a0e-3d98-a0cb-16c587ee07cc:STORY,0fc6d7b2-dea8-36fd-afac-befc2ab1992d:STORY,a2abd904-3d26-3470-9572-6cbd3ca6845e:STORY,2ebdbf49-d6ed-38ce-9f2b-c411f020ed80:STORY,2ceecca1-540a-383f-be72-13c9c0c07406:STORY,8fb60bec-18a1-3929-b744-bd7e261a7910:STORY,643c4794-b06a-3c0e-b629-135ff053da06:STORY,2d547cec-be00-3904-b1f0-58c54b12f68b:STORY,9b570ac6-5908-32e5-9e53-b87c4f57bebc:STORY,bd81e923-2fe4-33bb-8c46-78f845da93e3:STORY,0f38486a-1c17-345f-92fb-acf1c0dcaec8:STORY,e994fa6d-2a92-3765-a643-c7ccb83f9a9d:STORY,bd35c64c-dff9-3bab-b7c7-c227cb2ad247:STORY,de9a0431-f71e-3c3e-aab2-1c593adf32e8:STORY,7abe84ab-bd78-3e5d-b7d3-213d3595fc2b:STORY,883867cc-6959-33c1-87c5-ef4a8bb75564:STORY,dc92d84e-f371-3cce-810b-55e5a76a8e51:STORY,f700da13-5920-3de5-bd00-91231eeade84:STORY,c4c90a4a-57b7-338e-beb2-ec4f30423cc8:VIDEO,f626e40e-bec1-3445-ad54-5b8560a4e40a:STORY,27b59a19-4c8a-363a-b430-b5d058adae61:STORY,b3675399-fd93-3b4a-87d5-866377dfc039:STORY,18870763-181b-354a-9f36-cb800ae9d697:STORY,74745352-3430-3781-8afd-4d74656741d8:STORY,7f0eaad7-9ee2-3a49-a22a-1533bb355ed0:STORY,8e9d7d4a-081e-34dc-a295-edeb61d64975:VIDEO,1e800594-3425-3e37-8af1-623d57726872:VIDEO,20f846c3-a906-3c04-96ad-620b8b9d5a06:STORY,fd4356f2-7ecd-30ca-ae27-e2487ee871db:STORY,48bfb7f5-7994-30a1-aaca-1e1c4d3b2d77:STORY,266a3126-afd5-3bdf-9da2-dc0da4010016:STORY,782ce416-9622-3063-8f77-4d89cd12e0fa:STORY,1a23ebac-5a0d-3593-8aa3-9ffe0f4dfea5:STORY,0f5ca4d8-e369-31d9-879e-a2e6767ad8f4:STORY,09db2702-0e5b-3f6d-86f8-bcadccbd6076:STORY,201cd30c-e14f-3e8d-9d82-6a7b3c6304db:STORY,9f0c2712-0559-3883-8d1b-17b24ac9227d:STORY,c8e6907f-219d-4391-8413-2de28ea6b107:STORY,34e31eb6-4ee4-3b20-bf1b-a1c21924f5b8:STORY,b0d87a23-1cf1-3bc0-ae99-45a2fdd134d3:VIDEO,20ac6dac-81f9-38b1-bbe8-fb940adc0c4c:STORY,7d28978a-f863-3901-bc4a-d1996e3e1bff:VIDEO,d3771417-1a88-39fe-9803-14c1b6050723:STORY,5e3f9a94-c246-316d-ac43-4f7e52144629:STORY,8b919cb4-5388-325c-8b2e-4d18980be780:STORY,19b289c6-4cdf-3be8-9599-0a6a78f53384:VIDEO,0ea5665e-835a-3e21-b94f-4ce81fd697f0:STORY,50c98088-8103-3c4f-acf5-e544fda5cb99:STORY,c3a1d530-8225-3556-8f5b-f886d6913b58:STORY,b951e340-2cef-3d13-bb4b-52d66030041a:STORY,77c1073d-2724-3393-8c1c-cfa3a70ce6d6:STORY,3428a358-99db-328a-a28f-fd864ed4ccd3:STORY,ab0e112a-a702-334f-b2f2-c8177136ed7a:VIDEO,a0e66c61-1578-3e48-be66-3f9a5828824a:STORY,41096cf4-443e-3249-938a-594d42ceffcd:STORY,fbaa1075-f16f-33b2-a75a-29ae6be2e95e:STORY,10b7cea3-5c7d-4669-ad46-22092261fe02:STORY,7f40c9ff-bf06-40ef-9359-bac6351255a2:STORY,ebfb113a-f408-42cf-9255-070f0d5d0d27:STORY,906f7cb0-bc66-3251-8e4f-da35b8cf48c4:STORY,51a017d2-5b86-37e7-9523-b978db9aa2ac:STORY,0a777407-6b84-3d43-a9f3-a8088c920dfb:STORY,4f4ecf76-20de-319c-b1c6-d88d8fca7624:STORY,d12becbf-b038-4733-90aa-cc18020ae8f4:STORY,0ce93ba2-3970-388a-b780-f8d9260aa9bf:STORY,6fa2d5dd-c74c-3edf-afc1-033002f7d725:STORY,27951481-bc57-380a-b674-12a4af1c6972:STORY,a3315544-794c-3d77-a220-8d7f7deeecec:STORY,1a2d73f7-ae2c-3f58-a614-9a0fffd77fac:VIDEO,8c0f2a09-f3a2-3a4b-aafd-c139836fa0b7:STORY,ca484f9b-452f-3777-8413-5810bc4bf8a4:STORY,e6f21cef-9329-3e71-ab62-66c3100b54d5:STORY,80ac2dae-4156-3a0c-b92c-506ad7576601:STORY,144c5e84-a89f-30a5-9a0d-432a64269255:STORY,5c0dff72-e835-48ac-bfe9-0e62e2647010:STORY,298b3a01-f939-3db3-9b66-3dfb254c6b3f:STORY,2fb34824-9948-342d-9260-641ffc37b9ee:STORY,fee8ea7a-89f4-3b2a-8b14-7bc1f252db5f:STORY,50e8edb0-eaa2-385f-8024-aa36658e45ee:STORY,746eb31f-7251-3611-85aa-50e7b69db4f0:STORY,4b479169-e7dc-3a8e-9b45-104e14482f87:STORY,e2876681-214d-3b1a-920d-a151aa0e1531:STORY,6741acda-b13e-3e1d-88a5-f20cd1b3ac1e:STORY,a817da42-89bb-33ab-b6b1-198b9f36c4a9:STORY,e8b0d465-22b3-3df3-9990-e4dc4d6fcf8c:STORY,5a39882e-3a26-392e-9b45-7e3f510ef5f2:STORY,f7503682-7363-3ad0-83b8-4c60466fd1e7:STORY,8a7e238e-1a3c-318c-a802-700c2b47726f:STORY,c60f8b32-606f-39cb-8ced-e5722254947c:VIDEO,9a604f01-d391-36fb-8b83-a2f63c424872:STORY,584d99f7-4e2e-3468-a563-193ad8eba866:STORY,bafedcde-1688-3134-a50c-56d94202d1d8:VIDEO,50fa0fa3-13fb-3503-b5fd-fb0b3d7d996c:STORY,f9bd712e-68a8-3860-b59b-de95b2ec0c5f:STORY,2e802ed0-3b9f-3481-aa46-a985da10f78c:STORY,3d0af149-d14e-3688-9e23-c24bf6568f37:STORY,95f3eeca-6cfb-3fd8-9369-efbbb792e214:STORY,6bfa46dc-166e-3b1b-bfab-f52d90b46a33:STORY,f5c959aa-750b-3749-982f-de226c12ec79:STORY,ed9b5d67-1a41-34df-9678-e6875eadbe36:VIDEO,ed6c67b3-cb69-3a3e-b920-031a8c24f5a2:STORY,2c40bc85-e367-39f5-a36e-22b1c40c9140:VIDEO,f35ccad8-0bbf-4d46-a266-489efc2f0d56:STORY,79c5f4e3-0d09-33a8-a509-f7e2e9282b24:VIDEO,a4b86ebc-54a3-3da8-acfd-70659fe4d776:STORY,0164b3bd-8349-3049-85a3-0553e9c8deb4:STORY,5088681e-7a3a-3c4e-843a-83dd95aca79c:STORY,7317ebe9-197e-31f4-8fd7-4240fe803feb:STORY,257956e6-2e05-3e01-b74d-26fe36fe1869:VIDEO,22247226-db97-3406-adc1-3e5ae8a8cd03:STORY,a33c5dc0-c9e5-35ac-a6b8-f60a3e88d1be:VIDEO,971129fb-d802-3fa2-b726-982c67c3b803:STORY,41bd442b-ad6e-334c-ab61-6b4dc91bd89c:STORY,ee721bcf-2ecc-4ea4-a037-e7d4958e5b29:STORY,948a81d4-20bf-3c66-bcf2-b75e9f204885:STORY,4bd97e5b-7e4d-3eb5-ab80-a18b234b5e52:STORY,0ac51e65-7a3c-3ccb-8b92-66eb379646e9:STORY,c5eaa9a4-5555-3cea-bac6-c2f607f63893:STORY,1ce45e00-e6f3-4b15-ad6d-87f6550f1064:STORY,49ceefe3-4c4b-4272-aa7d-86f2892bfa33:STORY,87ad1952-d672-3693-8c78-40eb3af15210:STORY,6e128ead-bad1-3fdd-9839-9e83900d60f5:STORY,d40cb837-380d-3549-a4a3-d48ce64ade83:STORY,66f0ed43-2bc1-311e-b326-076e9e3c330e:STORY,cce2e09c-1e61-35fb-be01-1f9567123aa1:STORY,7c342136-48df-342e-847c-d7fb00ff0c35:STORY,51542b62-3301-3b05-b8e5-5216c3ba35a7:STORY,18fe0e7b-74b3-3d67-964f-20a0e6081804:STORY,99e361fc-6264-3a1e-8c5c-25585e2c3209:STORY,f4df0b4d-83e3-30c7-9922-1d1f17704025:STORY,62b120dd-f758-340c-b665-e5724a5daf3d:STORY,1b1d0ed5-e338-3963-b19f-f29a7c60d46f:STORY"},"nextPage":true,"stream":[{"id":"035a1404-14a9-4292-96b8-08cca27526ee","content":{"id":"035a1404-14a9-4292-96b8-08cca27526ee","contentType":"STORY","title":"Earnings live: Meta stock tumbles, Microsoft slides, and Alphabet rises as Big Tech earnings pour in","description":"","summary":"Third quarter earnings season is ramping up, and analysts expect S&P 500 companies grew their profits by 8% during the quarter.","pubDate":"2025-10-29T20:33:56Z","displayTime":"2025-10-29T23:17:45Z","isHosted":true,"bypassModal":false,"previewUrl":null,"thumbnail":{"originalUrl":"https://s.yimg.com/os/creatr-uploaded-images/2025-10/8efaaeb0-b506-11f0-a7fe-d44b4a3949f3","originalWidth":6160,"originalHeight":4107,"caption":"","resolutions":[{"url":"https://s.yimg.com/uu/api/res/1.2/iPVQF9zjKWi_y9Zv9ny8tA--~B/aD00MTA3O3c9NjE2MDthcHBpZD15dGFjaHlvbg--/https://s.yimg.com/os/creatr-uploaded-images/2025-10/8efaaeb0-b506-11f0-a7fe-d44b4a3949f3","width":6160,"height":4107,"tag":"original"},{"url":"https://s.yimg.com/uu/api/res/1.2/J_cyhBPYMOZUeYdERi8iOg--~B/Zmk9c3RyaW07aD0xMjg7dz0xNzA7YXBwaWQ9eXRhY2h5b24-/https://s.yimg.com/os/creatr-uploaded-images/2025-10/8efaaeb0-b506-11f0-a7fe-d44b4a3949f3","width":170,"height":128,"tag":"170x128"}]},"provider":{"displayName":"Yahoo Finance","url":"http://finance.yahoo.com/"},"canonicalUrl":{"url":"https://finance.yahoo.com/news/live/earnings-live-meta-stock-tumbles-microsoft-slides-and-alphabet-rises-as-big-tech-earnings-pour-in-203356226.html","site":"finance","region":"US","lang":"en-US"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/live/earnings-live-meta-stock-tumbles-microsoft-slides-and-alphabet-rises-as-big-tech-earnings-pour-in-203356226.html","site":"finance","region":"US","lang":"en-US"},"metadata":{"editorsPick":true},"finance":{"premiumFinance":{"isPremiumNews":false,"isPremiumFreeNews":false}},"storyline":{"storylineItems":[{"content":{"id":"6b01c7dd-5955-364d-8d37-f3cb72866470","contentType":"VIDEO","isHosted":true,"title":"Apple & Amazon earnings, Fed, mortgage rates: What to Watch","thumbnail":{"originalUrl":"https://s.yimg.com/os/creatr-uploaded-images/2025-10/3f4d91f0-b4f9-11f0-97f2-4d904ec286a4","originalWidth":1920,"originalHeight":1080,"caption":"","resolutions":null},"provider":{"displayName":"Yahoo Finance Video","sourceId":"video.yahoofinance.com"},"previewUrl":null,"providerContentUrl":"","canonicalUrl":{"url":"https://finance.yahoo.com/video/apple-amazon-earnings-fed-mortgage-230000928.html"},"clickThroughUrl":{"url":"https://finance.yahoo.com/video/apple-amazon-earnings-fed-mortgage-230000928.html"}}},{"content":{"id":"900efe0a-762c-4804-9f4f-525025ee9407","contentType":"STORY","isHosted":true,"title":"Stock market today: Dow, S&P 500, Nasdaq futures drop after mixed Big Tech earnings with Trump-Xi meet ahead","thumbnail":{"originalUrl":"https://s.yimg.com/os/creatr-uploaded-images/2025-10/5b474fb0-9fc1-11f0-bfb4-6397842a3388","originalWidth":6048,"originalHeight":4032,"caption":"","resolutions":null},"provider":{"displayName":"Yahoo Finance","sourceId":"yahoofinance.com"},"previewUrl":null,"providerContentUrl":"","canonicalUrl":{"url":"https://finance.yahoo.com/news/live/stock-market-today-dow-sp-500-nasdaq-futures-drop-after-mixed-big-tech-earnings-with-trump-xi-meet-ahead-224956110.html"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/live/stock-market-today-dow-sp-500-nasdaq-futures-drop-after-mixed-big-tech-earnings-with-trump-xi-meet-ahead-224956110.html"}}}]}}},{"id":"33f28a65-933e-4982-b3ad-d27c9b3826d3","content":{"id":"33f28a65-933e-4982-b3ad-d27c9b3826d3","contentType":"STORY","title":"Stock market today: Nasdaq clinches record, Dow, S&P 500 dip as Fed cuts rates, Powell downplays December cut","description":"","summary":"Investors are looking to the Fed policy decision for insight into the path of interest rates.","pubDate":"2025-10-29T20:02:48Z","displayTime":"2025-10-29T21:01:12Z","isHosted":true,"bypassModal":false,"previewUrl":null,"thumbnail":{"originalUrl":"https://s.yimg.com/os/creatr-uploaded-images/2025-10/5b474fb0-9fc1-11f0-bfb4-6397842a3388","originalWidth":6048,"originalHeight":4032,"caption":"","resolutions":[{"url":"https://s.yimg.com/uu/api/res/1.2/Temg8F66OEYEXgeHQPLL6A--~B/aD00MDMyO3c9NjA0ODthcHBpZD15dGFjaHlvbg--/https://s.yimg.com/os/creatr-uploaded-images/2025-10/5b474fb0-9fc1-11f0-bfb4-6397842a3388","width":6048,"height":4032,"tag":"original"},{"url":"https://s.yimg.com/uu/api/res/1.2/MREc.PYHbvRCOyPMoQlmdA--~B/Zmk9c3RyaW07aD0xMjg7dz0xNzA7YXBwaWQ9eXRhY2h5b24-/https://s.yimg.com/os/creatr-uploaded-images/2025-10/5b474fb0-9fc1-11f0-bfb4-6397842a3388","width":170,"height":128,"tag":"170x128"}]},"provider":{"displayName":"Yahoo Finance","url":"http://finance.yahoo.com/"},"canonicalUrl":{"url":"https://finance.yahoo.com/news/live/stock-market-today-nasdaq-clinches-record-dow-sp-500-dip-as-fed-cuts-rates-powell-downplays-december-cut-200248155.html","site":"finance","region":"US","lang":"en-US"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/live/stock-market-today-nasdaq-clinches-record-dow-sp-500-dip-as-fed-cuts-rates-powell-downplays-december-cut-200248155.html","site":"finance","region":"US","lang":"en-US"},"metadata":{"editorsPick":true},"finance":{"premiumFinance":{"isPremiumNews":false,"isPremiumFreeNews":false}},"storyline":{"storylineItems":[{"content":{"id":"20c47519-ea08-4294-a932-2b183b8d9688","contentType":"STORY","isHosted":true,"title":"Meta stock sinks after tax hit weighs on earnings, company touts 'notably larger' AI investments in year ahead","thumbnail":{"originalUrl":"https://s.yimg.com/os/creatr-uploaded-images/2024-09/7dc2b5e0-7e0a-11ef-bc4e-8daee8e28f16","originalWidth":4140,"originalHeight":2703,"caption":"","resolutions":null},"provider":{"displayName":"Yahoo Finance","sourceId":"yahoofinance.com"},"previewUrl":null,"providerContentUrl":"","canonicalUrl":{"url":"https://finance.yahoo.com/news/meta-stock-sinks-after-tax-hit-weighs-on-earnings-company-touts-notably-larger-ai-investments-in-year-ahead-201015549.html"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/meta-stock-sinks-after-tax-hit-weighs-on-earnings-company-touts-notably-larger-ai-investments-in-year-ahead-201015549.html"}}},{"content":{"id":"78158cfc-3055-3f0a-a674-335810464105","contentType":"VIDEO","isHosted":true,"title":"Nvidia is proof hyperscalers won't back off AI capex: Analyst","thumbnail":{"originalUrl":"https://s.yimg.com/os/creatr-uploaded-images/2025-10/3ddd4270-b4e0-11f0-99fb-33945a34ec8f","originalWidth":3158,"originalHeight":1778,"caption":"","resolutions":null},"provider":{"displayName":"Yahoo Finance Video","sourceId":"video.yahoofinance.com"},"previewUrl":null,"providerContentUrl":"","canonicalUrl":{"url":"https://finance.yahoo.com/video/nvidia-proof-hyperscalers-wont-back-162000705.html"},"clickThroughUrl":{"url":"https://finance.yahoo.com/video/nvidia-proof-hyperscalers-wont-back-162000705.html"}}}]}}},{"id":"6c3f0185-4ee5-446e-a977-c8a465440027","content":{"id":"6c3f0185-4ee5-446e-a977-c8a465440027","contentType":"STORY","title":"Apple to report Q4 earnings after market cap touches $4 trillion","description":"","summary":"Apple will report its Q4 earnings.","pubDate":"2025-10-29T16:01:23Z","displayTime":"2025-10-29T16:01:23Z","isHosted":true,"bypassModal":false,"previewUrl":null,"thumbnail":{"originalUrl":"https://s.yimg.com/os/creatr-uploaded-images/2025-10/0ccb43b0-adbc-11f0-ae6e-1f688afcac90","originalWidth":4676,"originalHeight":3117,"caption":"","resolutions":[{"url":"https://s.yimg.com/uu/api/res/1.2/ffL_uVz4UC9Z1T1tZnyjuQ--~B/aD0zMTE3O3c9NDY3NjthcHBpZD15dGFjaHlvbg--/https://s.yimg.com/os/creatr-uploaded-images/2025-10/0ccb43b0-adbc-11f0-ae6e-1f688afcac90","width":4676,"height":3117,"tag":"original"},{"url":"https://s.yimg.com/uu/api/res/1.2/NhQXY.6T0gPpiCSw8THaKg--~B/Zmk9c3RyaW07aD0xMjg7dz0xNzA7YXBwaWQ9eXRhY2h5b24-/https://s.yimg.com/os/creatr-uploaded-images/2025-10/0ccb43b0-adbc-11f0-ae6e-1f688afcac90","width":170,"height":128,"tag":"170x128"}]},"provider":{"displayName":"Yahoo Finance","url":"http://finance.yahoo.com/"},"canonicalUrl":{"url":"https://finance.yahoo.com/news/apple-to-report-q4-earnings-after-market-cap-touches-4-trillion-160123241.html","site":"finance","region":"US","lang":"en-US"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/apple-to-report-q4-earnings-after-market-cap-touches-4-trillion-160123241.html","site":"finance","region":"US","lang":"en-US"},"metadata":{"editorsPick":true},"finance":{"premiumFinance":{"isPremiumNews":false,"isPremiumFreeNews":false}},"storyline":{"storylineItems":[{"content":{"id":"329801b8-ef7b-3c8a-9f4a-514ef3d1f975","contentType":"VIDEO","isHosted":true,"title":"Nvidia price target raised by UBS and Melius, Apple gets lift","thumbnail":{"originalUrl":"https://s.yimg.com/os/creatr-uploaded-images/2025-10/99dfc1d0-b4d4-11f0-9fef-3b697fd317a8","originalWidth":3000,"originalHeight":1689,"caption":"","resolutions":null},"provider":{"displayName":"Yahoo Finance Video","sourceId":"video.yahoofinance.com"},"previewUrl":null,"providerContentUrl":"","canonicalUrl":{"url":"https://finance.yahoo.com/video/nvidia-price-target-raised-ubs-143728259.html"},"clickThroughUrl":{"url":"https://finance.yahoo.com/video/nvidia-price-target-raised-ubs-143728259.html"}}},{"content":{"id":"be0894a4-0a3b-3843-85aa-c422e0c50621","contentType":"VIDEO","isHosted":true,"title":"SK Hynix, Nvidia, Mercedes, MAG 7 earnings: Trending Stocks","thumbnail":{"originalUrl":"https://s.yimg.com/os/creatr-uploaded-images/2025-10/940062f0-b4bc-11f0-96f3-d858f95d112e","originalWidth":5000,"originalHeight":2815,"caption":"","resolutions":null},"provider":{"displayName":"Yahoo Finance Video","sourceId":"video.yahoofinance.com"},"previewUrl":null,"providerContentUrl":"","canonicalUrl":{"url":"https://finance.yahoo.com/video/sk-hynix-nvidia-mercedes-mag-122009518.html"},"clickThroughUrl":{"url":"https://finance.yahoo.com/video/sk-hynix-nvidia-mercedes-mag-122009518.html"}}}]}}},{"id":"9a0bd4d3-3429-3ac8-8270-823db10f2ee0","content":{"id":"9a0bd4d3-3429-3ac8-8270-823db10f2ee0","contentType":"VIDEO","title":"Is the 'gold rush' into AI spending sustainable for markets?","description":"

Artificial intelligence has been dominating headlines this week as Nvidia (NVDA) CEO Jensen Huang unveils the developer's latest innovations and partnerships at the 2025 GTC (GPU Technology Conference), and Microsoft's (MSFT) stake in OpenAI (OPAI.PVT) climbs to $135 billion. This all comes ahead of Big Tech earnings reported by industry giants this week, like Microsoft, Meta Platforms (META), Alphabet (GOOGL, GOOG), Amazon (AMZN), and Apple (AAPL).

\n

Interactive Brokers Chief Strategist Steve Sosnick speaks with Josh Lipton about the environment that tech giants have created by their broadening AI capex spending.

\n

Also catch Steve Sosnick explain why he fears \"the lack of fear\" in markets the most.

\n

To watch more expert insights and analysis on the latest market action, check out more Market Domination.

","summary":"Artificial intelligence has been dominating headlines this week as Nvidia (NVDA) CEO Jensen Huang unveils the developer's latest innovations and partnerships at the 2025 GTC (GPU Technology Conference), and Microsoft's (MSFT) stake in OpenAI (OPAI.PVT) climbs to $135 billion. This all comes ahead of Big Tech earnings reported by industry giants this week, like Microsoft, Meta Platforms (META), Alphabet (GOOGL, GOOG), Amazon (AMZN), and Apple (AAPL). Interactive Brokers Chief Strategist Steve Sosnick speaks with Josh Lipton about the environment that tech giants have created by their broadening AI capex spending. Also catch Steve Sosnick explain why he fears \"the lack of fear\" in markets the most. To watch more expert insights and analysis on the latest market action, check out more Market Domination.","pubDate":"2025-10-29T11:30:01Z","displayTime":"","isHosted":true,"bypassModal":false,"previewUrl":null,"thumbnail":{"originalUrl":"https://s.yimg.com/os/creatr-uploaded-images/2025-10/b7c7cc40-b43b-11f0-959f-560fa90db5ed","originalWidth":4838,"originalHeight":2724,"caption":"","resolutions":[{"url":"https://s.yimg.com/uu/api/res/1.2/QzQLfTFwKoiaSfwlgAChBg--~B/aD0yNzI0O3c9NDgzODthcHBpZD15dGFjaHlvbg--/https://s.yimg.com/os/creatr-uploaded-images/2025-10/b7c7cc40-b43b-11f0-959f-560fa90db5ed","width":4838,"height":2724,"tag":"original"},{"url":"https://s.yimg.com/uu/api/res/1.2/L1FZYgBB4mXSzn9QVT9E2A--~B/Zmk9c3RyaW07aD0xMjg7dz0xNzA7YXBwaWQ9eXRhY2h5b24-/https://s.yimg.com/os/creatr-uploaded-images/2025-10/b7c7cc40-b43b-11f0-959f-560fa90db5ed","width":170,"height":128,"tag":"170x128"}]},"provider":{"displayName":"Yahoo Finance Video","url":"https://finance.yahoo.com/"},"canonicalUrl":{"url":"https://finance.yahoo.com/video/gold-rush-ai-spending-sustainable-113001347.html","site":"finance","region":"US","lang":"en-US"},"clickThroughUrl":{"url":"https://finance.yahoo.com/video/gold-rush-ai-spending-sustainable-113001347.html","site":"finance","region":"US","lang":"en-US"},"metadata":{"editorsPick":true},"finance":{"premiumFinance":{"isPremiumNews":false,"isPremiumFreeNews":false}},"storyline":{"storylineItems":[{"content":{"id":"1e22339b-649c-4e83-a6af-c75b7cf6c39a","contentType":"STORY","isHosted":true,"title":"Stock market today: Dow, S&P 500, Nasdaq notch fresh records as Nvidia soars ahead of Fed rate decision","thumbnail":{"originalUrl":"https://s.yimg.com/os/creatr-uploaded-images/2025-10/eaf7d1d0-b384-11f0-8ffe-f10af12d1cc6","originalWidth":3978,"originalHeight":2652,"caption":"","resolutions":null},"provider":{"displayName":"Yahoo Finance","sourceId":"yahoofinance.com"},"previewUrl":null,"providerContentUrl":"","canonicalUrl":{"url":"https://finance.yahoo.com/news/live/stock-market-today-dow-sp-500-nasdaq-notch-fresh-records-as-nvidia-soars-ahead-of-fed-rate-decision-200057996.html"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/live/stock-market-today-dow-sp-500-nasdaq-notch-fresh-records-as-nvidia-soars-ahead-of-fed-rate-decision-200057996.html"}}},{"content":{"id":"ebbd3efc-9332-48c3-876b-88b6983c78fa","contentType":"STORY","isHosted":true,"title":"The next chapter in OpenAI's dealmaking frees it to make even more","thumbnail":{"originalUrl":"https://s.yimg.com/os/creatr-uploaded-images/2025-10/13911440-a99d-11f0-b97f-924facdd303a","originalWidth":6000,"originalHeight":4000,"caption":"","resolutions":null},"provider":{"displayName":"Yahoo Finance","sourceId":"yahoofinance.com"},"previewUrl":null,"providerContentUrl":"","canonicalUrl":{"url":"https://finance.yahoo.com/news/the-next-chapter-in-openais-dealmaking-frees-it-to-make-even-more-100039904.html"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/the-next-chapter-in-openais-dealmaking-frees-it-to-make-even-more-100039904.html"}}}]}}},{"id":"41c20674-d5de-402c-b845-c1a9f1853820","content":{"id":"41c20674-d5de-402c-b845-c1a9f1853820","contentType":"STORY","title":"Big Tech's earnings will focus on AI and iPhones","description":"","summary":"Big Tech's earnings are coming, and AI will be top of mind.","pubDate":"2025-10-28T12:57:17Z","displayTime":"2025-10-29T05:18:57Z","isHosted":true,"bypassModal":false,"previewUrl":null,"thumbnail":{"originalUrl":"https://s.yimg.com/os/creatr-uploaded-images/2023-11/0a76ada0-7cec-11ee-bbc6-12c2f8f3e178","originalWidth":3600,"originalHeight":2400,"caption":"","resolutions":[{"url":"https://s.yimg.com/uu/api/res/1.2/_EpbjrdT18xg3D2bmJgwQg--~B/aD0yNDAwO3c9MzYwMDthcHBpZD15dGFjaHlvbg--/https://s.yimg.com/os/creatr-uploaded-images/2023-11/0a76ada0-7cec-11ee-bbc6-12c2f8f3e178","width":3600,"height":2400,"tag":"original"},{"url":"https://s.yimg.com/uu/api/res/1.2/K0FEw3TURF3A0CG5nLIR.A--~B/Zmk9c3RyaW07aD0xMjg7dz0xNzA7YXBwaWQ9eXRhY2h5b24-/https://s.yimg.com/os/creatr-uploaded-images/2023-11/0a76ada0-7cec-11ee-bbc6-12c2f8f3e178","width":170,"height":128,"tag":"170x128"}]},"provider":{"displayName":"Yahoo Finance","url":"http://finance.yahoo.com/"},"canonicalUrl":{"url":"https://finance.yahoo.com/news/big-techs-earnings-will-focus-on-ai-and-iphones-125717589.html","site":"finance","region":"US","lang":"en-US"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/big-techs-earnings-will-focus-on-ai-and-iphones-125717589.html","site":"finance","region":"US","lang":"en-US"},"metadata":{"editorsPick":true},"finance":{"premiumFinance":{"isPremiumNews":false,"isPremiumFreeNews":false}},"storyline":null}},{"id":"1d8c4dc2-09bc-3dfb-b8f4-0bf70f108142","content":{"id":"1d8c4dc2-09bc-3dfb-b8f4-0bf70f108142","contentType":"STORY","title":"Skyworks CEO sees chance to save power in AI-driven phones with Qorvo deal","description":"","summary":"WASHINGTON (Reuters) -Skyworks Solutions CEO Phil Brace sees a path toward chips that would help smartphones have faster wireless data connections while consuming less power by combining his firm's technologies with those from smaller Qorvo, he told Reuters in an interview on Wednesday. Skyworks on Tuesday announced a cash and stock offer to buy the smaller Qorvo that values Qorvo at $9.76 billion. While the two firms do have some areas of competition, Brace said the challenge both face is the fast-growing amount of data that has to be processed on phones and shuffled back and forth between phones and data centers to power AI applications such as chatbots.","pubDate":"2025-10-29T22:22:33Z","displayTime":"2025-10-29T22:22:33Z","isHosted":true,"bypassModal":false,"previewUrl":null,"thumbnail":null,"provider":{"displayName":"Reuters","url":"http://www.reuters.com/"},"canonicalUrl":{"url":"https://finance.yahoo.com/news/skyworks-ceo-sees-chance-save-222233481.html","site":"finance","region":"US","lang":"en-US"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/skyworks-ceo-sees-chance-save-222233481.html","site":"finance","region":"US","lang":"en-US"},"metadata":{"editorsPick":false},"finance":{"premiumFinance":{"isPremiumNews":false,"isPremiumFreeNews":false}},"storyline":null}},{"id":"e2b0e826-70b0-3cac-9e11-3afb3d96b6a2","content":{"id":"e2b0e826-70b0-3cac-9e11-3afb3d96b6a2","contentType":"STORY","title":"Dow Falls From Record Level as Powell Dampens December Rate Cut Bets","description":"","summary":"The Dow Jones Industrial Average fell from Tuesday's record high level after Federal Reserve Jerome","pubDate":"2025-10-29T21:16:55Z","displayTime":"2025-10-29T21:16:55Z","isHosted":true,"bypassModal":false,"previewUrl":null,"thumbnail":{"originalUrl":"https://media.zenfs.com/en/mt_newswires_premium_news_706/2475a8618428bf41d7b045240805a142","originalWidth":1000,"originalHeight":750,"caption":"stocks market trading wall street equity - Shutterstock","resolutions":[{"url":"https://s.yimg.com/uu/api/res/1.2/eYNqcCgn3G_qEuDD8GI7aw--~B/aD03NTA7dz0xMDAwO2FwcGlkPXl0YWNoeW9u/https://media.zenfs.com/en/mt_newswires_premium_news_706/2475a8618428bf41d7b045240805a142","width":1000,"height":750,"tag":"original"},{"url":"https://s.yimg.com/uu/api/res/1.2/KemtEB6CG4krkJpa4AjedQ--~B/Zmk9c3RyaW07aD0xMjg7dz0xNzA7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/mt_newswires_premium_news_706/2475a8618428bf41d7b045240805a142","width":170,"height":128,"tag":"170x128"}]},"provider":{"displayName":"MT Newswires","url":"https://www.mtnewswires.com/"},"canonicalUrl":{"url":"https://finance.yahoo.com/news/dow-falls-record-level-powell-211655201.html","site":"finance","region":"US","lang":"en-US"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/dow-falls-record-level-powell-211655201.html","site":"finance","region":"US","lang":"en-US"},"metadata":{"editorsPick":false},"finance":{"premiumFinance":{"isPremiumNews":true,"isPremiumFreeNews":false}},"storyline":null}},{"id":"6e2eba35-6692-3003-97ca-20edb178c44c","content":{"id":"6e2eba35-6692-3003-97ca-20edb178c44c","contentType":"STORY","title":"Why the AI bull market is raising diversification red flags","description":"","summary":"Financial advisors know how to guide clients through volatility, but the math displaying the portfolio risks of AI may present an altogether different challenge.","pubDate":"2025-10-29T21:10:52Z","displayTime":"2025-10-29T21:10:52Z","isHosted":true,"bypassModal":false,"previewUrl":null,"thumbnail":{"originalUrl":"https://media.zenfs.com/en/financial_planning_664/4569bbb80d954fd5d7e63de612fd043c","originalWidth":4000,"originalHeight":2668,"caption":"","resolutions":[{"url":"https://s.yimg.com/uu/api/res/1.2/GCIPAUyMVLoErCnR8YwkwA--~B/aD0yNjY4O3c9NDAwMDthcHBpZD15dGFjaHlvbg--/https://media.zenfs.com/en/financial_planning_664/4569bbb80d954fd5d7e63de612fd043c","width":4000,"height":2668,"tag":"original"},{"url":"https://s.yimg.com/uu/api/res/1.2/8R.jqYLv5nA1h6uD86fX7A--~B/Zmk9c3RyaW07aD0xMjg7dz0xNzA7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/financial_planning_664/4569bbb80d954fd5d7e63de612fd043c","width":170,"height":128,"tag":"170x128"}]},"provider":{"displayName":"Financial Planning","url":"https://www.financial-planning.com/"},"canonicalUrl":{"url":"https://www.financial-planning.com/news/ai-investment-risks-beg-for-diversification","site":"finance","region":"US","lang":"en-US"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/why-ai-bull-market-raising-211052120.html","site":"finance","region":"US","lang":"en-US"},"metadata":{"editorsPick":false},"finance":{"premiumFinance":{"isPremiumNews":false,"isPremiumFreeNews":false}},"storyline":null}},{"id":"e13ad495-46fb-3b34-9876-5f18e98221af","content":{"id":"e13ad495-46fb-3b34-9876-5f18e98221af","contentType":"STORY","title":"Nvidia's Huang joins tech titans funding Trump's ballroom","description":"","summary":"WASHINGTON (Reuters) -Nvidia CEO Jensen Huang donated to fund the construction of U. President Donald Trump’s White House ballroom, he told reporters, joining other tech leaders supporting the project.","pubDate":"2025-10-29T19:53:41Z","displayTime":"2025-10-29T19:53:41Z","isHosted":true,"bypassModal":false,"previewUrl":null,"thumbnail":{"originalUrl":"https://media.zenfs.com/en/reuters.com/1ba7198781678c41284d1c6f3a972a14","originalWidth":800,"originalHeight":533,"caption":"FILE PHOTO: Nvidia CEO Jensen Huang gestures as U.S. President Donald Trump (not pictured) delivers remarks during the \"Winning the AI Race\" Summit in Washington D.C., U.S., July 23, 2025. REUTERS/Kent Nishimura/File Photo","resolutions":[{"url":"https://s.yimg.com/uu/api/res/1.2/yYOvMVUtG0kPB2JpWyrdxA--~B/aD01MzM7dz04MDA7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/reuters.com/1ba7198781678c41284d1c6f3a972a14","width":800,"height":533,"tag":"original"},{"url":"https://s.yimg.com/uu/api/res/1.2/Gwl6hrSXCnY9b7KwmxA_ZA--~B/Zmk9c3RyaW07aD0xMjg7dz0xNzA7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/reuters.com/1ba7198781678c41284d1c6f3a972a14","width":170,"height":128,"tag":"170x128"}]},"provider":{"displayName":"Reuters","url":"https://www.reuters.com/"},"canonicalUrl":{"url":"https://finance.yahoo.com/news/nvidias-huang-joins-tech-titans-195341976.html","site":"finance","region":"US","lang":"en-US"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/nvidias-huang-joins-tech-titans-195341976.html","site":"finance","region":"US","lang":"en-US"},"metadata":{"editorsPick":false},"finance":{"premiumFinance":{"isPremiumNews":false,"isPremiumFreeNews":false}},"storyline":null}},{"id":"632e460d-798f-3f9f-877a-90c68028dcf5","content":{"id":"632e460d-798f-3f9f-877a-90c68028dcf5","contentType":"STORY","title":"Nvidia Stock Is Still a Buy. Why $5 Trillion Isn’t the Top.","description":"","summary":"Nvidia’s stock has gained $1 trillion in value in just three months. The gain arguably underestimates the company’s improving fundamentals.","pubDate":"2025-10-29T19:23:00Z","displayTime":"2025-10-29T19:23:00Z","isHosted":false,"bypassModal":false,"previewUrl":"https://finance.yahoo.com/m/632e460d-798f-3f9f-877a-90c68028dcf5/nvidia-stock-is-still-a-buy-.html","thumbnail":{"originalUrl":"https://media.zenfs.com/en/Barrons.com/43cc00cf40ac86f36aed84d077f4fd7c","originalWidth":1280,"originalHeight":640,"caption":"","resolutions":[{"url":"https://s.yimg.com/uu/api/res/1.2/zJcT64WfS7aY6SkOlCC8XA--~B/aD02NDA7dz0xMjgwO2FwcGlkPXl0YWNoeW9u/https://media.zenfs.com/en/Barrons.com/43cc00cf40ac86f36aed84d077f4fd7c","width":1280,"height":640,"tag":"original"},{"url":"https://s.yimg.com/uu/api/res/1.2/TghMRQwf9UzrcvKb9vC7hg--~B/Zmk9c3RyaW07aD0xMjg7dz0xNzA7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/Barrons.com/43cc00cf40ac86f36aed84d077f4fd7c","width":170,"height":128,"tag":"170x128"}]},"provider":{"displayName":"Barrons.com","url":"http://www.barrons.com/"},"canonicalUrl":{"url":"https://www.barrons.com/articles/nvidia-stock-a-buy-5-trillion-130eee1e?siteid=yhoof2&yptr=yahoo","site":"finance","region":"US","lang":"en-US"},"clickThroughUrl":null,"metadata":{"editorsPick":false},"finance":{"premiumFinance":{"isPremiumNews":false,"isPremiumFreeNews":false}},"storyline":null}}]}},"status":"OK"} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/news_pressRelease_AAPL.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/news_pressRelease_AAPL.json new file mode 100644 index 0000000..5ba359e --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/news_pressRelease_AAPL.json @@ -0,0 +1 @@ +{"data":{"tickerStream":{"pagination":{"requestedCount":10,"contentOverrides":{"9cb51524-3605-31c0-a112-c48e081c4fba":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"cb281070-a6ab-3041-8c8d-07d84f418aaa":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"54ee98f9-b460-3e6b-94fe-664b3bf0093a":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"e8c8d42d-c5d2-31d1-866b-96c5ed2c14db":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"41a850dc-6d1d-35d9-a614-a1f4716a3326":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"b93fc879-d86e-3499-b143-b99549625726":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"007dd305-d764-3025-8b3b-d158723a529b":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"b3c87d54-f435-3698-bfe8-8798ec5221d6":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"49050449-fd91-3ae4-9547-594fb79bfab0":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"14057ca2-14ab-3faf-ac85-f3802ad2521d":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"0b564e4e-4fea-32f7-a2c7-40eeeb21ee98":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"460f3dbb-8d83-3af6-8468-c96b2a0e8fc9":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"3bc62f89-6fba-305e-a78c-da65df34985c":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"bc24d53e-fe84-3838-8ce6-b5d5ec016bbd":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"ba881e9b-f882-366e-8cbc-2a76973ea85b":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"c5f2506b-0fdf-3a1a-a2d7-3850ab63e616":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"d6b01bce-5743-3c0b-861b-91038a0c92b8":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"55c08287-70ee-38ce-8aa6-123e3350360b":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"2d0d5eba-145f-366c-8943-855633047187":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"16c93eb0-cccb-373c-b48e-9991a3e401a2":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"e1ea04b3-0d21-3212-b666-9bc45b8a4b9d":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"cb79e71b-727c-3213-a12d-95ff3b2dbc09":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"3bf5f0a8-0840-3163-8198-39be02c74d16":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"de4cb86f-e5f8-3e34-bfdc-179a6372aba7":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"35bd20d4-8d92-3c63-8e60-6abefc8088e0":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"bcc2d5ae-b8f4-3398-bec5-e1cdaf9bb490":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"f1150eb2-9fdd-3f49-95e4-b046e5421fba":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"b5a43b11-157b-3800-8460-20b052bf50b2":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"157b9e32-2542-3c5b-b595-7d49df7fb4f5":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"3218d30f-a294-30b6-a0f6-8ff26c642646":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"22635904-da59-34ec-8e31-8827fdfce490":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"f98a7208-6fd4-3f7a-836a-a98c6db6a51e":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"b624792e-efde-3ee7-b1e0-1852107711aa":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"305c9fd9-ca03-3edd-b786-116696365807":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"d1c3fec3-3afc-369d-ae54-34c9d19d9db3":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"6c958154-d331-3b1f-a1c8-acecdab37086":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"720c1c65-bd89-3708-8f6c-faca649405a5":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"80751938-a81c-3780-9e5f-1b2b34b59897":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"38afa140-31e6-3e70-83ab-8fccdae3c38c":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"f698a0f8-c511-3e68-807f-a1e7d6df7d3b":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"4ed61071-455f-3e9f-9b92-589bfd7db7c0":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"61764b41-a88f-3b72-bbe5-82be647ce556":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"5a8add05-e9de-3052-b287-275703d5b667":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"dfc85608-45c6-3677-a1b0-23541893c3ec":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"45836dc3-583d-376d-98a4-619f5f2d6431":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"18402586-9af4-3397-9eaf-43db2a1ed202":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"6005842e-6142-371a-8d76-cc57f79185c4":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"6e8a4e57-e4a0-3a06-ad13-8eb4bbc698b6":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"3aaf9f3c-95e9-3124-9aa9-6e908c52a5c7":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"b370f509-9f93-3729-84fc-113096dbd426":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"9c6d0999-310c-33a0-88af-76294d67a641":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"357855e0-324b-3613-9da1-ab9f73ff2c96":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"04c7b291-ef76-3eef-bf91-61cad8099aa6":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"3a6e3876-a3cf-38da-ac3c-1833f8cc0b38":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"c683553a-d61e-3c38-8ff4-1de6ef5412c5":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"c10df403-218d-3ff8-8d09-026c8edd1ad7":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"b805d5eb-63e6-327b-87d2-e751c3d00752":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"0ddb6d5e-4c46-39fb-a447-e609e5e4bd56":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"18f287f5-9c05-3fa4-8864-16af46c11017":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"7d5a37c3-d041-3b76-9c78-7e639327879c":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"15717555-9cf4-3b5e-8671-99b76303c5aa":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"b409134f-9cb9-34de-92ec-7ed7a311b923":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"9ddf4118-a6e5-3a74-957b-5d688312176b":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"735f1988-3188-3e73-86d0-d53dc475e041":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"aeb7da7a-82b1-3b61-a108-a840ac19fd15":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"33ea8b8b-1abf-346e-9bcd-784e000d0067":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"1e760a26-1514-3eb5-adb3-8af89b754444":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"4d251545-3837-311d-83c6-0996ec5a6eaf":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"be290ee0-9607-3411-8804-57983d31b007":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"3551d74a-7abf-33e0-b884-6208b6d31741":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"57945427-bc9a-3c07-a97d-097e7bc140bc":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"8543f465-9d6d-32e6-9114-fa1ffb3a66fe":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"b0c1f5c6-5268-338d-a0b6-22d1c423867c":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"f0345166-6166-3a25-8c3a-861ff6921cab":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"ee011ca8-5da3-3ce7-a021-2353aa2b0f52":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"503d06a5-e340-3dca-b063-5475fea0be06":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"4f87cbd2-025c-3fd3-99c1-d90cc5ff4cc3":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"ee424cdb-f389-35bf-b451-d939f63fb6bb":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"4d409ace-adc1-362c-9a9d-575f4d69d727":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"b93e9d0f-d549-3135-b017-b93c80967314":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"ef7e37a2-03fb-36d2-b474-b7569c81a52f":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"f20b1bdb-4e0e-37c5-ab76-a7c9eefc7f39":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"173ad1b2-ee40-395e-8210-bdef1f986f00":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"98b25532-a70b-3a8e-88be-1e3a06bd0917":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"ca200c47-2981-3dc0-b8d7-1f6b3a748917":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"123e3ee3-f863-3cf2-8070-1510a8799810":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"151649fa-33b1-3396-86a8-b2b54d112711":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"f5bd69c6-54f7-3965-8b00-417883866830":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"96f9ade1-b024-3b59-8b65-733d45420e07":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"c87f41ad-0826-38ea-bc77-ab5ac538698f":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"fa0152c0-ee9d-32ae-a698-49d25229041f":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"d52e8a32-f970-3f03-b6f7-a8583d5dc31a":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"a46377af-eccc-3265-bc0f-d0de57c22e22":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"860bc45f-bdb2-3de5-b187-0d93cf29e0cb":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"c20eec0c-b0b7-3425-9da6-498a1bd18ebf":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"e6e1be01-dc26-3fe6-985a-b6718c238265":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"dfbaac4f-7211-3ec9-b852-efd86fb38301":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"4768a034-6bc8-3df4-b950-145ad97e26f1":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"ce1abbaf-ff77-35f4-b73e-2b0e1c9ca907":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"bd3f9fc1-a3b9-3fc5-a3e6-ca32c63f1f38":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"4365e98d-0593-31d0-b103-fdbc77141481":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"166530da-1951-320b-a54f-4ce038bdde4a":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"5760d5f0-af2e-32d1-9ea3-6955dd309bfa":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"692f8003-543c-304e-b63f-1bea16dbc820":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"e60e7872-d036-3396-9a5b-e6b0af9d196f":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"5f1c0443-20fa-3954-9551-d57a893b5014":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"4aea73ac-75f8-3008-b730-e785eec0885c":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"9ac8e210-5267-3689-9309-ecbc034572a4":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"89660b06-f988-3455-93bc-1aeabab7dccf":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"38e1a3ba-66de-3f01-acd1-0fe6d7dee4d0":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"3ffc51c1-7d4b-3af5-a411-27cb4d0efb2b":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"786fdfe3-f1ba-30f5-8b0b-2797b42222ea":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"7b80c725-57a1-3e2e-9169-d365a085b19d":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"b3b50554-f708-3613-aa62-e138da89e930":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"7334e847-df9d-3587-80e1-fcf8fd9bfc18":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"03f8d90d-65ec-3d04-b1f4-656dcb042cbb":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"5750fa8a-c231-3c2a-9639-a344253bba6c":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"9a2453a5-4a34-3bb1-883d-5c99cdaa859c":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"f7bda007-ff19-3408-a220-4dd8e7eb034d":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"3cd1288b-57b8-3955-af11-020f1353e6e4":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"e38ad48d-2d0d-3b14-8837-2bbc0d6970cb":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"ae61de5a-8b86-3c83-bb99-7d6a1a6414f3":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"91408cee-6620-3839-9297-0f768b5d6837":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"cb5163a6-7bc7-3e21-9d71-5a70d98664a2":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"5ea36d0e-b89f-38e9-b8c1-2da124211701":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"2980524b-2bab-3466-93bb-01ca52bb7dc9":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"67bb9901-3d96-32af-96d7-f2127859e3c2":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"755fc6cf-3ab3-338a-9c63-98ac14f581c8":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"d519e528-bc8f-335f-ac64-f54cb20e44df":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"87a913cc-06d4-3742-b883-da3d1b4f8e88":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"1ccc4971-fdfb-395a-8bb5-03edd724d8db":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"7a5ab375-e1a2-3886-b26d-f721d2c0104c":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"2e265d34-cdb6-3832-9da8-84709afdc754":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"0064f396-d687-3b5f-bde6-63df7b891f74":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"98aec07c-831c-388c-b88d-12061a9fbb40":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"fdaea5fc-503e-3740-a2c5-35f549c6aff0":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"eb5c430a-25e3-372c-9e06-26419e9331a1":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"c6769792-5651-351a-b014-161648a848bd":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"374efc90-2669-3977-bc7b-71dcdf33d7af":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"8e656719-4e97-33dc-9666-4f65b5bbade9":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"89f6167a-e01e-30a4-9e88-78172586d370":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"1b09ba25-6e50-31c9-8de3-98301a2d93e4":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"ce30337d-1bc0-3869-816e-9f4b4baaae29":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"cbcc84f3-6aa7-3879-a820-a51d99712f57":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"88ce16b8-7dd7-30ee-bbfa-e4c2201ec466":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"03b55967-0f0f-37ff-b319-445d03639a86":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"22e58487-4e16-3637-a117-8200bb727095":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"0dfde221-0bbc-396a-a59b-82a557ece4fd":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"11b801c0-6058-34b2-beff-72c8da655b2d":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"395abb90-e692-32a7-bc40-5f1eb851c83c":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"a7f3010e-714b-3305-abf1-0b13053c0efc":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"62e85f16-c3c0-3955-a13b-8d0b96fbb4d0":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"19d36dcf-6f87-32fa-b4d3-0e499b043e81":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"dac62277-3174-336f-8730-48710a53b483":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"7dd8fe6f-1b1f-31ff-8a31-6a7b49234bb5":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"7bf5d71c-a04b-30d0-80c2-ba3129b911c8":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"c5e198e0-b5e2-37b9-ba18-e99eb392c84e":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"38d90288-09d4-3bbe-9e31-1dce926db4dc":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"170416ef-5246-317e-9890-52bbe5ce039e":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"e4de8711-3eb5-3df1-b6cc-64c8aaaee72c":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"9dbc2ffc-95fe-37e7-ae0f-7d00f886f610":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"3d996f27-e1b7-3f82-b8eb-9dc8b5eb5002":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"67308b0b-2b07-3760-b4e7-31535b863400":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"3ed62c6f-b2a4-3263-a3ec-9c48c7ccf3ba":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"b3b598a9-7758-3cd7-affa-eda9885fec32":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"403d208b-fab8-3a0b-8343-7d155016bb37":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"8d497c54-490e-37da-862b-1fd0ffbdeb3c":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"e85b008c-6fb3-3da8-91f1-acfd2d2ce4ee":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"7135c5ae-8b03-3fed-a23c-a27357bb1b21":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"deb31fe4-d892-3188-bc94-ceeec11c02b9":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"87b78f20-6465-3697-abf8-262592b2b050":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"3d695399-18a4-39f8-a54c-0fd3083779aa":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"33dcd0f3-ffbe-3227-8ef6-1a5c5d15be77":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"5e999274-0251-3488-8d8c-3b1dc4d9d252":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"7b3855d0-f95b-3f10-88f9-90bc7b4d18be":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"738b3321-9c85-3770-bfae-7b503377774e":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"995c6452-2c53-3ba2-b8a1-c5e02edddd86":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"76bb86ed-f628-3c5a-b9e7-1485f5ae33a6":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"050c8eb6-02fd-393e-a44a-1cc906883bc2":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"57fbb2f1-d892-3e95-bba5-5fe4b4dccb7d":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"96f57e9a-7c60-3abe-85d5-ba32acc689d9":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"4c9f1da8-1280-30b0-a8cd-64e821991bc5":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"63f94589-1a55-3e8d-81c4-1b26d2548bef":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"643315ca-fe0e-3735-9ba1-07687baedfe5":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"cb769fbc-749b-380c-af67-3de999adadec":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"92495038-1201-3369-a2f0-425622823c0a":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"8f4dfd4d-a9d8-3e3b-8500-859ac3046831":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"de423c55-407e-3bb3-be5c-db61f7b962e6":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"f7de98cc-34d4-3015-8d1f-31e500255ce3":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"},"749620a5-7fd7-3a4d-9973-67e9518dd26c":{"list":"ee9a71c0-d3f8-11e9-aff7-e24e68c3a35b"}},"remainingCount":190,"uuids":"738b3321-9c85-3770-bfae-7b503377774e:STORY,e4de8711-3eb5-3df1-b6cc-64c8aaaee72c:STORY,98b25532-a70b-3a8e-88be-1e3a06bd0917:STORY,03f8d90d-65ec-3d04-b1f4-656dcb042cbb:STORY,e38ad48d-2d0d-3b14-8837-2bbc0d6970cb:STORY,15717555-9cf4-3b5e-8671-99b76303c5aa:STORY,33ea8b8b-1abf-346e-9bcd-784e000d0067:STORY,57fbb2f1-d892-3e95-bba5-5fe4b4dccb7d:STORY,7135c5ae-8b03-3fed-a23c-a27357bb1b21:STORY,860bc45f-bdb2-3de5-b187-0d93cf29e0cb:STORY,0064f396-d687-3b5f-bde6-63df7b891f74:STORY,2980524b-2bab-3466-93bb-01ca52bb7dc9:STORY,19d36dcf-6f87-32fa-b4d3-0e499b043e81:STORY,b805d5eb-63e6-327b-87d2-e751c3d00752:STORY,63f94589-1a55-3e8d-81c4-1b26d2548bef:STORY,1e760a26-1514-3eb5-adb3-8af89b754444:STORY,9cb51524-3605-31c0-a112-c48e081c4fba:STORY,995c6452-2c53-3ba2-b8a1-c5e02edddd86:STORY,eb5c430a-25e3-372c-9e06-26419e9331a1:STORY,d52e8a32-f970-3f03-b6f7-a8583d5dc31a:STORY,3d695399-18a4-39f8-a54c-0fd3083779aa:STORY,04c7b291-ef76-3eef-bf91-61cad8099aa6:STORY,8d497c54-490e-37da-862b-1fd0ffbdeb3c:STORY,96f57e9a-7c60-3abe-85d5-ba32acc689d9:STORY,5f1c0443-20fa-3954-9551-d57a893b5014:STORY,cb79e71b-727c-3213-a12d-95ff3b2dbc09:STORY,87a913cc-06d4-3742-b883-da3d1b4f8e88:STORY,9c6d0999-310c-33a0-88af-76294d67a641:STORY,b409134f-9cb9-34de-92ec-7ed7a311b923:STORY,bd3f9fc1-a3b9-3fc5-a3e6-ca32c63f1f38:STORY,9dbc2ffc-95fe-37e7-ae0f-7d00f886f610:STORY,11b801c0-6058-34b2-beff-72c8da655b2d:STORY,e8c8d42d-c5d2-31d1-866b-96c5ed2c14db:STORY,b93fc879-d86e-3499-b143-b99549625726:STORY,80751938-a81c-3780-9e5f-1b2b34b59897:STORY,dac62277-3174-336f-8730-48710a53b483:STORY,7b80c725-57a1-3e2e-9169-d365a085b19d:STORY,0dfde221-0bbc-396a-a59b-82a557ece4fd:STORY,3bf5f0a8-0840-3163-8198-39be02c74d16:STORY,7a5ab375-e1a2-3886-b26d-f721d2c0104c:STORY,4c9f1da8-1280-30b0-a8cd-64e821991bc5:STORY,87b78f20-6465-3697-abf8-262592b2b050:STORY,3a6e3876-a3cf-38da-ac3c-1833f8cc0b38:STORY,cbcc84f3-6aa7-3879-a820-a51d99712f57:STORY,dfbaac4f-7211-3ec9-b852-efd86fb38301:STORY,1b09ba25-6e50-31c9-8de3-98301a2d93e4:STORY,786fdfe3-f1ba-30f5-8b0b-2797b42222ea:STORY,18f287f5-9c05-3fa4-8864-16af46c11017:STORY,166530da-1951-320b-a54f-4ce038bdde4a:STORY,f5bd69c6-54f7-3965-8b00-417883866830:STORY,f98a7208-6fd4-3f7a-836a-a98c6db6a51e:STORY,22e58487-4e16-3637-a117-8200bb727095:STORY,ca200c47-2981-3dc0-b8d7-1f6b3a748917:STORY,91408cee-6620-3839-9297-0f768b5d6837:STORY,5760d5f0-af2e-32d1-9ea3-6955dd309bfa:STORY,720c1c65-bd89-3708-8f6c-faca649405a5:STORY,e85b008c-6fb3-3da8-91f1-acfd2d2ce4ee:STORY,cb5163a6-7bc7-3e21-9d71-5a70d98664a2:STORY,92495038-1201-3369-a2f0-425622823c0a:STORY,4d251545-3837-311d-83c6-0996ec5a6eaf:STORY,f20b1bdb-4e0e-37c5-ab76-a7c9eefc7f39:STORY,f1150eb2-9fdd-3f49-95e4-b046e5421fba:STORY,38d90288-09d4-3bbe-9e31-1dce926db4dc:STORY,33dcd0f3-ffbe-3227-8ef6-1a5c5d15be77:STORY,d1c3fec3-3afc-369d-ae54-34c9d19d9db3:STORY,123e3ee3-f863-3cf2-8070-1510a8799810:STORY,151649fa-33b1-3396-86a8-b2b54d112711:STORY,6c958154-d331-3b1f-a1c8-acecdab37086:STORY,b3c87d54-f435-3698-bfe8-8798ec5221d6:STORY,0ddb6d5e-4c46-39fb-a447-e609e5e4bd56:STORY,5750fa8a-c231-3c2a-9639-a344253bba6c:STORY,4f87cbd2-025c-3fd3-99c1-d90cc5ff4cc3:STORY,503d06a5-e340-3dca-b063-5475fea0be06:STORY,54ee98f9-b460-3e6b-94fe-664b3bf0093a:STORY,3aaf9f3c-95e9-3124-9aa9-6e908c52a5c7:STORY,305c9fd9-ca03-3edd-b786-116696365807:STORY,8f4dfd4d-a9d8-3e3b-8500-859ac3046831:STORY,395abb90-e692-32a7-bc40-5f1eb851c83c:STORY,3218d30f-a294-30b6-a0f6-8ff26c642646:STORY,16c93eb0-cccb-373c-b48e-9991a3e401a2:STORY,1ccc4971-fdfb-395a-8bb5-03edd724d8db:STORY,98aec07c-831c-388c-b88d-12061a9fbb40:STORY,735f1988-3188-3e73-86d0-d53dc475e041:STORY,9a2453a5-4a34-3bb1-883d-5c99cdaa859c:STORY,c6769792-5651-351a-b014-161648a848bd:STORY,4768a034-6bc8-3df4-b950-145ad97e26f1:STORY,c5f2506b-0fdf-3a1a-a2d7-3850ab63e616:STORY,aeb7da7a-82b1-3b61-a108-a840ac19fd15:STORY,b370f509-9f93-3729-84fc-113096dbd426:STORY,fdaea5fc-503e-3740-a2c5-35f549c6aff0:STORY,755fc6cf-3ab3-338a-9c63-98ac14f581c8:STORY,3ed62c6f-b2a4-3263-a3ec-9c48c7ccf3ba:STORY,b3b598a9-7758-3cd7-affa-eda9885fec32:STORY,c683553a-d61e-3c38-8ff4-1de6ef5412c5:STORY,9ddf4118-a6e5-3a74-957b-5d688312176b:STORY,ba881e9b-f882-366e-8cbc-2a76973ea85b:STORY,ee011ca8-5da3-3ce7-a021-2353aa2b0f52:STORY,a46377af-eccc-3265-bc0f-d0de57c22e22:STORY,ef7e37a2-03fb-36d2-b474-b7569c81a52f:STORY,de423c55-407e-3bb3-be5c-db61f7b962e6:STORY,4d409ace-adc1-362c-9a9d-575f4d69d727:STORY,cb281070-a6ab-3041-8c8d-07d84f418aaa:STORY,38afa140-31e6-3e70-83ab-8fccdae3c38c:STORY,e60e7872-d036-3396-9a5b-e6b0af9d196f:STORY,0b564e4e-4fea-32f7-a2c7-40eeeb21ee98:STORY,3ffc51c1-7d4b-3af5-a411-27cb4d0efb2b:STORY,cb769fbc-749b-380c-af67-3de999adadec:STORY,170416ef-5246-317e-9890-52bbe5ce039e:STORY,18402586-9af4-3397-9eaf-43db2a1ed202:STORY,45836dc3-583d-376d-98a4-619f5f2d6431:STORY,49050449-fd91-3ae4-9547-594fb79bfab0:STORY,be290ee0-9607-3411-8804-57983d31b007:STORY,c20eec0c-b0b7-3425-9da6-498a1bd18ebf:STORY,4365e98d-0593-31d0-b103-fdbc77141481:STORY,ee424cdb-f389-35bf-b451-d939f63fb6bb:STORY,88ce16b8-7dd7-30ee-bbfa-e4c2201ec466:STORY,f7bda007-ff19-3408-a220-4dd8e7eb034d:STORY,7b3855d0-f95b-3f10-88f9-90bc7b4d18be:STORY,3d996f27-e1b7-3f82-b8eb-9dc8b5eb5002:STORY,b93e9d0f-d549-3135-b017-b93c80967314:STORY,4aea73ac-75f8-3008-b730-e785eec0885c:STORY,b0c1f5c6-5268-338d-a0b6-22d1c423867c:STORY,c5e198e0-b5e2-37b9-ba18-e99eb392c84e:STORY,67bb9901-3d96-32af-96d7-f2127859e3c2:STORY,643315ca-fe0e-3735-9ba1-07687baedfe5:STORY,de4cb86f-e5f8-3e34-bfdc-179a6372aba7:STORY,89660b06-f988-3455-93bc-1aeabab7dccf:STORY,ce1abbaf-ff77-35f4-b73e-2b0e1c9ca907:STORY,61764b41-a88f-3b72-bbe5-82be647ce556:STORY,57945427-bc9a-3c07-a97d-097e7bc140bc:STORY,e1ea04b3-0d21-3212-b666-9bc45b8a4b9d:STORY,35bd20d4-8d92-3c63-8e60-6abefc8088e0:STORY,8e656719-4e97-33dc-9666-4f65b5bbade9:STORY,c87f41ad-0826-38ea-bc77-ab5ac538698f:STORY,3551d74a-7abf-33e0-b884-6208b6d31741:STORY,b5a43b11-157b-3800-8460-20b052bf50b2:STORY,f698a0f8-c511-3e68-807f-a1e7d6df7d3b:STORY,d519e528-bc8f-335f-ac64-f54cb20e44df:STORY,692f8003-543c-304e-b63f-1bea16dbc820:STORY,6005842e-6142-371a-8d76-cc57f79185c4:STORY,f0345166-6166-3a25-8c3a-861ff6921cab:STORY,ce30337d-1bc0-3869-816e-9f4b4baaae29:STORY,dfc85608-45c6-3677-a1b0-23541893c3ec:STORY,76bb86ed-f628-3c5a-b9e7-1485f5ae33a6:STORY,e6e1be01-dc26-3fe6-985a-b6718c238265:STORY,7d5a37c3-d041-3b76-9c78-7e639327879c:STORY,357855e0-324b-3613-9da1-ab9f73ff2c96:STORY,157b9e32-2542-3c5b-b595-7d49df7fb4f5:STORY,7dd8fe6f-1b1f-31ff-8a31-6a7b49234bb5:STORY,41a850dc-6d1d-35d9-a614-a1f4716a3326:STORY,deb31fe4-d892-3188-bc94-ceeec11c02b9:STORY,89f6167a-e01e-30a4-9e88-78172586d370:STORY,8543f465-9d6d-32e6-9114-fa1ffb3a66fe:STORY,5e999274-0251-3488-8d8c-3b1dc4d9d252:STORY,460f3dbb-8d83-3af6-8468-c96b2a0e8fc9:STORY,a7f3010e-714b-3305-abf1-0b13053c0efc:STORY,b624792e-efde-3ee7-b1e0-1852107711aa:STORY,3bc62f89-6fba-305e-a78c-da65df34985c:STORY,2e265d34-cdb6-3832-9da8-84709afdc754:STORY,22635904-da59-34ec-8e31-8827fdfce490:STORY,7bf5d71c-a04b-30d0-80c2-ba3129b911c8:STORY,b3b50554-f708-3613-aa62-e138da89e930:STORY,d6b01bce-5743-3c0b-861b-91038a0c92b8:STORY,96f9ade1-b024-3b59-8b65-733d45420e07:STORY,f7de98cc-34d4-3015-8d1f-31e500255ce3:STORY,6e8a4e57-e4a0-3a06-ad13-8eb4bbc698b6:STORY,fa0152c0-ee9d-32ae-a698-49d25229041f:STORY,62e85f16-c3c0-3955-a13b-8d0b96fbb4d0:STORY,4ed61071-455f-3e9f-9b92-589bfd7db7c0:STORY,ae61de5a-8b86-3c83-bb99-7d6a1a6414f3:STORY,749620a5-7fd7-3a4d-9973-67e9518dd26c:STORY,050c8eb6-02fd-393e-a44a-1cc906883bc2:STORY,55c08287-70ee-38ce-8aa6-123e3350360b:STORY,67308b0b-2b07-3760-b4e7-31535b863400:STORY,7334e847-df9d-3587-80e1-fcf8fd9bfc18:STORY,403d208b-fab8-3a0b-8343-7d155016bb37:STORY,5ea36d0e-b89f-38e9-b8c1-2da124211701:STORY,173ad1b2-ee40-395e-8210-bdef1f986f00:STORY,3cd1288b-57b8-3955-af11-020f1353e6e4:STORY,c10df403-218d-3ff8-8d09-026c8edd1ad7:STORY,2d0d5eba-145f-366c-8943-855633047187:STORY,5a8add05-e9de-3052-b287-275703d5b667:STORY,007dd305-d764-3025-8b3b-d158723a529b:STORY,03b55967-0f0f-37ff-b319-445d03639a86:STORY,bc24d53e-fe84-3838-8ce6-b5d5ec016bbd:STORY,bcc2d5ae-b8f4-3398-bec5-e1cdaf9bb490:STORY,38e1a3ba-66de-3f01-acd1-0fe6d7dee4d0:STORY,14057ca2-14ab-3faf-ac85-f3802ad2521d:STORY,9ac8e210-5267-3689-9309-ecbc034572a4:STORY,374efc90-2669-3977-bc7b-71dcdf33d7af:STORY"},"nextPage":true,"stream":[{"id":"2bb257be-7f1f-3d8b-b37d-d8b770611e51","content":{"id":"2bb257be-7f1f-3d8b-b37d-d8b770611e51","contentType":"STORY","title":"Altrata Report Finds the Total Net Worth of the Billionaire Class Surged by More Than 10%","description":"","summary":"Today Altrata, a leader in intelligence on the wealthy and well-connected, releases the Billionaire Census 2025, examining trends amongst the global billionaire population.","pubDate":"2025-10-29T13:46:00Z","displayTime":"2025-10-29T13:46:00Z","isHosted":true,"bypassModal":false,"previewUrl":null,"thumbnail":{"originalUrl":"https://media.zenfs.com/en/prnewswire.com/9f7267ea988c5c7216e7927c7bc4e76b","originalWidth":400,"originalHeight":80,"caption":"Engage the exceptional. (PRNewsfoto/Altrata)","resolutions":[{"url":"https://s.yimg.com/uu/api/res/1.2/Phn2dcnACzPNnK.0nNeMdA--~B/aD04MDt3PTQwMDthcHBpZD15dGFjaHlvbg--/https://media.zenfs.com/en/prnewswire.com/9f7267ea988c5c7216e7927c7bc4e76b","width":400,"height":80,"tag":"original"},{"url":"https://s.yimg.com/uu/api/res/1.2/JCbHQHRiebTbJNXQ4GkjcQ--~B/Zmk9c3RyaW07aD0xMjg7dz0xNzA7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/prnewswire.com/9f7267ea988c5c7216e7927c7bc4e76b","width":170,"height":128,"tag":"170x128"}]},"provider":{"displayName":"PR Newswire","url":"https://www.prnewswire.com/"},"canonicalUrl":{"url":"https://finance.yahoo.com/news/altrata-report-finds-total-net-134600838.html","site":"finance","region":"US","lang":"en-US"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/altrata-report-finds-total-net-134600838.html","site":"finance","region":"US","lang":"en-US"},"metadata":null,"finance":{"premiumFinance":{"isPremiumNews":false,"isPremiumFreeNews":false}},"storyline":null}},{"id":"95ed65bb-4490-3cab-9c90-0a40976a91e7","content":{"id":"95ed65bb-4490-3cab-9c90-0a40976a91e7","contentType":"STORY","title":"SellYourMac Expands to Canada, Offering Canadians a Trusted Way to Sell, Reuse, and Recycle Apple Devices","description":"","summary":"MISSISSAUGA, Ontario & CINCINNATI, October 29, 2025--SellYourMac.com (SYM), a reCommerce and IT asset transition service company committed to reusing, repurposing, and recycling used Apple products, today announced the launch of SellYourMac Canada, now available at www.sellyourmac.ca. Canadian customers can now easily and securely sell their used Apple devices for top value while contributing to sustainability and responsible e-waste management.","pubDate":"2025-10-29T12:30:00Z","displayTime":"2025-10-29T12:30:00Z","isHosted":true,"bypassModal":false,"previewUrl":null,"thumbnail":{"originalUrl":"https://media.zenfs.com/en/business-wire.com/95990c0aee4bebcc0bdfa218b0d40bc9","originalWidth":480,"originalHeight":322,"caption":"","resolutions":[{"url":"https://s.yimg.com/uu/api/res/1.2/G8klFLMU7of.vlHu1z1xew--~B/aD0zMjI7dz00ODA7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/business-wire.com/95990c0aee4bebcc0bdfa218b0d40bc9","width":480,"height":322,"tag":"original"},{"url":"https://s.yimg.com/uu/api/res/1.2/BoYA2t7ZW2vOzQhctGadVg--~B/Zmk9c3RyaW07aD0xMjg7dz0xNzA7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/business-wire.com/95990c0aee4bebcc0bdfa218b0d40bc9","width":170,"height":128,"tag":"170x128"}]},"provider":{"displayName":"Business Wire","url":"http://www.businesswire.com/"},"canonicalUrl":{"url":"https://finance.yahoo.com/news/sellyourmac-expands-canada-offering-canadians-123000869.html","site":"finance","region":"US","lang":"en-US"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/sellyourmac-expands-canada-offering-canadians-123000869.html","site":"finance","region":"US","lang":"en-US"},"metadata":null,"finance":{"premiumFinance":{"isPremiumNews":false,"isPremiumFreeNews":false}},"storyline":null}},{"id":"e42cc628-fcfb-3b2b-be65-f7b4b87b9ff9","content":{"id":"e42cc628-fcfb-3b2b-be65-f7b4b87b9ff9","contentType":"STORY","title":"Jamf Enters into Definitive Agreement to be Acquired by Francisco Partners in $2.2 Billion Transaction","description":"","summary":"MINNEAPOLIS, October 29, 2025--Jamf (NASDAQ: JAMF), the standard in managing and securing Apple at work, today announced that it has entered into a definitive agreement with Francisco Partners (\"FP\") for FP to acquire all the outstanding shares of Jamf. FP is a leading global investment firm focused exclusively on technology and technology-enabled businesses.","pubDate":"2025-10-29T11:00:00Z","displayTime":"2025-10-29T11:00:00Z","isHosted":true,"bypassModal":false,"previewUrl":null,"thumbnail":{"originalUrl":"https://media.zenfs.com/en/business-wire.com/7fccff7920497f90b335e84beac3819f","originalWidth":1024,"originalHeight":512,"caption":"","resolutions":[{"url":"https://s.yimg.com/uu/api/res/1.2/AYC0n_zSLbDk_hPB91aveg--~B/aD01MTI7dz0xMDI0O2FwcGlkPXl0YWNoeW9u/https://media.zenfs.com/en/business-wire.com/7fccff7920497f90b335e84beac3819f","width":1024,"height":512,"tag":"original"},{"url":"https://s.yimg.com/uu/api/res/1.2/p6lcnsnGSskpVdc_iEk7Dg--~B/Zmk9c3RyaW07aD0xMjg7dz0xNzA7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/business-wire.com/7fccff7920497f90b335e84beac3819f","width":170,"height":128,"tag":"170x128"}]},"provider":{"displayName":"Business Wire","url":"http://www.businesswire.com/"},"canonicalUrl":{"url":"https://finance.yahoo.com/news/jamf-enters-definitive-agreement-acquired-110000899.html","site":"finance","region":"US","lang":"en-US"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/jamf-enters-definitive-agreement-acquired-110000899.html","site":"finance","region":"US","lang":"en-US"},"metadata":null,"finance":{"premiumFinance":{"isPremiumNews":false,"isPremiumFreeNews":false}},"storyline":null}},{"id":"a71386b9-0ae6-35d8-8f6a-e00f5704d2c3","content":{"id":"a71386b9-0ae6-35d8-8f6a-e00f5704d2c3","contentType":"STORY","title":"6G and AI Investment to Drive Global Communications Industry Growth, Omdia Forecasts","description":"","summary":"LONDON, October 29, 2025--Global Communications Providers (CP) revenue is expected to reach $5.6 trillion by 2030, growing at a 6.2% CAGR from 2025, according to Omdia’s latest global forecast for CP revenue and capital expenditure (capex). This steady growth is driven by technology innovation, infrastructure expansion, and strategic investment in 6G and AI.","pubDate":"2025-10-29T09:05:00Z","displayTime":"2025-10-29T09:05:00Z","isHosted":true,"bypassModal":false,"previewUrl":null,"thumbnail":{"originalUrl":"https://media.zenfs.com/en/business-wire.com/5ca7b249d94e209ea0d708ddf59b1524","originalWidth":480,"originalHeight":442,"caption":"Yearly CP revenue, by type, 2021-30","resolutions":[{"url":"https://s.yimg.com/uu/api/res/1.2/xG0._B.z3R7Bi5DQf9ibwA--~B/aD00NDI7dz00ODA7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/business-wire.com/5ca7b249d94e209ea0d708ddf59b1524","width":480,"height":442,"tag":"original"},{"url":"https://s.yimg.com/uu/api/res/1.2/l1J98CnNukZVTrcAgRiDCA--~B/Zmk9c3RyaW07aD0xMjg7dz0xNzA7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/business-wire.com/5ca7b249d94e209ea0d708ddf59b1524","width":170,"height":128,"tag":"170x128"}]},"provider":{"displayName":"Business Wire","url":"http://www.businesswire.com/"},"canonicalUrl":{"url":"https://finance.yahoo.com/news/6g-ai-investment-drive-global-090500786.html","site":"finance","region":"US","lang":"en-US"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/6g-ai-investment-drive-global-090500786.html","site":"finance","region":"US","lang":"en-US"},"metadata":null,"finance":{"premiumFinance":{"isPremiumNews":false,"isPremiumFreeNews":false}},"storyline":null}},{"id":"a6812032-43f8-3e3e-8974-ab1ad20bbec6","content":{"id":"a6812032-43f8-3e3e-8974-ab1ad20bbec6","contentType":"STORY","title":"Simon Yueng of Biel Crystal, was awarded KPMG's \"Excellent Family Business Future Entrepreneur\"","description":"","summary":"The KPMG China Future Entrepreneur Award 2025 award ceremony was held in Shenzhen on October 24. Simon Yueng, Executive Director and Vice President of Biel Crystal, was awarded \"Excellent Family Business Future Entrepreneur\".","pubDate":"2025-10-29T02:52:00Z","displayTime":"2025-10-29T02:52:00Z","isHosted":true,"bypassModal":false,"previewUrl":null,"thumbnail":{"originalUrl":"https://media.zenfs.com/en/prnewswire.com/a336e64a3b0b8f8f515f62479e16070e","originalWidth":400,"originalHeight":267,"caption":"The award presentation scene","resolutions":[{"url":"https://s.yimg.com/uu/api/res/1.2/5lluzix2VbAdMBvaQNrX4Q--~B/aD0yNjc7dz00MDA7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/prnewswire.com/a336e64a3b0b8f8f515f62479e16070e","width":400,"height":267,"tag":"original"},{"url":"https://s.yimg.com/uu/api/res/1.2/ymF4ji4adqGg7f6V1NZoWQ--~B/Zmk9c3RyaW07aD0xMjg7dz0xNzA7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/prnewswire.com/a336e64a3b0b8f8f515f62479e16070e","width":170,"height":128,"tag":"170x128"}]},"provider":{"displayName":"PR Newswire","url":"https://www.prnewswire.com/"},"canonicalUrl":{"url":"https://finance.yahoo.com/news/simon-yueng-biel-crystal-awarded-025200496.html","site":"finance","region":"US","lang":"en-US"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/simon-yueng-biel-crystal-awarded-025200496.html","site":"finance","region":"US","lang":"en-US"},"metadata":null,"finance":{"premiumFinance":{"isPremiumNews":false,"isPremiumFreeNews":false}},"storyline":null}},{"id":"f79ca522-d638-3e19-9e52-75c16e1457b2","content":{"id":"f79ca522-d638-3e19-9e52-75c16e1457b2","contentType":"STORY","title":"Jamf's RapidIdentity Selected for Major Ohio Higher Education Consortium Contract","description":"","summary":"HOUSTON, October 28, 2025--Jamf (NASDAQ: JAMF), the standard in managing and securing Apple at work, today announced that RapidIdentity from Jamf has been awarded a transformative contract with The Inter-University Council Purchasing Group (IUC-PG) of Ohio. RapidIdentity has been selected as one of two vetted identity and access management platforms contractually available for all 86 member institutions of this prestigious consortium. This significant five-year agreement establishes pre-negotiat","pubDate":"2025-10-28T13:15:00Z","displayTime":"2025-10-28T13:15:00Z","isHosted":true,"bypassModal":false,"previewUrl":null,"thumbnail":{"originalUrl":"https://media.zenfs.com/en/business-wire.com/8f9bc884fa24f8d5d7311366924e61aa","originalWidth":1024,"originalHeight":512,"caption":"","resolutions":[{"url":"https://s.yimg.com/uu/api/res/1.2/yzSyOkiyIQ9QQD_.wgShDA--~B/aD01MTI7dz0xMDI0O2FwcGlkPXl0YWNoeW9u/https://media.zenfs.com/en/business-wire.com/8f9bc884fa24f8d5d7311366924e61aa","width":1024,"height":512,"tag":"original"},{"url":"https://s.yimg.com/uu/api/res/1.2/XtmSqCiXfPUzPuNtRPR3bQ--~B/Zmk9c3RyaW07aD0xMjg7dz0xNzA7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/business-wire.com/8f9bc884fa24f8d5d7311366924e61aa","width":170,"height":128,"tag":"170x128"}]},"provider":{"displayName":"Business Wire","url":"http://www.businesswire.com/"},"canonicalUrl":{"url":"https://finance.yahoo.com/news/jamfs-rapididentity-selected-major-ohio-131500674.html","site":"finance","region":"US","lang":"en-US"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/jamfs-rapididentity-selected-major-ohio-131500674.html","site":"finance","region":"US","lang":"en-US"},"metadata":null,"finance":{"premiumFinance":{"isPremiumNews":false,"isPremiumFreeNews":false}},"storyline":null}},{"id":"988016e0-20be-311a-90f4-f66d381296fa","content":{"id":"988016e0-20be-311a-90f4-f66d381296fa","contentType":"STORY","title":"Corning Announces Third-Quarter 2025 Financial Results(1) with Record Core Sales and Core EPS","description":"","summary":"CORNING, N.Y., October 28, 2025--Corning Incorporated (NYSE: GLW) today announced its third-quarter 2025 results and provided its outlook for fourth-quarter 2025.","pubDate":"2025-10-28T11:00:00Z","displayTime":"2025-10-28T11:00:00Z","isHosted":true,"bypassModal":false,"previewUrl":null,"thumbnail":{"originalUrl":"https://media.zenfs.com/en/business-wire.com/5d88f380228f3337ae0a20867d63000c","originalWidth":354,"originalHeight":480,"caption":"Corning Incorporated Q3 2025 8-K Financial Tables","resolutions":[{"url":"https://s.yimg.com/uu/api/res/1.2/BOiOp5FY1.Xi7CjgvJ2GAQ--~B/aD00ODA7dz0zNTQ7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/business-wire.com/5d88f380228f3337ae0a20867d63000c","width":354,"height":480,"tag":"original"},{"url":"https://s.yimg.com/uu/api/res/1.2/z2mKoc27QmroqQy4slvCCg--~B/Zmk9c3RyaW07aD0xMjg7dz0xNzA7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/business-wire.com/5d88f380228f3337ae0a20867d63000c","width":170,"height":128,"tag":"170x128"}]},"provider":{"displayName":"Business Wire","url":"http://www.businesswire.com/"},"canonicalUrl":{"url":"https://finance.yahoo.com/news/corning-announces-third-quarter-2025-110000584.html","site":"finance","region":"US","lang":"en-US"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/corning-announces-third-quarter-2025-110000584.html","site":"finance","region":"US","lang":"en-US"},"metadata":null,"finance":{"premiumFinance":{"isPremiumNews":false,"isPremiumFreeNews":false}},"storyline":null}},{"id":"a917755d-6b96-3207-9d94-f510790d52d1","content":{"id":"a917755d-6b96-3207-9d94-f510790d52d1","contentType":"STORY","title":"KEYNOTE MAESTRO FREDDIE RAVEL TO AWE AUDIENCE WITH LIFE IN TUNE® EXPERIENCE IN VENTURA COUNTY","description":"","summary":"Marketing Maven's new client, GRAMMY honored pianist, #1 recording artist, and thought leader Freddie Ravel, is pulling back the curtain on the transformative power of music. Having performed with legends like Earth, Wind & Fire, Madonna, Santana, and Prince, Ravel works with organizations like Google, Apple, Blue Cross, and NASA to bring teams \"back in tune,\" sparking unity, collaboration, and individual performance.","pubDate":"2025-10-28T00:04:00Z","displayTime":"2025-10-28T00:04:00Z","isHosted":true,"bypassModal":false,"previewUrl":null,"thumbnail":{"originalUrl":"https://media.zenfs.com/en/prnewswire.com/bede09f191ab526e72daba48da0dc6b6","originalWidth":284,"originalHeight":400,"caption":"Life in Tune® offers a unique and patented program that uses melody and harmony to make people and teams the best that they can be. The program allows one to enter “a symphony of solutions,” where one finds their melody for leadership, truth and personal alignment. Harmony is for collaboration, and Score is the achievement of a desired outcome. Teams can compose a unified Score that defines their vision, values and desired results.","resolutions":[{"url":"https://s.yimg.com/uu/api/res/1.2/TvFY9kllhPFPkiTFioaJTw--~B/aD00MDA7dz0yODQ7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/prnewswire.com/bede09f191ab526e72daba48da0dc6b6","width":284,"height":400,"tag":"original"},{"url":"https://s.yimg.com/uu/api/res/1.2/escOQ2ubriGTfn1H4wEhiQ--~B/Zmk9c3RyaW07aD0xMjg7dz0xNzA7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/prnewswire.com/bede09f191ab526e72daba48da0dc6b6","width":170,"height":128,"tag":"170x128"}]},"provider":{"displayName":"PR Newswire","url":"https://www.prnewswire.com/"},"canonicalUrl":{"url":"https://finance.yahoo.com/news/keynote-maestro-freddie-ravel-awe-000400454.html","site":"finance","region":"US","lang":"en-US"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/keynote-maestro-freddie-ravel-awe-000400454.html","site":"finance","region":"US","lang":"en-US"},"metadata":null,"finance":{"premiumFinance":{"isPremiumNews":false,"isPremiumFreeNews":false}},"storyline":null}},{"id":"f59dbec1-9644-3496-99ce-586e219f079c","content":{"id":"f59dbec1-9644-3496-99ce-586e219f079c","contentType":"STORY","title":"NYSE Content Advisory: Pre-Market Update + Project Nemo named Money20/20 Grand Prix Winner","description":"","summary":"The New York Stock Exchange (NYSE) provides a daily pre-market update directly from the NYSE Trading Floor. Access today's NYSE Pre-market update for market insights before trading begins.","pubDate":"2025-10-27T12:55:00Z","displayTime":"2025-10-27T12:55:00Z","isHosted":true,"bypassModal":false,"previewUrl":null,"thumbnail":{"originalUrl":"https://media.zenfs.com/en/prnewswire.com/b9671ae7464553162e92a9c647b76a24","originalWidth":400,"originalHeight":208,"caption":"NYSE Logo (PRNewsfoto/New York Stock Exchange)","resolutions":[{"url":"https://s.yimg.com/uu/api/res/1.2/k59AkJvBAW4GqmoY_jBo5w--~B/aD0yMDg7dz00MDA7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/prnewswire.com/b9671ae7464553162e92a9c647b76a24","width":400,"height":208,"tag":"original"},{"url":"https://s.yimg.com/uu/api/res/1.2/bSwHdAo13OSbQnXnne8PLw--~B/Zmk9c3RyaW07aD0xMjg7dz0xNzA7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/prnewswire.com/b9671ae7464553162e92a9c647b76a24","width":170,"height":128,"tag":"170x128"}]},"provider":{"displayName":"PR Newswire","url":"https://www.prnewswire.com/"},"canonicalUrl":{"url":"https://finance.yahoo.com/news/nyse-content-advisory-pre-market-125500018.html","site":"finance","region":"US","lang":"en-US"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/nyse-content-advisory-pre-market-125500018.html","site":"finance","region":"US","lang":"en-US"},"metadata":null,"finance":{"premiumFinance":{"isPremiumNews":false,"isPremiumFreeNews":false}},"storyline":null}},{"id":"0262b813-e536-3fa0-9e66-ea0ccf0aaff5","content":{"id":"0262b813-e536-3fa0-9e66-ea0ccf0aaff5","contentType":"STORY","title":"Medical Care Technologies Inc. (OTC PINK:MDCE) Approved as Official Apple Developer","description":"","summary":"Company advances digital technology initiatives with approval to publish applications on Apple's App Store MESA, ARIZONA / ACCESS Newswire / October 24, 2025 / Medical Care Technologies Inc. (OTC PINK:MDCE), a technology-driven company specializing ...","pubDate":"2025-10-24T13:30:00Z","displayTime":"2025-10-24T13:30:00Z","isHosted":true,"bypassModal":false,"previewUrl":null,"thumbnail":{"originalUrl":"https://media.zenfs.com/en/accesswire.ca/21d49c456213ec3d6f592c95e9ac0196","originalWidth":750,"originalHeight":750,"caption":"","resolutions":[{"url":"https://s.yimg.com/uu/api/res/1.2/.MD5kObh7lSwlavqSz9QSQ--~B/aD03NTA7dz03NTA7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/accesswire.ca/21d49c456213ec3d6f592c95e9ac0196","width":750,"height":750,"tag":"original"},{"url":"https://s.yimg.com/uu/api/res/1.2/xojB4OQdjUlj.avJBsRj3Q--~B/Zmk9c3RyaW07aD0xMjg7dz0xNzA7YXBwaWQ9eXRhY2h5b24-/https://media.zenfs.com/en/accesswire.ca/21d49c456213ec3d6f592c95e9ac0196","width":170,"height":128,"tag":"170x128"}]},"provider":{"displayName":"ACCESS Newswire","url":"https://www.accessnewswire.com/"},"canonicalUrl":{"url":"https://finance.yahoo.com/news/medical-care-technologies-inc-otc-133000105.html","site":"finance","region":"US","lang":"en-US"},"clickThroughUrl":{"url":"https://finance.yahoo.com/news/medical-care-technologies-inc-otc-133000105.html","site":"finance","region":"US","lang":"en-US"},"metadata":null,"finance":{"premiumFinance":{"isPremiumNews":false,"isPremiumFreeNews":false}},"storyline":null}}]}},"status":"OK"} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL.json new file mode 100644 index 0000000..5629487 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL.json @@ -0,0 +1 @@ +{"optionChain":{"result":[{"underlyingSymbol":"AAPL","expirationDates":[1761868800,1762473600,1763078400,1763683200,1764288000,1764892800,1766102400,1768521600,1771545600,1773964800,1776384000,1778803200,1781740800,1787270400,1789689600,1797552000,1799971200,1813190400,1829001600,1832025600],"strikes":[110.0,120.0,125.0,130.0,135.0,140.0,145.0,150.0,155.0,160.0,165.0,170.0,175.0,180.0,185.0,190.0,195.0,200.0,205.0,210.0,215.0,220.0,222.5,225.0,227.5,230.0,232.5,235.0,237.5,240.0,242.5,245.0,247.5,250.0,252.5,255.0,257.5,260.0,262.5,265.0,267.5,270.0,272.5,275.0,277.5,280.0,282.5,285.0,287.5,290.0,292.5,295.0,297.5,300.0,302.5,305.0,310.0,315.0,320.0,325.0,330.0],"hasMiniOptions":false,"quote":{"language":"en-US","region":"US","quoteType":"EQUITY","typeDisp":"Equity","quoteSourceName":"Nasdaq Real Time Price","triggerable":true,"customPriceAlertConfidence":"HIGH","currency":"USD","corporateActions":[],"postMarketTime":1761782396,"regularMarketTime":1761768001,"exchange":"NMS","messageBoardId":"finmb_24937","exchangeTimezoneName":"America/New_York","exchangeTimezoneShortName":"EDT","gmtOffSetMilliseconds":-14400000,"market":"us_market","esgPopulated":false,"regularMarketChangePercent":0.260228,"regularMarketPrice":269.7,"shortName":"Apple Inc.","postMarketChangePercent":0.38931692,"postMarketPrice":270.75,"postMarketChange":1.0499878,"regularMarketChange":0.700012,"regularMarketDayHigh":271.41,"regularMarketDayRange":"267.11 - 271.41","regularMarketDayLow":267.11,"regularMarketVolume":43332154,"regularMarketPreviousClose":269.0,"bid":255.72,"ask":269.86,"bidSize":1,"askSize":1,"fullExchangeName":"NasdaqGS","financialCurrency":"USD","regularMarketOpen":269.275,"averageDailyVolume3Month":54683671,"averageDailyVolume10Day":46237010,"fiftyTwoWeekLowChange":100.490005,"fiftyTwoWeekLowChangePercent":0.59387743,"fiftyTwoWeekRange":"169.21 - 271.41","fiftyTwoWeekHighChange":-1.7099915,"fiftyTwoWeekHighChangePercent":-0.0063003995,"fiftyTwoWeekLow":169.21,"fiftyTwoWeekHigh":271.41,"fiftyTwoWeekChangePercent":16.90569,"dividendDate":1755129600,"earningsTimestamp":1761854400,"earningsTimestampStart":1761854400,"earningsTimestampEnd":1761854400,"marketState":"POSTPOST","hasPrePostMarketData":true,"firstTradeDateMilliseconds":345479400000,"priceHint":2,"sharesOutstanding":14840390000,"bookValue":4.431,"fiftyDayAverage":245.6484,"fiftyDayAverageChange":24.051605,"fiftyDayAverageChangePercent":0.09791069,"twoHundredDayAverage":222.7724,"twoHundredDayAverageChange":46.927612,"twoHundredDayAverageChangePercent":0.21065272,"marketCap":4002453389312,"forwardPE":32.454872,"priceToBook":60.866623,"sourceInterval":15,"exchangeDataDelayedBy":0,"averageAnalystRating":"2.0 - Buy","tradeable":false,"cryptoTradeable":false,"earningsCallTimestampStart":1761858000,"earningsCallTimestampEnd":1761858000,"isEarningsDateEstimate":false,"trailingAnnualDividendRate":1.01,"trailingPE":40.925644,"dividendRate":1.04,"trailingAnnualDividendYield":0.0037546468,"dividendYield":0.39,"epsTrailingTwelveMonths":6.59,"epsForward":8.31,"epsCurrentYear":7.3908,"priceEpsCurrentYear":36.491314,"longName":"Apple Inc.","displayName":"Apple","symbol":"AAPL"},"options":[{"expirationDate":1761868800,"hasMiniOptions":false,"calls":[{"contractSymbol":"AAPL251031C00125000","strike":125.0,"currency":"USD","lastPrice":145.99,"change":2.1900024,"percentChange":1.5229502,"volume":2,"openInterest":29,"bid":143.5,"ask":145.45,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761746446,"impliedVolatility":5.398440751953125,"inTheMoney":true},{"contractSymbol":"AAPL251031C00140000","strike":140.0,"currency":"USD","lastPrice":107.52,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":2,"bid":128.6,"ask":130.4,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1760621651,"impliedVolatility":4.62500421875,"inTheMoney":true},{"contractSymbol":"AAPL251031C00150000","strike":150.0,"currency":"USD","lastPrice":118.51,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":20,"bid":118.65,"ask":120.4,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761664894,"impliedVolatility":4.191411010742188,"inTheMoney":true},{"contractSymbol":"AAPL251031C00155000","strike":155.0,"currency":"USD","lastPrice":105.37,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":315,"bid":113.5,"ask":115.5,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761243276,"impliedVolatility":4.0781299023437505,"inTheMoney":true},{"contractSymbol":"AAPL251031C00160000","strike":160.0,"currency":"USD","lastPrice":101.8,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":108.65,"ask":110.4,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761076602,"impliedVolatility":3.783203666992188,"inTheMoney":true},{"contractSymbol":"AAPL251031C00165000","strike":165.0,"currency":"USD","lastPrice":94.3,"change":0.0,"percentChange":0.0,"openInterest":1,"bid":103.5,"ask":105.5,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761232806,"impliedVolatility":3.673828940429688,"inTheMoney":true},{"contractSymbol":"AAPL251031C00170000","strike":170.0,"currency":"USD","lastPrice":92.86,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":6,"bid":98.65,"ask":100.45,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761321370,"impliedVolatility":3.4394545263671876,"inTheMoney":true},{"contractSymbol":"AAPL251031C00175000","strike":175.0,"currency":"USD","lastPrice":84.3,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":3,"bid":93.55,"ask":95.5,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761227523,"impliedVolatility":3.291017397460937,"inTheMoney":true},{"contractSymbol":"AAPL251031C00180000","strike":180.0,"currency":"USD","lastPrice":84.05,"change":0.0,"percentChange":0.0,"volume":12,"openInterest":33,"bid":89.25,"ask":90.55,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761330799,"impliedVolatility":2.488285029296875,"inTheMoney":true},{"contractSymbol":"AAPL251031C00185000","strike":185.0,"currency":"USD","lastPrice":83.8,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":3,"bid":83.9,"ask":85.5,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761662046,"impliedVolatility":0.500005,"inTheMoney":true},{"contractSymbol":"AAPL251031C00190000","strike":190.0,"currency":"USD","lastPrice":75.7,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":10,"bid":78.9,"ask":80.4,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761576764,"impliedVolatility":2.6826204809570315,"inTheMoney":true},{"contractSymbol":"AAPL251031C00195000","strike":195.0,"currency":"USD","lastPrice":73.69,"change":5.0,"percentChange":7.2790794,"volume":2,"openInterest":149,"bid":73.55,"ask":75.5,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761765332,"impliedVolatility":2.5791051147460937,"inTheMoney":true},{"contractSymbol":"AAPL251031C00200000","strike":200.0,"currency":"USD","lastPrice":68.48,"change":-0.23999786,"percentChange":-0.34924018,"volume":7,"openInterest":158,"bid":69.1,"ask":70.45,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761766447,"impliedVolatility":1.671876640625,"inTheMoney":true},{"contractSymbol":"AAPL251031C00205000","strike":205.0,"currency":"USD","lastPrice":62.7,"change":-0.95000076,"percentChange":-1.4925385,"volume":3,"openInterest":67,"bid":64.05,"ask":65.5,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761751955,"impliedVolatility":1.5468772656249998,"inTheMoney":true},{"contractSymbol":"AAPL251031C00210000","strike":210.0,"currency":"USD","lastPrice":58.67,"change":2.0699997,"percentChange":3.6572435,"volume":48,"openInterest":322,"bid":59.05,"ask":60.55,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761766875,"impliedVolatility":1.48047134765625,"inTheMoney":true},{"contractSymbol":"AAPL251031C00215000","strike":215.0,"currency":"USD","lastPrice":53.89,"change":-0.61999893,"percentChange":-1.1374041,"volume":38,"openInterest":293,"bid":53.9,"ask":55.5,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761765325,"impliedVolatility":0.500005,"inTheMoney":true},{"contractSymbol":"AAPL251031C00220000","strike":220.0,"currency":"USD","lastPrice":49.11,"change":0.060001373,"percentChange":0.12232696,"volume":6,"openInterest":1000,"bid":48.9,"ask":50.45,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761757805,"impliedVolatility":1.734376328125,"inTheMoney":true},{"contractSymbol":"AAPL251031C00222500","strike":222.5,"currency":"USD","lastPrice":46.28,"change":-0.97999954,"percentChange":-2.0736344,"volume":1,"openInterest":262,"bid":46.1,"ask":48.0,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761765067,"impliedVolatility":1.6796891015625,"inTheMoney":true},{"contractSymbol":"AAPL251031C00225000","strike":225.0,"currency":"USD","lastPrice":44.91,"change":0.3600006,"percentChange":0.80808216,"volume":77,"openInterest":2341,"bid":43.95,"ask":45.9,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767784,"impliedVolatility":1.2578162109375,"inTheMoney":true},{"contractSymbol":"AAPL251031C00227500","strike":227.5,"currency":"USD","lastPrice":40.46,"change":-1.5400009,"percentChange":-3.666669,"volume":3,"openInterest":1033,"bid":41.1,"ask":43.05,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761753687,"impliedVolatility":1.5439475927734374,"inTheMoney":true},{"contractSymbol":"AAPL251031C00230000","strike":230.0,"currency":"USD","lastPrice":38.71,"change":-0.5699997,"percentChange":-1.4511194,"volume":24,"openInterest":908,"bid":38.45,"ask":40.5,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761765932,"impliedVolatility":1.4448269946289063,"inTheMoney":true},{"contractSymbol":"AAPL251031C00232500","strike":232.5,"currency":"USD","lastPrice":35.5,"change":-2.0,"percentChange":-5.3333335,"volume":10,"openInterest":52,"bid":36.1,"ask":38.05,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761751588,"impliedVolatility":1.3867218164062498,"inTheMoney":true},{"contractSymbol":"AAPL251031C00235000","strike":235.0,"currency":"USD","lastPrice":34.5,"change":0.13000107,"percentChange":0.37823996,"volume":81,"openInterest":1883,"bid":34.3,"ask":35.8,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767109,"impliedVolatility":1.0810592822265626,"inTheMoney":true},{"contractSymbol":"AAPL251031C00237500","strike":237.5,"currency":"USD","lastPrice":31.11,"change":-0.7399998,"percentChange":-2.3233902,"volume":12,"openInterest":234,"bid":31.35,"ask":32.95,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761765392,"impliedVolatility":1.193363408203125,"inTheMoney":true},{"contractSymbol":"AAPL251031C00240000","strike":240.0,"currency":"USD","lastPrice":29.38,"change":-0.060001373,"percentChange":-0.20380901,"volume":83,"openInterest":1961,"bid":29.3,"ask":30.2,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767925,"impliedVolatility":0.6914093359375001,"inTheMoney":true},{"contractSymbol":"AAPL251031C00242500","strike":242.5,"currency":"USD","lastPrice":26.95,"change":0.32999992,"percentChange":1.2396691,"volume":6,"openInterest":488,"bid":26.85,"ask":28.35,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767827,"impliedVolatility":0.9003916210937499,"inTheMoney":true},{"contractSymbol":"AAPL251031C00245000","strike":245.0,"currency":"USD","lastPrice":24.85,"change":0.59000015,"percentChange":2.4319875,"volume":1555,"openInterest":3860,"bid":24.6,"ask":25.8,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767168,"impliedVolatility":0.8720715917968749,"inTheMoney":true},{"contractSymbol":"AAPL251031C00247500","strike":247.5,"currency":"USD","lastPrice":23.0,"change":1.0,"percentChange":4.5454545,"volume":56,"openInterest":1214,"bid":22.2,"ask":23.35,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767445,"impliedVolatility":0.8266618896484373,"inTheMoney":true},{"contractSymbol":"AAPL251031C00250000","strike":250.0,"currency":"USD","lastPrice":19.6,"change":-0.049999237,"percentChange":-0.25444904,"volume":942,"openInterest":7503,"bid":19.8,"ask":20.4,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767881,"impliedVolatility":0.6894562304687502,"inTheMoney":true},{"contractSymbol":"AAPL251031C00252500","strike":252.5,"currency":"USD","lastPrice":17.95,"change":0.80000114,"percentChange":4.6647296,"volume":92,"openInterest":1571,"bid":17.45,"ask":18.1,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767327,"impliedVolatility":0.676761044921875,"inTheMoney":true},{"contractSymbol":"AAPL251031C00255000","strike":255.0,"currency":"USD","lastPrice":15.45,"change":0.25,"percentChange":1.6447369,"volume":4682,"openInterest":6100,"bid":15.3,"ask":16.0,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767983,"impliedVolatility":0.6962920996093751,"inTheMoney":true},{"contractSymbol":"AAPL251031C00257500","strike":257.5,"currency":"USD","lastPrice":13.1,"change":0.10000038,"percentChange":0.7692337,"volume":505,"openInterest":2851,"bid":13.2,"ask":13.8,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767583,"impliedVolatility":0.6831086376953126,"inTheMoney":true},{"contractSymbol":"AAPL251031C00260000","strike":260.0,"currency":"USD","lastPrice":11.2,"change":0.18999958,"percentChange":1.7257,"volume":2048,"openInterest":11586,"bid":11.3,"ask":11.55,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767969,"impliedVolatility":0.6645541357421877,"inTheMoney":true},{"contractSymbol":"AAPL251031C00262500","strike":262.5,"currency":"USD","lastPrice":9.31,"change":0.1600008,"percentChange":1.7486427,"volume":1724,"openInterest":17211,"bid":9.4,"ask":9.6,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767962,"impliedVolatility":0.6508823974609376,"inTheMoney":true},{"contractSymbol":"AAPL251031C00265000","strike":265.0,"currency":"USD","lastPrice":7.59,"change":0.23000002,"percentChange":3.1250002,"volume":4928,"openInterest":17992,"bid":7.7,"ask":7.9,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767982,"impliedVolatility":0.6472203247070312,"inTheMoney":true},{"contractSymbol":"AAPL251031C00267500","strike":267.5,"currency":"USD","lastPrice":6.03,"change":0.05000019,"percentChange":0.8361236,"volume":6131,"openInterest":9269,"bid":6.15,"ask":6.3,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767974,"impliedVolatility":0.6364782446289063,"inTheMoney":true},{"contractSymbol":"AAPL251031C00270000","strike":270.0,"currency":"USD","lastPrice":4.8,"change":0.22000027,"percentChange":4.803499,"volume":26764,"openInterest":22123,"bid":4.8,"ask":5.0,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767998,"impliedVolatility":0.6335485864257813,"inTheMoney":false},{"contractSymbol":"AAPL251031C00272500","strike":272.5,"currency":"USD","lastPrice":3.65,"change":0.1500001,"percentChange":4.285717,"volume":13259,"openInterest":14363,"bid":3.7,"ask":3.85,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767996,"impliedVolatility":0.6311072045898438,"inTheMoney":false},{"contractSymbol":"AAPL251031C00275000","strike":275.0,"currency":"USD","lastPrice":2.82,"change":0.25,"percentChange":9.727627,"volume":14829,"openInterest":18707,"bid":2.8,"ask":2.85,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767999,"impliedVolatility":0.6264685791015626,"inTheMoney":false},{"contractSymbol":"AAPL251031C00277500","strike":277.5,"currency":"USD","lastPrice":2.07,"change":0.19999993,"percentChange":10.695183,"volume":4858,"openInterest":5423,"bid":2.06,"ask":2.11,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767990,"impliedVolatility":0.6259803027343751,"inTheMoney":false},{"contractSymbol":"AAPL251031C00280000","strike":280.0,"currency":"USD","lastPrice":1.5,"change":0.19000006,"percentChange":14.503821,"volume":16440,"openInterest":17968,"bid":1.5,"ask":1.54,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767999,"impliedVolatility":0.6281775463867189,"inTheMoney":false},{"contractSymbol":"AAPL251031C00282500","strike":282.5,"currency":"USD","lastPrice":1.06,"change":0.14999992,"percentChange":16.483507,"volume":4649,"openInterest":5291,"bid":1.08,"ask":1.13,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767982,"impliedVolatility":0.6342810009765626,"inTheMoney":false},{"contractSymbol":"AAPL251031C00285000","strike":285.0,"currency":"USD","lastPrice":0.8,"change":0.17000002,"percentChange":26.98413,"volume":10158,"openInterest":7512,"bid":0.79,"ask":0.82,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767989,"impliedVolatility":0.6430699755859376,"inTheMoney":false},{"contractSymbol":"AAPL251031C00287500","strike":287.5,"currency":"USD","lastPrice":0.57,"change":0.15,"percentChange":35.71429,"volume":2453,"openInterest":2689,"bid":0.57,"ask":0.61,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767986,"impliedVolatility":0.6547886083984377,"inTheMoney":false},{"contractSymbol":"AAPL251031C00290000","strike":290.0,"currency":"USD","lastPrice":0.41,"change":0.109999985,"percentChange":36.66666,"volume":4706,"openInterest":7216,"bid":0.42,"ask":0.46,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767992,"impliedVolatility":0.6694368994140626,"inTheMoney":false},{"contractSymbol":"AAPL251031C00292500","strike":292.5,"currency":"USD","lastPrice":0.31,"change":0.09,"percentChange":40.909092,"volume":1114,"openInterest":817,"bid":0.32,"ask":0.35,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767960,"impliedVolatility":0.6865265722656251,"inTheMoney":false},{"contractSymbol":"AAPL251031C00295000","strike":295.0,"currency":"USD","lastPrice":0.24,"change":0.08,"percentChange":50.0,"volume":894,"openInterest":6509,"bid":0.24,"ask":0.29,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767876,"impliedVolatility":0.70898728515625,"inTheMoney":false},{"contractSymbol":"AAPL251031C00297500","strike":297.5,"currency":"USD","lastPrice":0.2,"change":0.1,"percentChange":100.0,"volume":541,"openInterest":1253,"bid":0.2,"ask":0.22,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767909,"impliedVolatility":0.7304714453125,"inTheMoney":false},{"contractSymbol":"AAPL251031C00300000","strike":300.0,"currency":"USD","lastPrice":0.16,"change":0.06999999,"percentChange":77.77776,"volume":7109,"openInterest":9828,"bid":0.15,"ask":0.16,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767994,"impliedVolatility":0.7412135253906249,"inTheMoney":false},{"contractSymbol":"AAPL251031C00302500","strike":302.5,"currency":"USD","lastPrice":0.14,"change":0.08,"percentChange":133.33333,"volume":298,"openInterest":909,"bid":0.12,"ask":0.15,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767386,"impliedVolatility":0.77148666015625,"inTheMoney":false},{"contractSymbol":"AAPL251031C00305000","strike":305.0,"currency":"USD","lastPrice":0.11,"change":0.07,"percentChange":175.0,"volume":7028,"openInterest":2619,"bid":0.1,"ask":0.11,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767814,"impliedVolatility":0.78711150390625,"inTheMoney":false},{"contractSymbol":"AAPL251031C00310000","strike":310.0,"currency":"USD","lastPrice":0.08,"change":0.06,"percentChange":300.0,"volume":2174,"openInterest":3817,"bid":0.08,"ask":0.09,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767920,"impliedVolatility":0.84961087890625,"inTheMoney":false},{"contractSymbol":"AAPL251031C00315000","strike":315.0,"currency":"USD","lastPrice":0.08,"change":0.07,"percentChange":700.0001,"volume":3856,"openInterest":992,"bid":0.06,"ask":0.08,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767494,"impliedVolatility":0.9101571484375,"inTheMoney":false},{"contractSymbol":"AAPL251031C00320000","strike":320.0,"currency":"USD","lastPrice":0.04,"change":0.03,"percentChange":300.0,"volume":7248,"openInterest":1999,"bid":0.04,"ask":0.07,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767344,"impliedVolatility":0.9570316796874999,"inTheMoney":false},{"contractSymbol":"AAPL251031C00325000","strike":325.0,"currency":"USD","lastPrice":0.04,"change":0.03,"percentChange":300.0,"volume":3257,"openInterest":939,"bid":0.03,"ask":0.06,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767488,"impliedVolatility":1.0078174609375,"inTheMoney":false},{"contractSymbol":"AAPL251031C00330000","strike":330.0,"currency":"USD","lastPrice":0.03,"change":0.02,"percentChange":200.0,"volume":2182,"openInterest":971,"bid":0.02,"ask":0.04,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767171,"impliedVolatility":1.0351610742187503,"inTheMoney":false}],"puts":[{"contractSymbol":"AAPL251031P00110000","strike":110.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":25,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761658201,"impliedVolatility":3.6250009375000003,"inTheMoney":false},{"contractSymbol":"AAPL251031P00120000","strike":120.0,"currency":"USD","lastPrice":0.05,"change":0.0,"percentChange":0.0,"openInterest":1,"bid":0.0,"ask":0.02,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1757965090,"impliedVolatility":3.468751328125,"inTheMoney":false},{"contractSymbol":"AAPL251031P00125000","strike":125.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":6,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1760635357,"impliedVolatility":3.1250021875,"inTheMoney":false},{"contractSymbol":"AAPL251031P00130000","strike":130.0,"currency":"USD","lastPrice":0.05,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":13,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1759162638,"impliedVolatility":3.0000025,"inTheMoney":false},{"contractSymbol":"AAPL251031P00135000","strike":135.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":46,"openInterest":49,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1760967939,"impliedVolatility":2.81250296875,"inTheMoney":false},{"contractSymbol":"AAPL251031P00140000","strike":140.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":23,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761582339,"impliedVolatility":2.68750328125,"inTheMoney":false},{"contractSymbol":"AAPL251031P00145000","strike":145.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":17,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1759844128,"impliedVolatility":2.56250359375,"inTheMoney":false},{"contractSymbol":"AAPL251031P00150000","strike":150.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":65,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761582182,"impliedVolatility":2.43750390625,"inTheMoney":false},{"contractSymbol":"AAPL251031P00155000","strike":155.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":11,"openInterest":55,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1760983893,"impliedVolatility":2.3125042187499996,"inTheMoney":false},{"contractSymbol":"AAPL251031P00160000","strike":160.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":149,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761579470,"impliedVolatility":2.1875045312499997,"inTheMoney":false},{"contractSymbol":"AAPL251031P00165000","strike":165.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":22,"openInterest":139,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761232359,"impliedVolatility":2.0625048437499998,"inTheMoney":false},{"contractSymbol":"AAPL251031P00170000","strike":170.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":278,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761579456,"impliedVolatility":1.9375003125,"inTheMoney":false},{"contractSymbol":"AAPL251031P00175000","strike":175.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":6,"openInterest":248,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761571921,"impliedVolatility":1.8125009374999999,"inTheMoney":false},{"contractSymbol":"AAPL251031P00180000","strike":180.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":14,"openInterest":332,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761576113,"impliedVolatility":1.6875015625,"inTheMoney":false},{"contractSymbol":"AAPL251031P00185000","strike":185.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":230,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761230370,"impliedVolatility":1.5937520312499998,"inTheMoney":false},{"contractSymbol":"AAPL251031P00190000","strike":190.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":458,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761666372,"impliedVolatility":1.5000025,"inTheMoney":false},{"contractSymbol":"AAPL251031P00195000","strike":195.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":11,"openInterest":237,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761761352,"impliedVolatility":1.3750031249999999,"inTheMoney":false},{"contractSymbol":"AAPL251031P00200000","strike":200.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":1275,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761752269,"impliedVolatility":1.2812535937499998,"inTheMoney":false},{"contractSymbol":"AAPL251031P00205000","strike":205.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":250,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761679824,"impliedVolatility":1.1875040625,"inTheMoney":false},{"contractSymbol":"AAPL251031P00210000","strike":210.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":83,"openInterest":2788,"bid":0.01,"ask":0.03,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767100,"impliedVolatility":1.2343788281249999,"inTheMoney":false},{"contractSymbol":"AAPL251031P00215000","strike":215.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":6,"openInterest":777,"bid":0.0,"ask":0.02,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761754444,"impliedVolatility":1.0625046875000002,"inTheMoney":false},{"contractSymbol":"AAPL251031P00220000","strike":220.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":330,"openInterest":3766,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767557,"impliedVolatility":0.992187578125,"inTheMoney":false},{"contractSymbol":"AAPL251031P00222500","strike":222.5,"currency":"USD","lastPrice":0.04,"change":0.02,"percentChange":100.0,"volume":65,"openInterest":83,"bid":0.0,"ask":0.03,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761763310,"impliedVolatility":0.937500625,"inTheMoney":false},{"contractSymbol":"AAPL251031P00225000","strike":225.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":271,"openInterest":2927,"bid":0.01,"ask":0.03,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767609,"impliedVolatility":0.92187578125,"inTheMoney":false},{"contractSymbol":"AAPL251031P00227500","strike":227.5,"currency":"USD","lastPrice":0.03,"change":0.0,"percentChange":0.0,"volume":670,"openInterest":1246,"bid":0.01,"ask":0.03,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761762821,"impliedVolatility":0.867188828125,"inTheMoney":false},{"contractSymbol":"AAPL251031P00230000","strike":230.0,"currency":"USD","lastPrice":0.04,"change":0.01,"percentChange":33.333336,"volume":1692,"openInterest":4404,"bid":0.02,"ask":0.04,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767961,"impliedVolatility":0.8554701953125,"inTheMoney":false},{"contractSymbol":"AAPL251031P00232500","strike":232.5,"currency":"USD","lastPrice":0.04,"change":-0.010000002,"percentChange":-20.000002,"volume":1588,"openInterest":1344,"bid":0.04,"ask":0.05,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767454,"impliedVolatility":0.8437515624999999,"inTheMoney":false},{"contractSymbol":"AAPL251031P00235000","strike":235.0,"currency":"USD","lastPrice":0.05,"change":-0.02,"percentChange":-28.571428,"volume":1853,"openInterest":3688,"bid":0.04,"ask":0.05,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767989,"impliedVolatility":0.789064609375,"inTheMoney":false},{"contractSymbol":"AAPL251031P00237500","strike":237.5,"currency":"USD","lastPrice":0.05,"change":-0.040000003,"percentChange":-44.444447,"volume":315,"openInterest":7898,"bid":0.05,"ask":0.07,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767794,"impliedVolatility":0.7617211328125001,"inTheMoney":false},{"contractSymbol":"AAPL251031P00240000","strike":240.0,"currency":"USD","lastPrice":0.09,"change":-0.009999998,"percentChange":-9.999997,"volume":1839,"openInterest":7163,"bid":0.08,"ask":0.09,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767972,"impliedVolatility":0.7421900781249999,"inTheMoney":false},{"contractSymbol":"AAPL251031P00242500","strike":242.5,"currency":"USD","lastPrice":0.11,"change":-0.030000001,"percentChange":-21.428572,"volume":1155,"openInterest":1953,"bid":0.1,"ask":0.13,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767958,"impliedVolatility":0.71679970703125,"inTheMoney":false},{"contractSymbol":"AAPL251031P00245000","strike":245.0,"currency":"USD","lastPrice":0.16,"change":-0.049999997,"percentChange":-23.809523,"volume":2531,"openInterest":6923,"bid":0.15,"ask":0.16,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767989,"impliedVolatility":0.6914093359375001,"inTheMoney":false},{"contractSymbol":"AAPL251031P00247500","strike":247.5,"currency":"USD","lastPrice":0.24,"change":-0.030000016,"percentChange":-11.111116,"volume":2020,"openInterest":3423,"bid":0.21,"ask":0.25,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767964,"impliedVolatility":0.6777375976562501,"inTheMoney":false},{"contractSymbol":"AAPL251031P00250000","strike":250.0,"currency":"USD","lastPrice":0.35,"change":-0.060000002,"percentChange":-14.634147,"volume":6377,"openInterest":10042,"bid":0.32,"ask":0.35,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767961,"impliedVolatility":0.6621127539062501,"inTheMoney":false},{"contractSymbol":"AAPL251031P00252500","strike":252.5,"currency":"USD","lastPrice":0.52,"change":-0.060000002,"percentChange":-10.344828,"volume":2111,"openInterest":7338,"bid":0.47,"ask":0.5,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767994,"impliedVolatility":0.647464462890625,"inTheMoney":false},{"contractSymbol":"AAPL251031P00255000","strike":255.0,"currency":"USD","lastPrice":0.72,"change":-0.109999955,"percentChange":-13.253007,"volume":4713,"openInterest":6847,"bid":0.7,"ask":0.73,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767999,"impliedVolatility":0.6381872119140625,"inTheMoney":false},{"contractSymbol":"AAPL251031P00257500","strike":257.5,"currency":"USD","lastPrice":1.05,"change":-0.13,"percentChange":-11.01695,"volume":6301,"openInterest":6253,"bid":1.03,"ask":1.07,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767986,"impliedVolatility":0.6323278955078125,"inTheMoney":false},{"contractSymbol":"AAPL251031P00260000","strike":260.0,"currency":"USD","lastPrice":1.56,"change":-0.110000014,"percentChange":-6.5868278,"volume":7654,"openInterest":19169,"bid":1.49,"ask":1.53,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767970,"impliedVolatility":0.6279334082031252,"inTheMoney":false},{"contractSymbol":"AAPL251031P00262500","strike":262.5,"currency":"USD","lastPrice":2.2,"change":-0.12999988,"percentChange":-5.579394,"volume":4594,"openInterest":9483,"bid":2.09,"ask":2.15,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767985,"impliedVolatility":0.6237830590820312,"inTheMoney":false},{"contractSymbol":"AAPL251031P00265000","strike":265.0,"currency":"USD","lastPrice":3.04,"change":-0.16000009,"percentChange":-5.0000024,"volume":7171,"openInterest":8901,"bid":2.83,"ask":2.95,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767960,"impliedVolatility":0.6186561572265626,"inTheMoney":false},{"contractSymbol":"AAPL251031P00267500","strike":267.5,"currency":"USD","lastPrice":3.9,"change":-0.2999997,"percentChange":-7.142851,"volume":6763,"openInterest":3661,"bid":3.8,"ask":3.95,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767996,"impliedVolatility":0.6174354663085939,"inTheMoney":false},{"contractSymbol":"AAPL251031P00270000","strike":270.0,"currency":"USD","lastPrice":5.21,"change":-0.13999987,"percentChange":-2.61682,"volume":10888,"openInterest":6834,"bid":4.9,"ask":5.1,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767991,"impliedVolatility":0.6084023535156251,"inTheMoney":true},{"contractSymbol":"AAPL251031P00272500","strike":272.5,"currency":"USD","lastPrice":6.65,"change":-0.049999714,"percentChange":-0.7462644,"volume":1194,"openInterest":1022,"bid":6.25,"ask":6.45,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767822,"impliedVolatility":0.6022988989257814,"inTheMoney":true},{"contractSymbol":"AAPL251031P00275000","strike":275.0,"currency":"USD","lastPrice":8.25,"change":-0.25,"percentChange":-2.9411764,"volume":1377,"openInterest":4509,"bid":7.85,"ask":8.05,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767788,"impliedVolatility":0.6025430371093752,"inTheMoney":true},{"contractSymbol":"AAPL251031P00277500","strike":277.5,"currency":"USD","lastPrice":9.3,"change":-1.0500002,"percentChange":-10.144929,"volume":418,"openInterest":655,"bid":9.55,"ask":9.85,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767591,"impliedVolatility":0.5981485498046876,"inTheMoney":true},{"contractSymbol":"AAPL251031P00280000","strike":280.0,"currency":"USD","lastPrice":11.34,"change":-0.71000004,"percentChange":-5.892116,"volume":831,"openInterest":1345,"bid":11.5,"ask":11.8,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767615,"impliedVolatility":0.5988809643554689,"inTheMoney":true},{"contractSymbol":"AAPL251031P00282500","strike":282.5,"currency":"USD","lastPrice":14.1,"change":0.10000038,"percentChange":0.7142884,"volume":23,"openInterest":19,"bid":13.2,"ask":14.75,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767790,"impliedVolatility":0.6479527392578125,"inTheMoney":true},{"contractSymbol":"AAPL251031P00285000","strike":285.0,"currency":"USD","lastPrice":16.5,"change":0.0,"percentChange":0.0,"volume":38,"openInterest":38,"bid":15.5,"ask":16.6,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761676559,"impliedVolatility":0.629886513671875,"inTheMoney":true},{"contractSymbol":"AAPL251031P00287500","strike":287.5,"currency":"USD","lastPrice":17.73,"change":-1.0200005,"percentChange":-5.4400024,"volume":2,"openInterest":11,"bid":17.45,"ask":19.35,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761745048,"impliedVolatility":0.6577182666015625,"inTheMoney":true},{"contractSymbol":"AAPL251031P00290000","strike":290.0,"currency":"USD","lastPrice":20.8,"change":-0.52000046,"percentChange":-2.4390266,"volume":41,"openInterest":16,"bid":20.0,"ask":21.35,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761762435,"impliedVolatility":0.6455113574218749,"inTheMoney":true},{"contractSymbol":"AAPL251031P00295000","strike":295.0,"currency":"USD","lastPrice":25.5,"change":-0.45000076,"percentChange":-1.7341069,"volume":2,"openInterest":14,"bid":24.7,"ask":26.5,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761763158,"impliedVolatility":0.726565234375,"inTheMoney":true},{"contractSymbol":"AAPL251031P00300000","strike":300.0,"currency":"USD","lastPrice":32.0,"change":1.0,"percentChange":3.2258065,"volume":8,"openInterest":0,"bid":29.6,"ask":31.6,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761766419,"impliedVolatility":0.8330094824218748,"inTheMoney":true},{"contractSymbol":"AAPL251031P00310000","strike":310.0,"currency":"USD","lastPrice":56.26,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":46.55,"ask":47.85,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1758807002,"impliedVolatility":2.48901744934082,"inTheMoney":true},{"contractSymbol":"AAPL251031P00315000","strike":315.0,"currency":"USD","lastPrice":51.78,"change":0.0,"percentChange":0.0,"volume":14,"openInterest":0,"bid":44.2,"ask":46.4,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1760986525,"impliedVolatility":0.500005,"inTheMoney":true},{"contractSymbol":"AAPL251031P00320000","strike":320.0,"currency":"USD","lastPrice":59.55,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":49.25,"ask":51.55,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761248907,"impliedVolatility":1.0351610742187503,"inTheMoney":true},{"contractSymbol":"AAPL251031P00325000","strike":325.0,"currency":"USD","lastPrice":64.57,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":54.25,"ask":56.4,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761248907,"impliedVolatility":0.945313046875,"inTheMoney":true}]}]}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1757030400.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1757030400.json new file mode 100644 index 0000000..7b5cef7 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1757030400.json @@ -0,0 +1 @@ +{"optionChain":{"result":[{"underlyingSymbol":"AAPL","expirationDates":[1757030400,1757635200,1758240000,1758844800,1759449600,1760054400,1760659200,1763683200,1766102400,1768521600,1771545600,1773964800,1776384000,1778803200,1781740800,1787270400,1789689600,1797552000,1799971200,1813190400,1829001600],"strikes":[110.0,125.0,130.0,135.0,140.0,145.0,150.0,155.0,160.0,165.0,170.0,175.0,180.0,185.0,190.0,195.0,197.5,200.0,202.5,205.0,207.5,210.0,212.5,215.0,217.5,220.0,222.5,225.0,227.5,230.0,232.5,235.0,237.5,240.0,242.5,245.0,247.5,250.0,252.5,255.0,257.5,260.0,262.5,265.0,270.0,275.0,280.0,285.0,290.0,295.0,300.0,305.0,315.0,320.0,325.0],"hasMiniOptions":false,"quote":{"language":"en-US","region":"US","quoteType":"EQUITY","typeDisp":"Equity","quoteSourceName":"Delayed Quote","triggerable":true,"customPriceAlertConfidence":"HIGH","hasPrePostMarketData":true,"firstTradeDateMilliseconds":345479400000,"priceHint":2,"postMarketChangePercent":-0.04738546,"postMarketPrice":232.03,"postMarketChange":-0.11000061,"regularMarketChange":-0.41999817,"regularMarketDayHigh":233.3613,"regularMarketDayRange":"231.37 - 233.3613","regularMarketDayLow":231.37,"regularMarketVolume":39418437,"regularMarketPreviousClose":232.56,"bid":232.04,"ask":232.28,"bidSize":8,"askSize":8,"fullExchangeName":"NasdaqGS","financialCurrency":"USD","regularMarketOpen":232.56,"averageDailyVolume3Month":53942777,"averageDailyVolume10Day":38652350,"fiftyTwoWeekLowChange":62.929993,"fiftyTwoWeekLowChangePercent":0.37190467,"fiftyTwoWeekRange":"169.21 - 260.1","fiftyTwoWeekHighChange":-27.960007,"fiftyTwoWeekHighChangePercent":-0.10749714,"fiftyTwoWeekLow":169.21,"fiftyTwoWeekHigh":260.1,"fiftyTwoWeekChangePercent":4.206133,"dividendDate":1755129600,"earningsTimestamp":1753992000,"earningsTimestampStart":1761854400,"earningsTimestampEnd":1761854400,"earningsCallTimestampStart":1753995600,"earningsCallTimestampEnd":1753995600,"isEarningsDateEstimate":true,"trailingAnnualDividendRate":1.01,"trailingPE":35.2261,"dividendRate":1.04,"trailingAnnualDividendYield":0.0043429653,"dividendYield":0.45,"epsTrailingTwelveMonths":6.59,"epsForward":8.31,"epsCurrentYear":7.38566,"priceEpsCurrentYear":31.431177,"sharesOutstanding":14840399872,"bookValue":4.431,"fiftyDayAverage":215.5476,"fiftyDayAverageChange":16.592392,"fiftyDayAverageChangePercent":0.07697785,"twoHundredDayAverage":221.1669,"twoHundredDayAverageChange":10.973099,"twoHundredDayAverageChangePercent":0.04961456,"marketCap":3445050310656,"forwardPE":27.935017,"priceToBook":52.389977,"sourceInterval":15,"exchangeDataDelayedBy":0,"averageAnalystRating":"2.0 - Buy","tradeable":false,"cryptoTradeable":false,"currency":"USD","shortName":"Apple Inc.","longName":"Apple Inc.","marketState":"CLOSED","regularMarketChangePercent":-0.18059777,"regularMarketPrice":232.14,"corporateActions":[],"postMarketTime":1756511989,"regularMarketTime":1756497601,"exchange":"NMS","messageBoardId":"finmb_24937","exchangeTimezoneName":"America/New_York","exchangeTimezoneShortName":"EDT","gmtOffSetMilliseconds":-14400000,"market":"us_market","esgPopulated":false,"displayName":"Apple","symbol":"AAPL"},"options":[{"expirationDate":1757030400,"hasMiniOptions":false,"calls":[{"contractSymbol":"AAPL250905C00110000","strike":110.0,"currency":"USD","lastPrice":119.65,"change":0.0,"percentChange":0.0,"openInterest":3,"bid":120.2,"ask":123.75,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756309236,"impliedVolatility":3.9433595166015625,"inTheMoney":true},{"contractSymbol":"AAPL250905C00125000","strike":125.0,"currency":"USD","lastPrice":106.65,"change":0.0,"percentChange":0.0,"openInterest":2,"bid":105.2,"ask":109.05,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1755287400,"impliedVolatility":3.4785169287109374,"inTheMoney":true},{"contractSymbol":"AAPL250905C00135000","strike":135.0,"currency":"USD","lastPrice":91.35,"change":0.0,"percentChange":0.0,"openInterest":4,"bid":95.4,"ask":99.15,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1755709130,"impliedVolatility":1.984375078125,"inTheMoney":true},{"contractSymbol":"AAPL250905C00140000","strike":140.0,"currency":"USD","lastPrice":85.13,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":1,"bid":90.4,"ask":93.75,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1755803828,"impliedVolatility":2.8222685693359377,"inTheMoney":true},{"contractSymbol":"AAPL250905C00150000","strike":150.0,"currency":"USD","lastPrice":78.0,"change":0.0,"percentChange":0.0,"volume":66,"openInterest":76,"bid":80.25,"ask":83.75,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756149159,"impliedVolatility":2.4980506298828127,"inTheMoney":true},{"contractSymbol":"AAPL250905C00155000","strike":155.0,"currency":"USD","lastPrice":49.2,"change":0.0,"percentChange":0.0,"volume":12,"openInterest":14,"bid":75.25,"ask":78.75,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1754413105,"impliedVolatility":2.3427775805664055,"inTheMoney":true},{"contractSymbol":"AAPL250905C00160000","strike":160.0,"currency":"USD","lastPrice":71.7,"change":0.0,"percentChange":0.0,"openInterest":3,"bid":70.25,"ask":73.75,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1755287400,"impliedVolatility":2.1918990515136714,"inTheMoney":true},{"contractSymbol":"AAPL250905C00165000","strike":165.0,"currency":"USD","lastPrice":64.9,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":2,"bid":65.9,"ask":68.6,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756309229,"impliedVolatility":1.2734411328124997,"inTheMoney":true},{"contractSymbol":"AAPL250905C00170000","strike":170.0,"currency":"USD","lastPrice":58.0,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":4,"bid":60.25,"ask":63.7,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1754941484,"impliedVolatility":1.8857427587890623,"inTheMoney":true},{"contractSymbol":"AAPL250905C00175000","strike":175.0,"currency":"USD","lastPrice":53.05,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":10,"bid":55.25,"ask":58.7,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756148998,"impliedVolatility":1.7451184619140623,"inTheMoney":true},{"contractSymbol":"AAPL250905C00180000","strike":180.0,"currency":"USD","lastPrice":52.6,"change":-0.8500023,"percentChange":-1.5902755,"volume":11,"openInterest":216,"bid":51.45,"ask":53.05,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756495949,"impliedVolatility":0.9804689453125001,"inTheMoney":true},{"contractSymbol":"AAPL250905C00185000","strike":185.0,"currency":"USD","lastPrice":47.8,"change":2.4799995,"percentChange":5.4721966,"volume":4,"openInterest":57,"bid":45.8,"ask":47.8,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756487009,"impliedVolatility":1.2036172631835935,"inTheMoney":true},{"contractSymbol":"AAPL250905C00190000","strike":190.0,"currency":"USD","lastPrice":40.45,"change":0.0,"percentChange":0.0,"volume":7,"openInterest":188,"bid":40.95,"ask":43.7,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756388402,"impliedVolatility":0.860352958984375,"inTheMoney":true},{"contractSymbol":"AAPL250905C00195000","strike":195.0,"currency":"USD","lastPrice":37.55,"change":0.5999985,"percentChange":1.6238117,"volume":7,"openInterest":702,"bid":37.05,"ask":38.4,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756496185,"impliedVolatility":0.94726615234375,"inTheMoney":true},{"contractSymbol":"AAPL250905C00197500","strike":197.5,"currency":"USD","lastPrice":35.18,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":0,"bid":33.0,"ask":36.25,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756495689,"impliedVolatility":1.1494183154296875,"inTheMoney":true},{"contractSymbol":"AAPL250905C00200000","strike":200.0,"currency":"USD","lastPrice":32.43,"change":-0.95000076,"percentChange":-2.8460178,"volume":108,"openInterest":343,"bid":32.15,"ask":32.5,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756492292,"impliedVolatility":0.6689486230468751,"inTheMoney":true},{"contractSymbol":"AAPL250905C00202500","strike":202.5,"currency":"USD","lastPrice":30.02,"change":-0.07999992,"percentChange":-0.26578045,"volume":332,"openInterest":2,"bid":28.7,"ask":30.9,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756494546,"impliedVolatility":0.6064492480468751,"inTheMoney":true},{"contractSymbol":"AAPL250905C00205000","strike":205.0,"currency":"USD","lastPrice":27.75,"change":-0.70000076,"percentChange":-2.4604597,"volume":136,"openInterest":911,"bid":26.4,"ask":28.45,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756496797,"impliedVolatility":0.622074091796875,"inTheMoney":true},{"contractSymbol":"AAPL250905C00207500","strike":207.5,"currency":"USD","lastPrice":25.35,"change":-0.44999886,"percentChange":-1.7441816,"volume":90,"openInterest":4098,"bid":24.7,"ask":26.0,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756495216,"impliedVolatility":0.70117486328125,"inTheMoney":true},{"contractSymbol":"AAPL250905C00210000","strike":210.0,"currency":"USD","lastPrice":22.16,"change":-0.80999947,"percentChange":-3.5263364,"volume":910,"openInterest":1523,"bid":22.1,"ask":23.25,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497571,"impliedVolatility":0.6001016552734376,"inTheMoney":true},{"contractSymbol":"AAPL250905C00212500","strike":212.5,"currency":"USD","lastPrice":19.9,"change":-0.75,"percentChange":-3.6319613,"volume":295,"openInterest":139,"bid":19.25,"ask":20.55,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756493863,"impliedVolatility":0.6279334082031252,"inTheMoney":true},{"contractSymbol":"AAPL250905C00215000","strike":215.0,"currency":"USD","lastPrice":17.05,"change":-1.0,"percentChange":-5.5401664,"volume":510,"openInterest":3790,"bid":17.35,"ask":17.75,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497300,"impliedVolatility":0.5063525927734375,"inTheMoney":true},{"contractSymbol":"AAPL250905C00217500","strike":217.5,"currency":"USD","lastPrice":15.42,"change":-0.8299999,"percentChange":-5.107692,"volume":328,"openInterest":335,"bid":14.85,"ask":15.3,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756496906,"impliedVolatility":0.45898978515625005,"inTheMoney":true},{"contractSymbol":"AAPL250905C00220000","strike":220.0,"currency":"USD","lastPrice":12.56,"change":-0.90999985,"percentChange":-6.755752,"volume":3109,"openInterest":5609,"bid":12.55,"ask":12.85,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497550,"impliedVolatility":0.4089414575195312,"inTheMoney":true},{"contractSymbol":"AAPL250905C00222500","strike":222.5,"currency":"USD","lastPrice":10.25,"change":-0.75,"percentChange":-6.818182,"volume":618,"openInterest":843,"bid":10.25,"ask":10.7,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497484,"impliedVolatility":0.40039662109375,"inTheMoney":true},{"contractSymbol":"AAPL250905C00225000","strike":225.0,"currency":"USD","lastPrice":8.15,"change":-0.75,"percentChange":-8.426967,"volume":1122,"openInterest":10891,"bid":8.1,"ask":8.45,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497582,"impliedVolatility":0.3635317553710938,"inTheMoney":true},{"contractSymbol":"AAPL250905C00227500","strike":227.5,"currency":"USD","lastPrice":6.11,"change":-0.69000006,"percentChange":-10.147059,"volume":4936,"openInterest":3493,"bid":6.1,"ask":6.3,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497588,"impliedVolatility":0.32642275146484373,"inTheMoney":true},{"contractSymbol":"AAPL250905C00230000","strike":230.0,"currency":"USD","lastPrice":4.31,"change":-0.72000027,"percentChange":-14.314119,"volume":12395,"openInterest":15256,"bid":4.3,"ask":4.45,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497598,"impliedVolatility":0.30298548583984375,"inTheMoney":true},{"contractSymbol":"AAPL250905C00232500","strike":232.5,"currency":"USD","lastPrice":2.85,"change":-0.6500001,"percentChange":-18.571432,"volume":19760,"openInterest":7939,"bid":2.86,"ask":2.93,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497598,"impliedVolatility":0.2863840893554687,"inTheMoney":false},{"contractSymbol":"AAPL250905C00235000","strike":235.0,"currency":"USD","lastPrice":1.71,"change":-0.5899999,"percentChange":-25.65217,"volume":35313,"openInterest":16427,"bid":1.7,"ask":1.74,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497598,"impliedVolatility":0.27075924560546877,"inTheMoney":false},{"contractSymbol":"AAPL250905C00237500","strike":237.5,"currency":"USD","lastPrice":0.91,"change":-0.48999995,"percentChange":-34.999996,"volume":7374,"openInterest":7122,"bid":0.92,"ask":0.95,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497597,"impliedVolatility":0.26148199462890626,"inTheMoney":false},{"contractSymbol":"AAPL250905C00240000","strike":240.0,"currency":"USD","lastPrice":0.45,"change":-0.39,"percentChange":-46.42857,"volume":13972,"openInterest":11369,"bid":0.45,"ask":0.47,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497598,"impliedVolatility":0.2553785400390626,"inTheMoney":false},{"contractSymbol":"AAPL250905C00242500","strike":242.5,"currency":"USD","lastPrice":0.2,"change":-0.29000002,"percentChange":-59.183674,"volume":4002,"openInterest":3927,"bid":0.21,"ask":0.22,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497599,"impliedVolatility":0.2534254345703125,"inTheMoney":false},{"contractSymbol":"AAPL250905C00245000","strike":245.0,"currency":"USD","lastPrice":0.1,"change":-0.18,"percentChange":-64.28572,"volume":3416,"openInterest":6356,"bid":0.1,"ask":0.11,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497598,"impliedVolatility":0.25977302734375,"inTheMoney":false},{"contractSymbol":"AAPL250905C00247500","strike":247.5,"currency":"USD","lastPrice":0.04,"change":-0.11000001,"percentChange":-73.333336,"volume":2439,"openInterest":1234,"bid":0.04,"ask":0.05,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497500,"impliedVolatility":0.26270268554687504,"inTheMoney":false},{"contractSymbol":"AAPL250905C00250000","strike":250.0,"currency":"USD","lastPrice":0.03,"change":-0.07,"percentChange":-70.0,"volume":4672,"openInterest":12004,"bid":0.03,"ask":0.04,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497520,"impliedVolatility":0.2890696093749999,"inTheMoney":false},{"contractSymbol":"AAPL250905C00252500","strike":252.5,"currency":"USD","lastPrice":0.02,"change":-0.05,"percentChange":-71.42857,"volume":799,"openInterest":1892,"bid":0.02,"ask":0.03,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756496939,"impliedVolatility":0.3086006640625,"inTheMoney":false},{"contractSymbol":"AAPL250905C00255000","strike":255.0,"currency":"USD","lastPrice":0.02,"change":-0.02,"percentChange":-50.0,"volume":636,"openInterest":2845,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497547,"impliedVolatility":0.3242255078124999,"inTheMoney":false},{"contractSymbol":"AAPL250905C00257500","strike":257.5,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-50.0,"volume":73,"openInterest":1037,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756482468,"impliedVolatility":0.35547519531249994,"inTheMoney":false},{"contractSymbol":"AAPL250905C00260000","strike":260.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":387,"openInterest":2231,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756496675,"impliedVolatility":0.38281867187499996,"inTheMoney":false},{"contractSymbol":"AAPL250905C00262500","strike":262.5,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-50.0,"volume":491,"openInterest":153,"bid":0.0,"ask":0.03,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497372,"impliedVolatility":0.42969320312500003,"inTheMoney":false},{"contractSymbol":"AAPL250905C00265000","strike":265.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":402,"openInterest":1716,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497512,"impliedVolatility":0.4062559375,"inTheMoney":false},{"contractSymbol":"AAPL250905C00270000","strike":270.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":779,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756486837,"impliedVolatility":0.46094289062500005,"inTheMoney":false},{"contractSymbol":"AAPL250905C00275000","strike":275.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":4,"openInterest":365,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756222080,"impliedVolatility":0.507817421875,"inTheMoney":false},{"contractSymbol":"AAPL250905C00280000","strike":280.0,"currency":"USD","lastPrice":0.03,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":314,"bid":0.0,"ask":0.21,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1755611872,"impliedVolatility":0.726565234375,"inTheMoney":false},{"contractSymbol":"AAPL250905C00285000","strike":285.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":68,"bid":0.0,"ask":0.15,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1755703648,"impliedVolatility":0.7500025,"inTheMoney":false},{"contractSymbol":"AAPL250905C00290000","strike":290.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":4,"openInterest":288,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1755793517,"impliedVolatility":0.6093789062500001,"inTheMoney":false},{"contractSymbol":"AAPL250905C00295000","strike":295.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":18,"openInterest":301,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756393029,"impliedVolatility":0.6562534375000001,"inTheMoney":false},{"contractSymbol":"AAPL250905C00300000","strike":300.0,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-50.0,"volume":4,"openInterest":92,"bid":0.0,"ask":0.42,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756495806,"impliedVolatility":1.0449266503906252,"inTheMoney":false},{"contractSymbol":"AAPL250905C00305000","strike":305.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":12,"bid":0.0,"ask":0.42,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1755269058,"impliedVolatility":1.1005904345703126,"inTheMoney":false},{"contractSymbol":"AAPL250905C00315000","strike":315.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":105,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1754938523,"impliedVolatility":0.8125018749999999,"inTheMoney":false},{"contractSymbol":"AAPL250905C00320000","strike":320.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"openInterest":1,"bid":0.0,"ask":0.42,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1754404106,"impliedVolatility":1.259769326171875,"inTheMoney":false},{"contractSymbol":"AAPL250905C00325000","strike":325.0,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-50.0,"volume":2,"openInterest":1,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756489240,"impliedVolatility":0.9062509375,"inTheMoney":false}],"puts":[{"contractSymbol":"AAPL250905P00110000","strike":110.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"openInterest":41,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1755023904,"impliedVolatility":1.9375003125,"inTheMoney":false},{"contractSymbol":"AAPL250905P00125000","strike":125.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":15,"openInterest":79,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1755113978,"impliedVolatility":1.625001875,"inTheMoney":false},{"contractSymbol":"AAPL250905P00130000","strike":130.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"openInterest":2,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1754509107,"impliedVolatility":1.5312523437499999,"inTheMoney":false},{"contractSymbol":"AAPL250905P00135000","strike":135.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":3,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1754664740,"impliedVolatility":1.4375028125,"inTheMoney":false},{"contractSymbol":"AAPL250905P00140000","strike":140.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":118,"bid":0.0,"ask":0.2,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756483219,"impliedVolatility":1.79296978515625,"inTheMoney":false},{"contractSymbol":"AAPL250905P00145000","strike":145.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":14,"bid":0.0,"ask":0.21,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756137843,"impliedVolatility":1.69140779296875,"inTheMoney":false},{"contractSymbol":"AAPL250905P00150000","strike":150.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":97,"bid":0.0,"ask":0.61,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756476515,"impliedVolatility":1.8300789746093749,"inTheMoney":false},{"contractSymbol":"AAPL250905P00155000","strike":155.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":140,"bid":0.0,"ask":0.05,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756151375,"impliedVolatility":1.2500037499999999,"inTheMoney":false},{"contractSymbol":"AAPL250905P00160000","strike":160.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":134,"bid":0.0,"ask":0.21,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756494493,"impliedVolatility":1.3671906640625,"inTheMoney":false},{"contractSymbol":"AAPL250905P00165000","strike":165.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":9,"openInterest":841,"bid":0.0,"ask":0.21,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756302528,"impliedVolatility":1.2656286718749998,"inTheMoney":false},{"contractSymbol":"AAPL250905P00170000","strike":170.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":48,"openInterest":666,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756307395,"impliedVolatility":0.8437515624999999,"inTheMoney":false},{"contractSymbol":"AAPL250905P00175000","strike":175.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":226,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756493841,"impliedVolatility":0.7812521875,"inTheMoney":false},{"contractSymbol":"AAPL250905P00180000","strike":180.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":591,"openInterest":883,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497232,"impliedVolatility":0.70312796875,"inTheMoney":false},{"contractSymbol":"AAPL250905P00185000","strike":185.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":51,"openInterest":1213,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497559,"impliedVolatility":0.70312796875,"inTheMoney":false},{"contractSymbol":"AAPL250905P00190000","strike":190.0,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-50.0,"volume":144,"openInterest":1801,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756496711,"impliedVolatility":0.6250037500000001,"inTheMoney":false},{"contractSymbol":"AAPL250905P00195000","strike":195.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":280,"openInterest":1543,"bid":0.02,"ask":0.03,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497424,"impliedVolatility":0.585941640625,"inTheMoney":false},{"contractSymbol":"AAPL250905P00197500","strike":197.5,"currency":"USD","lastPrice":0.03,"change":0.0,"percentChange":0.0,"volume":117,"openInterest":242,"bid":0.02,"ask":0.03,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756494004,"impliedVolatility":0.5468795312500001,"inTheMoney":false},{"contractSymbol":"AAPL250905P00200000","strike":200.0,"currency":"USD","lastPrice":0.03,"change":-0.020000001,"percentChange":-40.000004,"volume":693,"openInterest":4840,"bid":0.03,"ask":0.04,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497347,"impliedVolatility":0.5273484765625001,"inTheMoney":false},{"contractSymbol":"AAPL250905P00202500","strike":202.5,"currency":"USD","lastPrice":0.04,"change":0.0,"percentChange":0.0,"volume":122,"openInterest":186,"bid":0.04,"ask":0.05,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497455,"impliedVolatility":0.5039112109375,"inTheMoney":false},{"contractSymbol":"AAPL250905P00205000","strike":205.0,"currency":"USD","lastPrice":0.05,"change":0.0,"percentChange":0.0,"volume":1074,"openInterest":3438,"bid":0.05,"ask":0.06,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497555,"impliedVolatility":0.48242705078125003,"inTheMoney":false},{"contractSymbol":"AAPL250905P00207500","strike":207.5,"currency":"USD","lastPrice":0.07,"change":-0.009999998,"percentChange":-12.499998,"volume":419,"openInterest":4962,"bid":0.06,"ask":0.07,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756496939,"impliedVolatility":0.45117736328125,"inTheMoney":false},{"contractSymbol":"AAPL250905P00210000","strike":210.0,"currency":"USD","lastPrice":0.09,"change":-0.019999996,"percentChange":-18.181814,"volume":1160,"openInterest":3365,"bid":0.08,"ask":0.1,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497591,"impliedVolatility":0.432622861328125,"inTheMoney":false},{"contractSymbol":"AAPL250905P00212500","strike":212.5,"currency":"USD","lastPrice":0.11,"change":-0.019999996,"percentChange":-15.384613,"volume":1554,"openInterest":1478,"bid":0.12,"ask":0.13,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497015,"impliedVolatility":0.4062559375,"inTheMoney":false},{"contractSymbol":"AAPL250905P00215000","strike":215.0,"currency":"USD","lastPrice":0.17,"change":-0.060000002,"percentChange":-26.086956,"volume":3297,"openInterest":5834,"bid":0.16,"ask":0.18,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497574,"impliedVolatility":0.383795224609375,"inTheMoney":false},{"contractSymbol":"AAPL250905P00217500","strike":217.5,"currency":"USD","lastPrice":0.25,"change":-0.03999999,"percentChange":-13.7931,"volume":4475,"openInterest":3728,"bid":0.24,"ask":0.26,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497538,"impliedVolatility":0.3632876171875,"inTheMoney":false},{"contractSymbol":"AAPL250905P00220000","strike":220.0,"currency":"USD","lastPrice":0.37,"change":-0.07999998,"percentChange":-17.777775,"volume":4381,"openInterest":7662,"bid":0.35,"ask":0.37,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497591,"impliedVolatility":0.3403386279296875,"inTheMoney":false},{"contractSymbol":"AAPL250905P00222500","strike":222.5,"currency":"USD","lastPrice":0.57,"change":-0.09000003,"percentChange":-13.636369,"volume":4740,"openInterest":13794,"bid":0.54,"ask":0.56,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497566,"impliedVolatility":0.32227240234374993,"inTheMoney":false},{"contractSymbol":"AAPL250905P00225000","strike":225.0,"currency":"USD","lastPrice":0.84,"change":-0.15000004,"percentChange":-15.151519,"volume":11592,"openInterest":26920,"bid":0.83,"ask":0.86,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497596,"impliedVolatility":0.30542686767578126,"inTheMoney":false},{"contractSymbol":"AAPL250905P00227500","strike":227.5,"currency":"USD","lastPrice":1.28,"change":-0.18000007,"percentChange":-12.328772,"volume":6641,"openInterest":2579,"bid":1.27,"ask":1.31,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497599,"impliedVolatility":0.2880930566406249,"inTheMoney":false},{"contractSymbol":"AAPL250905P00230000","strike":230.0,"currency":"USD","lastPrice":1.98,"change":-0.16000009,"percentChange":-7.476639,"volume":34103,"openInterest":3806,"bid":1.96,"ask":1.99,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497598,"impliedVolatility":0.2722240747070312,"inTheMoney":false},{"contractSymbol":"AAPL250905P00232500","strike":232.5,"currency":"USD","lastPrice":2.99,"change":-0.119999886,"percentChange":-3.8585174,"volume":7436,"openInterest":1366,"bid":2.92,"ask":2.99,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497594,"impliedVolatility":0.25879647460937505,"inTheMoney":true},{"contractSymbol":"AAPL250905P00235000","strike":235.0,"currency":"USD","lastPrice":4.41,"change":0.059999943,"percentChange":1.379309,"volume":1755,"openInterest":1460,"bid":4.25,"ask":4.4,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497588,"impliedVolatility":0.25086198364257806,"inTheMoney":true},{"contractSymbol":"AAPL250905P00237500","strike":237.5,"currency":"USD","lastPrice":6.25,"change":0.21000004,"percentChange":3.476822,"volume":1195,"openInterest":807,"bid":5.95,"ask":6.15,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497565,"impliedVolatility":0.24195093994140626,"inTheMoney":true},{"contractSymbol":"AAPL250905P00240000","strike":240.0,"currency":"USD","lastPrice":8.45,"change":0.42999935,"percentChange":5.3615875,"volume":884,"openInterest":5580,"bid":7.8,"ask":8.4,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756497368,"impliedVolatility":0.2668530346679688,"inTheMoney":true},{"contractSymbol":"AAPL250905P00242500","strike":242.5,"currency":"USD","lastPrice":10.47,"change":0.31000042,"percentChange":3.0511851,"volume":31,"openInterest":55,"bid":10.0,"ask":11.25,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756493809,"impliedVolatility":0.37695935546875,"inTheMoney":true},{"contractSymbol":"AAPL250905P00245000","strike":245.0,"currency":"USD","lastPrice":12.72,"change":0.19999981,"percentChange":1.5974425,"volume":37,"openInterest":131,"bid":12.05,"ask":13.1,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756492626,"impliedVolatility":0.3042061767578125,"inTheMoney":true},{"contractSymbol":"AAPL250905P00247500","strike":247.5,"currency":"USD","lastPrice":16.0,"change":1.4799995,"percentChange":10.192834,"volume":200,"openInterest":2,"bid":14.45,"ask":16.1,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756476526,"impliedVolatility":0.46045461425781253,"inTheMoney":true},{"contractSymbol":"AAPL250905P00250000","strike":250.0,"currency":"USD","lastPrice":17.91,"change":1.1599998,"percentChange":6.925372,"volume":17,"openInterest":10,"bid":16.35,"ask":18.6,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756490747,"impliedVolatility":0.5102588037109376,"inTheMoney":true},{"contractSymbol":"AAPL250905P00255000","strike":255.0,"currency":"USD","lastPrice":22.35,"change":0.3199997,"percentChange":1.4525633,"volume":2,"openInterest":2,"bid":21.1,"ask":24.9,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756474884,"impliedVolatility":0.82226740234375,"inTheMoney":true},{"contractSymbol":"AAPL250905P00257500","strike":257.5,"currency":"USD","lastPrice":24.55,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":23.95,"ask":27.4,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756410546,"impliedVolatility":0.5361374511718751,"inTheMoney":true},{"contractSymbol":"AAPL250905P00260000","strike":260.0,"currency":"USD","lastPrice":31.55,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":0,"bid":26.45,"ask":29.75,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1754671265,"impliedVolatility":0.5463912548828126,"inTheMoney":true},{"contractSymbol":"AAPL250905P00275000","strike":275.0,"currency":"USD","lastPrice":46.0,"change":0.0,"percentChange":0.0,"volume":4,"openInterest":0,"bid":41.3,"ask":44.9,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1756137810,"impliedVolatility":0.7578149218750001,"inTheMoney":true},{"contractSymbol":"AAPL250905P00280000","strike":280.0,"currency":"USD","lastPrice":55.16,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":0,"bid":46.05,"ask":49.85,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1755805933,"impliedVolatility":0.7128934960937501,"inTheMoney":true},{"contractSymbol":"AAPL250905P00300000","strike":300.0,"currency":"USD","lastPrice":69.5,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":66.1,"ask":69.9,"contractSize":"REGULAR","expiration":1757030400,"lastTradeDate":1755280993,"impliedVolatility":0.9863282617187501,"inTheMoney":true}]}]}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1757635200.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1757635200.json new file mode 100644 index 0000000..ee251c5 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1757635200.json @@ -0,0 +1 @@ +{"optionChain":{"result":[{"underlyingSymbol":"AAPL","expirationDates":[1757635200,1758240000,1758844800,1759449600,1760054400,1760659200,1761264000,1763683200,1766102400,1768521600,1771545600,1773964800,1776384000,1778803200,1781740800,1787270400,1789689600,1797552000,1799971200,1813190400,1829001600],"strikes":[110.0,130.0,135.0,140.0,145.0,150.0,155.0,160.0,165.0,170.0,175.0,180.0,185.0,190.0,195.0,200.0,205.0,207.5,210.0,212.5,215.0,217.5,220.0,222.5,225.0,227.5,230.0,232.5,235.0,237.5,240.0,242.5,245.0,247.5,250.0,252.5,255.0,257.5,260.0,265.0,270.0,275.0,280.0,285.0,290.0,295.0,300.0,305.0,310.0,315.0,320.0,325.0],"hasMiniOptions":false,"quote":{"language":"en-US","region":"US","quoteType":"EQUITY","typeDisp":"Equity","quoteSourceName":"Nasdaq Real Time Price","triggerable":true,"customPriceAlertConfidence":"HIGH","currency":"USD","regularMarketChangePercent":1.4286369,"regularMarketPrice":230.03,"marketState":"POST","tradeable":false,"cryptoTradeable":false,"hasPrePostMarketData":true,"firstTradeDateMilliseconds":345479400000,"priceHint":2,"postMarketChangePercent":-0.31734806,"postMarketPrice":229.3,"postMarketChange":-0.7299957,"regularMarketChange":3.2400055,"regularMarketDayHigh":230.45,"regularMarketDayRange":"226.65 - 230.45","regularMarketDayLow":226.65,"regularMarketVolume":49962970,"regularMarketPreviousClose":226.79,"bid":229.91,"ask":230.08,"bidSize":2,"askSize":2,"fullExchangeName":"NasdaqGS","financialCurrency":"USD","regularMarketOpen":226.875,"averageDailyVolume3Month":54747006,"averageDailyVolume10Day":52031080,"fiftyTwoWeekLowChange":60.819992,"fiftyTwoWeekLowChangePercent":0.35943496,"fiftyTwoWeekRange":"169.21 - 260.1","fiftyTwoWeekHighChange":-30.070007,"fiftyTwoWeekHighChangePercent":-0.11560941,"fiftyTwoWeekLow":169.21,"fiftyTwoWeekHigh":260.1,"fiftyTwoWeekChangePercent":1.8045545,"dividendDate":1755129600,"earningsTimestamp":1753992000,"earningsTimestampStart":1761854400,"earningsTimestampEnd":1761854400,"earningsCallTimestampStart":1753995600,"earningsCallTimestampEnd":1753995600,"isEarningsDateEstimate":true,"trailingAnnualDividendRate":1.01,"trailingPE":34.85303,"dividendRate":1.04,"trailingAnnualDividendYield":0.0044534593,"dividendYield":0.46,"corporateActions":[],"postMarketTime":1757629687,"regularMarketTime":1757620801,"exchange":"NMS","messageBoardId":"finmb_24937","exchangeTimezoneName":"America/New_York","exchangeTimezoneShortName":"EDT","gmtOffSetMilliseconds":-14400000,"market":"us_market","esgPopulated":false,"epsTrailingTwelveMonths":6.6,"epsForward":8.31,"epsCurrentYear":7.38332,"priceEpsCurrentYear":31.155361,"sharesOutstanding":14840399872,"bookValue":4.431,"fiftyDayAverage":220.249,"fiftyDayAverageChange":9.781006,"fiftyDayAverageChangePercent":0.044408858,"twoHundredDayAverage":221.4848,"twoHundredDayAverageChange":8.545197,"twoHundredDayAverageChangePercent":0.038581412,"marketCap":3413737209856,"forwardPE":27.681105,"priceToBook":51.913788,"sourceInterval":15,"exchangeDataDelayedBy":0,"averageAnalystRating":"2.0 - Buy","shortName":"Apple Inc.","longName":"Apple Inc.","displayName":"Apple","symbol":"AAPL"},"options":[{"expirationDate":1757635200,"hasMiniOptions":false,"calls":[{"contractSymbol":"AAPL250912C00110000","strike":110.0,"currency":"USD","lastPrice":118.95,"change":-10.300003,"percentChange":-7.969054,"volume":2,"openInterest":4,"bid":118.55,"ask":121.75,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757600129,"impliedVolatility":4.10937986328125,"inTheMoney":true},{"contractSymbol":"AAPL250912C00130000","strike":130.0,"currency":"USD","lastPrice":99.2,"change":1.8299942,"percentChange":1.879423,"volume":2,"openInterest":6,"bid":98.2,"ask":101.75,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757610956,"impliedVolatility":5.024417781982422,"inTheMoney":true},{"contractSymbol":"AAPL250912C00140000","strike":140.0,"currency":"USD","lastPrice":88.03,"change":-5.6200027,"percentChange":-6.0010705,"volume":1,"openInterest":1,"bid":88.65,"ask":91.8,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1756403539,"impliedVolatility":3.0468773828125,"inTheMoney":true},{"contractSymbol":"AAPL250912C00150000","strike":150.0,"currency":"USD","lastPrice":78.55,"change":-9.119995,"percentChange":-10.402641,"volume":1,"openInterest":4,"bid":78.65,"ask":81.8,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757349693,"impliedVolatility":2.6640658398437504,"inTheMoney":true},{"contractSymbol":"AAPL250912C00160000","strike":160.0,"currency":"USD","lastPrice":68.55,"change":-9.339996,"percentChange":-11.991265,"volume":2,"openInterest":1,"bid":68.3,"ask":71.8,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757600855,"impliedVolatility":1.765626171875,"inTheMoney":true},{"contractSymbol":"AAPL250912C00175000","strike":175.0,"currency":"USD","lastPrice":54.9,"change":-5.199997,"percentChange":-8.652242,"volume":2,"openInterest":9,"bid":54.25,"ask":55.95,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757612738,"impliedVolatility":1.5625021874999998,"inTheMoney":true},{"contractSymbol":"AAPL250912C00180000","strike":180.0,"currency":"USD","lastPrice":48.52,"change":1.670002,"percentChange":3.564572,"volume":9,"openInterest":156,"bid":49.7,"ask":50.95,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757604280,"impliedVolatility":1.748048134765625,"inTheMoney":true},{"contractSymbol":"AAPL250912C00185000","strike":185.0,"currency":"USD","lastPrice":44.93,"change":-5.989998,"percentChange":-11.763546,"volume":12,"openInterest":48,"bid":44.3,"ask":45.95,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757613563,"impliedVolatility":1.3281283593749997,"inTheMoney":true},{"contractSymbol":"AAPL250912C00190000","strike":190.0,"currency":"USD","lastPrice":39.07,"change":2.0699997,"percentChange":5.594594,"volume":2,"openInterest":155,"bid":39.35,"ask":41.0,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757603636,"impliedVolatility":1.259769326171875,"inTheMoney":true},{"contractSymbol":"AAPL250912C00195000","strike":195.0,"currency":"USD","lastPrice":34.92,"change":2.7999992,"percentChange":8.717308,"volume":2,"openInterest":297,"bid":34.6,"ask":35.5,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757614011,"impliedVolatility":0.85937640625,"inTheMoney":true},{"contractSymbol":"AAPL250912C00200000","strike":200.0,"currency":"USD","lastPrice":30.0,"change":3.6499996,"percentChange":13.851991,"volume":92,"openInterest":452,"bid":29.85,"ask":30.45,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757619014,"impliedVolatility":0.9355475195312499,"inTheMoney":true},{"contractSymbol":"AAPL250912C00205000","strike":205.0,"currency":"USD","lastPrice":25.25,"change":3.1499996,"percentChange":14.253392,"volume":21,"openInterest":636,"bid":24.5,"ask":25.4,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757617085,"impliedVolatility":0.9765627343750001,"inTheMoney":true},{"contractSymbol":"AAPL250912C00207500","strike":207.5,"currency":"USD","lastPrice":19.81,"change":0.17000008,"percentChange":0.86558086,"volume":1,"openInterest":60,"bid":22.3,"ask":23.2,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757597590,"impliedVolatility":0.804689453125,"inTheMoney":true},{"contractSymbol":"AAPL250912C00210000","strike":210.0,"currency":"USD","lastPrice":20.51,"change":3.4899998,"percentChange":20.505285,"volume":195,"openInterest":1829,"bid":19.85,"ask":20.45,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620194,"impliedVolatility":0.65039412109375,"inTheMoney":true},{"contractSymbol":"AAPL250912C00212500","strike":212.5,"currency":"USD","lastPrice":17.41,"change":2.5900002,"percentChange":17.476383,"volume":92,"openInterest":216,"bid":17.1,"ask":18.1,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757618559,"impliedVolatility":0.5312546875,"inTheMoney":true},{"contractSymbol":"AAPL250912C00215000","strike":215.0,"currency":"USD","lastPrice":15.15,"change":3.4499998,"percentChange":29.487177,"volume":611,"openInterest":3790,"bid":14.8,"ask":15.2,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620649,"impliedVolatility":0.5419967675781252,"inTheMoney":true},{"contractSymbol":"AAPL250912C00217500","strike":217.5,"currency":"USD","lastPrice":12.9,"change":4.1499996,"percentChange":47.42857,"volume":138,"openInterest":459,"bid":12.45,"ask":12.75,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620607,"impliedVolatility":0.49219257812500006,"inTheMoney":true},{"contractSymbol":"AAPL250912C00220000","strike":220.0,"currency":"USD","lastPrice":10.01,"change":2.8600001,"percentChange":40.0,"volume":813,"openInterest":2423,"bid":9.95,"ask":10.4,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620749,"impliedVolatility":0.46973186523437505,"inTheMoney":true},{"contractSymbol":"AAPL250912C00222500","strike":222.5,"currency":"USD","lastPrice":8.15,"change":3.2499995,"percentChange":66.326515,"volume":1022,"openInterest":1885,"bid":7.25,"ask":7.75,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620573,"impliedVolatility":0.33008482421874996,"inTheMoney":true},{"contractSymbol":"AAPL250912C00225000","strike":225.0,"currency":"USD","lastPrice":5.26,"change":2.2600002,"percentChange":75.33334,"volume":9987,"openInterest":9120,"bid":5.1,"ask":5.35,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620793,"impliedVolatility":0.273444765625,"inTheMoney":true},{"contractSymbol":"AAPL250912C00227500","strike":227.5,"currency":"USD","lastPrice":2.87,"change":1.2799999,"percentChange":80.503136,"volume":40041,"openInterest":13125,"bid":2.7,"ask":2.98,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620796,"impliedVolatility":0.20069158691406247,"inTheMoney":true},{"contractSymbol":"AAPL250912C00230000","strike":230.0,"currency":"USD","lastPrice":1.1,"change":0.38,"percentChange":52.777775,"volume":133131,"openInterest":37186,"bid":1.08,"ask":1.12,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620798,"impliedVolatility":0.1626060302734375,"inTheMoney":true},{"contractSymbol":"AAPL250912C00232500","strike":232.5,"currency":"USD","lastPrice":0.29,"change":-0.030000001,"percentChange":-9.375,"volume":64930,"openInterest":26372,"bid":0.29,"ask":0.3,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620798,"impliedVolatility":0.16553568847656253,"inTheMoney":false},{"contractSymbol":"AAPL250912C00235000","strike":235.0,"currency":"USD","lastPrice":0.09,"change":-0.06999999,"percentChange":-43.749996,"volume":60122,"openInterest":33912,"bid":0.08,"ask":0.09,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620797,"impliedVolatility":0.18946123046875002,"inTheMoney":false},{"contractSymbol":"AAPL250912C00237500","strike":237.5,"currency":"USD","lastPrice":0.04,"change":-0.07,"percentChange":-63.636364,"volume":14734,"openInterest":22619,"bid":0.03,"ask":0.04,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620793,"impliedVolatility":0.22461712890624996,"inTheMoney":false},{"contractSymbol":"AAPL250912C00240000","strike":240.0,"currency":"USD","lastPrice":0.02,"change":-0.06,"percentChange":-75.0,"volume":22768,"openInterest":54462,"bid":0.02,"ask":0.03,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620794,"impliedVolatility":0.27149166015625,"inTheMoney":false},{"contractSymbol":"AAPL250912C00242500","strike":242.5,"currency":"USD","lastPrice":0.01,"change":-0.04,"percentChange":-80.00001,"volume":9671,"openInterest":26218,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620788,"impliedVolatility":0.3086006640625,"inTheMoney":false},{"contractSymbol":"AAPL250912C00245000","strike":245.0,"currency":"USD","lastPrice":0.01,"change":-0.03,"percentChange":-75.0,"volume":9828,"openInterest":39886,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620732,"impliedVolatility":0.3632876171875,"inTheMoney":false},{"contractSymbol":"AAPL250912C00247500","strike":247.5,"currency":"USD","lastPrice":0.01,"change":-0.02,"percentChange":-66.66667,"volume":3162,"openInterest":19134,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620714,"impliedVolatility":0.38281867187499996,"inTheMoney":false},{"contractSymbol":"AAPL250912C00250000","strike":250.0,"currency":"USD","lastPrice":0.01,"change":-0.02,"percentChange":-66.66667,"volume":4182,"openInterest":30930,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620687,"impliedVolatility":0.42969320312500003,"inTheMoney":false},{"contractSymbol":"AAPL250912C00252500","strike":252.5,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-50.0,"volume":965,"openInterest":9866,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620162,"impliedVolatility":0.4687553125,"inTheMoney":false},{"contractSymbol":"AAPL250912C00255000","strike":255.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":176,"openInterest":19992,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620637,"impliedVolatility":0.51562984375,"inTheMoney":false},{"contractSymbol":"AAPL250912C00257500","strike":257.5,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":87,"openInterest":6993,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757598712,"impliedVolatility":0.5312546875,"inTheMoney":false},{"contractSymbol":"AAPL250912C00260000","strike":260.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":137,"openInterest":12857,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620114,"impliedVolatility":0.5625043750000001,"inTheMoney":false},{"contractSymbol":"AAPL250912C00265000","strike":265.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":128,"openInterest":8114,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757618667,"impliedVolatility":0.64062859375,"inTheMoney":false},{"contractSymbol":"AAPL250912C00270000","strike":270.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":19,"openInterest":5400,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757614149,"impliedVolatility":0.7187528125,"inTheMoney":false},{"contractSymbol":"AAPL250912C00275000","strike":275.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":2861,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757603147,"impliedVolatility":0.7812521875,"inTheMoney":false},{"contractSymbol":"AAPL250912C00280000","strike":280.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":11,"openInterest":3694,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757518313,"impliedVolatility":0.87500125,"inTheMoney":false},{"contractSymbol":"AAPL250912C00285000","strike":285.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":2439,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757512637,"impliedVolatility":0.937500625,"inTheMoney":false},{"contractSymbol":"AAPL250912C00290000","strike":290.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":327,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757527898,"impliedVolatility":1.000005,"inTheMoney":false},{"contractSymbol":"AAPL250912C00295000","strike":295.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":127,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1756393536,"impliedVolatility":1.0625046875000002,"inTheMoney":false},{"contractSymbol":"AAPL250912C00300000","strike":300.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":60,"openInterest":1353,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757597403,"impliedVolatility":1.125004375,"inTheMoney":false},{"contractSymbol":"AAPL250912C00305000","strike":305.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":23,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1756225640,"impliedVolatility":1.1875040625,"inTheMoney":false},{"contractSymbol":"AAPL250912C00310000","strike":310.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":7,"openInterest":26,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757338200,"impliedVolatility":1.2500037499999999,"inTheMoney":false},{"contractSymbol":"AAPL250912C00315000","strike":315.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":21,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1755526277,"impliedVolatility":1.3125034374999998,"inTheMoney":false},{"contractSymbol":"AAPL250912C00320000","strike":320.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":107,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757338207,"impliedVolatility":1.3750031249999999,"inTheMoney":false},{"contractSymbol":"AAPL250912C00325000","strike":325.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":793,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757602495,"impliedVolatility":1.4375028125,"inTheMoney":false}],"puts":[{"contractSymbol":"AAPL250912P00130000","strike":130.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":10,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757079423,"impliedVolatility":2.3750040624999995,"inTheMoney":false},{"contractSymbol":"AAPL250912P00135000","strike":135.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":24,"openInterest":379,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757424616,"impliedVolatility":2.2500043749999996,"inTheMoney":false},{"contractSymbol":"AAPL250912P00140000","strike":140.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":70,"openInterest":92,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1756922670,"impliedVolatility":2.0625048437499998,"inTheMoney":false},{"contractSymbol":"AAPL250912P00145000","strike":145.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":10,"openInterest":87,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1756231825,"impliedVolatility":1.9375003125,"inTheMoney":false},{"contractSymbol":"AAPL250912P00150000","strike":150.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":78,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757511335,"impliedVolatility":1.8125009374999999,"inTheMoney":false},{"contractSymbol":"AAPL250912P00155000","strike":155.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":50,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757513010,"impliedVolatility":1.6875015625,"inTheMoney":false},{"contractSymbol":"AAPL250912P00160000","strike":160.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":236,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757604714,"impliedVolatility":1.5625021874999998,"inTheMoney":false},{"contractSymbol":"AAPL250912P00165000","strike":165.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":126,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757094452,"impliedVolatility":1.4375028125,"inTheMoney":false},{"contractSymbol":"AAPL250912P00170000","strike":170.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":633,"bid":0.0,"ask":0.08,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757102069,"impliedVolatility":1.6015644921874999,"inTheMoney":false},{"contractSymbol":"AAPL250912P00175000","strike":175.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":62,"openInterest":151,"bid":0.0,"ask":0.09,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757445162,"impliedVolatility":1.484377578125,"inTheMoney":false},{"contractSymbol":"AAPL250912P00180000","strike":180.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":7,"openInterest":410,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757614175,"impliedVolatility":1.09375453125,"inTheMoney":false},{"contractSymbol":"AAPL250912P00185000","strike":185.0,"currency":"USD","lastPrice":0.02,"change":0.01,"percentChange":100.0,"volume":5,"openInterest":746,"bid":0.0,"ask":0.02,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757597809,"impliedVolatility":1.0312548437500002,"inTheMoney":false},{"contractSymbol":"AAPL250912P00190000","strike":190.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":622,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757516134,"impliedVolatility":0.8437515624999999,"inTheMoney":false},{"contractSymbol":"AAPL250912P00195000","strike":195.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":1784,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757598890,"impliedVolatility":0.7500025,"inTheMoney":false},{"contractSymbol":"AAPL250912P00200000","strike":200.0,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-50.0,"volume":88,"openInterest":4760,"bid":0.0,"ask":0.02,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620268,"impliedVolatility":0.6875031250000001,"inTheMoney":false},{"contractSymbol":"AAPL250912P00205000","strike":205.0,"currency":"USD","lastPrice":0.01,"change":-0.02,"percentChange":-66.66667,"volume":788,"openInterest":1955,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757618225,"impliedVolatility":0.6015664843750002,"inTheMoney":false},{"contractSymbol":"AAPL250912P00207500","strike":207.5,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-50.0,"volume":126,"openInterest":1547,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620687,"impliedVolatility":0.5468795312500001,"inTheMoney":false},{"contractSymbol":"AAPL250912P00210000","strike":210.0,"currency":"USD","lastPrice":0.01,"change":-0.02,"percentChange":-66.66667,"volume":471,"openInterest":3636,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620597,"impliedVolatility":0.507817421875,"inTheMoney":false},{"contractSymbol":"AAPL250912P00212500","strike":212.5,"currency":"USD","lastPrice":0.01,"change":-0.02,"percentChange":-66.66667,"volume":221,"openInterest":1441,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757618211,"impliedVolatility":0.445318046875,"inTheMoney":false},{"contractSymbol":"AAPL250912P00215000","strike":215.0,"currency":"USD","lastPrice":0.03,"change":-0.03,"percentChange":-50.0,"volume":1230,"openInterest":4466,"bid":0.02,"ask":0.03,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620437,"impliedVolatility":0.4101621484375,"inTheMoney":false},{"contractSymbol":"AAPL250912P00217500","strike":217.5,"currency":"USD","lastPrice":0.03,"change":-0.099999994,"percentChange":-76.92307,"volume":3280,"openInterest":5551,"bid":0.02,"ask":0.03,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620669,"impliedVolatility":0.34766277343749996,"inTheMoney":false},{"contractSymbol":"AAPL250912P00220000","strike":220.0,"currency":"USD","lastPrice":0.04,"change":-0.21000001,"percentChange":-84.0,"volume":7323,"openInterest":19542,"bid":0.03,"ask":0.04,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620736,"impliedVolatility":0.29883513671874995,"inTheMoney":false},{"contractSymbol":"AAPL250912P00222500","strike":222.5,"currency":"USD","lastPrice":0.06,"change":-0.5,"percentChange":-89.28571,"volume":9754,"openInterest":16223,"bid":0.05,"ask":0.06,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620743,"impliedVolatility":0.2500075,"inTheMoney":false},{"contractSymbol":"AAPL250912P00225000","strike":225.0,"currency":"USD","lastPrice":0.11,"change":-1.02,"percentChange":-90.26549,"volume":23338,"openInterest":10907,"bid":0.1,"ask":0.12,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620798,"impliedVolatility":0.20899228515624996,"inTheMoney":false},{"contractSymbol":"AAPL250912P00227500","strike":227.5,"currency":"USD","lastPrice":0.32,"change":-1.8700001,"percentChange":-85.38813,"volume":33759,"openInterest":12138,"bid":0.28,"ask":0.3,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620798,"impliedVolatility":0.16993017578125003,"inTheMoney":false},{"contractSymbol":"AAPL250912P00230000","strike":230.0,"currency":"USD","lastPrice":0.99,"change":-2.85,"percentChange":-74.21875,"volume":26901,"openInterest":14424,"bid":0.97,"ask":1.0,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620798,"impliedVolatility":0.149422568359375,"inTheMoney":false},{"contractSymbol":"AAPL250912P00232500","strike":232.5,"currency":"USD","lastPrice":2.73,"change":-3.12,"percentChange":-53.333336,"volume":1958,"openInterest":11401,"bid":2.43,"ask":3.05,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620798,"impliedVolatility":0.22022264160156246,"inTheMoney":true},{"contractSymbol":"AAPL250912P00235000","strike":235.0,"currency":"USD","lastPrice":4.65,"change":-3.65,"percentChange":-43.975903,"volume":2548,"openInterest":13681,"bid":4.9,"ask":5.25,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620772,"impliedVolatility":0.25440198730468755,"inTheMoney":true},{"contractSymbol":"AAPL250912P00237500","strike":237.5,"currency":"USD","lastPrice":7.29,"change":-3.3900003,"percentChange":-31.741575,"volume":1106,"openInterest":8878,"bid":7.25,"ask":8.05,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620591,"impliedVolatility":0.42139250488281255,"inTheMoney":true},{"contractSymbol":"AAPL250912P00240000","strike":240.0,"currency":"USD","lastPrice":9.64,"change":-3.6099997,"percentChange":-27.245281,"volume":846,"openInterest":4539,"bid":9.7,"ask":10.5,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757620102,"impliedVolatility":0.49609878906250005,"inTheMoney":true},{"contractSymbol":"AAPL250912P00242500","strike":242.5,"currency":"USD","lastPrice":12.84,"change":-3.0100002,"percentChange":-18.990538,"volume":142,"openInterest":193,"bid":12.15,"ask":13.1,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757616731,"impliedVolatility":0.6079140771484376,"inTheMoney":true},{"contractSymbol":"AAPL250912P00245000","strike":245.0,"currency":"USD","lastPrice":14.9,"change":-3.25,"percentChange":-17.906336,"volume":16059,"openInterest":1075,"bid":14.8,"ask":15.25,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757619697,"impliedVolatility":0.5625043750000001,"inTheMoney":true},{"contractSymbol":"AAPL250912P00247500","strike":247.5,"currency":"USD","lastPrice":17.56,"change":-3.460001,"percentChange":-16.460518,"volume":4722,"openInterest":234,"bid":17.25,"ask":17.75,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757615519,"impliedVolatility":0.63086306640625,"inTheMoney":true},{"contractSymbol":"AAPL250912P00250000","strike":250.0,"currency":"USD","lastPrice":15.32,"change":0.0,"percentChange":0.0,"volume":283,"openInterest":0,"bid":19.5,"ask":21.7,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757447889,"impliedVolatility":0.84179845703125,"inTheMoney":true},{"contractSymbol":"AAPL250912P00252500","strike":252.5,"currency":"USD","lastPrice":22.62,"change":6.6400013,"percentChange":41.55195,"volume":57,"openInterest":6,"bid":21.95,"ask":24.15,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757617298,"impliedVolatility":0.8964854101562498,"inTheMoney":true},{"contractSymbol":"AAPL250912P00255000","strike":255.0,"currency":"USD","lastPrice":29.1,"change":0.0,"percentChange":0.0,"volume":64,"openInterest":0,"bid":24.45,"ask":25.25,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757532721,"impliedVolatility":0.8261736132812498,"inTheMoney":true},{"contractSymbol":"AAPL250912P00257500","strike":257.5,"currency":"USD","lastPrice":18.65,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":1,"bid":26.9,"ask":29.1,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757343102,"impliedVolatility":1.013676806640625,"inTheMoney":true},{"contractSymbol":"AAPL250912P00260000","strike":260.0,"currency":"USD","lastPrice":30.1,"change":4.3999996,"percentChange":17.12062,"volume":7,"openInterest":1,"bid":29.65,"ask":30.6,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757617486,"impliedVolatility":0.8535170898437501,"inTheMoney":true},{"contractSymbol":"AAPL250912P00265000","strike":265.0,"currency":"USD","lastPrice":30.41,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":0,"bid":34.5,"ask":35.25,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1757447670,"impliedVolatility":1.0644578027343752,"inTheMoney":true},{"contractSymbol":"AAPL250912P00280000","strike":280.0,"currency":"USD","lastPrice":53.15,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":49.4,"ask":51.75,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1755719400,"impliedVolatility":1.5996113769531248,"inTheMoney":true},{"contractSymbol":"AAPL250912P00285000","strike":285.0,"currency":"USD","lastPrice":48.95,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":54.45,"ask":56.75,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1756914463,"impliedVolatility":1.7255873095703125,"inTheMoney":true},{"contractSymbol":"AAPL250912P00305000","strike":305.0,"currency":"USD","lastPrice":76.0,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":74.55,"ask":76.7,"contractSize":"REGULAR","expiration":1757635200,"lastTradeDate":1756834866,"impliedVolatility":2.1572311694335937,"inTheMoney":true}]}]}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1758240000.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1758240000.json new file mode 100644 index 0000000..9ea6b80 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1758240000.json @@ -0,0 +1 @@ +{"optionChain":{"result":[{"underlyingSymbol":"AAPL","expirationDates":[1758240000,1758844800,1759449600,1760054400,1760659200,1761264000,1761868800,1763683200,1766102400,1768521600,1771545600,1773964800,1776384000,1778803200,1781740800,1787270400,1789689600,1797552000,1799971200,1813190400,1829001600],"strikes":[5.0,10.0,15.0,20.0,25.0,30.0,35.0,40.0,45.0,50.0,55.0,60.0,65.0,70.0,75.0,80.0,85.0,90.0,95.0,100.0,105.0,110.0,115.0,120.0,125.0,130.0,135.0,140.0,145.0,150.0,155.0,160.0,165.0,170.0,175.0,180.0,185.0,190.0,195.0,200.0,202.5,205.0,207.5,210.0,212.5,215.0,217.5,220.0,222.5,225.0,227.5,230.0,232.5,235.0,237.5,240.0,242.5,245.0,247.5,250.0,252.5,255.0,257.5,260.0,262.5,265.0,267.5,270.0,275.0,280.0,285.0,290.0,295.0,300.0,305.0,310.0,320.0,330.0,340.0,350.0,360.0,370.0,380.0,390.0],"hasMiniOptions":false,"quote":{"language":"en-US","region":"US","quoteType":"EQUITY","typeDisp":"Equity","quoteSourceName":"Nasdaq Real Time Price","triggerable":true,"customPriceAlertConfidence":"HIGH","currency":"USD","shortName":"Apple Inc.","marketState":"POST","longName":"Apple Inc.","postMarketChangePercent":0.18829112,"postMarketPrice":239.44,"postMarketChange":0.44999695,"regularMarketChange":0.8400116,"regularMarketDayHigh":240.1,"regularMarketDayRange":"237.7301 - 240.1","regularMarketDayLow":237.7301,"regularMarketVolume":46408701,"regularMarketPreviousClose":238.15,"bid":236.0,"ask":250.95,"bidSize":1,"askSize":1,"fullExchangeName":"NasdaqGS","financialCurrency":"USD","regularMarketOpen":238.97,"averageDailyVolume3Month":55311645,"averageDailyVolume10Day":57971900,"fiftyTwoWeekLowChange":69.78,"fiftyTwoWeekLowChangePercent":0.41238695,"fiftyTwoWeekRange":"169.21 - 260.1","fiftyTwoWeekHighChange":-21.11,"fiftyTwoWeekHighChangePercent":-0.08116109,"fiftyTwoWeekLow":169.21,"fiftyTwoWeekHigh":260.1,"fiftyTwoWeekChangePercent":7.911551,"dividendDate":1755129600,"earningsTimestamp":1753992000,"earningsTimestampStart":1761854400,"earningsTimestampEnd":1761854400,"earningsCallTimestampStart":1753995600,"earningsCallTimestampEnd":1753995600,"isEarningsDateEstimate":true,"trailingAnnualDividendRate":1.01,"trailingPE":36.210606,"dividendRate":1.04,"trailingAnnualDividendYield":0.0042410246,"dividendYield":0.44,"epsTrailingTwelveMonths":6.6,"epsForward":8.31,"epsCurrentYear":7.38338,"corporateActions":[],"postMarketTime":1758147673,"regularMarketTime":1758139201,"exchange":"NMS","messageBoardId":"finmb_24937","exchangeTimezoneName":"America/New_York","exchangeTimezoneShortName":"EDT","gmtOffSetMilliseconds":-14400000,"market":"us_market","esgPopulated":false,"regularMarketChangePercent":0.35272375,"regularMarketPrice":238.99,"priceEpsCurrentYear":32.368645,"sharesOutstanding":14840390000,"bookValue":4.431,"fiftyDayAverage":222.1528,"fiftyDayAverageChange":16.837204,"fiftyDayAverageChangePercent":0.0757911,"twoHundredDayAverage":221.57825,"twoHundredDayAverageChange":17.411758,"twoHundredDayAverageChangePercent":0.07858063,"marketCap":3546704773120,"forwardPE":28.759325,"priceToBook":53.935905,"sourceInterval":15,"exchangeDataDelayedBy":0,"averageAnalystRating":"2.0 - Buy","tradeable":false,"cryptoTradeable":false,"hasPrePostMarketData":true,"firstTradeDateMilliseconds":345479400000,"priceHint":2,"displayName":"Apple","symbol":"AAPL"},"options":[{"expirationDate":1758240000,"hasMiniOptions":false,"calls":[{"contractSymbol":"AAPL250919C00005000","strike":5.0,"currency":"USD","lastPrice":234.1,"change":3.100006,"percentChange":1.3419939,"volume":37,"openInterest":578,"bid":232.95,"ask":234.7,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758131327,"impliedVolatility":23.8750025390625,"inTheMoney":true},{"contractSymbol":"AAPL250919C00010000","strike":10.0,"currency":"USD","lastPrice":229.55,"change":2.850006,"percentChange":1.2571708,"volume":6,"openInterest":27,"bid":228.0,"ask":229.65,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758119219,"impliedVolatility":18.28125428710938,"inTheMoney":true},{"contractSymbol":"AAPL250919C00015000","strike":15.0,"currency":"USD","lastPrice":213.35,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":2,"bid":222.95,"ask":224.6,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1755892200,"impliedVolatility":15.382812885742187,"inTheMoney":true},{"contractSymbol":"AAPL250919C00020000","strike":20.0,"currency":"USD","lastPrice":188.9,"change":0.0,"percentChange":0.0,"volume":10,"openInterest":0,"bid":217.35,"ask":218.6,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1753969238,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250919C00025000","strike":25.0,"currency":"USD","lastPrice":214.42,"change":9.309998,"percentChange":4.5390267,"volume":1,"openInterest":1,"bid":212.95,"ask":214.7,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1755696602,"impliedVolatility":12.621095861816407,"inTheMoney":true},{"contractSymbol":"AAPL250919C00030000","strike":30.0,"currency":"USD","lastPrice":200.5,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":2,"bid":207.9,"ask":209.7,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1756305475,"impliedVolatility":11.550784030761719,"inTheMoney":true},{"contractSymbol":"AAPL250919C00035000","strike":35.0,"currency":"USD","lastPrice":204.4,"change":10.399994,"percentChange":5.3608217,"volume":2,"openInterest":1,"bid":202.95,"ask":204.75,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758117917,"impliedVolatility":10.785159509277346,"inTheMoney":true},{"contractSymbol":"AAPL250919C00040000","strike":40.0,"currency":"USD","lastPrice":199.45,"change":12.5,"percentChange":6.68628,"volume":3,"openInterest":3,"bid":197.85,"ask":199.7,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758117917,"impliedVolatility":9.914066303710937,"inTheMoney":true},{"contractSymbol":"AAPL250919C00045000","strike":45.0,"currency":"USD","lastPrice":194.65,"change":0.48999023,"percentChange":0.25236416,"volume":2,"openInterest":6,"bid":193.0,"ask":194.75,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758117043,"impliedVolatility":9.36328539794922,"inTheMoney":true},{"contractSymbol":"AAPL250919C00050000","strike":50.0,"currency":"USD","lastPrice":188.19,"change":-1.0899963,"percentChange":-0.5758645,"volume":21,"openInterest":1125,"bid":187.9,"ask":189.55,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758117276,"impliedVolatility":8.363286022949218,"inTheMoney":true},{"contractSymbol":"AAPL250919C00055000","strike":55.0,"currency":"USD","lastPrice":183.2,"change":-1.0800018,"percentChange":-0.5860657,"volume":24,"openInterest":106,"bid":182.95,"ask":184.6,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758118120,"impliedVolatility":7.9687500390625,"inTheMoney":true},{"contractSymbol":"AAPL250919C00060000","strike":60.0,"currency":"USD","lastPrice":179.52,"change":0.30000305,"percentChange":0.16739373,"volume":4,"openInterest":116,"bid":177.9,"ask":179.55,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758118120,"impliedVolatility":7.410156987304687,"inTheMoney":true},{"contractSymbol":"AAPL250919C00065000","strike":65.0,"currency":"USD","lastPrice":174.53,"change":3.4199982,"percentChange":1.9987133,"volume":1,"openInterest":27,"bid":172.95,"ask":174.75,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758118643,"impliedVolatility":7.3437508203125,"inTheMoney":true},{"contractSymbol":"AAPL250919C00070000","strike":70.0,"currency":"USD","lastPrice":169.57,"change":0.3500061,"percentChange":0.20683494,"volume":3,"openInterest":16,"bid":167.9,"ask":169.6,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758118889,"impliedVolatility":6.705079743652345,"inTheMoney":true},{"contractSymbol":"AAPL250919C00075000","strike":75.0,"currency":"USD","lastPrice":164.67,"change":0.44999695,"percentChange":0.2740208,"volume":5,"openInterest":161,"bid":162.95,"ask":164.6,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758119340,"impliedVolatility":6.347658315429687,"inTheMoney":true},{"contractSymbol":"AAPL250919C00080000","strike":80.0,"currency":"USD","lastPrice":159.72,"change":3.2200012,"percentChange":2.0575087,"volume":4,"openInterest":27,"bid":157.9,"ask":159.6,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758119340,"impliedVolatility":6.0156274804687495,"inTheMoney":true},{"contractSymbol":"AAPL250919C00085000","strike":85.0,"currency":"USD","lastPrice":153.39,"change":1.6399994,"percentChange":1.0807245,"volume":2,"openInterest":164,"bid":152.8,"ask":154.75,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758116814,"impliedVolatility":5.910158862304687,"inTheMoney":true},{"contractSymbol":"AAPL250919C00090000","strike":90.0,"currency":"USD","lastPrice":146.8,"change":0.0,"percentChange":0.0,"volume":4,"openInterest":175,"bid":147.85,"ask":149.7,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757949675,"impliedVolatility":5.542971821289061,"inTheMoney":true},{"contractSymbol":"AAPL250919C00095000","strike":95.0,"currency":"USD","lastPrice":144.81,"change":3.0299988,"percentChange":2.137113,"volume":2,"openInterest":40,"bid":142.95,"ask":144.75,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758119339,"impliedVolatility":5.320315849609374,"inTheMoney":true},{"contractSymbol":"AAPL250919C00100000","strike":100.0,"currency":"USD","lastPrice":139.54,"change":0.41999817,"percentChange":0.30189633,"volume":15,"openInterest":1021,"bid":138.05,"ask":139.7,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758129716,"impliedVolatility":4.992191259765624,"inTheMoney":true},{"contractSymbol":"AAPL250919C00105000","strike":105.0,"currency":"USD","lastPrice":134.82,"change":0.5200043,"percentChange":0.38719603,"volume":3,"openInterest":31,"bid":132.95,"ask":134.7,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758119238,"impliedVolatility":4.736332204589844,"inTheMoney":true},{"contractSymbol":"AAPL250919C00110000","strike":110.0,"currency":"USD","lastPrice":129.85,"change":0.5,"percentChange":0.3865481,"volume":5,"openInterest":458,"bid":128.05,"ask":129.7,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758119310,"impliedVolatility":4.494145007324218,"inTheMoney":true},{"contractSymbol":"AAPL250919C00115000","strike":115.0,"currency":"USD","lastPrice":124.9,"change":0.6800003,"percentChange":0.5474161,"volume":4,"openInterest":111,"bid":123.0,"ask":124.75,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758119310,"impliedVolatility":4.310551486816405,"inTheMoney":true},{"contractSymbol":"AAPL250919C00120000","strike":120.0,"currency":"USD","lastPrice":119.62,"change":0.45000458,"percentChange":0.37761566,"volume":1,"openInterest":956,"bid":117.9,"ask":119.7,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758041218,"impliedVolatility":4.039067451171874,"inTheMoney":true},{"contractSymbol":"AAPL250919C00125000","strike":125.0,"currency":"USD","lastPrice":114.7,"change":0.47999573,"percentChange":0.4202379,"volume":20,"openInterest":252,"bid":113.0,"ask":114.75,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758124397,"impliedVolatility":3.871094072265625,"inTheMoney":true},{"contractSymbol":"AAPL250919C00130000","strike":130.0,"currency":"USD","lastPrice":109.42,"change":0.1800003,"percentChange":0.16477509,"volume":1,"openInterest":82,"bid":107.9,"ask":109.75,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758041153,"impliedVolatility":3.6640633398437505,"inTheMoney":true},{"contractSymbol":"AAPL250919C00135000","strike":135.0,"currency":"USD","lastPrice":103.7,"change":-0.48000336,"percentChange":-0.46074423,"volume":3,"openInterest":92,"bid":103.0,"ask":104.8,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758116274,"impliedVolatility":3.502930930175781,"inTheMoney":true},{"contractSymbol":"AAPL250919C00140000","strike":140.0,"currency":"USD","lastPrice":98.93,"change":-0.13999939,"percentChange":-0.14131361,"volume":308,"openInterest":1019,"bid":98.35,"ask":99.5,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758125564,"impliedVolatility":3.052736743164062,"inTheMoney":true},{"contractSymbol":"AAPL250919C00145000","strike":145.0,"currency":"USD","lastPrice":93.83,"change":-0.18999481,"percentChange":-0.20207915,"volume":15,"openInterest":364,"bid":93.05,"ask":94.65,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758137646,"impliedVolatility":3.0058618603515628,"inTheMoney":true},{"contractSymbol":"AAPL250919C00150000","strike":150.0,"currency":"USD","lastPrice":89.08,"change":-0.36999512,"percentChange":-0.41363347,"volume":36,"openInterest":6350,"bid":88.65,"ask":89.2,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138294,"impliedVolatility":2.3593791015624994,"inTheMoney":true},{"contractSymbol":"AAPL250919C00155000","strike":155.0,"currency":"USD","lastPrice":84.85,"change":0.40000153,"percentChange":0.47365487,"volume":18,"openInterest":345,"bid":83.0,"ask":84.65,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758117244,"impliedVolatility":2.6552767993164066,"inTheMoney":true},{"contractSymbol":"AAPL250919C00160000","strike":160.0,"currency":"USD","lastPrice":79.58,"change":0.13000488,"percentChange":0.16363107,"volume":37,"openInterest":1900,"bid":78.35,"ask":79.5,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758119508,"impliedVolatility":2.3750040624999995,"inTheMoney":true},{"contractSymbol":"AAPL250919C00165000","strike":165.0,"currency":"USD","lastPrice":74.75,"change":3.5899963,"percentChange":5.044964,"volume":3,"openInterest":2826,"bid":73.3,"ask":74.55,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758122589,"impliedVolatility":2.2548871752929687,"inTheMoney":true},{"contractSymbol":"AAPL250919C00170000","strike":170.0,"currency":"USD","lastPrice":69.75,"change":0.5299988,"percentChange":0.76567286,"volume":9,"openInterest":1204,"bid":68.25,"ask":69.55,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758129075,"impliedVolatility":2.0996141259765624,"inTheMoney":true},{"contractSymbol":"AAPL250919C00175000","strike":175.0,"currency":"USD","lastPrice":64.64,"change":0.73999786,"percentChange":1.1580561,"volume":6,"openInterest":1726,"bid":63.0,"ask":64.65,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758118747,"impliedVolatility":2.00781748046875,"inTheMoney":true},{"contractSymbol":"AAPL250919C00180000","strike":180.0,"currency":"USD","lastPrice":58.65,"change":-0.3899994,"percentChange":-0.66056806,"volume":10,"openInterest":1847,"bid":58.55,"ask":59.5,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758135365,"impliedVolatility":1.2109414453124998,"inTheMoney":true},{"contractSymbol":"AAPL250919C00185000","strike":185.0,"currency":"USD","lastPrice":54.4,"change":0.2100029,"percentChange":0.38753074,"volume":20,"openInterest":4806,"bid":53.45,"ask":54.75,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758136210,"impliedVolatility":1.2695349023437497,"inTheMoney":true},{"contractSymbol":"AAPL250919C00190000","strike":190.0,"currency":"USD","lastPrice":49.42,"change":0.76999664,"percentChange":1.5827268,"volume":26,"openInterest":2635,"bid":48.7,"ask":49.8,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758136507,"impliedVolatility":1.3125034374999998,"inTheMoney":true},{"contractSymbol":"AAPL250919C00195000","strike":195.0,"currency":"USD","lastPrice":44.36,"change":0.010002136,"percentChange":0.022552732,"volume":70,"openInterest":2337,"bid":43.75,"ask":44.2,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758136939,"impliedVolatility":1.142582412109375,"inTheMoney":true},{"contractSymbol":"AAPL250919C00200000","strike":200.0,"currency":"USD","lastPrice":38.92,"change":0.61999893,"percentChange":1.6187962,"volume":114,"openInterest":7794,"bid":38.8,"ask":39.5,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758132598,"impliedVolatility":0.9765627343750001,"inTheMoney":true},{"contractSymbol":"AAPL250919C00202500","strike":202.5,"currency":"USD","lastPrice":36.86,"change":0.29999924,"percentChange":0.82056683,"volume":81,"openInterest":12,"bid":35.95,"ask":37.05,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758126235,"impliedVolatility":0.6562534375000001,"inTheMoney":true},{"contractSymbol":"AAPL250919C00205000","strike":205.0,"currency":"USD","lastPrice":34.12,"change":-0.13000107,"percentChange":-0.37956515,"volume":101,"openInterest":8537,"bid":33.8,"ask":34.35,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138516,"impliedVolatility":0.7812521875,"inTheMoney":true},{"contractSymbol":"AAPL250919C00207500","strike":207.5,"currency":"USD","lastPrice":31.4,"change":-0.17000008,"percentChange":-0.5384861,"volume":1,"openInterest":125,"bid":30.95,"ask":32.15,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758051067,"impliedVolatility":0.69336244140625,"inTheMoney":true},{"contractSymbol":"AAPL250919C00210000","strike":210.0,"currency":"USD","lastPrice":29.0,"change":0.6499996,"percentChange":2.2927675,"volume":461,"openInterest":14521,"bid":28.75,"ask":29.4,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139196,"impliedVolatility":0.6738313867187502,"inTheMoney":true},{"contractSymbol":"AAPL250919C00212500","strike":212.5,"currency":"USD","lastPrice":26.35,"change":-0.44000053,"percentChange":-1.6424059,"volume":47,"openInterest":497,"bid":26.1,"ask":27.3,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758127105,"impliedVolatility":0.719729365234375,"inTheMoney":true},{"contractSymbol":"AAPL250919C00215000","strike":215.0,"currency":"USD","lastPrice":23.75,"change":0.4300003,"percentChange":1.8439121,"volume":400,"openInterest":21425,"bid":23.8,"ask":24.4,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138728,"impliedVolatility":0.5898478515625001,"inTheMoney":true},{"contractSymbol":"AAPL250919C00217500","strike":217.5,"currency":"USD","lastPrice":21.74,"change":0.09000015,"percentChange":0.4157051,"volume":64,"openInterest":782,"bid":21.4,"ask":22.35,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138159,"impliedVolatility":0.6796907031250001,"inTheMoney":true},{"contractSymbol":"AAPL250919C00220000","strike":220.0,"currency":"USD","lastPrice":18.84,"change":0.45000076,"percentChange":2.4469862,"volume":1303,"openInterest":22172,"bid":18.95,"ask":19.3,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139140,"impliedVolatility":0.584965087890625,"inTheMoney":true},{"contractSymbol":"AAPL250919C00222500","strike":222.5,"currency":"USD","lastPrice":16.6,"change":0.47000122,"percentChange":2.913833,"volume":197,"openInterest":4271,"bid":16.45,"ask":16.8,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138509,"impliedVolatility":0.5214891601562499,"inTheMoney":true},{"contractSymbol":"AAPL250919C00225000","strike":225.0,"currency":"USD","lastPrice":14.0,"change":0.6000004,"percentChange":4.477615,"volume":1715,"openInterest":19286,"bid":13.95,"ask":14.35,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139140,"impliedVolatility":0.4731497998046875,"inTheMoney":true},{"contractSymbol":"AAPL250919C00227500","strike":227.5,"currency":"USD","lastPrice":11.45,"change":0.34999943,"percentChange":3.153148,"volume":353,"openInterest":6334,"bid":11.5,"ask":11.85,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138245,"impliedVolatility":0.4062559375,"inTheMoney":true},{"contractSymbol":"AAPL250919C00230000","strike":230.0,"currency":"USD","lastPrice":9.01,"change":0.3800001,"percentChange":4.403246,"volume":7039,"openInterest":34804,"bid":9.05,"ask":9.4,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139133,"impliedVolatility":0.3501041552734374,"inTheMoney":true},{"contractSymbol":"AAPL250919C00232500","strike":232.5,"currency":"USD","lastPrice":6.75,"change":0.32000017,"percentChange":4.9766746,"volume":1597,"openInterest":12526,"bid":6.7,"ask":7.0,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139190,"impliedVolatility":0.29761444580078117,"inTheMoney":true},{"contractSymbol":"AAPL250919C00235000","strike":235.0,"currency":"USD","lastPrice":4.65,"change":0.20000029,"percentChange":4.4943886,"volume":7860,"openInterest":31085,"bid":4.65,"ask":4.8,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139198,"impliedVolatility":0.264655791015625,"inTheMoney":true},{"contractSymbol":"AAPL250919C00237500","strike":237.5,"currency":"USD","lastPrice":2.75,"change":0.0,"percentChange":0.0,"volume":12571,"openInterest":12152,"bid":2.79,"ask":2.85,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139156,"impliedVolatility":0.23413851806640623,"inTheMoney":true},{"contractSymbol":"AAPL250919C00240000","strike":240.0,"currency":"USD","lastPrice":1.44,"change":-0.13,"percentChange":-8.280254,"volume":75932,"openInterest":55098,"bid":1.41,"ask":1.44,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139199,"impliedVolatility":0.21973436523437498,"inTheMoney":false},{"contractSymbol":"AAPL250919C00242500","strike":242.5,"currency":"USD","lastPrice":0.64,"change":-0.18,"percentChange":-21.95122,"volume":42407,"openInterest":25331,"bid":0.61,"ask":0.64,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139196,"impliedVolatility":0.21826953613281247,"inTheMoney":false},{"contractSymbol":"AAPL250919C00245000","strike":245.0,"currency":"USD","lastPrice":0.27,"change":-0.13,"percentChange":-32.5,"volume":46727,"openInterest":60266,"bid":0.25,"ask":0.27,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139196,"impliedVolatility":0.225593681640625,"inTheMoney":false},{"contractSymbol":"AAPL250919C00247500","strike":247.5,"currency":"USD","lastPrice":0.13,"change":-0.08,"percentChange":-38.095238,"volume":16190,"openInterest":13473,"bid":0.12,"ask":0.13,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139194,"impliedVolatility":0.24414818359375,"inTheMoney":false},{"contractSymbol":"AAPL250919C00250000","strike":250.0,"currency":"USD","lastPrice":0.08,"change":-0.049999997,"percentChange":-38.461536,"volume":13655,"openInterest":51977,"bid":0.07,"ask":0.08,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139187,"impliedVolatility":0.27149166015625,"inTheMoney":false},{"contractSymbol":"AAPL250919C00252500","strike":252.5,"currency":"USD","lastPrice":0.05,"change":-0.029999997,"percentChange":-37.499996,"volume":2055,"openInterest":9166,"bid":0.04,"ask":0.06,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138889,"impliedVolatility":0.304694453125,"inTheMoney":false},{"contractSymbol":"AAPL250919C00255000","strike":255.0,"currency":"USD","lastPrice":0.04,"change":0.0,"percentChange":0.0,"volume":5343,"openInterest":15152,"bid":0.03,"ask":0.04,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139094,"impliedVolatility":0.33008482421874996,"inTheMoney":false},{"contractSymbol":"AAPL250919C00257500","strike":257.5,"currency":"USD","lastPrice":0.02,"change":-0.02,"percentChange":-50.0,"volume":971,"openInterest":4790,"bid":0.02,"ask":0.03,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138172,"impliedVolatility":0.35938140625,"inTheMoney":false},{"contractSymbol":"AAPL250919C00260000","strike":260.0,"currency":"USD","lastPrice":0.01,"change":-0.02,"percentChange":-66.66667,"volume":2394,"openInterest":16687,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138846,"impliedVolatility":0.3789124609375,"inTheMoney":false},{"contractSymbol":"AAPL250919C00262500","strike":262.5,"currency":"USD","lastPrice":0.01,"change":-0.02,"percentChange":-66.66667,"volume":334,"openInterest":1048,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138952,"impliedVolatility":0.41797457031249996,"inTheMoney":false},{"contractSymbol":"AAPL250919C00265000","strike":265.0,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-50.0,"volume":974,"openInterest":7638,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139030,"impliedVolatility":0.45313046875,"inTheMoney":false},{"contractSymbol":"AAPL250919C00267500","strike":267.5,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-50.0,"volume":50,"openInterest":609,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758127076,"impliedVolatility":0.45313046875,"inTheMoney":false},{"contractSymbol":"AAPL250919C00270000","strike":270.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2127,"openInterest":11082,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758137182,"impliedVolatility":0.48438015625,"inTheMoney":false},{"contractSymbol":"AAPL250919C00275000","strike":275.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":106,"openInterest":8109,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758131728,"impliedVolatility":0.51562984375,"inTheMoney":false},{"contractSymbol":"AAPL250919C00280000","strike":280.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":6570,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758041217,"impliedVolatility":0.57812921875,"inTheMoney":false},{"contractSymbol":"AAPL250919C00285000","strike":285.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":72,"openInterest":2812,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758137182,"impliedVolatility":0.64062859375,"inTheMoney":false},{"contractSymbol":"AAPL250919C00290000","strike":290.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":9,"openInterest":5344,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758034085,"impliedVolatility":0.6875031250000001,"inTheMoney":false},{"contractSymbol":"AAPL250919C00295000","strike":295.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":10,"openInterest":1961,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757686136,"impliedVolatility":0.7500025,"inTheMoney":false},{"contractSymbol":"AAPL250919C00300000","strike":300.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":4,"openInterest":9050,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758120922,"impliedVolatility":0.8125018749999999,"inTheMoney":false},{"contractSymbol":"AAPL250919C00305000","strike":305.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":4,"openInterest":2037,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757347122,"impliedVolatility":0.85937640625,"inTheMoney":false},{"contractSymbol":"AAPL250919C00310000","strike":310.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":3771,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757433511,"impliedVolatility":0.9062509375,"inTheMoney":false},{"contractSymbol":"AAPL250919C00320000","strike":320.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":6,"openInterest":4259,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1756231208,"impliedVolatility":1.000005,"inTheMoney":false},{"contractSymbol":"AAPL250919C00330000","strike":330.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":1740,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757529078,"impliedVolatility":1.09375453125,"inTheMoney":false},{"contractSymbol":"AAPL250919C00340000","strike":340.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":99,"openInterest":1461,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757692233,"impliedVolatility":1.1875040625,"inTheMoney":false},{"contractSymbol":"AAPL250919C00350000","strike":350.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":30,"openInterest":4454,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757690944,"impliedVolatility":1.2812535937499998,"inTheMoney":false},{"contractSymbol":"AAPL250919C00360000","strike":360.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":3338,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757529094,"impliedVolatility":1.3750031249999999,"inTheMoney":false},{"contractSymbol":"AAPL250919C00370000","strike":370.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":100,"openInterest":992,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758044417,"impliedVolatility":1.4375028125,"inTheMoney":false},{"contractSymbol":"AAPL250919C00380000","strike":380.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":17,"openInterest":1591,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758122807,"impliedVolatility":1.5625021874999998,"inTheMoney":false},{"contractSymbol":"AAPL250919C00390000","strike":390.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":4681,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758033261,"impliedVolatility":1.625001875,"inTheMoney":false}],"puts":[{"contractSymbol":"AAPL250919P00005000","strike":5.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":3324,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1754335962,"impliedVolatility":13.000001875,"inTheMoney":false},{"contractSymbol":"AAPL250919P00010000","strike":10.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":19,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758036820,"impliedVolatility":10.500003437500002,"inTheMoney":false},{"contractSymbol":"AAPL250919P00015000","strike":15.0,"currency":"USD","lastPrice":0.11,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":59,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757943000,"impliedVolatility":9.000004375,"inTheMoney":false},{"contractSymbol":"AAPL250919P00020000","strike":20.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":8,"openInterest":132,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757351248,"impliedVolatility":8.250004843749998,"inTheMoney":false},{"contractSymbol":"AAPL250919P00025000","strike":25.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":30,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757947087,"impliedVolatility":7.500000625,"inTheMoney":false},{"contractSymbol":"AAPL250919P00030000","strike":30.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"openInterest":11,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1754919007,"impliedVolatility":6.7500015625000005,"inTheMoney":false},{"contractSymbol":"AAPL250919P00035000","strike":35.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":39,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1750780925,"impliedVolatility":6.2500021875,"inTheMoney":false},{"contractSymbol":"AAPL250919P00040000","strike":40.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":124,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1750783065,"impliedVolatility":5.87500265625,"inTheMoney":false},{"contractSymbol":"AAPL250919P00045000","strike":45.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":50,"openInterest":751,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1750868154,"impliedVolatility":5.500003124999999,"inTheMoney":false},{"contractSymbol":"AAPL250919P00050000","strike":50.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":1024,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1752175702,"impliedVolatility":5.12500359375,"inTheMoney":false},{"contractSymbol":"AAPL250919P00055000","strike":55.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":794,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1753807298,"impliedVolatility":4.7500040624999995,"inTheMoney":false},{"contractSymbol":"AAPL250919P00060000","strike":60.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":2471,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1756483060,"impliedVolatility":4.500004375,"inTheMoney":false},{"contractSymbol":"AAPL250919P00065000","strike":65.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":287,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1756301499,"impliedVolatility":4.2500046875,"inTheMoney":false},{"contractSymbol":"AAPL250919P00070000","strike":70.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":1008,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1751310672,"impliedVolatility":4.000005,"inTheMoney":false},{"contractSymbol":"AAPL250919P00075000","strike":75.0,"currency":"USD","lastPrice":0.03,"change":0.0,"percentChange":0.0,"volume":70,"openInterest":769,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1751991298,"impliedVolatility":3.8750003125,"inTheMoney":false},{"contractSymbol":"AAPL250919P00080000","strike":80.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":1979,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1755611022,"impliedVolatility":3.6250009375000003,"inTheMoney":false},{"contractSymbol":"AAPL250919P00085000","strike":85.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":196,"openInterest":1315,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1754505993,"impliedVolatility":3.3750015624999996,"inTheMoney":false},{"contractSymbol":"AAPL250919P00090000","strike":90.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":2986,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1754671067,"impliedVolatility":3.2500018749999997,"inTheMoney":false},{"contractSymbol":"AAPL250919P00095000","strike":95.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":657,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1755698616,"impliedVolatility":3.06250234375,"inTheMoney":false},{"contractSymbol":"AAPL250919P00100000","strike":100.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":1645,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757082075,"impliedVolatility":2.8750028125,"inTheMoney":false},{"contractSymbol":"AAPL250919P00105000","strike":105.0,"currency":"USD","lastPrice":0.03,"change":0.0,"percentChange":0.0,"volume":91,"openInterest":2343,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1754919125,"impliedVolatility":2.750003125,"inTheMoney":false},{"contractSymbol":"AAPL250919P00110000","strike":110.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":35,"openInterest":2720,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1755882536,"impliedVolatility":2.6250034375,"inTheMoney":false},{"contractSymbol":"AAPL250919P00115000","strike":115.0,"currency":"USD","lastPrice":0.04,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":938,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757530612,"impliedVolatility":2.43750390625,"inTheMoney":false},{"contractSymbol":"AAPL250919P00120000","strike":120.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":3901,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757338203,"impliedVolatility":2.3125042187499996,"inTheMoney":false},{"contractSymbol":"AAPL250919P00125000","strike":125.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":4,"openInterest":1294,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757943164,"impliedVolatility":2.1875045312499997,"inTheMoney":false},{"contractSymbol":"AAPL250919P00130000","strike":130.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":2385,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758037264,"impliedVolatility":2.0625048437499998,"inTheMoney":false},{"contractSymbol":"AAPL250919P00135000","strike":135.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":10,"openInterest":3151,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757526899,"impliedVolatility":1.9375003125,"inTheMoney":false},{"contractSymbol":"AAPL250919P00140000","strike":140.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":10,"openInterest":9115,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757603044,"impliedVolatility":1.8125009374999999,"inTheMoney":false},{"contractSymbol":"AAPL250919P00145000","strike":145.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":4,"openInterest":6057,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757964610,"impliedVolatility":1.6875015625,"inTheMoney":false},{"contractSymbol":"AAPL250919P00150000","strike":150.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":4677,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758030678,"impliedVolatility":1.625001875,"inTheMoney":false},{"contractSymbol":"AAPL250919P00155000","strike":155.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":2722,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757944469,"impliedVolatility":1.5000025,"inTheMoney":false},{"contractSymbol":"AAPL250919P00160000","strike":160.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":4,"openInterest":12751,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758032082,"impliedVolatility":1.3750031249999999,"inTheMoney":false},{"contractSymbol":"AAPL250919P00165000","strike":165.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":26,"openInterest":22592,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758043761,"impliedVolatility":1.3125034374999998,"inTheMoney":false},{"contractSymbol":"AAPL250919P00170000","strike":170.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":71,"openInterest":17672,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758137182,"impliedVolatility":1.1875040625,"inTheMoney":false},{"contractSymbol":"AAPL250919P00175000","strike":175.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":8517,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758127367,"impliedVolatility":1.09375453125,"inTheMoney":false},{"contractSymbol":"AAPL250919P00180000","strike":180.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":14,"openInterest":22399,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138484,"impliedVolatility":1.000005,"inTheMoney":false},{"contractSymbol":"AAPL250919P00185000","strike":185.0,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-50.0,"volume":176,"openInterest":15162,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758137183,"impliedVolatility":0.9062509375,"inTheMoney":false},{"contractSymbol":"AAPL250919P00190000","strike":190.0,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-50.0,"volume":183,"openInterest":16094,"bid":0.0,"ask":0.02,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138714,"impliedVolatility":0.87500125,"inTheMoney":false},{"contractSymbol":"AAPL250919P00195000","strike":195.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":135,"openInterest":25497,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758137182,"impliedVolatility":0.8203142968749999,"inTheMoney":false},{"contractSymbol":"AAPL250919P00200000","strike":200.0,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-50.0,"volume":734,"openInterest":37543,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138134,"impliedVolatility":0.726565234375,"inTheMoney":false},{"contractSymbol":"AAPL250919P00202500","strike":202.5,"currency":"USD","lastPrice":0.02,"change":-0.01,"percentChange":-33.333336,"volume":750,"openInterest":835,"bid":0.01,"ask":0.03,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139197,"impliedVolatility":0.70312796875,"inTheMoney":false},{"contractSymbol":"AAPL250919P00205000","strike":205.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":221,"openInterest":19467,"bid":0.02,"ask":0.03,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138187,"impliedVolatility":0.6718782812500002,"inTheMoney":false},{"contractSymbol":"AAPL250919P00207500","strike":207.5,"currency":"USD","lastPrice":0.03,"change":0.0,"percentChange":0.0,"volume":61,"openInterest":3988,"bid":0.02,"ask":0.03,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758137539,"impliedVolatility":0.6250037500000001,"inTheMoney":false},{"contractSymbol":"AAPL250919P00210000","strike":210.0,"currency":"USD","lastPrice":0.03,"change":0.0,"percentChange":0.0,"volume":1988,"openInterest":16733,"bid":0.03,"ask":0.04,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138960,"impliedVolatility":0.5976602734375001,"inTheMoney":false},{"contractSymbol":"AAPL250919P00212500","strike":212.5,"currency":"USD","lastPrice":0.04,"change":0.0,"percentChange":0.0,"volume":64,"openInterest":5330,"bid":0.03,"ask":0.04,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138567,"impliedVolatility":0.5507857421875001,"inTheMoney":false},{"contractSymbol":"AAPL250919P00215000","strike":215.0,"currency":"USD","lastPrice":0.05,"change":0.010000002,"percentChange":25.000004,"volume":707,"openInterest":14030,"bid":0.04,"ask":0.05,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138769,"impliedVolatility":0.5195360546875,"inTheMoney":false},{"contractSymbol":"AAPL250919P00217500","strike":217.5,"currency":"USD","lastPrice":0.06,"change":0.009999998,"percentChange":19.999994,"volume":779,"openInterest":5852,"bid":0.05,"ask":0.06,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139094,"impliedVolatility":0.48828636718750007,"inTheMoney":false},{"contractSymbol":"AAPL250919P00220000","strike":220.0,"currency":"USD","lastPrice":0.06,"change":-0.010000002,"percentChange":-14.285716,"volume":1847,"openInterest":24168,"bid":0.06,"ask":0.07,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139093,"impliedVolatility":0.44727115234375,"inTheMoney":false},{"contractSymbol":"AAPL250919P00222500","strike":222.5,"currency":"USD","lastPrice":0.07,"change":-0.020000003,"percentChange":-22.222223,"volume":1804,"openInterest":5980,"bid":0.07,"ask":0.08,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138941,"impliedVolatility":0.4023497265625,"inTheMoney":false},{"contractSymbol":"AAPL250919P00225000","strike":225.0,"currency":"USD","lastPrice":0.09,"change":-0.03999999,"percentChange":-30.769224,"volume":4451,"openInterest":20691,"bid":0.08,"ask":0.1,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139040,"impliedVolatility":0.3632876171875,"inTheMoney":false},{"contractSymbol":"AAPL250919P00227500","strike":227.5,"currency":"USD","lastPrice":0.12,"change":-0.07,"percentChange":-36.842106,"volume":2256,"openInterest":6966,"bid":0.12,"ask":0.13,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139169,"impliedVolatility":0.3232489550781249,"inTheMoney":false},{"contractSymbol":"AAPL250919P00230000","strike":230.0,"currency":"USD","lastPrice":0.21,"change":-0.13000001,"percentChange":-38.2353,"volume":10848,"openInterest":23104,"bid":0.18,"ask":0.2,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139192,"impliedVolatility":0.29053443847656246,"inTheMoney":false},{"contractSymbol":"AAPL250919P00232500","strike":232.5,"currency":"USD","lastPrice":0.37,"change":-0.24000001,"percentChange":-39.34426,"volume":8438,"openInterest":8639,"bid":0.32,"ask":0.34,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139174,"impliedVolatility":0.2617261328125,"inTheMoney":false},{"contractSymbol":"AAPL250919P00235000","strike":235.0,"currency":"USD","lastPrice":0.64,"change":-0.49,"percentChange":-43.36283,"volume":27192,"openInterest":14306,"bid":0.62,"ask":0.64,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139198,"impliedVolatility":0.23853300537109373,"inTheMoney":false},{"contractSymbol":"AAPL250919P00237500","strike":237.5,"currency":"USD","lastPrice":1.25,"change":-0.70000005,"percentChange":-35.897438,"volume":41986,"openInterest":4795,"bid":1.21,"ask":1.25,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139198,"impliedVolatility":0.22095505615234373,"inTheMoney":false},{"contractSymbol":"AAPL250919P00240000","strike":240.0,"currency":"USD","lastPrice":2.35,"change":-0.9000001,"percentChange":-27.69231,"volume":17821,"openInterest":9380,"bid":2.31,"ask":2.36,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139180,"impliedVolatility":0.20899228515624996,"inTheMoney":true},{"contractSymbol":"AAPL250919P00242500","strike":242.5,"currency":"USD","lastPrice":4.12,"change":-0.86000013,"percentChange":-17.26908,"volume":1424,"openInterest":957,"bid":3.95,"ask":4.1,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138857,"impliedVolatility":0.21094539062499998,"inTheMoney":true},{"contractSymbol":"AAPL250919P00245000","strike":245.0,"currency":"USD","lastPrice":6.41,"change":-0.7000003,"percentChange":-9.845292,"volume":1088,"openInterest":4637,"bid":6.05,"ask":6.45,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138857,"impliedVolatility":0.2622144091796875,"inTheMoney":true},{"contractSymbol":"AAPL250919P00247500","strike":247.5,"currency":"USD","lastPrice":8.8,"change":0.19000053,"percentChange":2.2067425,"volume":128,"openInterest":187,"bid":8.35,"ask":9.15,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758139151,"impliedVolatility":0.37280900634765624,"inTheMoney":true},{"contractSymbol":"AAPL250919P00250000","strike":250.0,"currency":"USD","lastPrice":11.05,"change":-0.05000019,"percentChange":-0.45045215,"volume":134,"openInterest":613,"bid":10.9,"ask":11.3,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758135066,"impliedVolatility":0.35547519531249994,"inTheMoney":true},{"contractSymbol":"AAPL250919P00252500","strike":252.5,"currency":"USD","lastPrice":13.6,"change":-0.39999962,"percentChange":-2.85714,"volume":4,"openInterest":51,"bid":13.25,"ask":13.9,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758138549,"impliedVolatility":0.44385321777343745,"inTheMoney":true},{"contractSymbol":"AAPL250919P00255000","strike":255.0,"currency":"USD","lastPrice":16.5,"change":0.5500002,"percentChange":3.448277,"volume":6,"openInterest":119,"bid":15.75,"ask":16.5,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758134429,"impliedVolatility":0.5312546875,"inTheMoney":true},{"contractSymbol":"AAPL250919P00257500","strike":257.5,"currency":"USD","lastPrice":18.3,"change":0.0,"percentChange":0.0,"volume":5,"bid":18.2,"ask":18.95,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758052200,"impliedVolatility":0.5742230078125,"inTheMoney":true},{"contractSymbol":"AAPL250919P00260000","strike":260.0,"currency":"USD","lastPrice":23.9,"change":0.0,"percentChange":0.0,"volume":500,"openInterest":46,"bid":20.75,"ask":21.55,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757962891,"impliedVolatility":0.5009815527343751,"inTheMoney":true},{"contractSymbol":"AAPL250919P00265000","strike":265.0,"currency":"USD","lastPrice":31.25,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":15,"bid":25.7,"ask":26.5,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757695362,"impliedVolatility":0.55273884765625,"inTheMoney":true},{"contractSymbol":"AAPL250919P00270000","strike":270.0,"currency":"USD","lastPrice":29.8,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":2,"bid":30.75,"ask":31.55,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1758031976,"impliedVolatility":0.6796907031250001,"inTheMoney":true},{"contractSymbol":"AAPL250919P00275000","strike":275.0,"currency":"USD","lastPrice":39.95,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":0,"bid":35.75,"ask":36.45,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757944641,"impliedVolatility":0.7148466015625,"inTheMoney":true},{"contractSymbol":"AAPL250919P00280000","strike":280.0,"currency":"USD","lastPrice":43.15,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":0,"bid":40.75,"ask":41.5,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757439626,"impliedVolatility":0.8203142968749999,"inTheMoney":true},{"contractSymbol":"AAPL250919P00285000","strike":285.0,"currency":"USD","lastPrice":45.75,"change":0.0,"percentChange":0.0,"volume":4,"openInterest":0,"bid":45.7,"ask":46.6,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757091405,"impliedVolatility":0.9238288867187501,"inTheMoney":true},{"contractSymbol":"AAPL250919P00290000","strike":290.0,"currency":"USD","lastPrice":63.2,"change":0.0,"percentChange":0.0,"volume":30,"openInterest":0,"bid":50.75,"ask":51.5,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1755719400,"impliedVolatility":0.9726565234375,"inTheMoney":true},{"contractSymbol":"AAPL250919P00295000","strike":295.0,"currency":"USD","lastPrice":60.15,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":0,"bid":55.7,"ask":56.5,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757441412,"impliedVolatility":1.01172369140625,"inTheMoney":true},{"contractSymbol":"AAPL250919P00300000","strike":300.0,"currency":"USD","lastPrice":62.31,"change":0.0,"percentChange":0.0,"volume":6,"openInterest":0,"bid":60.75,"ask":61.65,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757004843,"impliedVolatility":1.197269638671875,"inTheMoney":true},{"contractSymbol":"AAPL250919P00310000","strike":310.0,"currency":"USD","lastPrice":78.6,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":0,"bid":70.75,"ask":71.5,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1755538411,"impliedVolatility":1.2519568652343749,"inTheMoney":true},{"contractSymbol":"AAPL250919P00320000","strike":320.0,"currency":"USD","lastPrice":105.9,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":0,"bid":121.85,"ask":123.1,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1742305446,"impliedVolatility":7.633789520263672,"inTheMoney":true},{"contractSymbol":"AAPL250919P00330000","strike":330.0,"currency":"USD","lastPrice":130.0,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":0,"bid":125.65,"ask":128.25,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1746461335,"impliedVolatility":7.239014623107911,"inTheMoney":true},{"contractSymbol":"AAPL250919P00340000","strike":340.0,"currency":"USD","lastPrice":109.95,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":0,"bid":100.75,"ask":101.5,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757620200,"impliedVolatility":1.623048759765625,"inTheMoney":true},{"contractSymbol":"AAPL250919P00350000","strike":350.0,"currency":"USD","lastPrice":122.84,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":0,"bid":102.15,"ask":104.15,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1731098506,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250919P00370000","strike":370.0,"currency":"USD","lastPrice":130.9,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":0,"bid":130.75,"ask":131.55,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757015399,"impliedVolatility":1.99609376953125,"inTheMoney":true},{"contractSymbol":"AAPL250919P00380000","strike":380.0,"currency":"USD","lastPrice":148.8,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":141.55,"ask":142.3,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1755287400,"impliedVolatility":2.750003125,"inTheMoney":true},{"contractSymbol":"AAPL250919P00390000","strike":390.0,"currency":"USD","lastPrice":153.33,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":0,"bid":150.8,"ask":151.55,"contractSize":"REGULAR","expiration":1758240000,"lastTradeDate":1757949504,"impliedVolatility":2.2460981347656244,"inTheMoney":true}]}]}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1758844800.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1758844800.json new file mode 100644 index 0000000..69a03aa --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1758844800.json @@ -0,0 +1 @@ +{"optionChain":{"result":[{"underlyingSymbol":"AAPL","expirationDates":[1758844800,1759449600,1760054400,1760659200,1761264000,1761868800,1763683200,1766102400,1768521600,1771545600,1773964800,1776384000,1778803200,1781740800,1787270400,1789689600,1797552000,1799971200,1813190400,1829001600,1832025600],"strikes":[110.0,120.0,125.0,130.0,135.0,140.0,145.0,150.0,155.0,160.0,165.0,170.0,175.0,180.0,185.0,190.0,195.0,200.0,202.5,205.0,207.5,210.0,212.5,215.0,217.5,220.0,222.5,225.0,227.5,230.0,232.5,235.0,237.5,240.0,242.5,245.0,247.5,250.0,252.5,255.0,257.5,260.0,262.5,265.0,270.0,275.0,280.0,285.0,290.0,295.0,300.0,305.0,310.0,315.0,320.0,325.0],"hasMiniOptions":false,"quote":{"language":"en-US","region":"US","quoteType":"EQUITY","typeDisp":"Equity","quoteSourceName":"Delayed Quote","triggerable":true,"customPriceAlertConfidence":"HIGH","currency":"USD","exchange":"NMS","messageBoardId":"finmb_24937","exchangeTimezoneName":"America/New_York","exchangeTimezoneShortName":"EDT","gmtOffSetMilliseconds":-14400000,"market":"us_market","esgPopulated":false,"regularMarketChangePercent":0.634143,"regularMarketPrice":253.91,"marketState":"REGULAR","corporateActions":[],"regularMarketTime":1758813557,"shortName":"Apple Inc.","longName":"Apple Inc.","firstTradeDateMilliseconds":345479400000,"priceHint":2,"regularMarketChange":1.6000061,"regularMarketDayHigh":254.3199,"regularMarketDayRange":"251.712 - 254.3199","regularMarketDayLow":251.712,"regularMarketVolume":11065253,"regularMarketPreviousClose":252.31,"bid":251.02,"ask":254.97,"bidSize":3,"askSize":4,"fullExchangeName":"NasdaqGS","financialCurrency":"USD","regularMarketOpen":253.205,"averageDailyVolume3Month":57147661,"averageDailyVolume10Day":67471010,"fiftyTwoWeekLowChange":84.7,"fiftyTwoWeekLowChangePercent":0.5005614,"fiftyTwoWeekRange":"169.21 - 260.1","fiftyTwoWeekHighChange":-6.1900024,"fiftyTwoWeekHighChangePercent":-0.023798548,"fiftyTwoWeekLow":169.21,"fiftyTwoWeekHigh":260.1,"fiftyTwoWeekChangePercent":10.8957405,"dividendDate":1755129600,"earningsTimestamp":1753992000,"earningsTimestampStart":1761854400,"earningsTimestampEnd":1761854400,"earningsCallTimestampStart":1753995600,"earningsCallTimestampEnd":1753995600,"isEarningsDateEstimate":true,"trailingAnnualDividendRate":1.01,"trailingPE":38.52959,"dividendRate":1.04,"trailingAnnualDividendYield":0.004003012,"dividendYield":0.41,"epsTrailingTwelveMonths":6.59,"epsForward":8.31,"epsCurrentYear":7.38523,"priceEpsCurrentYear":34.380783,"sharesOutstanding":14840390000,"bookValue":4.431,"fiftyDayAverage":226.6076,"fiftyDayAverageChange":27.302399,"fiftyDayAverageChangePercent":0.12048315,"twoHundredDayAverage":221.84135,"twoHundredDayAverageChange":32.06865,"twoHundredDayAverageChangePercent":0.14455667,"marketCap":3768123392000,"forwardPE":30.554752,"priceToBook":57.30309,"sourceInterval":15,"exchangeDataDelayedBy":0,"averageAnalystRating":"2.0 - Buy","tradeable":false,"cryptoTradeable":false,"hasPrePostMarketData":true,"displayName":"Apple","symbol":"AAPL"},"options":[{"expirationDate":1758844800,"hasMiniOptions":false,"calls":[{"contractSymbol":"AAPL250926C00110000","strike":110.0,"currency":"USD","lastPrice":141.62,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":17,"bid":143.0,"ask":143.45,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758743172,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00120000","strike":120.0,"currency":"USD","lastPrice":135.7,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":20,"bid":132.6,"ask":133.75,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758636473,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00125000","strike":125.0,"currency":"USD","lastPrice":131.09,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":12,"bid":127.95,"ask":128.4,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758635383,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00130000","strike":130.0,"currency":"USD","lastPrice":125.55,"change":0.0,"percentChange":0.0,"volume":8,"openInterest":10,"bid":123.1,"ask":123.9,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758638942,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00135000","strike":135.0,"currency":"USD","lastPrice":116.91,"change":-4.0699997,"percentChange":-3.364192,"volume":1,"openInterest":5,"bid":117.95,"ask":118.45,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758807319,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00140000","strike":140.0,"currency":"USD","lastPrice":116.03,"change":0.0,"percentChange":0.0,"volume":7,"openInterest":13,"bid":113.0,"ask":113.45,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758644558,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00145000","strike":145.0,"currency":"USD","lastPrice":106.77,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":6,"bid":108.2,"ask":108.55,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758738642,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00150000","strike":150.0,"currency":"USD","lastPrice":101.77,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":7,"bid":103.05,"ask":103.45,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758738642,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00155000","strike":155.0,"currency":"USD","lastPrice":100.93,"change":0.0,"percentChange":0.0,"volume":4,"openInterest":8,"bid":98.1,"ask":98.75,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758642821,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00160000","strike":160.0,"currency":"USD","lastPrice":91.64,"change":0.0,"percentChange":0.0,"volume":7,"openInterest":6,"bid":93.15,"ask":93.9,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758740081,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00165000","strike":165.0,"currency":"USD","lastPrice":87.44,"change":0.62000275,"percentChange":0.7141243,"volume":4,"openInterest":9,"bid":87.95,"ask":88.75,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758738642,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00170000","strike":170.0,"currency":"USD","lastPrice":81.87,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":7,"bid":82.8,"ask":83.7,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758732044,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00175000","strike":175.0,"currency":"USD","lastPrice":76.65,"change":0.27000427,"percentChange":0.3535013,"volume":3,"openInterest":14,"bid":78.05,"ask":78.95,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758741409,"impliedVolatility":1.9140629296875002,"inTheMoney":true},{"contractSymbol":"AAPL250926C00180000","strike":180.0,"currency":"USD","lastPrice":76.2,"change":0.0,"percentChange":0.0,"volume":6,"openInterest":33,"bid":72.95,"ask":73.4,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758641282,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00185000","strike":185.0,"currency":"USD","lastPrice":66.3,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":153,"bid":68.05,"ask":68.85,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758742374,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00190000","strike":190.0,"currency":"USD","lastPrice":62.98,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":148,"bid":63.05,"ask":63.35,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758724282,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00195000","strike":195.0,"currency":"USD","lastPrice":56.9,"change":0.0,"percentChange":0.0,"volume":11,"openInterest":265,"bid":58.15,"ask":59.0,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758732044,"impliedVolatility":1.54297103515625,"inTheMoney":true},{"contractSymbol":"AAPL250926C00200000","strike":200.0,"currency":"USD","lastPrice":54.15,"change":2.300003,"percentChange":4.4358788,"volume":2,"openInterest":393,"bid":53.0,"ask":53.35,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758810322,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00202500","strike":202.5,"currency":"USD","lastPrice":49.99,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":57,"bid":50.6,"ask":50.85,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758721741,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00205000","strike":205.0,"currency":"USD","lastPrice":47.8,"change":0.20000076,"percentChange":0.42016968,"volume":4,"openInterest":133,"bid":48.05,"ask":48.8,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758807438,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00207500","strike":207.5,"currency":"USD","lastPrice":44.52,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":46,"bid":45.1,"ask":46.15,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758727004,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00210000","strike":210.0,"currency":"USD","lastPrice":43.03,"change":0.4300003,"percentChange":1.0093905,"volume":4,"openInterest":838,"bid":43.25,"ask":43.55,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758743724,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00212500","strike":212.5,"currency":"USD","lastPrice":39.1,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":55,"bid":40.7,"ask":41.4,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758729069,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00215000","strike":215.0,"currency":"USD","lastPrice":38.45,"change":1.9300003,"percentChange":5.284776,"volume":1,"openInterest":691,"bid":38.15,"ask":38.45,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758742892,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00217500","strike":217.5,"currency":"USD","lastPrice":37.5,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":283,"bid":35.75,"ask":36.3,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758720693,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00220000","strike":220.0,"currency":"USD","lastPrice":33.2,"change":0.6500015,"percentChange":1.9969325,"volume":8,"openInterest":2347,"bid":33.25,"ask":33.5,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811532,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00222500","strike":222.5,"currency":"USD","lastPrice":30.64,"change":1.4599991,"percentChange":5.0034237,"volume":49,"openInterest":875,"bid":30.6,"ask":31.65,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758735824,"impliedVolatility":0.98437515625,"inTheMoney":true},{"contractSymbol":"AAPL250926C00225000","strike":225.0,"currency":"USD","lastPrice":28.25,"change":1.0699997,"percentChange":3.936717,"volume":20,"openInterest":1797,"bid":28.05,"ask":29.2,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758807438,"impliedVolatility":0.94726615234375,"inTheMoney":true},{"contractSymbol":"AAPL250926C00227500","strike":227.5,"currency":"USD","lastPrice":24.55,"change":-0.35000038,"percentChange":-1.405624,"volume":7,"openInterest":1890,"bid":25.6,"ask":25.9,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758808197,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00230000","strike":230.0,"currency":"USD","lastPrice":23.83,"change":1.4099998,"percentChange":6.2890267,"volume":50,"openInterest":10402,"bid":23.25,"ask":24.15,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758810995,"impliedVolatility":0.7753928710937501,"inTheMoney":true},{"contractSymbol":"AAPL250926C00232500","strike":232.5,"currency":"USD","lastPrice":21.36,"change":1.4099998,"percentChange":7.067668,"volume":75,"openInterest":1552,"bid":20.75,"ask":21.0,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758810984,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00235000","strike":235.0,"currency":"USD","lastPrice":18.45,"change":1.1500015,"percentChange":6.647408,"volume":141,"openInterest":3660,"bid":18.2,"ask":18.45,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811294,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00237500","strike":237.5,"currency":"USD","lastPrice":16.45,"change":1.4000006,"percentChange":9.302329,"volume":220,"openInterest":5656,"bid":15.65,"ask":15.9,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758810984,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00240000","strike":240.0,"currency":"USD","lastPrice":14.03,"change":1.75,"percentChange":14.250814,"volume":275,"openInterest":17006,"bid":13.05,"ask":13.35,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758810714,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00242500","strike":242.5,"currency":"USD","lastPrice":11.7,"change":1.75,"percentChange":17.58794,"volume":81,"openInterest":7331,"bid":10.6,"ask":10.85,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758810438,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00245000","strike":245.0,"currency":"USD","lastPrice":8.37,"change":0.8199997,"percentChange":10.860923,"volume":882,"openInterest":19947,"bid":8.35,"ask":8.65,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811460,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00247500","strike":247.5,"currency":"USD","lastPrice":5.99,"change":0.6899996,"percentChange":13.018859,"volume":1177,"openInterest":5964,"bid":6.0,"ask":6.25,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811512,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL250926C00250000","strike":250.0,"currency":"USD","lastPrice":3.8,"change":0.5,"percentChange":15.151515,"volume":4165,"openInterest":14138,"bid":3.85,"ask":3.95,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811512,"impliedVolatility":0.12207909179687501,"inTheMoney":true},{"contractSymbol":"AAPL250926C00252500","strike":252.5,"currency":"USD","lastPrice":2.12,"change":0.28999984,"percentChange":16.292126,"volume":17583,"openInterest":15351,"bid":2.05,"ask":2.09,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811701,"impliedVolatility":0.1687094848632813,"inTheMoney":true},{"contractSymbol":"AAPL250926C00255000","strike":255.0,"currency":"USD","lastPrice":0.92,"change":0.050000012,"percentChange":5.7471275,"volume":43456,"openInterest":32482,"bid":0.9,"ask":0.92,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811687,"impliedVolatility":0.1860432958984375,"inTheMoney":false},{"contractSymbol":"AAPL250926C00257500","strike":257.5,"currency":"USD","lastPrice":0.33,"change":-0.059999973,"percentChange":-15.384608,"volume":30030,"openInterest":26024,"bid":0.34,"ask":0.35,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811654,"impliedVolatility":0.2004474487304687,"inTheMoney":false},{"contractSymbol":"AAPL250926C00260000","strike":260.0,"currency":"USD","lastPrice":0.13,"change":-0.050000012,"percentChange":-27.777782,"volume":19773,"openInterest":51322,"bid":0.12,"ask":0.13,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811610,"impliedVolatility":0.21778125976562498,"inTheMoney":false},{"contractSymbol":"AAPL250926C00262500","strike":262.5,"currency":"USD","lastPrice":0.05,"change":-0.040000003,"percentChange":-44.444447,"volume":4737,"openInterest":19269,"bid":0.05,"ask":0.06,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811521,"impliedVolatility":0.24414818359375,"inTheMoney":false},{"contractSymbol":"AAPL250926C00265000","strike":265.0,"currency":"USD","lastPrice":0.02,"change":-0.04,"percentChange":-66.66667,"volume":1986,"openInterest":29003,"bid":0.02,"ask":0.03,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811525,"impliedVolatility":0.2695385546875,"inTheMoney":false},{"contractSymbol":"AAPL250926C00270000","strike":270.0,"currency":"USD","lastPrice":0.01,"change":-0.02,"percentChange":-66.66667,"volume":2866,"openInterest":14534,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811437,"impliedVolatility":0.35156898437499995,"inTheMoney":false},{"contractSymbol":"AAPL250926C00275000","strike":275.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":889,"openInterest":6609,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758809429,"impliedVolatility":0.4062559375,"inTheMoney":false},{"contractSymbol":"AAPL250926C00280000","strike":280.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":34,"openInterest":6203,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758808480,"impliedVolatility":0.48438015625,"inTheMoney":false},{"contractSymbol":"AAPL250926C00285000","strike":285.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":2606,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758742230,"impliedVolatility":0.5312546875,"inTheMoney":false},{"contractSymbol":"AAPL250926C00290000","strike":290.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":403,"openInterest":4724,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758739913,"impliedVolatility":0.5937540625000001,"inTheMoney":false},{"contractSymbol":"AAPL250926C00295000","strike":295.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":2557,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758807001,"impliedVolatility":0.6718782812500002,"inTheMoney":false},{"contractSymbol":"AAPL250926C00300000","strike":300.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":50,"openInterest":3103,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758656566,"impliedVolatility":0.7500025,"inTheMoney":false},{"contractSymbol":"AAPL250926C00305000","strike":305.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":64,"openInterest":2984,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758634407,"impliedVolatility":0.8125018749999999,"inTheMoney":false},{"contractSymbol":"AAPL250926C00310000","strike":310.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":25,"openInterest":61,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758634248,"impliedVolatility":0.87500125,"inTheMoney":false},{"contractSymbol":"AAPL250926C00315000","strike":315.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":65,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758649551,"impliedVolatility":0.937500625,"inTheMoney":false},{"contractSymbol":"AAPL250926C00320000","strike":320.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":57,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758720604,"impliedVolatility":0.98437515625,"inTheMoney":false},{"contractSymbol":"AAPL250926C00325000","strike":325.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":7,"openInterest":138,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758807002,"impliedVolatility":1.0625046875000002,"inTheMoney":false}],"puts":[{"contractSymbol":"AAPL250926P00110000","strike":110.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":10,"openInterest":42,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1756237149,"impliedVolatility":3.3750015624999996,"inTheMoney":false},{"contractSymbol":"AAPL250926P00125000","strike":125.0,"currency":"USD","lastPrice":0.04,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":1483,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1755618013,"impliedVolatility":2.8750028125,"inTheMoney":false},{"contractSymbol":"AAPL250926P00130000","strike":130.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":188,"openInterest":209,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1756927783,"impliedVolatility":2.750003125,"inTheMoney":false},{"contractSymbol":"AAPL250926P00135000","strike":135.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":40,"openInterest":186,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1757093039,"impliedVolatility":2.6250034375,"inTheMoney":false},{"contractSymbol":"AAPL250926P00140000","strike":140.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":186,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758728941,"impliedVolatility":2.43750390625,"inTheMoney":false},{"contractSymbol":"AAPL250926P00145000","strike":145.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":11,"openInterest":112,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758634203,"impliedVolatility":2.3125042187499996,"inTheMoney":false},{"contractSymbol":"AAPL250926P00150000","strike":150.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":53,"openInterest":65,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1757532337,"impliedVolatility":2.1875045312499997,"inTheMoney":false},{"contractSymbol":"AAPL250926P00155000","strike":155.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":100,"openInterest":113,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1757697017,"impliedVolatility":2.0625048437499998,"inTheMoney":false},{"contractSymbol":"AAPL250926P00160000","strike":160.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":58,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1757692622,"impliedVolatility":1.9375003125,"inTheMoney":false},{"contractSymbol":"AAPL250926P00165000","strike":165.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":101,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1757691238,"impliedVolatility":1.8125009374999999,"inTheMoney":false},{"contractSymbol":"AAPL250926P00170000","strike":170.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":920,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758651924,"impliedVolatility":1.6875015625,"inTheMoney":false},{"contractSymbol":"AAPL250926P00175000","strike":175.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":156,"openInterest":435,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758292196,"impliedVolatility":1.5625021874999998,"inTheMoney":false},{"contractSymbol":"AAPL250926P00180000","strike":180.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":10,"openInterest":696,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758642024,"impliedVolatility":1.46875265625,"inTheMoney":false},{"contractSymbol":"AAPL250926P00185000","strike":185.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":7,"openInterest":258,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758634209,"impliedVolatility":1.3750031249999999,"inTheMoney":false},{"contractSymbol":"AAPL250926P00190000","strike":190.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":567,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758735302,"impliedVolatility":1.2500037499999999,"inTheMoney":false},{"contractSymbol":"AAPL250926P00195000","strike":195.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":885,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758634203,"impliedVolatility":1.15625421875,"inTheMoney":false},{"contractSymbol":"AAPL250926P00200000","strike":200.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":57,"openInterest":1988,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758730416,"impliedVolatility":1.0625046875000002,"inTheMoney":false},{"contractSymbol":"AAPL250926P00202500","strike":202.5,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":3161,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758639602,"impliedVolatility":0.98437515625,"inTheMoney":false},{"contractSymbol":"AAPL250926P00205000","strike":205.0,"currency":"USD","lastPrice":0.02,"change":0.01,"percentChange":0.0,"volume":11,"openInterest":2001,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758729052,"impliedVolatility":0.937500625,"inTheMoney":false},{"contractSymbol":"AAPL250926P00207500","strike":207.5,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":250,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758721741,"impliedVolatility":0.9062509375,"inTheMoney":false},{"contractSymbol":"AAPL250926P00210000","strike":210.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":14,"openInterest":2487,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811222,"impliedVolatility":0.8437515624999999,"inTheMoney":false},{"contractSymbol":"AAPL250926P00212500","strike":212.5,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":20,"openInterest":471,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758740828,"impliedVolatility":0.79687703125,"inTheMoney":false},{"contractSymbol":"AAPL250926P00215000","strike":215.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":28,"openInterest":3052,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758739716,"impliedVolatility":0.7500025,"inTheMoney":false},{"contractSymbol":"AAPL250926P00217500","strike":217.5,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":2015,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758807587,"impliedVolatility":0.70312796875,"inTheMoney":false},{"contractSymbol":"AAPL250926P00220000","strike":220.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":8,"openInterest":4729,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758809579,"impliedVolatility":0.6562534375000001,"inTheMoney":false},{"contractSymbol":"AAPL250926P00222500","strike":222.5,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":32,"openInterest":4588,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758742265,"impliedVolatility":0.6718782812500002,"inTheMoney":false},{"contractSymbol":"AAPL250926P00225000","strike":225.0,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-50.0,"volume":433,"openInterest":5548,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758809546,"impliedVolatility":0.5625043750000001,"inTheMoney":false},{"contractSymbol":"AAPL250926P00227500","strike":227.5,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":182,"openInterest":7712,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811313,"impliedVolatility":0.570316796875,"inTheMoney":false},{"contractSymbol":"AAPL250926P00230000","strike":230.0,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-50.0,"volume":153,"openInterest":7284,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811228,"impliedVolatility":0.523442265625,"inTheMoney":false},{"contractSymbol":"AAPL250926P00232500","strike":232.5,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-33.333336,"volume":154,"openInterest":4573,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811322,"impliedVolatility":0.48438015625,"inTheMoney":false},{"contractSymbol":"AAPL250926P00235000","strike":235.0,"currency":"USD","lastPrice":0.02,"change":-0.01,"percentChange":-25.0,"volume":800,"openInterest":9475,"bid":0.02,"ask":0.03,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811509,"impliedVolatility":0.45313046875,"inTheMoney":false},{"contractSymbol":"AAPL250926P00237500","strike":237.5,"currency":"USD","lastPrice":0.02,"change":-0.02,"percentChange":-50.0,"volume":389,"openInterest":9287,"bid":0.02,"ask":0.03,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811121,"impliedVolatility":0.398443515625,"inTheMoney":false},{"contractSymbol":"AAPL250926P00240000","strike":240.0,"currency":"USD","lastPrice":0.03,"change":-0.020000001,"percentChange":-40.000004,"volume":1689,"openInterest":12188,"bid":0.03,"ask":0.04,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811319,"impliedVolatility":0.35938140625,"inTheMoney":false},{"contractSymbol":"AAPL250926P00242500","strike":242.5,"currency":"USD","lastPrice":0.04,"change":-0.050000004,"percentChange":-55.555557,"volume":1195,"openInterest":12084,"bid":0.04,"ask":0.05,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811675,"impliedVolatility":0.31250687499999996,"inTheMoney":false},{"contractSymbol":"AAPL250926P00245000","strike":245.0,"currency":"USD","lastPrice":0.07,"change":-0.12,"percentChange":-60.000004,"volume":6534,"openInterest":15566,"bid":0.07,"ask":0.08,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811747,"impliedVolatility":0.274421318359375,"inTheMoney":false},{"contractSymbol":"AAPL250926P00247500","strike":247.5,"currency":"USD","lastPrice":0.16,"change":-0.27,"percentChange":-60.0,"volume":8059,"openInterest":11008,"bid":0.16,"ask":0.17,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811705,"impliedVolatility":0.2475661181640625,"inTheMoney":false},{"contractSymbol":"AAPL250926P00250000","strike":250.0,"currency":"USD","lastPrice":0.46,"change":-0.51,"percentChange":-52.577316,"volume":24279,"openInterest":15167,"bid":0.44,"ask":0.46,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811684,"impliedVolatility":0.23780059082031246,"inTheMoney":false},{"contractSymbol":"AAPL250926P00252500","strike":252.5,"currency":"USD","lastPrice":1.18,"change":-0.81000006,"percentChange":-40.70352,"volume":18707,"openInterest":9668,"bid":1.16,"ask":1.18,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811675,"impliedVolatility":0.24048611083984373,"inTheMoney":false},{"contractSymbol":"AAPL250926P00255000","strike":255.0,"currency":"USD","lastPrice":2.56,"change":-0.94000006,"percentChange":-26.857145,"volume":4769,"openInterest":5004,"bid":2.46,"ask":2.5,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811669,"impliedVolatility":0.2536695727539063,"inTheMoney":true},{"contractSymbol":"AAPL250926P00257500","strike":257.5,"currency":"USD","lastPrice":4.5,"change":-1.0,"percentChange":-20.833332,"volume":473,"openInterest":1858,"bid":4.5,"ask":4.6,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811671,"impliedVolatility":0.31641308593749995,"inTheMoney":true},{"contractSymbol":"AAPL250926P00260000","strike":260.0,"currency":"USD","lastPrice":6.83,"change":-1.0,"percentChange":-12.771392,"volume":89,"openInterest":2157,"bid":6.65,"ask":6.85,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811364,"impliedVolatility":0.3715883154296875,"inTheMoney":true},{"contractSymbol":"AAPL250926P00262500","strike":262.5,"currency":"USD","lastPrice":9.15,"change":-1.6500006,"percentChange":-15.277783,"volume":8,"openInterest":324,"bid":9.3,"ask":9.55,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758811436,"impliedVolatility":0.4997608618164063,"inTheMoney":true},{"contractSymbol":"AAPL250926P00265000","strike":265.0,"currency":"USD","lastPrice":11.1,"change":-2.5499992,"percentChange":-18.681314,"volume":35,"openInterest":95,"bid":11.35,"ask":11.9,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758810334,"impliedVolatility":0.5524947094726562,"inTheMoney":true},{"contractSymbol":"AAPL250926P00270000","strike":270.0,"currency":"USD","lastPrice":16.79,"change":-1.5599995,"percentChange":-8.501359,"volume":1,"openInterest":20,"bid":16.6,"ask":17.0,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758740295,"impliedVolatility":0.6806672558593752,"inTheMoney":true},{"contractSymbol":"AAPL250926P00275000","strike":275.0,"currency":"USD","lastPrice":22.05,"change":0.0,"percentChange":0.0,"volume":28,"openInterest":4,"bid":21.5,"ask":22.0,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758723341,"impliedVolatility":0.8051777294921875,"inTheMoney":true},{"contractSymbol":"AAPL250926P00280000","strike":280.0,"currency":"USD","lastPrice":27.81,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":1,"bid":26.35,"ask":27.1,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758722910,"impliedVolatility":0.92773509765625,"inTheMoney":true},{"contractSymbol":"AAPL250926P00290000","strike":290.0,"currency":"USD","lastPrice":34.11,"change":0.0,"percentChange":0.0,"volume":4,"openInterest":2,"bid":36.3,"ask":37.25,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1758569643,"impliedVolatility":1.189457177734375,"inTheMoney":true},{"contractSymbol":"AAPL250926P00295000","strike":295.0,"currency":"USD","lastPrice":65.0,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":41.2,"ask":42.3,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1757620200,"impliedVolatility":1.2939488427734374,"inTheMoney":true},{"contractSymbol":"AAPL250926P00300000","strike":300.0,"currency":"USD","lastPrice":65.23,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":46.35,"ask":47.25,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1757703970,"impliedVolatility":1.4267606787109375,"inTheMoney":true},{"contractSymbol":"AAPL250926P00305000","strike":305.0,"currency":"USD","lastPrice":74.9,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":0,"bid":51.2,"ask":52.3,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1757620200,"impliedVolatility":1.51172119140625,"inTheMoney":true},{"contractSymbol":"AAPL250926P00310000","strike":310.0,"currency":"USD","lastPrice":79.95,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":56.1,"ask":57.15,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1757620200,"impliedVolatility":1.5507834960937497,"inTheMoney":true},{"contractSymbol":"AAPL250926P00315000","strike":315.0,"currency":"USD","lastPrice":87.6,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":61.2,"ask":62.3,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1756151639,"impliedVolatility":1.7158217333984376,"inTheMoney":true},{"contractSymbol":"AAPL250926P00320000","strike":320.0,"currency":"USD","lastPrice":88.75,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":66.35,"ask":67.25,"contractSize":"REGULAR","expiration":1758844800,"lastTradeDate":1755287400,"impliedVolatility":1.8398445507812498,"inTheMoney":true}]}]}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1759449600.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1759449600.json new file mode 100644 index 0000000..0e08a52 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1759449600.json @@ -0,0 +1 @@ +{"optionChain":{"result":[{"underlyingSymbol":"AAPL","expirationDates":[1759449600,1760054400,1760659200,1761264000,1761868800,1762473600,1763683200,1766102400,1768521600,1771545600,1773964800,1776384000,1778803200,1781740800,1787270400,1789689600,1797552000,1799971200,1813190400,1829001600,1832025600],"strikes":[110.0,120.0,125.0,130.0,135.0,140.0,145.0,150.0,155.0,160.0,165.0,170.0,175.0,180.0,185.0,190.0,195.0,200.0,205.0,210.0,212.5,215.0,217.5,220.0,222.5,225.0,227.5,230.0,232.5,235.0,237.5,240.0,242.5,245.0,247.5,250.0,252.5,255.0,257.5,260.0,262.5,265.0,267.5,270.0,272.5,275.0,277.5,280.0,282.5,285.0,287.5,290.0,292.5,295.0,300.0,305.0,310.0,315.0,320.0,325.0],"hasMiniOptions":false,"quote":{"language":"en-US","region":"US","quoteType":"EQUITY","typeDisp":"Equity","quoteSourceName":"Nasdaq Real Time Price","triggerable":true,"customPriceAlertConfidence":"HIGH","currency":"USD","marketState":"REGULAR","exchange":"NMS","messageBoardId":"finmb_24937","exchangeTimezoneName":"America/New_York","exchangeTimezoneShortName":"EDT","gmtOffSetMilliseconds":-14400000,"market":"us_market","esgPopulated":false,"shortName":"Apple Inc.","longName":"Apple Inc.","corporateActions":[],"regularMarketTime":1759414855,"regularMarketChange":1.1100006,"regularMarketDayHigh":257.3,"regularMarketDayRange":"254.15 - 257.3","regularMarketDayLow":254.15,"regularMarketVolume":10896303,"regularMarketPreviousClose":255.45,"bid":256.42,"ask":256.66,"bidSize":1,"askSize":1,"fullExchangeName":"NasdaqGS","financialCurrency":"USD","regularMarketOpen":256.59,"averageDailyVolume3Month":55007147,"averageDailyVolume10Day":64382750,"fiftyTwoWeekLowChange":87.34999,"fiftyTwoWeekLowChangePercent":0.51622236,"fiftyTwoWeekRange":"169.21 - 260.1","fiftyTwoWeekHighChange":-3.5400085,"fiftyTwoWeekHighChangePercent":-0.013610182,"fiftyTwoWeekLow":169.21,"fiftyTwoWeekHigh":260.1,"fiftyTwoWeekChangePercent":13.196266,"dividendDate":1755129600,"earningsTimestamp":1753992000,"earningsTimestampStart":1761854400,"earningsTimestampEnd":1761854400,"earningsCallTimestampStart":1753995600,"earningsCallTimestampEnd":1753995600,"isEarningsDateEstimate":true,"trailingAnnualDividendRate":1.01,"trailingPE":38.755287,"dividendRate":1.04,"trailingAnnualDividendYield":0.003953807,"dividendYield":0.41,"epsTrailingTwelveMonths":6.62,"epsForward":8.31,"epsCurrentYear":7.38095,"priceEpsCurrentYear":34.759754,"sharesOutstanding":14840390000,"bookValue":4.431,"fiftyDayAverage":230.9796,"fiftyDayAverageChange":25.580399,"fiftyDayAverageChangePercent":0.110747434,"twoHundredDayAverage":222.0911,"twoHundredDayAverageChange":34.468903,"twoHundredDayAverageChangePercent":0.15520164,"marketCap":3807450234880,"forwardPE":30.873644,"priceToBook":57.901146,"sourceInterval":15,"exchangeDataDelayedBy":0,"averageAnalystRating":"2.0 - Buy","hasPrePostMarketData":true,"firstTradeDateMilliseconds":345479400000,"priceHint":2,"tradeable":false,"cryptoTradeable":false,"regularMarketChangePercent":0.43452755,"regularMarketPrice":256.56,"displayName":"Apple","symbol":"AAPL"},"options":[{"expirationDate":1759449600,"hasMiniOptions":false,"calls":[{"contractSymbol":"AAPL251003C00110000","strike":110.0,"currency":"USD","lastPrice":146.72,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":2,"bid":144.25,"ask":145.8,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759345539,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00125000","strike":125.0,"currency":"USD","lastPrice":131.34,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":3,"bid":129.4,"ask":130.95,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1758909839,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00135000","strike":135.0,"currency":"USD","lastPrice":121.2,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":1,"bid":119.3,"ask":120.95,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759336108,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00145000","strike":145.0,"currency":"USD","lastPrice":83.19,"change":0.0,"percentChange":0.0,"openInterest":10,"bid":109.7,"ask":111.15,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1756225298,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00150000","strike":150.0,"currency":"USD","lastPrice":96.05,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":2,"bid":104.35,"ask":105.8,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1758311400,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00155000","strike":155.0,"currency":"USD","lastPrice":82.8,"change":0.0,"percentChange":0.0,"openInterest":4,"bid":99.4,"ask":100.95,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1758218586,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00160000","strike":160.0,"currency":"USD","lastPrice":96.77,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":6,"bid":94.4,"ask":95.9,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759345539,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00165000","strike":165.0,"currency":"USD","lastPrice":91.4,"change":0.0,"percentChange":0.0,"openInterest":1,"bid":89.4,"ask":90.9,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1758556889,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00170000","strike":170.0,"currency":"USD","lastPrice":85.07,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":5,"bid":84.4,"ask":85.9,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1758903402,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00175000","strike":175.0,"currency":"USD","lastPrice":78.45,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":4,"bid":79.25,"ask":80.85,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759257353,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00180000","strike":180.0,"currency":"USD","lastPrice":74.74,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":24,"bid":74.45,"ask":75.9,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759250712,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00185000","strike":185.0,"currency":"USD","lastPrice":69.53,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":14,"bid":69.35,"ask":70.85,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759250842,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00190000","strike":190.0,"currency":"USD","lastPrice":63.7,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":115,"bid":64.3,"ask":65.8,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759173411,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00195000","strike":195.0,"currency":"USD","lastPrice":60.7,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":16,"bid":59.35,"ask":60.9,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759348769,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00200000","strike":200.0,"currency":"USD","lastPrice":56.77,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":108,"bid":54.4,"ask":55.95,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759345539,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00205000","strike":205.0,"currency":"USD","lastPrice":52.03,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":128,"bid":49.35,"ask":50.85,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759345815,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00210000","strike":210.0,"currency":"USD","lastPrice":44.47,"change":-1.1800003,"percentChange":-2.5848856,"volume":4,"openInterest":542,"bid":44.3,"ask":45.8,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412480,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00212500","strike":212.5,"currency":"USD","lastPrice":42.95,"change":0.0,"percentChange":0.0,"volume":4,"openInterest":73,"bid":41.85,"ask":43.45,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759331751,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00215000","strike":215.0,"currency":"USD","lastPrice":39.9,"change":-1.9699974,"percentChange":-4.7050333,"volume":2,"openInterest":531,"bid":39.65,"ask":40.75,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412704,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00217500","strike":217.5,"currency":"USD","lastPrice":38.92,"change":0.0,"percentChange":0.0,"volume":25,"openInterest":260,"bid":37.15,"ask":38.5,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759344333,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00220000","strike":220.0,"currency":"USD","lastPrice":35.0,"change":-0.6500015,"percentChange":-1.823286,"volume":5,"openInterest":1785,"bid":34.9,"ask":35.5,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412742,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00222500","strike":222.5,"currency":"USD","lastPrice":32.9,"change":-0.25,"percentChange":-0.75414777,"volume":15,"openInterest":653,"bid":32.15,"ask":33.6,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412206,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00225000","strike":225.0,"currency":"USD","lastPrice":30.14,"change":-0.6100006,"percentChange":-1.9837419,"volume":24,"openInterest":1793,"bid":29.4,"ask":30.5,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412694,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00227500","strike":227.5,"currency":"USD","lastPrice":27.85,"change":-0.30999947,"percentChange":-1.1008503,"volume":2,"openInterest":1189,"bid":27.35,"ask":28.25,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412202,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00230000","strike":230.0,"currency":"USD","lastPrice":24.71,"change":-1.0400009,"percentChange":-4.0388384,"volume":1,"openInterest":4791,"bid":24.85,"ask":25.7,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412373,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00232500","strike":232.5,"currency":"USD","lastPrice":24.75,"change":1.1399994,"percentChange":4.8284597,"volume":6,"openInterest":492,"bid":22.25,"ask":23.4,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759411801,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00235000","strike":235.0,"currency":"USD","lastPrice":19.38,"change":-1.3700008,"percentChange":-6.602414,"volume":15,"openInterest":13848,"bid":19.5,"ask":20.4,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412409,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00237500","strike":237.5,"currency":"USD","lastPrice":19.18,"change":0.47999954,"percentChange":2.5668423,"volume":2,"openInterest":1121,"bid":17.5,"ask":18.45,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759411801,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00240000","strike":240.0,"currency":"USD","lastPrice":15.28,"change":-0.6700001,"percentChange":-4.2006273,"volume":7,"openInterest":7308,"bid":14.7,"ask":15.35,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412943,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00242500","strike":242.5,"currency":"USD","lastPrice":12.75,"change":-0.40999985,"percentChange":-3.1155005,"volume":11,"openInterest":1675,"bid":12.55,"ask":13.55,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412893,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00245000","strike":245.0,"currency":"USD","lastPrice":9.58,"change":-1.0699997,"percentChange":-10.046946,"volume":44,"openInterest":8030,"bid":10.2,"ask":10.4,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412404,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00247500","strike":247.5,"currency":"USD","lastPrice":7.43,"change":-0.87000036,"percentChange":-10.481932,"volume":113,"openInterest":3720,"bid":7.65,"ask":7.75,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412556,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00250000","strike":250.0,"currency":"USD","lastPrice":5.5,"change":-0.5,"percentChange":-8.333333,"volume":1059,"openInterest":16330,"bid":5.5,"ask":5.65,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412752,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00252500","strike":252.5,"currency":"USD","lastPrice":3.3,"change":-0.60000014,"percentChange":-15.384619,"volume":978,"openInterest":6451,"bid":3.4,"ask":3.5,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759413022,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251003C00255000","strike":255.0,"currency":"USD","lastPrice":1.82,"change":-0.42999995,"percentChange":-19.111109,"volume":10381,"openInterest":20299,"bid":1.8,"ask":1.82,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759413049,"impliedVolatility":0.10743080078125,"inTheMoney":true},{"contractSymbol":"AAPL251003C00257500","strike":257.5,"currency":"USD","lastPrice":0.8,"change":-0.31,"percentChange":-27.927927,"volume":12622,"openInterest":17772,"bid":0.77,"ask":0.78,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759413008,"impliedVolatility":0.15699085205078128,"inTheMoney":false},{"contractSymbol":"AAPL251003C00260000","strike":260.0,"currency":"USD","lastPrice":0.32,"change":-0.17000002,"percentChange":-34.693882,"volume":18087,"openInterest":58306,"bid":0.31,"ask":0.32,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759413048,"impliedVolatility":0.1870198486328125,"inTheMoney":false},{"contractSymbol":"AAPL251003C00262500","strike":262.5,"currency":"USD","lastPrice":0.13,"change":-0.09,"percentChange":-40.909092,"volume":4130,"openInterest":21716,"bid":0.13,"ask":0.14,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759413002,"impliedVolatility":0.2153398779296875,"inTheMoney":false},{"contractSymbol":"AAPL251003C00265000","strike":265.0,"currency":"USD","lastPrice":0.07,"change":-0.04,"percentChange":-36.363636,"volume":2232,"openInterest":25350,"bid":0.06,"ask":0.07,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412840,"impliedVolatility":0.245124736328125,"inTheMoney":false},{"contractSymbol":"AAPL251003C00267500","strike":267.5,"currency":"USD","lastPrice":0.03,"change":-0.04,"percentChange":-57.142857,"volume":413,"openInterest":5465,"bid":0.03,"ask":0.04,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412689,"impliedVolatility":0.27539787109374997,"inTheMoney":false},{"contractSymbol":"AAPL251003C00270000","strike":270.0,"currency":"USD","lastPrice":0.03,"change":-0.01,"percentChange":-25.0,"volume":1054,"openInterest":19957,"bid":0.02,"ask":0.03,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759413009,"impliedVolatility":0.31250687499999996,"inTheMoney":false},{"contractSymbol":"AAPL251003C00272500","strike":272.5,"currency":"USD","lastPrice":0.01,"change":-0.02,"percentChange":-66.66667,"volume":86,"openInterest":4824,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412651,"impliedVolatility":0.3437565625,"inTheMoney":false},{"contractSymbol":"AAPL251003C00275000","strike":275.0,"currency":"USD","lastPrice":0.01,"change":-0.02,"percentChange":-66.66667,"volume":125,"openInterest":6052,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412152,"impliedVolatility":0.39063109375,"inTheMoney":false},{"contractSymbol":"AAPL251003C00277500","strike":277.5,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-50.0,"volume":179,"openInterest":3653,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412639,"impliedVolatility":0.398443515625,"inTheMoney":false},{"contractSymbol":"AAPL251003C00280000","strike":280.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":6746,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412884,"impliedVolatility":0.437505625,"inTheMoney":false},{"contractSymbol":"AAPL251003C00282500","strike":282.5,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":120,"openInterest":1554,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759343090,"impliedVolatility":0.48438015625,"inTheMoney":false},{"contractSymbol":"AAPL251003C00285000","strike":285.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":103,"openInterest":1799,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759340329,"impliedVolatility":0.51562984375,"inTheMoney":false},{"contractSymbol":"AAPL251003C00287500","strike":287.5,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":53,"openInterest":572,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759327365,"impliedVolatility":0.5312546875,"inTheMoney":false},{"contractSymbol":"AAPL251003C00290000","strike":290.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":1678,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759326043,"impliedVolatility":0.5625043750000001,"inTheMoney":false},{"contractSymbol":"AAPL251003C00292500","strike":292.5,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":902,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759161684,"impliedVolatility":0.5937540625000001,"inTheMoney":false},{"contractSymbol":"AAPL251003C00295000","strike":295.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":610,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759161113,"impliedVolatility":0.6250037500000001,"inTheMoney":false},{"contractSymbol":"AAPL251003C00300000","strike":300.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":30,"openInterest":1561,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759341410,"impliedVolatility":0.6875031250000001,"inTheMoney":false},{"contractSymbol":"AAPL251003C00305000","strike":305.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":273,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1758899757,"impliedVolatility":0.7656273437500001,"inTheMoney":false},{"contractSymbol":"AAPL251003C00310000","strike":310.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":382,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759346505,"impliedVolatility":0.8281267187499999,"inTheMoney":false},{"contractSymbol":"AAPL251003C00315000","strike":315.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":85,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759152602,"impliedVolatility":0.9062509375,"inTheMoney":false},{"contractSymbol":"AAPL251003C00320000","strike":320.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":4,"openInterest":140,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1758732347,"impliedVolatility":0.9687503125,"inTheMoney":false},{"contractSymbol":"AAPL251003C00325000","strike":325.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":127,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759344174,"impliedVolatility":1.000005,"inTheMoney":false}],"puts":[{"contractSymbol":"AAPL251003P00110000","strike":110.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":10,"openInterest":62,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759249355,"impliedVolatility":3.43750140625,"inTheMoney":false},{"contractSymbol":"AAPL251003P00120000","strike":120.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":29,"openInterest":134,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1757516218,"impliedVolatility":3.1250021875,"inTheMoney":false},{"contractSymbol":"AAPL251003P00125000","strike":125.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"openInterest":2,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1756831717,"impliedVolatility":2.93750265625,"inTheMoney":false},{"contractSymbol":"AAPL251003P00130000","strike":130.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":567,"openInterest":622,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1758118168,"impliedVolatility":2.750003125,"inTheMoney":false},{"contractSymbol":"AAPL251003P00135000","strike":135.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":20,"openInterest":279,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759331590,"impliedVolatility":2.6250034375,"inTheMoney":false},{"contractSymbol":"AAPL251003P00140000","strike":140.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"openInterest":100,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1757700252,"impliedVolatility":2.50000375,"inTheMoney":false},{"contractSymbol":"AAPL251003P00145000","strike":145.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"openInterest":16,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1756918504,"impliedVolatility":2.3750040624999995,"inTheMoney":false},{"contractSymbol":"AAPL251003P00150000","strike":150.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":18,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1757691257,"impliedVolatility":2.2500043749999996,"inTheMoney":false},{"contractSymbol":"AAPL251003P00155000","strike":155.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":6,"openInterest":18,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1758211061,"impliedVolatility":2.1250046874999997,"inTheMoney":false},{"contractSymbol":"AAPL251003P00160000","strike":160.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":50,"openInterest":149,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1758205136,"impliedVolatility":1.96875015625,"inTheMoney":false},{"contractSymbol":"AAPL251003P00165000","strike":165.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":169,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759326023,"impliedVolatility":1.875000625,"inTheMoney":false},{"contractSymbol":"AAPL251003P00170000","strike":170.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":324,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1758817530,"impliedVolatility":1.75000125,"inTheMoney":false},{"contractSymbol":"AAPL251003P00175000","strike":175.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":19,"openInterest":140,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759152616,"impliedVolatility":1.625001875,"inTheMoney":false},{"contractSymbol":"AAPL251003P00180000","strike":180.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":756,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759254300,"impliedVolatility":1.5000025,"inTheMoney":false},{"contractSymbol":"AAPL251003P00185000","strike":185.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":323,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759325401,"impliedVolatility":1.40625296875,"inTheMoney":false},{"contractSymbol":"AAPL251003P00190000","strike":190.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":737,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1758905248,"impliedVolatility":1.3125034374999998,"inTheMoney":false},{"contractSymbol":"AAPL251003P00195000","strike":195.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":1144,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759254886,"impliedVolatility":1.1875040625,"inTheMoney":false},{"contractSymbol":"AAPL251003P00200000","strike":200.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":8048,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759326087,"impliedVolatility":1.09375453125,"inTheMoney":false},{"contractSymbol":"AAPL251003P00205000","strike":205.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":1183,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759327622,"impliedVolatility":0.98437515625,"inTheMoney":false},{"contractSymbol":"AAPL251003P00210000","strike":210.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":17,"openInterest":3233,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759345756,"impliedVolatility":0.87500125,"inTheMoney":false},{"contractSymbol":"AAPL251003P00212500","strike":212.5,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":569,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759254899,"impliedVolatility":0.8437515624999999,"inTheMoney":false},{"contractSymbol":"AAPL251003P00215000","strike":215.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":10,"openInterest":2346,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412495,"impliedVolatility":0.7812521875,"inTheMoney":false},{"contractSymbol":"AAPL251003P00217500","strike":217.5,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":784,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759343511,"impliedVolatility":0.7500025,"inTheMoney":false},{"contractSymbol":"AAPL251003P00220000","strike":220.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":426,"openInterest":3217,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759348628,"impliedVolatility":0.6875031250000001,"inTheMoney":false},{"contractSymbol":"AAPL251003P00222500","strike":222.5,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":104,"openInterest":354,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759342952,"impliedVolatility":0.7187528125,"inTheMoney":false},{"contractSymbol":"AAPL251003P00225000","strike":225.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":14,"openInterest":3201,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412145,"impliedVolatility":0.6718782812500002,"inTheMoney":false},{"contractSymbol":"AAPL251003P00227500","strike":227.5,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":74,"openInterest":1099,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759336032,"impliedVolatility":0.6171913281250001,"inTheMoney":false},{"contractSymbol":"AAPL251003P00230000","strike":230.0,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-50.0,"volume":15,"openInterest":3756,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412692,"impliedVolatility":0.570316796875,"inTheMoney":false},{"contractSymbol":"AAPL251003P00232500","strike":232.5,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":4,"openInterest":1894,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412632,"impliedVolatility":0.51562984375,"inTheMoney":false},{"contractSymbol":"AAPL251003P00235000","strike":235.0,"currency":"USD","lastPrice":0.02,"change":0.01,"percentChange":0.0,"volume":10,"openInterest":4897,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412360,"impliedVolatility":0.48438015625,"inTheMoney":false},{"contractSymbol":"AAPL251003P00237500","strike":237.5,"currency":"USD","lastPrice":0.03,"change":0.02,"percentChange":200.0,"volume":2,"openInterest":2703,"bid":0.02,"ask":0.03,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412146,"impliedVolatility":0.45313046875,"inTheMoney":false},{"contractSymbol":"AAPL251003P00240000","strike":240.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":30,"openInterest":11811,"bid":0.02,"ask":0.03,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412916,"impliedVolatility":0.398443515625,"inTheMoney":false},{"contractSymbol":"AAPL251003P00242500","strike":242.5,"currency":"USD","lastPrice":0.03,"change":-0.01,"percentChange":-20.000002,"volume":80,"openInterest":5537,"bid":0.03,"ask":0.04,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412902,"impliedVolatility":0.35938140625,"inTheMoney":false},{"contractSymbol":"AAPL251003P00245000","strike":245.0,"currency":"USD","lastPrice":0.06,"change":-0.010000002,"percentChange":-20.000002,"volume":890,"openInterest":7831,"bid":0.05,"ask":0.06,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412871,"impliedVolatility":0.32227240234374993,"inTheMoney":false},{"contractSymbol":"AAPL251003P00247500","strike":247.5,"currency":"USD","lastPrice":0.09,"change":-0.06999999,"percentChange":-43.749996,"volume":3183,"openInterest":10116,"bid":0.1,"ask":0.11,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412984,"impliedVolatility":0.2929758203124999,"inTheMoney":false},{"contractSymbol":"AAPL251003P00250000","strike":250.0,"currency":"USD","lastPrice":0.26,"change":-0.110000014,"percentChange":-29.729734,"volume":3670,"openInterest":13221,"bid":0.28,"ask":0.3,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759413004,"impliedVolatility":0.28955788574218744,"inTheMoney":false},{"contractSymbol":"AAPL251003P00252500","strike":252.5,"currency":"USD","lastPrice":0.68,"change":-0.17000002,"percentChange":-19.101126,"volume":7092,"openInterest":6989,"bid":0.68,"ask":0.7,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759413043,"impliedVolatility":0.28565167480468745,"inTheMoney":false},{"contractSymbol":"AAPL251003P00255000","strike":255.0,"currency":"USD","lastPrice":1.68,"change":-0.0200001,"percentChange":-1.1764765,"volume":7145,"openInterest":11691,"bid":1.66,"ask":1.69,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759413019,"impliedVolatility":0.31641308593749995,"inTheMoney":false},{"contractSymbol":"AAPL251003P00257500","strike":257.5,"currency":"USD","lastPrice":3.05,"change":0.0,"percentChange":0.0,"volume":1665,"openInterest":3602,"bid":3.05,"ask":3.15,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412948,"impliedVolatility":0.34961587890624996,"inTheMoney":true},{"contractSymbol":"AAPL251003P00260000","strike":260.0,"currency":"USD","lastPrice":5.22,"change":0.3199997,"percentChange":6.530606,"volume":663,"openInterest":1942,"bid":4.9,"ask":5.15,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412754,"impliedVolatility":0.41089456298828125,"inTheMoney":true},{"contractSymbol":"AAPL251003P00262500","strike":262.5,"currency":"USD","lastPrice":8.0,"change":0.8499999,"percentChange":11.888111,"volume":4,"openInterest":1605,"bid":7.25,"ask":7.55,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412717,"impliedVolatility":0.50586431640625,"inTheMoney":true},{"contractSymbol":"AAPL251003P00265000","strike":265.0,"currency":"USD","lastPrice":9.95,"change":0.6999998,"percentChange":7.567566,"volume":13,"openInterest":2262,"bid":9.7,"ask":9.95,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759412235,"impliedVolatility":0.567387138671875,"inTheMoney":true},{"contractSymbol":"AAPL251003P00267500","strike":267.5,"currency":"USD","lastPrice":11.9,"change":0.0,"percentChange":0.0,"volume":39,"openInterest":31,"bid":12.25,"ask":12.9,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759348480,"impliedVolatility":0.7036162451171876,"inTheMoney":true},{"contractSymbol":"AAPL251003P00270000","strike":270.0,"currency":"USD","lastPrice":14.4,"change":0.0,"percentChange":0.0,"volume":79,"openInterest":456,"bid":14.5,"ask":15.55,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759348480,"impliedVolatility":0.7827170166015626,"inTheMoney":true},{"contractSymbol":"AAPL251003P00272500","strike":272.5,"currency":"USD","lastPrice":15.7,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":5,"bid":17.05,"ask":17.95,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759345707,"impliedVolatility":0.8620619262695313,"inTheMoney":true},{"contractSymbol":"AAPL251003P00275000","strike":275.0,"currency":"USD","lastPrice":20.56,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":8,"bid":19.6,"ask":20.45,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759247263,"impliedVolatility":0.948242705078125,"inTheMoney":true},{"contractSymbol":"AAPL251003P00277500","strike":277.5,"currency":"USD","lastPrice":25.72,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":22.1,"ask":23.05,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1758724846,"impliedVolatility":1.0371141894531253,"inTheMoney":true},{"contractSymbol":"AAPL251003P00280000","strike":280.0,"currency":"USD","lastPrice":28.3,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":1,"bid":24.4,"ask":25.4,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1758735140,"impliedVolatility":1.0737351000976565,"inTheMoney":true},{"contractSymbol":"AAPL251003P00285000","strike":285.0,"currency":"USD","lastPrice":30.2,"change":0.0,"percentChange":0.0,"volume":18,"openInterest":0,"bid":29.4,"ask":30.45,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1758564308,"impliedVolatility":1.2231484155273438,"inTheMoney":true},{"contractSymbol":"AAPL251003P00287500","strike":287.5,"currency":"USD","lastPrice":32.85,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":1,"bid":31.85,"ask":33.0,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759175400,"impliedVolatility":1.2919957275390623,"inTheMoney":true},{"contractSymbol":"AAPL251003P00305000","strike":305.0,"currency":"USD","lastPrice":50.45,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":49.5,"ask":50.55,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1758649436,"impliedVolatility":1.7666027294921873,"inTheMoney":true},{"contractSymbol":"AAPL251003P00310000","strike":310.0,"currency":"USD","lastPrice":55.6,"change":0.0,"percentChange":0.0,"volume":18,"openInterest":0,"bid":54.35,"ask":55.5,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1758653264,"impliedVolatility":1.8540046362304685,"inTheMoney":true},{"contractSymbol":"AAPL251003P00315000","strike":315.0,"currency":"USD","lastPrice":60.45,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":59.4,"ask":60.5,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1758649436,"impliedVolatility":1.9755860595703125,"inTheMoney":true},{"contractSymbol":"AAPL251003P00325000","strike":325.0,"currency":"USD","lastPrice":71.5,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":0,"bid":69.35,"ask":70.5,"contractSize":"REGULAR","expiration":1759449600,"lastTradeDate":1759257599,"impliedVolatility":2.183598291015625,"inTheMoney":true}]}]}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1760659200.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1760659200.json new file mode 100644 index 0000000..e798c45 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1760659200.json @@ -0,0 +1 @@ +{"optionChain":{"result":[{"underlyingSymbol":"AAPL","expirationDates":[1760659200,1761264000,1761868800,1762473600,1763078400,1763683200,1764288000,1766102400,1768521600,1771545600,1773964800,1776384000,1778803200,1781740800,1787270400,1789689600,1797552000,1799971200,1813190400,1829001600,1832025600],"strikes":[90.0,95.0,100.0,105.0,110.0,115.0,120.0,125.0,130.0,135.0,140.0,145.0,150.0,155.0,160.0,165.0,170.0,175.0,180.0,185.0,190.0,195.0,200.0,205.0,210.0,215.0,217.5,220.0,222.5,225.0,227.5,230.0,232.5,235.0,237.5,240.0,242.5,245.0,247.5,250.0,252.5,255.0,257.5,260.0,262.5,265.0,267.5,270.0,272.5,275.0,277.5,280.0,282.5,285.0,287.5,290.0,292.5,295.0,300.0,305.0,310.0,315.0,320.0,325.0,330.0,335.0,340.0,345.0,350.0,355.0,360.0,370.0],"hasMiniOptions":false,"quote":{"language":"en-US","region":"US","quoteType":"EQUITY","typeDisp":"Equity","quoteSourceName":"Nasdaq Real Time Price","triggerable":true,"customPriceAlertConfidence":"HIGH","currency":"USD","longName":"Apple Inc.","marketState":"POST","regularMarketChangePercent":0.633649,"regularMarketPrice":249.34,"corporateActions":[],"postMarketTime":1760564262,"regularMarketTime":1760558402,"hasPrePostMarketData":true,"firstTradeDateMilliseconds":345479400000,"priceHint":2,"postMarketChangePercent":0.016045779,"postMarketPrice":249.38,"postMarketChange":0.040008545,"regularMarketChange":1.5699921,"regularMarketDayHigh":251.82,"regularMarketDayRange":"247.48 - 251.82","regularMarketDayLow":247.48,"regularMarketVolume":31761708,"regularMarketPreviousClose":247.77,"bid":249.16,"ask":249.57,"bidSize":1,"askSize":1,"fullExchangeName":"NasdaqGS","financialCurrency":"USD","regularMarketOpen":249.38,"averageDailyVolume3Month":54722185,"averageDailyVolume10Day":42752840,"fiftyTwoWeekLowChange":80.12999,"fiftyTwoWeekLowChangePercent":0.47355348,"fiftyTwoWeekRange":"169.21 - 260.1","fiftyTwoWeekHighChange":-10.76001,"fiftyTwoWeekHighChangePercent":-0.04136874,"fiftyTwoWeekLow":169.21,"fiftyTwoWeekHigh":260.1,"fiftyTwoWeekChangePercent":6.8987846,"dividendDate":1755129600,"earningsTimestamp":1761854400,"earningsTimestampStart":1761854400,"earningsTimestampEnd":1761854400,"earningsCallTimestampStart":1761858000,"earningsCallTimestampEnd":1761858000,"isEarningsDateEstimate":false,"trailingAnnualDividendRate":1.01,"trailingPE":37.836113,"dividendRate":1.04,"trailingAnnualDividendYield":0.004076361,"dividendYield":0.42,"epsTrailingTwelveMonths":6.59,"epsForward":8.31,"epsCurrentYear":7.41093,"priceEpsCurrentYear":33.644897,"shortName":"Apple Inc.","sharesOutstanding":14840390000,"bookValue":4.431,"fiftyDayAverage":238.8128,"fiftyDayAverageChange":10.527191,"fiftyDayAverageChangePercent":0.044081353,"twoHundredDayAverage":222.16466,"twoHundredDayAverageChange":27.175339,"twoHundredDayAverageChangePercent":0.12232071,"marketCap":3700302807040,"forwardPE":30.004812,"priceToBook":56.271717,"sourceInterval":15,"exchangeDataDelayedBy":0,"averageAnalystRating":"2.1 - Buy","tradeable":false,"cryptoTradeable":false,"exchange":"NMS","messageBoardId":"finmb_24937","exchangeTimezoneName":"America/New_York","exchangeTimezoneShortName":"EDT","gmtOffSetMilliseconds":-14400000,"market":"us_market","esgPopulated":false,"displayName":"Apple","symbol":"AAPL"},"options":[{"expirationDate":1760659200,"hasMiniOptions":false,"calls":[{"contractSymbol":"AAPL251017C00090000","strike":90.0,"currency":"USD","lastPrice":157.94,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":27,"bid":158.55,"ask":160.1,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760461077,"impliedVolatility":5.7890652636718745,"inTheMoney":true},{"contractSymbol":"AAPL251017C00095000","strike":95.0,"currency":"USD","lastPrice":160.4,"change":0.0,"percentChange":0.0,"openInterest":2,"bid":153.55,"ask":155.15,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1758565633,"impliedVolatility":3.3750015624999996,"inTheMoney":true},{"contractSymbol":"AAPL251017C00100000","strike":100.0,"currency":"USD","lastPrice":158.38,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":19,"bid":148.55,"ask":150.1,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1759940314,"impliedVolatility":5.2363315795898435,"inTheMoney":true},{"contractSymbol":"AAPL251017C00105000","strike":105.0,"currency":"USD","lastPrice":110.26,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":14,"bid":149.7,"ask":151.65,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1753106573,"impliedVolatility":7.976562529296874,"inTheMoney":true},{"contractSymbol":"AAPL251017C00110000","strike":110.0,"currency":"USD","lastPrice":138.36,"change":0.0,"percentChange":0.0,"volume":345,"openInterest":392,"bid":138.15,"ask":139.6,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760385068,"impliedVolatility":4.0312549609375,"inTheMoney":true},{"contractSymbol":"AAPL251017C00115000","strike":115.0,"currency":"USD","lastPrice":112.7,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":99,"bid":139.7,"ask":141.45,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1755719400,"impliedVolatility":7.22509862487793,"inTheMoney":true},{"contractSymbol":"AAPL251017C00120000","strike":120.0,"currency":"USD","lastPrice":91.56,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":67,"bid":134.75,"ask":136.7,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1752673942,"impliedVolatility":6.941895853881836,"inTheMoney":true},{"contractSymbol":"AAPL251017C00125000","strike":125.0,"currency":"USD","lastPrice":114.66,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":19,"bid":123.6,"ask":125.15,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1758030724,"impliedVolatility":2.7343781640625,"inTheMoney":true},{"contractSymbol":"AAPL251017C00130000","strike":130.0,"currency":"USD","lastPrice":124.4,"change":0.0,"percentChange":0.0,"volume":10,"openInterest":164,"bid":118.2,"ask":119.6,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1758657228,"impliedVolatility":3.269533076171875,"inTheMoney":true},{"contractSymbol":"AAPL251017C00135000","strike":135.0,"currency":"USD","lastPrice":116.2,"change":-6.080002,"percentChange":-4.9721966,"volume":2,"openInterest":73,"bid":113.15,"ask":114.6,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760538267,"impliedVolatility":3.097658505859375,"inTheMoney":true},{"contractSymbol":"AAPL251017C00140000","strike":140.0,"currency":"USD","lastPrice":110.3,"change":-5.0999985,"percentChange":-4.4194093,"volume":4,"openInterest":76,"bid":108.2,"ask":109.6,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760542108,"impliedVolatility":2.92969017578125,"inTheMoney":true},{"contractSymbol":"AAPL251017C00145000","strike":145.0,"currency":"USD","lastPrice":104.74,"change":1.9300003,"percentChange":1.8772496,"volume":2,"openInterest":181,"bid":103.2,"ask":104.65,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760554380,"impliedVolatility":2.8398466503906246,"inTheMoney":true},{"contractSymbol":"AAPL251017C00150000","strike":150.0,"currency":"USD","lastPrice":99.68,"change":2.2399979,"percentChange":2.2988484,"volume":8,"openInterest":639,"bid":98.5,"ask":99.6,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760554303,"impliedVolatility":2.6132847167968754,"inTheMoney":true},{"contractSymbol":"AAPL251017C00155000","strike":155.0,"currency":"USD","lastPrice":91.56,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":174,"bid":93.25,"ask":94.65,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760122100,"impliedVolatility":2.5273474316406244,"inTheMoney":true},{"contractSymbol":"AAPL251017C00160000","strike":160.0,"currency":"USD","lastPrice":90.21,"change":2.4799957,"percentChange":2.8268502,"volume":2,"openInterest":299,"bid":88.6,"ask":90.15,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760542137,"impliedVolatility":1.8125009374999999,"inTheMoney":true},{"contractSymbol":"AAPL251017C00165000","strike":165.0,"currency":"USD","lastPrice":82.17,"change":0.0,"percentChange":0.0,"volume":6,"openInterest":236,"bid":83.25,"ask":84.65,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760471351,"impliedVolatility":2.2304731738281243,"inTheMoney":true},{"contractSymbol":"AAPL251017C00170000","strike":170.0,"currency":"USD","lastPrice":78.93,"change":1.6200027,"percentChange":2.0954635,"volume":5,"openInterest":647,"bid":78.6,"ask":80.4,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558316,"impliedVolatility":1.8984380078125,"inTheMoney":true},{"contractSymbol":"AAPL251017C00175000","strike":175.0,"currency":"USD","lastPrice":73.51,"change":1.3600006,"percentChange":1.8849627,"volume":124,"openInterest":1012,"bid":73.65,"ask":75.05,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760557359,"impliedVolatility":1.3125034374999998,"inTheMoney":true},{"contractSymbol":"AAPL251017C00180000","strike":180.0,"currency":"USD","lastPrice":69.05,"change":1.8800049,"percentChange":2.7988758,"volume":75,"openInterest":1658,"bid":68.8,"ask":69.9,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760557983,"impliedVolatility":1.21875390625,"inTheMoney":true},{"contractSymbol":"AAPL251017C00185000","strike":185.0,"currency":"USD","lastPrice":64.01,"change":2.2400017,"percentChange":3.6263585,"volume":3,"openInterest":649,"bid":63.6,"ask":65.4,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760549980,"impliedVolatility":1.5214867675781247,"inTheMoney":true},{"contractSymbol":"AAPL251017C00190000","strike":190.0,"currency":"USD","lastPrice":58.8,"change":0.8999977,"percentChange":1.5544002,"volume":17,"openInterest":1366,"bid":58.7,"ask":60.1,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558202,"impliedVolatility":1.2343788281249999,"inTheMoney":true},{"contractSymbol":"AAPL251017C00195000","strike":195.0,"currency":"USD","lastPrice":54.2,"change":2.3400002,"percentChange":4.5121484,"volume":25,"openInterest":1848,"bid":53.4,"ask":54.7,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760554562,"impliedVolatility":1.4599636376953122,"inTheMoney":true},{"contractSymbol":"AAPL251017C00200000","strike":200.0,"currency":"USD","lastPrice":48.97,"change":1.8600006,"percentChange":3.9482076,"volume":112,"openInterest":2776,"bid":48.85,"ask":49.9,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558377,"impliedVolatility":0.9609378906249999,"inTheMoney":true},{"contractSymbol":"AAPL251017C00205000","strike":205.0,"currency":"USD","lastPrice":43.6,"change":0.29999924,"percentChange":0.6928389,"volume":179,"openInterest":7301,"bid":43.85,"ask":45.05,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558080,"impliedVolatility":0.9960937890625,"inTheMoney":true},{"contractSymbol":"AAPL251017C00210000","strike":210.0,"currency":"USD","lastPrice":40.06,"change":1.5800018,"percentChange":4.106034,"volume":129,"openInterest":20107,"bid":39.0,"ask":39.95,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760554508,"impliedVolatility":0.9160164648437501,"inTheMoney":true},{"contractSymbol":"AAPL251017C00215000","strike":215.0,"currency":"USD","lastPrice":34.15,"change":0.6500015,"percentChange":1.9403031,"volume":115,"openInterest":6669,"bid":33.75,"ask":34.95,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558008,"impliedVolatility":0.585941640625,"inTheMoney":true},{"contractSymbol":"AAPL251017C00217500","strike":217.5,"currency":"USD","lastPrice":32.42,"change":2.2299976,"percentChange":7.3865438,"volume":4,"openInterest":103,"bid":30.8,"ask":32.2,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760555839,"impliedVolatility":0.8955088574218748,"inTheMoney":true},{"contractSymbol":"AAPL251017C00220000","strike":220.0,"currency":"USD","lastPrice":29.37,"change":1.4200001,"percentChange":5.0805006,"volume":424,"openInterest":13387,"bid":29.15,"ask":29.6,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558386,"impliedVolatility":0.57812921875,"inTheMoney":true},{"contractSymbol":"AAPL251017C00222500","strike":222.5,"currency":"USD","lastPrice":26.4,"change":2.1499996,"percentChange":8.865978,"volume":8,"openInterest":297,"bid":26.15,"ask":27.95,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760549848,"impliedVolatility":0.6962920996093751,"inTheMoney":true},{"contractSymbol":"AAPL251017C00225000","strike":225.0,"currency":"USD","lastPrice":24.26,"change":1.9200001,"percentChange":8.594449,"volume":1343,"openInterest":13595,"bid":24.3,"ask":25.1,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558345,"impliedVolatility":0.710940390625,"inTheMoney":true},{"contractSymbol":"AAPL251017C00227500","strike":227.5,"currency":"USD","lastPrice":21.64,"change":0.23999977,"percentChange":1.1214943,"volume":10,"openInterest":1174,"bid":21.6,"ask":22.8,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760547128,"impliedVolatility":0.649417568359375,"inTheMoney":true},{"contractSymbol":"AAPL251017C00230000","strike":230.0,"currency":"USD","lastPrice":19.42,"change":1.8700008,"percentChange":10.655276,"volume":4322,"openInterest":19081,"bid":19.0,"ask":20.05,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558345,"impliedVolatility":0.5117236328125,"inTheMoney":true},{"contractSymbol":"AAPL251017C00232500","strike":232.5,"currency":"USD","lastPrice":16.72,"change":1.3199997,"percentChange":8.571426,"volume":33,"openInterest":264,"bid":16.55,"ask":17.55,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558345,"impliedVolatility":0.624027197265625,"inTheMoney":true},{"contractSymbol":"AAPL251017C00235000","strike":235.0,"currency":"USD","lastPrice":14.55,"change":1.3000002,"percentChange":9.811322,"volume":706,"openInterest":14172,"bid":14.5,"ask":14.7,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558366,"impliedVolatility":0.46191944335937507,"inTheMoney":true},{"contractSymbol":"AAPL251017C00237500","strike":237.5,"currency":"USD","lastPrice":11.9,"change":1.5,"percentChange":14.423078,"volume":45,"openInterest":314,"bid":11.95,"ask":12.35,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558345,"impliedVolatility":0.4370173486328125,"inTheMoney":true},{"contractSymbol":"AAPL251017C00240000","strike":240.0,"currency":"USD","lastPrice":9.65,"change":1.25,"percentChange":14.880953,"volume":3635,"openInterest":26619,"bid":9.6,"ask":9.85,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558385,"impliedVolatility":0.367193828125,"inTheMoney":true},{"contractSymbol":"AAPL251017C00242500","strike":242.5,"currency":"USD","lastPrice":7.0,"change":0.5500002,"percentChange":8.527135,"volume":201,"openInterest":693,"bid":7.2,"ask":7.5,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558307,"impliedVolatility":0.3232489550781249,"inTheMoney":true},{"contractSymbol":"AAPL251017C00245000","strike":245.0,"currency":"USD","lastPrice":5.23,"change":0.73,"percentChange":16.222221,"volume":1961,"openInterest":15414,"bid":4.9,"ask":5.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558336,"impliedVolatility":0.243171630859375,"inTheMoney":true},{"contractSymbol":"AAPL251017C00247500","strike":247.5,"currency":"USD","lastPrice":3.2,"change":0.25,"percentChange":8.474576,"volume":7840,"openInterest":7792,"bid":3.1,"ask":3.2,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558362,"impliedVolatility":0.2399978344726562,"inTheMoney":true},{"contractSymbol":"AAPL251017C00250000","strike":250.0,"currency":"USD","lastPrice":1.84,"change":0.18000007,"percentChange":10.843378,"volume":43568,"openInterest":35241,"bid":1.84,"ask":1.9,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558392,"impliedVolatility":0.245124736328125,"inTheMoney":false},{"contractSymbol":"AAPL251017C00252500","strike":252.5,"currency":"USD","lastPrice":0.87,"change":-0.00999999,"percentChange":-1.1363626,"volume":36187,"openInterest":10860,"bid":0.87,"ask":0.91,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558399,"impliedVolatility":0.23438265625,"inTheMoney":false},{"contractSymbol":"AAPL251017C00255000","strike":255.0,"currency":"USD","lastPrice":0.35,"change":-0.099999994,"percentChange":-22.222221,"volume":32607,"openInterest":27822,"bid":0.33,"ask":0.35,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558385,"impliedVolatility":0.22461712890624996,"inTheMoney":false},{"contractSymbol":"AAPL251017C00257500","strike":257.5,"currency":"USD","lastPrice":0.13,"change":-0.08,"percentChange":-38.095238,"volume":17318,"openInterest":18237,"bid":0.12,"ask":0.13,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558315,"impliedVolatility":0.22657023437499998,"inTheMoney":false},{"contractSymbol":"AAPL251017C00260000","strike":260.0,"currency":"USD","lastPrice":0.05,"change":-0.040000003,"percentChange":-44.444447,"volume":16553,"openInterest":38728,"bid":0.04,"ask":0.05,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558392,"impliedVolatility":0.23438265625,"inTheMoney":false},{"contractSymbol":"AAPL251017C00262500","strike":262.5,"currency":"USD","lastPrice":0.02,"change":-0.030000001,"percentChange":-60.0,"volume":4876,"openInterest":22886,"bid":0.02,"ask":0.03,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558265,"impliedVolatility":0.257819921875,"inTheMoney":false},{"contractSymbol":"AAPL251017C00265000","strike":265.0,"currency":"USD","lastPrice":0.02,"change":-0.01,"percentChange":-33.333336,"volume":6299,"openInterest":23463,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558328,"impliedVolatility":0.28516339843749994,"inTheMoney":false},{"contractSymbol":"AAPL251017C00267500","strike":267.5,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1376,"openInterest":6818,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558022,"impliedVolatility":0.29688203124999996,"inTheMoney":false},{"contractSymbol":"AAPL251017C00270000","strike":270.0,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-50.0,"volume":1664,"openInterest":19733,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558274,"impliedVolatility":0.3320379296875,"inTheMoney":false},{"contractSymbol":"AAPL251017C00272500","strike":272.5,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":128,"openInterest":3703,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760557359,"impliedVolatility":0.367193828125,"inTheMoney":false},{"contractSymbol":"AAPL251017C00275000","strike":275.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1034,"openInterest":14843,"bid":0.0,"ask":0.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558167,"impliedVolatility":0.2500075,"inTheMoney":false},{"contractSymbol":"AAPL251017C00277500","strike":277.5,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":73,"openInterest":1337,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760549091,"impliedVolatility":0.42969320312500003,"inTheMoney":false},{"contractSymbol":"AAPL251017C00280000","strike":280.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":132,"openInterest":12924,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558046,"impliedVolatility":0.4687553125,"inTheMoney":false},{"contractSymbol":"AAPL251017C00282500","strike":282.5,"currency":"USD","lastPrice":0.02,"change":0.01,"percentChange":100.0,"volume":1,"openInterest":1138,"bid":0.0,"ask":0.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760555271,"impliedVolatility":0.2500075,"inTheMoney":false},{"contractSymbol":"AAPL251017C00285000","strike":285.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":6,"openInterest":3529,"bid":0.0,"ask":0.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760471021,"impliedVolatility":0.2500075,"inTheMoney":false},{"contractSymbol":"AAPL251017C00287500","strike":287.5,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":16,"openInterest":999,"bid":0.0,"ask":0.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760377348,"impliedVolatility":0.2500075,"inTheMoney":false},{"contractSymbol":"AAPL251017C00290000","strike":290.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":35,"openInterest":6057,"bid":0.0,"ask":0.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760552605,"impliedVolatility":0.2500075,"inTheMoney":false},{"contractSymbol":"AAPL251017C00292500","strike":292.5,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":8,"openInterest":247,"bid":0.0,"ask":0.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760460114,"impliedVolatility":0.500005,"inTheMoney":false},{"contractSymbol":"AAPL251017C00295000","strike":295.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":4,"openInterest":4176,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760549715,"impliedVolatility":0.6093789062500001,"inTheMoney":false},{"contractSymbol":"AAPL251017C00300000","strike":300.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":35,"openInterest":12540,"bid":0.0,"ask":0.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760544062,"impliedVolatility":0.500005,"inTheMoney":false},{"contractSymbol":"AAPL251017C00305000","strike":305.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":1224,"bid":0.0,"ask":0.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760448603,"impliedVolatility":0.500005,"inTheMoney":false},{"contractSymbol":"AAPL251017C00310000","strike":310.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":2215,"bid":0.0,"ask":0.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760374983,"impliedVolatility":0.500005,"inTheMoney":false},{"contractSymbol":"AAPL251017C00315000","strike":315.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":650,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760026347,"impliedVolatility":0.8125018749999999,"inTheMoney":false},{"contractSymbol":"AAPL251017C00320000","strike":320.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":1574,"bid":0.0,"ask":0.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760373736,"impliedVolatility":0.500005,"inTheMoney":false},{"contractSymbol":"AAPL251017C00325000","strike":325.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":8,"openInterest":642,"bid":0.0,"ask":0.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760018964,"impliedVolatility":0.500005,"inTheMoney":false},{"contractSymbol":"AAPL251017C00330000","strike":330.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":171,"openInterest":886,"bid":0.0,"ask":0.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760029203,"impliedVolatility":0.500005,"inTheMoney":false},{"contractSymbol":"AAPL251017C00335000","strike":335.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":20,"openInterest":82,"bid":0.0,"ask":0.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1758895354,"impliedVolatility":0.500005,"inTheMoney":false},{"contractSymbol":"AAPL251017C00340000","strike":340.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":35,"openInterest":153,"bid":0.0,"ask":0.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1758721979,"impliedVolatility":0.500005,"inTheMoney":false},{"contractSymbol":"AAPL251017C00345000","strike":345.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":391,"bid":0.0,"ask":0.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1758893402,"impliedVolatility":0.500005,"inTheMoney":false},{"contractSymbol":"AAPL251017C00350000","strike":350.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":150,"openInterest":450,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1755629365,"impliedVolatility":1.15625421875,"inTheMoney":false},{"contractSymbol":"AAPL251017C00355000","strike":355.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":225,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760104375,"impliedVolatility":1.1875040625,"inTheMoney":false},{"contractSymbol":"AAPL251017C00360000","strike":360.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":1497,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1754502698,"impliedVolatility":1.2500037499999999,"inTheMoney":false},{"contractSymbol":"AAPL251017C00370000","strike":370.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":2845,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760362209,"impliedVolatility":1.3125034374999998,"inTheMoney":false}],"puts":[{"contractSymbol":"AAPL251017P00090000","strike":90.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":957,"bid":0.0,"ask":0.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760553351,"impliedVolatility":0.500005,"inTheMoney":false},{"contractSymbol":"AAPL251017P00095000","strike":95.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":1135,"bid":0.0,"ask":0.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1759336103,"impliedVolatility":0.500005,"inTheMoney":false},{"contractSymbol":"AAPL251017P00100000","strike":100.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":860,"bid":0.0,"ask":0.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1759933058,"impliedVolatility":0.500005,"inTheMoney":false},{"contractSymbol":"AAPL251017P00105000","strike":105.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":292,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1757692598,"impliedVolatility":2.8750028125,"inTheMoney":false},{"contractSymbol":"AAPL251017P00110000","strike":110.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":615,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760462658,"impliedVolatility":2.750003125,"inTheMoney":false},{"contractSymbol":"AAPL251017P00115000","strike":115.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":20,"openInterest":595,"bid":0.0,"ask":0.21,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1758298528,"impliedVolatility":3.4218764453125,"inTheMoney":false},{"contractSymbol":"AAPL251017P00120000","strike":120.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":5852,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760449239,"impliedVolatility":2.43750390625,"inTheMoney":false},{"contractSymbol":"AAPL251017P00125000","strike":125.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":40,"openInterest":1704,"bid":0.0,"ask":0.21,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1759519475,"impliedVolatility":3.0781273046874995,"inTheMoney":false},{"contractSymbol":"AAPL251017P00130000","strike":130.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":101,"openInterest":1165,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760383831,"impliedVolatility":2.1875045312499997,"inTheMoney":false},{"contractSymbol":"AAPL251017P00135000","strike":135.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":1455,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1759930463,"impliedVolatility":2.0625048437499998,"inTheMoney":false},{"contractSymbol":"AAPL251017P00140000","strike":140.0,"currency":"USD","lastPrice":0.02,"change":0.01,"percentChange":100.0,"volume":1,"openInterest":2453,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760535003,"impliedVolatility":1.9375003125,"inTheMoney":false},{"contractSymbol":"AAPL251017P00145000","strike":145.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":4238,"bid":0.0,"ask":0.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760366421,"impliedVolatility":0.500005,"inTheMoney":false},{"contractSymbol":"AAPL251017P00150000","strike":150.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":4,"openInterest":11322,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760537977,"impliedVolatility":1.75000125,"inTheMoney":false},{"contractSymbol":"AAPL251017P00155000","strike":155.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":105,"openInterest":3080,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1759513862,"impliedVolatility":1.625001875,"inTheMoney":false},{"contractSymbol":"AAPL251017P00160000","strike":160.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":14167,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760467580,"impliedVolatility":1.5312523437499999,"inTheMoney":false},{"contractSymbol":"AAPL251017P00165000","strike":165.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":14,"openInterest":6204,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760451602,"impliedVolatility":1.4375028125,"inTheMoney":false},{"contractSymbol":"AAPL251017P00170000","strike":170.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":7156,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760448772,"impliedVolatility":1.3125034374999998,"inTheMoney":false},{"contractSymbol":"AAPL251017P00175000","strike":175.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":303,"openInterest":5878,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760462842,"impliedVolatility":1.2500037499999999,"inTheMoney":false},{"contractSymbol":"AAPL251017P00180000","strike":180.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":12196,"bid":0.0,"ask":0.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760535491,"impliedVolatility":0.500005,"inTheMoney":false},{"contractSymbol":"AAPL251017P00185000","strike":185.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":27,"openInterest":10259,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760553452,"impliedVolatility":1.0625046875000002,"inTheMoney":false},{"contractSymbol":"AAPL251017P00190000","strike":190.0,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-50.0,"volume":109,"openInterest":13944,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760556516,"impliedVolatility":0.9687503125,"inTheMoney":false},{"contractSymbol":"AAPL251017P00195000","strike":195.0,"currency":"USD","lastPrice":0.01,"change":-0.01,"percentChange":-50.0,"volume":333,"openInterest":9604,"bid":0.0,"ask":0.02,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760547080,"impliedVolatility":0.937500625,"inTheMoney":false},{"contractSymbol":"AAPL251017P00200000","strike":200.0,"currency":"USD","lastPrice":0.02,"change":-0.01,"percentChange":-33.333336,"volume":57,"openInterest":16718,"bid":0.0,"ask":0.02,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760557863,"impliedVolatility":0.8437515624999999,"inTheMoney":false},{"contractSymbol":"AAPL251017P00205000","strike":205.0,"currency":"USD","lastPrice":0.01,"change":-0.03,"percentChange":-75.0,"volume":149,"openInterest":11478,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760556583,"impliedVolatility":0.789064609375,"inTheMoney":false},{"contractSymbol":"AAPL251017P00210000","strike":210.0,"currency":"USD","lastPrice":0.02,"change":-0.02,"percentChange":-50.0,"volume":209,"openInterest":28725,"bid":0.02,"ask":0.03,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558316,"impliedVolatility":0.7382838671874999,"inTheMoney":false},{"contractSymbol":"AAPL251017P00215000","strike":215.0,"currency":"USD","lastPrice":0.02,"change":-0.030000001,"percentChange":-60.0,"volume":266,"openInterest":11187,"bid":0.02,"ask":0.03,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558388,"impliedVolatility":0.648441015625,"inTheMoney":false},{"contractSymbol":"AAPL251017P00217500","strike":217.5,"currency":"USD","lastPrice":0.02,"change":-0.030000001,"percentChange":-60.0,"volume":171,"openInterest":785,"bid":0.02,"ask":0.04,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760557417,"impliedVolatility":0.6132851171875001,"inTheMoney":false},{"contractSymbol":"AAPL251017P00220000","strike":220.0,"currency":"USD","lastPrice":0.03,"change":-0.03,"percentChange":-50.0,"volume":1816,"openInterest":19334,"bid":0.02,"ask":0.04,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760557540,"impliedVolatility":0.570316796875,"inTheMoney":false},{"contractSymbol":"AAPL251017P00222500","strike":222.5,"currency":"USD","lastPrice":0.04,"change":-0.030000001,"percentChange":-42.857143,"volume":858,"openInterest":746,"bid":0.03,"ask":0.04,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760557709,"impliedVolatility":0.5312546875,"inTheMoney":false},{"contractSymbol":"AAPL251017P00225000","strike":225.0,"currency":"USD","lastPrice":0.05,"change":-0.02,"percentChange":-28.571428,"volume":1636,"openInterest":12808,"bid":0.03,"ask":0.05,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760557918,"impliedVolatility":0.5097705273437501,"inTheMoney":false},{"contractSymbol":"AAPL251017P00227500","strike":227.5,"currency":"USD","lastPrice":0.05,"change":-0.040000003,"percentChange":-44.444447,"volume":638,"openInterest":2488,"bid":0.04,"ask":0.05,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558207,"impliedVolatility":0.46094289062500005,"inTheMoney":false},{"contractSymbol":"AAPL251017P00230000","strike":230.0,"currency":"USD","lastPrice":0.07,"change":-0.04,"percentChange":-36.363636,"volume":979,"openInterest":12612,"bid":0.05,"ask":0.06,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558272,"impliedVolatility":0.42383388671875,"inTheMoney":false},{"contractSymbol":"AAPL251017P00232500","strike":232.5,"currency":"USD","lastPrice":0.07,"change":-0.07,"percentChange":-50.0,"volume":271,"openInterest":3433,"bid":0.08,"ask":0.09,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558214,"impliedVolatility":0.40039662109375,"inTheMoney":false},{"contractSymbol":"AAPL251017P00235000","strike":235.0,"currency":"USD","lastPrice":0.1,"change":-0.10999999,"percentChange":-52.38095,"volume":7058,"openInterest":12556,"bid":0.11,"ask":0.12,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558385,"impliedVolatility":0.366217275390625,"inTheMoney":false},{"contractSymbol":"AAPL251017P00237500","strike":237.5,"currency":"USD","lastPrice":0.16,"change":-0.17000002,"percentChange":-51.515156,"volume":3000,"openInterest":5990,"bid":0.15,"ask":0.16,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558296,"impliedVolatility":0.33008482421874996,"inTheMoney":false},{"contractSymbol":"AAPL251017P00240000","strike":240.0,"currency":"USD","lastPrice":0.22,"change":-0.30999997,"percentChange":-58.490566,"volume":9047,"openInterest":14692,"bid":0.24,"ask":0.25,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558377,"impliedVolatility":0.30225307128906254,"inTheMoney":false},{"contractSymbol":"AAPL251017P00242500","strike":242.5,"currency":"USD","lastPrice":0.38,"change":-0.53000003,"percentChange":-58.24176,"volume":8254,"openInterest":5972,"bid":0.36,"ask":0.39,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558395,"impliedVolatility":0.2712475219726562,"inTheMoney":false},{"contractSymbol":"AAPL251017P00245000","strike":245.0,"currency":"USD","lastPrice":0.73,"change":-0.77,"percentChange":-51.333332,"volume":17177,"openInterest":14951,"bid":0.68,"ask":0.73,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558386,"impliedVolatility":0.25391371093750004,"inTheMoney":false},{"contractSymbol":"AAPL251017P00247500","strike":247.5,"currency":"USD","lastPrice":1.32,"change":-1.1,"percentChange":-45.454544,"volume":18580,"openInterest":9898,"bid":1.28,"ask":1.34,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558383,"impliedVolatility":0.2375564526367187,"inTheMoney":false},{"contractSymbol":"AAPL251017P00250000","strike":250.0,"currency":"USD","lastPrice":2.42,"change":-1.3299999,"percentChange":-35.466667,"volume":18073,"openInterest":9884,"bid":2.39,"ask":2.41,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558361,"impliedVolatility":0.22852333984375,"inTheMoney":true},{"contractSymbol":"AAPL251017P00252500","strike":252.5,"currency":"USD","lastPrice":3.95,"change":-1.97,"percentChange":-33.277027,"volume":2424,"openInterest":5022,"bid":3.8,"ask":4.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558381,"impliedVolatility":0.22510540527343748,"inTheMoney":true},{"contractSymbol":"AAPL251017P00255000","strike":255.0,"currency":"USD","lastPrice":6.3,"change":-1.7399998,"percentChange":-21.641787,"volume":1314,"openInterest":6674,"bid":5.75,"ask":6.0,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558183,"impliedVolatility":0.22266402343749997,"inTheMoney":true},{"contractSymbol":"AAPL251017P00257500","strike":257.5,"currency":"USD","lastPrice":8.7,"change":-1.5900002,"percentChange":-15.451897,"volume":418,"openInterest":3448,"bid":8.05,"ask":8.35,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760557729,"impliedVolatility":0.24658956542968752,"inTheMoney":true},{"contractSymbol":"AAPL251017P00260000","strike":260.0,"currency":"USD","lastPrice":10.79,"change":-1.8599997,"percentChange":-14.703555,"volume":880,"openInterest":3202,"bid":10.6,"ask":10.95,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760558346,"impliedVolatility":0.33301448242187504,"inTheMoney":true},{"contractSymbol":"AAPL251017P00262500","strike":262.5,"currency":"USD","lastPrice":13.3,"change":-0.9899998,"percentChange":-6.92792,"volume":8,"openInterest":19,"bid":12.8,"ask":13.65,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760542166,"impliedVolatility":0.44385321777343745,"inTheMoney":true},{"contractSymbol":"AAPL251017P00265000","strike":265.0,"currency":"USD","lastPrice":15.57,"change":-0.9300003,"percentChange":-5.6363654,"volume":39,"openInterest":13,"bid":15.35,"ask":16.3,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760557132,"impliedVolatility":0.5390671093750001,"inTheMoney":true},{"contractSymbol":"AAPL251017P00267500","strike":267.5,"currency":"USD","lastPrice":18.01,"change":-1.2600002,"percentChange":-6.538662,"volume":30,"openInterest":3,"bid":18.2,"ask":19.35,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760557132,"impliedVolatility":0.5913126806640626,"inTheMoney":true},{"contractSymbol":"AAPL251017P00270000","strike":270.0,"currency":"USD","lastPrice":20.43,"change":-1.289999,"percentChange":-5.9392223,"volume":112,"openInterest":4,"bid":20.3,"ask":21.1,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760557132,"impliedVolatility":0.5971719970703125,"inTheMoney":true},{"contractSymbol":"AAPL251017P00272500","strike":272.5,"currency":"USD","lastPrice":23.07,"change":-1.25,"percentChange":-5.139803,"volume":20,"openInterest":2,"bid":22.6,"ask":23.65,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760557132,"impliedVolatility":0.6650424121093752,"inTheMoney":true},{"contractSymbol":"AAPL251017P00275000","strike":275.0,"currency":"USD","lastPrice":25.52,"change":-1.0299988,"percentChange":-3.8794682,"volume":140,"openInterest":101,"bid":26.0,"ask":26.55,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760557131,"impliedVolatility":0.7553735400390625,"inTheMoney":true},{"contractSymbol":"AAPL251017P00277500","strike":277.5,"currency":"USD","lastPrice":28.8,"change":0.29999924,"percentChange":1.0526289,"volume":1,"openInterest":1,"bid":27.9,"ask":28.65,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760379759,"impliedVolatility":0.586918193359375,"inTheMoney":true},{"contractSymbol":"AAPL251017P00280000","strike":280.0,"currency":"USD","lastPrice":30.59,"change":-1.6100006,"percentChange":-5.000002,"volume":20,"openInterest":2,"bid":30.1,"ask":31.15,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760557132,"impliedVolatility":0.8164080859374999,"inTheMoney":true},{"contractSymbol":"AAPL251017P00285000","strike":285.0,"currency":"USD","lastPrice":27.05,"change":0.0,"percentChange":0.0,"volume":6,"openInterest":0,"bid":35.45,"ask":36.3,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1759503804,"impliedVolatility":0.7812521875,"inTheMoney":true},{"contractSymbol":"AAPL251017P00290000","strike":290.0,"currency":"USD","lastPrice":41.3,"change":0.0,"percentChange":0.0,"volume":17,"openInterest":0,"bid":40.8,"ask":41.45,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1760469781,"impliedVolatility":0.992187578125,"inTheMoney":true},{"contractSymbol":"AAPL251017P00295000","strike":295.0,"currency":"USD","lastPrice":68.15,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":0,"bid":49.1,"ask":50.5,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1757533800,"impliedVolatility":1.9128422326660157,"inTheMoney":true},{"contractSymbol":"AAPL251017P00300000","strike":300.0,"currency":"USD","lastPrice":44.1,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":0,"bid":50.8,"ask":51.6,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1759329373,"impliedVolatility":1.1982461962890625,"inTheMoney":true},{"contractSymbol":"AAPL251017P00305000","strike":305.0,"currency":"USD","lastPrice":48.05,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":0,"bid":55.1,"ask":56.15,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1759767393,"impliedVolatility":1.259769326171875,"inTheMoney":true},{"contractSymbol":"AAPL251017P00315000","strike":315.0,"currency":"USD","lastPrice":69.7,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":0,"bid":65.3,"ask":66.15,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1758311400,"impliedVolatility":1.05859845703125,"inTheMoney":true},{"contractSymbol":"AAPL251017P00320000","strike":320.0,"currency":"USD","lastPrice":65.4,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":0,"bid":70.55,"ask":71.4,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1759175400,"impliedVolatility":1.3876983740234374,"inTheMoney":true},{"contractSymbol":"AAPL251017P00330000","strike":330.0,"currency":"USD","lastPrice":98.75,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":74.2,"ask":75.1,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1755287400,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true},{"contractSymbol":"AAPL251017P00335000","strike":335.0,"currency":"USD","lastPrice":89.55,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":0,"bid":85.1,"ask":86.15,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1758306335,"impliedVolatility":1.7109389453125,"inTheMoney":true},{"contractSymbol":"AAPL251017P00355000","strike":355.0,"currency":"USD","lastPrice":157.02,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":141.2,"ask":142.45,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1744400796,"impliedVolatility":7.3303231027221685,"inTheMoney":true},{"contractSymbol":"AAPL251017P00360000","strike":360.0,"currency":"USD","lastPrice":162.01,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":146.25,"ask":147.6,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1744400796,"impliedVolatility":7.449951859436036,"inTheMoney":true},{"contractSymbol":"AAPL251017P00370000","strike":370.0,"currency":"USD","lastPrice":159.95,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":114.25,"ask":115.25,"contractSize":"REGULAR","expiration":1760659200,"lastTradeDate":1752846721,"impliedVolatility":1.0000000000000003E-5,"inTheMoney":true}]}]}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1761868800.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1761868800.json new file mode 100644 index 0000000..78558e2 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/options_v7_AAPL_1761868800.json @@ -0,0 +1 @@ +{"optionChain":{"result":[{"underlyingSymbol":"AAPL","expirationDates":[1761868800,1762473600,1763078400,1763683200,1764288000,1764892800,1766102400,1768521600,1771545600,1773964800,1776384000,1778803200,1781740800,1787270400,1789689600,1797552000,1799971200,1813190400,1829001600,1832025600],"strikes":[110.0,120.0,125.0,130.0,135.0,140.0,145.0,150.0,155.0,160.0,165.0,170.0,175.0,180.0,185.0,190.0,195.0,200.0,205.0,210.0,215.0,220.0,222.5,225.0,227.5,230.0,232.5,235.0,237.5,240.0,242.5,245.0,247.5,250.0,252.5,255.0,257.5,260.0,262.5,265.0,267.5,270.0,272.5,275.0,277.5,280.0,282.5,285.0,287.5,290.0,292.5,295.0,297.5,300.0,302.5,305.0,310.0,315.0,320.0,325.0,330.0],"hasMiniOptions":false,"quote":{"language":"en-US","region":"US","quoteType":"EQUITY","typeDisp":"Equity","quoteSourceName":"Nasdaq Real Time Price","triggerable":true,"customPriceAlertConfidence":"HIGH","postMarketChangePercent":0.38931692,"postMarketPrice":270.75,"postMarketChange":1.0499878,"regularMarketChange":0.700012,"regularMarketDayHigh":271.41,"regularMarketDayRange":"267.11 - 271.41","regularMarketDayLow":267.11,"regularMarketVolume":43332154,"regularMarketPreviousClose":269.0,"bid":255.72,"ask":269.86,"bidSize":1,"askSize":1,"fullExchangeName":"NasdaqGS","financialCurrency":"USD","regularMarketOpen":269.275,"averageDailyVolume3Month":54683671,"averageDailyVolume10Day":46237010,"fiftyTwoWeekLowChange":100.490005,"fiftyTwoWeekLowChangePercent":0.59387743,"fiftyTwoWeekRange":"169.21 - 271.41","fiftyTwoWeekHighChange":-1.7099915,"fiftyTwoWeekHighChangePercent":-0.0063003995,"fiftyTwoWeekLow":169.21,"fiftyTwoWeekHigh":271.41,"fiftyTwoWeekChangePercent":16.90569,"dividendDate":1755129600,"earningsTimestamp":1761854400,"earningsTimestampStart":1761854400,"earningsTimestampEnd":1761854400,"trailingPE":40.925644,"currency":"USD","corporateActions":[],"postMarketTime":1761782396,"regularMarketTime":1761768001,"exchange":"NMS","messageBoardId":"finmb_24937","exchangeTimezoneName":"America/New_York","exchangeTimezoneShortName":"EDT","gmtOffSetMilliseconds":-14400000,"market":"us_market","esgPopulated":false,"regularMarketChangePercent":0.260228,"regularMarketPrice":269.7,"shortName":"Apple Inc.","longName":"Apple Inc.","earningsCallTimestampStart":1761858000,"earningsCallTimestampEnd":1761858000,"isEarningsDateEstimate":false,"trailingAnnualDividendRate":1.01,"dividendRate":1.04,"trailingAnnualDividendYield":0.0037546468,"dividendYield":0.39,"epsTrailingTwelveMonths":6.59,"epsForward":8.31,"epsCurrentYear":7.3908,"priceEpsCurrentYear":36.491314,"sharesOutstanding":14840390000,"bookValue":4.431,"fiftyDayAverage":245.6484,"fiftyDayAverageChange":24.051605,"fiftyDayAverageChangePercent":0.09791069,"twoHundredDayAverage":222.7724,"twoHundredDayAverageChange":46.927612,"twoHundredDayAverageChangePercent":0.21065272,"marketCap":4002453389312,"forwardPE":32.454872,"priceToBook":60.866623,"sourceInterval":15,"exchangeDataDelayedBy":0,"averageAnalystRating":"2.0 - Buy","hasPrePostMarketData":true,"firstTradeDateMilliseconds":345479400000,"priceHint":2,"marketState":"POSTPOST","tradeable":false,"cryptoTradeable":false,"displayName":"Apple","symbol":"AAPL"},"options":[{"expirationDate":1761868800,"hasMiniOptions":false,"calls":[{"contractSymbol":"AAPL251031C00125000","strike":125.0,"currency":"USD","lastPrice":145.99,"change":2.1900024,"percentChange":1.5229502,"volume":2,"openInterest":29,"bid":143.5,"ask":145.45,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761746446,"impliedVolatility":5.398440751953125,"inTheMoney":true},{"contractSymbol":"AAPL251031C00140000","strike":140.0,"currency":"USD","lastPrice":107.52,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":2,"bid":128.6,"ask":130.4,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1760621651,"impliedVolatility":4.62500421875,"inTheMoney":true},{"contractSymbol":"AAPL251031C00150000","strike":150.0,"currency":"USD","lastPrice":118.51,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":20,"bid":118.65,"ask":120.4,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761664894,"impliedVolatility":4.191411010742188,"inTheMoney":true},{"contractSymbol":"AAPL251031C00155000","strike":155.0,"currency":"USD","lastPrice":105.37,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":315,"bid":113.5,"ask":115.5,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761243276,"impliedVolatility":4.0781299023437505,"inTheMoney":true},{"contractSymbol":"AAPL251031C00160000","strike":160.0,"currency":"USD","lastPrice":101.8,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":108.65,"ask":110.4,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761076602,"impliedVolatility":3.783203666992188,"inTheMoney":true},{"contractSymbol":"AAPL251031C00165000","strike":165.0,"currency":"USD","lastPrice":94.3,"change":0.0,"percentChange":0.0,"openInterest":1,"bid":103.5,"ask":105.5,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761232806,"impliedVolatility":3.673828940429688,"inTheMoney":true},{"contractSymbol":"AAPL251031C00170000","strike":170.0,"currency":"USD","lastPrice":92.86,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":6,"bid":98.65,"ask":100.45,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761321370,"impliedVolatility":3.4394545263671876,"inTheMoney":true},{"contractSymbol":"AAPL251031C00175000","strike":175.0,"currency":"USD","lastPrice":84.3,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":3,"bid":93.55,"ask":95.5,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761227523,"impliedVolatility":3.291017397460937,"inTheMoney":true},{"contractSymbol":"AAPL251031C00180000","strike":180.0,"currency":"USD","lastPrice":84.05,"change":0.0,"percentChange":0.0,"volume":12,"openInterest":33,"bid":89.25,"ask":90.55,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761330799,"impliedVolatility":2.488285029296875,"inTheMoney":true},{"contractSymbol":"AAPL251031C00185000","strike":185.0,"currency":"USD","lastPrice":83.8,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":3,"bid":83.9,"ask":85.5,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761662046,"impliedVolatility":0.500005,"inTheMoney":true},{"contractSymbol":"AAPL251031C00190000","strike":190.0,"currency":"USD","lastPrice":75.7,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":10,"bid":78.9,"ask":80.4,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761576764,"impliedVolatility":2.6826204809570315,"inTheMoney":true},{"contractSymbol":"AAPL251031C00195000","strike":195.0,"currency":"USD","lastPrice":73.69,"change":5.0,"percentChange":7.2790794,"volume":2,"openInterest":149,"bid":73.55,"ask":75.5,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761765332,"impliedVolatility":2.5791051147460937,"inTheMoney":true},{"contractSymbol":"AAPL251031C00200000","strike":200.0,"currency":"USD","lastPrice":68.48,"change":-0.23999786,"percentChange":-0.34924018,"volume":7,"openInterest":158,"bid":69.1,"ask":70.45,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761766447,"impliedVolatility":1.671876640625,"inTheMoney":true},{"contractSymbol":"AAPL251031C00205000","strike":205.0,"currency":"USD","lastPrice":62.7,"change":-0.95000076,"percentChange":-1.4925385,"volume":3,"openInterest":67,"bid":64.05,"ask":65.5,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761751955,"impliedVolatility":1.5468772656249998,"inTheMoney":true},{"contractSymbol":"AAPL251031C00210000","strike":210.0,"currency":"USD","lastPrice":58.67,"change":2.0699997,"percentChange":3.6572435,"volume":48,"openInterest":322,"bid":59.05,"ask":60.55,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761766875,"impliedVolatility":1.48047134765625,"inTheMoney":true},{"contractSymbol":"AAPL251031C00215000","strike":215.0,"currency":"USD","lastPrice":53.89,"change":-0.61999893,"percentChange":-1.1374041,"volume":38,"openInterest":293,"bid":53.9,"ask":55.5,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761765325,"impliedVolatility":0.500005,"inTheMoney":true},{"contractSymbol":"AAPL251031C00220000","strike":220.0,"currency":"USD","lastPrice":49.11,"change":0.060001373,"percentChange":0.12232696,"volume":6,"openInterest":1000,"bid":48.9,"ask":50.45,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761757805,"impliedVolatility":1.734376328125,"inTheMoney":true},{"contractSymbol":"AAPL251031C00222500","strike":222.5,"currency":"USD","lastPrice":46.28,"change":-0.97999954,"percentChange":-2.0736344,"volume":1,"openInterest":262,"bid":46.1,"ask":48.0,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761765067,"impliedVolatility":1.6796891015625,"inTheMoney":true},{"contractSymbol":"AAPL251031C00225000","strike":225.0,"currency":"USD","lastPrice":44.91,"change":0.3600006,"percentChange":0.80808216,"volume":77,"openInterest":2341,"bid":43.95,"ask":45.9,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767784,"impliedVolatility":1.2578162109375,"inTheMoney":true},{"contractSymbol":"AAPL251031C00227500","strike":227.5,"currency":"USD","lastPrice":40.46,"change":-1.5400009,"percentChange":-3.666669,"volume":3,"openInterest":1033,"bid":41.1,"ask":43.05,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761753687,"impliedVolatility":1.5439475927734374,"inTheMoney":true},{"contractSymbol":"AAPL251031C00230000","strike":230.0,"currency":"USD","lastPrice":38.71,"change":-0.5699997,"percentChange":-1.4511194,"volume":24,"openInterest":908,"bid":38.45,"ask":40.5,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761765932,"impliedVolatility":1.4448269946289063,"inTheMoney":true},{"contractSymbol":"AAPL251031C00232500","strike":232.5,"currency":"USD","lastPrice":35.5,"change":-2.0,"percentChange":-5.3333335,"volume":10,"openInterest":52,"bid":36.1,"ask":38.05,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761751588,"impliedVolatility":1.3867218164062498,"inTheMoney":true},{"contractSymbol":"AAPL251031C00235000","strike":235.0,"currency":"USD","lastPrice":34.5,"change":0.13000107,"percentChange":0.37823996,"volume":81,"openInterest":1883,"bid":34.3,"ask":35.8,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767109,"impliedVolatility":1.0810592822265626,"inTheMoney":true},{"contractSymbol":"AAPL251031C00237500","strike":237.5,"currency":"USD","lastPrice":31.11,"change":-0.7399998,"percentChange":-2.3233902,"volume":12,"openInterest":234,"bid":31.35,"ask":32.95,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761765392,"impliedVolatility":1.193363408203125,"inTheMoney":true},{"contractSymbol":"AAPL251031C00240000","strike":240.0,"currency":"USD","lastPrice":29.38,"change":-0.060001373,"percentChange":-0.20380901,"volume":83,"openInterest":1961,"bid":29.3,"ask":30.2,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767925,"impliedVolatility":0.6914093359375001,"inTheMoney":true},{"contractSymbol":"AAPL251031C00242500","strike":242.5,"currency":"USD","lastPrice":26.95,"change":0.32999992,"percentChange":1.2396691,"volume":6,"openInterest":488,"bid":26.85,"ask":28.35,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767827,"impliedVolatility":0.9003916210937499,"inTheMoney":true},{"contractSymbol":"AAPL251031C00245000","strike":245.0,"currency":"USD","lastPrice":24.85,"change":0.59000015,"percentChange":2.4319875,"volume":1555,"openInterest":3860,"bid":24.6,"ask":25.8,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767168,"impliedVolatility":0.8720715917968749,"inTheMoney":true},{"contractSymbol":"AAPL251031C00247500","strike":247.5,"currency":"USD","lastPrice":23.0,"change":1.0,"percentChange":4.5454545,"volume":56,"openInterest":1214,"bid":22.2,"ask":23.35,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767445,"impliedVolatility":0.8266618896484373,"inTheMoney":true},{"contractSymbol":"AAPL251031C00250000","strike":250.0,"currency":"USD","lastPrice":19.6,"change":-0.049999237,"percentChange":-0.25444904,"volume":942,"openInterest":7503,"bid":19.8,"ask":20.4,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767881,"impliedVolatility":0.6894562304687502,"inTheMoney":true},{"contractSymbol":"AAPL251031C00252500","strike":252.5,"currency":"USD","lastPrice":17.95,"change":0.80000114,"percentChange":4.6647296,"volume":92,"openInterest":1571,"bid":17.45,"ask":18.1,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767327,"impliedVolatility":0.676761044921875,"inTheMoney":true},{"contractSymbol":"AAPL251031C00255000","strike":255.0,"currency":"USD","lastPrice":15.45,"change":0.25,"percentChange":1.6447369,"volume":4682,"openInterest":6100,"bid":15.3,"ask":16.0,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767983,"impliedVolatility":0.6962920996093751,"inTheMoney":true},{"contractSymbol":"AAPL251031C00257500","strike":257.5,"currency":"USD","lastPrice":13.1,"change":0.10000038,"percentChange":0.7692337,"volume":505,"openInterest":2851,"bid":13.2,"ask":13.8,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767583,"impliedVolatility":0.6831086376953126,"inTheMoney":true},{"contractSymbol":"AAPL251031C00260000","strike":260.0,"currency":"USD","lastPrice":11.2,"change":0.18999958,"percentChange":1.7257,"volume":2048,"openInterest":11586,"bid":11.3,"ask":11.55,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767969,"impliedVolatility":0.6645541357421877,"inTheMoney":true},{"contractSymbol":"AAPL251031C00262500","strike":262.5,"currency":"USD","lastPrice":9.31,"change":0.1600008,"percentChange":1.7486427,"volume":1724,"openInterest":17211,"bid":9.4,"ask":9.6,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767962,"impliedVolatility":0.6508823974609376,"inTheMoney":true},{"contractSymbol":"AAPL251031C00265000","strike":265.0,"currency":"USD","lastPrice":7.59,"change":0.23000002,"percentChange":3.1250002,"volume":4928,"openInterest":17992,"bid":7.7,"ask":7.9,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767982,"impliedVolatility":0.6472203247070312,"inTheMoney":true},{"contractSymbol":"AAPL251031C00267500","strike":267.5,"currency":"USD","lastPrice":6.03,"change":0.05000019,"percentChange":0.8361236,"volume":6131,"openInterest":9269,"bid":6.15,"ask":6.3,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767974,"impliedVolatility":0.6364782446289063,"inTheMoney":true},{"contractSymbol":"AAPL251031C00270000","strike":270.0,"currency":"USD","lastPrice":4.8,"change":0.22000027,"percentChange":4.803499,"volume":26764,"openInterest":22123,"bid":4.8,"ask":5.0,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767998,"impliedVolatility":0.6335485864257813,"inTheMoney":false},{"contractSymbol":"AAPL251031C00272500","strike":272.5,"currency":"USD","lastPrice":3.65,"change":0.1500001,"percentChange":4.285717,"volume":13259,"openInterest":14363,"bid":3.7,"ask":3.85,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767996,"impliedVolatility":0.6311072045898438,"inTheMoney":false},{"contractSymbol":"AAPL251031C00275000","strike":275.0,"currency":"USD","lastPrice":2.82,"change":0.25,"percentChange":9.727627,"volume":14829,"openInterest":18707,"bid":2.8,"ask":2.85,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767999,"impliedVolatility":0.6264685791015626,"inTheMoney":false},{"contractSymbol":"AAPL251031C00277500","strike":277.5,"currency":"USD","lastPrice":2.07,"change":0.19999993,"percentChange":10.695183,"volume":4858,"openInterest":5423,"bid":2.06,"ask":2.11,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767990,"impliedVolatility":0.6259803027343751,"inTheMoney":false},{"contractSymbol":"AAPL251031C00280000","strike":280.0,"currency":"USD","lastPrice":1.5,"change":0.19000006,"percentChange":14.503821,"volume":16440,"openInterest":17968,"bid":1.5,"ask":1.54,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767999,"impliedVolatility":0.6281775463867189,"inTheMoney":false},{"contractSymbol":"AAPL251031C00282500","strike":282.5,"currency":"USD","lastPrice":1.06,"change":0.14999992,"percentChange":16.483507,"volume":4649,"openInterest":5291,"bid":1.08,"ask":1.13,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767982,"impliedVolatility":0.6342810009765626,"inTheMoney":false},{"contractSymbol":"AAPL251031C00285000","strike":285.0,"currency":"USD","lastPrice":0.8,"change":0.17000002,"percentChange":26.98413,"volume":10158,"openInterest":7512,"bid":0.79,"ask":0.82,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767989,"impliedVolatility":0.6430699755859376,"inTheMoney":false},{"contractSymbol":"AAPL251031C00287500","strike":287.5,"currency":"USD","lastPrice":0.57,"change":0.15,"percentChange":35.71429,"volume":2453,"openInterest":2689,"bid":0.57,"ask":0.61,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767986,"impliedVolatility":0.6547886083984377,"inTheMoney":false},{"contractSymbol":"AAPL251031C00290000","strike":290.0,"currency":"USD","lastPrice":0.41,"change":0.109999985,"percentChange":36.66666,"volume":4706,"openInterest":7216,"bid":0.42,"ask":0.46,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767992,"impliedVolatility":0.6694368994140626,"inTheMoney":false},{"contractSymbol":"AAPL251031C00292500","strike":292.5,"currency":"USD","lastPrice":0.31,"change":0.09,"percentChange":40.909092,"volume":1114,"openInterest":817,"bid":0.32,"ask":0.35,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767960,"impliedVolatility":0.6865265722656251,"inTheMoney":false},{"contractSymbol":"AAPL251031C00295000","strike":295.0,"currency":"USD","lastPrice":0.24,"change":0.08,"percentChange":50.0,"volume":894,"openInterest":6509,"bid":0.24,"ask":0.29,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767876,"impliedVolatility":0.70898728515625,"inTheMoney":false},{"contractSymbol":"AAPL251031C00297500","strike":297.5,"currency":"USD","lastPrice":0.2,"change":0.1,"percentChange":100.0,"volume":541,"openInterest":1253,"bid":0.2,"ask":0.22,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767909,"impliedVolatility":0.7304714453125,"inTheMoney":false},{"contractSymbol":"AAPL251031C00300000","strike":300.0,"currency":"USD","lastPrice":0.16,"change":0.06999999,"percentChange":77.77776,"volume":7109,"openInterest":9828,"bid":0.15,"ask":0.16,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767994,"impliedVolatility":0.7412135253906249,"inTheMoney":false},{"contractSymbol":"AAPL251031C00302500","strike":302.5,"currency":"USD","lastPrice":0.14,"change":0.08,"percentChange":133.33333,"volume":298,"openInterest":909,"bid":0.12,"ask":0.15,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767386,"impliedVolatility":0.77148666015625,"inTheMoney":false},{"contractSymbol":"AAPL251031C00305000","strike":305.0,"currency":"USD","lastPrice":0.11,"change":0.07,"percentChange":175.0,"volume":7028,"openInterest":2619,"bid":0.1,"ask":0.11,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767814,"impliedVolatility":0.78711150390625,"inTheMoney":false},{"contractSymbol":"AAPL251031C00310000","strike":310.0,"currency":"USD","lastPrice":0.08,"change":0.06,"percentChange":300.0,"volume":2174,"openInterest":3817,"bid":0.08,"ask":0.09,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767920,"impliedVolatility":0.84961087890625,"inTheMoney":false},{"contractSymbol":"AAPL251031C00315000","strike":315.0,"currency":"USD","lastPrice":0.08,"change":0.07,"percentChange":700.0001,"volume":3856,"openInterest":992,"bid":0.06,"ask":0.08,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767494,"impliedVolatility":0.9101571484375,"inTheMoney":false},{"contractSymbol":"AAPL251031C00320000","strike":320.0,"currency":"USD","lastPrice":0.04,"change":0.03,"percentChange":300.0,"volume":7248,"openInterest":1999,"bid":0.04,"ask":0.07,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767344,"impliedVolatility":0.9570316796874999,"inTheMoney":false},{"contractSymbol":"AAPL251031C00325000","strike":325.0,"currency":"USD","lastPrice":0.04,"change":0.03,"percentChange":300.0,"volume":3257,"openInterest":939,"bid":0.03,"ask":0.06,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767488,"impliedVolatility":1.0078174609375,"inTheMoney":false},{"contractSymbol":"AAPL251031C00330000","strike":330.0,"currency":"USD","lastPrice":0.03,"change":0.02,"percentChange":200.0,"volume":2182,"openInterest":971,"bid":0.02,"ask":0.04,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767171,"impliedVolatility":1.0351610742187503,"inTheMoney":false}],"puts":[{"contractSymbol":"AAPL251031P00110000","strike":110.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":25,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761658201,"impliedVolatility":3.6250009375000003,"inTheMoney":false},{"contractSymbol":"AAPL251031P00120000","strike":120.0,"currency":"USD","lastPrice":0.05,"change":0.0,"percentChange":0.0,"openInterest":1,"bid":0.0,"ask":0.02,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1757965090,"impliedVolatility":3.468751328125,"inTheMoney":false},{"contractSymbol":"AAPL251031P00125000","strike":125.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":6,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1760635357,"impliedVolatility":3.1250021875,"inTheMoney":false},{"contractSymbol":"AAPL251031P00130000","strike":130.0,"currency":"USD","lastPrice":0.05,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":13,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1759162638,"impliedVolatility":3.0000025,"inTheMoney":false},{"contractSymbol":"AAPL251031P00135000","strike":135.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":46,"openInterest":49,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1760967939,"impliedVolatility":2.81250296875,"inTheMoney":false},{"contractSymbol":"AAPL251031P00140000","strike":140.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":23,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761582339,"impliedVolatility":2.68750328125,"inTheMoney":false},{"contractSymbol":"AAPL251031P00145000","strike":145.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":17,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1759844128,"impliedVolatility":2.56250359375,"inTheMoney":false},{"contractSymbol":"AAPL251031P00150000","strike":150.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":65,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761582182,"impliedVolatility":2.43750390625,"inTheMoney":false},{"contractSymbol":"AAPL251031P00155000","strike":155.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":11,"openInterest":55,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1760983893,"impliedVolatility":2.3125042187499996,"inTheMoney":false},{"contractSymbol":"AAPL251031P00160000","strike":160.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":149,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761579470,"impliedVolatility":2.1875045312499997,"inTheMoney":false},{"contractSymbol":"AAPL251031P00165000","strike":165.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":22,"openInterest":139,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761232359,"impliedVolatility":2.0625048437499998,"inTheMoney":false},{"contractSymbol":"AAPL251031P00170000","strike":170.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":5,"openInterest":278,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761579456,"impliedVolatility":1.9375003125,"inTheMoney":false},{"contractSymbol":"AAPL251031P00175000","strike":175.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":6,"openInterest":248,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761571921,"impliedVolatility":1.8125009374999999,"inTheMoney":false},{"contractSymbol":"AAPL251031P00180000","strike":180.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":14,"openInterest":332,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761576113,"impliedVolatility":1.6875015625,"inTheMoney":false},{"contractSymbol":"AAPL251031P00185000","strike":185.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":2,"openInterest":230,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761230370,"impliedVolatility":1.5937520312499998,"inTheMoney":false},{"contractSymbol":"AAPL251031P00190000","strike":190.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":1,"openInterest":458,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761666372,"impliedVolatility":1.5000025,"inTheMoney":false},{"contractSymbol":"AAPL251031P00195000","strike":195.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":11,"openInterest":237,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761761352,"impliedVolatility":1.3750031249999999,"inTheMoney":false},{"contractSymbol":"AAPL251031P00200000","strike":200.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":1275,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761752269,"impliedVolatility":1.2812535937499998,"inTheMoney":false},{"contractSymbol":"AAPL251031P00205000","strike":205.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":3,"openInterest":250,"bid":0.0,"ask":0.01,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761679824,"impliedVolatility":1.1875040625,"inTheMoney":false},{"contractSymbol":"AAPL251031P00210000","strike":210.0,"currency":"USD","lastPrice":0.01,"change":0.0,"percentChange":0.0,"volume":83,"openInterest":2788,"bid":0.01,"ask":0.03,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767100,"impliedVolatility":1.2343788281249999,"inTheMoney":false},{"contractSymbol":"AAPL251031P00215000","strike":215.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":6,"openInterest":777,"bid":0.0,"ask":0.02,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761754444,"impliedVolatility":1.0625046875000002,"inTheMoney":false},{"contractSymbol":"AAPL251031P00220000","strike":220.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":330,"openInterest":3766,"bid":0.01,"ask":0.02,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767557,"impliedVolatility":0.992187578125,"inTheMoney":false},{"contractSymbol":"AAPL251031P00222500","strike":222.5,"currency":"USD","lastPrice":0.04,"change":0.02,"percentChange":100.0,"volume":65,"openInterest":83,"bid":0.0,"ask":0.03,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761763310,"impliedVolatility":0.937500625,"inTheMoney":false},{"contractSymbol":"AAPL251031P00225000","strike":225.0,"currency":"USD","lastPrice":0.02,"change":0.0,"percentChange":0.0,"volume":271,"openInterest":2927,"bid":0.01,"ask":0.03,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767609,"impliedVolatility":0.92187578125,"inTheMoney":false},{"contractSymbol":"AAPL251031P00227500","strike":227.5,"currency":"USD","lastPrice":0.03,"change":0.0,"percentChange":0.0,"volume":670,"openInterest":1246,"bid":0.01,"ask":0.03,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761762821,"impliedVolatility":0.867188828125,"inTheMoney":false},{"contractSymbol":"AAPL251031P00230000","strike":230.0,"currency":"USD","lastPrice":0.04,"change":0.01,"percentChange":33.333336,"volume":1692,"openInterest":4404,"bid":0.02,"ask":0.04,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767961,"impliedVolatility":0.8554701953125,"inTheMoney":false},{"contractSymbol":"AAPL251031P00232500","strike":232.5,"currency":"USD","lastPrice":0.04,"change":-0.010000002,"percentChange":-20.000002,"volume":1588,"openInterest":1344,"bid":0.04,"ask":0.05,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767454,"impliedVolatility":0.8437515624999999,"inTheMoney":false},{"contractSymbol":"AAPL251031P00235000","strike":235.0,"currency":"USD","lastPrice":0.05,"change":-0.02,"percentChange":-28.571428,"volume":1853,"openInterest":3688,"bid":0.04,"ask":0.05,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767989,"impliedVolatility":0.789064609375,"inTheMoney":false},{"contractSymbol":"AAPL251031P00237500","strike":237.5,"currency":"USD","lastPrice":0.05,"change":-0.040000003,"percentChange":-44.444447,"volume":315,"openInterest":7898,"bid":0.05,"ask":0.07,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767794,"impliedVolatility":0.7617211328125001,"inTheMoney":false},{"contractSymbol":"AAPL251031P00240000","strike":240.0,"currency":"USD","lastPrice":0.09,"change":-0.009999998,"percentChange":-9.999997,"volume":1839,"openInterest":7163,"bid":0.08,"ask":0.09,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767972,"impliedVolatility":0.7421900781249999,"inTheMoney":false},{"contractSymbol":"AAPL251031P00242500","strike":242.5,"currency":"USD","lastPrice":0.11,"change":-0.030000001,"percentChange":-21.428572,"volume":1155,"openInterest":1953,"bid":0.1,"ask":0.13,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767958,"impliedVolatility":0.71679970703125,"inTheMoney":false},{"contractSymbol":"AAPL251031P00245000","strike":245.0,"currency":"USD","lastPrice":0.16,"change":-0.049999997,"percentChange":-23.809523,"volume":2531,"openInterest":6923,"bid":0.15,"ask":0.16,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767989,"impliedVolatility":0.6914093359375001,"inTheMoney":false},{"contractSymbol":"AAPL251031P00247500","strike":247.5,"currency":"USD","lastPrice":0.24,"change":-0.030000016,"percentChange":-11.111116,"volume":2020,"openInterest":3423,"bid":0.21,"ask":0.25,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767964,"impliedVolatility":0.6777375976562501,"inTheMoney":false},{"contractSymbol":"AAPL251031P00250000","strike":250.0,"currency":"USD","lastPrice":0.35,"change":-0.060000002,"percentChange":-14.634147,"volume":6377,"openInterest":10042,"bid":0.32,"ask":0.35,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767961,"impliedVolatility":0.6621127539062501,"inTheMoney":false},{"contractSymbol":"AAPL251031P00252500","strike":252.5,"currency":"USD","lastPrice":0.52,"change":-0.060000002,"percentChange":-10.344828,"volume":2111,"openInterest":7338,"bid":0.47,"ask":0.5,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767994,"impliedVolatility":0.647464462890625,"inTheMoney":false},{"contractSymbol":"AAPL251031P00255000","strike":255.0,"currency":"USD","lastPrice":0.72,"change":-0.109999955,"percentChange":-13.253007,"volume":4713,"openInterest":6847,"bid":0.7,"ask":0.73,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767999,"impliedVolatility":0.6381872119140625,"inTheMoney":false},{"contractSymbol":"AAPL251031P00257500","strike":257.5,"currency":"USD","lastPrice":1.05,"change":-0.13,"percentChange":-11.01695,"volume":6301,"openInterest":6253,"bid":1.03,"ask":1.07,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767986,"impliedVolatility":0.6323278955078125,"inTheMoney":false},{"contractSymbol":"AAPL251031P00260000","strike":260.0,"currency":"USD","lastPrice":1.56,"change":-0.110000014,"percentChange":-6.5868278,"volume":7654,"openInterest":19169,"bid":1.49,"ask":1.53,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767970,"impliedVolatility":0.6279334082031252,"inTheMoney":false},{"contractSymbol":"AAPL251031P00262500","strike":262.5,"currency":"USD","lastPrice":2.2,"change":-0.12999988,"percentChange":-5.579394,"volume":4594,"openInterest":9483,"bid":2.09,"ask":2.15,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767985,"impliedVolatility":0.6237830590820312,"inTheMoney":false},{"contractSymbol":"AAPL251031P00265000","strike":265.0,"currency":"USD","lastPrice":3.04,"change":-0.16000009,"percentChange":-5.0000024,"volume":7171,"openInterest":8901,"bid":2.83,"ask":2.95,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767960,"impliedVolatility":0.6186561572265626,"inTheMoney":false},{"contractSymbol":"AAPL251031P00267500","strike":267.5,"currency":"USD","lastPrice":3.9,"change":-0.2999997,"percentChange":-7.142851,"volume":6763,"openInterest":3661,"bid":3.8,"ask":3.95,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767996,"impliedVolatility":0.6174354663085939,"inTheMoney":false},{"contractSymbol":"AAPL251031P00270000","strike":270.0,"currency":"USD","lastPrice":5.21,"change":-0.13999987,"percentChange":-2.61682,"volume":10888,"openInterest":6834,"bid":4.9,"ask":5.1,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767991,"impliedVolatility":0.6084023535156251,"inTheMoney":true},{"contractSymbol":"AAPL251031P00272500","strike":272.5,"currency":"USD","lastPrice":6.65,"change":-0.049999714,"percentChange":-0.7462644,"volume":1194,"openInterest":1022,"bid":6.25,"ask":6.45,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767822,"impliedVolatility":0.6022988989257814,"inTheMoney":true},{"contractSymbol":"AAPL251031P00275000","strike":275.0,"currency":"USD","lastPrice":8.25,"change":-0.25,"percentChange":-2.9411764,"volume":1377,"openInterest":4509,"bid":7.85,"ask":8.05,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767788,"impliedVolatility":0.6025430371093752,"inTheMoney":true},{"contractSymbol":"AAPL251031P00277500","strike":277.5,"currency":"USD","lastPrice":9.3,"change":-1.0500002,"percentChange":-10.144929,"volume":418,"openInterest":655,"bid":9.55,"ask":9.85,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767591,"impliedVolatility":0.5981485498046876,"inTheMoney":true},{"contractSymbol":"AAPL251031P00280000","strike":280.0,"currency":"USD","lastPrice":11.34,"change":-0.71000004,"percentChange":-5.892116,"volume":831,"openInterest":1345,"bid":11.5,"ask":11.8,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767615,"impliedVolatility":0.5988809643554689,"inTheMoney":true},{"contractSymbol":"AAPL251031P00282500","strike":282.5,"currency":"USD","lastPrice":14.1,"change":0.10000038,"percentChange":0.7142884,"volume":23,"openInterest":19,"bid":13.2,"ask":14.75,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761767790,"impliedVolatility":0.6479527392578125,"inTheMoney":true},{"contractSymbol":"AAPL251031P00285000","strike":285.0,"currency":"USD","lastPrice":16.5,"change":0.0,"percentChange":0.0,"volume":38,"openInterest":38,"bid":15.5,"ask":16.6,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761676559,"impliedVolatility":0.629886513671875,"inTheMoney":true},{"contractSymbol":"AAPL251031P00287500","strike":287.5,"currency":"USD","lastPrice":17.73,"change":-1.0200005,"percentChange":-5.4400024,"volume":2,"openInterest":11,"bid":17.45,"ask":19.35,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761745048,"impliedVolatility":0.6577182666015625,"inTheMoney":true},{"contractSymbol":"AAPL251031P00290000","strike":290.0,"currency":"USD","lastPrice":20.8,"change":-0.52000046,"percentChange":-2.4390266,"volume":41,"openInterest":16,"bid":20.0,"ask":21.35,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761762435,"impliedVolatility":0.6455113574218749,"inTheMoney":true},{"contractSymbol":"AAPL251031P00295000","strike":295.0,"currency":"USD","lastPrice":25.5,"change":-0.45000076,"percentChange":-1.7341069,"volume":2,"openInterest":14,"bid":24.7,"ask":26.5,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761763158,"impliedVolatility":0.726565234375,"inTheMoney":true},{"contractSymbol":"AAPL251031P00300000","strike":300.0,"currency":"USD","lastPrice":32.0,"change":1.0,"percentChange":3.2258065,"volume":8,"openInterest":0,"bid":29.6,"ask":31.6,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761766419,"impliedVolatility":0.8330094824218748,"inTheMoney":true},{"contractSymbol":"AAPL251031P00310000","strike":310.0,"currency":"USD","lastPrice":56.26,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":46.55,"ask":47.85,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1758807002,"impliedVolatility":2.48901744934082,"inTheMoney":true},{"contractSymbol":"AAPL251031P00315000","strike":315.0,"currency":"USD","lastPrice":51.78,"change":0.0,"percentChange":0.0,"volume":14,"openInterest":0,"bid":44.2,"ask":46.4,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1760986525,"impliedVolatility":0.500005,"inTheMoney":true},{"contractSymbol":"AAPL251031P00320000","strike":320.0,"currency":"USD","lastPrice":59.55,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":49.25,"ask":51.55,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761248907,"impliedVolatility":1.0351610742187503,"inTheMoney":true},{"contractSymbol":"AAPL251031P00325000","strike":325.0,"currency":"USD","lastPrice":64.57,"change":0.0,"percentChange":0.0,"openInterest":0,"bid":54.25,"ask":56.4,"contractSize":"REGULAR","expiration":1761868800,"lastTradeDate":1761248907,"impliedVolatility":0.945313046875,"inTheMoney":true}]}]}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_7203.T.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_7203.T.json new file mode 100644 index 0000000..3aa735a --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_7203.T.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"assetProfile":{"address1":"1 Toyota-cho","city":"Toyota","zip":"471-8571","country":"Japan","phone":"81 565 28 2121","website":"https://global.toyota/en","industry":"Auto Manufacturers","industryKey":"auto-manufacturers","industryDisp":"Auto Manufacturers","sector":"Consumer Cyclical","sectorKey":"consumer-cyclical","sectorDisp":"Consumer Cyclical","longBusinessSummary":"Toyota Motor Corporation designs, manufactures, assembles, and sells passenger vehicles, minivans and commercial vehicles, and related parts and accessories in Japan, North America, Europe, Asia, Central and South America, Oceania, Africa, and the Middle East. It operates through Automotive, Financial Services, and All Other segments. The company offers subcompact and compact cars; mini-vehicles; mid-size, luxury, sports, and specialty cars; recreational and sport-utility vehicles; pickup trucks; minivans; trucks; and buses. It also develops and sells battery electric vehicles and batteries. In addition, the company provides financial services, such as retail financing and leasing, wholesale financing, insurance, and credit cards. Further, it operates GAZOO.com, a web portal for automobile information, as well as engages in telecommunications and other businesses. It offers vehicles under the Toyota and Lexus brand names. Toyota Motor Corporation was founded in 1933 and is headquartered in Toyota, Japan.","fullTimeEmployees":389144,"companyOfficers":[{"maxAge":1,"name":"Mr. Koji Sato","age":55,"title":"President, CEO, Operating Officer & Director","yearBorn":1969,"fiscalYear":2025,"totalPay":{"raw":429000000,"fmt":"429M","longFmt":"429,000,000"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Yoichi Miyazaki","age":61,"title":"Operating Officer, EVP, Chief Competitive Officer, CFO & Director","yearBorn":1963,"fiscalYear":2025,"totalPay":{"raw":247000000,"fmt":"247M","longFmt":"247,000,000"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Hiroki Nakajima","age":62,"title":"Operating Officer, VP, Executive VP, CTO & Director","yearBorn":1962,"fiscalYear":2025,"totalPay":{"raw":243000000,"fmt":"243M","longFmt":"243,000,000"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Simon Humphries","age":57,"title":"Chief Branding Officer, Senior GM of Design, Head of Design & Operating Officer","yearBorn":1967,"fiscalYear":2025,"totalPay":{"raw":170000000,"fmt":"170M","longFmt":"170,000,000"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Takanori Azuma","title":"Chief Risk Officer & Chief Officer of Accounting Group","fiscalYear":2025,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Dr. Gill A. Pratt","title":"Chief Scientist, CEO & Executive Fellow of Toyota Research Institute, Inc.","fiscalYear":2025,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Masahiro Yamamoto","title":"Chief Officer of General Administration & Human Resources Group","fiscalYear":2025,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Didier Leroy","age":67,"title":"Chief Competitive Officer & President of Bus. Planning","yearBorn":1957,"fiscalYear":2025,"totalPay":{"raw":1786810000,"fmt":"1.79B","longFmt":"1,786,810,000"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Hirofumi Muta","age":68,"title":"Chief Officer -Safety & Health Promotion, Plant & Environmental Engg Divs, Production Control Group","yearBorn":1956,"fiscalYear":2025,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Steve St. Angelo","age":69,"title":"Senior Managing Officer & CEO of Latin America & Caribbean Region","yearBorn":1955,"fiscalYear":2025,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}}],"auditRisk":1,"boardRisk":8,"compensationRisk":1,"shareHolderRightsRisk":4,"overallRisk":5,"governanceEpochDate":1759276800,"compensationAsOfEpochDate":1767139200,"irWebsite":"http://www.toyota-global.com/investors/","executiveTeam":[],"maxAge":86400},"quoteType":{"exchange":"JPX","quoteType":"EQUITY","symbol":"7203.T","underlyingSymbol":"7203.T","shortName":"TOYOTA MOTOR CORP","longName":"Toyota Motor Corporation","firstTradeDateEpochUtc":925948800,"timeZoneFullName":"Asia/Tokyo","timeZoneShortName":"JST","uuid":"7fefb7fe-72a5-3aef-bc8b-6a6d9ecc4fe6","messageBoardId":"finmb_319676","gmtOffSetMilliseconds":32400000,"maxAge":1}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_AAPL.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_AAPL.json new file mode 100644 index 0000000..f210082 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_AAPL.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"assetProfile":{"address1":"One Apple Park Way","city":"Cupertino","state":"CA","zip":"95014","country":"United States","phone":"(408) 996-1010","website":"https://www.apple.com","industry":"Consumer Electronics","industryKey":"consumer-electronics","industryDisp":"Consumer Electronics","sector":"Technology","sectorKey":"technology","sectorDisp":"Technology","longBusinessSummary":"Apple Inc. designs, manufactures, and markets smartphones, personal computers, tablets, wearables, and accessories worldwide. The company offers iPhone, a line of smartphones; Mac, a line of personal computers; iPad, a line of multi-purpose tablets; and wearables, home, and accessories comprising AirPods, Apple TV, Apple Watch, Beats products, and HomePod. It also provides AppleCare support and cloud services; and operates various platforms, including the App Store that allow customers to discover and download applications and digital content, such as books, music, video, games, and podcasts, as well as advertising services include third-party licensing arrangements and its own advertising platforms. In addition, the company offers various subscription-based services, such as Apple Arcade, a game subscription service; Apple Fitness+, a personalized fitness service; Apple Music, which offers users a curated listening experience with on-demand radio stations; Apple News+, a subscription news and magazine service; Apple TV+, which offers exclusive original content; Apple Card, a co-branded credit card; and Apple Pay, a cashless payment service, as well as licenses its intellectual property. The company serves consumers, and small and mid-sized businesses; and the education, enterprise, and government markets. It distributes third-party applications for its products through the App Store. The company also sells its products through its retail and online stores, and direct sales force; and third-party cellular network carriers, wholesalers, retailers, and resellers. Apple Inc. was founded in 1976 and is headquartered in Cupertino, California.","fullTimeEmployees":150000,"companyOfficers":[{"maxAge":1,"name":"Mr. Timothy D. Cook","age":63,"title":"CEO & Director","yearBorn":1961,"fiscalYear":2024,"totalPay":{"raw":16520856,"fmt":"16.52M","longFmt":"16,520,856"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Ms. Katherine L. Adams","age":60,"title":"Senior VP & General Counsel","yearBorn":1964,"fiscalYear":2024,"totalPay":{"raw":5022182,"fmt":"5.02M","longFmt":"5,022,182"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Ms. Deirdre O'Brien","age":57,"title":"Senior Vice President of Retail & People","yearBorn":1967,"fiscalYear":2024,"totalPay":{"raw":5022182,"fmt":"5.02M","longFmt":"5,022,182"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Jeffrey E. Williams","age":60,"title":"Senior Vice President of Design, Watch, & Health","yearBorn":1964,"fiscalYear":2024,"totalPay":{"raw":5020737,"fmt":"5.02M","longFmt":"5,020,737"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Kevan Parekh","age":52,"title":"Senior VP & CFO","yearBorn":1972,"fiscalYear":2024,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Sabih Khan","title":"Chief Operating Officer","fiscalYear":2024,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Chris Kondo","title":"Senior Director of Corporate Accounting","fiscalYear":2024,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Suhasini Chandramouli","title":"Director of Investor Relations","fiscalYear":2024,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Ms. Kristin Huguet Quayle","title":"Vice President of Worldwide Communications","fiscalYear":2024,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Greg Joswiak","title":"Senior Vice President of Worldwide Marketing","fiscalYear":2024,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}}],"auditRisk":7,"boardRisk":1,"compensationRisk":3,"shareHolderRightsRisk":1,"overallRisk":1,"governanceEpochDate":1759276800,"compensationAsOfEpochDate":1735603200,"irWebsite":"http://investor.apple.com/","executiveTeam":[],"maxAge":86400},"quoteType":{"exchange":"NMS","quoteType":"EQUITY","symbol":"AAPL","underlyingSymbol":"AAPL","shortName":"Apple Inc.","longName":"Apple Inc.","firstTradeDateEpochUtc":345479400,"timeZoneFullName":"America/New_York","timeZoneShortName":"EDT","uuid":"8b10e4ae-9eeb-3684-921a-9ab27e4d87aa","messageBoardId":"finmb_24937","gmtOffSetMilliseconds":-14400000,"maxAge":1}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_AMZN.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_AMZN.json new file mode 100644 index 0000000..2c024c0 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_AMZN.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"assetProfile":{"address1":"410 Terry Avenue North","city":"Seattle","state":"WA","zip":"98109-5210","country":"United States","phone":"206 266 1000","website":"https://www.aboutamazon.com","industry":"Internet Retail","industryKey":"internet-retail","industryDisp":"Internet Retail","sector":"Consumer Cyclical","sectorKey":"consumer-cyclical","sectorDisp":"Consumer Cyclical","longBusinessSummary":"Amazon.com, Inc. engages in the retail sale of consumer products, advertising, and subscriptions service through online and physical stores in North America and internationally. The company operates through three segments: North America, International, and Amazon Web Services (AWS). It also manufactures and sells electronic devices, including Kindle, fire tablets, fire TVs, echo, ring, blink, and eero; and develops and produces media content. In addition, the company offers programs that enable sellers to sell their products in its stores; and programs that allow authors, independent publishers, musicians, filmmakers, Twitch streamers, skill and app developers, and others to publish and sell content. Further, it provides compute, storage, database, analytics, machine learning, and other services, as well as advertising services through programs, such as sponsored ads, display, and video advertising. Additionally, the company offers Amazon Prime, a membership program. The company's products offered through its stores include merchandise and content purchased for resale and products offered by third-party sellers. It also provides AgentCore services, such as AgentCore Runtime, AgentCore Memory, AgentCore Observability, AgentCore Identity, AgentCore Gateway, AgentCore Browser, and AgentCore Code Interpreter. It serves consumers, sellers, developers, enterprises, content creators, advertisers, and employees. Amazon.com, Inc. was incorporated in 1994 and is headquartered in Seattle, Washington.","fullTimeEmployees":1546000,"companyOfficers":[{"maxAge":1,"name":"Mr. Jeffrey P. Bezos","age":60,"title":"Founder & Executive Chairman","yearBorn":1964,"fiscalYear":2024,"totalPay":{"raw":1681840,"fmt":"1.68M","longFmt":"1,681,840"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Andrew R. Jassy","age":56,"title":"President, CEO & Director","yearBorn":1968,"fiscalYear":2024,"totalPay":{"raw":1596889,"fmt":"1.6M","longFmt":"1,596,889"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Brian T. Olsavsky","age":60,"title":"Senior VP & CFO","yearBorn":1964,"fiscalYear":2024,"totalPay":{"raw":371900,"fmt":"371.9k","longFmt":"371,900"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. David A. Zapolsky J.D.","age":60,"title":"Senior VP, Chief Global Affairs & Legal Officer","yearBorn":1964,"fiscalYear":2024,"totalPay":{"raw":371900,"fmt":"371.9k","longFmt":"371,900"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Matthew S. Garman","age":47,"title":"Chief Executive Officer of Amazon Web Series","yearBorn":1977,"fiscalYear":2024,"totalPay":{"raw":384275,"fmt":"384.27k","longFmt":"384,275"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Ms. Shelley L. Reynolds","age":59,"title":"VP, Worldwide Controller & Principal Accounting Officer","yearBorn":1965,"fiscalYear":2024,"totalPay":{"raw":163200,"fmt":"163.2k","longFmt":"163,200"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Dr. Werner Vogels","title":"Chief Technology Officer","fiscalYear":2024,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Dave Fildes","title":"Vice President of Investor Relations","fiscalYear":2024,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}}],"auditRisk":2,"boardRisk":8,"compensationRisk":10,"shareHolderRightsRisk":3,"overallRisk":9,"governanceEpochDate":1759276800,"compensationAsOfEpochDate":1735603200,"irWebsite":"http://phx.corporate-ir.net/phoenix.zhtml?c=97664&p=irol-irhome","executiveTeam":[],"maxAge":86400},"quoteType":{"exchange":"NMS","quoteType":"EQUITY","symbol":"AMZN","underlyingSymbol":"AMZN","shortName":"Amazon.com, Inc.","longName":"Amazon.com, Inc.","firstTradeDateEpochUtc":863703000,"timeZoneFullName":"America/New_York","timeZoneShortName":"EDT","uuid":"261fd26b-0151-3813-b0d0-97e4ed4c6505","messageBoardId":"finmb_18749","gmtOffSetMilliseconds":-14400000,"maxAge":1}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_GOOGL.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_GOOGL.json new file mode 100644 index 0000000..b1ea95e --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_GOOGL.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"assetProfile":{"address1":"1600 Amphitheatre Parkway","city":"Mountain View","state":"CA","zip":"94043","country":"United States","phone":"650-253-0000","website":"https://abc.xyz","industry":"Internet Content & Information","industryKey":"internet-content-information","industryDisp":"Internet Content & Information","sector":"Communication Services","sectorKey":"communication-services","sectorDisp":"Communication Services","longBusinessSummary":"Alphabet Inc. offers various products and platforms in the United States, Europe, the Middle East, Africa, the Asia-Pacific, Canada, and Latin America. It operates through Google Services, Google Cloud, and Other Bets segments. The Google Services segment provides products and services, including ads, Android, Chrome, devices, Gmail, Google Drive, Google Maps, Google Photos, Google Play, Search, and YouTube. It is also involved in the sale of apps and in-app purchases and digital content in the Google Play and YouTube; and devices, as well as in the provision of YouTube consumer subscription services. The Google Cloud segment offers AI infrastructure, Vertex AI platform, cybersecurity, data and analytics, and other services; Google Workspace that include cloud-based communication and collaboration tools for enterprises, such as Calendar, Gmail, Docs, Drive, and Meet; and other services for enterprise customers. The Other Bets segment sells healthcare-related and internet services. The company was incorporated in 1998 and is headquartered in Mountain View, California.","fullTimeEmployees":187103,"companyOfficers":[{"maxAge":1,"name":"Mr. Sundar Pichai","age":51,"title":"CEO & Director","yearBorn":1973,"fiscalYear":2024,"totalPay":{"raw":10319413,"fmt":"10.32M","longFmt":"10,319,413"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Ms. Ruth M. Porat","age":66,"title":"President & Chief Investment Officer","yearBorn":1958,"fiscalYear":2024,"totalPay":{"raw":3023363,"fmt":"3.02M","longFmt":"3,023,363"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Dr. Lawrence Edward Page II","age":51,"title":"Co-Founder & Director","yearBorn":1973,"fiscalYear":2024,"totalPay":{"raw":1,"fmt":"1","longFmt":"1"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Sergey Brin","age":50,"title":"Co-Founder & Director","yearBorn":1974,"fiscalYear":2024,"totalPay":{"raw":1,"fmt":"1","longFmt":"1"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Ms. Anat Ashkenazi","age":51,"title":"Senior VP & CFO","yearBorn":1973,"fiscalYear":2024,"totalPay":{"raw":11455556,"fmt":"11.46M","longFmt":"11,455,556"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. J. Kent Walker","age":63,"title":"President of Global Affairs, Chief Legal Officer & Company Secretary","yearBorn":1961,"fiscalYear":2024,"totalPay":{"raw":3019696,"fmt":"3.02M","longFmt":"3,019,696"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Philipp Schindler","age":53,"title":"Senior Vice President & Chief Business Officer of Google","yearBorn":1971,"fiscalYear":2024,"totalPay":{"raw":3051699,"fmt":"3.05M","longFmt":"3,051,699"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Ms. Amie Thuener O'Toole","age":49,"title":"Corporate Controller, Chief Accounting Officer & VP","yearBorn":1975,"fiscalYear":2024,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Ms. Ellen West","title":"Vice President of Investor Relations","fiscalYear":2024,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Ms. Fiona Clare Cicconi","age":58,"title":"Chief People Officer","yearBorn":1966,"fiscalYear":2024,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}}],"auditRisk":7,"boardRisk":9,"compensationRisk":10,"shareHolderRightsRisk":10,"overallRisk":10,"governanceEpochDate":1759276800,"compensationAsOfEpochDate":1735603200,"executiveTeam":[],"maxAge":86400},"quoteType":{"exchange":"NMS","quoteType":"EQUITY","symbol":"GOOGL","underlyingSymbol":"GOOGL","shortName":"Alphabet Inc.","longName":"Alphabet Inc.","firstTradeDateEpochUtc":1092922200,"timeZoneFullName":"America/New_York","timeZoneShortName":"EDT","uuid":"e15ce71f-f533-3912-9f11-a46c09e2412b","messageBoardId":"finmb_29096","gmtOffSetMilliseconds":-14400000,"maxAge":1}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_GS2C.DE.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_GS2C.DE.json new file mode 100644 index 0000000..447766e --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_GS2C.DE.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"assetProfile":{"address1":"625 Westport Parkway","city":"Grapevine","state":"TX","zip":"76051","country":"United States","phone":"817 424 2000","website":"https://www.gamestop.com","industry":"Specialty Retail","industryKey":"specialty-retail","industryDisp":"Specialty Retail","sector":"Consumer Cyclical","sectorKey":"consumer-cyclical","sectorDisp":"Consumer Cyclical","longBusinessSummary":"GameStop Corp., a specialty retailer, provides games and entertainment products through its stores and e-commerce platforms in the United States, Canada, Australia, and Europe. The company sells new and pre-owned gaming platforms; accessories, such as controllers, and gaming headsets; new and pre-owned gaming software; and in-game digital currency, digital downloadable content, and full-game downloads. It sells collectibles comprising apparel, toys, trading cards, gadgets, and other retail products for pop culture and technology enthusiasts. The company operates stores and e-commerce sites under the GameStop, EB Games, and Micromania brands; and pop culture themed stores that sell collectibles, apparel, gadgets, electronics, toys, and other retail products under the Zing Pop Culture brand. The company was formerly known as GSC Holdings Corp. GameStop Corp. was founded in 1996 and is headquartered in Grapevine, Texas.","fullTimeEmployees":6000,"companyOfficers":[{"maxAge":1,"name":"Mr. Ryan Cohen","age":38,"title":"President, CEO & Executive Chairman","yearBorn":1986,"fiscalYear":2024,"totalPay":{"raw":230381,"fmt":"230.38k","longFmt":"230,381"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Daniel William Moore","age":41,"title":"Principal Accounting Officer & Principal Financial Officer","yearBorn":1983,"fiscalYear":2024,"totalPay":{"raw":190009,"fmt":"190.01k","longFmt":"190,009"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Mark Haymond Robinson","age":46,"title":"General Counsel & Secretary","yearBorn":1978,"fiscalYear":2024,"totalPay":{"raw":171572,"fmt":"171.57k","longFmt":"171,572"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}}],"auditRisk":8,"boardRisk":8,"compensationRisk":6,"shareHolderRightsRisk":3,"overallRisk":6,"governanceEpochDate":1759276800,"compensationAsOfEpochDate":1735603200,"irWebsite":"http://phx.corporate-ir.net/phoenix.zhtml?c=130125&p=irol-irhome","executiveTeam":[],"maxAge":86400},"quoteType":{"exchange":"GER","quoteType":"EQUITY","symbol":"GS2C.DE","underlyingSymbol":"GS2C.DE","shortName":"Gamestop Corp. R","longName":"GameStop Corp.","firstTradeDateEpochUtc":1198742400,"timeZoneFullName":"Europe/Berlin","timeZoneShortName":"CET","uuid":"d0b5f8b9-574d-3dc3-99b9-59661c10aaeb","messageBoardId":"finmb_1342560","gmtOffSetMilliseconds":3600000,"maxAge":1}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_MSFT.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_MSFT.json new file mode 100644 index 0000000..9f03720 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_MSFT.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"assetProfile":{"address1":"One Microsoft Way","city":"Redmond","state":"WA","zip":"98052-6399","country":"United States","phone":"425 882 8080","website":"https://www.microsoft.com","industry":"Software - Infrastructure","industryKey":"software-infrastructure","industryDisp":"Software - Infrastructure","sector":"Technology","sectorKey":"technology","sectorDisp":"Technology","longBusinessSummary":"Microsoft Corporation develops and supports software, services, devices, and solutions worldwide. The company's Productivity and Business Processes segment offers Microsoft 365 Commercial, Enterprise Mobility + Security, Windows Commercial, Power BI, Exchange, SharePoint, Microsoft Teams, Security and Compliance, and Copilot; Microsoft 365 Commercial products, such as Windows Commercial on-premises and Office licensed services; Microsoft 365 Consumer products and cloud services, such as Microsoft 365 Consumer subscriptions, Office licensed on-premises, and other consumer services; LinkedIn; Dynamics products and cloud services, such as Dynamics 365, cloud-based applications, and on-premises ERP and CRM applications. Its Intelligent Cloud segment provides Server products and cloud services, such as Azure and other cloud services, GitHub, Nuance Healthcare, virtual desktop offerings, and other cloud services; Server products, including SQL and Windows Server, Visual Studio and System Center related Client Access Licenses, and other on-premises offerings; Enterprise and partner services, including Enterprise Support and Nuance professional Services, Industry Solutions, Microsoft Partner Network, and Learning Experience. The company's Personal Computing segment provides Windows and Devices, such as Windows OEM licensing and Devices and Surface and PC accessories; Gaming services and solutions, such as Xbox hardware, content, and services, first- and third-party content Xbox Game Pass, subscriptions, and Cloud Gaming, advertising, and other cloud services; search and news advertising services, such as Bing and Copilot, Microsoft News and Edge, and third-party affiliates. It sells its products through OEMs, distributors, and resellers; and online and retail stores. The company was founded in 1975 and is headquartered in Redmond, Washington.","fullTimeEmployees":228000,"companyOfficers":[{"maxAge":1,"name":"Mr. Satya Nadella","age":57,"title":"Chairman & CEO","yearBorn":1967,"fiscalYear":2025,"totalPay":{"raw":12251294,"fmt":"12.25M","longFmt":"12,251,294"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Bradford L. Smith LCA","age":65,"title":"President & Vice Chairman","yearBorn":1959,"fiscalYear":2025,"totalPay":{"raw":4532750,"fmt":"4.53M","longFmt":"4,532,750"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Ms. Amy E. Hood","age":52,"title":"Executive VP & CFO","yearBorn":1972,"fiscalYear":2025,"totalPay":{"raw":4444191,"fmt":"4.44M","longFmt":"4,444,191"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Takeshi Numoto","age":53,"title":"Executive VP & Chief Marketing Officer","yearBorn":1971,"fiscalYear":2025,"totalPay":{"raw":2871250,"fmt":"2.87M","longFmt":"2,871,250"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Judson B. Althoff","age":51,"title":"Executive VP & CEO of Commercial Business","yearBorn":1973,"fiscalYear":2025,"totalPay":{"raw":4490115,"fmt":"4.49M","longFmt":"4,490,115"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Ms. Carolina Dybeck Happe","age":52,"title":"Executive VP & COO","yearBorn":1972,"fiscalYear":2025,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Ms. Alice L. Jolla","age":58,"title":"Corporate VP & Chief Accounting Officer","yearBorn":1966,"fiscalYear":2025,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Jonathan Neilson","title":"Vice President of Investor Relations","fiscalYear":2025,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Jonathan M. Palmer","title":"Corporate Vice President & Chief Legal Officer","fiscalYear":2025,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Frank X. Shaw","title":"Chief Communications Officer","fiscalYear":2025,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}}],"auditRisk":9,"boardRisk":5,"compensationRisk":4,"shareHolderRightsRisk":2,"overallRisk":3,"governanceEpochDate":1759276800,"compensationAsOfEpochDate":1767139200,"irWebsite":"http://www.microsoft.com/investor/default.aspx","executiveTeam":[],"maxAge":86400},"quoteType":{"exchange":"NMS","quoteType":"EQUITY","symbol":"MSFT","underlyingSymbol":"MSFT","shortName":"Microsoft Corporation","longName":"Microsoft Corporation","firstTradeDateEpochUtc":511108200,"timeZoneFullName":"America/New_York","timeZoneShortName":"EDT","uuid":"b004b3ec-de24-385e-b2c1-923f10d3fb62","messageBoardId":"finmb_21835","gmtOffSetMilliseconds":-14400000,"maxAge":1}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_QQQ.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_QQQ.json new file mode 100644 index 0000000..252537d --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_QQQ.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"assetProfile":{"longBusinessSummary":"To maintain the correspondence between the composition and weights of the securities in the trust (the \"securities\") and the stocks in the NASDAQ-100 Index®, the adviser adjusts the securities from time to time to conform to periodic changes in the identity and/or relative weights of index securities. The composition and weighting of the securities portion of a portfolio deposit are also adjusted to conform to changes in the index.","companyOfficers":[],"executiveTeam":[],"maxAge":86400},"fundProfile":{"maxAge":1,"styleBoxUrl":"https://s.yimg.com/lq/i/fi/3_0stylelargeeq3.gif","family":"Invesco","categoryName":"Large Growth","legalType":"Exchange Traded Fund","managementInfo":{"managerName":null,"managerBio":null,"startdate":{}},"feesExpensesInvestment":{"annualReportExpenseRatio":{"raw":0.002,"fmt":"0.20%"},"frontEndSalesLoad":{},"deferredSalesLoad":{},"twelveBOne":{},"netExpRatio":{},"grossExpRatio":{},"annualHoldingsTurnover":{"raw":0.080699995,"fmt":"8.07%"},"totalNetAssets":{"raw":653084.1,"fmt":"653,084.12"},"projectionValues":{}},"feesExpensesInvestmentCat":{"annualReportExpenseRatio":{"raw":0.0092221,"fmt":"0.92%"},"frontEndSalesLoad":{},"deferredSalesLoad":{},"twelveBOne":{},"annualHoldingsTurnover":{"raw":0.5102,"fmt":"51.02%"},"totalNetAssets":{"raw":653084.1,"fmt":"653,084.12"},"projectionValuesCat":{}},"initInvestment":{},"initIraInvestment":{},"initAipInvestment":{},"subseqInvestment":{},"subseqIraInvestment":{},"subseqAipInvestment":{},"brokerages":[]},"quoteType":{"exchange":"NGM","quoteType":"ETF","symbol":"QQQ","underlyingSymbol":"QQQ","shortName":"Invesco QQQ Trust, Series 1","longName":"Invesco QQQ Trust","firstTradeDateEpochUtc":921076200,"timeZoneFullName":"America/New_York","timeZoneShortName":"EDT","uuid":"714d32d4-5f6c-33bc-b36a-765119cb3c4c","messageBoardId":"finmb_8108558","gmtOffSetMilliseconds":-14400000,"maxAge":1}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_SAP.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_SAP.json new file mode 100644 index 0000000..6bbf3f3 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_SAP.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"assetProfile":{"address1":"Dietmar-Hopp-Allee 16","city":"Walldorf","zip":"69190","country":"Germany","phone":"49 6227 7 47474","fax":"49 6227 7 57575","website":"https://www.sap.com","industry":"Software - Application","industryKey":"software-application","industryDisp":"Software - Application","sector":"Technology","sectorKey":"technology","sectorDisp":"Technology","longBusinessSummary":"SAP SE, together with its subsidiaries, provides enterprise application and business solutions worldwide. It offers SAP S/4HANA that provides software capabilities for finance, risk and project management, procurement, manufacturing, supply chain and asset management, and research and development; SAP SuccessFactors solutions for human resources, including HR, time, payroll, talent and employee experience management, and analytics and planning; and spend management solutions that covers direct and indirect spend, travel and expense, and external workforce management. The company also provides SAP customer experience solutions; SAP Business Technology platform that enables customers and partners to build, integrate, and automate applications; and SAP Business Network, a business-to-business collaboration platform that helps digitalize key business processes across the supply chain and enables communication between trading partners. In addition, it offers SAP Signavio to help customers to discover, analyze, and understand their business process operations; industry solutions that provides customers and partners with industry-specific solutions; and SAP LeanIX to visualize their as-is enterprise architecture, assess interdependencies and the potential impact of IT modernization, and manage the transition toward the target landscape with established practices and a detailed roadmap. Further, the company provides WalkMe to execute workflows across various number of applications; SAP Enable Now, which offers e-learning content embedded in SAP workflows; Taulia solutions for working capital management to help businesses create and deliver the right cash flow strategy, and the flexibility to adjust it to meet liquidity challenges; and sustainability solutions and services. Additionally, it provides services and support solutions. SAP SE was founded in 1972 and is headquartered in Walldorf, Germany.","fullTimeEmployees":110730,"companyOfficers":[{"maxAge":1,"name":"Mr. Christian Klein","age":44,"title":"CEO & Member of Executive Board","yearBorn":1980,"fiscalYear":2024,"totalPay":{"raw":5628184,"fmt":"5.63M","longFmt":"5,628,184"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Lars Lamade","age":53,"title":"Head of Global Sponsorships & Deputy Chairperson of the Supervisory Board","yearBorn":1971,"fiscalYear":2024,"totalPay":{"raw":372088,"fmt":"372.09k","longFmt":"372,088"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Dr. Hasso C. Plattner","age":80,"title":"Co-Founder","yearBorn":1944,"fiscalYear":2024,"totalPay":{"raw":208892,"fmt":"208.89k","longFmt":"208,892"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Dominik Asam","age":55,"title":"CFO & Member of Executive Board","yearBorn":1969,"fiscalYear":2024,"totalPay":{"raw":3824745,"fmt":"3.82M","longFmt":"3,824,745"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Thomas Saueressig","age":39,"title":"Customer Services & Delivery and Member of Executive Board","yearBorn":1985,"fiscalYear":2024,"totalPay":{"raw":3084066,"fmt":"3.08M","longFmt":"3,084,066"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Ms. Gina Vargiu-Breuer","age":48,"title":"Chief People Officer, Labor Director & Member of Executive Board","yearBorn":1976,"fiscalYear":2024,"totalPay":{"raw":2535877,"fmt":"2.54M","longFmt":"2,535,877"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Muhammad Alam","age":46,"title":"Lead Product Engineering & Member of Executive Board","yearBorn":1978,"fiscalYear":2024,"totalPay":{"raw":2489564,"fmt":"2.49M","longFmt":"2,489,564"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Ms. Margret Klein-Magar","age":60,"title":"VP, Head of SAP Alumni Relations & Member of Supervisory Board","yearBorn":1964,"fiscalYear":2024,"totalPay":{"raw":308442,"fmt":"308.44k","longFmt":"308,442"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Dr. Christine Regitz","age":58,"title":"VP & Global Head of SAP Women in Tech","yearBorn":1966,"fiscalYear":2024,"totalPay":{"raw":135220,"fmt":"135.22k","longFmt":"135,220"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Ms. Monika Kovachka-Dimitrova","age":49,"title":"Chief Operations Expert","yearBorn":1975,"fiscalYear":2024,"totalPay":{"raw":131140,"fmt":"131.14k","longFmt":"131,140"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}}],"compensationAsOfEpochDate":1735603200,"executiveTeam":[],"maxAge":86400},"quoteType":{"exchange":"NYQ","quoteType":"EQUITY","symbol":"SAP","underlyingSymbol":"SAP","shortName":"SAP SE","longName":"SAP SE","firstTradeDateEpochUtc":811431000,"timeZoneFullName":"America/New_York","timeZoneShortName":"EDT","uuid":"999bc6b1-8351-3be8-97d2-6f559ba408ae","messageBoardId":"finmb_126475","gmtOffSetMilliseconds":-14400000,"maxAge":1}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_TSCO.L.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_TSCO.L.json new file mode 100644 index 0000000..1269b4b --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_TSCO.L.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"assetProfile":{"address1":"Tesco House","address2":"Shire Park Kestrel Way","city":"Welwyn Garden City","zip":"AL7 1GA","country":"United Kingdom","phone":"44 330 6780 639","website":"https://www.tescoplc.com","industry":"Grocery Stores","industryKey":"grocery-stores","industryDisp":"Grocery Stores","sector":"Consumer Defensive","sectorKey":"consumer-defensive","sectorDisp":"Consumer Defensive","longBusinessSummary":"Tesco PLC, together with its subsidiaries, operates as a grocery retailer in the United Kingdom, Republic of Ireland, the Czech Republic, Slovakia, and Hungary. It offers grocery products through its stores, as well as online. The company is also involved in the food and drink wholesaling activities. In addition, it provides mobile virtual network operating services, as well as insurance products, such as for home, travel, pet, and car insurance products. Further, the company operates a network of one stop convenience stores; and offers data science, technology, software, and consultancy services. Tesco PLC was founded in 1919 and is based in Welwyn Garden City, the United Kingdom.","fullTimeEmployees":340000,"companyOfficers":[{"maxAge":1,"name":"Mr. Ken Murphy","age":58,"title":"Group CEO & Executive Director","yearBorn":1966,"fiscalYear":2025,"totalPay":{"raw":4536000,"fmt":"4.54M","longFmt":"4,536,000"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Imran Nawaz","age":51,"title":"CFO & Director","yearBorn":1973,"fiscalYear":2025,"totalPay":{"raw":2557000,"fmt":"2.56M","longFmt":"2,557,000"},"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. James Glavey","title":"Chief Operating Officer","fiscalYear":2025,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Guus Dekkers","age":59,"title":"Chief Technology Officer","yearBorn":1965,"fiscalYear":2025,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Ms. Kay Majid","title":"Group General Counsel","fiscalYear":2025,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Ms. Christine Heffernan","title":"Chief Communications & Sustainability Officer","fiscalYear":2025,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Ms. Emma Taylor","title":"Chief People Officer","fiscalYear":2025,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Ms. Natasha Elizabeth Adams","age":48,"title":"Chief Strategy, Commercial & Transformation Officer","yearBorn":1976,"fiscalYear":2025,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Mr. Ashwin Prasad","age":48,"title":"Chief Executive Officer of UK","yearBorn":1976,"fiscalYear":2025,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}},{"maxAge":1,"name":"Ms. Sarah Lawler","title":"MD of Tesco Convenience","fiscalYear":2025,"exercisedValue":{"raw":0,"fmt":null,"longFmt":"0"},"unexercisedValue":{"raw":0,"fmt":null,"longFmt":"0"}}],"auditRisk":7,"boardRisk":1,"compensationRisk":1,"shareHolderRightsRisk":1,"overallRisk":1,"governanceEpochDate":1759276800,"compensationAsOfEpochDate":1767139200,"executiveTeam":[],"maxAge":86400},"quoteType":{"exchange":"LSE","quoteType":"EQUITY","symbol":"TSCO.L","underlyingSymbol":"TSCO.L","shortName":"TESCO PLC ORD 6 1/3P","longName":"Tesco PLC","firstTradeDateEpochUtc":583743600,"timeZoneFullName":"Europe/London","timeZoneShortName":"GMT","uuid":"7f8b4287-5961-3c42-8687-85f57a4754d4","messageBoardId":"finmb_413744","gmtOffSetMilliseconds":0,"maxAge":1}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_VFINX.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_VFINX.json new file mode 100644 index 0000000..855cd55 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_api_assetProfile-quoteType-fundProfile_VFINX.json @@ -0,0 +1 @@ +{"quoteSummary":{"result":[{"assetProfile":{"address1":"Valley Forge, PA 19482","address2":"PO Box 2600","address3":"Vanguard Index Funds","phone":"800-523-1036","longBusinessSummary":"The fund employs an indexing investment approach designed to track the performance of the Standard & Poor's 500 Index, a widely recognized benchmark of U.S. stock market performance that is dominated by the stocks of large U.S. companies. The advisor attempts to replicate the target index by investing all, or substantially all, of its assets in the stocks that make up the index, holding each stock in approximately the same proportion as its weighting in the index. The fund is non-diversified.","companyOfficers":[],"executiveTeam":[],"maxAge":86400},"fundProfile":{"maxAge":1,"styleBoxUrl":"https://s.yimg.com/lq/i/fi/3_0stylelargeeq2.gif","family":"Vanguard","categoryName":"Large Blend","legalType":null,"managementInfo":{"managerName":"","managerBio":"Aurélie Denis, CFA, Portfolio Manager at Vanguard. She has been with Vanguard since 2016, has worked in investment management since 2017, has managed investment portfolios since 2023, and has co-managed the Fund since February 2023. Education: B.S., Pennsylvania State University.","startdate":{"raw":1739836800,"fmt":"2025-02-18"}},"feesExpensesInvestment":{"annualReportExpenseRatio":{"raw":0.0014,"fmt":"0.14%"},"frontEndSalesLoad":{},"deferredSalesLoad":{},"twelveBOne":{},"netExpRatio":{"raw":0.0014,"fmt":"0.14%"},"grossExpRatio":{"raw":0.0014,"fmt":"0.14%"},"annualHoldingsTurnover":{},"totalNetAssets":{},"projectionValues":{"5y":{"raw":0.0,"fmt":"0"},"3y":{"raw":0.0,"fmt":"0"},"10y":{"raw":0.0,"fmt":"0"}}},"feesExpensesInvestmentCat":{"annualReportExpenseRatio":{"raw":0.0078231,"fmt":"0.78%"},"frontEndSalesLoad":{},"deferredSalesLoad":{},"twelveBOne":{},"annualHoldingsTurnover":{},"totalNetAssets":{},"projectionValuesCat":{"5y":{"raw":0.0,"fmt":"0"},"3y":{"raw":0.0,"fmt":"0"},"10y":{"raw":0.0,"fmt":"0"}}},"initInvestment":{"raw":0.0,"fmt":"0"},"initIraInvestment":{},"initAipInvestment":{},"subseqInvestment":{"raw":0.0,"fmt":"0"},"subseqIraInvestment":{},"subseqAipInvestment":{},"brokerages":["TradeStation Securities","Northwestern Mutual Inv Srvc, LLC","Cetera Advisors LLC","Cetera Advisor Networks LLC","Protected Investors of America","Comerica Bank","E TRADE Financial","H&R Block Financial Advisors Inc","Mid Atlantic Capital Corp","Morgan Stanley - Brokerage Accounts","Pershing FundCenter","Schwab Institutional","EP Fee Small","Shareholders Services Group","JPMorgan","Merrill Lynch","T. Rowe Price","TD Ameritrade Trust Company","(Junk)CommonWealth PPS","LPL SAM Eligible","Fidelity Retail FundsNetwork","Fidelity Institutional FundsNetwork","DATALynx","Ameriprise Brokerage","Federated TrustConnect","Cetera Advisors LLC – PAM, PRIME, Premier, mAA Advisor","Cetera Wealth Services LLC – PAM, PRIME, Premier, mAA Advisor","Nationwide Retirement Resource","RBC Wealth Management-Advisory Eligible","Schwab Institutional Only","Ameriprise SPS Advantage","Mony Securities Corp","SunAmerica Securities Premier / Pinnacle","Vanguard NTF","ETrade No Load Fee","SunGard Transaction Network","Royal Alliance","Raymond James","Raymond James WRAP Eligible","Bear Stearns No-Load Transaction Fee","Commonwealth Universe","FTJ FundChoice","Robert W. Baird & Co.","HSBC Brokerage (USA) Inc","JPMorgan INVEST","WFA MF Advisory Updated 10.1.25","RBC Wealth Management-Network Eligible","DailyAccess Corporation RTC","DailyAccess Corporation FRIAG","WR Hambrecht Co LLC","Sterne, Agee & Leach, Inc.,","EP Fee Large","ING Financial Ptnrs PAM and PRIME Approv","Firstrade","Scottrade TF","Standard Retirement Services, Inc.","Diversified 401(k)","Principal Advantage","TIAA-CREF Brokerage Services","Thrivent – Advisory Eligible","Morgan Stanley Consulting Group Advisor","Matrix Financial Solutions","Trade PMR Transaction Fee","DWS Retirement Sevs Investment Offerings","TD Ameritrade Retail","TD Ameritrade Institutional","Nationwide Retirement Clear Adva","ING Financial Advisers - SAS Funds","NYLIM 401(k) Complete","Met Life Resources MFSP Alliance List","ADP Access","Mid Atlantic Capital Group","HD Vest - Vest Advisor","Securities America Advisors","Bear Stearns","JP MORGAN NO-LOAD NTF","JP MORGAN LOAD","JP MORGAN NO-LOAD TRANSACTION FEE","MSWM Brokerage","WFA Fdntl Choice/PIM Updated 10.1.25","ADP Access Open Fund Architecture","DailyAccess Corporation Mid-Atlantic","RBC Wealth Management-Wrap Eligible","Waddell & Reed Choice MAP Flex","E-Plan Services, Inc.","H Beck Inc.","ING Financial Partners Inc.","Securities America Inc.","Morgan Stanley Portfolio Management","Nationwide Retirement Flexible Advantage","Smith Barney Portfolio Manager","Merrill Edge","DailyAccess Corporation Matrix","DailyAccess Corporation MATC","WF MFAQ Screen Updated 03/31/2015","Cetera Financial Specialist LLC – Premier, mAA Advisor","LPL SWM","Schwab All (Retail, Instl, Retirement)","Lincoln Investment Planning","Pershing Retirement Plan Network","Ameriprise SPS Advisor","HD Vest","Kestra Financial","Commonwealth (PPS Access Program)","Commonwealth (NTF - PPS/Advisory)","Commonwealth (PPS/Advisory)","ADP Advisor Access 3(21) Tier 0","ADP Advisor Access Tier 0","LPL Brokerage Availability","Thrivent – Retail Eligible","Pruco Securities LLC -PruChoice Mutual Funds","Pruco Securities LLC -PruUMA’s Mutual Funds","ADP Raymond James 3(38)","Avantax","Allowab (non-advisory)","Mid Atlantic Clearing and Settlement","Schwab (Transaction Fee)"]},"quoteType":{"exchange":"NAS","quoteType":"MUTUALFUND","symbol":"VFINX","underlyingSymbol":"VFINX","shortName":"Vanguard Index Trust 500 Index ","longName":"Vanguard 500 Index Fund","firstTradeDateEpochUtc":315671400,"timeZoneFullName":"America/New_York","timeZoneShortName":"EDT","uuid":"a659dd86-1f1c-3b4e-9eb8-48926035713f","messageBoardId":"finmb_28117396","gmtOffSetMilliseconds":-14400000,"maxAge":1}}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_html_AAPL.html b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_html_AAPL.html new file mode 100644 index 0000000..d0dda72 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_html_AAPL.html @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Apple Inc. (AAPL) Stock Price, News, Quote & History - Yahoo Finance + + + +
+
NasdaqGS - Nasdaq Real Time Price USD

Apple Inc. (AAPL)

268.80
-0.20
(-0.07%)
As of 12:37:00 PM EDT. Market Open.
AAPL Q4 2025 earnings call
October 30, 2025 at 5 PM EDT
Chart Range Bar
Loading chart for AAPL
  • Previous Close 269.00
  • Open 269.27
  • Bid 268.78 x 100
  • Ask 269.00 x 700
  • Day's Range 267.11 - 271.41
  • 52 Week Range 169.21 - 271.41
  • Volume 21,354,043
  • Avg. Volume 54,683,671
  • Market Cap (intraday) 3.989T
  • Beta (5Y Monthly) 1.09
  • PE Ratio (TTM) 40.79
  • EPS (TTM) 6.59
  • Earnings Date Oct 30, 2025
  • Forward Dividend & Yield 1.04 (0.39%)
  • Ex-Dividend Date Aug 11, 2025
  • 1y Target Est 256.01

Apple Inc. designs, manufactures, and markets smartphones, personal computers, tablets, wearables, and accessories worldwide. The company offers iPhone, a line of smartphones; Mac, a line of personal computers; iPad, a line of multi-purpose tablets; and wearables, home, and accessories comprising AirPods, Apple TV, Apple Watch, Beats products, and HomePod. It also provides AppleCare support and cloud services; and operates various platforms, including the App Store that allow customers to discover and download applications and digital content, such as books, music, video, games, and podcasts, as well as advertising services include third-party licensing arrangements and its own advertising platforms. In addition, the company offers various subscription-based services, such as Apple Arcade, a game subscription service; Apple Fitness+, a personalized fitness service; Apple Music, which offers users a curated listening experience with on-demand radio stations; Apple News+, a subscription news and magazine service; Apple TV+, which offers exclusive original content; Apple Card, a co-branded credit card; and Apple Pay, a cashless payment service, as well as licenses its intellectual property. The company serves consumers, and small and mid-sized businesses; and the education, enterprise, and government markets. It distributes third-party applications for its products through the App Store. The company also sells its products through its retail and online stores, and direct sales force; and third-party cellular network carriers, wholesalers, retailers, and resellers. Apple Inc. was founded in 1976 and is headquartered in Cupertino, California.

www.apple.com

150,000

Full Time Employees

September 28

Fiscal Year Ends

Recent News: AAPL

View More

News headlines Apple is preparing to release its Q4 earnings report, highlighting strong iPhone 17 sales growth. Analysts are optimistic, with Bank of America raising the price target to $320, while concerns about slowing sales momentum persist.

Apple is preparing to release its Q4 earnings report, highlighting strong iPhone 17 sales growth. Analysts are optimistic, with Bank of America raising the price target to $320, while concerns about slowing sales momentum persist.

Updated 13 minutes ago · Powered by Yahoo Finance AI

Related Videos: AAPL

Performance Overview: AAPL

Trailing total returns as of 10/29/2025, which may include dividends or other distributions. Benchmark is S&P 500 (^GSPC) .

YTD Return

AAPL
7.73%
S&P 500 (^GSPC)
17.43%

1-Year Return

AAPL
15.58%
S&P 500 (^GSPC)
18.41%

3-Year Return

AAPL
75.34%
S&P 500 (^GSPC)
77.05%

5-Year Return

AAPL
139.66%
S&P 500 (^GSPC)
108.66%

Earnings Trends: AAPL

View More

Earnings Per Share

GAAP
Normalized
GAAP
Normalized
 

Revenue vs. Earnings

Annual
Quarterly
Annual
Quarterly
Q3 FY25
Revenue 94.04B
Earnings 23.43B

Q4

FY24

Q1

FY25

Q2

FY25

Q3

FY25

0
20B
40B
60B
80B
100B
120B
 

Analyst Insights: AAPL

View More

Top Analyst

Daiwa Capital
76/100
Latest Rating
Outperform
 

Analyst Price Targets

180.00 Low
256.01 Average
268.80 Current
320.00 High
 

Analyst Recommendations

  • Strong Buy
  • Buy
  • Hold
  • Underperform
  • Sell
 

Latest Rating

Date 10/29/2025
Analyst B of A Securities
Rating Action Maintains
Rating Buy
Price Action Raises
Price Target 270 -> 320
 

Statistics: AAPL

View More

Valuation Measures

Annual
As of 10/2/2025
  • Market Cap

    3.82T

  • Enterprise Value

    3.86T

  • Trailing P/E

    39.02

  • Forward P/E

    32.05

  • PEG Ratio (5yr expected)

    2.47

  • Price/Sales (ttm)

    9.50

  • Price/Book (mrq)

    57.97

  • Enterprise Value/Revenue

    9.45

  • Enterprise Value/EBITDA

    27.26

Financial Highlights

Profitability and Income Statement

  • Profit Margin

    24.30%

  • Return on Assets (ttm)

    24.55%

  • Return on Equity (ttm)

    149.81%

  • Revenue (ttm)

    408.62B

  • Net Income Avi to Common (ttm)

    99.28B

  • Diluted EPS (ttm)

    6.59

Balance Sheet and Cash Flow

  • Total Cash (mrq)

    55.37B

  • Total Debt/Equity (mrq)

    154.49%

  • Levered Free Cash Flow (ttm)

    94.87B

Compare To: AAPL

Select to analyze similar companies using key performance metrics; select up to 4 stocks.

Company Insights: AAPL

Fair Value

268.80 Current
 

Dividend Score

0 Low
Sector Avg.
100 High
 

Hiring Score

0 Low
Sector Avg.
100 High
 

Insider Sentiment Score

0 Low
Sector Avg.
100 High
 

Research Reports: AAPL

View More
  • The Argus Innovation Model Portfolio

    The United States economy is full of innovation. It has to be. Manufacturing industries that dominated the economy decades ago - textiles, televisions, even automobiles to a large degree - have moved overseas, where labor and materials costs are lower. Yet the U.S. economy, even during the pandemic and the recent period of high inflation, has expanded to record levels. If U.S. corporations weren't innovating, creating new products (such as AI and vaccines) and services (such as Zoom calls and Netflix), as well as moving into new markets (clean energy, rare drugs), the domestic economy would not be growing, and capital would not be flooding into the country. Consider that U.S. GDP was approximately $1 trillion in 1930 but was almost $30 trillion at the end of 2024. That's growth of 30-times. Meanwhile, the U.S. population has grown less than 3-times during that time span, to 340 million from 120 million. The delta between GDP growth and population growth has been driven, in large part, by innovation.

     
  • The steady march of the charts from bottom left to top right continues, and it feels like the one-day downside wonder on October 10 never happened.

    The steady march of the charts from bottom left to top right continues, and it feels like the one-day downside wonder on October 10 never happened. The major indices are near or at all-time highs as we are firmly in the most favorable seasonal time of the year. We are also about to enter a three-week period with many, many earnings reports. We think next week will be critical, as five of the eight companies with market caps above a trillion dollars will report (MSFT, META, GOOGL, AMZN, and AAPL). All the mega-cap as well as the mid- and small-cap indices rose between 1.1% and 1.6% on Monday, and breadth was fairly strong -- with NYSE advancers leading decliners by 1,700. Advancing volume was more than 80% of total NYSE volume for the second time in the past six days. Sector strength was broad again, with nine of 11 S&P 500 sectors up between 0.8% and 1.2%. Some breadth measures remain neutral to slightly bullish on an absolute basis, and weak-to-neutral on a relative basis if we consider what most indices have done since April. Some 57% of companies in the Nasdaq 100 (QQQ) are above their 200-day moving average, down from 86% three months ago. For the S&P 500, the reading is 65%, and for the S&P 100, it is 71%. Historically, these numbers are around 75%-85% after an advance such as we have seen. The deterioration in the QQQ is at least mildly concerning. But because of the heavy IT weight in the index, the weakening breadth doesn't seem to have hurt the price action. If the titans start to correct, there isn't enough underlying strength to support the index. (Mark Arbeter, CMT)

     
  • Global Stocks Performing Well

    We have three strategic asset-allocation models, based on risk: Conservative, Growth, and Aggressive. We make tactical adjustments to the models, based on our outlooks for the segments of the capital markets. In terms of performance, stocks and bonds started the year close, but stocks have pulled ahead through September. From an asset-allocation standpoint, our Stock-Bond Barometer still slightly favors bonds over stocks for long-term portfolio positioning. We are over-weight on large-cap stocks at this stage of the market cycle, favoring them for growth exposure, financial strength, and exposure to the IT sector. Our recommended exposure to small- and mid-caps is 5%-10% of equity allocation, below the benchmark weighting. The dearth of IPOs has meant a lack of exciting new companies in the marketplace. While top-tier stocks (i.e., the Mag 7) have soared on enthusiasm for AI, the legacy small companies haven't been able to innovate to the same degree. One of the market surprises this year has been the performance of global stocks, which have taken an impressive performance lead in 2025. We expect the long-term trend favoring U.S. stocks to ultimately re-emerge, given volatile global economic, political, geopolitical, and currency conditions. That said, international stocks offer favorable near-term valuations, and we target 15%-20% of equity exposure to the group. In terms of growth and value, value got off to a good start in 2025 and looks likely to outperform if the market returns to a risk-off environment. Over the long term, we anticipate that growth, led by the IT sector, will top returns from value, led by Energy, Real Estate and Materials, due to favorable secular and demographic trends.

     
  • Stock indices saw nice gains again on Monday, as the technology road grader (AI) keeps grinding along.

    Stock indices saw nice gains again on Monday, as the technology road grader (AI) keeps grinding along. Monday it was tech and only tech, as the sector ETF (XLK) popped 1.5%. Other than the very low-weighted Utilities sector, which rose 1%, there were minor gains in Real Estate and Industrials and losses or no movement in the seven other S&P 500 sectors. NYSE breadth was flat and NYSE advancing volume versus declining volume also was even. Yet the S&P 500 gained 0.4%, the S&P 100 added 0.5%, the Nasdaq rose 0.7%, and the Nasdaq 100 was up 0.6%. We did see some continued participation by the small-cap indices and even the Russell 2000 crept into all-time high territory last week. The mega-cap dominance continues and the daily moves in these behemoths remains jaw dropping. Computer hardware popped 4%, led by AAPL's 4.3% surge. Semiconductors climbed about 2% (depending on which index you use), aided by NVDA's 4% spike as well as a 3% gain by TSM. Semi equipment names remain on fire, with LRCX, AMAT, and KLAC all adding between 2.5% and 5.5%. Components of the computer services and electrical components industries also had a big day. It certainly feels like we are in the melt-up stage of the rally that started back in April, as the major indices have once again reached overbought conditions on the daily charts and have cycled into overbought territory on a weekly basis. The price action has been steep since early September, and the angle of ascent is starting to accelerate on the Nasdaq and the QQQ. While this can go on for some time, it is a warning. (Mark Arbeter, CMT)

     

People Also Watch

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_html_QQQ.html b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_html_QQQ.html new file mode 100644 index 0000000..fe180b0 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_html_QQQ.html @@ -0,0 +1,281 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Invesco QQQ Trust (QQQ) Stock Price, News, Quote & History - Yahoo Finance + + + +
+
NasdaqGM - Delayed Quote USD

Invesco QQQ Trust (QQQ)

605.49
-5.89
(-0.96%)
At close: 4:00:01 PM EDT
605.52
+0.03
+(0.00%)
After hours: 7:22:01 PM EDT
Chart Range Bar
Loading chart for QQQ
  • Previous Close 611.38
  • Open 610.82
  • Bid 604.50 x 5300
  • Ask 606.25 x 100
  • Day's Range 599.74 - 611.41
  • 52 Week Range 402.39 - 613.18
  • Volume 58,939,632
  • Avg. Volume 50,906,957
  • Net Assets 385.76B
  • NAV 611.70
  • PE Ratio (TTM) 34.14
  • Yield 0.47%
  • YTD Daily Total Return 20.07%
  • Beta (5Y Monthly) 1.10
  • Expense Ratio (net) 0.20%

To maintain the correspondence between the composition and weights of the securities in the trust (the "securities") and the stocks in the NASDAQ-100 Index®, the adviser adjusts the securities from time to time to conform to periodic changes in the identity and/or relative weights of index securities. The composition and weighting of the securities portion of a portfolio deposit are also adjusted to conform to changes in the index.

Invesco

Fund Family

Large Growth

Fund Category

385.76B

Net Assets

1999-03-10

Inception Date

Performance Overview: QQQ

View More

Trailing returns as of 10/20/2025. Category is Large Growth.

YTD Return

QQQ
20.07%
Category
15.25%
 

1-Year Return

QQQ
24.34%
Category
21.73%
 

3-Year Return

QQQ
32.31%
Category
28.11%
 

People Also Watch

Holdings: QQQ

View More

Top 10 Holdings (52.92% of Total Assets)

SymbolCompany% Assets
NVIDIA Corporation 9.88%
Microsoft Corporation 8.39%
Apple Inc. 8.24%
Broadcom Inc. 5.59%
Amazon.com, Inc. 5.10%
Tesla, Inc. 3.53%
Meta Platforms, Inc. 3.47%
Alphabet Inc. 3.08%
Alphabet Inc. 2.88%
Netflix, Inc. 2.73%

Sector Weightings

SectorQQQ
Technology   54.42%
Healthcare   4.22%
Industrials   3.35%
Utilities   1.37%
Energy   0.48%
Real Estate   0.19%

Recent News: QQQ

View More

Research Reports: QQQ

View More
  • Netflix Earnings: Operating Momentum Remained Strong, but a Surprising Expense Leaves Some Questions

    Netflix’s relatively simple business model involves only one business, its streaming service. It has the biggest television entertainment subscriber base in both the United States and the collective international market, with more than 300 million subscribers globally. Netflix has exposure to nearly the entire global population outside of China. The firm has traditionally avoided a regular slate of live programming or sports content, instead focusing on on-demand access to episodic television, movies, and documentaries. The firm introduced ad-supported subscription plans in 2022, giving the firm exposure to the advertising market in addition to the subscription fees that have historically accounted for nearly all its revenue.

    Rating
    Price Target
     
  • AMD: The Company Has Arrived in AI With a Massive OpenAI Deal; Fair Value Estimate to $210 From $155

    Advanced Micro Devices designs a variety of digital semiconductors for markets such as PCs, gaming consoles, data centers (including artificial intelligence), industrial, and automotive applications. AMD’s traditional strength was in central processing units and graphics processing units used in PCs and data centers. However, AMD is emerging as a prominent player in AI GPUs and related hardware. Additionally, the firm supplies the chips found in prominent game consoles such as the Sony PlayStation and Microsoft Xbox.

    Rating
    Price Target
     
  • Raising 12-month target price to $275

    Advanced Micro Devices is the number two player in x86-based microprocessors, behind Intel, and, with the 2008 acquisition of ATI, a top player in graphic processors. It now competes with Nvidia in the GPU processor space for AI applications. In 2021, Advanced Micro Devices acquired Xilinx, expanding its presence in embedded computing and data center. In 2025, AMD acquired ZT systems, which provides AI and general purpose infrastructure for the global hyperscale providers.

    Rating
    Price Target
     
  • Alphabet: Remedies Proposed in the Google Search Case Do Not Pose a Material Risk to Alphabet

    Alphabet is a holding company that wholly owns internet giant Google. The California-based company derives slightly less than 90% of its revenue from Google services, the vast majority of which is advertising sales. Alongside online ads, Google services houses sales stemming from Google’s subscription services (YouTube TV, YouTube Music among others), platforms (sales and in-app purchases on Play Store), and devices (Chromebooks, Pixel smartphones, and smart home products such as Chromecast). Google’s cloud computing platform, or GCP, accounts for roughly 10% of Alphabet’s revenue with the firm’s investments in up-and-coming technologies such as self-driving cars (Waymo), health (Verily), and internet access (Google Fiber) making up the rest.

    Rating
    Price Target
     

Related Tickers

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_html_VFINX.html b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_html_VFINX.html new file mode 100644 index 0000000..f0d2e56 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/profile_html_VFINX.html @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Vanguard 500 Index Fund (VFINX) Stock Price, News, Quote & History - Yahoo Finance + + + +
+
Nasdaq - Delayed Quote USD

Vanguard 500 Index Fund (VFINX)

636.38
-0.03
(-0.00%)
At close: 8:05:03 PM EDT
Chart Range Bar
Loading chart for VFINX
  • Previous Close 636.41
  • YTD Return 14.71%
  • Expense Ratio 0.14%
  • Category Large Blend
  • Last Cap Gain 39.00
  • Morningstar Rating
  • Morningstar Risk Rating Above Average
  • Sustainability Rating
  • Net Assets 1.41T
  • Beta (5Y Monthly) 1.00
  • Yield 1.05%
  • 5y Average Return --
  • Holdings Turnover 2.00%
  • Last Dividend 4.85
  • Inception Date Sep 7, 2010

The fund employs an indexing investment approach designed to track the performance of the Standard & Poor's 500 Index, a widely recognized benchmark of U.S. stock market performance that is dominated by the stocks of large U.S. companies. The advisor attempts to replicate the target index by investing all, or substantially all, of its assets in the stocks that make up the index, holding each stock in approximately the same proportion as its weighting in the index. The fund is non-diversified.

Vanguard

Fund Family

Large Blend

Fund Category

1.41T

Net Assets

2010-09-07

Inception Date

Performance Overview: VFINX

View more

Trailing returns as of 10/20/2025. Category is Large Blend.

YTD Return

VFINX
14.71%
Category
5.31%
 

1-Year Return

VFINX
17.43%
Category
20.31%
 

3-Year Return

VFINX
24.76%
Category
6.48%
 

5-Year Return

VFINX
16.31%
Category
11.78%
 

People Also Watch

Holdings: VFINX

View more

Sector Weightings

SectorVFINX
Technology   35.69%
Healthcare   8.88%
Industrials   7.52%
Energy   2.89%
Utilities   2.35%
Real Estate   1.94%

Recent News: VFINX

View more

Top Mutual Funds

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/quote_v7_AAPL-MSFT.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/quote_v7_AAPL-MSFT.json new file mode 100644 index 0000000..74305fc --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/quote_v7_AAPL-MSFT.json @@ -0,0 +1 @@ +{"quoteResponse":{"result":[{"language":"en-US","region":"US","quoteType":"EQUITY","typeDisp":"Equity","quoteSourceName":"Nasdaq Real Time Price","triggerable":true,"customPriceAlertConfidence":"HIGH","currency":"USD","exchange":"NMS","messageBoardId":"finmb_24937","exchangeTimezoneName":"America/New_York","exchangeTimezoneShortName":"EDT","gmtOffSetMilliseconds":-14400000,"market":"us_market","esgPopulated":false,"regularMarketChangePercent":0.260228,"regularMarketPrice":269.7,"marketState":"POSTPOST","shortName":"Apple Inc.","longName":"Apple Inc.","corporateActions":[],"postMarketTime":1761782396,"regularMarketTime":1761768001,"hasPrePostMarketData":true,"firstTradeDateMilliseconds":345479400000,"priceHint":2,"postMarketChangePercent":0.38931692,"postMarketPrice":270.75,"postMarketChange":1.0499878,"regularMarketChange":0.700012,"regularMarketDayHigh":271.41,"regularMarketDayRange":"267.11 - 271.41","regularMarketDayLow":267.11,"regularMarketVolume":43332154,"regularMarketPreviousClose":269.0,"bid":255.72,"ask":269.86,"bidSize":1,"askSize":1,"fullExchangeName":"NasdaqGS","financialCurrency":"USD","regularMarketOpen":269.275,"averageDailyVolume3Month":54683671,"averageDailyVolume10Day":46237010,"fiftyTwoWeekLowChange":100.490005,"fiftyTwoWeekLowChangePercent":0.59387743,"fiftyTwoWeekRange":"169.21 - 271.41","fiftyTwoWeekHighChange":-1.7099915,"fiftyTwoWeekHighChangePercent":-0.0063003995,"fiftyTwoWeekLow":169.21,"fiftyTwoWeekHigh":271.41,"fiftyTwoWeekChangePercent":16.90569,"dividendDate":1755129600,"earningsTimestamp":1761854400,"earningsTimestampStart":1761854400,"earningsTimestampEnd":1761854400,"earningsCallTimestampStart":1761858000,"earningsCallTimestampEnd":1761858000,"isEarningsDateEstimate":false,"trailingAnnualDividendRate":1.01,"trailingPE":40.925644,"dividendRate":1.04,"trailingAnnualDividendYield":0.0037546468,"dividendYield":0.39,"epsTrailingTwelveMonths":6.59,"epsForward":8.31,"epsCurrentYear":7.3908,"priceEpsCurrentYear":36.491314,"sharesOutstanding":14840390000,"bookValue":4.431,"fiftyDayAverage":245.6484,"fiftyDayAverageChange":24.051605,"fiftyDayAverageChangePercent":0.09791069,"twoHundredDayAverage":222.7724,"twoHundredDayAverageChange":46.927612,"twoHundredDayAverageChangePercent":0.21065272,"marketCap":4002453389312,"forwardPE":32.454872,"priceToBook":60.866623,"sourceInterval":15,"exchangeDataDelayedBy":0,"averageAnalystRating":"2.0 - Buy","tradeable":false,"cryptoTradeable":false,"displayName":"Apple","symbol":"AAPL"},{"language":"en-US","region":"US","quoteType":"EQUITY","typeDisp":"Equity","quoteSourceName":"Nasdaq Real Time Price","triggerable":true,"customPriceAlertConfidence":"HIGH","currency":"USD","exchange":"NMS","messageBoardId":"finmb_21835","exchangeTimezoneName":"America/New_York","exchangeTimezoneShortName":"EDT","gmtOffSetMilliseconds":-14400000,"market":"us_market","esgPopulated":false,"regularMarketChangePercent":-0.0959322,"regularMarketPrice":541.55,"marketState":"POSTPOST","shortName":"Microsoft Corporation","longName":"Microsoft Corporation","corporateActions":[],"postMarketTime":1761782399,"regularMarketTime":1761768001,"hasPrePostMarketData":true,"firstTradeDateMilliseconds":511108200000,"priceHint":2,"postMarketChangePercent":-3.9793165,"postMarketPrice":520.0,"postMarketChange":-21.549988,"regularMarketChange":-0.52002,"regularMarketDayHigh":546.27,"regularMarketDayRange":"536.7287 - 546.27","regularMarketDayLow":536.7287,"regularMarketVolume":33295714,"regularMarketPreviousClose":542.07,"bid":540.89,"ask":542.56,"bidSize":1,"askSize":1,"fullExchangeName":"NasdaqGS","financialCurrency":"USD","regularMarketOpen":544.94,"averageDailyVolume3Month":20534825,"averageDailyVolume10Day":17753650,"fiftyTwoWeekLowChange":196.75998,"fiftyTwoWeekLowChangePercent":0.57066613,"fiftyTwoWeekRange":"344.79 - 555.45","fiftyTwoWeekHighChange":-13.900024,"fiftyTwoWeekHighChangePercent":-0.025024798,"fiftyTwoWeekLow":344.79,"fiftyTwoWeekHigh":555.45,"fiftyTwoWeekChangePercent":25.325405,"dividendDate":1765411200,"earningsTimestamp":1761768000,"earningsTimestampStart":1761768000,"earningsTimestampEnd":1761768000,"earningsCallTimestampStart":1761773400,"earningsCallTimestampEnd":1761773400,"isEarningsDateEstimate":false,"trailingAnnualDividendRate":3.32,"trailingPE":39.644947,"dividendRate":3.64,"trailingAnnualDividendYield":0.00612467,"dividendYield":0.67,"epsTrailingTwelveMonths":13.66,"epsForward":14.95,"epsCurrentYear":15.53804,"priceEpsCurrentYear":34.853172,"sharesOutstanding":7433087554,"bookValue":46.204,"fiftyDayAverage":512.5176,"fiftyDayAverageChange":29.03241,"fiftyDayAverageChangePercent":0.05664666,"twoHundredDayAverage":459.5124,"twoHundredDayAverageChange":82.0376,"twoHundredDayAverageChangePercent":0.17853186,"marketCap":4025388367872,"forwardPE":36.22408,"priceToBook":11.720847,"sourceInterval":15,"exchangeDataDelayedBy":0,"averageAnalystRating":"1.2 - Strong Buy","tradeable":false,"cryptoTradeable":false,"displayName":"Microsoft","symbol":"MSFT"}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/quote_v7_AAPL.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/quote_v7_AAPL.json new file mode 100644 index 0000000..d7e2f8c --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/quote_v7_AAPL.json @@ -0,0 +1 @@ +{"quoteResponse":{"result":[{"language":"en-US","region":"US","quoteType":"EQUITY","typeDisp":"Equity","quoteSourceName":"Nasdaq Real Time Price","triggerable":true,"customPriceAlertConfidence":"HIGH","shortName":"Apple Inc.","currency":"USD","hasPrePostMarketData":true,"firstTradeDateMilliseconds":345479400000,"priceHint":2,"postMarketChangePercent":0.38931692,"postMarketPrice":270.75,"postMarketChange":1.0499878,"regularMarketChange":0.700012,"regularMarketDayHigh":271.41,"regularMarketDayRange":"267.11 - 271.41","regularMarketDayLow":267.11,"regularMarketVolume":43332154,"regularMarketPreviousClose":269.0,"bid":255.72,"ask":269.86,"bidSize":1,"askSize":1,"fullExchangeName":"NasdaqGS","financialCurrency":"USD","regularMarketOpen":269.275,"averageDailyVolume3Month":54683671,"averageDailyVolume10Day":46237010,"fiftyTwoWeekLowChange":100.490005,"fiftyTwoWeekLowChangePercent":0.59387743,"fiftyTwoWeekRange":"169.21 - 271.41","fiftyTwoWeekHighChange":-1.7099915,"fiftyTwoWeekHighChangePercent":-0.0063003995,"fiftyTwoWeekLow":169.21,"fiftyTwoWeekHigh":271.41,"fiftyTwoWeekChangePercent":16.90569,"dividendDate":1755129600,"earningsTimestamp":1761854400,"earningsTimestampStart":1761854400,"earningsTimestampEnd":1761854400,"earningsCallTimestampStart":1761858000,"earningsCallTimestampEnd":1761858000,"isEarningsDateEstimate":false,"trailingAnnualDividendRate":1.01,"trailingPE":40.925644,"dividendRate":1.04,"trailingAnnualDividendYield":0.0037546468,"dividendYield":0.39,"epsTrailingTwelveMonths":6.59,"epsForward":8.31,"epsCurrentYear":7.3908,"priceEpsCurrentYear":36.491314,"sharesOutstanding":14840390000,"bookValue":4.431,"fiftyDayAverage":245.6484,"fiftyDayAverageChange":24.051605,"fiftyDayAverageChangePercent":0.09791069,"longName":"Apple Inc.","exchange":"NMS","messageBoardId":"finmb_24937","exchangeTimezoneName":"America/New_York","exchangeTimezoneShortName":"EDT","gmtOffSetMilliseconds":-14400000,"market":"us_market","esgPopulated":false,"regularMarketChangePercent":0.260228,"regularMarketPrice":269.7,"marketState":"POSTPOST","twoHundredDayAverage":222.7724,"twoHundredDayAverageChange":46.927612,"twoHundredDayAverageChangePercent":0.21065272,"marketCap":4002453389312,"forwardPE":32.454872,"priceToBook":60.866623,"sourceInterval":15,"exchangeDataDelayedBy":0,"averageAnalystRating":"2.0 - Buy","tradeable":false,"cryptoTradeable":false,"corporateActions":[],"postMarketTime":1761782396,"regularMarketTime":1761768001,"displayName":"Apple","symbol":"AAPL"}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/quote_v7_GS2C.DE.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/quote_v7_GS2C.DE.json new file mode 100644 index 0000000..82bf0c2 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/quote_v7_GS2C.DE.json @@ -0,0 +1 @@ +{"quoteResponse":{"result":[{"language":"en-US","region":"US","quoteType":"EQUITY","typeDisp":"Equity","quoteSourceName":"Delayed Quote","triggerable":false,"customPriceAlertConfidence":"LOW","currency":"EUR","marketState":"PREPRE","shortName":"Gamestop Corp. R","longName":"GameStop Corp.","hasPrePostMarketData":false,"firstTradeDateMilliseconds":1198742400000,"priceHint":2,"regularMarketChange":-0.13199997,"regularMarketDayHigh":20.15,"regularMarketDayRange":"19.786 - 20.15","regularMarketDayLow":19.786,"regularMarketVolume":9179,"regularMarketPreviousClose":20.08,"bid":0.0,"ask":19.804,"fullExchangeName":"XETRA","financialCurrency":"USD","regularMarketOpen":20.13,"averageDailyVolume3Month":9397,"averageDailyVolume10Day":13517,"fiftyTwoWeekLowChange":1.3479996,"fiftyTwoWeekLowChangePercent":0.072473094,"fiftyTwoWeekRange":"18.6 - 32.895","fiftyTwoWeekHighChange":-12.9470005,"fiftyTwoWeekHighChangePercent":-0.39358565,"fiftyTwoWeekLow":18.6,"fiftyTwoWeekHigh":32.895,"fiftyTwoWeekChangePercent":-4.3809533,"earningsTimestamp":1757431800,"earningsTimestampStart":1765297800,"earningsTimestampEnd":1765297800,"isEarningsDateEstimate":true,"trailingAnnualDividendRate":0.0,"trailingPE":28.910145,"trailingAnnualDividendYield":0.0,"epsTrailingTwelveMonths":0.69,"corporateActions":[],"regularMarketTime":1761755744,"exchange":"GER","messageBoardId":"finmb_1342560","exchangeTimezoneName":"Europe/Berlin","exchangeTimezoneShortName":"CET","gmtOffSetMilliseconds":3600000,"market":"de_market","esgPopulated":false,"regularMarketChangePercent":-0.6573704,"regularMarketPrice":19.948,"sharesOutstanding":447908690,"bookValue":11.565,"fiftyDayAverage":20.79306,"fiftyDayAverageChange":-0.84506035,"fiftyDayAverageChangePercent":-0.04064146,"twoHundredDayAverage":22.307425,"twoHundredDayAverageChange":-2.3594246,"twoHundredDayAverageChangePercent":-0.105768576,"marketCap":8934882304,"priceToBook":1.7248596,"sourceInterval":15,"exchangeDataDelayedBy":15,"tradeable":false,"cryptoTradeable":false,"symbol":"GS2C.DE"}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/quote_v7_GS2C.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/quote_v7_GS2C.json new file mode 100644 index 0000000..bdc568b --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/quote_v7_GS2C.json @@ -0,0 +1 @@ +{"quoteResponse":{"result":[],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/quote_v7_MSFT.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/quote_v7_MSFT.json new file mode 100644 index 0000000..26013f0 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/quote_v7_MSFT.json @@ -0,0 +1 @@ +{"quoteResponse":{"result":[{"language":"en-US","region":"US","quoteType":"EQUITY","typeDisp":"Equity","quoteSourceName":"Nasdaq Real Time Price","triggerable":true,"customPriceAlertConfidence":"HIGH","currency":"USD","longName":"Microsoft Corporation","hasPrePostMarketData":true,"firstTradeDateMilliseconds":511108200000,"priceHint":2,"postMarketChangePercent":-3.9793165,"postMarketPrice":520.0,"postMarketChange":-21.549988,"regularMarketChange":-0.52002,"regularMarketDayHigh":546.27,"regularMarketDayRange":"536.7287 - 546.27","regularMarketDayLow":536.7287,"regularMarketVolume":33295714,"regularMarketPreviousClose":542.07,"bid":540.89,"ask":542.56,"bidSize":1,"askSize":1,"fullExchangeName":"NasdaqGS","financialCurrency":"USD","regularMarketOpen":544.94,"averageDailyVolume3Month":20534825,"averageDailyVolume10Day":17753650,"fiftyTwoWeekLowChange":196.75998,"fiftyTwoWeekLowChangePercent":0.57066613,"fiftyTwoWeekRange":"344.79 - 555.45","fiftyTwoWeekHighChange":-13.900024,"fiftyTwoWeekHighChangePercent":-0.025024798,"fiftyTwoWeekLow":344.79,"fiftyTwoWeekHigh":555.45,"fiftyTwoWeekChangePercent":25.325405,"dividendDate":1765411200,"earningsTimestamp":1761768000,"earningsTimestampStart":1761768000,"earningsTimestampEnd":1761768000,"earningsCallTimestampStart":1761773400,"earningsCallTimestampEnd":1761773400,"isEarningsDateEstimate":false,"trailingAnnualDividendRate":3.32,"trailingPE":39.644947,"dividendRate":3.64,"trailingAnnualDividendYield":0.00612467,"dividendYield":0.67,"epsTrailingTwelveMonths":13.66,"epsForward":14.95,"epsCurrentYear":15.53804,"priceEpsCurrentYear":34.853172,"sharesOutstanding":7433087554,"bookValue":46.204,"fiftyDayAverage":512.5176,"fiftyDayAverageChange":29.03241,"fiftyDayAverageChangePercent":0.05664666,"twoHundredDayAverage":459.5124,"twoHundredDayAverageChange":82.0376,"twoHundredDayAverageChangePercent":0.17853186,"marketCap":4025388367872,"forwardPE":36.22408,"priceToBook":11.720847,"sourceInterval":15,"exchangeDataDelayedBy":0,"averageAnalystRating":"1.2 - Strong Buy","tradeable":false,"cryptoTradeable":false,"corporateActions":[],"postMarketTime":1761782399,"regularMarketTime":1761768001,"exchange":"NMS","messageBoardId":"finmb_21835","exchangeTimezoneName":"America/New_York","exchangeTimezoneShortName":"EDT","gmtOffSetMilliseconds":-14400000,"market":"us_market","esgPopulated":false,"regularMarketChangePercent":-0.0959322,"regularMarketPrice":541.55,"shortName":"Microsoft Corporation","marketState":"POSTPOST","displayName":"Microsoft","symbol":"MSFT"}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/search_v1_apple.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/search_v1_apple.json new file mode 100644 index 0000000..5ddeb29 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/search_v1_apple.json @@ -0,0 +1 @@ +{"explains":[],"count":7,"quotes":[{"exchange":"NMS","shortname":"Apple Inc.","quoteType":"EQUITY","symbol":"AAPL","index":"quotes","score":39332.0,"typeDisp":"Equity","longname":"Apple Inc.","exchDisp":"NASDAQ","sector":"Technology","sectorDisp":"Technology","industry":"Consumer Electronics","industryDisp":"Consumer Electronics","dispSecIndFlag":true,"isYahooFinance":true},{"exchange":"NYQ","shortname":"Apple Hospitality REIT, Inc.","quoteType":"EQUITY","symbol":"APLE","index":"quotes","score":20043.0,"typeDisp":"Equity","longname":"Apple Hospitality REIT, Inc.","exchDisp":"NYSE","sector":"Real Estate","sectorDisp":"Real Estate","industry":"REIT—Hotel & Motel","industryDisp":"REIT—Hotel & Motel","isYahooFinance":true},{"exchange":"VIE","shortname":"APPLE INC","quoteType":"EQUITY","symbol":"AAPL.VI","index":"quotes","score":20025.0,"typeDisp":"Equity","longname":"Apple Inc.","exchDisp":"Vienna","sector":"Technology","sectorDisp":"Technology","industry":"Consumer Electronics","industryDisp":"Consumer Electronics","isYahooFinance":true},{"exchange":"FRA","shortname":"Apple Inc. R","quoteType":"EQUITY","symbol":"APC.F","index":"quotes","score":20024.0,"typeDisp":"Equity","longname":"Apple Inc.","exchDisp":"Frankfurt","sector":"Technology","sectorDisp":"Technology","industry":"Consumer Electronics","industryDisp":"Consumer Electronics","isYahooFinance":true},{"exchange":"BUE","shortname":"APPLE INC CEDEAR(REPR 1/20 SHR)","quoteType":"EQUITY","symbol":"AAPL.BA","index":"quotes","score":20018.0,"typeDisp":"Equity","longname":"Apple Inc.","exchDisp":"Buenos Aires","sector":"Technology","sectorDisp":"Technology","industry":"Consumer Electronics","industryDisp":"Consumer Electronics","isYahooFinance":true},{"exchange":"OQB","shortname":"Apple iSports Group Inc.","quoteType":"EQUITY","symbol":"AAPI","index":"quotes","score":20010.0,"typeDisp":"Equity","longname":"Apple iSports Group, Inc.","exchDisp":"OQB","sector":"Consumer Cyclical","sectorDisp":"Consumer Cyclical","industry":"Gambling","industryDisp":"Gambling","isYahooFinance":true},{"exchange":"GER","shortname":"Apple Inc. R","quoteType":"EQUITY","symbol":"APC.DE","index":"quotes","score":20007.0,"typeDisp":"Equity","longname":"Apple Inc.","exchDisp":"XETRA","sector":"Technology","sectorDisp":"Technology","industry":"Consumer Electronics","industryDisp":"Consumer Electronics","isYahooFinance":true}],"news":[],"nav":[],"lists":[],"researchReports":[],"screenerFieldResults":[],"totalTime":49,"timeTakenForQuotes":426,"timeTakenForNews":0,"timeTakenForAlgowatchlist":400,"timeTakenForPredefinedScreener":400,"timeTakenForCrunchbase":0,"timeTakenForNav":400,"timeTakenForResearchReports":0,"timeTakenForScreenerField":0,"timeTakenForCulturalAssets":0,"timeTakenForSearchLists":0} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/stream_ws_MULTI.b64 b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/stream_ws_MULTI.b64 new file mode 100644 index 0000000..39d24ae --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/stream_ws_MULTI.b64 @@ -0,0 +1 @@ +{"type":"pricing","message":"CgdCVEMtVVNEFbau10cYgIWhqcZmIgNVU0QqA0NDQzApOAFFodj6v0iAgJOO0gNVtq7XR11nZNZHZSD2CcV9APnWR7ABgICTjtID2AEE4AGAgJOO0gPoAYCAk47SA/IBA0JUQ4ECAAAAAJoEc0GJAgCAZf/XBYBC"} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_annualBasicAverageShares_MSFT.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_annualBasicAverageShares_MSFT.json new file mode 100644 index 0000000..550cf23 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_annualBasicAverageShares_MSFT.json @@ -0,0 +1 @@ +{"timeseries":{"result":[{"meta":{"symbol":["MSFT"],"type":["annualBasicAverageShares"]},"timestamp":[1719705600,1751241600],"annualBasicAverageShares":[{"dataId":29010,"asOfDate":"2024-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":7.431E9,"fmt":"7.43B"}},{"dataId":29010,"asOfDate":"2025-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":7.433E9,"fmt":"7.43B"}}]}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_balance_sheet_annual_MSFT.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_balance_sheet_annual_MSFT.json new file mode 100644 index 0000000..31fa9e9 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_balance_sheet_annual_MSFT.json @@ -0,0 +1 @@ +{"timeseries":{"result":[{"meta":{"symbol":["MSFT"],"type":["annualTotalAssets"]},"timestamp":[1656547200,1688083200,1719705600,1751241600],"annualTotalAssets":[{"dataId":23220,"asOfDate":"2022-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":3.6484E11,"fmt":"364.84B"}},{"dataId":23220,"asOfDate":"2023-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":4.11976E11,"fmt":"411.98B"}},{"dataId":23220,"asOfDate":"2024-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":5.12163E11,"fmt":"512.16B"}},{"dataId":23220,"asOfDate":"2025-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":6.19003E11,"fmt":"619.00B"}}]},{"meta":{"symbol":["MSFT"],"type":["annualOrdinarySharesNumber"]},"timestamp":[1656547200,1688083200,1719705600,1751241600],"annualOrdinarySharesNumber":[{"dataId":23393,"asOfDate":"2022-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":7.464E9,"fmt":"7.46B"}},{"dataId":23393,"asOfDate":"2023-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":7.432E9,"fmt":"7.43B"}},{"dataId":23393,"asOfDate":"2024-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":7.434138859E9,"fmt":"7.43B"}},{"dataId":23393,"asOfDate":"2025-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":7.434E9,"fmt":"7.43B"}}]},{"meta":{"symbol":["MSFT"],"type":["annualLongTermDebt"]},"timestamp":[1656547200,1688083200,1719705600,1751241600],"annualLongTermDebt":[{"dataId":23123,"asOfDate":"2022-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":4.7032E10,"fmt":"47.03B"}},{"dataId":23123,"asOfDate":"2023-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":4.199E10,"fmt":"41.99B"}},{"dataId":23123,"asOfDate":"2024-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":4.2688E10,"fmt":"42.69B"}},{"dataId":23123,"asOfDate":"2025-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":4.0152E10,"fmt":"40.15B"}}]},{"meta":{"symbol":["MSFT"],"type":["annualCashAndCashEquivalents"]},"timestamp":[1656547200,1688083200,1719705600,1751241600],"annualCashAndCashEquivalents":[{"dataId":23030,"asOfDate":"2022-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":1.3931E10,"fmt":"13.93B"}},{"dataId":23030,"asOfDate":"2023-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":3.4704E10,"fmt":"34.70B"}},{"dataId":23030,"asOfDate":"2024-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":1.8315E10,"fmt":"18.32B"}},{"dataId":23030,"asOfDate":"2025-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":3.0242E10,"fmt":"30.24B"}}]},{"meta":{"symbol":["MSFT"],"type":["annualStockholdersEquity"]},"timestamp":[1656547200,1688083200,1719705600,1751241600],"annualStockholdersEquity":[{"dataId":23215,"asOfDate":"2022-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":1.66542E11,"fmt":"166.54B"}},{"dataId":23215,"asOfDate":"2023-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":2.06223E11,"fmt":"206.22B"}},{"dataId":23215,"asOfDate":"2024-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":2.68477E11,"fmt":"268.48B"}},{"dataId":23215,"asOfDate":"2025-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":3.43479E11,"fmt":"343.48B"}}]},{"meta":{"symbol":["MSFT"],"type":["annualTotalLiabilitiesNetMinorityInterest"]},"timestamp":[1656547200,1688083200,1719705600,1751241600],"annualTotalLiabilitiesNetMinorityInterest":[{"dataId":23259,"asOfDate":"2022-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":1.98298E11,"fmt":"198.30B"}},{"dataId":23259,"asOfDate":"2023-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":2.05753E11,"fmt":"205.75B"}},{"dataId":23259,"asOfDate":"2024-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":2.43686E11,"fmt":"243.69B"}},{"dataId":23259,"asOfDate":"2025-06-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":2.75524E11,"fmt":"275.52B"}}]}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_cash_flow_annual_GOOGL.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_cash_flow_annual_GOOGL.json new file mode 100644 index 0000000..1c497f1 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_cash_flow_annual_GOOGL.json @@ -0,0 +1 @@ +{"timeseries":{"result":[{"meta":{"symbol":["GOOGL"],"type":["annualCapitalExpenditure"]},"timestamp":[1640908800,1672444800,1703980800,1735603200],"annualCapitalExpenditure":[{"dataId":26005,"asOfDate":"2021-12-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":-2.464E10,"fmt":"-24.64B"}},{"dataId":26005,"asOfDate":"2022-12-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":-3.1485E10,"fmt":"-31.48B"}},{"dataId":26005,"asOfDate":"2023-12-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":-3.2251E10,"fmt":"-32.25B"}},{"dataId":26005,"asOfDate":"2024-12-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":-5.2535E10,"fmt":"-52.53B"}}]},{"meta":{"symbol":["GOOGL"],"type":["annualNetIncome"]},"timestamp":[1640908800,1672444800,1703980800,1735603200],"annualNetIncome":[{"dataId":20091,"asOfDate":"2021-12-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":7.6033E10,"fmt":"76.03B"}},{"dataId":20091,"asOfDate":"2022-12-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":5.9972E10,"fmt":"59.97B"}},{"dataId":20091,"asOfDate":"2023-12-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":7.3795E10,"fmt":"73.80B"}},{"dataId":20091,"asOfDate":"2024-12-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":1.00118E11,"fmt":"100.12B"}}]},{"meta":{"symbol":["GOOGL"],"type":["annualFreeCashFlow"]},"timestamp":[1640908800,1672444800,1703980800,1735603200],"annualFreeCashFlow":[{"dataId":26185,"asOfDate":"2021-12-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":6.7012E10,"fmt":"67.01B"}},{"dataId":26185,"asOfDate":"2022-12-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":6.001E10,"fmt":"60.01B"}},{"dataId":26185,"asOfDate":"2023-12-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":6.9495E10,"fmt":"69.50B"}},{"dataId":26185,"asOfDate":"2024-12-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":7.2764E10,"fmt":"72.76B"}}]},{"meta":{"symbol":["GOOGL"],"type":["annualOperatingCashFlow"]},"timestamp":[1640908800,1672444800,1703980800,1735603200],"annualOperatingCashFlow":[{"dataId":26014,"asOfDate":"2021-12-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":9.1652E10,"fmt":"91.65B"}},{"dataId":26014,"asOfDate":"2022-12-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":9.1495E10,"fmt":"91.50B"}},{"dataId":26014,"asOfDate":"2023-12-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":1.01746E11,"fmt":"101.75B"}},{"dataId":26014,"asOfDate":"2024-12-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":1.25299E11,"fmt":"125.30B"}}]}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_income_statement_annual_7203.T.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_income_statement_annual_7203.T.json new file mode 100644 index 0000000..1cdbe95 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_income_statement_annual_7203.T.json @@ -0,0 +1 @@ +{"timeseries":{"result":[{"meta":{"symbol":["7203.T"],"type":["annualTotalRevenue"]},"timestamp":[1648684800,1680220800,1711843200,1743379200],"annualTotalRevenue":[{"dataId":20100,"asOfDate":"2022-03-31","periodType":"12M","currencyCode":"JPY","reportedValue":{"raw":3.1379507E13,"fmt":"31.38T"}},{"dataId":20100,"asOfDate":"2023-03-31","periodType":"12M","currencyCode":"JPY","reportedValue":{"raw":3.7154298E13,"fmt":"37.15T"}},{"dataId":20100,"asOfDate":"2024-03-31","periodType":"12M","currencyCode":"JPY","reportedValue":{"raw":4.5095325E13,"fmt":"45.10T"}},{"dataId":20100,"asOfDate":"2025-03-31","periodType":"12M","currencyCode":"JPY","reportedValue":{"raw":4.8036704E13,"fmt":"48.04T"}}]},{"meta":{"symbol":["7203.T"],"type":["annualGrossProfit"]},"timestamp":[1648684800,1680220800,1711843200,1743379200],"annualGrossProfit":[{"dataId":20046,"asOfDate":"2022-03-31","periodType":"12M","currencyCode":"JPY","reportedValue":{"raw":5.971673E12,"fmt":"5.97T"}},{"dataId":20046,"asOfDate":"2023-03-31","periodType":"12M","currencyCode":"JPY","reportedValue":{"raw":6.313016E12,"fmt":"6.31T"}},{"dataId":20046,"asOfDate":"2024-03-31","periodType":"12M","currencyCode":"JPY","reportedValue":{"raw":9.368318E12,"fmt":"9.37T"}},{"dataId":20046,"asOfDate":"2025-03-31","periodType":"12M","currencyCode":"JPY","reportedValue":{"raw":9.578038E12,"fmt":"9.58T"}}]},{"meta":{"symbol":["7203.T"],"type":["annualNetIncome"]},"timestamp":[1648684800,1680220800,1711843200,1743379200],"annualNetIncome":[{"dataId":20091,"asOfDate":"2022-03-31","periodType":"12M","currencyCode":"JPY","reportedValue":{"raw":2.85011E12,"fmt":"2.85T"}},{"dataId":20091,"asOfDate":"2023-03-31","periodType":"12M","currencyCode":"JPY","reportedValue":{"raw":2.451318E12,"fmt":"2.45T"}},{"dataId":20091,"asOfDate":"2024-03-31","periodType":"12M","currencyCode":"JPY","reportedValue":{"raw":4.944933E12,"fmt":"4.94T"}},{"dataId":20091,"asOfDate":"2025-03-31","periodType":"12M","currencyCode":"JPY","reportedValue":{"raw":4.765086E12,"fmt":"4.77T"}}]},{"meta":{"symbol":["7203.T"],"type":["annualOperatingIncome"]},"timestamp":[1648684800,1680220800,1711843200,1743379200],"annualOperatingIncome":[{"dataId":20109,"asOfDate":"2022-03-31","periodType":"12M","currencyCode":"JPY","reportedValue":{"raw":2.995696E12,"fmt":"3.00T"},"businessSegmentData":[{"dataValue":4.2302E10,"segmentType":"Business","segmentName":"All Other","isPrimarySegment":0},{"dataValue":2.28429E12,"segmentType":"Business","segmentName":"Automotive","isPrimarySegment":1},{"dataValue":6.57001E11,"segmentType":"Business","segmentName":"Financial Services","isPrimarySegment":0},{"dataValue":1.2104E10,"segmentType":"Business","segmentName":"Inter-segment Elimination/ Unallocated Amount","isPrimarySegment":0}]},{"dataId":20109,"asOfDate":"2023-03-31","periodType":"12M","currencyCode":"JPY","reportedValue":{"raw":2.725026E12,"fmt":"2.73T"},"businessSegmentData":[{"dataValue":1.03451E11,"segmentType":"Business","segmentName":"All Other","isPrimarySegment":0},{"dataValue":2.180637E12,"segmentType":"Business","segmentName":"Automotive","isPrimarySegment":1},{"dataValue":4.37516E11,"segmentType":"Business","segmentName":"Financial Services","isPrimarySegment":0},{"dataValue":3.42E9,"segmentType":"Business","segmentName":"Inter-segment Elimination/ Unallocated Amount","isPrimarySegment":0}]},{"dataId":20109,"asOfDate":"2024-03-31","periodType":"12M","currencyCode":"JPY","reportedValue":{"raw":5.352935E12,"fmt":"5.35T"},"businessSegmentData":[{"dataValue":1.75241E11,"segmentType":"Business","segmentName":"All Other","isPrimarySegment":0},{"dataValue":4.621475E12,"segmentType":"Business","segmentName":"Automotive","isPrimarySegment":1},{"dataValue":5.70023E11,"segmentType":"Business","segmentName":"Financial Services","isPrimarySegment":0},{"dataValue":-1.3805E10,"segmentType":"Business","segmentName":"Inter-segment Elimination/ Unallocated Amount","isPrimarySegment":0}]},{"dataId":20109,"asOfDate":"2025-03-31","periodType":"12M","currencyCode":"JPY","reportedValue":{"raw":4.795586E12,"fmt":"4.80T"},"businessSegmentData":[{"dataValue":1.81194E11,"segmentType":"Business","segmentName":"All Other","isPrimarySegment":0},{"dataValue":3.940278E12,"segmentType":"Business","segmentName":"Automotive","isPrimarySegment":1},{"dataValue":6.83519E11,"segmentType":"Business","segmentName":"Financial Services","isPrimarySegment":0},{"dataValue":-9.405E9,"segmentType":"Business","segmentName":"Inter-segment Elimination/ Unallocated Amount","isPrimarySegment":0}]}]}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_income_statement_annual_AAPL.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_income_statement_annual_AAPL.json new file mode 100644 index 0000000..b95097a --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_income_statement_annual_AAPL.json @@ -0,0 +1 @@ +{"timeseries":{"result":[{"meta":{"symbol":["AAPL"],"type":["annualTotalRevenue"]},"timestamp":[1632960000,1664496000,1696032000,1727654400],"annualTotalRevenue":[{"dataId":20100,"asOfDate":"2021-09-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":3.65817E11,"fmt":"365.82B"}},{"dataId":20100,"asOfDate":"2022-09-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":3.94328E11,"fmt":"394.33B"}},{"dataId":20100,"asOfDate":"2023-09-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":3.83285E11,"fmt":"383.29B"}},{"dataId":20100,"asOfDate":"2024-09-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":3.91035E11,"fmt":"391.04B"}}]},{"meta":{"symbol":["AAPL"],"type":["annualOperatingIncome"]},"timestamp":[1632960000,1664496000,1696032000,1727654400],"annualOperatingIncome":[{"dataId":20109,"asOfDate":"2021-09-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":1.08949E11,"fmt":"108.95B"},"geographicSegmentData":[{"dataValue":5.3382E10,"segmentType":"Geographic","segmentName":"Americas, excluding U.S","isPrimarySegment":0},{"dataValue":3.2505E10,"segmentType":"Geographic","segmentName":"Europe","isPrimarySegment":0},{"dataValue":2.8504E10,"segmentType":"Geographic","segmentName":"Greater China","isPrimarySegment":0},{"dataValue":1.2798E10,"segmentType":"Geographic","segmentName":"Japan","isPrimarySegment":0},{"dataValue":-6.143E9,"segmentType":"Geographic","segmentName":"Other corporate expenses, net","isPrimarySegment":0},{"dataValue":-2.1914E10,"segmentType":"Geographic","segmentName":"Research and development expense","isPrimarySegment":0},{"dataValue":9.817E9,"segmentType":"Geographic","segmentName":"Rest of Asia Pacific","isPrimarySegment":0}]},{"dataId":20109,"asOfDate":"2022-09-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":1.19437E11,"fmt":"119.44B"},"geographicSegmentData":[{"dataValue":6.2683E10,"segmentType":"Geographic","segmentName":"Americas, excluding U.S","isPrimarySegment":0},{"dataValue":3.5233E10,"segmentType":"Geographic","segmentName":"Europe","isPrimarySegment":0},{"dataValue":3.1153E10,"segmentType":"Geographic","segmentName":"Greater China","isPrimarySegment":0},{"dataValue":1.2257E10,"segmentType":"Geographic","segmentName":"Japan","isPrimarySegment":0},{"dataValue":-7.207E9,"segmentType":"Geographic","segmentName":"Other corporate expenses, net","isPrimarySegment":0},{"dataValue":-2.6251E10,"segmentType":"Geographic","segmentName":"Research and development expense","isPrimarySegment":0},{"dataValue":1.1569E10,"segmentType":"Geographic","segmentName":"Rest of Asia Pacific","isPrimarySegment":0}]},{"dataId":20109,"asOfDate":"2023-09-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":1.14301E11,"fmt":"114.30B"},"geographicSegmentData":[{"dataValue":6.0508E10,"segmentType":"Geographic","segmentName":"Americas, excluding U.S","isPrimarySegment":0},{"dataValue":3.6098E10,"segmentType":"Geographic","segmentName":"Europe","isPrimarySegment":0},{"dataValue":3.0328E10,"segmentType":"Geographic","segmentName":"Greater China","isPrimarySegment":0},{"dataValue":1.1888E10,"segmentType":"Geographic","segmentName":"Japan","isPrimarySegment":0},{"dataValue":-6.672E9,"segmentType":"Geographic","segmentName":"Other corporate expenses, net","isPrimarySegment":0},{"dataValue":-2.9915E10,"segmentType":"Geographic","segmentName":"Research and development expense","isPrimarySegment":0},{"dataValue":1.2066E10,"segmentType":"Geographic","segmentName":"Rest of Asia Pacific","isPrimarySegment":0}]},{"dataId":20109,"asOfDate":"2024-09-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":1.23216E11,"fmt":"123.22B"},"geographicSegmentData":[{"dataValue":6.7656E10,"segmentType":"Geographic","segmentName":"Americas","isPrimarySegment":0},{"dataValue":4.179E10,"segmentType":"Geographic","segmentName":"Europe","isPrimarySegment":0},{"dataValue":2.7082E10,"segmentType":"Geographic","segmentName":"Greater China","isPrimarySegment":0},{"dataValue":1.2454E10,"segmentType":"Geographic","segmentName":"Japan","isPrimarySegment":0},{"dataValue":-7.458E9,"segmentType":"Geographic","segmentName":"Other corporate expenses, net","isPrimarySegment":0},{"dataValue":-3.137E10,"segmentType":"Geographic","segmentName":"Research and development expense","isPrimarySegment":0},{"dataValue":1.3062E10,"segmentType":"Geographic","segmentName":"Rest of Asia Pacific","isPrimarySegment":0}]}]},{"meta":{"symbol":["AAPL"],"type":["annualNetIncome"]},"timestamp":[1632960000,1664496000,1696032000,1727654400],"annualNetIncome":[{"dataId":20091,"asOfDate":"2021-09-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":9.468E10,"fmt":"94.68B"}},{"dataId":20091,"asOfDate":"2022-09-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":9.9803E10,"fmt":"99.80B"}},{"dataId":20091,"asOfDate":"2023-09-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":9.6995E10,"fmt":"97.00B"}},{"dataId":20091,"asOfDate":"2024-09-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":9.3736E10,"fmt":"93.74B"}}]},{"meta":{"symbol":["AAPL"],"type":["annualGrossProfit"]},"timestamp":[1632960000,1664496000,1696032000,1727654400],"annualGrossProfit":[{"dataId":20046,"asOfDate":"2021-09-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":1.52836E11,"fmt":"152.84B"}},{"dataId":20046,"asOfDate":"2022-09-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":1.70782E11,"fmt":"170.78B"}},{"dataId":20046,"asOfDate":"2023-09-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":1.69148E11,"fmt":"169.15B"}},{"dataId":20046,"asOfDate":"2024-09-30","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":1.80683E11,"fmt":"180.68B"}}]}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_income_statement_annual_GS2C.DE.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_income_statement_annual_GS2C.DE.json new file mode 100644 index 0000000..80aa084 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_income_statement_annual_GS2C.DE.json @@ -0,0 +1 @@ +{"timeseries":{"result":[{"meta":{"symbol":["GS2C.DE"],"type":["annualNetIncome"]},"timestamp":[1643587200,1675123200,1706659200,1738281600],"annualNetIncome":[{"dataId":20091,"asOfDate":"2022-01-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":-3.813E8,"fmt":"-381.30M"}},{"dataId":20091,"asOfDate":"2023-01-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":-3.131E8,"fmt":"-313.10M"}},{"dataId":20091,"asOfDate":"2024-01-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":6700000.0,"fmt":"6.70M"}},{"dataId":20091,"asOfDate":"2025-01-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":1.313E8,"fmt":"131.30M"}}]},{"meta":{"symbol":["GS2C.DE"],"type":["annualOperatingIncome"]},"timestamp":[1643587200,1675123200,1706659200,1738281600],"annualOperatingIncome":[{"dataId":20109,"asOfDate":"2022-01-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":-3.618E8,"fmt":"-361.80M"},"geographicSegmentData":[{"dataValue":3.06E7,"segmentType":"Geographic","segmentName":"Australia","isPrimarySegment":0},{"dataValue":-1100000.0,"segmentType":"Geographic","segmentName":"Canada","isPrimarySegment":0},{"dataValue":-3.99E7,"segmentType":"Geographic","segmentName":"Europe","isPrimarySegment":0},{"dataValue":-3.581E8,"segmentType":"Geographic","segmentName":"United States","isPrimarySegment":1}]},{"dataId":20109,"asOfDate":"2023-01-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":-3.089E8,"fmt":"-308.90M"},"geographicSegmentData":[{"dataValue":1.38E7,"segmentType":"Geographic","segmentName":"Australia","isPrimarySegment":0},{"dataValue":-8600000.0,"segmentType":"Geographic","segmentName":"Canada","isPrimarySegment":0},{"dataValue":-3.06E7,"segmentType":"Geographic","segmentName":"Europe","isPrimarySegment":0},{"dataValue":-2.862E8,"segmentType":"Geographic","segmentName":"United States","isPrimarySegment":1}]},{"dataId":20109,"asOfDate":"2024-01-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":-2.97E7,"fmt":"-29.70M"},"geographicSegmentData":[{"dataValue":-3500000.0,"segmentType":"Geographic","segmentName":"Australia","isPrimarySegment":0},{"dataValue":-8400000.0,"segmentType":"Geographic","segmentName":"Canada","isPrimarySegment":0},{"dataValue":-2.02E7,"segmentType":"Geographic","segmentName":"Europe","isPrimarySegment":0},{"dataValue":-2400000.0,"segmentType":"Geographic","segmentName":"United States","isPrimarySegment":1}]},{"dataId":20109,"asOfDate":"2025-01-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":-1.65E7,"fmt":"-16.50M"},"geographicSegmentData":[{"dataValue":-1.19E7,"segmentType":"Geographic","segmentName":"Australia","isPrimarySegment":0},{"dataValue":-1.0E7,"segmentType":"Geographic","segmentName":"Canada","isPrimarySegment":0},{"dataValue":-3.82E7,"segmentType":"Geographic","segmentName":"Europe","isPrimarySegment":0},{"dataValue":3.39E7,"segmentType":"Geographic","segmentName":"United States","isPrimarySegment":1}]}]},{"meta":{"symbol":["GS2C.DE"],"type":["annualTotalRevenue"]},"timestamp":[1643587200,1675123200,1706659200,1738281600],"annualTotalRevenue":[{"dataId":20100,"asOfDate":"2022-01-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":6.0107E9,"fmt":"6.01B"}},{"dataId":20100,"asOfDate":"2023-01-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":5.9272E9,"fmt":"5.93B"}},{"dataId":20100,"asOfDate":"2024-01-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":5.2728E9,"fmt":"5.27B"}},{"dataId":20100,"asOfDate":"2025-01-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":3.823E9,"fmt":"3.82B"}}]},{"meta":{"symbol":["GS2C.DE"],"type":["annualGrossProfit"]},"timestamp":[1643587200,1675123200,1706659200,1738281600],"annualGrossProfit":[{"dataId":20046,"asOfDate":"2022-01-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":1.3478E9,"fmt":"1.35B"}},{"dataId":20046,"asOfDate":"2023-01-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":1.3721E9,"fmt":"1.37B"}},{"dataId":20046,"asOfDate":"2024-01-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":1.2942E9,"fmt":"1.29B"}},{"dataId":20046,"asOfDate":"2025-01-31","periodType":"12M","currencyCode":"USD","reportedValue":{"raw":1.1139E9,"fmt":"1.11B"}}]}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_income_statement_annual_SAP.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_income_statement_annual_SAP.json new file mode 100644 index 0000000..779d08a --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_income_statement_annual_SAP.json @@ -0,0 +1 @@ +{"timeseries":{"result":[{"meta":{"symbol":["SAP"],"type":["annualOperatingIncome"]},"timestamp":[1640908800,1672444800,1703980800,1735603200],"annualOperatingIncome":[{"dataId":20109,"asOfDate":"2021-12-31","periodType":"12M","currencyCode":"EUR","reportedValue":{"raw":6.465E9,"fmt":"6.46B"},"businessSegmentData":[{"dataValue":-6.22E8,"segmentType":"Business","segmentName":"Acquisition-related charges","isPrimarySegment":0},{"dataValue":0.0,"segmentType":"Business","segmentName":"Adjustment for currency impact","isPrimarySegment":0},{"dataValue":0.0,"segmentType":"Business","segmentName":"Adjustment for regulatory compliance matter expenses","isPrimarySegment":0},{"dataValue":9.567E9,"segmentType":"Business","segmentName":"Applications, Technology & Support (ATS)","isPrimarySegment":1},{"dataValue":-1.958E9,"segmentType":"Business","segmentName":"Other expenses","isPrimarySegment":0},{"dataValue":8.59E8,"segmentType":"Business","segmentName":"Other revenue","isPrimarySegment":0},{"dataValue":4.4E7,"segmentType":"Business","segmentName":"Qualtrics","isPrimarySegment":0},{"dataValue":-1.57E8,"segmentType":"Business","segmentName":"Restructuring","isPrimarySegment":0},{"dataValue":7.28E8,"segmentType":"Business","segmentName":"Services","isPrimarySegment":0},{"dataValue":-2.794E9,"segmentType":"Business","segmentName":"Share-based payment expenses","isPrimarySegment":0},{"dataValue":9.284E9,"segmentType":"Business","segmentName":"Single segment","isPrimarySegment":1}]},{"dataId":20109,"asOfDate":"2022-12-31","periodType":"12M","currencyCode":"EUR","reportedValue":{"raw":6.051E9,"fmt":"6.05B"},"businessSegmentData":[{"dataValue":-3.29E8,"segmentType":"Business","segmentName":"Acquisition-related charges","isPrimarySegment":0},{"dataValue":0.0,"segmentType":"Business","segmentName":"Adjustment for currency impact","isPrimarySegment":0},{"dataValue":0.0,"segmentType":"Business","segmentName":"Adjustment for regulatory compliance matter expenses","isPrimarySegment":0},{"dataValue":-1.858E9,"segmentType":"Business","segmentName":"Other expenses","isPrimarySegment":0},{"dataValue":1.024E9,"segmentType":"Business","segmentName":"Other revenue","isPrimarySegment":0},{"dataValue":9.5E7,"segmentType":"Business","segmentName":"Qualtrics","isPrimarySegment":0},{"dataValue":-1.38E8,"segmentType":"Business","segmentName":"Restructuring","isPrimarySegment":0},{"dataValue":-1.431E9,"segmentType":"Business","segmentName":"Share-based payment expenses","isPrimarySegment":0},{"dataValue":8.824E9,"segmentType":"Business","segmentName":"Single segment","isPrimarySegment":1}]},{"dataId":20109,"asOfDate":"2023-12-31","periodType":"12M","currencyCode":"EUR","reportedValue":{"raw":6.014E9,"fmt":"6.01B"},"businessSegmentData":[{"dataValue":-3.45E8,"segmentType":"Business","segmentName":"Acquisition-related charges","isPrimarySegment":0},{"dataValue":0.0,"segmentType":"Business","segmentName":"Adjustment for currency impact","isPrimarySegment":0},{"dataValue":-1.55E8,"segmentType":"Business","segmentName":"Adjustment for regulatory compliance matter expenses","isPrimarySegment":0},{"dataValue":-2.239E9,"segmentType":"Business","segmentName":"Other expenses","isPrimarySegment":0},{"dataValue":1.15E9,"segmentType":"Business","segmentName":"Other revenue","isPrimarySegment":0},{"dataValue":-2.15E8,"segmentType":"Business","segmentName":"Restructuring","isPrimarySegment":0},{"dataValue":-2.22E9,"segmentType":"Business","segmentName":"Share-based payment expenses","isPrimarySegment":0},{"dataValue":9.811E9,"segmentType":"Business","segmentName":"Single segment","isPrimarySegment":1}]},{"dataId":20109,"asOfDate":"2024-12-31","periodType":"12M","currencyCode":"EUR","reportedValue":{"raw":7.808E9,"fmt":"7.81B"}}]},{"meta":{"symbol":["SAP"],"type":["annualNetIncome"]},"timestamp":[1640908800,1672444800,1703980800,1735603200],"annualNetIncome":[{"dataId":20091,"asOfDate":"2021-12-31","periodType":"12M","currencyCode":"EUR","reportedValue":{"raw":5.256E9,"fmt":"5.26B"}},{"dataId":20091,"asOfDate":"2022-12-31","periodType":"12M","currencyCode":"EUR","reportedValue":{"raw":2.284E9,"fmt":"2.28B"}},{"dataId":20091,"asOfDate":"2023-12-31","periodType":"12M","currencyCode":"EUR","reportedValue":{"raw":6.139E9,"fmt":"6.14B"}},{"dataId":20091,"asOfDate":"2024-12-31","periodType":"12M","currencyCode":"EUR","reportedValue":{"raw":3.124E9,"fmt":"3.12B"}}]},{"meta":{"symbol":["SAP"],"type":["annualGrossProfit"]},"timestamp":[1640908800,1672444800,1703980800,1735603200],"annualGrossProfit":[{"dataId":20046,"asOfDate":"2021-12-31","periodType":"12M","currencyCode":"EUR","reportedValue":{"raw":1.9734E10,"fmt":"19.73B"}},{"dataId":20046,"asOfDate":"2022-12-31","periodType":"12M","currencyCode":"EUR","reportedValue":{"raw":2.1482E10,"fmt":"21.48B"}},{"dataId":20046,"asOfDate":"2023-12-31","periodType":"12M","currencyCode":"EUR","reportedValue":{"raw":2.2534E10,"fmt":"22.53B"}},{"dataId":20046,"asOfDate":"2024-12-31","periodType":"12M","currencyCode":"EUR","reportedValue":{"raw":2.4932E10,"fmt":"24.93B"}}]},{"meta":{"symbol":["SAP"],"type":["annualTotalRevenue"]},"timestamp":[1640908800,1672444800,1703980800,1735603200],"annualTotalRevenue":[{"dataId":20100,"asOfDate":"2021-12-31","periodType":"12M","currencyCode":"EUR","reportedValue":{"raw":2.6953E10,"fmt":"26.95B"}},{"dataId":20100,"asOfDate":"2022-12-31","periodType":"12M","currencyCode":"EUR","reportedValue":{"raw":2.9519E10,"fmt":"29.52B"}},{"dataId":20100,"asOfDate":"2023-12-31","periodType":"12M","currencyCode":"EUR","reportedValue":{"raw":3.1207E10,"fmt":"31.21B"}},{"dataId":20100,"asOfDate":"2024-12-31","periodType":"12M","currencyCode":"EUR","reportedValue":{"raw":3.4176E10,"fmt":"34.18B"}}]}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_income_statement_annual_TSCO.L.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_income_statement_annual_TSCO.L.json new file mode 100644 index 0000000..4ef4c89 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_income_statement_annual_TSCO.L.json @@ -0,0 +1 @@ +{"timeseries":{"result":[{"meta":{"symbol":["TSCO.L"],"type":["annualTotalRevenue"]},"timestamp":[1646006400,1677542400,1709164800,1740700800],"annualTotalRevenue":[{"dataId":20100,"asOfDate":"2022-02-28","periodType":"12M","currencyCode":"GBP","reportedValue":{"raw":6.1344E10,"fmt":"61.34B"}},{"dataId":20100,"asOfDate":"2023-02-28","periodType":"12M","currencyCode":"GBP","reportedValue":{"raw":6.5322E10,"fmt":"65.32B"}},{"dataId":20100,"asOfDate":"2024-02-29","periodType":"12M","currencyCode":"GBP","reportedValue":{"raw":6.8187E10,"fmt":"68.19B"}},{"dataId":20100,"asOfDate":"2025-02-28","periodType":"12M","currencyCode":"GBP","reportedValue":{"raw":6.9916E10,"fmt":"69.92B"}}]},{"meta":{"symbol":["TSCO.L"],"type":["annualGrossProfit"]},"timestamp":[1646006400,1677542400,1709164800,1740700800],"annualGrossProfit":[{"dataId":20046,"asOfDate":"2022-02-28","periodType":"12M","currencyCode":"GBP","reportedValue":{"raw":4.633E9,"fmt":"4.63B"}},{"dataId":20046,"asOfDate":"2023-02-28","periodType":"12M","currencyCode":"GBP","reportedValue":{"raw":3.361E9,"fmt":"3.36B"}},{"dataId":20046,"asOfDate":"2024-02-29","periodType":"12M","currencyCode":"GBP","reportedValue":{"raw":4.849E9,"fmt":"4.85B"}},{"dataId":20046,"asOfDate":"2025-02-28","periodType":"12M","currencyCode":"GBP","reportedValue":{"raw":5.051E9,"fmt":"5.05B"}}]},{"meta":{"symbol":["TSCO.L"],"type":["annualOperatingIncome"]},"timestamp":[1646006400,1677542400,1709164800,1740700800],"annualOperatingIncome":[{"dataId":20109,"asOfDate":"2022-02-28","periodType":"12M","currencyCode":"GBP","reportedValue":{"raw":2.649E9,"fmt":"2.65B"},"businessSegmentData":[{"dataValue":2.384E9,"segmentType":"Business","segmentName":"Retail","isPrimarySegment":1},{"dataValue":1.76E8,"segmentType":"Business","segmentName":"Tesco Bank","isPrimarySegment":0}],"geographicSegmentData":[{"dataValue":1.93E8,"segmentType":"Geographic","segmentName":"Central Europe","isPrimarySegment":0},{"dataValue":1.76E8,"segmentType":"Geographic","segmentName":"Tesco bank","isPrimarySegment":0},{"dataValue":2.191E9,"segmentType":"Geographic","segmentName":"United Kingdom & Republic of Ireland (UK & ROI)","isPrimarySegment":1}]},{"dataId":20109,"asOfDate":"2023-02-28","periodType":"12M","currencyCode":"GBP","reportedValue":{"raw":1.48E9,"fmt":"1.48B"},"businessSegmentData":[{"dataValue":-1.07E8,"segmentType":"Business","segmentName":"Banking operations","isPrimarySegment":0},{"dataValue":1.393E9,"segmentType":"Business","segmentName":"Retail","isPrimarySegment":1},{"dataValue":1.24E8,"segmentType":"Business","segmentName":"Tesco Bank","isPrimarySegment":0}],"geographicSegmentData":[{"dataValue":1.44E8,"segmentType":"Geographic","segmentName":"Central Europe","isPrimarySegment":0},{"dataValue":1.32E8,"segmentType":"Geographic","segmentName":"Tesco bank","isPrimarySegment":0},{"dataValue":1.249E9,"segmentType":"Geographic","segmentName":"United Kingdom & Republic of Ireland (UK & ROI)","isPrimarySegment":1}]},{"dataId":20109,"asOfDate":"2024-02-29","periodType":"12M","currencyCode":"GBP","reportedValue":{"raw":2.825E9,"fmt":"2.83B"},"businessSegmentData":[{"dataValue":6.59E8,"segmentType":"Business","segmentName":"Banking operations","isPrimarySegment":0},{"dataValue":2.755E9,"segmentType":"Business","segmentName":"Retail","isPrimarySegment":1},{"dataValue":-5.93E8,"segmentType":"Business","segmentName":"Tesco Bank","isPrimarySegment":0}],"geographicSegmentData":[{"dataValue":6.59E8,"segmentType":"Geographic","segmentName":"Banking operations","isPrimarySegment":0},{"dataValue":6.6E7,"segmentType":"Geographic","segmentName":"Central Europe","isPrimarySegment":0},{"dataValue":-5.93E8,"segmentType":"Geographic","segmentName":"Tesco bank","isPrimarySegment":0},{"dataValue":2.689E9,"segmentType":"Geographic","segmentName":"United Kingdom & Republic of Ireland (UK & ROI)","isPrimarySegment":1}]},{"dataId":20109,"asOfDate":"2025-02-28","periodType":"12M","currencyCode":"GBP","reportedValue":{"raw":2.809E9,"fmt":"2.81B"},"geographicSegmentData":[{"dataValue":-1.8E7,"segmentType":"Geographic","segmentName":"Central Europe","isPrimarySegment":0},{"dataValue":2.729E9,"segmentType":"Geographic","segmentName":"United Kingdom & Republic of Ireland (UK & ROI)","isPrimarySegment":1}]}]},{"meta":{"symbol":["TSCO.L"],"type":["annualNetIncome"]},"timestamp":[1646006400,1677542400,1709164800,1740700800],"annualNetIncome":[{"dataId":20091,"asOfDate":"2022-02-28","periodType":"12M","currencyCode":"GBP","reportedValue":{"raw":1.481E9,"fmt":"1.48B"}},{"dataId":20091,"asOfDate":"2023-02-28","periodType":"12M","currencyCode":"GBP","reportedValue":{"raw":7.37E8,"fmt":"737.00M"}},{"dataId":20091,"asOfDate":"2024-02-29","periodType":"12M","currencyCode":"GBP","reportedValue":{"raw":1.188E9,"fmt":"1.19B"}},{"dataId":20091,"asOfDate":"2025-02-28","periodType":"12M","currencyCode":"GBP","reportedValue":{"raw":1.626E9,"fmt":"1.63B"}}]}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_income_statement_quarterly_AAPL.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_income_statement_quarterly_AAPL.json new file mode 100644 index 0000000..dce9e98 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_income_statement_quarterly_AAPL.json @@ -0,0 +1 @@ +{"timeseries":{"result":[{"meta":{"symbol":["AAPL"],"type":["quarterlyGrossProfit"]},"timestamp":[1719705600,1727654400,1735603200,1743379200,1751241600],"quarterlyGrossProfit":[{"dataId":20046,"asOfDate":"2024-06-30","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":3.9678E10,"fmt":"39.68B"}},{"dataId":20046,"asOfDate":"2024-09-30","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":4.3879E10,"fmt":"43.88B"}},{"dataId":20046,"asOfDate":"2024-12-31","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":5.8275E10,"fmt":"58.27B"}},{"dataId":20046,"asOfDate":"2025-03-31","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":4.4867E10,"fmt":"44.87B"}},{"dataId":20046,"asOfDate":"2025-06-30","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":4.3718E10,"fmt":"43.72B"}}]},{"meta":{"symbol":["AAPL"],"type":["quarterlyTotalRevenue"]},"timestamp":[1719705600,1727654400,1735603200,1743379200,1751241600],"quarterlyTotalRevenue":[{"dataId":20100,"asOfDate":"2024-06-30","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":8.5777E10,"fmt":"85.78B"}},{"dataId":20100,"asOfDate":"2024-09-30","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":9.493E10,"fmt":"94.93B"}},{"dataId":20100,"asOfDate":"2024-12-31","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":1.243E11,"fmt":"124.30B"}},{"dataId":20100,"asOfDate":"2025-03-31","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":9.5359E10,"fmt":"95.36B"}},{"dataId":20100,"asOfDate":"2025-06-30","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":9.4036E10,"fmt":"94.04B"}}]},{"meta":{"symbol":["AAPL"],"type":["quarterlyOperatingIncome"]},"timestamp":[1719705600,1727654400,1735603200,1743379200,1751241600],"quarterlyOperatingIncome":[{"dataId":20109,"asOfDate":"2024-06-30","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":2.5352E10,"fmt":"25.35B"}},{"dataId":20109,"asOfDate":"2024-09-30","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":2.9591E10,"fmt":"29.59B"}},{"dataId":20109,"asOfDate":"2024-12-31","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":4.2832E10,"fmt":"42.83B"}},{"dataId":20109,"asOfDate":"2025-03-31","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":2.9589E10,"fmt":"29.59B"}},{"dataId":20109,"asOfDate":"2025-06-30","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":2.8202E10,"fmt":"28.20B"}}]},{"meta":{"symbol":["AAPL"],"type":["quarterlyNetIncome"]},"timestamp":[1719705600,1727654400,1735603200,1743379200,1751241600],"quarterlyNetIncome":[{"dataId":20091,"asOfDate":"2024-06-30","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":2.1448E10,"fmt":"21.45B"}},{"dataId":20091,"asOfDate":"2024-09-30","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":1.4736E10,"fmt":"14.74B"}},{"dataId":20091,"asOfDate":"2024-12-31","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":3.633E10,"fmt":"36.33B"}},{"dataId":20091,"asOfDate":"2025-03-31","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":2.478E10,"fmt":"24.78B"}},{"dataId":20091,"asOfDate":"2025-06-30","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":2.3434E10,"fmt":"23.43B"}}]}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_quarterlyBasicAverageShares_MSFT.json b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_quarterlyBasicAverageShares_MSFT.json new file mode 100644 index 0000000..9dc60e6 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fixtures/timeseries_quarterlyBasicAverageShares_MSFT.json @@ -0,0 +1 @@ +{"timeseries":{"result":[{"meta":{"symbol":["MSFT"],"type":["quarterlyBasicAverageShares"]},"timestamp":[1719705600,1727654400,1735603200,1743379200,1751241600],"quarterlyBasicAverageShares":[{"dataId":29010,"asOfDate":"2024-06-30","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":7.433E9,"fmt":"7.43B"}},{"dataId":29010,"asOfDate":"2024-09-30","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":7.433E9,"fmt":"7.43B"}},{"dataId":29010,"asOfDate":"2024-12-31","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":7.435E9,"fmt":"7.43B"}},{"dataId":29010,"asOfDate":"2025-03-31","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":7.434E9,"fmt":"7.43B"}},{"dataId":29010,"asOfDate":"2025-06-30","periodType":"3M","currencyCode":"USD","reportedValue":{"raw":7.432E9,"fmt":"7.43B"}}]}],"error":null}} \ No newline at end of file diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fundamentals.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fundamentals.rs new file mode 100644 index 0000000..edbad33 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fundamentals.rs @@ -0,0 +1,10 @@ +mod common; + +#[path = "fundamentals/fcf_fallback.rs"] +mod fcf_fallback; +#[path = "fundamentals/live.rs"] +mod fundamentals_live; +#[path = "fundamentals/offline.rs"] +mod fundamentals_offline; +#[path = "fundamentals/retry_synthetic.rs"] +mod fundamentals_retry_synth; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fundamentals/fcf_fallback.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fundamentals/fcf_fallback.rs new file mode 100644 index 0000000..1a8fa2c --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fundamentals/fcf_fallback.rs @@ -0,0 +1,95 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use paft::money::{Currency, IsoCurrency}; +use url::Url; +use yfinance_rs::core::conversions::*; +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::test] +async fn cashflow_computes_fcf_when_missing() { + let server = MockServer::start(); + let sym = "GOOGL"; + + let body = r#"{ + "timeseries": { + "result": [ + { + "meta": { "type": ["annualOperatingCashFlow"] }, + "timestamp": [1234567890], + "annualOperatingCashFlow": [{ "asOfDate": "2009-02-13", "periodType": "12M", "currencyCode": "USD", "reportedValue": {"raw": 100.0} }] + }, + { + "meta": { "type": ["annualCapitalExpenditure"] }, + "timestamp": [1234567890], + "annualCapitalExpenditure": [{ "asOfDate": "2009-02-13", "periodType": "12M", "currencyCode": "USD", "reportedValue": {"raw": -30.0} }] + }, + { + "meta": { "type": ["annualNetIncome"] }, + "timestamp": [1234567890], + "annualNetIncome": [{ "asOfDate": "2009-02-13", "periodType": "12M", "currencyCode": "USD", "reportedValue": {"raw": 65.0} }] + } + ], + "error": null + } + }"#; + + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!( + "/ws/fundamentals-timeseries/v1/finance/timeseries/{sym}" + )) + .query_param_exists("type") + .query_param("crumb", "crumb"); + then.status(200) + .header("content-type", "application/json") + .body(body); + }); + + let client = YfClient::builder() + .base_timeseries( + Url::parse(&format!( + "{}/ws/fundamentals-timeseries/v1/finance/timeseries/", + server.base_url() + )) + .unwrap(), + ) + ._preauth("cookie", "crumb") + .build() + .unwrap(); + + let t = Ticker::new(&client, sym); + let rows = t.cashflow(None).await.unwrap(); + + mock.assert(); + + assert_eq!(rows.len(), 1); + assert_eq!( + rows[0].operating_cashflow, + Some(f64_to_money_with_currency( + 100.0, + Currency::Iso(IsoCurrency::USD) + )) + ); + assert_eq!( + rows[0].capital_expenditures, + Some(f64_to_money_with_currency( + -30.0, + Currency::Iso(IsoCurrency::USD) + )) + ); + assert_eq!( + rows[0].free_cash_flow, + Some(f64_to_money_with_currency( + 70.0, + Currency::Iso(IsoCurrency::USD) + )), + "fcf = ocf + capex (where capex is negative)" + ); + assert_eq!( + rows[0].net_income, + Some(f64_to_money_with_currency( + 65.0, + Currency::Iso(IsoCurrency::USD) + )) + ); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fundamentals/live.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fundamentals/live.rs new file mode 100644 index 0000000..daadc6a --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fundamentals/live.rs @@ -0,0 +1,83 @@ +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_fundamentals_smoke() { + // Mirrors the live/record pattern used elsewhere + if !crate::common::live_or_record_enabled() { + return; + } + + let client = yfinance_rs::YfClient::builder().build().unwrap(); + + // Use distinct symbols to avoid fixture clobbering since endpoint name is the same + // ("fundamentals_api_.json") + // income (quarterly) + { + let t = yfinance_rs::Ticker::new(&client, "AAPL"); + let _ = t.quarterly_income_stmt(None).await.unwrap(); + } + // balance (annual) + { + let t = yfinance_rs::Ticker::new(&client, "MSFT"); + let _ = t.balance_sheet(None).await.unwrap(); + } + // cashflow (annual) + { + let t = yfinance_rs::Ticker::new(&client, "GOOGL"); + let _ = t.cashflow(None).await.unwrap(); + } + // earnings + { + let t = yfinance_rs::Ticker::new(&client, "AMZN"); + let _ = t.earnings(None).await.unwrap(); + } + // calendar + { + let t = yfinance_rs::Ticker::new(&client, "META"); + let _ = t.calendar().await.unwrap(); + } + + if !crate::common::is_recording() { + // If not recording, at least assert we got *some* data from live + // (No strict expectations; shapes vary by company) + let t = yfinance_rs::Ticker::new(&client, "AAPL"); + let income = t.quarterly_income_stmt(None).await.unwrap(); + assert!(!income.is_empty()); + } + + if !crate::common::is_recording() { + let t = yfinance_rs::Ticker::new(&client, "MSFT"); + let balance_sheet = t.balance_sheet(None).await.unwrap(); + assert!(!balance_sheet.is_empty()); + assert!(balance_sheet[0].shares_outstanding.is_some()); + } +} + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_fundamentals_for_record() { + // Only run when actually recording; this populates fixtures for offline tests. + if !crate::common::is_recording() { + return; + } + + let client = yfinance_rs::YfClient::builder().build().unwrap(); + + let t1 = yfinance_rs::Ticker::new(&client, "AAPL"); + let _ = t1.quarterly_income_stmt(None).await; + + let t2 = yfinance_rs::Ticker::new(&client, "MSFT"); + let _ = t2.balance_sheet(None).await; + + let t3 = yfinance_rs::Ticker::new(&client, "GOOGL"); + let _ = t3.cashflow(None).await; + + let t4 = yfinance_rs::Ticker::new(&client, "AMZN"); + let _ = t4.earnings(None).await; + + let t5 = yfinance_rs::Ticker::new(&client, "META"); + let _ = t5.calendar().await; + + // Also record annual income statement for TSCO.L used by currency inference offline test + let t6 = yfinance_rs::Ticker::new(&client, "TSCO.L"); + let _ = t6.income_stmt(None).await; +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fundamentals/offline.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fundamentals/offline.rs new file mode 100644 index 0000000..126ad8a --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fundamentals/offline.rs @@ -0,0 +1,199 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::{ApiPreference, Ticker, YfClient}; + +fn fixture(endpoint: &str, symbol: &str) -> String { + crate::common::fixture(endpoint, symbol, "json") +} + +/* ------------- income statement (quarterly) ------------- */ + +#[tokio::test] +async fn offline_income_quarterly_uses_recorded_fixture() { + // Use AAPL for incomeStatementHistoryQuarterly (record first) + let sym = "AAPL"; + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!( + "/ws/fundamentals-timeseries/v1/finance/timeseries/{sym}" + )) + .query_param_exists("type") + .query_param("crumb", "crumb"); + then.status(200) + .header("content-type", "application/json") + .body(fixture("timeseries_income_statement_quarterly", sym)); + }); + + let client = YfClient::builder() + .base_timeseries( + Url::parse(&format!( + "{}/ws/fundamentals-timeseries/v1/finance/timeseries/", + server.base_url() + )) + .unwrap(), + ) + ._preauth("cookie", "crumb") + .build() + .unwrap(); + + let t = Ticker::new(&client, sym); + let rows = t.quarterly_income_stmt(None).await.unwrap(); + + mock.assert(); + assert!(!rows.is_empty(), "record with YF_RECORD=1 first"); +} + +/* ---------------- balance sheet (annual) ---------------- */ + +#[tokio::test] +async fn offline_balance_sheet_annual_uses_recorded_fixture() { + let sym = "MSFT"; + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!( + "/ws/fundamentals-timeseries/v1/finance/timeseries/{sym}" + )) + .query_param_exists("type") + .query_param("crumb", "crumb"); + then.status(200) + .header("content-type", "application/json") + .body(fixture("timeseries_balance_sheet_annual", sym)); + }); + + let client = YfClient::builder() + .base_timeseries( + Url::parse(&format!( + "{}/ws/fundamentals-timeseries/v1/finance/timeseries/", + server.base_url() + )) + .unwrap(), + ) + ._preauth("cookie", "crumb") + .build() + .unwrap(); + + let t = Ticker::new(&client, sym); + let rows = t.balance_sheet(None).await.unwrap(); + + mock.assert(); + assert!(!rows.is_empty(), "record with YF_RECORD=1 first"); +} + +/* ---------------- cashflow (annual) ---------------- */ + +#[tokio::test] +async fn offline_cashflow_annual_uses_recorded_fixture() { + let sym = "GOOGL"; + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!( + "/ws/fundamentals-timeseries/v1/finance/timeseries/{sym}" + )) + .query_param_exists("type") + .query_param("crumb", "crumb"); + then.status(200) + .header("content-type", "application/json") + .body(fixture("timeseries_cash_flow_annual", sym)); + }); + + let client = YfClient::builder() + .base_timeseries( + Url::parse(&format!( + "{}/ws/fundamentals-timeseries/v1/finance/timeseries/", + server.base_url() + )) + .unwrap(), + ) + ._preauth("cookie", "crumb") + .build() + .unwrap(); + + let t = Ticker::new(&client, sym); + let rows = t.cashflow(None).await.unwrap(); + + mock.assert(); + assert!(!rows.is_empty(), "record with YF_RECORD=1 first"); +} + +/* ---------------- earnings ---------------- */ + +#[tokio::test] +async fn offline_earnings_uses_recorded_fixture() { + // Use AMZN for earnings (record first) + let sym = "AMZN"; + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "earnings") + .query_param("crumb", "crumb"); + then.status(200) + .header("content-type", "application/json") + .body(fixture("fundamentals_api_earnings", sym)); + }); + + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + ._api_preference(ApiPreference::ApiOnly) + ._preauth("cookie", "crumb") + .build() + .unwrap(); + + let t = Ticker::new(&client, sym); + let e = t.earnings(None).await.unwrap(); + + mock.assert(); + assert!( + !e.yearly.is_empty() || !e.quarterly.is_empty() || !e.quarterly_eps.is_empty(), + "record with YF_RECORD=1 first" + ); +} + +/* ---------------- calendar ---------------- */ + +#[tokio::test] +async fn offline_calendar_uses_recorded_fixture() { + // Use META for calendarEvents (record first) + let sym = "META"; + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "calendarEvents") + .query_param("crumb", "crumb"); + then.status(200) + .header("content-type", "application/json") + .body(fixture("fundamentals_api_calendarEvents", sym)); + }); + + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + ._api_preference(ApiPreference::ApiOnly) + ._preauth("cookie", "crumb") + .build() + .unwrap(); + + let t = Ticker::new(&client, sym); + let cal = t.calendar().await.unwrap(); + + mock.assert(); + assert!( + !cal.earnings_dates.is_empty() + || cal.ex_dividend_date.is_some() + || cal.ex_dividend_date.is_some(), + "record with YF_RECORD=1 first" + ); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fundamentals/retry_synthetic.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fundamentals/retry_synthetic.rs new file mode 100644 index 0000000..29d01e4 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/fundamentals/retry_synthetic.rs @@ -0,0 +1,84 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::{ApiPreference, Ticker, YfClient}; + +#[tokio::test] +async fn fundamentals_invalid_crumb_then_retry_succeeds() { + let server = MockServer::start(); + let sym = "AAPL"; + + // first call with stale crumb -> Invalid Crumb + let first = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "earnings") + .query_param("crumb", "stale"); + then.status(200) + .header("content-type", "application/json") + .body(r#"{"quoteSummary":{"result":null,"error":{"description":"Invalid Crumb"}}}"#); + }); + + // cookie + crumb refresh endpoints + let cookie = server.mock(|when, then| { + when.method(GET).path("/consent"); + then.status(200).header( + "set-cookie", + "A=B; Max-Age=315360000; Domain=.yahoo.com; Path=/; Secure; SameSite=None", + ); + }); + let crumb = server.mock(|when, then| { + when.method(GET).path("/v1/test/getcrumb"); + then.status(200).body("fresh"); + }); + + // second call with fresh crumb returns minimal earnings payload + let ok = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "earnings") + .query_param("crumb", "fresh"); + then.status(200) + .header("content-type", "application/json") + .body( + r#"{ + "quoteSummary": { + "result": [{ + "earnings": { + "financialsChart": { + "yearly": [], + "quarterly": [] + }, + "earningsChart": { + "quarterly": [] + } + } + }], + "error": null + } + }"#, + ); + }); + + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + .cookie_url(Url::parse(&format!("{}/consent", server.base_url())).unwrap()) + .crumb_url(Url::parse(&format!("{}/v1/test/getcrumb", server.base_url())).unwrap()) + ._api_preference(ApiPreference::ApiOnly) + ._preauth("cookie", "stale") + .build() + .unwrap(); + + let t = Ticker::new(&client, sym); + let e = t.earnings(None).await.unwrap(); + + first.assert(); + cookie.assert(); + crumb.assert(); + ok.assert(); + + // We just verify it returns a valid (possibly empty) structure + assert!(e.yearly.is_empty() && e.quarterly.is_empty() && e.quarterly_eps.is_empty()); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history.rs new file mode 100644 index 0000000..c07eccc --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history.rs @@ -0,0 +1,34 @@ +mod common; + +#[path = "history/adjust_from_splits_only.rs"] +mod adjust_from_splits_only; +#[path = "history/adjust.rs"] +mod history_adjust; +#[path = "history/intervals.rs"] +mod history_intervals; +#[path = "history/live.rs"] +mod history_live; +#[path = "history/meta.rs"] +mod history_meta; +#[path = "history/nulls_synthetic.rs"] +mod history_nulls_synth; +#[path = "history/offline.rs"] +mod history_offline; +#[path = "history/params.rs"] +mod history_params; +#[path = "history/ranges_new.rs"] +mod history_ranges_new; +#[path = "history/smoke.rs"] +mod history_smoke; + +#[path = "history/keepna_true.rs"] +mod keepna_true; + +#[path = "history/http_status_error.rs"] +mod http_status_error; + +#[path = "history/retry_synthetic.rs"] +mod retry_synthetic; + +#[path = "history/caching_synthetic.rs"] +mod caching_synthetic; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/adjust.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/adjust.rs new file mode 100644 index 0000000..a08cca9 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/adjust.rs @@ -0,0 +1,95 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::core::Interval; +use yfinance_rs::core::conversions::*; +use yfinance_rs::{Action, HistoryBuilder, YfClient}; + +#[tokio::test] +async fn history_auto_adjust_and_actions() { + let server = MockServer::start(); + + // Three days: + // t1=1000 (before 2:1 split), t2=2000 (split date), t3=3000 (dividend date) + // OHLC all ~100, volume = 10 each day + // adjclose encodes: 0.5 factor on t1 (split), 1.0 on t2, 0.99 on t3 (dividend) + let body = r#"{ + "chart":{ + "result":[ + { + "timestamp":[1000,2000,3000], + "indicators":{ + "quote":[{ + "open":[100.0,100.0,100.0], + "high":[101.0,101.0,101.0], + "low":[99.0,99.0,99.0], + "close":[100.0,100.0,100.0], + "volume":[10,10,10] + }], + "adjclose":[{"adjclose":[50.0,100.0,99.0]}] + }, + "events":{ + "splits":{ + "2000":{"date":2000,"numerator":2,"denominator":1} + }, + "dividends":{ + "3000":{"date":3000,"amount":1.0} + } + } + } + ], + "error":null + } + }"#; + + let mock = server.mock(|when, then| { + when.method(GET).path("/v8/finance/chart/TEST"); + then.status(200) + .header("content-type", "application/json") + .body(body); + }); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let resp = HistoryBuilder::new(&client, "TEST") + .interval(Interval::D1) + .auto_adjust(true) + .fetch_full() + .await + .unwrap(); + + mock.assert(); + + assert!(resp.adjusted); + assert_eq!(resp.candles.len(), 3); + + // t1 (1000): prices halved, volume doubled due to 2:1 split after this candle + let c0 = &resp.candles[0]; + assert!((money_to_f64(&c0.open) - 50.0).abs() < 1e-9); + assert!((money_to_f64(&c0.high) - 50.5).abs() < 1e-9); + assert!((money_to_f64(&c0.low) - 49.5).abs() < 1e-9); + assert!((money_to_f64(&c0.close) - 50.0).abs() < 1e-9); + assert_eq!(c0.volume, Some(20)); + + // t2 (2000): unchanged prices, unchanged volume + let c1 = &resp.candles[1]; + assert!((money_to_f64(&c1.close) - 100.0).abs() < 1e-9); + assert_eq!(c1.volume, Some(10)); + + // t3 (3000): dividend -> adjclose=99 => factor 0.99 + let c2 = &resp.candles[2]; + assert!((money_to_f64(&c2.close) - 99.0).abs() < 1e-9); + assert_eq!(c2.volume, Some(10)); + + // actions parsed and sorted + assert_eq!(resp.actions.len(), 2); + assert!( + matches!(resp.actions[0], Action::Split { ts, numerator:2, denominator:1 } if ts.timestamp()==2000) + ); + assert!( + matches!(&resp.actions[1], Action::Dividend { ts, amount } if ts.timestamp()==3000 && (money_to_f64(amount)-1.0).abs()<1e-9) + ); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/adjust_from_splits_only.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/adjust_from_splits_only.rs new file mode 100644 index 0000000..9cab704 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/adjust_from_splits_only.rs @@ -0,0 +1,67 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::core::Interval; +use yfinance_rs::core::conversions::*; +use yfinance_rs::{HistoryBuilder, YfClient}; + +#[tokio::test] +async fn history_auto_adjust_uses_splits_when_adjclose_missing() { + let server = MockServer::start(); + + // No "adjclose" block; only a 2-for-1 split at the second timestamp. + let body = r#"{ + "chart":{ + "result":[ + { + "timestamp":[1000,2000], + "indicators":{ + "quote":[{ + "open":[100.0,100.0], + "high":[101.0,101.0], + "low":[ 99.0, 99.0], + "close":[100.0,100.0], + "volume":[10,10] + }], + "adjclose":[] + }, + "events": { + "splits": { + "2000": { "date": 2000, "numerator": 2, "denominator": 1 } + } + } + } + ], + "error": null + } + }"#; + + let mock = server.mock(|when, then| { + when.method(GET).path("/v8/finance/chart/TEST"); + then.status(200) + .header("content-type", "application/json") + .body(body); + }); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let resp = HistoryBuilder::new(&client, "TEST") + .interval(Interval::D1) + .auto_adjust(true) + .fetch_full() + .await + .unwrap(); + + mock.assert(); + + assert!(resp.adjusted); + assert_eq!(resp.candles.len(), 2); + // First bar should be split-adjusted (0.5x); second bar unchanged. + assert!((money_to_f64(&resp.candles[0].close) - 50.0).abs() < 1e-9); + assert!((money_to_f64(&resp.candles[1].close) - 100.0).abs() < 1e-9); + // Volume before split should be multiplied by 2 and rounded. + assert_eq!(resp.candles[0].volume, Some(20)); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/caching_synthetic.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/caching_synthetic.rs new file mode 100644 index 0000000..ac8e585 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/caching_synthetic.rs @@ -0,0 +1,85 @@ +use httpmock::{Method::GET, MockServer}; +use std::time::Duration; +use url::Url; +use yfinance_rs::{HistoryBuilder, YfClient, core::client::CacheMode}; + +#[tokio::test] +async fn history_serves_from_cache_on_second_call() { + let server = MockServer::start(); + let sym = "CACHE"; + + // This mock only expects to be called ONCE. + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v8/finance/chart/{sym}")) + .query_param("range", "6mo") + .query_param("interval", "1d"); + then.status(200) + .header("content-type", "application/json") + .body(crate::common::fixture("history_chart", "AAPL", "json")); + }); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .cache_ttl(Duration::from_secs(10)) // Enable caching + .build() + .unwrap(); + + let builder = HistoryBuilder::new(&client, sym); + + // First call, should hit the network + let bars1 = builder.clone().fetch().await.unwrap(); + mock.assert(); // Verifies the mock was called exactly once + + // Second call, should be served from cache + let bars2 = builder.clone().fetch().await.unwrap(); + + // Verify again. The hit count should still be 1. + mock.assert(); + + assert_eq!(bars1.len(), bars2.len()); + assert_eq!(bars1[0], bars2[0]); +} + +#[tokio::test] +async fn history_cache_refresh_bypasses_cache_get_but_updates_cache() { + let server = MockServer::start(); + let sym = "REFRESH"; + + // This mock expects to be called TWICE. + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v8/finance/chart/{sym}")) + .query_param("range", "6mo") + .query_param("interval", "1d"); + then.status(200) + .header("content-type", "application/json") + .body(crate::common::fixture("history_chart", "MSFT", "json")); + }); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .cache_ttl(Duration::from_secs(10)) // Enable caching + .build() + .unwrap(); + + let builder = HistoryBuilder::new(&client, sym); + + // First call, hits network and populates cache + let _ = builder.clone().fetch().await.unwrap(); + mock.assert_calls(1); + + // Second call with CacheMode::Refresh, should hit network again + let _ = builder + .clone() + .cache_mode(CacheMode::Refresh) + .fetch() + .await + .unwrap(); + mock.assert_calls(2); + + // Third call with default CacheMode::Use, should now be served from cache + let _ = builder.clone().fetch().await.unwrap(); + // The hit count should NOT increase, so we assert it's still 2. + mock.assert_calls(2); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/http_status_error.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/http_status_error.rs new file mode 100644 index 0000000..a7d611d --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/http_status_error.rs @@ -0,0 +1,38 @@ +// tests/history/http_status_error.rs + +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::YfClient; + +#[tokio::test] +async fn history_returns_status_error_on_non_2xx() { + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + when.method(GET).path("/v8/finance/chart/FAIL"); + then.status(500).body("oops"); + }); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .retry_enabled(false) // Disable retries for this test + .build() + .unwrap(); + + let err = yfinance_rs::HistoryBuilder::new(&client, "FAIL") + .fetch() + .await + .unwrap_err(); + + // The mock server now correctly expects only one call + mock.assert(); + + match err { + yfinance_rs::YfError::ServerError { status, url } => { + assert_eq!(status, 500); + assert!(url.contains("/v8/finance/chart/FAIL")); + } + other => panic!("expected ServerError, got {other:?}"), + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/intervals.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/intervals.rs new file mode 100644 index 0000000..157fb7a --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/intervals.rs @@ -0,0 +1,36 @@ +use crate::common; +use httpmock::Method::GET; +use url::Url; +use yfinance_rs::core::Interval; +use yfinance_rs::{HistoryBuilder, YfClient}; + +#[tokio::test] +async fn history_allows_intraday_interval() { + let server = common::setup_server(); + + let mock = server.mock(|when, then| { + when.method(GET) + .path("/v8/finance/chart/AAPL") + .query_param("range", "6mo") + .query_param("interval", "5m") + .query_param("includePrePost", "false") + .query_param("events", "div|split|capitalGains"); + then.status(200) + .header("content-type", "application/json") + .body(common::fixture("history_chart", "AAPL", "json")); + }); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + // Only checking query params wiring; body content comes from fixture + let _ = HistoryBuilder::new(&client, "AAPL") + .interval(Interval::I5m) + .fetch() + .await + .unwrap(); + + mock.assert(); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/keepna_true.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/keepna_true.rs new file mode 100644 index 0000000..24e2900 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/keepna_true.rs @@ -0,0 +1,48 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::core::conversions::*; +use yfinance_rs::{HistoryBuilder, YfClient}; + +#[tokio::test] +async fn history_keepna_preserves_null_rows() { + let server = MockServer::start(); + + let body = r#"{ + "chart":{"result":[{"timestamp":[1,2], + "indicators":{"quote":[{ + "open":[100.0,null], + "high":[101.0,null], + "low":[ 99.0,null], + "close":[100.5,null], + "volume":[1000,2000] + }]}}],"error":null} + }"#; + + let mock = server.mock(|when, then| { + when.method(GET).path("/v8/finance/chart/AAPL"); + then.status(200) + .header("content-type", "application/json") + .body(body); + }); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let bars = HistoryBuilder::new(&client, "AAPL") + .keepna(true) + .fetch() + .await + .unwrap(); + + mock.assert(); + + assert_eq!(bars.len(), 2, "second row kept with NaNs"); + // With Money type, NaN values might be converted to 0.0 or default values + // Let's just check that the row exists and has some values + assert!(money_to_f64(&bars[1].open) == 0.0 || money_to_f64(&bars[1].open).is_nan()); + assert!(money_to_f64(&bars[1].close) == 0.0 || money_to_f64(&bars[1].close).is_nan()); + assert_eq!(bars[1].volume, Some(2000)); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/live.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/live.rs new file mode 100644 index 0000000..f911ada --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/live.rs @@ -0,0 +1,36 @@ +use yfinance_rs::core::conversions::*; + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_history_smoke() { + if !crate::common::live_or_record_enabled() { + return; + } + + let client = yfinance_rs::YfClient::builder().build().unwrap(); + let bars = yfinance_rs::HistoryBuilder::new(&client, "AAPL") + .fetch() + .await + .unwrap(); + + if !crate::common::is_recording() { + assert!(!bars.is_empty()); + assert!(money_to_f64(&bars[0].open) > 0.0 && money_to_f64(&bars[0].close) > 0.0); + } +} + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_history_for_record() { + if !crate::common::is_recording() { + return; + } + + let client = yfinance_rs::YfClient::builder().build().unwrap(); + let _ = yfinance_rs::HistoryBuilder::new(&client, "AAPL") + .fetch() + .await; + let _ = yfinance_rs::HistoryBuilder::new(&client, "MSFT") + .fetch() + .await; +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/meta.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/meta.rs new file mode 100644 index 0000000..0270e6b --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/meta.rs @@ -0,0 +1,56 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::core::Range; +use yfinance_rs::{Ticker, YfClient}; + +fn meta_body() -> String { + r#"{ + "chart":{ + "result":[ + { + "meta": { "timezone":"America/New_York", "gmtoffset": -14400 }, + "timestamp": [], + "indicators": { + "quote":[{ "open":[], "high":[], "low":[], "close":[], "volume":[] }], + "adjclose":[{ "adjclose":[] }] + } + } + ], + "error": null + } + }"# + .to_string() +} + +#[tokio::test] +async fn get_history_metadata_returns_timezone() { + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + when.method(GET) + .path("/v8/finance/chart/MSFT") + .query_param("range", "1d") + .query_param("interval", "1d") + .query_param("events", "div|split|capitalGains"); + then.status(200) + .header("content-type", "application/json") + .body(meta_body()); + }); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let t = Ticker::new(&client, "MSFT"); + let meta = t.get_history_metadata(Some(Range::D1)).await.unwrap(); + + mock.assert(); + let m = meta.expect("meta should be Some"); + assert_eq!( + m.timezone.as_ref().map(std::string::ToString::to_string), + Some("America/New_York".to_string()) + ); + assert_eq!(m.utc_offset_seconds, Some(-14400)); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/nulls_synthetic.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/nulls_synthetic.rs new file mode 100644 index 0000000..c04b8c1 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/nulls_synthetic.rs @@ -0,0 +1,51 @@ +use httpmock::{Method::GET, MockServer}; +use paft::money::{Currency, IsoCurrency}; +use url::Url; +use yfinance_rs::core::conversions::*; +use yfinance_rs::{HistoryBuilder, YfClient}; + +#[tokio::test] +async fn history_skips_points_with_null_ohlc() { + let server = MockServer::start(); + + // Minimal chart payload: first point valid, second has open=null so must be skipped + let body = r#"{ + "chart":{"result":[{"timestamp":[1704067200,1704153600], + "indicators":{"quote":[{ + "open":[100.0,null], + "high":[101.0,102.0], + "low":[99.0,100.0], + "close":[100.5,101.5], + "volume":[1000000,1100000] + }]}}],"error":null} + }"#; + + let mock = server.mock(|when, then| { + when.method(GET).path("/v8/finance/chart/AAPL"); + then.status(200) + .header("content-type", "application/json") + .body(body); + }); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let bars = HistoryBuilder::new(&client, "AAPL").fetch().await.unwrap(); + mock.assert(); + assert_eq!( + bars.len(), + 1, + "second point with null open should be filtered out" + ); + assert_eq!( + bars[0].open, + f64_to_money_with_currency(100.0, Currency::Iso(IsoCurrency::USD)) + ); + assert_eq!( + bars[0].close, + f64_to_money_with_currency(100.5, Currency::Iso(IsoCurrency::USD)) + ); + assert_eq!(bars[0].volume, Some(1_000_000)); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/offline.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/offline.rs new file mode 100644 index 0000000..a7f512f --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/offline.rs @@ -0,0 +1,41 @@ +use httpmock::{Method::GET, MockServer}; +use url::Url; + +#[tokio::test] +async fn offline_history_uses_recorded_fixture() { + fn fixture_dir() -> std::path::PathBuf { + std::env::var("YF_FIXDIR").map_or_else( + |_| std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures"), + std::path::PathBuf::from, + ) + } + fn fixture(endpoint: &str, symbol: &str, ext: &str) -> String { + let filename = format!("{endpoint}_{symbol}.{ext}"); + let path = fixture_dir().join(&filename); + std::fs::read_to_string(&path) + .unwrap_or_else(|e| panic!("failed to read fixture {}: {}", path.display(), e)) + } + + let server = MockServer::start(); + let sym = "AAPL"; + + let mock = server.mock(|when, then| { + when.method(GET).path(format!("/v8/finance/chart/{sym}")); + then.status(200) + .header("content-type", "application/json") + .body(fixture("history_chart", sym, "json")); + }); + + let client = yfinance_rs::YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let bars = yfinance_rs::HistoryBuilder::new(&client, sym) + .fetch() + .await + .unwrap(); + + mock.assert(); + assert!(!bars.is_empty(), "record with YF_RECORD=1 first"); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/params.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/params.rs new file mode 100644 index 0000000..2660484 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/params.rs @@ -0,0 +1,34 @@ +use crate::common; +use httpmock::Method::GET; +use url::Url; +use yfinance_rs::core::Range; +use yfinance_rs::{HistoryBuilder, YfClient}; + +#[tokio::test] +async fn history_has_expected_query_params() { + let server = common::setup_server(); + + let mock = server.mock(|when, then| { + when.method(GET) + .path("/v8/finance/chart/AAPL") + .query_param("range", "6mo") + .query_param("interval", "1d") + .query_param("events", "div|split|capitalGains"); + then.status(200) + .header("content-type", "application/json") + .body(common::fixture("history_chart", "AAPL", "json")); + }); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let _ = HistoryBuilder::new(&client, "AAPL") + .range(Range::M6) + .fetch() + .await + .unwrap(); + + mock.assert(); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/ranges_new.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/ranges_new.rs new file mode 100644 index 0000000..5e376dd --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/ranges_new.rs @@ -0,0 +1,81 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::core::Range; +use yfinance_rs::{HistoryBuilder, YfClient}; + +fn minimal_ok_body() -> String { + r#"{ + "chart": { + "result": [{ + "meta": {"timezone":"America/New_York","gmtoffset":-14400}, + "timestamp": [], + "indicators": { "quote":[{ "open":[], "high":[], "low":[], "close":[], "volume":[] }], "adjclose":[{"adjclose":[]}] } + }], + "error": null + } + }"#.to_string() +} + +#[tokio::test] +async fn history_range_1d_ytd_10y() { + let server = MockServer::start(); + + let m1d = server.mock(|when, then| { + when.method(GET) + .path("/v8/finance/chart/AAPL") + .query_param("range", "1d") + .query_param("interval", "1d") + .query_param("events", "div|split|capitalGains"); + then.status(200) + .header("content-type", "application/json") + .body(minimal_ok_body()); + }); + + let mytd = server.mock(|when, then| { + when.method(GET) + .path("/v8/finance/chart/AAPL") + .query_param("range", "ytd") + .query_param("interval", "1d") + .query_param("events", "div|split|capitalGains"); + then.status(200) + .header("content-type", "application/json") + .body(minimal_ok_body()); + }); + + let m10y = server.mock(|when, then| { + when.method(GET) + .path("/v8/finance/chart/AAPL") + .query_param("range", "10y") + .query_param("interval", "1d") + .query_param("events", "div|split|capitalGains"); + then.status(200) + .header("content-type", "application/json") + .body(minimal_ok_body()); + }); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let _ = HistoryBuilder::new(&client, "AAPL") + .range(Range::D1) + .fetch() + .await + .unwrap(); + let _ = HistoryBuilder::new(&client, "AAPL") + .range(Range::Ytd) + .fetch() + .await + .unwrap(); + let _ = HistoryBuilder::new(&client, "AAPL") + .range(Range::Y10) + .fetch() + .await + .unwrap(); + + m1d.assert(); + mytd.assert(); + m10y.assert(); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/retry_synthetic.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/retry_synthetic.rs new file mode 100644 index 0000000..004c280 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/retry_synthetic.rs @@ -0,0 +1,52 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use std::time::Duration; +use url::Url; +use yfinance_rs::{ + HistoryBuilder, YfClient, YfError, + core::client::{Backoff, RetryConfig}, +}; + +#[tokio::test] +async fn history_retries_on_persistent_5xx() { + let server = MockServer::start(); + let sym = "RETRY"; + + // This single mock will persistently fail, allowing us to count the retries. + let fail_mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v8/finance/chart/{sym}")) + .query_param("range", "6mo") + .query_param("interval", "1d") + .query_param("events", "div|split|capitalGains") + .query_param("includePrePost", "false"); + then.status(503).body("Service Unavailable"); + }); + + let max_retries = 3; + let client_retry_config = RetryConfig { + backoff: Backoff::Fixed(Duration::from_millis(1)), + max_retries, + ..RetryConfig::default() + }; + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .retry_config(client_retry_config) + .build() + .unwrap(); + + // We expect this to fail, so we match on the Err variant. + let result = HistoryBuilder::new(&client, sym).fetch().await; + + // The mock should be hit 1 (initial) + 3 (retries) = 4 times. + fail_mock.assert_calls((1 + max_retries) as usize); + + // Assert that the final result is the expected error. + match result { + Err(YfError::ServerError { status, .. }) => { + assert_eq!(status, 503); + } + other => panic!("Expected a ServerError after all retries failed, got {other:?}"), + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/smoke.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/smoke.rs new file mode 100644 index 0000000..dba7932 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/history/smoke.rs @@ -0,0 +1,101 @@ +use crate::common; +use crate::common::{mock_history_chart, setup_server}; +use url::Url; +use yfinance_rs::core::Range; +use yfinance_rs::core::conversions::*; +use yfinance_rs::{HistoryBuilder, YfClient}; + +#[tokio::test] +async fn history_happy_path() { + let server = setup_server(); + let mock = mock_history_chart(&server, "AAPL"); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let bars = HistoryBuilder::new(&client, "AAPL") + .range(Range::M6) + .fetch() + .await + .unwrap(); + + mock.assert(); + // The recorded fixture has many data points, not just 2. + assert!(bars.len() > 100, "Expected a significant number of bars"); + assert!(money_to_f64(&bars[0].open) > 0.0); + assert!(money_to_f64(&bars[0].high) > 0.0); + assert!(money_to_f64(&bars[0].low) > 0.0); + assert!(money_to_f64(&bars[0].close) > 0.0); +} + +#[tokio::test] +async fn history_no_data_is_ok() { + let server = setup_server(); + let mock = mock_history_chart(&server, "MSFT"); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let bars = HistoryBuilder::new(&client, "MSFT").fetch().await.unwrap(); + mock.assert(); + // The recorded fixture for a real stock will have data. + assert!(!bars.is_empty(), "Expected some data for a real stock"); +} + +#[tokio::test] +async fn history_absolute_range_happy() { + use chrono::{Duration, TimeZone, Utc}; + + let server = setup_server(); + + let start = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(); + let end = start + Duration::days(10); + + let mock = server.mock(|when, then| { + when.method(httpmock::Method::GET) + .path("/v8/finance/chart/AAPL") + .query_param("period1", start.timestamp().to_string()) + .query_param("period2", end.timestamp().to_string()); + then.status(200) + .header("content-type", "application/json") + .body(common::fixture("history_chart", "AAPL", "json")); + }); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let bars = HistoryBuilder::new(&client, "AAPL") + .between(start, end) + .fetch() + .await + .unwrap(); + + mock.assert(); + // The mock serves the full 6-month fixture regardless of the date range, + // so we expect the full data set to be parsed. + assert!(bars.len() > 100, "Expected a significant number of bars"); + assert!(money_to_f64(&bars[0].open) > 0.0); +} + +#[tokio::test] +async fn history_between_invalid_dates() { + use chrono::{Duration, TimeZone, Utc}; + let client = YfClient::default(); + + let start = Utc.with_ymd_and_hms(2024, 1, 10, 0, 0, 0).unwrap(); + let end = start - Duration::days(1); + + let err = HistoryBuilder::new(&client, "AAPL") + .between(start, end) + .fetch() + .await + .unwrap_err(); + + assert!(matches!(err, yfinance_rs::YfError::InvalidDates)); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/holders.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/holders.rs new file mode 100644 index 0000000..3534b3d --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/holders.rs @@ -0,0 +1,6 @@ +mod common; + +#[path = "holders/live.rs"] +mod holders_live; +#[path = "holders/offline.rs"] +mod holders_offline; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/holders/live.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/holders/live.rs new file mode 100644 index 0000000..2cb9d99 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/holders/live.rs @@ -0,0 +1,34 @@ +// tests/holders/live.rs + +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_holders_smoke_and_or_record() { + if !crate::common::live_or_record_enabled() { + return; + } + + let client = YfClient::builder().build().unwrap(); + // Use a major stock that is guaranteed to have all types of holder data. + let t = Ticker::new(&client, "AAPL"); + + // Call all methods to ensure a complete fixture is recorded if YF_RECORD=1 + let major = t.major_holders().await.unwrap(); + let institutional = t.institutional_holders().await.unwrap(); + let mutual_fund = t.mutual_fund_holders().await.unwrap(); + let insider_trans = t.insider_transactions().await.unwrap(); + let insider_roster = t.insider_roster_holders().await.unwrap(); + let net_purchase = t.net_share_purchase_activity().await.unwrap(); + + // If just running live (not recording), do some basic sanity checks. + if !crate::common::is_recording() { + assert!(!major.is_empty(), "expected major holders"); + assert!(!institutional.is_empty(), "expected institutional holders"); + assert!(!mutual_fund.is_empty(), "expected mutual fund holders"); + assert!(!insider_roster.is_empty(), "expected insider roster"); + assert!(net_purchase.is_some(), "expected net purchase activity"); + // Insider transactions can often be empty, so we don't assert on it. + let _ = insider_trans; + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/holders/offline.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/holders/offline.rs new file mode 100644 index 0000000..b1fd4db --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/holders/offline.rs @@ -0,0 +1,92 @@ +use httpmock::{Method::GET, Mock, MockServer}; +use url::Url; +use yfinance_rs::core::conversions::*; +use yfinance_rs::{Ticker, YfClient}; + +fn fixture(endpoint: &str, symbol: &str) -> String { + crate::common::fixture(endpoint, symbol, "json") +} + +fn setup_holders_mock<'a>(server: &'a MockServer, symbol: &'a str) -> Mock<'a> { + let modules = "institutionOwnership,fundOwnership,majorHoldersBreakdown,insiderTransactions,insiderHolders,netSharePurchaseActivity"; + server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{symbol}")) + .query_param("modules", modules) + .query_param("crumb", "crumb"); + then.status(200) + .header("content-type", "application/json") + .body(fixture("holders_api_institutionOwnership-fundOwnership-majorHoldersBreakdown-insiderTransactions-insiderHolders-netSharePurchaseActivity", symbol)); + }) +} + +#[tokio::test] +async fn offline_all_holders_from_fixture() { + let sym = "AAPL"; + let server = MockServer::start(); + let mock = setup_holders_mock(&server, sym); + + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + ._preauth("cookie", "crumb") + .build() + .unwrap(); + + // Test each method; each will make an independent API call which the mock will serve. + let t = Ticker::new(&client, sym); + + // Major Holders + let major = t.major_holders().await.unwrap(); + assert!(!major.is_empty(), "major holders missing from fixture"); + assert!( + major + .iter() + .any(|h| h.category.contains("Held by All Insider")) + ); + assert!( + major + .iter() + .any(|h| h.category.contains("Held by Institutions")) + ); + + // Institutional Holders + let institutional = t.institutional_holders().await.unwrap(); + assert!( + !institutional.is_empty(), + "institutional holders missing from fixture" + ); + assert!(institutional[0].shares.unwrap_or(0) > 0); + + // Mutual Fund Holders + let mutual_fund = t.mutual_fund_holders().await.unwrap(); + assert!( + !mutual_fund.is_empty(), + "mutual fund holders missing from fixture" + ); + assert!(money_to_f64(mutual_fund[0].value.as_ref().unwrap()) > 0.0); + + // Insider Roster + let insider_roster = t.insider_roster_holders().await.unwrap(); + assert!( + !insider_roster.is_empty(), + "insider roster missing from fixture" + ); + assert!( + insider_roster + .iter() + .any(|h| h.name.to_lowercase().contains("cook")) + ); + + // Net Share Purchase Activity + let net_purchase = t.net_share_purchase_activity().await.unwrap().unwrap(); + assert!(!net_purchase.period.to_string().is_empty()); + assert!(net_purchase.total_insider_shares.unwrap_or(0) > 0); + + // Insider Transactions (can be empty) + let _insider_trans = t.insider_transactions().await.unwrap(); + + // Verify the mock was hit for each of the 6 calls. + mock.assert_calls(6); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/news.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/news.rs new file mode 100644 index 0000000..f42db57 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/news.rs @@ -0,0 +1,6 @@ +mod common; + +#[path = "news/live.rs"] +mod live; +#[path = "news/offline.rs"] +mod offline; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/news/live.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/news/live.rs new file mode 100644 index 0000000..29f47ec --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/news/live.rs @@ -0,0 +1,46 @@ +use yfinance_rs::{NewsTab, Ticker, YfClient}; + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_news_smoke_and_or_record() { + if !crate::common::live_or_record_enabled() { + return; + } + + let client = YfClient::builder().build().unwrap(); + let ticker = Ticker::new(&client, "AAPL"); + + // This call will record `tests/fixtures/news_AAPL.json` if YF_RECORD=1 + let news = ticker.news().await.unwrap(); + + if !crate::common::is_recording() { + // Basic sanity checks when running in live-only mode + assert!( + !news.is_empty(), + "Expected to get at least one news article for AAPL" + ); + let article = &news[0]; + assert!(!article.uuid.is_empty()); + assert!(!article.title.is_empty()); + assert!(article.published_at.timestamp() > 1_000_000_000); // Sanity check timestamp + } +} + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_news_press_releases_for_record() { + if !crate::common::is_recording() { + return; + } + + let client = YfClient::builder().build().unwrap(); + let ticker = Ticker::new(&client, "AAPL"); + + // This will record the fixture `news_pressReleases_AAPL.json` + let _ = ticker + .news_builder() + .tab(NewsTab::PressReleases) + .fetch() + .await + .unwrap(); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/news/offline.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/news/offline.rs new file mode 100644 index 0000000..9c24314 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/news/offline.rs @@ -0,0 +1,96 @@ +use httpmock::{Method::POST, MockServer}; +use serde_json::json; +use url::Url; +use yfinance_rs::{NewsTab, Ticker, YfClient}; + +fn fixture(endpoint: &str, symbol: &str) -> String { + crate::common::fixture(endpoint, symbol, "json") +} + +#[tokio::test] +async fn offline_news_uses_recorded_fixture() { + let server = MockServer::start(); + let sym = "AAPL"; + + let expected_payload = json!({ + "serviceConfig": { + "snippetCount": 10, + "s": [sym] + } + }); + + let mock = server.mock(|when, then| { + when.method(POST) + .path("/xhr/ncp") + .query_param("queryRef", "latestNews") + .query_param("serviceKey", "ncp_fin") + .json_body(expected_payload); + then.status(200) + .header("content-type", "application/json") + // Use the new, specific fixture name + .body(fixture("news_latestNews", sym)); + }); + + let client = YfClient::builder() + .base_news(Url::parse(&server.base_url()).unwrap()) + .build() + .unwrap(); + + let ticker = Ticker::new(&client, sym); + let articles = ticker.news().await.unwrap(); + + mock.assert(); + + // Make the assertion flexible: check that we got some articles, not an exact number. + assert!( + !articles.is_empty(), + "Expected to parse at least one article from the fixture" + ); + + // Perform general checks on the first article + let first = &articles[0]; + assert!(!first.uuid.is_empty()); + assert!(!first.title.is_empty()); + assert!(first.published_at.timestamp() > 0); +} + +#[tokio::test] +async fn offline_news_builder_configures_request() { + let server = MockServer::start(); + let sym = "AAPL"; + + let expected_payload = json!({ + "serviceConfig": { + "snippetCount": 5, + "s": [sym] + } + }); + + let mock = server.mock(|when, then| { + when.method(POST) + .path("/xhr/ncp") + .query_param("queryRef", "pressRelease") // Corresponds to NewsTab::PressReleases + .query_param("serviceKey", "ncp_fin") + .json_body(expected_payload); + then.status(200) + .header("content-type", "application/json") + // Use the new, specific fixture for press releases + .body(fixture("news_pressRelease", sym)); + }); + + let client = YfClient::builder() + .base_news(Url::parse(&server.base_url()).unwrap()) + .build() + .unwrap(); + let ticker = Ticker::new(&client, sym); + + let _articles = ticker + .news_builder() + .count(5) + .tab(NewsTab::PressReleases) + .fetch() + .await + .unwrap(); + + mock.assert(); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/profile.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/profile.rs new file mode 100644 index 0000000..b37c157 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/profile.rs @@ -0,0 +1,12 @@ +mod common; + +#[path = "profile/api_smoke.rs"] +mod profile_api_smoke; +#[path = "profile/fallback_synthetic.rs"] +mod profile_fallback_synth; +#[path = "profile/live.rs"] +mod profile_live; +#[path = "profile/scrape_smoke.rs"] +mod profile_scrape_smoke; +#[path = "profile/scrape_variants_synthetic.rs"] +mod scrape_variants_synth; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/profile/api_smoke.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/profile/api_smoke.rs new file mode 100644 index 0000000..50a947e --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/profile/api_smoke.rs @@ -0,0 +1,94 @@ +use crate::common::setup_server; +use httpmock::Method::GET; +use paft::fundamentals::profile::Profile; +use url::Url; +use yfinance_rs::{ApiPreference, YfClient}; + +#[tokio::test] +async fn profile_api_company_happy() { + let server = setup_server(); + let sym = "AAPL"; + let crumb = "test-crumb"; + + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "assetProfile,quoteType,fundProfile") + .query_param("crumb", crumb); + then.status(200) + .header("content-type", "application/json") + .body(crate::common::fixture( + "profile_api_assetProfile-quoteType-fundProfile", + sym, + "json", + )); + }); + + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + ._api_preference(ApiPreference::ApiOnly) + ._preauth("cookie", crumb) + .build() + .unwrap(); + + let prof = yfinance_rs::profile::load_profile(&client, sym) + .await + .unwrap(); + mock.assert(); + + match prof { + Profile::Company(c) => { + assert_eq!(c.name, "Apple Inc."); + assert_eq!(c.sector.as_deref(), Some("Technology")); + assert_eq!(c.industry.as_deref(), Some("Consumer Electronics")); + assert_eq!(c.website.as_deref(), Some("https://www.apple.com")); + } + Profile::Fund(_) => panic!("expected Company"), + } +} + +#[tokio::test] +async fn profile_api_fund_happy() { + let server = setup_server(); + let sym = "QQQ"; + let crumb = "test-crumb"; + + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "assetProfile,quoteType,fundProfile") + .query_param("crumb", crumb); + then.status(200) + .header("content-type", "application/json") + .body(crate::common::fixture( + "profile_api_assetProfile-quoteType-fundProfile", + sym, + "json", + )); + }); + + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + ._api_preference(ApiPreference::ApiOnly) + ._preauth("cookie", crumb) + .build() + .unwrap(); + + let prof = yfinance_rs::profile::load_profile(&client, sym) + .await + .unwrap(); + mock.assert(); + + match prof { + Profile::Fund(f) => { + assert_eq!(f.name, "Invesco QQQ Trust"); + assert_eq!(f.family.as_deref(), Some("Invesco")); + assert_eq!(f.kind.to_string(), "ETF"); + } + Profile::Company(_) => panic!("expected Fund"), + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/profile/fallback_synthetic.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/profile/fallback_synthetic.rs new file mode 100644 index 0000000..0bb542a --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/profile/fallback_synthetic.rs @@ -0,0 +1,44 @@ +use crate::common; +use httpmock::Method::GET; +use paft::fundamentals::profile::Profile; +use url::Url; +use yfinance_rs::{ApiPreference, YfClient}; + +#[tokio::test] +async fn api_then_scrape_fallback_on_other_error() { + let server = common::setup_server(); + let sym = "AAPL"; + + // API returns a generic error (not "Invalid Crumb") + let api_err = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")); + then.status(200) + .header("content-type", "application/json") + .body(r#"{"quoteSummary":{"result":null,"error":{"description":"Something broke"}}}"#); + }); + + // Scrape path gets used instead + let scrape = common::mock_profile_scrape(&server, sym); + + let client = YfClient::builder() + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + .base_quote(Url::parse(&format!("{}/quote/", server.base_url())).unwrap()) + ._api_preference(ApiPreference::ApiThenScrape) + ._preauth("cookie", "crumb") + .build() + .unwrap(); + + let p = yfinance_rs::profile::load_profile(&client, sym) + .await + .unwrap(); + api_err.assert(); + scrape.assert(); + + match p { + Profile::Company(c) => assert_eq!(c.name, "Apple Inc."), + Profile::Fund(_) => panic!("expected Company"), + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/profile/live.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/profile/live.rs new file mode 100644 index 0000000..9821365 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/profile/live.rs @@ -0,0 +1,57 @@ +use paft::fundamentals::profile::Profile; + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_profile_company() { + if !(std::env::var("YF_LIVE").ok().as_deref() == Some("1") + || std::env::var("YF_RECORD").ok().as_deref() == Some("1")) + { + return; + } + let client = yfinance_rs::YfClient::builder().build().unwrap(); + let prof = yfinance_rs::profile::load_profile(&client, "AAPL") + .await + .unwrap(); + match prof { + Profile::Company(c) => { + assert!(!c.name.is_empty()); + } + Profile::Fund(_) => panic!("expected company"), + } +} + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_profile_fund_for_record() { + if std::env::var("YF_RECORD").ok().as_deref() != Some("1") { + return; + } + let client = yfinance_rs::YfClient::builder().build().unwrap(); + let _ = yfinance_rs::profile::load_profile(&client, "QQQ").await; +} + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_profile_fund_scrape_for_record() { + if std::env::var("YF_RECORD").ok().as_deref() != Some("1") { + return; + } + let client = yfinance_rs::YfClient::builder() + ._api_preference(yfinance_rs::ApiPreference::ScrapeOnly) + .build() + .unwrap(); + let _ = yfinance_rs::profile::load_profile(&client, "AAPL").await; +} + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_profile_company_scrape_for_record() { + if std::env::var("YF_RECORD").ok().as_deref() != Some("1") { + return; + } + let client = yfinance_rs::YfClient::builder() + ._api_preference(yfinance_rs::ApiPreference::ScrapeOnly) + .build() + .unwrap(); + let _ = yfinance_rs::profile::load_profile(&client, "QQQ").await; +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/profile/scrape_smoke.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/profile/scrape_smoke.rs new file mode 100644 index 0000000..f24574c --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/profile/scrape_smoke.rs @@ -0,0 +1,60 @@ +use crate::common::{mock_profile_scrape, setup_server}; +use paft::fundamentals::profile::Profile; +use url::Url; +use yfinance_rs::{ApiPreference, YfClient}; + +#[tokio::test] +async fn profile_scrape_company_happy() { + let server = setup_server(); + let sym = "AAPL"; + let mock = mock_profile_scrape(&server, sym); + + let client = YfClient::builder() + .base_quote(Url::parse(&format!("{}/quote/", server.base_url())).unwrap()) + ._api_preference(ApiPreference::ScrapeOnly) + .build() + .unwrap(); + + let prof = yfinance_rs::profile::load_profile(&client, sym) + .await + .unwrap(); + mock.assert(); + + match prof { + Profile::Company(c) => { + assert_eq!(c.name, "Apple Inc."); + assert_eq!(c.sector.as_deref(), Some("Technology")); + assert_eq!(c.industry.as_deref(), Some("Consumer Electronics")); + assert_eq!(c.website.as_deref(), Some("https://www.apple.com")); + assert!(c.address.is_some()); + } + Profile::Fund(_) => panic!("expected Company"), + } +} + +#[tokio::test] +async fn profile_scrape_fund_happy() { + let server = setup_server(); + let sym = "QQQ"; + let mock = mock_profile_scrape(&server, sym); + + let client = YfClient::builder() + .base_quote(Url::parse(&format!("{}/quote/", server.base_url())).unwrap()) + ._api_preference(ApiPreference::ScrapeOnly) + .build() + .unwrap(); + + let prof = yfinance_rs::profile::load_profile(&client, sym) + .await + .unwrap(); + mock.assert(); + + match prof { + Profile::Fund(f) => { + assert_eq!(f.name, "Invesco QQQ Trust"); + assert_eq!(f.family.as_deref(), Some("Invesco")); + assert_eq!(f.kind.to_string(), "ETF"); + } + Profile::Company(_) => panic!("expected Fund"), + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/profile/scrape_variants_synthetic.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/profile/scrape_variants_synthetic.rs new file mode 100644 index 0000000..335f385 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/profile/scrape_variants_synthetic.rs @@ -0,0 +1,85 @@ +use httpmock::{Method::GET, MockServer}; +use paft::fundamentals::profile::Profile; +use url::Url; +use yfinance_rs::{ApiPreference, YfClient}; + +fn svelte_html(payload: &str) -> String { + format!( + r#" + + +"# + ) +} + +#[tokio::test] +async fn scrape_sveltekit_equity() { + let server = MockServer::start(); + let sym = "DEMO"; + + // data-sveltekit-fetched array → nodes[].data.quoteSummary.result[0] + let payload = r#"[{"nodes":[{"data":{"quoteSummary":{"result":[{ + "quoteType":{"quoteType":"EQUITY","longName":"Demo Co"}, + "summaryProfile":{"sector":"Tech","industry":"Gadgets","website":"https://demo.invalid"} + }]}}}]}]"#; + + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/quote/{sym}")) + .query_param("p", sym); + then.status(200) + .header("content-type", "text/html") + .body(svelte_html(payload)); + }); + + let client = YfClient::builder() + .base_quote(Url::parse(&format!("{}/quote/", server.base_url())).unwrap()) + ._api_preference(ApiPreference::ScrapeOnly) + .build() + .unwrap(); + + let prof = yfinance_rs::profile::load_profile(&client, sym) + .await + .unwrap(); + mock.assert(); + + match prof { + Profile::Company(c) => { + assert_eq!(c.name, "Demo Co"); + assert_eq!(c.sector.as_deref(), Some("Tech")); + } + Profile::Fund(_) => panic!("expected Company"), + } +} + +#[tokio::test] +async fn scrape_infers_equity_when_quote_type_missing() { + let server = MockServer::start(); + let sym = "INFER"; + + // No quoteType; presence of summaryProfile should infer EQUITY + let payload = r#"[{"nodes":[{"data":{"quoteSummary":{"result":[{ + "summaryProfile":{"sector":"Tech"} + }]}}}]}]"#; + + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/quote/{sym}")) + .query_param("p", sym); + then.status(200) + .header("content-type", "text/html") + .body(svelte_html(payload)); + }); + + let client = YfClient::builder() + .base_quote(Url::parse(&format!("{}/quote/", server.base_url())).unwrap()) + ._api_preference(ApiPreference::ScrapeOnly) + .build() + .unwrap(); + + let prof = yfinance_rs::profile::load_profile(&client, sym) + .await + .unwrap(); + mock.assert(); + assert!(matches!(prof, Profile::Company(_))); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/quotes.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/quotes.rs new file mode 100644 index 0000000..29dc292 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/quotes.rs @@ -0,0 +1,9 @@ +mod common; + +#[path = "quotes/offline.rs"] +mod quotes_offline; +#[path = "quotes/retry_synthetic.rs"] +mod quotes_retry_synth; + +#[path = "quotes/live.rs"] +mod live; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/quotes/live.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/quotes/live.rs new file mode 100644 index 0000000..458b342 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/quotes/live.rs @@ -0,0 +1,21 @@ +/// Live recorder to create `tests/fixtures/quote_v7_MULTI.json` via `internal::net::get_text`. +/// Run it explicitly with recording turned on: +/// `YF_RECORD=1` cargo test --test quotes -- --ignored `record_multi_quotes_live` +use url::Url; + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn record_multi_quotes_live() { + let url = Url::parse("https://query1.finance.yahoo.com/v7/finance/quote").unwrap(); + let client = yfinance_rs::YfClient::builder() + .base_quote_v7(url) + .build() + .unwrap(); + + // Use the real base URL; this will record to quote_v7_MULTI.json + let _ = yfinance_rs::QuotesBuilder::new(client) + .symbols(["AAPL", "MSFT"]) + .fetch() + .await + .unwrap(); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/quotes/offline.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/quotes/offline.rs new file mode 100644 index 0000000..836cf3d --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/quotes/offline.rs @@ -0,0 +1,36 @@ +use crate::common::{mock_quote_v7_multi, setup_server}; +use std::path::Path; +use url::Url; + +#[tokio::test] +async fn offline_multi_quotes_uses_recorded_fixture() { + // Skip if the recorded fixture isn't present; you must run the live recorder first. + let fixture = Path::new("tests/fixtures/quote_v7_MULTI.json"); + if !fixture.exists() { + eprintln!( + "skipping offline test: missing {}. run the live recorder with YF_RECORD=1 first.", + fixture.display() + ); + return; + } + + let server = setup_server(); + let _mock = mock_quote_v7_multi(&server, "AAPL,MSFT"); + + let base = Url::parse(&format!("{}/v7/finance/quote", server.base_url())).unwrap(); + let client = yfinance_rs::YfClient::builder() + .base_quote_v7(base) + .build() + .unwrap(); + + let quotes = yfinance_rs::QuotesBuilder::new(client) + .symbols(["AAPL", "MSFT"]) + .fetch() + .await + .unwrap(); + + // Sanity against the recorded fixture + let syms: Vec<_> = quotes.iter().map(|q| q.symbol.as_str()).collect(); + assert!(syms.contains(&"AAPL")); + assert!(syms.contains(&"MSFT")); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/quotes/retry_synthetic.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/quotes/retry_synthetic.rs new file mode 100644 index 0000000..49e1fd9 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/quotes/retry_synthetic.rs @@ -0,0 +1,95 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use paft::money::{Currency, IsoCurrency}; +use url::Url; +use yfinance_rs::core::conversions::*; + +#[tokio::test] +async fn batch_quotes_401_then_retry_with_crumb_succeeds() { + let server = MockServer::start(); + + // Respond OK only when the crumb is present (define this first so the + // second request matches here; the fallback 401 is defined below). + let ok = server.mock(|when, then| { + when.method(GET) + .path("/v7/finance/quote") + .query_param("crumb", "crumb-value"); + then.status(200) + .header("content-type", "application/json") + .body(r#"{ + "quoteResponse": { + "result": [ + { "symbol":"AAPL", "regularMarketPrice": 123.0, "currency":"USD", "fullExchangeName":"NasdaqGS" }, + { "symbol":"MSFT", "regularMarketPrice": 456.0, "currency":"USD", "exchange":"NasdaqGS" } + ], + "error": null + } + }"#); + }); + + // First call returns 401 (no crumb) + let first = server.mock(|when, then| { + when.method(GET).path("/v7/finance/quote"); + then.status(401).body("unauthorized"); + }); + + // Cookie + crumb endpoints + let cookie = server.mock(|when, then| { + when.method(GET).path("/consent"); + then.status(200).header( + "set-cookie", + "A=B; Max-Age=315360000; Domain=.yahoo.com; Path=/; Secure; SameSite=None", + ); + }); + let crumb = server.mock(|when, then| { + when.method(GET).path("/v1/test/getcrumb"); + then.status(200).body("crumb-value"); + }); + + let base = Url::parse(&format!("{}/v7/finance/quote", server.base_url())).unwrap(); + + let client = yfinance_rs::YfClient::builder() + .cookie_url(Url::parse(&format!("{}/consent", server.base_url())).unwrap()) + .crumb_url(Url::parse(&format!("{}/v1/test/getcrumb", server.base_url())).unwrap()) + .base_quote_v7(base) + .build() + .unwrap(); + + let quotes = yfinance_rs::QuotesBuilder::new(client) + .symbols(["AAPL", "MSFT"]) + .fetch() + .await + .unwrap(); + + // Verify mocks were actually hit + first.assert(); + cookie.assert(); + crumb.assert(); + ok.assert(); + + assert_eq!(quotes.len(), 2); + let aapl = quotes.iter().find(|q| q.symbol.as_str() == "AAPL").unwrap(); + let msft = quotes.iter().find(|q| q.symbol.as_str() == "MSFT").unwrap(); + assert_eq!( + aapl.price, + Some(f64_to_money_with_currency( + 123.0, + Currency::Iso(IsoCurrency::USD) + )) + ); + assert_eq!( + msft.price, + Some(f64_to_money_with_currency( + 456.0, + Currency::Iso(IsoCurrency::USD) + )) + ); + assert_eq!( + aapl.exchange.as_ref().map(std::string::ToString::to_string), + Some("NASDAQ".to_string()) + ); + assert_eq!( + msft.exchange.as_ref().map(std::string::ToString::to_string), + Some("NASDAQ".to_string()) + ); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/quotes_status_mapping.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/quotes_status_mapping.rs new file mode 100644 index 0000000..e953cde --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/quotes_status_mapping.rs @@ -0,0 +1,73 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; + +#[tokio::test] +async fn quotes_v7_404_maps_to_not_found() { + let server = MockServer::start(); + + // Prepare a v7 endpoint that returns 404 + let mock = server.mock(|when, then| { + when.method(GET) + .path("/v7/finance/quote") + .query_param("symbols", "MISSING"); + then.status(404) + .header("content-type", "application/json") + .body("{}"); + }); + + let client = yfinance_rs::YfClient::builder() + .base_quote_v7(Url::parse(&format!("{}/v7/finance/quote", server.base_url())).unwrap()) + .retry_enabled(false) + .build() + .unwrap(); + + let symbols = ["MISSING".to_string()]; + let err = yfinance_rs::quote::quotes(&client, symbols.iter().cloned()) + .await + .unwrap_err(); + + mock.assert(); + + match err { + yfinance_rs::YfError::NotFound { url } => { + assert!(url.contains("/v7/finance/quote")); + } + other => panic!("expected NotFound, got {other:?}"), + } +} + +#[tokio::test] +async fn quotes_v7_429_maps_to_rate_limited() { + let server = MockServer::start(); + + // Prepare a v7 endpoint that returns 429 + let mock = server.mock(|when, then| { + when.method(GET) + .path("/v7/finance/quote") + .query_param("symbols", "AAPL"); + then.status(429) + .header("content-type", "application/json") + .body("{}"); + }); + + let client = yfinance_rs::YfClient::builder() + .base_quote_v7(Url::parse(&format!("{}/v7/finance/quote", server.base_url())).unwrap()) + .retry_enabled(false) + .build() + .unwrap(); + + let symbols = ["AAPL".to_string()]; + let err = yfinance_rs::quote::quotes(&client, symbols.iter().cloned()) + .await + .unwrap_err(); + + mock.assert(); + + match err { + yfinance_rs::YfError::RateLimited { url } => { + assert!(url.contains("/v7/finance/quote")); + } + other => panic!("expected RateLimited, got {other:?}"), + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/search.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/search.rs new file mode 100644 index 0000000..ad1dc33 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/search.rs @@ -0,0 +1,6 @@ +mod common; + +#[path = "search/live.rs"] +mod search_live; +#[path = "search/offline.rs"] +mod search_offline; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/search/live.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/search/live.rs new file mode 100644 index 0000000..08e2dfd --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/search/live.rs @@ -0,0 +1,23 @@ +use yfinance_rs::{SearchBuilder, YfClient}; + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_search_smoke_and_or_record() { + if !crate::common::live_or_record_enabled() { + return; + } + + let client = YfClient::builder().build().unwrap(); + + // Keep the query simple/stable so the recorded fixture is reusable. + let query = "apple"; + + let resp = SearchBuilder::new(&client, query).fetch().await.unwrap(); + + if !crate::common::is_recording() { + assert!(!resp.results.is_empty()); + // Heuristic: expect to see AAPL when searching "apple" + let has_aapl = resp.results.iter().any(|q| q.symbol.as_str() == "AAPL"); + assert!(has_aapl, "expected AAPL among search results for 'apple'"); + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/search/offline.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/search/offline.rs new file mode 100644 index 0000000..e9386cc --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/search/offline.rs @@ -0,0 +1,41 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::{SearchBuilder, YfClient}; + +fn fixture(endpoint: &str, key: &str) -> String { + crate::common::fixture(endpoint, key, "json") +} + +#[tokio::test] +async fn offline_search_uses_recorded_fixture() { + // Query we'll use for fixture key + let query = "apple"; + let server = MockServer::start(); + + // Mock Yahoo /v1/finance/search with expected params + let mock = server.mock(|when, then| { + when.method(GET) + .path("/v1/finance/search") + .query_param("q", query) + .query_param("quotesCount", "10") + .query_param("newsCount", "0") + .query_param("listsCount", "0"); + then.status(200) + .header("content-type", "application/json") + .body(fixture("search_v1", query)); + }); + + let client = YfClient::builder().build().unwrap(); + + let resp = SearchBuilder::new(&client, query) + .search_base(Url::parse(&format!("{}/v1/finance/search", server.base_url())).unwrap()) + .fetch() + .await + .unwrap(); + + mock.assert(); + // At least one result expected (record with YF_RECORD=1 first) + assert!(!resp.results.is_empty(), "record with YF_RECORD=1 first"); + assert!(resp.results.iter().any(|q| q.symbol.as_str() == "AAPL")); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/stream.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/stream.rs new file mode 100644 index 0000000..4670e43 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/stream.rs @@ -0,0 +1,8 @@ +mod common; + +#[path = "stream/live.rs"] +mod stream_live; +#[path = "stream/offline.rs"] +mod stream_offline; +#[path = "stream/websocket_decoder.rs"] +mod websocket_decoder; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/stream/live.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/stream/live.rs new file mode 100644 index 0000000..6ea14fd --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/stream/live.rs @@ -0,0 +1,36 @@ +use tokio::time::{Duration, timeout}; +use yfinance_rs::StreamMethod; + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_stream_smoke() { + if !crate::common::live_or_record_enabled() { + return; + } + + let client = yfinance_rs::YfClient::builder().build().unwrap(); + + let builder = yfinance_rs::StreamBuilder::new(&client) + .symbols(["BTC-USD"]) // Switched to a 24/7 symbol + .method(StreamMethod::Websocket); + + let (handle, mut rx) = builder.start().unwrap(); + + let got = timeout(Duration::from_secs(90), rx.recv()).await; + + handle.abort(); + + let update = got + .expect("no live update within timeout") + .expect("stream closed without emitting"); + + // Updated assertion for the new symbol + assert_eq!(update.symbol.as_str(), "BTC-USD"); + assert!( + update + .price + .as_ref() + .map_or(0.0, yfinance_rs::core::conversions::money_to_f64) + > 0.0 + ); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/stream/offline.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/stream/offline.rs new file mode 100644 index 0000000..2e09904 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/stream/offline.rs @@ -0,0 +1,186 @@ +use tokio::time::{Duration, timeout}; +use url::Url; +use yfinance_rs::StreamMethod; +use yfinance_rs::core::client::CacheMode; + +#[tokio::test] +async fn stream_websocket_fallback_to_polling_offline() { + let server = crate::common::setup_server(); + + let mock = server.mock(|when, then| { + when.method(httpmock::Method::GET) + .path("/v7/finance/quote") + .query_param("symbols", "AAPL"); + then.status(200) + .header("content-type", "application/json") + .body(crate::common::fixture("quote_v7", "AAPL", "json")); + }); + + let client = yfinance_rs::YfClient::builder() + .base_quote_v7(Url::parse(&format!("{}/v7/finance/quote", server.base_url())).unwrap()) + .base_stream(Url::parse("wss://invalid-url-for-testing.invalid/").unwrap()) + .build() + .unwrap(); + + let builder = yfinance_rs::StreamBuilder::new(&client) + .symbols(["AAPL"]) + .method(StreamMethod::WebsocketWithFallback) + .interval(Duration::from_millis(40)); + + let (handle, mut rx) = builder.start().unwrap(); + + let got = timeout(Duration::from_secs(3), rx.recv()).await; + handle.abort(); + + mock.assert(); + + let update = got + .expect("timed out waiting for cached stream update") + .expect("stream closed without emitting an update"); + + assert_eq!(update.symbol.as_str(), "AAPL"); + assert!( + update + .price + .as_ref() + .map_or(0.0, yfinance_rs::core::conversions::money_to_f64) + > 0.0, + "cached price should be > 0" + ); +} + +#[tokio::test] +async fn stream_polling_explicitly_offline() { + let server = crate::common::setup_server(); + + let mock = server.mock(|when, then| { + when.method(httpmock::Method::GET) + .path("/v7/finance/quote") + .query_param("symbols", "MSFT"); + then.status(200) + .header("content-type", "application/json") + .body(crate::common::fixture("quote_v7", "MSFT", "json")); + }); + + let client = yfinance_rs::YfClient::builder() + .base_quote_v7(Url::parse(&format!("{}/v7/finance/quote", server.base_url())).unwrap()) + .build() + .unwrap(); + + let builder = yfinance_rs::StreamBuilder::new(&client) + .symbols(["MSFT"]) + .method(StreamMethod::Polling) + .interval(Duration::from_millis(50)); + + let (handle, mut rx) = builder.start().unwrap(); + let got = timeout(Duration::from_secs(3), rx.recv()).await; + handle.abort(); + mock.assert(); + + assert!(got.is_ok()); +} + +#[tokio::test] +async fn stream_polling_emits_on_volume_only_change_with_diff_only() { + let server = crate::common::setup_server(); + + // First response: price P, volume V1 + let body1 = r#"{ + "quoteResponse": { + "result": [ + { + "symbol": "MSFT", + "regularMarketPrice": 420.00, + "regularMarketPreviousClose": 420.00, + "regularMarketVolume": 1000, + "currency": "USD" + } + ], + "error": null + } + }"#; + + // Second response: same price P, higher volume V2 + let body2 = r#"{ + "quoteResponse": { + "result": [ + { + "symbol": "MSFT", + "regularMarketPrice": 420.00, + "regularMarketPreviousClose": 420.00, + "regularMarketVolume": 1500, + "currency": "USD" + } + ], + "error": null + } + }"#; + + // Set up two sequential mocks. The first is limited to a single call so the second one is used next. + let mut m1 = server.mock(|when, then| { + when.method(httpmock::Method::GET) + .path("/v7/finance/quote") + .query_param("symbols", "MSFT"); + then.status(200) + .header("content-type", "application/json") + .body(body1); + }); + + let client = yfinance_rs::YfClient::builder() + .base_quote_v7(Url::parse(&format!("{}/v7/finance/quote", server.base_url())).unwrap()) + .build() + .unwrap(); + + // diff_only defaults to true; ensure we bypass cache so each poll hits the server + let builder = yfinance_rs::StreamBuilder::new(&client) + .symbols(["MSFT"]) + .method(StreamMethod::Polling) + .interval(Duration::from_millis(100)) + .cache_mode(CacheMode::Bypass); + + let (handle, mut rx) = builder.start().unwrap(); + + // First tick (price change from None -> P) should emit + let first = timeout(Duration::from_secs(3), rx.recv()).await; + // After first emission, switch the mock to return a higher volume + m1.delete(); + let _m2 = server.mock(|when, then| { + when.method(httpmock::Method::GET) + .path("/v7/finance/quote") + .query_param("symbols", "MSFT"); + then.status(200) + .header("content-type", "application/json") + .body(body2); + }); + + // Second tick: price unchanged, volume increased -> must emit + let second = timeout(Duration::from_secs(3), rx.recv()).await; + + handle.abort(); + + let first = first + .expect("timed out waiting for first update") + .expect("stream closed before first update"); + let second = second + .expect("timed out waiting for second update") + .expect("stream closed before second update"); + + // Price should be unchanged between ticks in this scenario + let first_price = first + .price + .as_ref() + .map_or(f64::NAN, yfinance_rs::core::conversions::money_to_f64); + let second_price = second + .price + .as_ref() + .map_or(f64::NAN, yfinance_rs::core::conversions::money_to_f64); + assert!( + (first_price - second_price).abs() < 1e-9, + "price should be unchanged when only volume increases" + ); + + assert!( + second.volume.unwrap_or(0) > 0, + "second update should carry positive volume delta when price is unchanged" + ); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/stream/websocket_decoder.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/stream/websocket_decoder.rs new file mode 100644 index 0000000..286747f --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/stream/websocket_decoder.rs @@ -0,0 +1,22 @@ +use super::common; + +#[test] +fn decode_real_websocket_message() { + let base64_msg = common::fixture("stream_ws", "MULTI", "b64"); + let update = yfinance_rs::stream::decode_and_map_message(&base64_msg).unwrap(); + + // Generic assertions, as the symbol/price will change with each recording + assert!(!update.symbol.is_empty(), "symbol should not be empty"); + assert!(update.price.is_some(), "price should be present"); + assert!( + update + .price + .as_ref() + .map_or(0.0, yfinance_rs::core::conversions::money_to_f64) + > 0.0, + "price should be positive" + ); + + // Decoder is stateless: volume must be None + assert!(update.volume.is_none(), "decoder should not set volume"); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker.rs new file mode 100644 index 0000000..6d234d2 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker.rs @@ -0,0 +1,30 @@ +mod common; + +#[path = "ticker/actions.rs"] +mod actions; +#[path = "ticker/capital_gains.rs"] +mod capital_gains; +#[path = "ticker/fast_info.rs"] +mod fast_info; +#[path = "ticker/history_convenience.rs"] +mod history_convenience; +#[path = "ticker/info_live.rs"] +mod info_live; +#[path = "ticker/info_offline.rs"] +mod info_offline; +#[path = "ticker/isin_live.rs"] +mod isin_live; +#[path = "ticker/isin_offline.rs"] +mod isin_offline; +#[path = "ticker/live.rs"] +mod live; +#[path = "ticker/offline.rs"] +mod offline; +#[path = "ticker/options.rs"] +mod options; +#[path = "ticker/options_expiry_from_url_fallback.rs"] +mod options_expiry_from_url_fallback; +#[path = "ticker/quote.rs"] +mod quote; +#[path = "ticker/shares.rs"] +mod shares; diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/actions.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/actions.rs new file mode 100644 index 0000000..56aba1a --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/actions.rs @@ -0,0 +1,69 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::core::Range; +use yfinance_rs::{Ticker, YfClient}; + +fn body_with_actions() -> String { + r#"{ + "chart":{ + "result":[ + { + "timestamp":[1000,2000,3000], + "indicators":{ + "quote":[{ + "open":[100.0,100.0,100.0], + "high":[101.0,101.0,101.0], + "low":[99.0,99.0,99.0], + "close":[100.0,100.0,100.0], + "volume":[10,10,10] + }], + "adjclose":[{"adjclose":[50.0,100.0,99.0]}] + }, + "events":{ + "splits":{ + "2000":{"date":2000,"numerator":2,"denominator":1} + }, + "dividends":{ + "3000":{"date":3000,"amount":1.0} + } + } + } + ], + "error":null + } + }"# + .to_string() +} + +#[tokio::test] +async fn ticker_actions_dividends_splits() { + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + when.method(GET) + .path("/v8/finance/chart/TEST") + .query_param("range", "max") + .query_param("interval", "1d") + .query_param("events", "div|split|capitalGains"); + then.status(200) + .header("content-type", "application/json") + .body(body_with_actions()); + }); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let t = Ticker::new(&client, "TEST"); + + let acts = t.actions(None).await.unwrap(); + mock.assert(); + + assert_eq!(acts.len(), 2); + let divs = t.dividends(Some(Range::Max)).await.unwrap(); + assert_eq!(divs, vec![(3000, 1.0)]); + let splits = t.splits(Some(Range::Max)).await.unwrap(); + assert_eq!(splits, vec![(2000, 2, 1)]); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/capital_gains.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/capital_gains.rs new file mode 100644 index 0000000..74bf672 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/capital_gains.rs @@ -0,0 +1,36 @@ +use httpmock::{Method::GET, MockServer}; +use url::Url; +use yfinance_rs::core::Range; +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::test] +async fn offline_capital_gains_from_history() { + let server = MockServer::start(); + let sym = "VFINX"; + + let mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v8/finance/chart/{sym}")) + .query_param("range", "max") + .query_param("interval", "1d") + .query_param("events", "div|split|capitalGains"); + then.status(200) + .header("content-type", "application/json") + .body(crate::common::fixture("history_chart", sym, "json")); + }); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let t = Ticker::new(&client, sym); + let gains = t.capital_gains(Some(Range::Max)).await.unwrap(); + + mock.assert(); + assert!( + !gains.is_empty(), + "capital gains missing from fixture for VFINX. Did you run `just test-record ticker`?" + ); + assert!(gains[0].1 > 0.0, "gain amount should be positive"); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/fast_info.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/fast_info.rs new file mode 100644 index 0000000..4207dbc --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/fast_info.rs @@ -0,0 +1,50 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::test] +async fn fast_info_uses_previous_close_when_price_missing() { + let server = MockServer::start(); + + let body = r#"{ + "quoteResponse": { + "result": [{ + "symbol": "AAPL", + "regularMarketPrice": null, + "regularMarketPreviousClose": 199.5, + "currency": "USD", + "fullExchangeName": "NasdaqGS" + }], + "error": null + } + }"#; + + let mock = server.mock(|when, then| { + when.method(GET) + .path("/v7/finance/quote") + .query_param("symbols", "AAPL"); + then.status(200) + .header("content-type", "application/json") + .body(body); + }); + + let client = YfClient::builder() + .base_quote_v7(Url::parse(&format!("{}/v7/finance/quote", server.base_url())).unwrap()) + .build() + .unwrap(); + let t = Ticker::new(&client, "AAPL"); + + let fi = t.fast_info().await.unwrap(); + mock.assert(); + + assert_eq!(fi.symbol.as_str(), "AAPL"); + assert!( + (yfinance_rs::core::conversions::money_to_f64(&fi.previous_close.unwrap()) - 199.5).abs() + < 1e-9 + ); + assert_eq!( + fi.exchange.map(|e| e.to_string()).as_deref(), + Some("NASDAQ") + ); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/history_convenience.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/history_convenience.rs new file mode 100644 index 0000000..aba0e4a --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/history_convenience.rs @@ -0,0 +1,54 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::YfClient; +use yfinance_rs::core::conversions::*; +use yfinance_rs::core::{Interval, Range}; + +fn minimal_ok_body() -> String { + r#"{ + "chart": { + "result": [{ + "meta": {"timezone":"America/New_York","gmtoffset":-14400}, + "timestamp": [1000], + "indicators": { + "quote":[{ "open":[100.0], "high":[101.0], "low":[99.0], "close":[100.5], "volume":[1000] }], + "adjclose":[{ "adjclose":[100.5] }] + } + }], + "error": null + } + }"#.to_string() +} + +#[tokio::test] +async fn ticker_history_convenience_builds_expected_query() { + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + when.method(GET) + .path("/v8/finance/chart/AAPL") + .query_param("range", "ytd") + .query_param("interval", "1d") + .query_param("includePrePost", "false") + .query_param("events", "div|split|capitalGains"); + then.status(200) + .header("content-type", "application/json") + .body(minimal_ok_body()); + }); + + let client = YfClient::builder() + .base_chart(Url::parse(&format!("{}/v8/finance/chart/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let ticker = yfinance_rs::Ticker::new(&client, "AAPL"); + let bars = ticker + .history(Some(Range::Ytd), Some(Interval::D1), false) + .await + .unwrap(); + + mock.assert(); + assert_eq!(bars.len(), 1); + assert!((money_to_f64(&bars[0].close) - 100.5).abs() < 1e-9); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/info_live.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/info_live.rs new file mode 100644 index 0000000..2585256 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/info_live.rs @@ -0,0 +1,25 @@ +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_info_smoke_and_or_record() { + if !crate::common::live_or_record_enabled() { + return; + } + + let client = YfClient::builder().build().unwrap(); + let ticker = Ticker::new(&client, "MSFT"); + + // This will trigger all the API calls needed for .info() + // and record fixtures if YF_RECORD=1 is set. + // Note: The concurrent nature of .info() means the `analysis_api_MSFT.json` + // fixture will contain the modules from whichever analysis call finishes last. + // The offline test is designed to handle this. + let info = ticker.info().await.unwrap(); + + if !crate::common::is_recording() { + // Basic sanity checks for live mode + assert_eq!(info.symbol.as_str(), "MSFT"); + assert!(info.last.is_some(), "Expected a market price for MSFT"); + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/info_offline.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/info_offline.rs new file mode 100644 index 0000000..da30bc7 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/info_offline.rs @@ -0,0 +1,100 @@ +use httpmock::{Method::GET, MockServer}; +use url::Url; +use yfinance_rs::{ApiPreference, Ticker, YfClient}; + +#[tokio::test] +async fn offline_info_uses_recorded_fixtures() { + let server = MockServer::start(); + let sym = "MSFT"; + let crumb = "test-crumb"; + + // 1. Mock for quote::fetch_quote -> uses `quote_v7_MSFT.json` + let quote_mock = server.mock(|when, then| { + when.method(GET) + .path("/v7/finance/quote") + .query_param("symbols", sym); + then.status(200) + .header("content-type", "application/json") + .body(crate::common::fixture("quote_v7", sym, "json")); + }); + + // 2. Mock for Profile::load -> uses `profile_api_assetProfile-quoteType-fundProfile_MSFT.json` + let profile_mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "assetProfile,quoteType,fundProfile"); + then.status(200) + .header("content-type", "application/json") + .body(crate::common::fixture( + "profile_api_assetProfile-quoteType-fundProfile", + sym, + "json", + )); + }); + + // 3. Mock for price_target -> uses `analysis_api_financialData_MSFT.json` + let price_target_mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "financialData"); + then.status(200) + .header("content-type", "application/json") + .body(crate::common::fixture( + "analysis_api_financialData", + sym, + "json", + )); + }); + + // 4. Mock for recommendations_summary -> uses `analysis_api_recommendationTrend-financialData_MSFT.json` + let rec_summary_mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "recommendationTrend,financialData"); + then.status(200) + .header("content-type", "application/json") + .body(crate::common::fixture( + "analysis_api_recommendationTrend-financialData", + sym, + "json", + )); + }); + + // 5. Mock for esg_scores -> uses `esg_api_esgScores_MSFT.json` + let esg_mock = server.mock(|when, then| { + when.method(GET) + .path(format!("/v10/finance/quoteSummary/{sym}")) + .query_param("modules", "esgScores"); + then.status(200) + .header("content-type", "application/json") + .body(crate::common::fixture("esg_api_esgScores", sym, "json")); + }); + + let client = YfClient::builder() + .base_quote_v7(Url::parse(&format!("{}/v7/finance/quote", server.base_url())).unwrap()) + .base_quote_api( + Url::parse(&format!("{}/v10/finance/quoteSummary/", server.base_url())).unwrap(), + ) + ._api_preference(ApiPreference::ApiOnly) + ._preauth("cookie", crumb) + .build() + .unwrap(); + + let ticker = Ticker::new(&client, sym); + let info = ticker.info().await.unwrap(); + + // Assert all mocks were hit + quote_mock.assert(); + assert_eq!( + profile_mock.calls(), + 2, + "profile fetch should occur twice (currency + info)" + ); + price_target_mock.assert(); + rec_summary_mock.assert(); + esg_mock.assert(); + + // Verify data aggregation with more robust checks. Run recorders if these fail. + assert_eq!(info.symbol.as_str(), "MSFT"); + assert!(info.last.is_some(), "Price missing from quote fixture."); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/isin_live.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/isin_live.rs new file mode 100644 index 0000000..5c993f5 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/isin_live.rs @@ -0,0 +1,33 @@ +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_isin_smoke_and_or_record() { + if !crate::common::live_or_record_enabled() { + return; + } + + let client = YfClient::builder().build().unwrap(); + + // Test a company. This will trigger a Yahoo quote request and a Business Insider request. + // Both responses will be recorded as fixtures if YF_RECORD=1. + let ticker_company = Ticker::new(&client, "AAPL"); + let isin_company = ticker_company.isin().await.unwrap(); + + // Test a fund. + let ticker_fund = Ticker::new(&client, "QQQ"); + let isin_fund = ticker_fund.isin().await.unwrap(); + + if !crate::common::is_recording() { + assert_eq!( + isin_company.as_deref(), + Some("US0378331005"), + "Expected correct ISIN for AAPL" + ); + assert_eq!( + isin_fund.as_deref(), + Some("US46090E1038"), + "Expected correct ISIN for QQQ" + ); + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/isin_offline.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/isin_offline.rs new file mode 100644 index 0000000..9bd2e6d --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/isin_offline.rs @@ -0,0 +1,40 @@ +use crate::common::fixture; +use httpmock::{Method::GET, MockServer}; +use url::Url; +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::test] +async fn offline_isin_happy_path() { + let server = MockServer::start(); + let sym = "AAPL"; + + let isin_mock = server.mock(|when, then| { + when.method(GET) + .path("/ajax/SearchController_Suggest") + .query_param("query", sym); + then.status(200) + .header("content-type", "application/json") + .body(fixture("isin_search", sym, "json")); + }); + + let client = YfClient::builder() + .base_insider_search( + Url::parse(&format!( + "{}/ajax/SearchController_Suggest", + server.base_url() + )) + .unwrap(), + ) + .build() + .unwrap(); + + let ticker = Ticker::new(&client, sym); + let isin = ticker.isin().await.unwrap(); + + isin_mock.assert(); + assert_eq!( + isin, + Some("US0378331005".to_string()), + "ISIN not parsed from fixture. Did you run `just test-record ticker` first?" + ); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/live.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/live.rs new file mode 100644 index 0000000..f635a1b --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/live.rs @@ -0,0 +1,102 @@ +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_ticker_quote_for_record() { + if !crate::common::live_or_record_enabled() { + return; + } + + let client = yfinance_rs::YfClient::builder().build().unwrap(); + + for sym in ["AAPL", "MSFT"] { + let t = yfinance_rs::Ticker::new(&client, sym); + let q = t.quote().await.unwrap(); + + if !crate::common::is_recording() { + assert_eq!(q.symbol.as_str(), sym); + assert!(q.price.is_some() || q.previous_close.is_some()); + } + } +} + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_ticker_options_for_record() { + if !crate::common::live_or_record_enabled() { + return; + } + + let client = yfinance_rs::YfClient::builder().build().unwrap(); + + let sym = "AAPL"; + let t = yfinance_rs::Ticker::new(&client, sym); + + let expiries = t.options().await.unwrap(); + + if crate::common::is_recording() { + assert!( + crate::common::fixture_exists("options_v7", sym, "json"), + "recording pass should persist options_v7 fixture for {sym}" + ); + } + + if !crate::common::is_recording() { + // In live mode (non-recording), we expect Yahoo to return at least one expiry. + assert!( + !expiries.is_empty(), + "live options lookup for {sym} should return expirations" + ); + } + + if let Some(first) = expiries.first().copied() { + let chain = t.option_chain(Some(first)).await.unwrap(); + + if crate::common::is_recording() { + let key = format!("{sym}_{first}"); + assert!( + crate::common::fixture_exists("options_v7", &key, "json"), + "recording pass should persist dated options_v7 fixture for {key}" + ); + assert!( + chain.calls.iter().chain(chain.puts.iter()).next().is_some(), + "recorded chain for {sym} should include at least one contract" + ); + } + + if !crate::common::is_recording() { + // Instead of a useless `>= 0` check on usize, ensure the chain is coherent: + // every returned contract (if any) must match the requested expiration. + assert!( + chain.calls.iter().chain(chain.puts.iter()).all(|c| c + .expiration_at + .unwrap() + .timestamp() + == first), + "all option contracts should match the requested expiration for {sym}" + ); + } + } +} + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_ticker_shares_for_record() { + if !crate::common::is_recording() { + return; + } + let client = yfinance_rs::YfClient::builder().build().unwrap(); + let t = yfinance_rs::Ticker::new(&client, "MSFT"); + let _ = t.shares().await; + let _ = t.quarterly_shares().await; +} + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_ticker_capital_gains_for_record() { + if !crate::common::is_recording() { + return; + } + + let client = yfinance_rs::YfClient::builder().build().unwrap(); + let t = yfinance_rs::Ticker::new(&client, "VFINX"); + let _ = t.capital_gains(None).await.unwrap(); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/offline.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/offline.rs new file mode 100644 index 0000000..2c84591 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/offline.rs @@ -0,0 +1,57 @@ +use url::Url; +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::test] +async fn offline_quote_uses_recorded_fixture() { + let server = crate::common::setup_server(); + let sym = "AAPL"; + let mock = crate::common::mock_quote_v7(&server, sym); + + let client = YfClient::builder() + .base_quote_v7(Url::parse(&format!("{}/v7/finance/quote", server.base_url())).unwrap()) + .build() + .unwrap(); + let t = Ticker::new(&client, sym); + + let q = t.quote().await.unwrap(); + mock.assert(); + + assert_eq!(q.symbol.as_str(), sym); + // Don’t assert exact prices — fixtures will be from your latest recording + assert!(q.price.is_some() || q.previous_close.is_some()); +} + +#[tokio::test] +async fn offline_options_uses_recorded_fixtures() { + let server = crate::common::setup_server(); + let sym = "AAPL"; + + // Expirations (no date) + let mock_exp = crate::common::mock_options_v7(&server, sym); + + let client = YfClient::builder() + .base_options_v7(Url::parse(&format!("{}/v7/finance/options/", server.base_url())).unwrap()) + .build() + .unwrap(); + let t = Ticker::new(&client, sym); + + let expiries = t.options().await.unwrap(); + mock_exp.assert(); + + assert!( + !expiries.is_empty(), + "record expirations via YF_RECORD=1 first" + ); + + // Chain for the first date (date-scoped fixture) + let d = expiries[0]; + let mock_chain = crate::common::mock_options_v7_for_date(&server, sym, d); + + let chain = t.option_chain(Some(d)).await.unwrap(); + mock_chain.assert(); + + // Contracts, if present, should carry the requested expiration + for c in chain.calls.iter().chain(chain.puts.iter()) { + assert_eq!(c.expiration_at.unwrap().timestamp(), d); + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/options.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/options.rs new file mode 100644 index 0000000..598908b --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/options.rs @@ -0,0 +1,297 @@ +use serde_json::Value; +use url::Url; +use yfinance_rs::core::conversions::money_to_currency_str; +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::test] +async fn options_expirations_happy() { + let server = crate::common::setup_server(); + let symbol = "AAPL"; + + let mock = crate::common::mock_options_v7(&server, symbol); + + let client = YfClient::builder() + .base_options_v7(Url::parse(&format!("{}/v7/finance/options/", server.base_url())).unwrap()) + .build() + .unwrap(); + let t = Ticker::new(&client, symbol); + + let expiries = t.options().await.unwrap(); + mock.assert(); + + assert!( + !expiries.is_empty(), + "record {symbol} options fixtures first via YF_RECORD=1 cargo test --test ticker -- options" + ); +} + +#[tokio::test] +async fn option_chain_for_specific_date() { + let server = crate::common::setup_server(); + let symbol = "AAPL"; + + let exp_mock = crate::common::mock_options_v7(&server, symbol); + let quote_mock = crate::common::mock_quote_v7(&server, symbol); + + let client = YfClient::builder() + .base_options_v7(Url::parse(&format!("{}/v7/finance/options/", server.base_url())).unwrap()) + .base_quote_v7(Url::parse(&format!("{}/v7/finance/quote", server.base_url())).unwrap()) + .build() + .unwrap(); + let t = Ticker::new(&client, symbol); + + let expiries = t.options().await.unwrap(); + exp_mock.assert(); + + assert!( + !expiries.is_empty(), + "record {symbol} options fixtures first via YF_RECORD=1 cargo test --test ticker -- options" + ); + + let date = expiries[0]; + let chain_mock = crate::common::mock_options_v7_for_date(&server, symbol, date); + + let chain = t.option_chain(Some(date)).await.unwrap(); + chain_mock.assert(); + assert_eq!( + quote_mock.calls(), + 0, + "options currency should prevent quote fallback" + ); + + assert!( + !chain.calls.is_empty(), + "recorded {symbol} chain should include call contracts" + ); + assert!( + !chain.puts.is_empty(), + "recorded {symbol} chain should include put contracts" + ); + + let c = &chain.calls[0]; + assert_eq!(money_to_currency_str(&c.strike).as_deref(), Some("USD")); + assert_eq!(c.expiration_at.unwrap().timestamp(), date); + + let p = &chain.puts[0]; + if let Some(price) = p.price.as_ref() { + assert_eq!(money_to_currency_str(price).as_deref(), Some("USD")); + } + assert_eq!(p.expiration_at.unwrap().timestamp(), date); +} + +#[tokio::test] +async fn option_chain_currency_fallback_fetches_quote() { + let server = crate::common::setup_server(); + let symbol = "AAPL"; + + assert_fixture_present(symbol); + + let mut base_json = load_options_json(symbol); + let expiries = extract_expiration_dates(&base_json); + assert!( + !expiries.is_empty(), + "recorded {symbol} options fixture missing expiration dates" + ); + strip_quote_currency(&mut base_json); + let base_payload = base_json.to_string(); + + let date = expiries[0]; + let fixture_key = format!("{symbol}_{date}"); + assert_fixture_present(&fixture_key); + + let mut dated_json = load_options_json(&fixture_key); + strip_quote_currency(&mut dated_json); + let dated_payload = dated_json.to_string(); + + let base_mock = mock_base_options_request(&server, symbol, base_payload); + let chain_mock = mock_dated_options_request(&server, symbol, date, dated_payload); + let quote_mock = crate::common::mock_quote_v7(&server, symbol); + + let client = YfClient::builder() + .base_options_v7(Url::parse(&format!("{}/v7/finance/options/", server.base_url())).unwrap()) + .base_quote_v7(Url::parse(&format!("{}/v7/finance/quote", server.base_url())).unwrap()) + .build() + .unwrap(); + + let ticker = Ticker::new(&client, symbol); + + let expiries_resp = ticker.options().await.unwrap(); + base_mock.assert(); + assert_eq!(expiries_resp, expiries); + + let chain = ticker.option_chain(Some(date)).await.unwrap(); + + chain_mock.assert(); + quote_mock.assert(); + + assert!( + quote_mock.calls() >= 1, + "fallback should hit quote endpoint at least once" + ); + + let combined = chain + .calls + .iter() + .chain(chain.puts.iter()) + .collect::>(); + assert!( + !combined.is_empty(), + "recorded chain for {symbol} should include contracts" + ); + + for contract in combined { + assert_eq!( + money_to_currency_str(&contract.strike).as_deref(), + Some("USD") + ); + assert_eq!(contract.expiration_at.unwrap().timestamp(), date); + } +} + +fn assert_fixture_present(id: &str) { + assert!( + crate::common::fixture_exists("options_v7", id, "json"), + "record {id} options fixtures via YF_RECORD=1 cargo test --test ticker -- options" + ); +} + +fn load_options_json(id: &str) -> Value { + let body = crate::common::fixture("options_v7", id, "json"); + serde_json::from_str(&body).expect("options fixture json") +} + +fn extract_expiration_dates(json: &Value) -> Vec { + json["optionChain"]["result"][0]["expirationDates"] + .as_array() + .expect("expirationDates array") + .iter() + .map(|v| v.as_i64().expect("epoch")) + .collect() +} + +fn strip_quote_currency(json: &mut Value) { + if let Some(obj) = json + .get_mut("optionChain") + .and_then(|oc| oc.get_mut("result")) + .and_then(|arr| arr.get_mut(0)) + .and_then(|node| node.get_mut("quote")) + .and_then(|quote| quote.as_object_mut()) + { + obj.remove("currency"); + } +} + +fn mock_base_options_request<'a>( + server: &'a httpmock::MockServer, + symbol: &str, + payload: String, +) -> httpmock::Mock<'a> { + let symbol = symbol.to_string(); + let body = payload; + server.mock(move |when, then| { + when.method(httpmock::Method::GET) + .path(format!("/v7/finance/options/{symbol}")) + .is_true(|req| !req.query_params().iter().any(|(k, _)| k == "date")); + then.status(200) + .header("content-type", "application/json") + .body(body); + }) +} + +fn mock_dated_options_request<'a>( + server: &'a httpmock::MockServer, + symbol: &str, + date: i64, + payload: String, +) -> httpmock::Mock<'a> { + let symbol = symbol.to_string(); + let body = payload; + server.mock(move |when, then| { + when.method(httpmock::Method::GET) + .path(format!("/v7/finance/options/{symbol}")) + .query_param("date", date.to_string()); + then.status(200) + .header("content-type", "application/json") + .body(body); + }) +} + +#[tokio::test] +async fn options_retry_with_crumb_on_403() { + use httpmock::Method::GET; + use httpmock::MockServer; + use url::Url; + use yfinance_rs::{Ticker, YfClient}; + + let server = MockServer::start(); + + // First call returns 403 (unauthorized) ONLY when the crumb is missing. + let date = 1_737_072_000_i64; + let first = server.mock(|when, then| { + when.method(GET) + .path("/v7/finance/options/MSFT") + .query_param("date", date.to_string()) + .is_true(|req| !req.query_params().iter().any(|(k, _)| k == "crumb")); + then.status(403); + }); + + // Cookie + crumb endpoints for ensure_credentials() + let cookie = server.mock(|when, then| { + when.method(GET).path("/consent"); + then.status(200).header( + "set-cookie", + "A=B; Max-Age=315360000; Domain=.yahoo.com; Path=/; Secure; SameSite=None", + ); + }); + + let crumb = server.mock(|when, then| { + when.method(GET).path("/v1/test/getcrumb"); + then.status(200).body("crumb-value"); + }); + + // Second attempt with ?crumb= should succeed + let ok_body = r#"{ + "optionChain": { + "result": [{ + "underlyingSymbol":"MSFT", + "expirationDates":[1737072000], + "quote": { + "currency": "USD" + }, + "options": [{ + "expirationDate": 1737072000, + "calls": [], + "puts": [] + }] + }], + "error": null + } + }"#; + + let second = server.mock(|when, then| { + when.method(GET) + .path("/v7/finance/options/MSFT") + .query_param("date", date.to_string()) + .query_param("crumb", "crumb-value"); + then.status(200) + .header("content-type", "application/json") + .body(ok_body); + }); + + let client = YfClient::builder() + .cookie_url(Url::parse(&format!("{}/consent", server.base_url())).unwrap()) + .crumb_url(Url::parse(&format!("{}/v1/test/getcrumb", server.base_url())).unwrap()) + .base_options_v7(Url::parse(&format!("{}/v7/finance/options/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let t = Ticker::new(&client, "MSFT"); + + let chain = t.option_chain(Some(date)).await.unwrap(); + assert!(chain.calls.is_empty() && chain.puts.is_empty()); + + first.assert(); + cookie.assert(); + crumb.assert(); + second.assert(); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/options_expiry_from_url_fallback.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/options_expiry_from_url_fallback.rs new file mode 100644 index 0000000..9b915e6 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/options_expiry_from_url_fallback.rs @@ -0,0 +1,59 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::test] +async fn option_chain_expiration_falls_back_to_url_date() { + let server = MockServer::start(); + let date = 1_737_072_000_i64; + + // Note: "expirationDate" is deliberately omitted from the payload. + let body = r#"{ + "optionChain": { + "result": [{ + "options": [{ + "calls": [{ + "contractSymbol":"AAPL250117C00180000", + "strike":180.0 + }], + "puts": [{ + "contractSymbol":"AAPL250117P00180000", + "strike":180.0 + }] + }] + }], + "error": null + } + }"#; + + let mock = server.mock(|when, then| { + when.method(GET) + .path("/v7/finance/options/AAPL") + .query_param("date", date.to_string()); + then.status(200) + .header("content-type", "application/json") + .body(body); + }); + + let client = YfClient::builder() + .base_options_v7(Url::parse(&format!("{}/v7/finance/options/", server.base_url())).unwrap()) + .build() + .unwrap(); + + let t = Ticker::new(&client, "AAPL"); + + let chain = t.option_chain(Some(date)).await.unwrap(); + + mock.assert(); + + assert!(!chain.calls.is_empty() && !chain.puts.is_empty()); + assert!( + chain + .calls + .iter() + .chain(chain.puts.iter()) + .all(|c| c.expiration_at.unwrap().timestamp() == date), + "expiration must fall back to 'date' query param" + ); +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/quote.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/quote.rs new file mode 100644 index 0000000..1452890 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/quote.rs @@ -0,0 +1,132 @@ +use httpmock::Method::GET; +use httpmock::MockServer; +use url::Url; +use yfinance_rs::core::conversions::*; +use yfinance_rs::{Ticker, YfClient}; + +#[tokio::test] +async fn quote_v7_happy_path() { + let server = MockServer::start(); + + let body = r#"{ + "quoteResponse": { + "result": [ + { + "symbol":"AAPL", + "regularMarketPrice": 190.25, + "regularMarketPreviousClose": 189.50, + "currency": "USD", + "fullExchangeName": "NasdaqGS", + "marketState": "REGULAR" + } + ], + "error": null + } + }"#; + + let mock = server.mock(|when, then| { + when.method(GET) + .path("/v7/finance/quote") + .query_param("symbols", "AAPL"); + then.status(200) + .header("content-type", "application/json") + .body(body); + }); + + let client = YfClient::builder() + .base_quote_v7(Url::parse(&format!("{}/v7/finance/quote", server.base_url())).unwrap()) + .build() + .unwrap(); + let ticker = Ticker::new(&client, "AAPL"); + + let q = ticker.quote().await.unwrap(); + mock.assert(); + + assert_eq!(q.symbol.as_str(), "AAPL"); + assert_eq!( + q.exchange.as_ref().map(std::string::ToString::to_string), + Some("NASDAQ".to_string()) + ); + assert_eq!( + q.market_state + .as_ref() + .map(std::string::ToString::to_string), + Some("REGULAR".to_string()) + ); + assert!((money_to_f64(&q.price.unwrap()) - 190.25).abs() < 1e-9); + assert!((money_to_f64(&q.previous_close.unwrap()) - 189.50).abs() < 1e-9); +} + +#[tokio::test] +async fn fast_info_derives_last_price() { + let server = MockServer::start(); + + // Deliberately omit regularMarketPrice to test fallback → previous close + let body = r#"{ + "quoteResponse": { + "result": [ + { + "symbol":"MSFT", + "regularMarketPreviousClose": 421.00, + "currency": "USD", + "exchange": "NasdaqGS", + "marketState": "CLOSED" + } + ], + "error": null + } + }"#; + + let mock = server.mock(|when, then| { + when.method(GET) + .path("/v7/finance/quote") + .query_param("symbols", "MSFT"); + then.status(200) + .header("content-type", "application/json") + .body(body); + }); + + let client = YfClient::builder() + .base_quote_v7(Url::parse(&format!("{}/v7/finance/quote", server.base_url())).unwrap()) + .build() + .unwrap(); + let ticker = Ticker::new(&client, "MSFT"); + + let fi = ticker.fast_info().await.unwrap(); + mock.assert(); + + assert_eq!(fi.symbol.as_str(), "MSFT"); + assert!(fi.last.is_none()); + assert!( + (money_to_f64(&fi.previous_close.unwrap()) - 421.00).abs() < 1e-9, + "fallback to previous close" + ); + assert_eq!(fi.currency.map(|c| c.to_string()).as_deref(), Some("USD")); + assert_eq!( + fi.exchange.map(|e| e.to_string()).as_deref(), + Some("NASDAQ") + ); + assert_eq!( + fi.market_state.map(|s| s.to_string()).as_deref(), + Some("CLOSED") + ); +} + +#[tokio::test] +#[ignore = "exercise live Yahoo Finance API"] +async fn live_quote_smoke() { + if std::env::var("YF_LIVE").ok().as_deref() != Some("1") + && std::env::var("YF_RECORD").ok().as_deref() != Some("1") + { + return; + } + + let client = YfClient::builder().build().unwrap(); + let ticker = Ticker::new(&client, "AAPL"); + let fi = ticker.fast_info().await.unwrap(); + + if std::env::var("YF_RECORD").ok().as_deref() != Some("1") { + assert!(money_to_f64(&fi.last.unwrap()) > 0.0); + assert_eq!(fi.symbol.as_str(), "AAPL"); + } +} diff --git a/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/shares.rs b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/shares.rs new file mode 100644 index 0000000..2a6fd61 --- /dev/null +++ b/MosaicIQ/src-tauri/vendor/yfinance-rs-0.7.2/tests/ticker/shares.rs @@ -0,0 +1,67 @@ +use httpmock::{Method::GET, MockServer}; +use url::Url; +use yfinance_rs::{Ticker, YfClient}; + +fn fixture(endpoint: &str, symbol: &str) -> String { + crate::common::fixture(endpoint, symbol, "json") +} + +#[tokio::test] +async fn offline_shares_uses_recorded_fixture() { + let sym = "MSFT"; + let server = MockServer::start(); + + let mock_annual = server.mock(|when, then| { + when.method(GET) + .path(format!( + "/ws/fundamentals-timeseries/v1/finance/timeseries/{sym}" + )) + .query_param("symbol", sym) + .query_param("type", "annualBasicAverageShares") + .query_param_exists("period1") + .query_param_exists("period2"); + then.status(200) + .header("content-type", "application/json") + .body(fixture("timeseries_annualBasicAverageShares", sym)); + }); + + let mock_quarterly = server.mock(|when, then| { + when.method(GET) + .path(format!( + "/ws/fundamentals-timeseries/v1/finance/timeseries/{sym}" + )) + .query_param("symbol", sym) + .query_param("type", "quarterlyBasicAverageShares") + .query_param_exists("period1") + .query_param_exists("period2"); + then.status(200) + .header("content-type", "application/json") + .body(fixture("timeseries_quarterlyBasicAverageShares", sym)); + }); + + let client = YfClient::builder() + .base_timeseries( + Url::parse(&format!( + "{}/ws/fundamentals-timeseries/v1/finance/timeseries/", + server.base_url() + )) + .unwrap(), + ) + .build() + .unwrap(); + + let t = Ticker::new(&client, sym); + + let annual = t.shares().await.unwrap(); + mock_annual.assert(); + assert!(!annual.is_empty(), "annual shares missing from fixture"); + assert!(annual[0].shares > 0, "shares count should be positive"); + + let quarterly = t.quarterly_shares().await.unwrap(); + mock_quarterly.assert(); + assert!( + !quarterly.is_empty(), + "quarterly shares missing from fixture" + ); + assert!(quarterly[0].shares > 0, "shares count should be positive"); +} diff --git a/MosaicIQ/src/components/Home/Home.tsx b/MosaicIQ/src/components/Home/Home.tsx index 8906e60..d412b22 100644 --- a/MosaicIQ/src/components/Home/Home.tsx +++ b/MosaicIQ/src/components/Home/Home.tsx @@ -6,7 +6,7 @@ interface HomeProps { export const Home: React.FC = ({ onStart }) => { const quickCommands = [ - { cmd: '/search AAPL', desc: 'Search Apple Inc.', icon: '🔍' }, + { cmd: '/search AAPL', desc: 'Load live Apple quote', icon: '🔍' }, { cmd: '/portfolio', desc: 'View portfolio', icon: '📊' }, { cmd: '/news AAPL', desc: 'Latest news', icon: '📰' }, { cmd: '/analyze AAPL', desc: 'Run analysis', icon: '⚡' }, diff --git a/MosaicIQ/src/components/Terminal/CommandInput.tsx b/MosaicIQ/src/components/Terminal/CommandInput.tsx index 11729bd..124cd78 100644 --- a/MosaicIQ/src/components/Terminal/CommandInput.tsx +++ b/MosaicIQ/src/components/Terminal/CommandInput.tsx @@ -23,7 +23,7 @@ export const CommandInput: React.FC = ({ const suggestionsRef = useRef(null); const suggestions = [ - { command: '/search', description: 'Search for stock' }, + { command: '/search', description: 'Search live security data' }, { command: '/portfolio', description: 'Show portfolio' }, { command: '/news', description: 'Market news' }, { command: '/analyze', description: 'AI analysis' },