Add dynamic universe and subscription controls
This commit is contained in:
@@ -70,6 +70,8 @@ pub struct StrategyContext<'a> {
|
||||
pub data: &'a DataSet,
|
||||
pub portfolio: &'a PortfolioState,
|
||||
pub open_orders: &'a [OpenOrderView],
|
||||
pub dynamic_universe: Option<&'a BTreeSet<String>>,
|
||||
pub subscriptions: &'a BTreeSet<String>,
|
||||
pub process_events: &'a [ProcessEvent],
|
||||
pub active_process_event: Option<&'a ProcessEvent>,
|
||||
}
|
||||
@@ -157,6 +159,47 @@ impl StrategyContext<'_> {
|
||||
raw_sellable_qty.saturating_sub(self.symbol_open_sell_quantity(symbol))
|
||||
}
|
||||
|
||||
pub fn has_dynamic_universe(&self) -> bool {
|
||||
self.dynamic_universe
|
||||
.is_some_and(|symbols| !symbols.is_empty())
|
||||
}
|
||||
|
||||
pub fn dynamic_universe_count(&self) -> usize {
|
||||
self.dynamic_universe.map_or(0, BTreeSet::len)
|
||||
}
|
||||
|
||||
pub fn dynamic_universe_contains(&self, symbol: &str) -> bool {
|
||||
self.dynamic_universe
|
||||
.is_some_and(|symbols| symbols.contains(symbol))
|
||||
}
|
||||
|
||||
pub fn eligible_universe_on(
|
||||
&self,
|
||||
date: NaiveDate,
|
||||
) -> Vec<crate::data::EligibleUniverseSnapshot> {
|
||||
let eligible = self.data.eligible_universe_on(date);
|
||||
match self.dynamic_universe {
|
||||
Some(symbols) if !symbols.is_empty() => eligible
|
||||
.iter()
|
||||
.filter(|row| symbols.contains(&row.symbol))
|
||||
.cloned()
|
||||
.collect(),
|
||||
_ => eligible.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_subscriptions(&self) -> bool {
|
||||
!self.subscriptions.is_empty()
|
||||
}
|
||||
|
||||
pub fn subscription_count(&self) -> usize {
|
||||
self.subscriptions.len()
|
||||
}
|
||||
|
||||
pub fn is_subscribed(&self, symbol: &str) -> bool {
|
||||
self.subscriptions.contains(symbol)
|
||||
}
|
||||
|
||||
pub fn has_process_events(&self) -> bool {
|
||||
!self.process_events.is_empty() || self.active_process_event.is_some()
|
||||
}
|
||||
@@ -381,6 +424,18 @@ pub enum OrderIntent {
|
||||
CancelAll {
|
||||
reason: String,
|
||||
},
|
||||
UpdateUniverse {
|
||||
symbols: BTreeSet<String>,
|
||||
reason: String,
|
||||
},
|
||||
Subscribe {
|
||||
symbols: BTreeSet<String>,
|
||||
reason: String,
|
||||
},
|
||||
Unsubscribe {
|
||||
symbols: BTreeSet<String>,
|
||||
reason: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -696,6 +751,7 @@ impl Strategy for CnSmallCapRotationStrategy {
|
||||
benchmark,
|
||||
reference_level: signal_level,
|
||||
data: ctx.data,
|
||||
dynamic_universe: ctx.dynamic_universe,
|
||||
});
|
||||
let before_ma_count = selected_before_ma.len();
|
||||
let mut ma_rejects = Vec::new();
|
||||
@@ -1576,6 +1632,13 @@ impl JqMicroCapStrategy {
|
||||
if !selected_set.insert(symbol.clone()) {
|
||||
continue;
|
||||
}
|
||||
if ctx.has_dynamic_universe() && !ctx.dynamic_universe_contains(symbol) {
|
||||
selected_set.remove(symbol);
|
||||
if diagnostics.len() < 14 {
|
||||
diagnostics.push(format!("truth {} rejected by dynamic_universe", symbol));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if let Some(reason) = self.buy_rejection_reason(ctx, date, symbol)? {
|
||||
selected_set.remove(symbol);
|
||||
if diagnostics.len() < 14 {
|
||||
@@ -1588,8 +1651,8 @@ impl JqMicroCapStrategy {
|
||||
}
|
||||
|
||||
if selected.len() < self.config.stocknum {
|
||||
let universe = ctx.data.eligible_universe_on(date);
|
||||
let start = lower_bound_eligible(universe, band_low);
|
||||
let universe = ctx.eligible_universe_on(date);
|
||||
let start = lower_bound_eligible(&universe, band_low);
|
||||
for candidate in universe.iter().skip(start) {
|
||||
if candidate.market_cap_bn > band_high {
|
||||
break;
|
||||
@@ -1623,10 +1686,10 @@ impl JqMicroCapStrategy {
|
||||
return Ok((selected, diagnostics));
|
||||
}
|
||||
|
||||
let universe = ctx.data.eligible_universe_on(date);
|
||||
let universe = ctx.eligible_universe_on(date);
|
||||
let mut diagnostics = Vec::new();
|
||||
let mut selected = Vec::new();
|
||||
let start = lower_bound_eligible(universe, band_low);
|
||||
let start = lower_bound_eligible(&universe, band_low);
|
||||
|
||||
for candidate in universe.iter().skip(start) {
|
||||
if candidate.market_cap_bn > band_high {
|
||||
|
||||
Reference in New Issue
Block a user