调整回测撮合为分钟线执行价语义
This commit is contained in:
@@ -73,10 +73,9 @@ pub enum MatchingType {
|
||||
OpenAuction,
|
||||
CurrentBarClose,
|
||||
NextBarOpen,
|
||||
NextMinuteLast,
|
||||
NextMinuteBestOwn,
|
||||
NextMinuteBestCounterparty,
|
||||
CounterpartyOffer,
|
||||
MinuteLast,
|
||||
MinuteBestOwn,
|
||||
MinuteBestCounterparty,
|
||||
Vwap,
|
||||
Twap,
|
||||
}
|
||||
@@ -563,7 +562,7 @@ where
|
||||
matching_type: MatchingType,
|
||||
) -> Option<f64> {
|
||||
let raw_price = match matching_type {
|
||||
MatchingType::NextMinuteBestOwn => match side {
|
||||
MatchingType::MinuteBestOwn => match side {
|
||||
OrderSide::Buy => {
|
||||
if quote.bid1.is_finite() && quote.bid1 > 0.0 {
|
||||
Some(quote.bid1)
|
||||
@@ -587,13 +586,11 @@ where
|
||||
}
|
||||
}
|
||||
},
|
||||
MatchingType::NextMinuteBestCounterparty | MatchingType::CounterpartyOffer => {
|
||||
match side {
|
||||
OrderSide::Buy => quote.buy_price(),
|
||||
OrderSide::Sell => quote.sell_price(),
|
||||
}
|
||||
}
|
||||
MatchingType::NextMinuteLast | MatchingType::Vwap | MatchingType::Twap => {
|
||||
MatchingType::MinuteBestCounterparty => match side {
|
||||
OrderSide::Buy => quote.buy_price(),
|
||||
OrderSide::Sell => quote.sell_price(),
|
||||
},
|
||||
MatchingType::MinuteLast | MatchingType::Vwap | MatchingType::Twap => {
|
||||
if quote.last_price.is_finite() && quote.last_price > 0.0 {
|
||||
Some(quote.last_price)
|
||||
} else {
|
||||
@@ -4998,7 +4995,7 @@ where
|
||||
quotes,
|
||||
start_cursor,
|
||||
end_cursor,
|
||||
matching_type == MatchingType::NextMinuteLast && start_cursor.is_some(),
|
||||
matching_type == MatchingType::MinuteLast && start_cursor.is_some(),
|
||||
)),
|
||||
});
|
||||
}
|
||||
@@ -5056,12 +5053,12 @@ where
|
||||
let quote_quantity_limited =
|
||||
self.quote_quantity_limited_for_window(matching_type, start_cursor, end_cursor);
|
||||
let lot = round_lot.max(1);
|
||||
let exact_time_order_quote = matching_type != MatchingType::NextMinuteLast
|
||||
let exact_time_order_quote = matching_type != MatchingType::MinuteLast
|
||||
&& start_cursor.is_some()
|
||||
&& end_cursor.is_some()
|
||||
&& start_cursor == end_cursor;
|
||||
let use_decision_time_quote = start_cursor.is_some()
|
||||
&& (matching_type == MatchingType::NextMinuteLast || exact_time_order_quote);
|
||||
&& (matching_type == MatchingType::MinuteLast || exact_time_order_quote);
|
||||
let eligible_quotes: Vec<&IntradayExecutionQuote> = if use_decision_time_quote {
|
||||
self.latest_known_quote_at_or_before(
|
||||
quotes,
|
||||
@@ -5290,7 +5287,7 @@ where
|
||||
}
|
||||
|
||||
fn quote_quantity_limited(&self, matching_type: MatchingType) -> bool {
|
||||
self.volume_limit || self.liquidity_limit || matching_type != MatchingType::NextMinuteLast
|
||||
self.volume_limit || self.liquidity_limit || matching_type != MatchingType::MinuteLast
|
||||
}
|
||||
|
||||
fn quote_quantity_limited_for_window(
|
||||
@@ -5321,7 +5318,7 @@ fn matching_type_from_price_field(field: PriceField) -> MatchingType {
|
||||
PriceField::DayOpen => MatchingType::OpenAuction,
|
||||
PriceField::Open => MatchingType::NextBarOpen,
|
||||
PriceField::Close => MatchingType::CurrentBarClose,
|
||||
PriceField::Last => MatchingType::NextMinuteLast,
|
||||
PriceField::Last => MatchingType::MinuteLast,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5487,17 +5484,17 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_minute_last_without_volume_or_liquidity_limit_does_not_cap_quote_quantity() {
|
||||
fn minute_last_without_volume_or_liquidity_limit_does_not_cap_quote_quantity() {
|
||||
let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks)
|
||||
.with_volume_limit(false)
|
||||
.with_liquidity_limit(false);
|
||||
|
||||
assert!(!broker.quote_quantity_limited(MatchingType::NextMinuteLast));
|
||||
assert!(broker.quote_quantity_limited(MatchingType::CounterpartyOffer));
|
||||
assert!(!broker.quote_quantity_limited(MatchingType::MinuteLast));
|
||||
assert!(broker.quote_quantity_limited(MatchingType::MinuteBestCounterparty));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_minute_last_keeps_quote_quantity_cap_when_limits_enabled() {
|
||||
fn minute_last_keeps_quote_quantity_cap_when_limits_enabled() {
|
||||
let volume_limited =
|
||||
BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks)
|
||||
.with_volume_limit(true)
|
||||
@@ -5507,8 +5504,8 @@ mod tests {
|
||||
.with_volume_limit(false)
|
||||
.with_liquidity_limit(true);
|
||||
|
||||
assert!(volume_limited.quote_quantity_limited(MatchingType::NextMinuteLast));
|
||||
assert!(liquidity_limited.quote_quantity_limited(MatchingType::NextMinuteLast));
|
||||
assert!(volume_limited.quote_quantity_limited(MatchingType::MinuteLast));
|
||||
assert!(liquidity_limited.quote_quantity_limited(MatchingType::MinuteLast));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -5558,7 +5555,7 @@ mod tests {
|
||||
PriceField::Last,
|
||||
)
|
||||
.with_intraday_execution_start_time(date.and_hms_opt(10, 18, 0).unwrap().time())
|
||||
.with_matching_type(MatchingType::NextMinuteLast)
|
||||
.with_matching_type(MatchingType::MinuteLast)
|
||||
.with_slippage_model(SlippageModel::PriceRatio(0.001))
|
||||
.with_volume_limit(false)
|
||||
.with_liquidity_limit(false);
|
||||
@@ -5751,7 +5748,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_minute_last_execution_uses_latest_quote_before_decision_time() {
|
||||
fn minute_last_execution_uses_latest_quote_before_decision_time() {
|
||||
let date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date");
|
||||
let broker = BrokerSimulator::new_with_execution_price(
|
||||
ChinaAShareCostModel::default(),
|
||||
@@ -5772,7 +5769,7 @@ mod tests {
|
||||
&snapshot,
|
||||
&[quote],
|
||||
OrderSide::Sell,
|
||||
MatchingType::NextMinuteLast,
|
||||
MatchingType::MinuteLast,
|
||||
Some(decision_time),
|
||||
Some(decision_time),
|
||||
200,
|
||||
@@ -6026,7 +6023,7 @@ mod tests {
|
||||
&snapshot,
|
||||
&[quote],
|
||||
OrderSide::Buy,
|
||||
MatchingType::NextMinuteLast,
|
||||
MatchingType::MinuteLast,
|
||||
Some(start),
|
||||
None,
|
||||
100,
|
||||
@@ -6103,7 +6100,7 @@ mod tests {
|
||||
&snapshot,
|
||||
&[quote],
|
||||
OrderSide::Sell,
|
||||
MatchingType::NextMinuteLast,
|
||||
MatchingType::MinuteLast,
|
||||
Some(start),
|
||||
None,
|
||||
100,
|
||||
|
||||
@@ -1444,7 +1444,7 @@ where
|
||||
let snapshot = self.data.market(date, &intent.symbol);
|
||||
if matches!(
|
||||
self.broker.matching_type(),
|
||||
MatchingType::NextMinuteBestCounterparty | MatchingType::CounterpartyOffer
|
||||
MatchingType::MinuteBestCounterparty
|
||||
) {
|
||||
let depth = self.data.order_book_depth_on(date, &intent.symbol);
|
||||
if !depth.is_empty() {
|
||||
@@ -1454,7 +1454,7 @@ where
|
||||
let quotes = self.data.execution_quotes_on(date, &intent.symbol);
|
||||
for quote in quotes {
|
||||
let price = match self.broker.matching_type() {
|
||||
MatchingType::NextMinuteBestOwn => match intent.side() {
|
||||
MatchingType::MinuteBestOwn => match intent.side() {
|
||||
OrderSide::Buy => {
|
||||
if quote.bid1.is_finite() && quote.bid1 > 0.0 {
|
||||
quote.bid1
|
||||
@@ -1470,12 +1470,10 @@ where
|
||||
}
|
||||
}
|
||||
},
|
||||
MatchingType::NextMinuteBestCounterparty | MatchingType::CounterpartyOffer => {
|
||||
match intent.side() {
|
||||
OrderSide::Buy => quote.buy_price().unwrap_or(quote.last_price),
|
||||
OrderSide::Sell => quote.sell_price().unwrap_or(quote.last_price),
|
||||
}
|
||||
}
|
||||
MatchingType::MinuteBestCounterparty => match intent.side() {
|
||||
OrderSide::Buy => quote.buy_price().unwrap_or(quote.last_price),
|
||||
OrderSide::Sell => quote.sell_price().unwrap_or(quote.last_price),
|
||||
},
|
||||
_ => quote.last_price,
|
||||
};
|
||||
if let Some(snapshot) = snapshot {
|
||||
|
||||
@@ -286,7 +286,7 @@ fn band_low(index_close) {
|
||||
stamp_tax_rate_after_change: None,
|
||||
strict_value_budget: false,
|
||||
slippage_model: SlippageModel::None,
|
||||
matching_type: MatchingType::NextMinuteLast,
|
||||
matching_type: MatchingType::MinuteLast,
|
||||
quote_quantity_limit: true,
|
||||
current_day_precomputed_factors: false,
|
||||
prefer_precomputed_rolling_factors: false,
|
||||
@@ -1342,7 +1342,7 @@ impl PlatformExprStrategy {
|
||||
let last =
|
||||
|| (quote.last_price.is_finite() && quote.last_price > 0.0).then_some(quote.last_price);
|
||||
match self.config.matching_type {
|
||||
MatchingType::NextMinuteBestOwn => match side {
|
||||
MatchingType::MinuteBestOwn => match side {
|
||||
OrderSide::Buy => (quote.bid1.is_finite() && quote.bid1 > 0.0)
|
||||
.then_some(quote.bid1)
|
||||
.or_else(last),
|
||||
@@ -1350,17 +1350,16 @@ impl PlatformExprStrategy {
|
||||
.then_some(quote.ask1)
|
||||
.or_else(last),
|
||||
},
|
||||
MatchingType::NextMinuteBestCounterparty | MatchingType::CounterpartyOffer => {
|
||||
match side {
|
||||
MatchingType::MinuteBestCounterparty => match side {
|
||||
OrderSide::Buy => quote.buy_price(),
|
||||
OrderSide::Sell => quote.sell_price(),
|
||||
},
|
||||
MatchingType::MinuteLast | MatchingType::Vwap | MatchingType::Twap => {
|
||||
last().or_else(|| match side {
|
||||
OrderSide::Buy => quote.buy_price(),
|
||||
OrderSide::Sell => quote.sell_price(),
|
||||
}
|
||||
})
|
||||
}
|
||||
MatchingType::NextMinuteLast | MatchingType::Vwap | MatchingType::Twap => last()
|
||||
.or_else(|| match side {
|
||||
OrderSide::Buy => quote.buy_price(),
|
||||
OrderSide::Sell => quote.sell_price(),
|
||||
}),
|
||||
_ => match side {
|
||||
OrderSide::Buy => quote.buy_price(),
|
||||
OrderSide::Sell => quote.sell_price(),
|
||||
@@ -8423,7 +8422,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn platform_aiquant_next_minute_last_projection_uses_last_quote_for_buy_budget() {
|
||||
fn platform_aiquant_minute_last_projection_uses_last_quote_for_buy_budget() {
|
||||
let date = d(2023, 5, 4);
|
||||
let symbol = "000782.SZ";
|
||||
let data = DataSet::from_components_with_actions_and_quotes(
|
||||
@@ -8530,7 +8529,7 @@ mod tests {
|
||||
cfg.minimum_commission = Some(5.0);
|
||||
cfg.strict_value_budget = true;
|
||||
cfg.slippage_model = SlippageModel::PriceRatio(0.002);
|
||||
cfg.matching_type = MatchingType::NextMinuteLast;
|
||||
cfg.matching_type = MatchingType::MinuteLast;
|
||||
cfg.quote_quantity_limit = false;
|
||||
cfg.intraday_execution_time = Some(NaiveTime::from_hms_opt(10, 40, 0).expect("time"));
|
||||
let strategy = PlatformExprStrategy::new(cfg);
|
||||
@@ -8688,7 +8687,7 @@ mod tests {
|
||||
cfg.minimum_commission = Some(5.0);
|
||||
cfg.strict_value_budget = true;
|
||||
cfg.slippage_model = SlippageModel::PriceRatio(0.002);
|
||||
cfg.matching_type = MatchingType::NextMinuteLast;
|
||||
cfg.matching_type = MatchingType::MinuteLast;
|
||||
cfg.quote_quantity_limit = false;
|
||||
cfg.intraday_execution_time = Some(NaiveTime::from_hms_opt(10, 40, 0).expect("time"));
|
||||
let strategy = PlatformExprStrategy::new(cfg);
|
||||
|
||||
@@ -423,10 +423,9 @@ fn parse_matching_type(value: Option<&str>) -> Option<MatchingType> {
|
||||
"open_auction" => Some(MatchingType::OpenAuction),
|
||||
"current_bar_close" => Some(MatchingType::CurrentBarClose),
|
||||
"next_bar_open" => Some(MatchingType::NextBarOpen),
|
||||
"next_minute_last" => Some(MatchingType::NextMinuteLast),
|
||||
"next_minute_best_own" => Some(MatchingType::NextMinuteBestOwn),
|
||||
"next_minute_best_counterparty" => Some(MatchingType::NextMinuteBestCounterparty),
|
||||
"counterparty_offer" => Some(MatchingType::CounterpartyOffer),
|
||||
"minute_last" => Some(MatchingType::MinuteLast),
|
||||
"minute_best_own" => Some(MatchingType::MinuteBestOwn),
|
||||
"minute_best_counterparty" => Some(MatchingType::MinuteBestCounterparty),
|
||||
"vwap" => Some(MatchingType::Vwap),
|
||||
"twap" => Some(MatchingType::Twap),
|
||||
_ => None,
|
||||
@@ -1655,7 +1654,7 @@ mod tests {
|
||||
fn parses_execution_slippage_overrides_into_platform_config() {
|
||||
let spec = serde_json::json!({
|
||||
"execution": {
|
||||
"matchingType": "next_minute_last",
|
||||
"matchingType": "minute_last",
|
||||
"slippageModel": "price_ratio",
|
||||
"slippageValue": 0.001,
|
||||
"strictValueBudget": true
|
||||
@@ -1671,7 +1670,7 @@ mod tests {
|
||||
|
||||
let cfg = platform_expr_config_from_value("", "", &spec).expect("config");
|
||||
|
||||
assert_eq!(cfg.matching_type, MatchingType::NextMinuteLast);
|
||||
assert_eq!(cfg.matching_type, MatchingType::MinuteLast);
|
||||
assert_eq!(cfg.slippage_model, SlippageModel::PriceRatio(0.001));
|
||||
assert!(cfg.strict_value_budget);
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
|
||||
},
|
||||
ManualSection {
|
||||
title: "execution.matching_type / execution.slippage".to_string(),
|
||||
detail: "设置撮合模式和滑点。使用 execution.matching_type(\"next_minute_last\" | \"next_minute_best_own\" | \"next_minute_best_counterparty\" | \"counterparty_offer\" | \"vwap\" | \"current_bar_close\" | \"next_bar_open\" | \"open_auction\")。next_minute_last 使用分钟执行价 last_price;next_minute_best_own / next_minute_best_counterparty 会按 L1 买一卖一近似 平台内核 的最优价语义;counterparty_offer 在存在 order_book_depth 多档盘口数据时会按真实档位逐档扫单并计算加权成交价,不存在 depth 时回退 L1 对手方报价;vwap 会在盘中执行价链路上聚合多笔成交为单条 VWAP 成交;next_bar_open 使用决策日信号并在下一可交易日开盘撮合,禁止把执行日 open/high/low/close 解释为下单前已知数据;open_auction 使用当日集合竞价开盘价 day_open 进行撮合,且不额外施加滑点,并按竞价成交量而不是盘口一档流动性限制成交;滑点支持 execution.slippage(\"none\") / execution.slippage(\"price_ratio\", 0.001) / execution.slippage(\"tick_size\", 1) / execution.slippage(\"limit_price\"),其中 limit_price 会在限价单成交时按挂单价模拟 平台内核 的最坏成交价。".to_string(),
|
||||
detail: "设置撮合模式和滑点。使用 execution.matching_type(\"minute_last\" | \"minute_best_own\" | \"minute_best_counterparty\" | \"vwap\" | \"twap\" | \"current_bar_close\" | \"next_bar_open\" | \"open_auction\")。minute_last 使用分钟执行价 last_price;minute_best_own 使用己方一档报价,minute_best_counterparty 使用对手方一档报价,存在 order_book_depth 多档盘口数据时可按真实档位逐档扫单;vwap/twap 在分钟执行价窗口内聚合成交;next_bar_open 使用决策日信号并在下一可交易日开盘撮合,禁止把执行日 open/high/low/close 解释为下单前已知数据;open_auction 使用当日集合竞价开盘价 day_open 进行撮合,且不额外施加滑点,并按竞价成交量而不是盘口一档流动性限制成交;滑点支持 execution.slippage(\"none\") / execution.slippage(\"price_ratio\", 0.001) / execution.slippage(\"tick_size\", 1) / execution.slippage(\"limit_price\"),其中 limit_price 会在限价单成交时按挂单价模拟 平台内核 的最坏成交价。".to_string(),
|
||||
},
|
||||
ManualSection {
|
||||
title: "期货提交校验".to_string(),
|
||||
@@ -360,7 +360,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
|
||||
},
|
||||
ManualFactorSource {
|
||||
table: "盘口深度参数".to_string(),
|
||||
detail: "可选字段包括 date、symbol、timestamp、level、bid_price、bid_volume、ask_price、ask_volume。存在盘口深度时,期货 counterparty_offer / next_minute_best_counterparty 可按真实多档盘口逐档扫单;不存在时不会伪造 depth。".to_string(),
|
||||
detail: "可选字段包括 date、symbol、timestamp、level、bid_price、bid_volume、ask_price、ask_volume。存在盘口深度时,期货 minute_best_counterparty 可按真实多档盘口逐档扫单;不存在时不会伪造 depth。".to_string(),
|
||||
fields: vec![],
|
||||
},
|
||||
ManualFactorSource {
|
||||
@@ -384,7 +384,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
|
||||
},
|
||||
ManualExample {
|
||||
title: "分钟执行价撮合 + 最小价位滑点".to_string(),
|
||||
code: "execution.matching_type(\"next_minute_last\")\nexecution.slippage(\"tick_size\", 1)".to_string(),
|
||||
code: "execution.matching_type(\"minute_last\")\nexecution.slippage(\"tick_size\", 1)".to_string(),
|
||||
},
|
||||
ManualExample {
|
||||
title: "动态 universe 和订阅".to_string(),
|
||||
@@ -518,7 +518,7 @@ pub fn build_generation_prompt(
|
||||
prompt.push_str("- 生成的代码必须能转换为 strategy_spec 并提交 POST /v1/backtests。\n");
|
||||
prompt.push_str("- 不要使用手册未列出的字段、函数或外部平台 API 名称。\n\n");
|
||||
prompt.push_str("只允许使用这些可编译语句:market、benchmark、signal、rebalance.every_days(...).at([...])、selection.limit、selection.market_cap_band、filter.stock_ma、filter.stock_expr、ordering.rank_by、ordering.rank_expr、allocation.buy_scale、risk.stop_loss、risk.take_profit、risk.index_exposure、execution.matching_type、execution.slippage、universe.exclude。禁止输出 filter(...)、rank(...)、select.top(...)、weight.equal()、sell_rule(...)、backtest(...)、risk.max_position(...) 这类未支持伪语法。\n");
|
||||
prompt.push_str("参数形态必须严格:selection.market_cap_band 必须写 field=\"market_cap\" 或 field=\"free_float_cap\", lower=..., upper=...;禁止使用 float_market_cap;禁止使用 ma60、stock_ma60、signal_ma60、benchmark_ma60,60日价格均线写 rolling_mean(\"close\", 60) 或 ma(\"close\", 60),任意窗口均量写 rolling_mean(\"volume\", n) 或 vma(n);不要生成 fn score() 这类零参数函数,股票字段排序直接写在 ordering.rank_expr 内或用带参数函数;布尔字段按布尔使用,写 !is_st、!paused、!at_upper_limit、!at_lower_limit,不要写 is_st == 0;risk.index_exposure 只能传一个数值表达式,不要使用 risk.exposure;完整三元表达式 cond ? a : b 可以使用,但不得输出残缺问号/冒号片段;execution.matching_type 只能取 next_minute_last、next_minute_best_own、next_minute_best_counterparty、counterparty_offer、vwap、current_bar_close、next_bar_open、open_auction;next_bar_open 只能使用决策日信号,不能把执行日价格当作下单前信息;execution.slippage 必须写 execution.slippage(\"none\") 或 execution.slippage(\"price_ratio\", 0.001)。\n");
|
||||
prompt.push_str("参数形态必须严格:selection.market_cap_band 必须写 field=\"market_cap\" 或 field=\"free_float_cap\", lower=..., upper=...;禁止使用 float_market_cap;禁止使用 ma60、stock_ma60、signal_ma60、benchmark_ma60,60日价格均线写 rolling_mean(\"close\", 60) 或 ma(\"close\", 60),任意窗口均量写 rolling_mean(\"volume\", n) 或 vma(n);不要生成 fn score() 这类零参数函数,股票字段排序直接写在 ordering.rank_expr 内或用带参数函数;布尔字段按布尔使用,写 !is_st、!paused、!at_upper_limit、!at_lower_limit,不要写 is_st == 0;risk.index_exposure 只能传一个数值表达式,不要使用 risk.exposure;完整三元表达式 cond ? a : b 可以使用,但不得输出残缺问号/冒号片段;execution.matching_type 只能取 minute_last、minute_best_own、minute_best_counterparty、vwap、twap、current_bar_close、next_bar_open、open_auction;next_bar_open 只能使用决策日信号,不能把执行日价格当作下单前信息;execution.slippage 必须写 execution.slippage(\"none\") 或 execution.slippage(\"price_ratio\", 0.001)。\n");
|
||||
prompt.push_str("回测成功但 tradeCount=0 或 holdingCount=0 是无效策略;第一版必须保持稳定买入覆盖率,复杂因子只能在后续优化中逐步加严。\n");
|
||||
prompt.push_str("可参考但不要照抄的最小模板,回复时不要包含 ``` 代码围栏:\nstrategy(\"cn_a_smallcap_factor_rotation\") {\nmarket(\"CN_A\")\nbenchmark(\"000852.SH\")\nsignal(\"000001.SH\")\nrebalance.every_days(5).at([\"10:18\"])\nselection.limit(40)\nselection.market_cap_band(field=\"market_cap\", lower=0, upper=1000)\nfilter.stock_expr(listed_days >= 60 && !is_st && !paused && close > 2 && !at_upper_limit && !at_lower_limit)\nordering.rank_by(\"market_cap\", \"asc\")\nallocation.buy_scale(1.0)\nrisk.index_exposure(1.0)\nrisk.stop_loss(holding_return < -0.08)\nexecution.slippage(\"price_ratio\", 0.001)\n}\n\n");
|
||||
prompt.push_str("用户目标:\n");
|
||||
|
||||
Reference in New Issue
Block a user