use chrono::Utc;
use serde::{Deserialize, Serialize};
use crate::client::Client;
use crate::graphql::GraphQLRequest;
use crate::types::symbols_to_owned;
const QUERY_OTHER_MARKET_DATA: &str = r#"query OtherMarketData(
$symbols: [String!]!
$symbolDialectType: MDSymbolDialectType!
$upToHistoricalPeriodOffset: MDUpToQueryPeriodOffsetHistorical!
$upToQueryPeriodOffset: MDUpToQueryPeriodOffsetFuture!
$upToHistoricalPeriodForProfitMargin: MDUpToQueryPeriodOffsetHistorical!
) {
marketData(symbols: $symbols, symbolDialectType: $symbolDialectType) {
id
originRequest {
fromDialect
symbol
}
ratings {
compRating {
value
periodOffset
period
}
rsRating(where: { periodOffset: { eq: CURRENT } }) {
value
periodOffset
period
}
epsRating {
value
periodOffset
period
}
smrRating {
value
periodOffset
period
letterValue
}
adRating(where: { periodOffset: { eq: CURRENT }, period: { eq: P12M } }) {
letterValue
period
periodOffset
value
}
}
pricingStatistics {
endOfDayStatistics {
historicalPriceStatistics(where: { period: { eq: P1Q } }) {
period
periodOffset
periodEndDate {
value
formattedValue
}
priceHighDate {
value
formattedValue
}
priceHigh {
value
formattedValue
}
priceLowDate {
value
formattedValue
}
priceLow {
value
formattedValue
}
priceClose {
value
formattedValue
}
pricePercentChange {
value
formattedValue
}
}
pricingStartDate {
value
}
pricingEndDate {
value
}
volumeMovingAverages {
value
period
periodOffset
}
avgDollarVolume50Day {
value
formattedValue
}
marketCapitalization {
value
formattedValue
}
averageTrueRangePercent {
value
formattedValue
period
periodOffset
}
antEvents {
value
}
upDownVolumeRatio {
value
scalingFactor
formattedValue
}
alpha {
formattedValue
scalingFactor
value
}
beta {
value
scalingFactor
formattedValue
}
shortInterest {
daysToCover {
formattedValue
value
}
daysToCoverPercentChange {
formattedValue
value
}
percentOfFloat {
value
scalingFactor
formattedValue
}
volume {
value
scalingFactor
formattedValue
}
}
blueDotDailyEvents {
formattedValue
value
}
blueDotWeeklyEvents {
formattedValue
value
}
}
intradayStatistics {
pricePercentChangeVs {
formattedValue
value
subject
period
}
volumePercentChangeVs {
value
formattedValue
subject
period
}
isDailyBlueDotEvent
isWeeklyBlueDotEvent
yield {
formattedValue
scalingFactor
value
}
priceToCashFlowRatio {
value
scalingFactor
formattedValue
}
forwardPriceToEarningsRatio {
value
scalingFactor
formattedValue
}
priceToSalesRatio {
value
scalingFactor
formattedValue
}
priceToEarningsRatio {
formattedValue
scalingFactor
value
}
priceToEarningsVsSP500 {
value
scalingFactor
formattedValue
}
}
}
corporateActions {
dividendNextReportedExDate {
formattedValue
value
}
dividends {
amount {
formattedValue
}
changeIndicator
exDate {
value
}
}
spinoffs {
exDate {
value
}
}
splits {
splitDate {
value
}
}
}
symbology {
company {
companyName
address
address2
phone
businessDescription
url
city
country
stateProvince
}
instrument {
subType
ipoDate {
value
}
ipoPrice {
formattedValue
value
currencySymbolInfo {
unitSymbol
}
}
}
}
patternInfo {
patterns(
where: {
baseStartDate: { value: { gte: "{pattern_start_date}" } }
baseEndDate: { value: { lte: "{pattern_end_date}" } }
periodicity: { eq: DAILY }
}
) {
... on MDCupWithoutHandle {
baseDepth {
value
scalingFactor
formattedValue
}
avgVolumeRatePctOnPivot {
value
scalingFactor
formattedValue
}
pricePctChangeOnPivot {
value
scalingFactor
formattedValue
}
handleDepth {
value
scalingFactor
formattedValue
}
handleLength
cupLength
cupEndDate {
value
}
handleLowDate {
value
}
handleStartDate {
value
}
baseBottomDate {
value
}
id
baseLength
baseNumber
baseStatus
patternType
periodicity
baseStage
baseStartDate {
value
}
baseEndDate {
value
}
pivotPrice {
currencySymbolInfo {
mantissaPrecision
unitSymbol
isoCurrencyCode
isSuffix
}
value
scalingFactor
formattedValue
}
pivotDate {
value
}
pivotPriceDate {
value
}
leftSideHighDate {
value
}
}
... on MDCupWithHandle {
baseDepth {
value
scalingFactor
formattedValue
}
avgVolumeRatePctOnPivot {
value
scalingFactor
formattedValue
}
pricePctChangeOnPivot {
value
scalingFactor
formattedValue
}
handleDepth {
value
scalingFactor
formattedValue
}
handleLength
cupLength
cupEndDate {
value
}
handleLowDate {
value
}
handleStartDate {
value
}
baseBottomDate {
value
}
id
baseLength
baseNumber
baseStatus
patternType
periodicity
baseStage
baseStartDate {
value
}
baseEndDate {
value
}
pivotPrice {
currencySymbolInfo {
mantissaPrecision
unitSymbol
isoCurrencyCode
isSuffix
}
value
scalingFactor
formattedValue
}
pivotDate {
value
}
pivotPriceDate {
value
}
leftSideHighDate {
value
}
}
... on MDConsolidation {
baseDepth {
value
scalingFactor
formattedValue
}
avgVolumeRatePctOnPivot {
value
scalingFactor
formattedValue
}
pricePctChangeOnPivot {
value
scalingFactor
formattedValue
}
baseBottomDate {
value
}
id
baseLength
baseNumber
baseStatus
patternType
periodicity
baseStage
baseStartDate {
value
}
baseEndDate {
value
}
pivotPrice {
value
scalingFactor
formattedValue
currencySymbolInfo {
mantissaPrecision
unitSymbol
isoCurrencyCode
isSuffix
}
}
pivotDate {
value
}
pivotPriceDate {
value
}
leftSideHighDate {
value
}
}
... on MDFlatBase {
baseDepth {
value
scalingFactor
formattedValue
}
avgVolumeRatePctOnPivot {
value
scalingFactor
formattedValue
}
pricePctChangeOnPivot {
value
scalingFactor
formattedValue
}
baseBottomDate {
value
}
id
baseLength
baseNumber
baseStatus
patternType
periodicity
baseStage
baseStartDate {
value
}
baseEndDate {
value
}
pivotPrice {
currencySymbolInfo {
mantissaPrecision
unitSymbol
isoCurrencyCode
isSuffix
}
value
scalingFactor
formattedValue
}
pivotDate {
value
}
pivotPriceDate {
value
}
leftSideHighDate {
value
}
}
... on MDIpoBase {
upBars
blueBars
stallBars
upVolumeTotal {
value
scalingFactor
formattedValue
}
downBars
redBars
supportBars
downVolumeTotal {
value
scalingFactor
formattedValue
}
volumePctChangeOnPivot {
value
scalingFactor
formattedValue
}
baseDepth {
value
scalingFactor
formattedValue
}
avgVolumeRatePctOnPivot {
value
scalingFactor
formattedValue
}
pricePctChangeOnPivot {
value
scalingFactor
formattedValue
}
baseBottomDate {
value
}
id
baseLength
baseNumber
baseStatus
patternType
periodicity
baseStage
baseStartDate {
value
}
baseEndDate {
value
}
pivotPrice {
currencySymbolInfo {
mantissaPrecision
unitSymbol
isoCurrencyCode
isSuffix
}
value
scalingFactor
formattedValue
}
pivotDate {
value
}
pivotPriceDate {
value
}
leftSideHighDate {
value
}
}
... on MDSaucerWithHandle {
baseDepth {
value
scalingFactor
formattedValue
}
avgVolumeRatePctOnPivot {
value
scalingFactor
formattedValue
}
pricePctChangeOnPivot {
value
scalingFactor
formattedValue
}
handleDepth {
value
scalingFactor
formattedValue
}
handleLength
cupLength
cupEndDate {
value
}
handleLowDate {
value
}
handleStartDate {
value
}
baseBottomDate {
value
}
id
baseLength
baseNumber
baseStatus
patternType
periodicity
baseStage
baseStartDate {
value
}
baseEndDate {
value
}
pivotPrice {
currencySymbolInfo {
mantissaPrecision
unitSymbol
isoCurrencyCode
isSuffix
}
value
scalingFactor
formattedValue
}
pivotDate {
value
}
pivotPriceDate {
value
}
leftSideHighDate {
value
}
}
... on MDSaucerWithoutHandle {
baseDepth {
value
scalingFactor
formattedValue
}
avgVolumeRatePctOnPivot {
value
scalingFactor
formattedValue
}
pricePctChangeOnPivot {
value
scalingFactor
formattedValue
}
handleDepth {
value
scalingFactor
formattedValue
}
handleLength
cupLength
cupEndDate {
value
}
handleLowDate {
value
}
handleStartDate {
value
}
baseBottomDate {
value
}
id
baseLength
baseNumber
baseStatus
patternType
periodicity
baseStage
baseStartDate {
value
}
baseEndDate {
value
}
pivotPrice {
currencySymbolInfo {
mantissaPrecision
unitSymbol
isoCurrencyCode
isSuffix
}
value
scalingFactor
formattedValue
}
pivotDate {
value
}
pivotPriceDate {
value
}
leftSideHighDate {
value
}
}
... on MDAscendingBase {
avgVolumeRatePctOnPivot {
value
scalingFactor
formattedValue
}
pricePctChangeOnPivot {
value
scalingFactor
formattedValue
}
firstBottomDate {
value
}
secondAscendingHighDate {
value
}
secondBottomDate {
value
}
thirdAscendingHighDate {
value
}
thirdBottomDate {
value
}
pullBack1Depth {
value
scalingFactor
formattedValue
}
pullBack2Depth {
value
scalingFactor
formattedValue
}
pullBack3Depth {
value
scalingFactor
formattedValue
}
id
baseLength
baseNumber
baseStatus
patternType
periodicity
baseStage
baseStartDate {
value
}
baseEndDate {
value
}
pivotPrice {
currencySymbolInfo {
mantissaPrecision
unitSymbol
isoCurrencyCode
isSuffix
}
value
scalingFactor
formattedValue
}
pivotDate {
value
}
pivotPriceDate {
value
}
leftSideHighDate {
value
}
}
... on MDDoubleBottom {
baseDepth {
value
scalingFactor
formattedValue
}
avgVolumeRatePctOnPivot {
value
scalingFactor
formattedValue
}
pricePctChangeOnPivot {
value
scalingFactor
formattedValue
}
firstBottomDate {
value
}
secondBottomDate {
value
}
midPeakDate {
value
}
id
baseLength
baseNumber
baseStatus
patternType
periodicity
baseStage
baseStartDate {
value
}
baseEndDate {
value
}
pivotPrice {
currencySymbolInfo {
mantissaPrecision
unitSymbol
isoCurrencyCode
isSuffix
}
value
scalingFactor
formattedValue
}
pivotDate {
value
}
pivotPriceDate {
value
}
leftSideHighDate {
value
}
}
}
tightAreas(
where: {
startDate: { value: { gte: "{pattern_start_date}" } }
endDate: { value: { gte: "{pattern_start_date}" } }
}
) {
patternID
startDate {
value
}
endDate {
value
}
length
}
}
financials {
epsDueDate {
value
formattedValue
}
epsDueDateStatus
epsLastReportedDate {
value
}
consensusFinancials {
eps {
reportedEarnings(upToHistoricalPeriodOffset: $upToHistoricalPeriodOffset) {
value {
value
}
percentChangeYOY {
value
}
periodOffset
period
periodEndDate {
value
}
effectiveDate {
value
}
percentSurprise {
value
}
surpriseAmount {
value
}
quarterNumber
fiscalYear
}
growthRate(where: { period: { eq: P1Y } }) {
value
scalingFactor
period
formattedValue
}
earningsStability
}
sales {
reportedSales(upToHistoricalPeriodOffset: $upToHistoricalPeriodOffset) {
value {
value
}
percentChangeYOY {
value
}
periodOffset
period
periodEndDate {
value
}
effectiveDate {
value
}
percentSurprise {
value
}
surpriseAmount {
value
}
quarterNumber
fiscalYear
}
growthRate(where: { period: { eq: P3Y } }) {
formattedValue
period
scalingFactor
value
}
}
}
cashFlowPerShareLastYear {
formattedValue
value
}
profitMarginValues(upToHistoricalPeriodOffset: $upToHistoricalPeriodForProfitMargin) {
period
preTaxMargin {
formattedValue
scalingFactor
value
}
afterTaxMargin {
value
}
grossMargin {
value
}
returnOnEquity {
value
formattedValue
}
periodEndDate {
value
formattedValue
}
periodOffset
}
estimates {
epsEstimates(upToQueryPeriodOffset: $upToQueryPeriodOffset) {
revisionDirection
effectiveDate {
value
}
period
value {
value
}
percentChangeYOY {
value
}
periodEndDate {
value
}
type
}
salesEstimates(upToQueryPeriodOffset: $upToQueryPeriodOffset) {
revisionDirection
effectiveDate {
value
}
period
value {
value
}
percentChangeYOY {
value
}
periodEndDate {
value
}
}
}
}
industry {
name
sector
indCode
groupRanks {
value
period
periodOffset
}
groupRS {
value
periodOffset
letterValue
period
}
numberOfStocksInGroup
}
ownership {
fundsFloatPercentHeld {
value
scalingFactor
formattedValue
}
}
fundamentals {
researchAndDevelopmentPercentLastQtr {
value
scalingFactor
formattedValue
}
newCEODate {
value
}
debtPercent {
formattedValue
}
}
}
}"#;
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(),
};
let request = GraphQLRequest {
operation_name: "OtherMarketData".to_string(),
variables,
query,
};
self.graphql_post(&request).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());
}
}