mod common;
use crate::common::spec_path;
use tickerforge::{
generate_ticker_for_contract_signed, load_spec, parse_any_ticker_date, AnyParsedTicker,
ParsedFuturesTicker, TickerForge,
};
fn as_futures(any: AnyParsedTicker) -> ParsedFuturesTicker {
match any {
AnyParsedTicker::Futures(f) => f,
AnyParsedTicker::Option(o) => panic!("expected Futures but got Option: {:?}", o),
AnyParsedTicker::Equity(e) => panic!("expected Futures but got Equity: {:?}", e),
}
}
#[test]
fn parse_dol_offset_one_matches_generate() {
let forge = TickerForge::with_spec_path(&spec_path()).expect("forge");
let spec = load_spec().expect("spec");
let contract = spec.get_contract("DOL").expect("DOL contract");
let any = parse_any_ticker_date("DOL[1]", "2026-06-29").expect("parse");
let parsed = as_futures(any);
let from_signed =
generate_ticker_for_contract_signed(contract, "2026-06-29", &spec, 1).expect("signed");
let from_forge = forge.generate("DOL", "2026-06-29", 1).expect("forge");
assert_eq!(parsed.ticker().expect("ticker"), "DOLQ26");
assert_eq!(parsed.ticker().expect("ticker"), from_signed);
assert_eq!(parsed.ticker().expect("ticker"), from_forge);
assert_eq!(parsed.contract_offset, Some(1));
}
#[test]
fn parse_win_offset_two_matches_generate() {
let forge = TickerForge::with_spec_path(&spec_path()).expect("forge");
let any = parse_any_ticker_date("WIN[2]", "2026-06-29").expect("parse");
let parsed = as_futures(any);
assert_eq!(parsed.ticker().expect("ticker"), "WINZ26");
assert_eq!(
parsed.ticker().expect("ticker"),
forge.generate("WIN", "2026-06-29", 2).expect("forge")
);
assert_eq!(parsed.contract_offset, Some(2));
}
#[test]
fn parse_ccm_offset_one_matches_generate() {
let forge = TickerForge::with_spec_path(&spec_path()).expect("forge");
let spec = load_spec().expect("spec");
let contract = spec.get_contract("CCM").expect("CCM contract");
let any = parse_any_ticker_date("CCM[1]", "2026-09-01").expect("parse");
let parsed = as_futures(any);
let from_signed =
generate_ticker_for_contract_signed(contract, "2026-09-01", &spec, 1).expect("signed");
let from_forge = forge.generate("CCM", "2026-09-01", 1).expect("forge");
assert_eq!(parsed.ticker().expect("ticker"), "CCMX26");
assert_eq!(parsed.ticker().expect("ticker"), from_signed);
assert_eq!(parsed.ticker().expect("ticker"), from_forge);
assert_eq!(parsed.contract_offset, Some(1));
}
#[test]
fn parse_bgi_offset_six_matches_generate() {
let forge = TickerForge::with_spec_path(&spec_path()).expect("forge");
let spec = load_spec().expect("spec");
let contract = spec.get_contract("BGI").expect("BGI contract");
let any = parse_any_ticker_date("BGI[6]", "2026-09-01").expect("parse");
let parsed = as_futures(any);
let from_signed =
generate_ticker_for_contract_signed(contract, "2026-09-01", &spec, 6).expect("signed");
let from_forge = forge.generate("BGI", "2026-09-01", 6).expect("forge");
assert_eq!(parsed.ticker().expect("ticker"), "BGIH27");
assert_eq!(parsed.ticker().expect("ticker"), from_signed);
assert_eq!(parsed.ticker().expect("ticker"), from_forge);
assert_eq!(parsed.contract_offset, Some(6));
}
#[test]
fn parse_ccm_crosses_year() {
let forge = TickerForge::with_spec_path(&spec_path()).expect("forge");
let any = parse_any_ticker_date("CCM[3]", "2026-09-01").expect("parse");
let parsed = as_futures(any);
assert_eq!(parsed.ticker().expect("ticker"), "CCMH27");
assert_eq!(
parsed.ticker().expect("ticker"),
forge.generate("CCM", "2026-09-01", 3).expect("forge")
);
assert_eq!(parsed.year, 2027);
assert_eq!(parsed.month, 3);
assert_eq!(parsed.contract_offset, Some(3));
}
#[test]
fn parse_bgi_crosses_year() {
let forge = TickerForge::with_spec_path(&spec_path()).expect("forge");
let any = parse_any_ticker_date("BGI[6]", "2026-12-31").expect("parse");
let parsed = as_futures(any);
assert_eq!(parsed.ticker().expect("ticker"), "BGIN27");
assert_eq!(
parsed.ticker().expect("ticker"),
forge.generate("BGI", "2026-12-31", 6).expect("forge")
);
assert_eq!(parsed.year, 2027);
assert_eq!(parsed.month, 7);
assert_eq!(parsed.contract_offset, Some(6));
}
#[test]
fn parse_ind_negative_one_is_previous_contract() {
let any = parse_any_ticker_date("IND[-1]", "2026-06-18").expect("parse");
let parsed = as_futures(any);
assert_eq!(parsed.ticker().expect("ticker"), "INDM26");
assert_eq!(parsed.contract_offset, Some(-1));
}
#[test]
fn generate_signed_negative_one_returns_previous_dol() {
let spec = load_spec().expect("spec");
let contract = spec.get_contract("DOL").expect("DOL contract");
let ticker =
generate_ticker_for_contract_signed(contract, "2026-06-29", &spec, -1).expect("signed");
assert_eq!(ticker, "DOLM26");
}
#[test]
fn generate_signed_negative_two_returns_prior_dol() {
let spec = load_spec().expect("spec");
let contract = spec.get_contract("DOL").expect("DOL contract");
let ticker =
generate_ticker_for_contract_signed(contract, "2026-06-29", &spec, -2).expect("signed");
assert_eq!(ticker, "DOLK26");
}
#[test]
fn dol_offset_zero_equals_plain_dol_ticker() {
let tagged = as_futures(parse_any_ticker_date("DOL[0]", "2026-06-29").expect("tagged"));
let plain = as_futures(parse_any_ticker_date("DOL", "2026-06-29").expect("plain"));
assert_eq!(tagged.ticker().expect("ticker"), "DOLN26");
assert_eq!(
tagged.ticker().expect("ticker"),
plain.ticker().expect("ticker")
);
assert_eq!(tagged.contract_offset, Some(0));
assert_eq!(plain.contract_offset, None);
}
#[test]
fn contract_offset_some_for_tagged_none_for_full() {
let tagged = as_futures(parse_any_ticker_date("DOL[1]", "2026-06-29").expect("tagged"));
assert_eq!(tagged.contract_offset, Some(1));
let full = as_futures(parse_any_ticker_date("INDM26", "2026-06-18").expect("full"));
assert_eq!(full.contract_offset, None);
}
#[test]
fn out_of_range_positive_errors() {
let err = parse_any_ticker_date("DOL[999]", "2026-06-29").unwrap_err();
assert!(err.contains("out of range"));
}
#[test]
fn out_of_range_negative_errors() {
let err = parse_any_ticker_date("DOL[-999]", "2026-06-29").unwrap_err();
assert!(err.contains("out of range"));
}
#[test]
fn unknown_root_errors() {
let err = parse_any_ticker_date("ZZZ[1]", "2026-06-29").unwrap_err();
assert!(err.contains("Unable to parse ticker"));
}
#[test]
fn generate_signed_out_of_range_positive_errors() {
let spec = load_spec().expect("spec");
let contract = spec.get_contract("DOL").expect("DOL contract");
let err = generate_ticker_for_contract_signed(contract, "2026-06-29", &spec, 999).unwrap_err();
assert!(err.contains("out of range"));
}
#[test]
fn generate_signed_out_of_range_negative_errors() {
let spec = load_spec().expect("spec");
let contract = spec.get_contract("DOL").expect("DOL contract");
let err = generate_ticker_for_contract_signed(contract, "2026-06-29", &spec, -999).unwrap_err();
assert!(err.contains("out of range"));
}
#[test]
fn gen_signed_today_matches_generate_today() {
let forge = TickerForge::with_spec_path(&spec_path()).expect("forge");
let today = chrono::Local::now()
.date_naive()
.format("%Y-%m-%d")
.to_string();
assert_eq!(
forge.gen_signed("DOL", 1).expect("gen_signed"),
forge.generate("DOL", &today, 1).expect("generate"),
);
}
#[test]
fn tagged_root_stamps_reference_date_and_session() {
let parsed = as_futures(parse_any_ticker_date("DOL[1]", "2026-04-15").expect("parse"));
assert_eq!(
parsed.reference_date,
Some(chrono::NaiveDate::from_ymd_opt(2026, 4, 15).unwrap()),
);
assert_eq!(parsed.is_trading_session, Some(true));
assert_eq!(parsed.contract_offset, Some(1));
}