增强回测引擎第二版策略与快照层
This commit is contained in:
45
README.md
45
README.md
@@ -4,6 +4,7 @@
|
||||
|
||||
## 当前能力
|
||||
|
||||
- Phase 2:增加 snapshot bundle 视图与更贴近 jqdata 策略语义的动态市值带策略
|
||||
- 日频交易日历与确定性逐日回放
|
||||
- A 股日频市场快照、估值/因子快照、基准快照、候选资格标记
|
||||
- 策略接口与引擎驱动,不直接模拟 `jqdata` API
|
||||
@@ -53,19 +54,24 @@
|
||||
|
||||
## 策略实现
|
||||
|
||||
示例策略 `CnSmallCapRotationStrategy` 对应一类典型的 A 股小市值轮动逻辑:
|
||||
示例策略 `CnSmallCapRotationStrategy` 对应一类典型的 A 股小市值轮动逻辑,并在 phase 2 里更贴近原始 jqdata 语义:
|
||||
|
||||
1. 用指数点位相对基准水平切换市值带:
|
||||
- 强势区间:更偏小市值
|
||||
- 中性区间:中小市值
|
||||
- 弱势区间:偏大一些的防御市值带
|
||||
1. 用指数点位动态计算市值带:
|
||||
- `mystart = round((index_close - base_index_level) * xs + base_cap_floor)`
|
||||
- `myend = mystart + cap_span`
|
||||
2. 在当前市值带内,按总市值升序取 Top-N。
|
||||
3. 用指数短均线/长均线关系控制总仓位:
|
||||
- `1.0`: 风险偏好正常
|
||||
- `0.5`: 降半仓
|
||||
- `0.0`: 全部转现金
|
||||
4. 固定交易日频率再平衡。
|
||||
- 当 `MA(short) < MA(long) * rsi_rate` 时降到 `trade_rate`
|
||||
- 否则恢复到 `1.0`
|
||||
4. 按 `refresh_rate` 固定频率再平衡。
|
||||
5. 非再平衡日也会检查止损/止盈钩子并触发退出。
|
||||
6. 候选过滤纳入资格快照:
|
||||
- 停牌
|
||||
- ST
|
||||
- 新股
|
||||
- 科创板
|
||||
- 1 元股
|
||||
- allow_buy / allow_sell
|
||||
|
||||
这个接口不是 `jqdata` 风格的 `before_trading_start` / `handle_data` 直接脚本 API,而是:
|
||||
|
||||
@@ -79,7 +85,7 @@
|
||||
|
||||
如果原始逻辑大致是:
|
||||
|
||||
- 依据指数强弱切换可接受市值带
|
||||
- 依据指数点位动态切换可接受市值带
|
||||
- 从候选股票里选最小市值若干只
|
||||
- 按均线决定是否降仓
|
||||
- 周期性调仓
|
||||
@@ -94,7 +100,14 @@
|
||||
- `order_target_value` -> `StrategyDecision.target_weights` 由 `BrokerSimulator` 解释执行
|
||||
- 风险控制逻辑 -> `CnSmallCapRotationStrategy::gross_exposure`
|
||||
|
||||
## V1 明确简化点
|
||||
## Phase 2 新增内容
|
||||
|
||||
- `DataSet::bundle_on(date)`:引入按日 snapshot bundle 视图,方便未来直接对接 FiDataCenter / FiDataScraper 预计算快照
|
||||
- 策略诊断输出:equity curve 里新增 `diagnostics` 字段,记录市值带、候选样本、退出原因等信息
|
||||
- 候选资格快照扩展:补入 `is_kcb`、`is_one_yuan`
|
||||
- 增加策略选择行为测试
|
||||
|
||||
## V1 / V2 当前仍保留的简化点
|
||||
|
||||
下面这些是刻意保留为 v1 简化,而不是遗漏:
|
||||
|
||||
@@ -146,6 +159,16 @@ cargo build
|
||||
- 组合调仓只关心“目标持仓”和“当前持仓”的差量
|
||||
- 事件流是 append-only,适合批量写出和后处理分析
|
||||
|
||||
## 距离真实 6 年 / 5 分钟平台还差什么
|
||||
|
||||
当前仓库已经有“核心引擎 + 规则钩子 + 策略接口 + demo 回放”,但距离生产级目标还差:
|
||||
|
||||
- 真实 snapshot loader:接入 FiDataCenter / FiDataScraper 的 ClickHouse / Parquet / PostgreSQL 预计算快照,而不是 demo CSV
|
||||
- 分钟级执行层:把当前 `T-1 决策 / T 开盘执行` 扩展到更接近 `10:17 / 10:18` 的分钟级执行语义
|
||||
- 更完整的 A 股规则:复权、分红、涨跌停细分、创业板/北交所规则、成交量约束、滑点模型
|
||||
- 更高效的数据访问:按日期块和列式布局一次性加载 6 年快照,避免回测时回源拼表
|
||||
- 批量参数回测:多个参数集共享预计算快照与候选池缓存
|
||||
|
||||
## Roadmap
|
||||
|
||||
- 引入更明确的事件总线和 portfolio/account ledger 分层
|
||||
|
||||
@@ -26,7 +26,11 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
fs::create_dir_all(&output_dir)?;
|
||||
|
||||
let data = DataSet::from_csv_dir(&data_dir)?;
|
||||
let strategy = CnSmallCapRotationStrategy::new(CnSmallCapRotationConfig::demo());
|
||||
let mut strategy_cfg = CnSmallCapRotationConfig::demo();
|
||||
strategy_cfg.base_index_level = 3000.0;
|
||||
strategy_cfg.base_cap_floor = 38.0;
|
||||
strategy_cfg.cap_span = 25.0;
|
||||
let strategy = CnSmallCapRotationStrategy::new(strategy_cfg);
|
||||
let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks::default());
|
||||
let config = BacktestConfig {
|
||||
initial_cash: 1_000_000.0,
|
||||
@@ -60,17 +64,18 @@ fn workspace_root() -> PathBuf {
|
||||
|
||||
fn write_equity_curve_csv(path: &Path, rows: &[DailyEquityPoint]) -> Result<(), Box<dyn Error>> {
|
||||
let mut file = fs::File::create(path)?;
|
||||
writeln!(file, "date,cash,market_value,total_equity,benchmark_close,notes")?;
|
||||
writeln!(file, "date,cash,market_value,total_equity,benchmark_close,notes,diagnostics")?;
|
||||
for row in rows {
|
||||
writeln!(
|
||||
file,
|
||||
"{},{:.2},{:.2},{:.2},{:.2},{}",
|
||||
"{},{:.2},{:.2},{:.2},{:.2},{},{}",
|
||||
row.date,
|
||||
row.cash,
|
||||
row.market_value,
|
||||
row.total_equity,
|
||||
row.benchmark_close,
|
||||
sanitize_csv_field(&row.notes),
|
||||
sanitize_csv_field(&row.diagnostics),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -110,14 +110,31 @@ pub struct CandidateEligibility {
|
||||
pub is_paused: bool,
|
||||
pub allow_buy: bool,
|
||||
pub allow_sell: bool,
|
||||
pub is_kcb: bool,
|
||||
pub is_one_yuan: bool,
|
||||
}
|
||||
|
||||
impl CandidateEligibility {
|
||||
pub fn eligible_for_selection(&self) -> bool {
|
||||
!self.is_st && !self.is_new_listing && !self.is_paused && self.allow_buy && self.allow_sell
|
||||
!self.is_st
|
||||
&& !self.is_new_listing
|
||||
&& !self.is_paused
|
||||
&& !self.is_kcb
|
||||
&& !self.is_one_yuan
|
||||
&& self.allow_buy
|
||||
&& self.allow_sell
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DailySnapshotBundle {
|
||||
pub date: NaiveDate,
|
||||
pub benchmark: BenchmarkSnapshot,
|
||||
pub market: Vec<DailyMarketSnapshot>,
|
||||
pub factors: Vec<DailyFactorSnapshot>,
|
||||
pub candidates: Vec<CandidateEligibility>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DataSet {
|
||||
instruments: HashMap<String, Instrument>,
|
||||
@@ -246,6 +263,20 @@ impl DataSet {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn bundle_on(&self, date: NaiveDate) -> Result<DailySnapshotBundle, DataSetError> {
|
||||
let benchmark = self
|
||||
.benchmark(date)
|
||||
.cloned()
|
||||
.ok_or(DataSetError::MissingBenchmark { date })?;
|
||||
Ok(DailySnapshotBundle {
|
||||
date,
|
||||
benchmark,
|
||||
market: self.market_by_date.get(&date).cloned().unwrap_or_default(),
|
||||
factors: self.factor_by_date.get(&date).cloned().unwrap_or_default(),
|
||||
candidates: self.candidate_by_date.get(&date).cloned().unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn benchmark_closes_up_to(&self, date: NaiveDate, lookback: usize) -> Vec<f64> {
|
||||
self.calendar
|
||||
.trailing_days(date, lookback)
|
||||
@@ -342,6 +373,8 @@ fn read_candidates(path: &Path) -> Result<Vec<CandidateEligibility>, DataSetErro
|
||||
is_paused: row.parse_bool(4)?,
|
||||
allow_buy: row.parse_bool(5)?,
|
||||
allow_sell: row.parse_bool(6)?,
|
||||
is_kcb: row.parse_optional_bool(7).unwrap_or(false),
|
||||
is_one_yuan: row.parse_optional_bool(8).unwrap_or(false),
|
||||
});
|
||||
}
|
||||
Ok(snapshots)
|
||||
@@ -415,6 +448,12 @@ impl CsvRow {
|
||||
message: format!("invalid bool: {err}"),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_optional_bool(&self, index: usize) -> Option<bool> {
|
||||
self.fields
|
||||
.get(index)
|
||||
.and_then(|value| value.parse::<bool>().ok())
|
||||
}
|
||||
}
|
||||
|
||||
fn read_rows(path: &Path) -> Result<Vec<CsvRow>, DataSetError> {
|
||||
|
||||
@@ -41,6 +41,7 @@ pub struct DailyEquityPoint {
|
||||
pub total_equity: f64,
|
||||
pub benchmark_close: f64,
|
||||
pub notes: String,
|
||||
pub diagnostics: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -126,6 +127,7 @@ where
|
||||
date: execution_date,
|
||||
})?;
|
||||
let notes = decision.notes.join(" | ");
|
||||
let diagnostics = decision.diagnostics.join(" | ");
|
||||
|
||||
result.equity_curve.push(DailyEquityPoint {
|
||||
date: execution_date,
|
||||
@@ -134,6 +136,7 @@ where
|
||||
total_equity: portfolio.total_equity(),
|
||||
benchmark_close: benchmark.close,
|
||||
notes,
|
||||
diagnostics,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ pub use data::{
|
||||
CandidateEligibility,
|
||||
DailyFactorSnapshot,
|
||||
DailyMarketSnapshot,
|
||||
DailySnapshotBundle,
|
||||
DataSet,
|
||||
DataSetError,
|
||||
PriceField,
|
||||
|
||||
@@ -26,14 +26,21 @@ pub struct StrategyDecision {
|
||||
pub target_weights: BTreeMap<String, f64>,
|
||||
pub exit_symbols: BTreeSet<String>,
|
||||
pub notes: Vec<String>,
|
||||
pub diagnostics: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CnSmallCapRotationConfig {
|
||||
pub rebalance_every_n_days: usize,
|
||||
pub max_positions: usize,
|
||||
pub refresh_rate: usize,
|
||||
pub stocknum: usize,
|
||||
pub xs: f64,
|
||||
pub base_index_level: f64,
|
||||
pub base_cap_floor: f64,
|
||||
pub cap_span: f64,
|
||||
pub short_ma_days: usize,
|
||||
pub long_ma_days: usize,
|
||||
pub rsi_rate: f64,
|
||||
pub trade_rate: f64,
|
||||
pub stop_loss_pct: f64,
|
||||
pub take_profit_pct: f64,
|
||||
}
|
||||
@@ -41,10 +48,16 @@ pub struct CnSmallCapRotationConfig {
|
||||
impl CnSmallCapRotationConfig {
|
||||
pub fn demo() -> Self {
|
||||
Self {
|
||||
rebalance_every_n_days: 3,
|
||||
max_positions: 2,
|
||||
refresh_rate: 3,
|
||||
stocknum: 2,
|
||||
xs: 4.0 / 500.0,
|
||||
base_index_level: 2000.0,
|
||||
base_cap_floor: 7.0,
|
||||
cap_span: 10.0,
|
||||
short_ma_days: 3,
|
||||
long_ma_days: 5,
|
||||
rsi_rate: 1.0001,
|
||||
trade_rate: 0.5,
|
||||
stop_loss_pct: 0.08,
|
||||
take_profit_pct: 0.10,
|
||||
}
|
||||
@@ -60,7 +73,13 @@ pub struct CnSmallCapRotationStrategy {
|
||||
impl CnSmallCapRotationStrategy {
|
||||
pub fn new(config: CnSmallCapRotationConfig) -> Self {
|
||||
Self {
|
||||
selector: DynamicMarketCapBandSelector::demo(config.max_positions),
|
||||
selector: DynamicMarketCapBandSelector::new(
|
||||
config.base_index_level,
|
||||
config.base_cap_floor,
|
||||
config.cap_span,
|
||||
config.xs,
|
||||
config.stocknum,
|
||||
),
|
||||
config,
|
||||
last_gross_exposure: None,
|
||||
}
|
||||
@@ -86,12 +105,12 @@ impl CnSmallCapRotationStrategy {
|
||||
let short_ma = Self::moving_average(closes, self.config.short_ma_days);
|
||||
let long_ma = Self::moving_average(closes, self.config.long_ma_days);
|
||||
|
||||
if current >= long_ma && short_ma >= long_ma {
|
||||
if short_ma < long_ma * self.config.rsi_rate {
|
||||
self.config.trade_rate
|
||||
} else if current >= long_ma {
|
||||
1.0
|
||||
} else if current >= long_ma || short_ma >= long_ma {
|
||||
0.5
|
||||
} else {
|
||||
0.0
|
||||
self.config.trade_rate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +161,7 @@ impl Strategy for CnSmallCapRotationStrategy {
|
||||
.data
|
||||
.benchmark_closes_up_to(ctx.decision_date, self.config.long_ma_days);
|
||||
let gross_exposure = self.gross_exposure(&benchmark_closes);
|
||||
let periodic_rebalance = ctx.decision_index % self.config.rebalance_every_n_days == 0;
|
||||
let periodic_rebalance = ctx.decision_index % self.config.refresh_rate == 0;
|
||||
let exposure_changed = self
|
||||
.last_gross_exposure
|
||||
.map(|previous| (previous - gross_exposure).abs() > f64::EPSILON)
|
||||
@@ -155,6 +174,14 @@ impl Strategy for CnSmallCapRotationStrategy {
|
||||
"decision={} exec={} exposure={:.2}",
|
||||
ctx.decision_date, ctx.execution_date, gross_exposure
|
||||
)];
|
||||
let mut diagnostics = vec![format!(
|
||||
"benchmark_close={:.2} refresh_rate={} stocknum={} short_ma_days={} long_ma_days={}",
|
||||
benchmark.close,
|
||||
self.config.refresh_rate,
|
||||
self.config.stocknum,
|
||||
self.config.short_ma_days,
|
||||
self.config.long_ma_days,
|
||||
)];
|
||||
|
||||
if rebalance && gross_exposure > 0.0 {
|
||||
let selected = self.selector.select(&SelectionContext {
|
||||
@@ -165,9 +192,21 @@ impl Strategy for CnSmallCapRotationStrategy {
|
||||
|
||||
if !selected.is_empty() {
|
||||
let per_name_weight = gross_exposure / selected.len() as f64;
|
||||
for candidate in selected {
|
||||
for candidate in &selected {
|
||||
target_weights.insert(candidate.symbol.clone(), per_name_weight);
|
||||
}
|
||||
diagnostics.push(format!(
|
||||
"selected={} cap_band={:.2}-{:.2} sample={}",
|
||||
selected.len(),
|
||||
selected.first().map(|x| x.band_low).unwrap_or_default(),
|
||||
selected.first().map(|x| x.band_high).unwrap_or_default(),
|
||||
selected
|
||||
.iter()
|
||||
.take(5)
|
||||
.map(|x| format!("{}:{:.2}", x.symbol, x.market_cap_bn))
|
||||
.collect::<Vec<_>>()
|
||||
.join("|")
|
||||
));
|
||||
}
|
||||
|
||||
notes.push(format!("rebalance names={}", target_weights.len()));
|
||||
@@ -175,6 +214,10 @@ impl Strategy for CnSmallCapRotationStrategy {
|
||||
|
||||
if !exit_symbols.is_empty() {
|
||||
notes.push(format!("exit hooks={}", exit_symbols.len()));
|
||||
diagnostics.push(format!(
|
||||
"exit_symbols={}",
|
||||
exit_symbols.iter().cloned().collect::<Vec<_>>().join("|")
|
||||
));
|
||||
}
|
||||
if rebalance && gross_exposure == 0.0 {
|
||||
notes.push("risk throttle forced all-cash".to_string());
|
||||
@@ -187,6 +230,7 @@ impl Strategy for CnSmallCapRotationStrategy {
|
||||
target_weights,
|
||||
exit_symbols,
|
||||
notes,
|
||||
diagnostics,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ pub struct UniverseCandidate {
|
||||
pub symbol: String,
|
||||
pub market_cap_bn: f64,
|
||||
pub free_float_cap_bn: f64,
|
||||
pub band_low: f64,
|
||||
pub band_high: f64,
|
||||
}
|
||||
|
||||
pub struct SelectionContext<'a> {
|
||||
@@ -29,51 +31,54 @@ pub trait UniverseSelector {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DynamicMarketCapBandSelector {
|
||||
pub base_index_level: f64,
|
||||
pub bullish_threshold: f64,
|
||||
pub neutral_threshold: f64,
|
||||
pub bullish_band: (f64, f64),
|
||||
pub neutral_band: (f64, f64),
|
||||
pub defensive_band: (f64, f64),
|
||||
pub base_cap_floor: f64,
|
||||
pub cap_span: f64,
|
||||
pub xs: f64,
|
||||
pub top_n: usize,
|
||||
}
|
||||
|
||||
impl DynamicMarketCapBandSelector {
|
||||
pub fn demo(top_n: usize) -> Self {
|
||||
pub fn new(
|
||||
base_index_level: f64,
|
||||
base_cap_floor: f64,
|
||||
cap_span: f64,
|
||||
xs: f64,
|
||||
top_n: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
base_index_level: 3000.0,
|
||||
bullish_threshold: 1.02,
|
||||
neutral_threshold: 1.0,
|
||||
bullish_band: (30.0, 60.0),
|
||||
neutral_band: (40.0, 90.0),
|
||||
defensive_band: (60.0, 120.0),
|
||||
base_index_level,
|
||||
base_cap_floor,
|
||||
cap_span,
|
||||
xs,
|
||||
top_n,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn demo(top_n: usize) -> Self {
|
||||
Self::new(2000.0, 7.0, 10.0, 4.0 / 500.0, top_n)
|
||||
}
|
||||
|
||||
pub fn regime(&self, benchmark_level: f64) -> BandRegime {
|
||||
let ratio = benchmark_level / self.base_index_level;
|
||||
if ratio >= self.bullish_threshold {
|
||||
if benchmark_level >= self.base_index_level + 400.0 {
|
||||
BandRegime::Bullish
|
||||
} else if ratio >= self.neutral_threshold {
|
||||
} else if benchmark_level >= self.base_index_level {
|
||||
BandRegime::Neutral
|
||||
} else {
|
||||
BandRegime::Defensive
|
||||
}
|
||||
}
|
||||
|
||||
fn band(&self, regime: BandRegime) -> (f64, f64) {
|
||||
match regime {
|
||||
BandRegime::Bullish => self.bullish_band,
|
||||
BandRegime::Neutral => self.neutral_band,
|
||||
BandRegime::Defensive => self.defensive_band,
|
||||
}
|
||||
pub fn band_for_level(&self, benchmark_level: f64) -> (f64, f64) {
|
||||
let start = ((benchmark_level - self.base_index_level) * self.xs) + self.base_cap_floor;
|
||||
let low = start.round();
|
||||
(low, low + self.cap_span)
|
||||
}
|
||||
}
|
||||
|
||||
impl UniverseSelector for DynamicMarketCapBandSelector {
|
||||
fn select(&self, ctx: &SelectionContext<'_>) -> Vec<UniverseCandidate> {
|
||||
let regime = self.regime(ctx.benchmark.close);
|
||||
let (min_cap, max_cap) = self.band(regime);
|
||||
let _regime = self.regime(ctx.benchmark.close);
|
||||
let (min_cap, max_cap) = self.band_for_level(ctx.benchmark.close);
|
||||
|
||||
let mut selected = ctx
|
||||
.data
|
||||
@@ -94,6 +99,8 @@ impl UniverseSelector for DynamicMarketCapBandSelector {
|
||||
symbol: factor.symbol.clone(),
|
||||
market_cap_bn: factor.market_cap_bn,
|
||||
free_float_cap_bn: factor.free_float_cap_bn,
|
||||
band_low: min_cap,
|
||||
band_high: max_cap,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -23,6 +23,8 @@ fn candidate() -> CandidateEligibility {
|
||||
is_paused: false,
|
||||
allow_buy: true,
|
||||
allow_sell: true,
|
||||
is_kcb: false,
|
||||
is_one_yuan: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
34
crates/fidc-core/tests/strategy_selection.rs
Normal file
34
crates/fidc-core/tests/strategy_selection.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use chrono::NaiveDate;
|
||||
use fidc_core::{CnSmallCapRotationConfig, CnSmallCapRotationStrategy, DataSet, Strategy, StrategyContext, PortfolioState};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn strategy_emits_target_weights_and_diagnostics() {
|
||||
let data_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../data/demo");
|
||||
let data = DataSet::from_csv_dir(&data_dir).expect("demo data");
|
||||
let decision_date = NaiveDate::from_ymd_opt(2024, 1, 10).unwrap();
|
||||
let execution_date = NaiveDate::from_ymd_opt(2024, 1, 11).unwrap();
|
||||
let portfolio = PortfolioState::new(1_000_000.0);
|
||||
let mut cfg = CnSmallCapRotationConfig::demo();
|
||||
cfg.base_index_level = 3000.0;
|
||||
cfg.base_cap_floor = 38.0;
|
||||
cfg.cap_span = 25.0;
|
||||
let mut strategy = CnSmallCapRotationStrategy::new(cfg);
|
||||
|
||||
let decision = strategy
|
||||
.on_day(&StrategyContext {
|
||||
execution_date,
|
||||
decision_date,
|
||||
decision_index: 0,
|
||||
data: &data,
|
||||
portfolio: &portfolio,
|
||||
})
|
||||
.expect("decision");
|
||||
|
||||
assert!(decision.rebalance);
|
||||
assert!(!decision.target_weights.is_empty());
|
||||
assert!(decision
|
||||
.diagnostics
|
||||
.iter()
|
||||
.any(|line| line.contains("selected=")));
|
||||
}
|
||||
@@ -1,37 +1,37 @@
|
||||
date,symbol,is_st,is_new_listing,is_paused,allow_buy,allow_sell
|
||||
2024-01-02,000001.SZ,false,true,false,false,true
|
||||
2024-01-02,000002.SZ,false,false,false,true,true
|
||||
2024-01-02,000003.SZ,false,false,false,true,true
|
||||
2024-01-02,600001.SH,false,false,false,true,true
|
||||
2024-01-03,000001.SZ,false,true,false,false,true
|
||||
2024-01-03,000002.SZ,false,false,false,true,true
|
||||
2024-01-03,000003.SZ,false,false,false,true,true
|
||||
2024-01-03,600001.SH,false,false,false,true,true
|
||||
2024-01-04,000001.SZ,false,false,false,true,true
|
||||
2024-01-04,000002.SZ,false,false,false,true,true
|
||||
2024-01-04,000003.SZ,false,false,false,true,true
|
||||
2024-01-04,600001.SH,false,false,false,true,true
|
||||
2024-01-05,000001.SZ,false,false,false,true,true
|
||||
2024-01-05,000002.SZ,false,false,false,true,true
|
||||
2024-01-05,000003.SZ,false,false,false,true,true
|
||||
2024-01-05,600001.SH,false,false,false,true,true
|
||||
2024-01-08,000001.SZ,false,false,false,true,true
|
||||
2024-01-08,000002.SZ,false,false,false,true,true
|
||||
2024-01-08,000003.SZ,false,false,false,true,true
|
||||
2024-01-08,600001.SH,false,false,false,true,true
|
||||
2024-01-09,000001.SZ,false,false,false,true,true
|
||||
2024-01-09,000002.SZ,false,false,false,true,true
|
||||
2024-01-09,000003.SZ,false,false,false,true,true
|
||||
2024-01-09,600001.SH,false,false,false,true,true
|
||||
2024-01-10,000001.SZ,false,false,false,true,true
|
||||
2024-01-10,000002.SZ,false,false,false,true,true
|
||||
2024-01-10,000003.SZ,false,false,false,true,true
|
||||
2024-01-10,600001.SH,false,false,false,true,true
|
||||
2024-01-11,000001.SZ,false,false,false,true,true
|
||||
2024-01-11,000002.SZ,false,false,false,true,true
|
||||
2024-01-11,000003.SZ,false,false,false,true,true
|
||||
2024-01-11,600001.SH,false,false,true,false,false
|
||||
2024-01-12,000001.SZ,false,false,false,true,true
|
||||
2024-01-12,000002.SZ,false,false,false,true,true
|
||||
2024-01-12,000003.SZ,false,false,false,true,true
|
||||
2024-01-12,600001.SH,false,false,false,true,true
|
||||
date,symbol,is_st,is_new_listing,is_paused,allow_buy,allow_sell,is_kcb,is_one_yuan
|
||||
2024-01-02,000001.SZ,false,true,false,false,true,false,false
|
||||
2024-01-02,000002.SZ,false,false,false,true,true,false,false
|
||||
2024-01-02,000003.SZ,false,false,false,true,true,false,false
|
||||
2024-01-02,600001.SH,false,false,false,true,true,false,false
|
||||
2024-01-03,000001.SZ,false,true,false,false,true,false,false
|
||||
2024-01-03,000002.SZ,false,false,false,true,true,false,false
|
||||
2024-01-03,000003.SZ,false,false,false,true,true,false,false
|
||||
2024-01-03,600001.SH,false,false,false,true,true,false,false
|
||||
2024-01-04,000001.SZ,false,false,false,true,true,false,false
|
||||
2024-01-04,000002.SZ,false,false,false,true,true,false,false
|
||||
2024-01-04,000003.SZ,false,false,false,true,true,false,false
|
||||
2024-01-04,600001.SH,false,false,false,true,true,false,false
|
||||
2024-01-05,000001.SZ,false,false,false,true,true,false,false
|
||||
2024-01-05,000002.SZ,false,false,false,true,true,false,false
|
||||
2024-01-05,000003.SZ,false,false,false,true,true,false,false
|
||||
2024-01-05,600001.SH,false,false,false,true,true,false,false
|
||||
2024-01-08,000001.SZ,false,false,false,true,true,false,false
|
||||
2024-01-08,000002.SZ,false,false,false,true,true,false,false
|
||||
2024-01-08,000003.SZ,false,false,false,true,true,false,false
|
||||
2024-01-08,600001.SH,false,false,false,true,true,false,false
|
||||
2024-01-09,000001.SZ,false,false,false,true,true,false,false
|
||||
2024-01-09,000002.SZ,false,false,false,true,true,false,false
|
||||
2024-01-09,000003.SZ,false,false,false,true,true,false,false
|
||||
2024-01-09,600001.SH,false,false,false,true,true,false,false
|
||||
2024-01-10,000001.SZ,false,false,false,true,true,false,false
|
||||
2024-01-10,000002.SZ,false,false,false,true,true,false,false
|
||||
2024-01-10,000003.SZ,false,false,false,true,true,false,false
|
||||
2024-01-10,600001.SH,false,false,false,true,true,false,false
|
||||
2024-01-11,000001.SZ,false,false,false,true,true,false,false
|
||||
2024-01-11,000002.SZ,false,false,false,true,true,false,false
|
||||
2024-01-11,000003.SZ,false,false,false,true,true,false,false
|
||||
2024-01-11,600001.SH,false,false,true,false,false,false,false
|
||||
2024-01-12,000001.SZ,false,false,false,true,true,false,false
|
||||
2024-01-12,000002.SZ,false,false,false,true,true,false,false
|
||||
2024-01-12,000003.SZ,false,false,false,true,true,false,false
|
||||
2024-01-12,600001.SH,false,false,false,true,true,false,false
|
||||
|
||||
|
Reference in New Issue
Block a user