Support buy scaling from platform factors
This commit is contained in:
@@ -24,6 +24,7 @@ pub struct PlatformExprStrategyConfig {
|
||||
pub market_cap_upper_expr: String,
|
||||
pub selection_limit_expr: String,
|
||||
pub stock_filter_expr: String,
|
||||
pub buy_scale_expr: String,
|
||||
pub exposure_expr: String,
|
||||
pub stop_loss_expr: String,
|
||||
pub take_profit_expr: String,
|
||||
@@ -67,6 +68,7 @@ fn band_low(index_close) {
|
||||
stock_filter_expr:
|
||||
"stock_ma_short > stock_ma_mid * ma_ratio && stock_ma_mid > stock_ma_long"
|
||||
.to_string(),
|
||||
buy_scale_expr: "1.0".to_string(),
|
||||
exposure_expr:
|
||||
"benchmark_ma_short < benchmark_ma_long * ma_ratio ? 0.5 : 1.0".to_string(),
|
||||
stop_loss_expr: "0.93".to_string(),
|
||||
@@ -166,6 +168,8 @@ struct StockExpressionState {
|
||||
is_new_listing: bool,
|
||||
allow_buy: bool,
|
||||
allow_sell: bool,
|
||||
touched_upper_limit: bool,
|
||||
touched_lower_limit: bool,
|
||||
listed_days: i64,
|
||||
stock_ma_short: f64,
|
||||
stock_ma_mid: f64,
|
||||
@@ -300,6 +304,10 @@ impl PlatformExprStrategy {
|
||||
"is_new_listing",
|
||||
"allow_buy",
|
||||
"allow_sell",
|
||||
"touched_upper_limit",
|
||||
"touched_lower_limit",
|
||||
"hit_upper_limit",
|
||||
"hit_lower_limit",
|
||||
"listed_days",
|
||||
"stock_ma_short",
|
||||
"stock_ma_mid",
|
||||
@@ -794,6 +802,20 @@ impl PlatformExprStrategy {
|
||||
.data
|
||||
.market_decision_volume_moving_average(date, symbol, 60)
|
||||
.unwrap_or(stock_volume_ma20);
|
||||
let touched_upper_limit = factor
|
||||
.extra_factors
|
||||
.get("touched_upper_limit")
|
||||
.or_else(|| factor.extra_factors.get("hit_upper_limit"))
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
>= 0.5;
|
||||
let touched_lower_limit = factor
|
||||
.extra_factors
|
||||
.get("touched_lower_limit")
|
||||
.or_else(|| factor.extra_factors.get("hit_lower_limit"))
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
>= 0.5;
|
||||
|
||||
Ok(StockExpressionState {
|
||||
symbol: symbol.to_string(),
|
||||
@@ -821,6 +843,8 @@ impl PlatformExprStrategy {
|
||||
is_new_listing: candidate.is_new_listing,
|
||||
allow_buy: candidate.allow_buy,
|
||||
allow_sell: candidate.allow_sell,
|
||||
touched_upper_limit,
|
||||
touched_lower_limit,
|
||||
listed_days: if candidate.is_new_listing { 0 } else { 365 },
|
||||
stock_ma_short,
|
||||
stock_ma_mid,
|
||||
@@ -932,6 +956,10 @@ impl PlatformExprStrategy {
|
||||
scope.push("is_new_listing", stock.is_new_listing);
|
||||
scope.push("allow_buy", stock.allow_buy);
|
||||
scope.push("allow_sell", stock.allow_sell);
|
||||
scope.push("touched_upper_limit", stock.touched_upper_limit);
|
||||
scope.push("touched_lower_limit", stock.touched_lower_limit);
|
||||
scope.push("hit_upper_limit", stock.touched_upper_limit);
|
||||
scope.push("hit_lower_limit", stock.touched_lower_limit);
|
||||
scope.push("listed_days", stock.listed_days);
|
||||
scope.push("at_upper_limit", at_upper_limit);
|
||||
scope.push("at_lower_limit", at_lower_limit);
|
||||
@@ -983,6 +1011,16 @@ impl PlatformExprStrategy {
|
||||
factors.insert("is_new_listing".into(), Dynamic::from(stock.is_new_listing));
|
||||
factors.insert("allow_buy".into(), Dynamic::from(stock.allow_buy));
|
||||
factors.insert("allow_sell".into(), Dynamic::from(stock.allow_sell));
|
||||
factors.insert(
|
||||
"touched_upper_limit".into(),
|
||||
Dynamic::from(stock.touched_upper_limit),
|
||||
);
|
||||
factors.insert(
|
||||
"touched_lower_limit".into(),
|
||||
Dynamic::from(stock.touched_lower_limit),
|
||||
);
|
||||
factors.insert("hit_upper_limit".into(), Dynamic::from(stock.touched_upper_limit));
|
||||
factors.insert("hit_lower_limit".into(), Dynamic::from(stock.touched_lower_limit));
|
||||
factors.insert("listed_days".into(), Dynamic::from(stock.listed_days));
|
||||
factors.insert("at_upper_limit".into(), Dynamic::from(at_upper_limit));
|
||||
factors.insert("at_lower_limit".into(), Dynamic::from(at_lower_limit));
|
||||
@@ -1551,6 +1589,19 @@ impl PlatformExprStrategy {
|
||||
Ok(value.round().max(1.0) as usize)
|
||||
}
|
||||
|
||||
fn buy_scale(
|
||||
&self,
|
||||
ctx: &StrategyContext<'_>,
|
||||
day: &DayExpressionState,
|
||||
stock: &StockExpressionState,
|
||||
) -> Result<f64, BacktestError> {
|
||||
if self.config.buy_scale_expr.trim().is_empty() {
|
||||
return Ok(1.0);
|
||||
}
|
||||
self.eval_float(ctx, &self.config.buy_scale_expr, day, Some(stock), None)
|
||||
.map(|value| value.clamp(0.0, 1.0))
|
||||
}
|
||||
|
||||
fn stock_passes_expr(
|
||||
&self,
|
||||
ctx: &StrategyContext<'_>,
|
||||
@@ -1611,6 +1662,12 @@ impl PlatformExprStrategy {
|
||||
"stock_volume_ma60" | "volume_ma60" => Some(stock.stock_volume_ma60),
|
||||
"allow_buy" => Some(if stock.allow_buy { 1.0 } else { 0.0 }),
|
||||
"allow_sell" => Some(if stock.allow_sell { 1.0 } else { 0.0 }),
|
||||
"touched_upper_limit" | "hit_upper_limit" => {
|
||||
Some(if stock.touched_upper_limit { 1.0 } else { 0.0 })
|
||||
}
|
||||
"touched_lower_limit" | "hit_lower_limit" => {
|
||||
Some(if stock.touched_lower_limit { 1.0 } else { 0.0 })
|
||||
}
|
||||
"paused" => Some(if stock.paused { 1.0 } else { 0.0 }),
|
||||
"is_st" => Some(if stock.is_st { 1.0 } else { 0.0 }),
|
||||
"is_kcb" => Some(if stock.is_kcb { 1.0 } else { 0.0 }),
|
||||
@@ -1913,6 +1970,11 @@ impl Strategy for PlatformExprStrategy {
|
||||
if !self.stock_passes_expr(ctx, &day, &stock)? {
|
||||
continue;
|
||||
}
|
||||
let replacement_cash =
|
||||
replacement_cash * self.buy_scale(ctx, &day, &stock)?;
|
||||
if replacement_cash <= 0.0 {
|
||||
continue;
|
||||
}
|
||||
order_intents.push(OrderIntent::Value {
|
||||
symbol: symbol.clone(),
|
||||
value: replacement_cash,
|
||||
@@ -1977,9 +2039,13 @@ impl Strategy for PlatformExprStrategy {
|
||||
if !self.stock_passes_expr(ctx, &day, &stock)? {
|
||||
continue;
|
||||
}
|
||||
let buy_cash = fixed_buy_cash * self.buy_scale(ctx, &day, &stock)?;
|
||||
if buy_cash <= 0.0 {
|
||||
continue;
|
||||
}
|
||||
order_intents.push(OrderIntent::Value {
|
||||
symbol: symbol.clone(),
|
||||
value: fixed_buy_cash,
|
||||
value: buy_cash,
|
||||
reason: "periodic_rebalance_buy".to_string(),
|
||||
});
|
||||
self.project_order_value(
|
||||
@@ -1987,7 +2053,7 @@ impl Strategy for PlatformExprStrategy {
|
||||
&mut projected,
|
||||
date,
|
||||
symbol,
|
||||
fixed_buy_cash,
|
||||
buy_cash,
|
||||
&mut projected_execution_state,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ strategy("microcap_volume_trend_000852") {
|
||||
|
||||
risk.take_profit(close_rate)
|
||||
risk.stop_loss(loss_rate)
|
||||
allocation.buy_scale(touched_upper_limit ? 1.0 : trade_rate)
|
||||
|
||||
ordering.rank_by("market_cap", "asc")
|
||||
}
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
"stopLossExpr": "loss_rate",
|
||||
"takeProfitExpr": "close_rate"
|
||||
},
|
||||
"allocation": {
|
||||
"buyScaleExpr": "touched_upper_limit ? 1.0 : trade_rate"
|
||||
},
|
||||
"ordering": {
|
||||
"rankBy": "market_cap",
|
||||
"rankExpr": "",
|
||||
|
||||
Reference in New Issue
Block a user