use chrono::Utc;
use serde::{Deserialize, Serialize};
use crate::client::Client;
use crate::types::symbols_to_owned;
const QUERY_OTHER_MARKET_DATA: &str = include_str!("graphql/other_market_data.graphql");
const PATTERN_LOOKBACK_YEARS: i32 = 4;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct OtherMarketDataVariables {
symbols: Vec<String>,
symbol_dialect_type: String,
up_to_historical_period_for_profit_margin: String,
up_to_historical_period_offset: String,
up_to_query_period_offset: String,
}
pub type MdFormattedFloat = crate::types::FormattedFloat;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdScaledFloat {
pub value: Option<f64>,
pub scaling_factor: Option<f64>,
pub formatted_value: Option<String>,
}
pub type MdDateValue = crate::types::DateValue;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdFormattedString {
pub value: Option<String>,
pub formatted_value: Option<String>,
}
pub type MdValueWrapper = crate::types::FloatValue;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdCurrencySymbolInfo {
pub mantissa_precision: Option<i64>,
pub unit_symbol: Option<String>,
pub iso_currency_code: Option<String>,
pub is_suffix: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdCurrencyValue {
pub value: Option<f64>,
pub scaling_factor: Option<f64>,
pub formatted_value: Option<String>,
pub currency_symbol_info: Option<MdCurrencySymbolInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OtherMarketDataResponse {
#[serde(default)]
pub market_data: Vec<MdMarketDataItem>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdMarketDataItem {
pub id: Option<String>,
pub origin_request: Option<MdOriginRequest>,
pub ratings: Option<MdRatings>,
pub pricing_statistics: Option<MdPricingStatistics>,
pub corporate_actions: Option<MdCorporateActions>,
pub symbology: Option<MdSymbology>,
pub pattern_info: Option<MdPatternInfo>,
pub financials: Option<MdFinancials>,
pub industry: Option<MdIndustry>,
pub ownership: Option<MdOwnership>,
pub fundamentals: Option<MdFundamentals>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdOriginRequest {
pub from_dialect: Option<String>,
pub symbol: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdRatings {
#[serde(default)]
pub comp_rating: Vec<MdRating>,
#[serde(default)]
pub rs_rating: Vec<MdRating>,
#[serde(default)]
pub eps_rating: Vec<MdRating>,
#[serde(default)]
pub smr_rating: Vec<MdRating>,
#[serde(default)]
pub ad_rating: Vec<MdRating>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdRating {
pub value: Option<i64>,
pub period_offset: Option<String>,
pub period: Option<String>,
pub letter_value: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdPricingStatistics {
pub end_of_day_statistics: Option<MdEndOfDayStatistics>,
pub intraday_statistics: Option<MdIntradayStatistics>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdEndOfDayStatistics {
#[serde(default)]
pub historical_price_statistics: Vec<MdHistoricalPriceStatistic>,
pub pricing_start_date: Option<MdDateValue>,
pub pricing_end_date: Option<MdDateValue>,
#[serde(default)]
pub volume_moving_averages: Vec<MdVolumeMovingAverage>,
pub avg_dollar_volume_50_day: Option<MdFormattedFloat>,
pub market_capitalization: Option<MdFormattedFloat>,
#[serde(default)]
pub average_true_range_percent: Vec<MdAverageTrueRangePercent>,
#[serde(default)]
pub ant_events: Vec<MdDateValue>,
pub up_down_volume_ratio: Option<MdScaledFloat>,
pub alpha: Option<MdScaledFloat>,
pub beta: Option<MdScaledFloat>,
pub short_interest: Option<MdShortInterest>,
#[serde(default)]
pub blue_dot_daily_events: Vec<MdFormattedString>,
#[serde(default)]
pub blue_dot_weekly_events: Vec<MdFormattedString>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdHistoricalPriceStatistic {
pub period: Option<String>,
pub period_offset: Option<String>,
pub period_end_date: Option<MdFormattedString>,
pub price_high_date: Option<MdFormattedString>,
pub price_high: Option<MdFormattedFloat>,
pub price_low_date: Option<MdFormattedString>,
pub price_low: Option<MdFormattedFloat>,
pub price_close: Option<MdFormattedFloat>,
pub price_percent_change: Option<MdFormattedFloat>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdVolumeMovingAverage {
pub value: Option<f64>,
pub period: Option<String>,
pub period_offset: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdAverageTrueRangePercent {
pub value: Option<f64>,
pub formatted_value: Option<String>,
pub period: Option<String>,
pub period_offset: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdShortInterest {
pub days_to_cover: Option<MdFormattedFloat>,
pub days_to_cover_percent_change: Option<MdFormattedFloat>,
pub percent_of_float: Option<MdScaledFloat>,
pub volume: Option<MdScaledFloat>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdIntradayStatistics {
#[serde(default)]
pub price_percent_change_vs: Vec<MdPercentChangeVs>,
#[serde(default)]
pub volume_percent_change_vs: Vec<MdPercentChangeVs>,
pub is_daily_blue_dot_event: Option<bool>,
pub is_weekly_blue_dot_event: Option<bool>,
#[serde(rename = "yield")]
pub yield_value: Option<MdScaledFloat>,
pub price_to_cash_flow_ratio: Option<MdScaledFloat>,
pub forward_price_to_earnings_ratio: Option<MdScaledFloat>,
pub price_to_sales_ratio: Option<MdScaledFloat>,
pub price_to_earnings_ratio: Option<MdScaledFloat>,
#[serde(rename = "priceToEarningsVsSP500")]
pub price_to_earnings_vs_sp500: Option<MdScaledFloat>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdPercentChangeVs {
pub value: Option<f64>,
pub formatted_value: Option<String>,
pub subject: Option<String>,
pub period: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdCorporateActions {
pub dividend_next_reported_ex_date: Option<MdFormattedString>,
#[serde(default)]
pub dividends: Vec<MdDividend>,
#[serde(default)]
pub spinoffs: Vec<MdSpinoff>,
#[serde(default)]
pub splits: Vec<MdSplit>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdDividend {
pub amount: Option<MdFormattedFloat>,
pub change_indicator: Option<String>,
pub ex_date: Option<MdDateValue>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdSpinoff {
pub ex_date: Option<MdDateValue>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdSplit {
pub split_date: Option<MdDateValue>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdSymbology {
pub company: Option<MdCompany>,
pub instrument: Option<MdInstrument>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdCompany {
pub company_name: Option<String>,
pub address: Option<String>,
pub address2: Option<String>,
pub phone: Option<String>,
pub business_description: Option<String>,
pub url: Option<String>,
pub city: Option<String>,
pub country: Option<String>,
pub state_province: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdInstrument {
pub sub_type: Option<String>,
pub ipo_date: Option<MdDateValue>,
pub ipo_price: Option<MdCurrencyValue>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdPatternInfo {
#[serde(default)]
pub patterns: Vec<MdPattern>,
#[serde(default)]
pub tight_areas: Vec<MdTightArea>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[allow(missing_docs)]
pub struct MdPattern {
pub id: Option<String>,
pub pattern_type: Option<String>,
pub periodicity: Option<String>,
pub base_stage: Option<String>,
pub base_number: Option<i64>,
pub base_status: Option<String>,
pub base_length: Option<i64>,
pub base_depth: Option<MdScaledFloat>,
pub base_start_date: Option<MdDateValue>,
pub base_end_date: Option<MdDateValue>,
pub base_bottom_date: Option<MdDateValue>,
pub left_side_high_date: Option<MdDateValue>,
pub pivot_price: Option<MdCurrencyValue>,
pub pivot_date: Option<MdDateValue>,
pub pivot_price_date: Option<MdDateValue>,
pub avg_volume_rate_pct_on_pivot: Option<MdScaledFloat>,
pub price_pct_change_on_pivot: Option<MdScaledFloat>,
pub handle_depth: Option<MdScaledFloat>,
pub handle_length: Option<i64>,
pub cup_length: Option<i64>,
pub cup_end_date: Option<MdDateValue>,
pub handle_low_date: Option<MdDateValue>,
pub handle_start_date: Option<MdDateValue>,
pub up_bars: Option<i64>,
pub blue_bars: Option<i64>,
pub stall_bars: Option<i64>,
pub down_bars: Option<i64>,
pub red_bars: Option<i64>,
pub support_bars: Option<i64>,
pub up_volume_total: Option<MdScaledFloat>,
pub down_volume_total: Option<MdScaledFloat>,
pub volume_pct_change_on_pivot: Option<MdScaledFloat>,
pub first_bottom_date: Option<MdDateValue>,
pub second_ascending_high_date: Option<MdDateValue>,
pub second_bottom_date: Option<MdDateValue>,
pub third_ascending_high_date: Option<MdDateValue>,
pub third_bottom_date: Option<MdDateValue>,
pub pull_back_1_depth: Option<MdScaledFloat>,
pub pull_back_2_depth: Option<MdScaledFloat>,
pub pull_back_3_depth: Option<MdScaledFloat>,
pub mid_peak_date: Option<MdDateValue>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdTightArea {
#[serde(rename = "patternID")]
pub pattern_id: Option<i64>,
pub start_date: Option<MdDateValue>,
pub end_date: Option<MdDateValue>,
pub length: Option<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdFinancials {
pub eps_due_date: Option<MdFormattedString>,
pub eps_due_date_status: Option<String>,
pub eps_last_reported_date: Option<MdDateValue>,
pub consensus_financials: Option<MdConsensusFinancials>,
pub cash_flow_per_share_last_year: Option<MdFormattedFloat>,
#[serde(default)]
pub profit_margin_values: Vec<MdProfitMarginValue>,
pub estimates: Option<MdEstimates>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdConsensusFinancials {
pub eps: Option<MdConsensusEps>,
pub sales: Option<MdConsensusSales>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdConsensusEps {
#[serde(default)]
pub reported_earnings: Vec<MdReportedPeriod>,
#[serde(default)]
pub growth_rate: Vec<MdGrowthRate>,
pub earnings_stability: Option<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdConsensusSales {
#[serde(default)]
pub reported_sales: Vec<MdReportedPeriod>,
#[serde(default)]
pub growth_rate: Vec<MdGrowthRate>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdReportedPeriod {
pub value: Option<MdValueWrapper>,
#[serde(rename = "percentChangeYOY")]
pub percent_change_yoy: Option<MdValueWrapper>,
pub period_offset: Option<String>,
pub period: Option<String>,
pub period_end_date: Option<MdDateValue>,
pub effective_date: Option<MdDateValue>,
pub percent_surprise: Option<MdValueWrapper>,
pub surprise_amount: Option<MdValueWrapper>,
pub quarter_number: Option<i64>,
pub fiscal_year: Option<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdGrowthRate {
pub value: Option<f64>,
pub scaling_factor: Option<f64>,
pub period: Option<String>,
pub formatted_value: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdProfitMarginValue {
pub period: Option<String>,
pub pre_tax_margin: Option<MdScaledFloat>,
pub after_tax_margin: Option<MdValueWrapper>,
pub gross_margin: Option<MdValueWrapper>,
pub return_on_equity: Option<MdFormattedFloat>,
pub period_end_date: Option<MdFormattedString>,
pub period_offset: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdEstimates {
#[serde(default)]
pub eps_estimates: Vec<MdEstimate>,
#[serde(default)]
pub sales_estimates: Vec<MdEstimate>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdEstimate {
pub revision_direction: Option<String>,
pub effective_date: Option<MdDateValue>,
pub period: Option<String>,
pub value: Option<MdValueWrapper>,
#[serde(rename = "percentChangeYOY")]
pub percent_change_yoy: Option<MdValueWrapper>,
pub period_end_date: Option<MdDateValue>,
#[serde(rename = "type")]
pub estimate_type: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdIndustry {
pub name: Option<String>,
pub sector: Option<String>,
pub ind_code: Option<String>,
#[serde(default)]
pub group_ranks: Vec<MdGroupRank>,
#[serde(default, rename = "groupRS")]
pub group_rs: Vec<MdGroupRs>,
pub number_of_stocks_in_group: Option<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdGroupRank {
pub value: Option<i64>,
pub period: Option<String>,
pub period_offset: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdGroupRs {
pub value: Option<i64>,
pub period_offset: Option<String>,
pub letter_value: Option<String>,
pub period: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdOwnership {
pub funds_float_percent_held: Option<MdScaledFloat>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MdFundamentals {
pub research_and_development_percent_last_qtr: Option<MdScaledFloat>,
#[serde(rename = "newCEODate")]
pub new_ceo_date: Option<MdDateValue>,
pub debt_percent: Option<MdFormattedFloat>,
}
impl Client {
pub async fn other_market_data(
&self,
symbols: &[&str],
symbol_dialect_type: &str,
historical_period_profit_margin: &str,
historical_period_offset: &str,
query_period_offset: &str,
) -> crate::error::Result<OtherMarketDataResponse> {
let now = Utc::now();
let pattern_end = now.format("%Y-%m-%d").to_string();
let pattern_start = now
.checked_sub_months(chrono::Months::new(
u32::try_from(PATTERN_LOOKBACK_YEARS).unwrap_or(4) * 12,
))
.unwrap_or(now)
.format("%Y-%m-%d")
.to_string();
let query = QUERY_OTHER_MARKET_DATA
.replace("{pattern_start_date}", &pattern_start)
.replace("{pattern_end_date}", &pattern_end);
let variables = OtherMarketDataVariables {
symbols: symbols_to_owned(symbols),
symbol_dialect_type: symbol_dialect_type.to_string(),
up_to_historical_period_for_profit_margin: historical_period_profit_margin.to_string(),
up_to_historical_period_offset: historical_period_offset.to_string(),
up_to_query_period_offset: query_period_offset.to_string(),
};
self.graphql_operation("OtherMarketData", variables, query)
.await
}
}
#[cfg(test)]
mod tests {
use crate::test_support::mock_test;
#[tokio::test]
async fn other_market_data_parses_response() {
let (_server, client, mock) = mock_test("OtherMarketData").await;
let resp = client
.other_market_data(&["AAPL"], "CHARTING", "P12Q_AGO", "P24Q_AGO", "P4Q_FUTURE")
.await
.expect("other_market_data should succeed");
assert_eq!(resp.market_data.len(), 1);
let item = &resp.market_data[0];
assert_eq!(item.id.as_deref(), Some("AAPL"));
let origin = item.origin_request.as_ref().expect("origin_request");
assert_eq!(origin.from_dialect.as_deref(), Some("CHARTING"));
assert_eq!(origin.symbol.as_deref(), Some("AAPL"));
let ratings = item.ratings.as_ref().expect("ratings");
assert_eq!(ratings.comp_rating.len(), 1);
assert_eq!(ratings.comp_rating[0].value, Some(95));
assert_eq!(ratings.rs_rating.len(), 1);
assert_eq!(ratings.rs_rating[0].value, Some(92));
assert_eq!(ratings.smr_rating[0].letter_value.as_deref(), Some("A"));
assert_eq!(ratings.ad_rating[0].letter_value.as_deref(), Some("B+"));
let pricing = item
.pricing_statistics
.as_ref()
.expect("pricing_statistics");
let eod = pricing
.end_of_day_statistics
.as_ref()
.expect("end_of_day_statistics");
assert_eq!(eod.historical_price_statistics.len(), 1);
assert_eq!(
eod.historical_price_statistics[0]
.price_high
.as_ref()
.unwrap()
.value,
Some(198.5)
);
assert_eq!(
eod.market_capitalization.as_ref().unwrap().value,
Some(3_200_000_000_000.0)
);
let si = eod.short_interest.as_ref().expect("short_interest");
assert_eq!(si.days_to_cover.as_ref().unwrap().value, Some(1.5));
let intraday = pricing
.intraday_statistics
.as_ref()
.expect("intraday_statistics");
assert_eq!(intraday.is_daily_blue_dot_event, Some(true));
assert_eq!(intraday.is_weekly_blue_dot_event, Some(false));
assert_eq!(
intraday.price_to_earnings_vs_sp500.as_ref().unwrap().value,
Some(1.45)
);
assert_eq!(intraday.yield_value.as_ref().unwrap().value, Some(0.55));
let corp = item.corporate_actions.as_ref().expect("corporate_actions");
assert_eq!(corp.dividends.len(), 1);
assert_eq!(
corp.dividends[0].change_indicator.as_deref(),
Some("UNCHANGED")
);
assert!(corp.spinoffs.is_empty());
assert_eq!(corp.splits.len(), 1);
let symb = item.symbology.as_ref().expect("symbology");
let company = symb.company.as_ref().expect("company");
assert_eq!(company.company_name.as_deref(), Some("Apple Inc."));
assert_eq!(company.city.as_deref(), Some("Cupertino"));
let instrument = symb.instrument.as_ref().expect("instrument");
assert_eq!(instrument.sub_type.as_deref(), Some("COMMON_STOCK"));
let pattern_info = item.pattern_info.as_ref().expect("pattern_info");
assert_eq!(pattern_info.patterns.len(), 1);
let pattern = &pattern_info.patterns[0];
assert_eq!(pattern.pattern_type.as_deref(), Some("CUP_WITH_HANDLE"));
assert_eq!(pattern.pivot_price.as_ref().unwrap().value, Some(195.5));
assert_eq!(pattern_info.tight_areas.len(), 1);
assert_eq!(pattern_info.tight_areas[0].pattern_id, Some(1));
let fin = item.financials.as_ref().expect("financials");
assert_eq!(fin.eps_due_date_status.as_deref(), Some("CONFIRMED"));
let consensus = fin
.consensus_financials
.as_ref()
.expect("consensus_financials");
let eps = consensus.eps.as_ref().expect("eps");
assert_eq!(eps.reported_earnings.len(), 1);
assert_eq!(
eps.reported_earnings[0].value.as_ref().unwrap().value,
Some(1.65)
);
assert_eq!(eps.earnings_stability, Some(3));
let sales = consensus.sales.as_ref().expect("sales");
assert_eq!(sales.reported_sales.len(), 1);
assert_eq!(fin.profit_margin_values.len(), 1);
assert_eq!(
fin.profit_margin_values[0]
.pre_tax_margin
.as_ref()
.unwrap()
.value,
Some(30.5)
);
let estimates = fin.estimates.as_ref().expect("estimates");
assert_eq!(estimates.eps_estimates.len(), 1);
assert_eq!(
estimates.eps_estimates[0].revision_direction.as_deref(),
Some("UP")
);
assert_eq!(
estimates.eps_estimates[0].estimate_type.as_deref(),
Some("CONSENSUS")
);
let industry = item.industry.as_ref().expect("industry");
assert_eq!(industry.name.as_deref(), Some("Comp-Hardware/Peripherals"));
assert_eq!(industry.sector.as_deref(), Some("Technology"));
assert_eq!(industry.number_of_stocks_in_group, Some(25));
assert_eq!(industry.group_rs.len(), 1);
assert_eq!(industry.group_rs[0].letter_value.as_deref(), Some("A"));
let ownership = item.ownership.as_ref().expect("ownership");
assert_eq!(
ownership.funds_float_percent_held.as_ref().unwrap().value,
Some(62.5)
);
let fundamentals = item.fundamentals.as_ref().expect("fundamentals");
assert_eq!(
fundamentals.new_ceo_date.as_ref().unwrap().value.as_deref(),
Some("2011-08-24")
);
mock.assert();
}
#[cfg(not(coverage))]
#[tokio::test]
#[ignore]
async fn integration_other_market_data() {
let client = crate::test_support::live_client().await;
let resp = client
.other_market_data(&["AAPL"], "CHARTING", "P12Q_AGO", "P24Q_AGO", "P4Q_FUTURE")
.await
.expect("live other_market_data should succeed");
assert!(!resp.market_data.is_empty());
}
}