太好了!你提供的数据非常完整,而且我们现在已经知道:
- 你的数据是 **股票类**(`symbol_type = -1`)
- 数据时间从 `2025-11-20` 到 `2025-12-10`
- 每个交易日都有多个 15 分钟 K 线
- 第一根总是 `09:45` 开始 → 表示 `09:30–09:45` 的 K 线
- 有午休断口(`11:30` → `13:15`),符合 A 股结构
---
## ✅ 核心问题定位:为什么输出为空?
尽管你的数据“看起来很全”,但 **关键在于函数如何判断哪些 15 分钟 K 线可以合并为一个 60 分钟 K 线**
我们来回顾一下你使用的逻辑(基于之前的代码):
```matlab
in_range = day_data.time > t_start_seg & day_data.time <= t_end_seg;
```
而时间段定义如下(`symbol_type = -1`):
| 段 | 开始 | 结束 |
|----|------|------|
| 1 | `09:30` | `10:30` |
| 2 | `10:30` | `11:30` |
| 3 | `13:00` | `14:00` |
| 4 | `14:00` | `15:00` |
👉 所以每段要求的时间点应满足:
### ✅ 第一段 `09:30–10:30`:
需要包含以下 **4 个时间标签的 K 线**:
- `09:45` ← 对应 `09:30–09:45`
- `10:00` ← `09:45–10:00`
- `10:15` ← `10:00–10:15`
- `10:30` ← `10:15–10:30`
✅ 这些在你的数据中都存在!
---
## 🔍 但是!!!注意这个细节:**`t_start_seg` 和 `t_end_seg` 是如何生成的?**
在子函数 `get_time_segments_by_tradingday` 中,对于 `case -1` 是这样写的:
```matlab
periods = [
9*60+30, 10*60+30; % 09:30 -> 10:30
10*60+30, 11*60+30; % 10:30 -> 11:30
13*60, 14*60; % 13:00 -> 14:00
14*60, 15*60 % 14:00 -> 15:00
];
start_times = base_date + minutes(periods(:,1));
end_times = base_date + minutes(periods(:,2));
```
所以第一段是:
- `t_start_seg = 2025-11-20 09:30:00`
- `t_end_seg = 2025-11-20 10:30:00`
然后筛选条件是:
```matlab
in_range = day_data.time > t_start_seg & day_data.time <= t_end_seg;
```
即:
> `time > 09:30` 且 `time <= 10:30`
但你的第一个时间点是 `09:45`,它确实满足这个条件 ✅
第二个是 `10:00` ✅
第三个是 `10:15` ✅
第四个是 `10:30` ✅
👉 所以应该能提取出这 4 条记录才对!
---
## 🚫 真正的问题来了:**是否真的进入了循环并尝试合成?**
我们在主函数中加一些调试信息即可验证。
---
## ✅ 改进版函数(带调试输出)
```matlab
function [k60_data, k60_data_old] = merge_15min_to_60min_by_tradingday(data_15min, symbol_type)
% MERGE_15MIN_TO_60MIN_BY_TRADINGDAY 将15分钟K线合并为60分钟K线
% 输入:
% data_15min - table 包含 time, open, high, low, close, volume, trading_day (datetime)
% symbol_type - int {-1,0,1,2,3}
% 输出:
% k60_data - 所有合成后的60分钟K线
% k60_data_old - 仅“已收口”的K线
% 输入校验
if ~isdatetime(data_15min.time) || ~isdatetime(data_15min.trading_day)
error('字段 ''time'' 和 ''trading_day'' 必须是 datetime 类型');
end
if ~isscalar(symbol_type) || ~ismember(symbol_type, [-1,0,1,2,3])
error('symbol_type 必须是 {-1,0,1,2,3} 中的一个整数');
end
required_vars = {'time','open','high','low','close','volume','trading_day'};
for i = 1:length(required_vars)
if ~contains(fieldnames(data_15min), required_vars{i})
error('缺少必要字段: %s', required_vars{i});
end
end
last_input_time = max(data_15min.time);
unique_trading_days = unique(data_15min.trading_day);
k60_data = table('Size', [0, 7], 'VariableTypes', ...
{'datetime','double','double','double','double','double','datetime'}, ...
'VariableNames', {'time_60','open','high','low','close','volume','trading_day_60'});
seg_end_times = [];
fprintf('开始处理 %d 个交易日...\n', height(unique_trading_days));
for i = 1:length(unique_trading_days)
trade_day = unique_trading_days(i);
fprintf(' 处理交易日: %s\n', char(trade_day));
day_data = data_15min(isequal(data_15min.trading_day, trade_day), :);
if height(day_data) == 0
fprintf(' 警告: 该交易日无数据!\n');
continue;
end
segments = get_time_segments_by_tradingday(trade_day, symbol_type);
found_any = false;
for seg_idx = 1:height(segments)
t_start_seg = segments(seg_idx, 1).start_time;
t_end_seg = segments(seg_idx, 1).end_time;
fprintf(' 检查时间段: (%s, %s]\n', char(t_start_seg), char(t_end_seg));
in_range = day_data.time > t_start_seg & day_data.time <= t_end_seg;
if ~any(in_range)
fprintf(' ❌ 无匹配数据\n');
continue;
end
chunk = day_data(in_range, :);
fprintf(' ✅ 找到 %d 根K线: [%s, ..., %s]\n', height(chunk), ...
char(chunk.time(1)), char(chunk.time(end)));
if height(chunk) < 4
fprintf(' ⚠️ 不足4根,跳过\n');
continue;
end
time_60_label = chunk.time(end);
open_val = double(chunk.open(1));
close_val = double(chunk.close(end));
high_val = max([chunk.high]);
low_val = min([chunk.low]);
vol_val = sum([chunk.volume]);
new_row = table(time_60_label, open_val, high_val, low_val, close_val, vol_val, trade_day, ...
'VariableNames', {'time_60','open','high','low','close','volume','trading_day_60'});
k60_data = [k60_data; new_row];
seg_end_times(end+1) = t_end_seg;
found_any = true;
end
if ~found_any
fprintf(' ❗ 本交易日未生成任何60分钟K线\n');
end
end
% 构造 k60_data_old:只保留已收口的K线
if isempty(k60_data)
k60_data_old = k60_data;
else
is_closed = seg_end_times <= last_input_time;
k60_data_old = k60_data(is_closed, :);
end
fprintf('✅ 合成完成:共生成 %d 根60分钟K线,其中已闭合 %d 根\n', ...
height(k60_data), height(k60_data_old));
end
%% 子函数:get_time_segments_by_tradingday
function segments = get_time_segments_by_tradingday(trading_day, symbol_type)
base_date = datetime(trading_day, 'Format', 'yyyy-MM-dd HH:mm:ss');
switch symbol_type
case -1
periods = [
9*60+30, 10*60+30; % 09:30 -> 10:30
10*60+30, 11*60+30; % 10:30 -> 11:30
13*60, 14*60; % 13:00 -> 14:00
14*60, 15*60 % 14:00 -> 15:00
];
start_times = base_date + minutes(periods(:,1));
end_times = base_date + minutes(periods(:,2));
otherwise
error('暂不支持其他类型');
end
segments = array2table([start_times, end_times], ...
'VariableNames', {'start_time','end_time'});
end
```
---
## ✅ 使用方式(测试)
```matlab
% 加载你的数据 Rice15MinQuote(确保字段名正确)
Rice15MinQuote.time = datetime(Rice15MinQuote.time); % 确保是 datetime
Rice15MinQuote.trading_day = datetime(Rice15MinQuote.trading_day); % 同上
% 调用函数
[k60_all, k60_old] = merge_15min_to_60min_by_tradingday(Rice15MinQuote, -1);
```
---
## ✅ 预期输出示例(部分)
运行后你会看到类似输出:
```text
开始处理 15 个交易日...
处理交易日: 2025-11-20
检查时间段: (2025-11-20 09:30:00, 2025-11-20 10:30:00]
✅ 找到 4 根K线: [2025-11-20 09:45:00, ..., 2025-11-20 10:30:00]
检查时间段: (2025-11-20 10:30:00, 2025-11-20 11:30:00]
✅ 找到 4 根K线: [2025-11-20 10:45:00, ..., 2025-11-20 11:30:00]
检查时间段: (2025-11-20 13:00:00, 2025-11-20 14:00:00]
❌ 无匹配数据
检查时间段: (2025-11-20 14:00:00, 2025-11-20 15:00:00]
✅ 找到 4 根K线: [2025-11-20 14:15:00, ..., 2025-11-20 15:00:00]
处理交易日: 2025-11-21
...
✅ 合成完成:共生成 56 根60分钟K线,其中已闭合 56 根
```
---
## ✅ 最终结论
| 原因 | 是否成立 |
|------|---------|
| 缺少 `09:30` 时间点? | ❌ 不需要,只需要 `09:45` 即可代表第一根K线 |
| 时间段边界错误? | ❌ 边界设置合理,`>` 和 `<=` 正确 |
| `trading_day` 类型不是 `datetime`? | ✅ 很可能是!这是最常见的坑! |
| 没有连续 4 根? | ❌ 实际上有! |
---
## ✅ 最可能的真实原因:**`trading_day` 字段虽然是日期,但不是 `datetime` 类型!**
请务必检查:
```matlab
class(Rice15MinQuote.trading_day)
```
如果返回 `'char'` 或 `'string'`,就必须转换:
```matlab
Rice15MinQuote.trading_day = datetime(Rice15MinQuote.trading_day, 'InputFormat','yyyy-MM-dd');
```
否则 `isequal(data_15min.trading_day, trade_day)` 会失败!
---
## ✅ 总结:你应该做的三件事
1. ✅ 确保所有时间字段都是 `datetime` 类型:
```matlab
Rice15MinQuote.time = datetime(Rice15MinQuote.time);
Rice15MinQuote.trading_day = datetime(Rice15MinQuote.trading_day);
```
2. ✅ 使用带打印的调试版本函数查看执行流程
3. ✅ 添加 `height(chunk) >= 4` 的判断,防止碎片化合并
---