fix: present manual as usable indicators
This commit is contained in:
@@ -59,12 +59,16 @@ pub struct ManualExample {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct StrategyAiCatalog {
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub daily_fields: Vec<String>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub benchmark_fields: Vec<String>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub indicator_factors: Vec<String>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub clickhouse_table_fields: Vec<ManualFactorSource>,
|
||||
}
|
||||
|
||||
@@ -93,7 +97,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
|
||||
"平台策略脚本采用声明式 DSL + 表达式执行模型。".to_string(),
|
||||
"支持 let 变量、fn 自定义函数、when/unless/else 条件块、数据库字段因子映射。".to_string(),
|
||||
"支持数值型和字符串型数据库因子,字符串字段可用于行业、概念、标签、板块等分类过滤。".to_string(),
|
||||
"当前 ClickHouse 默认回测主表已落地 OHLCV、市值、流通市值、换手率、有效换手率、上市天数、停牌/ST/板块、涨跌停价格、tick 触达涨跌停、常用价格/成交量均线;复杂技术指标和财务报表字段必须来自预计算因子或后续扩展函数。".to_string(),
|
||||
"当前默认回测数据已支持 OHLCV、市值、流通市值、换手率、有效换手率、上市天数、停牌/ST/板块、涨跌停价格、tick 触达涨跌停、常用价格/成交量均线;复杂技术指标和财务报表字段必须来自预计算因子或后续扩展函数。".to_string(),
|
||||
"禁止自由 Python/JavaScript 命令式语句,最终必须输出平台 DSL。".to_string(),
|
||||
],
|
||||
ai_workflows: vec![
|
||||
@@ -117,7 +121,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
|
||||
backtest_api: vec![
|
||||
ManualSection {
|
||||
title: "POST /v1/backtests".to_string(),
|
||||
detail: "创建回测。请求 JSON 字段:strategy_id、strategy_version_id、user_id、runtime、execution、strategy_source、strategy_extract、strategy_spec、strategy_json。runtime 必填 start_date、end_date、source_table,建议 source_table 使用 fi_data_center.bt_daily_features_v1;signal_symbol 是信号指数,benchmark_symbol 是绩效基准,initial_cash 是初始资金。返回 run.runId 和 running 状态。".to_string(),
|
||||
detail: "创建回测。请求 JSON 字段:strategy_id、strategy_version_id、user_id、runtime、execution、strategy_source、strategy_extract、strategy_spec、strategy_json。runtime 必填 start_date、end_date,source_table 通常由平台默认填写;signal_symbol 是信号指数,benchmark_symbol 是绩效基准,initial_cash 是初始资金。返回 run.runId 和 running 状态。".to_string(),
|
||||
},
|
||||
ManualSection {
|
||||
title: "GET /v1/backtests/{run_id}".to_string(),
|
||||
@@ -125,7 +129,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
|
||||
},
|
||||
ManualSection {
|
||||
title: "GET /v1/backtests/{run_id}/events".to_string(),
|
||||
detail: "SSE 事件流。用于实时展示快照导出、ClickHouse 查询、每日回测进度、交易日完成、失败原因等。Agent 自动化场景可不保持长连接,但前端应使用它展示进度。".to_string(),
|
||||
detail: "SSE 事件流。用于实时展示快照导出、数据查询、每日回测进度、交易日完成、失败原因等。Agent 自动化场景可不保持长连接,但前端应使用它展示进度。".to_string(),
|
||||
},
|
||||
ManualSection {
|
||||
title: "GET /v1/backtests/{run_id}/result|equity|trades|holdings".to_string(),
|
||||
@@ -211,7 +215,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
|
||||
},
|
||||
ManualSection {
|
||||
title: "期货提交校验".to_string(),
|
||||
detail: "期货订单进入撮合前会先执行账户与交易规则校验:合约必须在上市/退市日期范围内,日行情不能停牌,trading_phase 需处于 continuous/trading/open_auction/auction/call_auction/opening_auction 等可交易阶段,限价必须为正且按 futures_trading_parameters.price_tick 或日行情 price_tick 对齐,并且不能越过 upper_limit/lower_limit;随后继续检查反向挂单自成交风险、保证金和可平数量。服务层可通过 FuturesValidationConfig 分别关闭 active instrument、trading phase、limit price tick、price limit 校验,用于兼容特殊数据源,但默认全部开启。".to_string(),
|
||||
detail: "期货订单进入撮合前会先执行账户与交易规则校验:合约必须在上市/退市日期范围内,日行情不能停牌,trading_phase 需处于 continuous/trading/open_auction/auction/call_auction/opening_auction 等可交易阶段,限价必须为正且按 futures_trading_parameters.price_tick 或日行情 price_tick 对齐,并且不能越过 upper_limit/lower_limit;随后继续检查反向挂单自成交风险、保证金和可平数量。服务层可通过 FuturesValidationConfig 分别关闭 active instrument、trading phase、limit price tick、price limit 校验,用于兼容特殊数据,但默认全部开启。".to_string(),
|
||||
},
|
||||
ManualSection {
|
||||
title: "trading.rotation / order.* / cancel.* / update_universe / subscribe".to_string(),
|
||||
@@ -265,7 +269,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
|
||||
ManualField { name: "in_dynamic_universe/is_subscribed".to_string(), field_type: "bool".to_string(), detail: "当前证券是否在动态 universe 内,以及是否仍在订阅集合中。".to_string() },
|
||||
ManualField { name: "stock_ma5/stock_ma10/stock_ma20/stock_ma30".to_string(), field_type: "float".to_string(), detail: "个股价格均线内建别名,按当前交易日前 N 个已完成交易日的收盘价计算;历史窗口不足时为 NaN,比较条件会自然不通过;15 日、45 日等任意窗口请改用 sma(\"close\", n)。".to_string() },
|
||||
ManualField { name: "stock_volume_ma5/stock_volume_ma10/stock_volume_ma20/stock_volume_ma60".to_string(), field_type: "float".to_string(), detail: "个股成交量均线内建别名,按当前交易日前 N 个已完成交易日的成交量计算,不包含回测当天未来成交量;历史窗口不足时为 NaN,比较条件会自然不通过;任意窗口请改用 rolling_mean(\"volume\", n)。".to_string() },
|
||||
ManualField { name: "factors[\"field\"] / factor(\"field\")".to_string(), field_type: "float/string".to_string(), detail: "当前证券当日数据库因子。默认回测主表可用字段以手册的数据库字段清单为准;自定义因子需要预先写入 factors.csv、factors/ 或回测数据源 extra_factors。数值字段返回数字,字符串字段返回字符串。".to_string() },
|
||||
ManualField { name: "factors[\"field\"] / factor(\"field\")".to_string(), field_type: "float/string".to_string(), detail: "当前证券当日可用因子。默认可用字段以手册的“可用指标、参数和字段”清单为准;自定义因子需要预先写入策略数据或 extra_factors。数值字段返回数字,字符串字段返回字符串。".to_string() },
|
||||
ManualField { name: "listed_days".to_string(), field_type: "int".to_string(), detail: "上市天数。".to_string() },
|
||||
],
|
||||
},
|
||||
@@ -296,7 +300,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
|
||||
ManualFunction { name: "instrument/instruments/all_instruments".to_string(), signature: "ctx.instrument(symbol)".to_string(), detail: "读取证券元数据,包括名称、板块、上市日期、退市日期、最小下单量、整手、最小价位等;all_instruments 按证券代码稳定排序返回全量证券。".to_string() },
|
||||
ManualFunction { name: "active_instruments/instruments_history".to_string(), signature: "ctx.active_instruments(&[symbol])".to_string(), detail: "active_instruments 返回当前交易日已上市且未退市的证券;instruments_history 返回给定代码的历史证券记录,包含当前已退市标的,对齐 平台内核 的 active_instruments/instruments_history 能力。".to_string() },
|
||||
ManualFunction { name: "get_trading_dates/get_previous_trading_date/get_next_trading_date".to_string(), signature: "ctx.get_previous_trading_date(date, n)".to_string(), detail: "交易日历 API。get_trading_dates 返回闭区间交易日;previous/next 返回相对某日向前或向后的第 n 个交易日,当前日自身不计入。".to_string() },
|
||||
ManualFunction { name: "is_suspended/is_st_stock".to_string(), signature: "ctx.is_suspended(symbol, count)".to_string(), detail: "读取指定证券截至当前交易日最近 count 个交易日的停牌或 ST 标记,返回 bool 序列,顺序从旧到新;对应 平台内核 的 is_suspended/is_st_stock 数据源能力。".to_string() },
|
||||
ManualFunction { name: "is_suspended/is_st_stock".to_string(), signature: "ctx.is_suspended(symbol, count)".to_string(), detail: "读取指定证券截至当前交易日最近 count 个交易日的停牌或 ST 标记,返回 bool 序列,顺序从旧到新;对应平台内核的 is_suspended/is_st_stock 数据能力。".to_string() },
|
||||
ManualFunction { name: "get_price".to_string(), signature: "ctx.get_price(symbol, start_date, end_date, \"1d\" | \"1m\" | \"tick\")".to_string(), detail: "按日期区间读取统一 PriceBar 序列。日线返回 open/high/low/close/last/volume/盘口字段;分钟或 tick 返回按 timestamp 排序的 last/bid1/ask1/volume_delta/amount_delta 映射,便于服务层转成表格或前端明细。".to_string() },
|
||||
ManualFunction { name: "get_dividend / dividend_cash / has_dividend".to_string(), signature: "dividend_cash(lookback) / has_dividend(lookback)".to_string(), detail: "高级数据 风格分红 API。Rust Context 可用 ctx.get_dividend(symbol, start_date) 读取明细;平台表达式可用 dividend_cash(lookback) 汇总当前股票最近 N 个交易日现金分红,用 has_dividend(lookback) 判断是否发生分红,也支持 dividend_cash(\"600000.SH\", lookback)。".to_string() },
|
||||
ManualFunction { name: "get_split / split_ratio / has_split".to_string(), signature: "split_ratio(lookback) / has_split(lookback)".to_string(), detail: "高级数据 风格拆分/送转 API。Rust Context 可用 ctx.get_split(symbol, start_date) 读取明细;平台表达式可用 split_ratio(lookback) 计算当前股票最近 N 个交易日累计拆分比例,has_split(lookback) 判断是否发生送转。".to_string() },
|
||||
@@ -309,7 +313,7 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
|
||||
ManualFunction { name: "get_turnover_rate / turnover_rate".to_string(), signature: "turnover_rate(\"turnover\" | \"effective\", lookback=1)".to_string(), detail: "换手率 API。turnover_rate(\"turnover\") 读取 turnover_rate/turnover_ratio;turnover_rate(\"effective\") 读取 effective_turnover_rate/effective_turnover_ratio;也可传任意字段名映射数据库因子。".to_string() },
|
||||
ManualFunction { name: "get_price_change_rate / price_change_rate".to_string(), signature: "price_change_rate(lookback=1)".to_string(), detail: "涨跌幅 API,默认按日行情 close / prev_close - 1 计算,缺少行情时回退 factors 中的 price_change_rate/change_rate/pct_change。返回小数,例如 0.1 表示上涨 10%。".to_string() },
|
||||
ManualFunction { name: "get_stock_connect / stock_connect".to_string(), signature: "stock_connect(\"north_bound\" | \"south_bound\" | \"all\", lookback=1)".to_string(), detail: "陆股通/互联互通标记 API,从 stock_connect_north_bound、north_bound、stock_connect_south_bound 等因子读取,返回数值标记。".to_string() },
|
||||
ManualFunction { name: "current_performance / fundamental / financial / pit_financial".to_string(), signature: "fundamental(\"net_profit\", lookback=1)".to_string(), detail: "财务与基本面 API。它们都是对 factors 的通用映射:fundamental(field) 会依次读取 fundamental_field / fundamentals_field / field,financial(field) 读取 financial_field / financials_field / field,pit_financial(field) 读取 pit_financial_field / pit_financials_field / field。注意:当前默认 ClickHouse 回测主表尚未落地完整财务三表和估值细项,只有对应字段被预计算进 factors/extra_factors 后才会返回真实值,否则为 0。".to_string() },
|
||||
ManualFunction { name: "current_performance / fundamental / financial / pit_financial".to_string(), signature: "fundamental(\"net_profit\", lookback=1)".to_string(), detail: "财务与基本面 API。它们都是对 factors 的通用映射:fundamental(field) 会依次读取 fundamental_field / fundamentals_field / field,financial(field) 读取 financial_field / financials_field / field,pit_financial(field) 读取 pit_financial_field / pit_financials_field / field。注意:当前默认可用指标尚未包含完整财务三表和估值细项,只有对应字段被预计算进因子后才会返回真实值,否则为 0。".to_string() },
|
||||
ManualFunction { name: "get_industry / industry_code / industry_name".to_string(), signature: "industry_code(\"citics\", 1) / industry_name(\"citics\", 1)".to_string(), detail: "行业 API。industry_code 读取数值行业代码,按 industry_citics_l1、industry_citics_1、citics_industry_l1、industry_code 等别名查找;industry_name 读取字符串行业名称,按 industry_citics_l1_name、citics_industry_l1_name、industry_name 等别名查找。".to_string() },
|
||||
ManualFunction { name: "get_dominant_future / dominant_future / dominant_future_price".to_string(), signature: "dominant_future(\"IF\") / dominant_future_price(\"IF\", \"close\", lookback=1)".to_string(), detail: "主力合约 API。dominant_future 返回当前日期匹配前缀的主力期货合约代码;dominant_future_price 读取该主力合约最近 N 个交易日指定字段的最新价格。Rust Context 可用 ctx.get_dominant_future(...) 和 ctx.get_dominant_future_price(...)。".to_string() },
|
||||
ManualFunction { name: "order/order_status/order_avg_price/order_transaction_cost".to_string(), signature: "ctx.order(order_id)".to_string(), detail: "按订单 id 查询运行时订单对象,支持已结束订单和当前挂单。返回字段包括 status、filled_quantity、unfilled_quantity、avg_price、transaction_cost、symbol、side、reason;可用便捷函数读取状态、成交均价和费用,对齐 平台内核 Order 的核心属性。".to_string() },
|
||||
@@ -317,40 +321,40 @@ pub fn built_in_strategy_manual() -> StrategyAiManual {
|
||||
ManualFunction { name: "deposit_withdraw/finance_repay/management_fee".to_string(), signature: "account.deposit_withdraw(amount, receiving_days=0)".to_string(), detail: "策略账户资金动作。deposit_withdraw 正数入金、负数出金;receiving_days 大于 0 时按交易日延迟到账,并保持净值口径不把外部资金流当成收益。finance_repay 正数融资、负数还款,会同步维护 cash_liabilities。set_management_fee_rate 设置结算管理费率;普通策略可覆盖 management_fee(ctx, rate) 自定义计算器,对齐 平台内核 管理费回调能力。".to_string() },
|
||||
ManualFunction { name: "rolling_mean".to_string(), signature: "rolling_mean(\"field\", lookback)".to_string(), detail: "任意字段滚动均值,支持 volume/amount/turnover_ratio、signal_open/signal_close、benchmark_open/benchmark_close 等。个股 volume 与 close 均按当前交易日前已完成交易日计算;单只股票历史窗口不足时,在选股过滤和买入仓位表达式中按不通过/0 仓处理,不会中断整次回测。任意成交量窗口推荐用它,比如 rolling_mean(\"volume\", 15)。".to_string() },
|
||||
ManualFunction { name: "sma".to_string(), signature: "sma(\"field\", lookback)".to_string(), detail: "rolling_mean 的别名。任意价格均线窗口推荐用它,比如 sma(\"close\", 15)。".to_string() },
|
||||
ManualFunction { name: "复杂技术指标".to_string(), signature: "factor_value(\"macd\", 1) 或预计算字段".to_string(), detail: "BOLL、EMA、WMA、DEMA、TEMA、KAMA、SAR、ADX、CCI、MACD、RSI、KDJ、WILLR、ATR、ROC、TRIX、MFI、Aroon、OBV、ADL、Beta、相关系数、线性回归、标准差、方差、K 线形态等目前不是默认内建函数;可先在 ClickHouse 或 factors.csv 中预计算成数值因子,再用 factor_value/rolling_mean 读取。".to_string() },
|
||||
ManualFunction { name: "复杂技术指标".to_string(), signature: "factor_value(\"macd\", 1) 或预计算字段".to_string(), detail: "BOLL、EMA、WMA、DEMA、TEMA、KAMA、SAR、ADX、CCI、MACD、RSI、KDJ、WILLR、ATR、ROC、TRIX、MFI、Aroon、OBV、ADL、Beta、相关系数、线性回归、标准差、方差、K 线形态等目前不是默认内建函数;可先预计算成数值因子,再用 factor_value/rolling_mean 读取。".to_string() },
|
||||
ManualFunction { name: "round/floor/ceil/abs/min/max/clamp".to_string(), signature: "round(x)".to_string(), detail: "常用数值函数。".to_string() },
|
||||
ManualFunction { name: "safe_div".to_string(), signature: "safe_div(lhs, rhs, fallback)".to_string(), detail: "安全除法。".to_string() },
|
||||
ManualFunction { name: "contains/starts_with/ends_with/lower/upper/trim/strlen".to_string(), signature: "starts_with(symbol, \"60\")".to_string(), detail: "字符串辅助函数。".to_string() },
|
||||
],
|
||||
factor_sources: vec![
|
||||
ManualFactorSource {
|
||||
table: "fi_data_center.bt_daily_features_v1".to_string(),
|
||||
detail: "回测日频主表,提供价格、均线、换手率、涨跌停触达等字段。".to_string(),
|
||||
table: "股票日频指标".to_string(),
|
||||
detail: "可直接用于选股、排序、仓位和止盈止损表达式的股票日频字段。".to_string(),
|
||||
fields: vec![],
|
||||
},
|
||||
ManualFactorSource {
|
||||
table: "fi_data_center.benchmark_daily_v1".to_string(),
|
||||
detail: "指数/基准日线表。".to_string(),
|
||||
table: "指数与基准指标".to_string(),
|
||||
detail: "可用于 signal、benchmark 和市场状态判断的指数字段。".to_string(),
|
||||
fields: vec![],
|
||||
},
|
||||
ManualFactorSource {
|
||||
table: "fi_data_center.stock_indicator_factors_v1".to_string(),
|
||||
detail: "股票指标因子窄表。当前已发现的数据集主要是总市值、流通市值、换手率、有效换手率;默认回测链路已把这些指标合并到 bt_daily_features_v1。其他财务、行业、概念、陆股通、技术指标等只有落地到回测主表或 extra_factors 后才可在策略中直接使用。".to_string(),
|
||||
table: "扩展指标因子".to_string(),
|
||||
detail: "当前可用扩展指标主要包括总市值、流通市值、换手率、有效换手率;其他财务、行业、概念、陆股通、技术指标等只有落地为可用因子后才可在策略中直接使用。".to_string(),
|
||||
fields: vec![],
|
||||
},
|
||||
ManualFactorSource {
|
||||
table: "factors.csv / factors/".to_string(),
|
||||
detail: "引擎离线数据源。基础列为 date,symbol,market_cap_bn,free_float_cap_bn,pe_ttm,turnover_ratio,effective_turnover_ratio,后续 JSON 扩展列可混合数值和字符串字段,例如 {\"custom_alpha\": 7, \"industry_name\": \"电子\", \"concept\": \"ai_chip\"}。字段名会去除首尾空白并转小写;建议统一使用小写下划线。".to_string(),
|
||||
table: "自定义因子".to_string(),
|
||||
detail: "基础字段包括 date、symbol、market_cap_bn、free_float_cap_bn、pe_ttm、turnover_ratio、effective_turnover_ratio;扩展字段可混合数值和字符串,例如 {\"custom_alpha\": 7, \"industry_name\": \"电子\", \"concept\": \"ai_chip\"}。字段名建议统一使用小写下划线。".to_string(),
|
||||
fields: vec![],
|
||||
},
|
||||
ManualFactorSource {
|
||||
table: "order_book_depth.csv / order_book_depth/".to_string(),
|
||||
detail: "可选多档盘口数据源,字段为 date,symbol,timestamp,level,bid_price,bid_volume,ask_price,ask_volume。存在该数据时,期货 counterparty_offer / next_tick_best_counterparty 可按真实多档盘口逐档扫单;不存在时不会伪造 depth。".to_string(),
|
||||
table: "盘口深度参数".to_string(),
|
||||
detail: "可选字段包括 date、symbol、timestamp、level、bid_price、bid_volume、ask_price、ask_volume。存在盘口深度时,期货 counterparty_offer / next_tick_best_counterparty 可按真实多档盘口逐档扫单;不存在时不会伪造 depth。".to_string(),
|
||||
fields: vec![],
|
||||
},
|
||||
ManualFactorSource {
|
||||
table: "futures_trading_parameters.csv / futures_trading_parameters/".to_string(),
|
||||
detail: "期货交易参数数据源,字段包括 symbol,effective_date,contract_multiplier,long_margin_rate,short_margin_rate,commission_type,open_commission_ratio,close_commission_ratio,close_today_commission_ratio,price_tick。回测会按交易日自动选择不晚于当前日期的最新参数,用于保证金、手续费和限价 tick 校验。".to_string(),
|
||||
table: "期货交易参数".to_string(),
|
||||
detail: "字段包括 symbol、effective_date、contract_multiplier、long_margin_rate、short_margin_rate、commission_type、open_commission_ratio、close_commission_ratio、close_today_commission_ratio、price_tick。回测会按交易日自动选择不晚于当前日期的最新参数,用于保证金、手续费和限价 tick 校验。".to_string(),
|
||||
fields: vec![],
|
||||
},
|
||||
],
|
||||
@@ -388,24 +392,14 @@ pub fn merge_catalog_into_manual(
|
||||
catalog: &StrategyAiCatalog,
|
||||
) -> StrategyAiManual {
|
||||
for source in &mut manual.factor_sources {
|
||||
if source.table.ends_with("bt_daily_features_v1") {
|
||||
if source.table == "股票日频指标" {
|
||||
source.fields = catalog.daily_fields.clone();
|
||||
} else if source.table.ends_with("benchmark_daily_v1") {
|
||||
} else if source.table == "指数与基准指标" {
|
||||
source.fields = catalog.benchmark_fields.clone();
|
||||
} else if source.table.ends_with("stock_indicator_factors_v1") {
|
||||
} else if source.table == "扩展指标因子" {
|
||||
source.fields = catalog.indicator_factors.clone();
|
||||
}
|
||||
}
|
||||
let existing_tables = manual
|
||||
.factor_sources
|
||||
.iter()
|
||||
.map(|source| source.table.clone())
|
||||
.collect::<std::collections::HashSet<_>>();
|
||||
for source in &catalog.clickhouse_table_fields {
|
||||
if !existing_tables.contains(&source.table) {
|
||||
manual.factor_sources.push(source.clone());
|
||||
}
|
||||
}
|
||||
manual
|
||||
}
|
||||
|
||||
@@ -448,11 +442,11 @@ pub fn render_manual_markdown(manual: &StrategyAiManual) -> String {
|
||||
));
|
||||
}
|
||||
out.push('\n');
|
||||
out.push_str("## 数据库字段与因子\n");
|
||||
out.push_str("## 可用指标、参数和字段\n");
|
||||
for source in &manual.factor_sources {
|
||||
out.push_str(&format!("### `{}`\n{}\n", source.table, source.detail));
|
||||
out.push_str(&format!("### {}\n{}\n", source.table, source.detail));
|
||||
if source.fields.is_empty() {
|
||||
out.push_str("- 当前未加载字段清单\n");
|
||||
out.push_str("- 可按本节说明提供或预计算后,通过 `factor(\"字段名\")` / `factor_value(\"字段名\", lookback)` 读取。\n");
|
||||
} else {
|
||||
for field in &source.fields {
|
||||
out.push_str(&format!("- `{}`\n", field));
|
||||
|
||||
Reference in New Issue
Block a user