清理CSV回测demo入口

This commit is contained in:
boris
2026-07-03 23:40:33 +08:00
parent fea09ce93c
commit 73dd006bb2
18 changed files with 593 additions and 1711 deletions
@@ -1,93 +0,0 @@
use fidc_core::DataSet;
use std::fs;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
fn temp_dir() -> PathBuf {
let uniq = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("clock")
.as_nanos();
let dir = std::env::temp_dir().join(format!("fidc-bt-partitioned-{uniq}"));
fs::create_dir_all(&dir).expect("mkdir temp");
dir
}
#[test]
fn can_load_partitioned_snapshot_dir() {
let dir = temp_dir();
fs::create_dir_all(dir.join("benchmark/2024/01")).unwrap();
fs::create_dir_all(dir.join("market/2024/01")).unwrap();
fs::create_dir_all(dir.join("factors/2024/01")).unwrap();
fs::create_dir_all(dir.join("candidates/2024/01")).unwrap();
fs::create_dir_all(dir.join("corporate_actions/2024/01")).unwrap();
fs::write(
dir.join("instruments.csv"),
"symbol,name,board,round_lot,listed_at,delisted_at,status\n000001.SZ,PingAn,SZ,100,2020-01-01,,active\n",
)
.unwrap();
fs::write(
dir.join("benchmark/2024/01/2024-01-02.csv"),
"date,benchmark,open,close,prev_close,volume\n2024-01-02,CSI300.DEMO,2990,3000,2980,100000000\n",
)
.unwrap();
fs::write(
dir.join("market/2024/01/2024-01-02.csv"),
"date,symbol,open,high,low,close,prev_close,volume,paused,upper_limit,lower_limit,day_open,last_price,bid1,ask1,price_tick\n2024-01-02,000001.SZ,10,10.5,9.9,10.2,10,100000,false,11,9,10.1,10.15,10.14,10.16,0.01\n",
)
.unwrap();
fs::write(
dir.join("factors/2024/01/2024-01-02.csv"),
"date,symbol,market_cap_bn,free_float_cap_bn,pe_ttm,turnover_ratio,effective_turnover_ratio\n2024-01-02,000001.SZ,40,35,12,3.2,2.1\n",
)
.unwrap();
fs::write(
dir.join("candidates/2024/01/2024-01-02.csv"),
"date,symbol,is_st,is_new_listing,is_paused,allow_buy,allow_sell,is_kcb,is_one_yuan\n2024-01-02,000001.SZ,false,false,false,true,true,false,false\n",
)
.unwrap();
fs::write(
dir.join("corporate_actions/2024/01/2024-01-02.csv"),
"date,symbol,payable_date,share_cash,share_bonus,share_gift,issue_quantity,issue_price,reform,adjust_factor\n2024-01-02,000001.SZ,2024-01-05,0.5,0.1,0.0,0,0,false,1.05\n",
)
.unwrap();
let data = DataSet::from_partitioned_dir(&dir).expect("partitioned dataset");
assert_eq!(data.benchmark_code(), "CSI300.DEMO");
assert!(
data.market_snapshots_on(chrono::NaiveDate::from_ymd_opt(2024, 1, 2).unwrap())
.len()
== 1
);
let market_rows =
data.market_snapshots_on(chrono::NaiveDate::from_ymd_opt(2024, 1, 2).unwrap());
let snapshot = market_rows.first().expect("market snapshot");
assert_eq!(snapshot.day_open, 10.1);
assert_eq!(snapshot.last_price, 10.15);
assert_eq!(snapshot.price_tick, 0.01);
assert_eq!(
data.instruments()
.get("000001.SZ")
.expect("instrument")
.round_lot,
100
);
assert_eq!(
data.instruments()
.get("000001.SZ")
.expect("instrument")
.listed_at,
Some(chrono::NaiveDate::from_ymd_opt(2020, 1, 1).unwrap())
);
let actions = data.corporate_actions_on(chrono::NaiveDate::from_ymd_opt(2024, 1, 2).unwrap());
assert_eq!(actions.len(), 1);
assert_eq!(
actions[0].payable_date,
Some(chrono::NaiveDate::from_ymd_opt(2024, 1, 5).unwrap())
);
assert!((actions[0].share_cash - 0.5).abs() < 1e-9);
assert!((actions[0].split_ratio() - 1.1).abs() < 1e-9);
let _ = fs::remove_dir_all(&dir);
}
+572 -6
View File
@@ -1,15 +1,582 @@
use chrono::NaiveDate;
use fidc_core::{
CnSmallCapRotationConfig, CnSmallCapRotationStrategy, DataSet, OmniMicroCapConfig,
BenchmarkSnapshot, CandidateEligibility, CnSmallCapRotationConfig, CnSmallCapRotationStrategy,
DailyFactorSnapshot, DailyMarketSnapshot, DataSet, Instrument, OmniMicroCapConfig,
OmniMicroCapStrategy, PortfolioState, Strategy, StrategyContext,
};
use std::collections::BTreeSet;
use std::path::PathBuf;
fn d(value: &str) -> NaiveDate {
NaiveDate::parse_from_str(value, "%Y-%m-%d").unwrap()
}
fn instrument(symbol: &str, name: &str) -> Instrument {
Instrument {
symbol: symbol.to_string(),
name: name.to_string(),
board: "Main".to_string(),
round_lot: 100,
listed_at: None,
delisted_at: None,
status: "active".to_string(),
}
}
fn market(
date: &str,
symbol: &str,
open: f64,
high: f64,
low: f64,
close: f64,
prev_close: f64,
volume: u64,
paused: bool,
) -> DailyMarketSnapshot {
DailyMarketSnapshot {
date: d(date),
symbol: symbol.to_string(),
timestamp: None,
day_open: open,
open,
high,
low,
close,
last_price: close,
bid1: close,
ask1: close,
prev_close,
volume,
minute_volume: 0,
bid1_volume: 0,
ask1_volume: 0,
trading_phase: None,
paused,
upper_limit: (prev_close * 1.10 * 100.0).round() / 100.0,
lower_limit: (prev_close * 0.90 * 100.0).round() / 100.0,
price_tick: 0.01,
}
}
fn factor(
date: &str,
symbol: &str,
market_cap_bn: f64,
free_float_cap_bn: f64,
) -> DailyFactorSnapshot {
DailyFactorSnapshot {
date: d(date),
symbol: symbol.to_string(),
market_cap_bn,
free_float_cap_bn,
pe_ttm: 18.0,
turnover_ratio: None,
effective_turnover_ratio: None,
extra_factors: Default::default(),
}
}
fn candidate(
date: &str,
symbol: &str,
is_new_listing: bool,
is_paused: bool,
allow_buy: bool,
allow_sell: bool,
) -> CandidateEligibility {
CandidateEligibility {
date: d(date),
symbol: symbol.to_string(),
is_st: false,
is_star_st: false,
is_new_listing,
is_paused,
allow_buy,
allow_sell,
is_kcb: false,
is_one_yuan: false,
risk_level_code: None,
}
}
fn benchmark(date: &str, open: f64, close: f64, prev_close: f64, volume: u64) -> BenchmarkSnapshot {
BenchmarkSnapshot {
date: d(date),
benchmark: "CSI300.DEMO".to_string(),
open,
close,
prev_close,
volume,
}
}
fn strategy_test_dataset() -> DataSet {
let dates = [
"2024-01-02",
"2024-01-03",
"2024-01-04",
"2024-01-05",
"2024-01-08",
"2024-01-09",
"2024-01-10",
"2024-01-11",
"2024-01-12",
];
let instruments = vec![
instrument("000001.SZ", "Alpha Components"),
instrument("000002.SZ", "Beta Precision"),
instrument("000003.SZ", "Charlie Materials"),
instrument("600001.SH", "Delta Industrials"),
];
let market = vec![
market(
"2024-01-02",
"000001.SZ",
10.0,
10.2,
9.9,
10.1,
9.8,
1_200_000,
false,
),
market(
"2024-01-02",
"000002.SZ",
11.0,
11.3,
10.9,
11.2,
10.8,
1_100_000,
false,
),
market(
"2024-01-02",
"000003.SZ",
8.0,
8.1,
7.8,
7.9,
8.0,
900_000,
false,
),
market(
"2024-01-02",
"600001.SH",
15.0,
15.2,
14.9,
15.1,
15.0,
800_000,
false,
),
market(
"2024-01-03",
"000001.SZ",
10.2,
10.5,
10.1,
10.4,
10.1,
1_250_000,
false,
),
market(
"2024-01-03",
"000002.SZ",
11.2,
11.6,
11.1,
11.5,
11.2,
1_120_000,
false,
),
market(
"2024-01-03",
"000003.SZ",
7.8,
7.9,
7.3,
7.4,
7.9,
930_000,
false,
),
market(
"2024-01-03",
"600001.SH",
15.1,
15.3,
15.0,
15.2,
15.1,
820_000,
false,
),
market(
"2024-01-04",
"000001.SZ",
10.5,
10.8,
10.4,
10.7,
10.4,
1_280_000,
false,
),
market(
"2024-01-04",
"000002.SZ",
11.4,
11.9,
11.3,
11.8,
11.5,
1_150_000,
false,
),
market(
"2024-01-04",
"000003.SZ",
7.3,
7.4,
7.0,
7.1,
7.4,
940_000,
false,
),
market(
"2024-01-04",
"600001.SH",
15.2,
15.5,
15.1,
15.4,
15.2,
830_000,
false,
),
market(
"2024-01-05",
"000001.SZ",
10.8,
11.1,
10.7,
11.0,
10.7,
1_300_000,
false,
),
market(
"2024-01-05",
"000002.SZ",
11.9,
12.1,
11.8,
12.0,
11.8,
1_180_000,
false,
),
market(
"2024-01-05",
"000003.SZ",
7.0,
7.1,
6.8,
6.9,
7.1,
950_000,
false,
),
market(
"2024-01-05",
"600001.SH",
15.4,
15.6,
15.3,
15.5,
15.4,
840_000,
false,
),
market(
"2024-01-08",
"000001.SZ",
11.1,
11.6,
11.0,
11.5,
11.0,
1_400_000,
false,
),
market(
"2024-01-08",
"000002.SZ",
12.1,
12.5,
12.0,
12.4,
12.0,
1_200_000,
false,
),
market(
"2024-01-08",
"000003.SZ",
7.0,
7.3,
6.9,
7.2,
6.9,
980_000,
false,
),
market(
"2024-01-08",
"600001.SH",
15.5,
15.7,
15.4,
15.6,
15.5,
850_000,
false,
),
market(
"2024-01-09",
"000001.SZ",
11.6,
12.4,
11.5,
12.3,
11.5,
1_500_000,
false,
),
market(
"2024-01-09",
"000002.SZ",
12.5,
12.9,
12.4,
12.8,
12.4,
1_250_000,
false,
),
market(
"2024-01-09",
"000003.SZ",
7.2,
7.5,
7.1,
7.4,
7.2,
990_000,
false,
),
market(
"2024-01-09",
"600001.SH",
15.6,
15.7,
15.4,
15.5,
15.6,
860_000,
false,
),
market(
"2024-01-10",
"000001.SZ",
12.2,
12.3,
11.9,
12.0,
12.3,
1_450_000,
false,
),
market(
"2024-01-10",
"000002.SZ",
12.7,
12.8,
12.5,
12.6,
12.8,
1_220_000,
false,
),
market(
"2024-01-10",
"000003.SZ",
7.5,
7.6,
7.4,
7.5,
7.4,
1_000_000,
false,
),
market(
"2024-01-10",
"600001.SH",
15.4,
15.5,
15.1,
15.2,
15.5,
870_000,
false,
),
market(
"2024-01-11",
"000001.SZ",
12.0,
12.1,
11.5,
11.6,
12.0,
1_420_000,
false,
),
market(
"2024-01-11",
"000002.SZ",
12.5,
12.6,
12.1,
12.2,
12.6,
1_210_000,
false,
),
market(
"2024-01-11",
"000003.SZ",
7.4,
7.5,
7.2,
7.3,
7.5,
980_000,
false,
),
market(
"2024-01-11",
"600001.SH",
15.2,
15.2,
15.2,
15.2,
15.2,
0,
true,
),
market(
"2024-01-12",
"000001.SZ",
11.5,
11.6,
11.1,
11.2,
11.6,
1_380_000,
false,
),
market(
"2024-01-12",
"000002.SZ",
12.1,
12.2,
11.8,
11.9,
12.2,
1_190_000,
false,
),
market(
"2024-01-12",
"000003.SZ",
7.2,
7.2,
6.9,
7.0,
7.3,
960_000,
false,
),
market(
"2024-01-12",
"600001.SH",
14.8,
15.0,
14.7,
14.9,
15.2,
850_000,
false,
),
];
let factors = dates
.iter()
.enumerate()
.flat_map(|(idx, date)| {
let i = idx as f64;
[
factor(date, "000001.SZ", 38.0 + i, 24.0 + i * 0.5),
factor(date, "000002.SZ", 45.0 + i, 30.0 + i * 0.5),
factor(date, "000003.SZ", 65.0 - i, 40.0 - i * 0.5),
factor(date, "600001.SH", 85.0 + i, 55.0 + i * 0.5),
]
})
.collect::<Vec<_>>();
let candidates = dates
.iter()
.flat_map(|date| {
let first_two = *date == "2024-01-02" || *date == "2024-01-03";
let paused_600001 = *date == "2024-01-11";
[
candidate(date, "000001.SZ", first_two, false, !first_two, true),
candidate(date, "000002.SZ", false, false, true, true),
candidate(date, "000003.SZ", false, false, true, true),
candidate(
date,
"600001.SH",
false,
paused_600001,
!paused_600001,
!paused_600001,
),
]
})
.collect::<Vec<_>>();
let benchmarks = vec![
benchmark("2024-01-02", 2990.0, 3000.0, 2980.0, 100_000_000),
benchmark("2024-01-03", 3005.0, 3020.0, 3000.0, 102_000_000),
benchmark("2024-01-04", 3025.0, 3050.0, 3020.0, 105_000_000),
benchmark("2024-01-05", 3055.0, 3080.0, 3050.0, 108_000_000),
benchmark("2024-01-08", 3085.0, 3110.0, 3080.0, 109_000_000),
benchmark("2024-01-09", 3100.0, 3090.0, 3110.0, 107_000_000),
benchmark("2024-01-10", 3080.0, 3040.0, 3090.0, 111_000_000),
benchmark("2024-01-11", 3030.0, 2990.0, 3040.0, 115_000_000),
benchmark("2024-01-12", 2980.0, 2950.0, 2990.0, 118_000_000),
];
DataSet::from_components(instruments, market, factors, candidates, benchmarks)
.expect("strategy test dataset")
}
#[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 data = strategy_test_dataset();
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);
@@ -53,8 +620,7 @@ fn strategy_emits_target_weights_and_diagnostics() {
#[test]
fn omni_strategy_emits_same_day_decision() {
let data_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../data/demo");
let data = DataSet::from_csv_dir(&data_dir).expect("demo data");
let data = strategy_test_dataset();
let execution_date = NaiveDate::from_ymd_opt(2024, 1, 10).unwrap();
let portfolio = PortfolioState::new(1_000_000.0);
let mut cfg = OmniMicroCapConfig::omni_microcap();