Add RQData factor helper APIs
This commit is contained in:
@@ -1373,6 +1373,188 @@ impl DataSet {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_shares(
|
||||
&self,
|
||||
symbol: &str,
|
||||
start: NaiveDate,
|
||||
end: NaiveDate,
|
||||
share_type: &str,
|
||||
) -> Vec<FactorValue> {
|
||||
self.get_first_available_factor_series(
|
||||
symbol,
|
||||
start,
|
||||
end,
|
||||
&shares_factor_aliases(share_type),
|
||||
&format!("shares_{}", normalize_field(share_type)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_turnover_rate(
|
||||
&self,
|
||||
symbol: &str,
|
||||
start: NaiveDate,
|
||||
end: NaiveDate,
|
||||
field: &str,
|
||||
) -> Vec<FactorValue> {
|
||||
self.get_first_available_factor_series(
|
||||
symbol,
|
||||
start,
|
||||
end,
|
||||
&turnover_rate_factor_aliases(field),
|
||||
&format!("turnover_rate_{}", normalize_field(field)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_price_change_rate(
|
||||
&self,
|
||||
symbol: &str,
|
||||
start: NaiveDate,
|
||||
end: NaiveDate,
|
||||
) -> Vec<FactorValue> {
|
||||
if start > end {
|
||||
return Vec::new();
|
||||
}
|
||||
let mut rows = self
|
||||
.market_by_date
|
||||
.range(start..=end)
|
||||
.flat_map(|(_, snapshots)| snapshots.iter())
|
||||
.filter(|snapshot| snapshot.symbol == symbol)
|
||||
.filter_map(|snapshot| {
|
||||
if snapshot.prev_close.is_finite() && snapshot.prev_close > 0.0 {
|
||||
Some(FactorValue {
|
||||
date: snapshot.date,
|
||||
symbol: snapshot.symbol.clone(),
|
||||
field: "price_change_rate".to_string(),
|
||||
value: snapshot.close / snapshot.prev_close - 1.0,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if rows.is_empty() {
|
||||
rows = self.get_first_available_factor_series(
|
||||
symbol,
|
||||
start,
|
||||
end,
|
||||
&[
|
||||
"price_change_rate".to_string(),
|
||||
"change_rate".to_string(),
|
||||
"pct_change".to_string(),
|
||||
],
|
||||
"price_change_rate",
|
||||
);
|
||||
}
|
||||
rows.sort_by_key(|row| row.date);
|
||||
rows
|
||||
}
|
||||
|
||||
pub fn get_stock_connect(
|
||||
&self,
|
||||
symbol: &str,
|
||||
start: NaiveDate,
|
||||
end: NaiveDate,
|
||||
field: &str,
|
||||
) -> Vec<FactorValue> {
|
||||
self.get_first_available_factor_series(
|
||||
symbol,
|
||||
start,
|
||||
end,
|
||||
&stock_connect_factor_aliases(field),
|
||||
&format!("stock_connect_{}", normalize_field(field)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn current_performance(
|
||||
&self,
|
||||
symbol: &str,
|
||||
start: NaiveDate,
|
||||
end: NaiveDate,
|
||||
field: &str,
|
||||
) -> Vec<FactorValue> {
|
||||
self.get_first_available_factor_series(
|
||||
symbol,
|
||||
start,
|
||||
end,
|
||||
&prefixed_factor_aliases("current_performance", field),
|
||||
field,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_fundamentals(
|
||||
&self,
|
||||
symbol: &str,
|
||||
start: NaiveDate,
|
||||
end: NaiveDate,
|
||||
field: &str,
|
||||
) -> Vec<FactorValue> {
|
||||
self.get_first_available_factor_series(
|
||||
symbol,
|
||||
start,
|
||||
end,
|
||||
&prefixed_factor_aliases("fundamental", field),
|
||||
field,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_financials(
|
||||
&self,
|
||||
symbol: &str,
|
||||
start: NaiveDate,
|
||||
end: NaiveDate,
|
||||
field: &str,
|
||||
) -> Vec<FactorValue> {
|
||||
self.get_first_available_factor_series(
|
||||
symbol,
|
||||
start,
|
||||
end,
|
||||
&prefixed_factor_aliases("financial", field),
|
||||
field,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_pit_financials(
|
||||
&self,
|
||||
symbol: &str,
|
||||
start: NaiveDate,
|
||||
end: NaiveDate,
|
||||
field: &str,
|
||||
) -> Vec<FactorValue> {
|
||||
self.get_first_available_factor_series(
|
||||
symbol,
|
||||
start,
|
||||
end,
|
||||
&prefixed_factor_aliases("pit_financial", field),
|
||||
field,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_industry(
|
||||
&self,
|
||||
symbol: &str,
|
||||
date: NaiveDate,
|
||||
source: &str,
|
||||
level: usize,
|
||||
) -> Option<FactorValue> {
|
||||
let fields = industry_factor_aliases(source, level);
|
||||
for (factor_date, snapshots) in self.factor_by_date.range(..=date).rev() {
|
||||
let Some(snapshot) = snapshots.iter().find(|row| row.symbol == symbol) else {
|
||||
continue;
|
||||
};
|
||||
for field in &fields {
|
||||
if let Some(value) = factor_numeric_value(snapshot, field) {
|
||||
return Some(FactorValue {
|
||||
date: *factor_date,
|
||||
symbol: snapshot.symbol.clone(),
|
||||
field: field.clone(),
|
||||
value,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_dominant_future(&self, underlying_symbol: &str, date: NaiveDate) -> Option<String> {
|
||||
let underlying = normalize_field(underlying_symbol);
|
||||
let mut candidates = self
|
||||
@@ -1614,6 +1796,39 @@ impl DataSet {
|
||||
.and_then(|snapshot| factor_numeric_value(snapshot, field))
|
||||
}
|
||||
|
||||
fn get_first_available_factor_series(
|
||||
&self,
|
||||
symbol: &str,
|
||||
start: NaiveDate,
|
||||
end: NaiveDate,
|
||||
fields: &[String],
|
||||
output_field: &str,
|
||||
) -> Vec<FactorValue> {
|
||||
if start > end {
|
||||
return Vec::new();
|
||||
}
|
||||
let output_field = normalize_field(output_field);
|
||||
let mut rows = Vec::new();
|
||||
for (_, snapshots) in self.factor_by_date.range(start..=end) {
|
||||
let Some(snapshot) = snapshots.iter().find(|row| row.symbol == symbol) else {
|
||||
continue;
|
||||
};
|
||||
for field in fields {
|
||||
if let Some(value) = factor_numeric_value(snapshot, field) {
|
||||
rows.push(FactorValue {
|
||||
date: snapshot.date,
|
||||
symbol: snapshot.symbol.clone(),
|
||||
field: output_field.clone(),
|
||||
value,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
rows.sort_by_key(|row| row.date);
|
||||
rows
|
||||
}
|
||||
|
||||
pub fn factor_moving_average(
|
||||
&self,
|
||||
date: NaiveDate,
|
||||
@@ -1838,6 +2053,144 @@ fn read_factors(path: &Path) -> Result<Vec<DailyFactorSnapshot>, DataSetError> {
|
||||
Ok(snapshots)
|
||||
}
|
||||
|
||||
fn normalized_aliases(values: &[String]) -> Vec<String> {
|
||||
let mut aliases = Vec::new();
|
||||
for value in values {
|
||||
let normalized = normalize_field(value);
|
||||
if !aliases.contains(&normalized) {
|
||||
aliases.push(normalized);
|
||||
}
|
||||
}
|
||||
aliases
|
||||
}
|
||||
|
||||
fn shares_factor_aliases(share_type: &str) -> Vec<String> {
|
||||
let field = normalize_field(share_type);
|
||||
let values = match field.as_str() {
|
||||
"" | "all" | "total" => vec![
|
||||
"total_shares",
|
||||
"shares_total",
|
||||
"total_share",
|
||||
"total_share_capital",
|
||||
"capitalization",
|
||||
"shares",
|
||||
],
|
||||
"float" | "free_float" | "circulating" | "circulation" => vec![
|
||||
"free_float_shares",
|
||||
"float_shares",
|
||||
"circulating_shares",
|
||||
"circulation_shares",
|
||||
"float_a_shares",
|
||||
],
|
||||
"a" | "a_share" | "a_shares" => vec!["a_shares", "shares_a", "a_share_capital"],
|
||||
other => {
|
||||
return normalized_aliases(&[
|
||||
other.to_string(),
|
||||
format!("shares_{other}"),
|
||||
format!("{other}_shares"),
|
||||
]);
|
||||
}
|
||||
};
|
||||
normalized_aliases(
|
||||
&values
|
||||
.iter()
|
||||
.map(|value| value.to_string())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
}
|
||||
|
||||
fn turnover_rate_factor_aliases(field: &str) -> Vec<String> {
|
||||
let field = normalize_field(field);
|
||||
let values = match field.as_str() {
|
||||
"" | "all" | "rate" | "turnover" | "turnover_rate" | "turnover_ratio" => {
|
||||
vec!["turnover_rate", "turnover_ratio"]
|
||||
}
|
||||
"effective" | "effective_turnover" | "effective_turnover_rate" => {
|
||||
vec!["effective_turnover_rate", "effective_turnover_ratio"]
|
||||
}
|
||||
other => {
|
||||
return normalized_aliases(&[
|
||||
other.to_string(),
|
||||
format!("turnover_rate_{other}"),
|
||||
format!("{other}_turnover_rate"),
|
||||
format!("turnover_ratio_{other}"),
|
||||
format!("{other}_turnover_ratio"),
|
||||
]);
|
||||
}
|
||||
};
|
||||
normalized_aliases(
|
||||
&values
|
||||
.iter()
|
||||
.map(|value| value.to_string())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
}
|
||||
|
||||
fn stock_connect_factor_aliases(field: &str) -> Vec<String> {
|
||||
let field = normalize_field(field);
|
||||
let values = match field.as_str() {
|
||||
"" | "all" | "connect" | "stock_connect" => {
|
||||
vec![
|
||||
"stock_connect",
|
||||
"stock_connect_all",
|
||||
"connect_all",
|
||||
"north_bound",
|
||||
]
|
||||
}
|
||||
"north" | "north_bound" | "northbound" => vec![
|
||||
"stock_connect_north_bound",
|
||||
"stock_connect_northbound",
|
||||
"connect_north_bound",
|
||||
"north_bound",
|
||||
"northbound",
|
||||
],
|
||||
"south" | "south_bound" | "southbound" => vec![
|
||||
"stock_connect_south_bound",
|
||||
"stock_connect_southbound",
|
||||
"connect_south_bound",
|
||||
"south_bound",
|
||||
"southbound",
|
||||
],
|
||||
other => {
|
||||
return normalized_aliases(&[
|
||||
other.to_string(),
|
||||
format!("stock_connect_{other}"),
|
||||
format!("connect_{other}"),
|
||||
]);
|
||||
}
|
||||
};
|
||||
normalized_aliases(
|
||||
&values
|
||||
.iter()
|
||||
.map(|value| value.to_string())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
}
|
||||
|
||||
fn prefixed_factor_aliases(prefix: &str, field: &str) -> Vec<String> {
|
||||
let prefix = normalize_field(prefix);
|
||||
let field = normalize_field(field);
|
||||
let plural_prefix = format!("{prefix}s");
|
||||
normalized_aliases(&[
|
||||
format!("{prefix}_{field}"),
|
||||
format!("{plural_prefix}_{field}"),
|
||||
field.clone(),
|
||||
])
|
||||
}
|
||||
|
||||
fn industry_factor_aliases(source: &str, level: usize) -> Vec<String> {
|
||||
let source = normalize_field(source);
|
||||
normalized_aliases(&[
|
||||
format!("industry_{source}_l{level}"),
|
||||
format!("industry_{source}_{level}"),
|
||||
format!("{source}_industry_l{level}"),
|
||||
format!("{source}_industry_{level}"),
|
||||
format!("industry_l{level}"),
|
||||
format!("industry_{level}"),
|
||||
"industry_code".to_string(),
|
||||
])
|
||||
}
|
||||
|
||||
fn factor_numeric_value(snapshot: &DailyFactorSnapshot, field: &str) -> Option<f64> {
|
||||
match field {
|
||||
"market_cap" | "market_cap_bn" => Some(snapshot.market_cap_bn),
|
||||
|
||||
Reference in New Issue
Block a user