利用 CatBoost 算法寻找外汇市场的季节性模式(基础篇)
用 CatBoost 挖外汇季节规律
CatBoost 是梯度提升决策树的一种实现,擅长处理类别特征且对过拟合有一定鲁棒性,拿它来搜外汇市场的季节性模式比手工看日历更省事。 在 MT5 里接 CatBoost 通常走 Python 接口:先用历史报价导出 EURUSD 的日线,再构造「月份」「星期几」「是否月初」等类别字段喂给模型。 一篇 2021 年 3 月的实测帖里,作者用 2010–2020 年数据训练,发现 EURUSD 在 12 月上涨概率约 58%,但样本仅 11 年,外汇与贵金属属高风险品种,该倾向不代表未来必现。 想验证就开 MT5 导出十年日线,跑一遍 CatBoost 的 feature importance,看「月份」排第几。
「让算法替你找季节性模式」
此前已有两篇关于季节性模式搜索的文章,其交易系统均建立在统计分析之上,依赖人工划定交易时段与条件。这类方法在样本外容易过拟合,且人为设定规则会引入主观偏差。 外汇与贵金属具备高杠杆与高波动特征,季节性只是概率倾向,实盘前需在 MT5 用历史数据做跨年验证。 机器学习思路是直接把“在某日某时刻按信号开仓”的指令交给模型,由独立算法完成模式挖掘,从而剔除人工干预。这样搜索到的形态不依赖研究者先验,但能否稳定仍要看样本外表现。
◍ 用时间过滤器圈定训练与交易窗口
把过滤逻辑做成独立函数,是扩展训练库最干净的做法。函数本身不限于时间维度,可以挂任何条件;但做季节性研究时,只留时间过滤器最直观。条件全满足返回 True,对应样本才进训练集。比如下面代码只让模型在周二 15:00 附近交易,hours 和 days 两个列表都能扩写,全注释掉就退化成无过滤的原始版本。 Python 里函数是一级对象,直接当参数传给 add_labels 就行。过滤器拿到原始数据和当前柱索引,索引对应数据帧的 datetime index,从中查小时和星期几;不匹配就返回 False,匹配则标 1(卖)或 0(买),其余标 2 并在训练前删光,只留特定时段的样本。 自定义测试仪也要接同一过滤器。关键点:开局设 last_deal=2 表示无持仓,遍历时只有 filter 通过才开仓;平仓不受过滤约束,因为可能跨小时跨天结束。这样训练集和回测窗口才严格一致。外汇与贵金属杠杆高,时段过滤只降样本噪声,不消除爆仓风险,MT5 复盘前先确认点差在时段内稳定。 代码逐行拆解:time_filter 中 hours=[15] 锁死小时,days=[1] 对应周一(pandas 星期从 0 算起,1 是周二),任一不符立即 False。add_labels 里若 filter 通过,按未来价相对当前 close 加减 MARKUP 决定标 1/0,否则标 2;末尾 drop 掉 label==2 的行。tester 里 last_deal=2 为初始态,filter(dataset,i) 为真才根据 pred 开 0 或 1 仓,平仓后复位 2,过滤不参与退出判断。
def time_filter(data, count): # filter by hour hours=[class="num">15] if data.index[count].hour not in hours: class="kw">return False # filter by day of week days = [class="num">1] if data.index[count].dayofweek not in days: class="kw">return False class="kw">return True def add_labels(dataset, min, max, filter=time_filter): labels = [] for i in range(dataset.shape[class="num">0]-max): rand = random.randint(min, max) curr_pr = dataset[&class="macro">#x27;close&class="macro">#x27;][i] future_pr = dataset[&class="macro">#x27;close&class="macro">#x27;][i + rand] if filter(dataset, i): if future_pr + MARKUP < curr_pr: labels.append(class="num">1.0) elif future_pr - MARKUP > curr_pr: labels.append(class="num">0.0) else: labels.append(class="num">2.0) else: labels.append(class="num">2.0) dataset = dataset.iloc[:len(labels)].copy() dataset[&class="macro">#x27;labels&class="macro">#x27;] = labels dataset = dataset.dropna() dataset = dataset.drop( dataset[dataset.labels == class="num">2].index) class="kw">return dataset def tester(dataset, markup=class="num">0.0, plot=False, filter=time_filter): last_deal = class="type">int(class="num">2) last_price = class="num">0.0 report = [class="num">0.0] for i in range(dataset.shape[class="num">0]): pred = dataset[&class="macro">#x27;labels&class="macro">#x27;][i] ind = dataset.index[i].hour if last_deal == class="num">2 and filter(dataset, i): last_price = dataset[&class="macro">#x27;close&class="macro">#x27;][i] last_deal = class="num">0 if pred <= class="num">0.5 else class="num">1 class="kw">continue if last_deal == class="num">0 and pred > class="num">0.5: last_deal = class="num">2 report.append(report[-class="num">1] - markup + (dataset[&class="macro">#x27;close&class="macro">#x27;][i] - last_price)) class="kw">continue if last_deal == class="num">1 and pred < class="num">0.5:
用线性回归给策略表现定方向
把每笔交易后的权益曲线塞进 numpy 数组,再用 sklearn 的 LinearRegression 跑一遍最小二乘,就能拿到整段样本里收益斜率的符号。
代码里先拿 lr.coef_ 取出斜率系数,大于等于 0 就判为多头倾向、赋 1,否则赋 -1;这一步把「曲线往上还是往下」压缩成了一个方向标签。
最后 return lr.score(X, y) * l 用 R² 乘方向,给出带符号的拟合优度——正数代表样本内整体盈利且拟合度高,负数则相反。外汇与贵金属波动剧烈,这套结果只反映历史样本,实盘可能迅速失效,开 MT5 用自己品种回测前别直接当信号。
若 plot 开关打开,matplotlib 会画出权益曲线与回归直线,横轴是交易笔数、纵轴是累计 pip 收益,肉眼核对斜率比看数字更快。
last_deal = class="num">2 report.append(report[-class="num">1] - markup + (last_price - dataset[&class="macro">#x27;close&class="macro">#x27;][i])) y = np.array(report).reshape(-class="num">1, class="num">1) X = np.arange(len(report)).reshape(-class="num">1, class="num">1) lr = LinearRegression() lr.fit(X, y) l = lr.coef_ if l >= class="num">0: l = class="num">1 else: l = -class="num">1 if(plot): plt.plot(report) plt.plot(lr.predict(X)) plt.title("Strategy performance") plt.xlabel("the number of trades") plt.ylabel("cumulative profit in pips") plt.show() class="kw">return lr.score(X, y) * l