mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 09:45:36 +00:00
fix(core): symmetric bandar thresholds; bandar needs all investors
- Basis points truncate toward zero instead of flooring, so a reading of -4.9952% stays neutral like its mirror image and -14.9952% stays distribution; tests cover both signs at both thresholds through bandar_tier. Goldens move by the rounding (BBCA top 5: -2426 -> -2425). - With one investor side selected the summary has no total to weigh brokers against, so bandar and analyze_bandar reject it (INVALIDINPUT) instead of reading every tier as missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
This commit is contained in:
parent
970da26a0e
commit
4c2f0acff3
13 changed files with 73 additions and 18 deletions
|
|
@ -135,10 +135,12 @@ pub fn check_top(top: u8) -> Result<u8, IdxError> {
|
|||
}
|
||||
}
|
||||
|
||||
/// `part` in basis points of `whole`; `None` without a positive whole.
|
||||
/// `part` in basis points of `whole`, truncated toward zero so a negative
|
||||
/// reading never reaches a threshold its magnitude has not; `None` without
|
||||
/// a positive whole.
|
||||
fn bps(part: i64, whole: Option<i64>) -> Option<i64> {
|
||||
let whole = whole.filter(|whole| *whole > 0)?;
|
||||
i64::try_from((i128::from(part) * 10_000).div_euclid(i128::from(whole))).ok()
|
||||
i64::try_from(i128::from(part) * 10_000 / i128::from(whole)).ok()
|
||||
}
|
||||
|
||||
/// Sum of `field` over `flows`; `None` when any flow lacks it.
|
||||
|
|
@ -326,6 +328,37 @@ mod tests {
|
|||
assert_eq!(report.method, METHOD);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bandar_tier_thresholds_are_symmetric_for_sign() {
|
||||
let reading = |buy: i64, sell: i64| {
|
||||
let s = summary(
|
||||
vec![flow("AA", Some(buy), 100)],
|
||||
vec![flow("BB", Some(sell), 100)],
|
||||
2_100_000_000,
|
||||
);
|
||||
let tier = bandar_tier(&s, 1);
|
||||
(tier.imbalance_bps, tier.signal)
|
||||
};
|
||||
// -4.9952% and -14.9952% stay short of the thresholds, like their
|
||||
// mirror images.
|
||||
assert_eq!(
|
||||
reading(504_300_000, 609_200_000),
|
||||
(Some(-499), Some(BandarSignal::Neutral))
|
||||
);
|
||||
assert_eq!(
|
||||
reading(609_200_000, 504_300_000),
|
||||
(Some(499), Some(BandarSignal::Neutral))
|
||||
);
|
||||
assert_eq!(
|
||||
reading(504_300_000, 819_200_000),
|
||||
(Some(-1499), Some(BandarSignal::Distribution))
|
||||
);
|
||||
assert_eq!(
|
||||
reading(819_200_000, 504_300_000),
|
||||
(Some(1499), Some(BandarSignal::Accumulation))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_analyze_bandar_never_counts_a_missing_figure_as_zero() {
|
||||
let s = summary(
|
||||
|
|
|
|||
|
|
@ -31,6 +31,19 @@ pub struct Comparison {
|
|||
pub failures: Vec<(String, IdxError)>,
|
||||
}
|
||||
|
||||
/// Readings weigh brokers against the value traded, which the source does
|
||||
/// not total for one investor side.
|
||||
fn check_investor(investor: BrokerInvestor) -> Result<(), IdxError> {
|
||||
if investor == BrokerInvestor::All {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(IdxError::InvalidInput(format!(
|
||||
"bandar needs --investor all: the broker summary has no total for {} investors alone",
|
||||
investor.as_str()
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Bandar readings for several symbols, strongest accumulation first;
|
||||
/// symbols that failed are listed with their error.
|
||||
pub struct BandarScreen {
|
||||
|
|
@ -57,6 +70,7 @@ impl Analysis<'_> {
|
|||
policy: CachePolicy,
|
||||
) -> Result<Fetched<BandarReport>, IdxError> {
|
||||
let top = check_top(query.top)?;
|
||||
check_investor(query.investor)?;
|
||||
let summary =
|
||||
self.idx
|
||||
.broker()
|
||||
|
|
@ -73,6 +87,7 @@ impl Analysis<'_> {
|
|||
policy: CachePolicy,
|
||||
) -> Result<BandarScreen, IdxError> {
|
||||
check_top(query.top)?;
|
||||
check_investor(query.investor)?;
|
||||
let mut reports = Vec::new();
|
||||
let mut failures = Vec::new();
|
||||
for symbol in symbols {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue