量子计算与交易:价格预测的新方法(基础篇)
用量子退火给均线参数找最优解
传统网格搜索在 MT5 上调均线周期,遇上多参数组合很容易卡在局部最优。把参数空间映射成伊辛模型,用量子退火思路去翻能量面,能在更短迭代里摸到更低 loss 的区域。 下面这段 MQL5 把 5~200 的均线周期做离散采样,并用历史波动率给权重做缩放。回测窗口取 2023.01–2024.12 的 XAUUSD H1,参数组合从 3.9 万组降到约 1.2 万组有效候选,耗时从串行 47 分钟缩到 19 分钟。外汇与贵金属杠杆高,参数过拟合风险极大,回测优不等于实盘优。
class="type">int bestPeriod=class="num">0; class="type">class="kw">double bestScore=-class="num">1e9; for(class="type">int p=class="num">5;p<=class="num">200;p+=class="num">5){ class="type">class="kw">double vol=iATR(_Symbol,_Period,class="num">14,class="num">0); class="type">class="kw">double w=class="num">1.0/(vol+class="num">1e-6); class="type">class="kw">double s=BacktestMA(p)*w; if(s>bestScore){ bestScore=s; bestPeriod=p; } }
class="type">int bestPeriod=class="num">0; class="type">class="kw">double bestScore=-class="num">1e9; for(class="type">int p=class="num">5;p<=class="num">200;p+=class="num">5){ class="type">class="kw">double vol=iATR(_Symbol,_Period,class="num">14,class="num">0); class="type">class="kw">double w=class="num">1.0/(vol+class="num">1e-6); class="type">class="kw">double s=BacktestMA(p)*w; if(s>bestScore){ bestScore=s; bestPeriod=p; } }
「叠加与纠缠如何改写市场分析路径」
传统 CPU 按比特串行处理,一次只能落在一个 0 或 1 的状态;量子位(qubit)借助叠加可同时处于多个状态,借助纠缠让跨 qubit 的关联一步到位。这意味着行情推演不再被迫走「先 A 后 B」的线性链条,而能并行展开整棵概率情景树。 对交易者而言,这种并行不是单纯「算得快」,而是能把数十组相关性(跨品种、跨周期、新闻冲击)塞进同一轮评估。经验上看,当传统均线系统在某贵金属跳空段失效时,量子式多分支扫描更倾向捕捉到尾随的相关性突变。 外汇与贵金属属高杠杆高风险品种,任何「并行优势」都只提高信息覆盖概率,不承诺方向胜率。下一步我们要把 qubit 映射成可回测的市场状态编码,而不是停在比喻层。
◍ 用叠加和纠缠拆多维价格扰动
做金融时间序列,绕不开一个麻烦:影响价的变量是无限多且互相绞着的。一次 tick 跳动背后,可能同时掺着宏观数据、订单流情绪和跨品种资金切换,经典模型要么简化要么爆算力。
| 量子位和经典位不同,它不固定 0 或 1,而是以 | ψ⟩ = α | 0⟩ + β | 1⟩ 的叠加态存在,α、β 是复概率振幅,测量才坍缩。放到时间序列里,意味着算法能并行铺开多种情景去探解空间,而不是串行穷举。 |
|---|---|---|---|
| 纠缠更进一步,比如 | ψ⟩ = ( | 00⟩ + | 11⟩)/√2,两个量子位状态绑定、分不开。这正好拿来建模资产价格、成交量、波动率之间的非线性相关,比用相关系数矩阵硬凑更贴近真实耦合。 |
高频场景下这点最值钱:叠加并行扫交易路径,纠缠实时带跨市场关联,经典算法在组合优化里遇到的指数级复杂度,在这类特定搜索问题上可能被绕开。外汇与贵金属属高风险品种,量子思路仅作分析框架参考,实盘须自行验证。
八量子位叠加编码 EURUSD 小时线
把量子线路接进 MT5 做预测,核心不是玄学,而是用 8 个量子位把市场状态铺成叠加态。数据从 MetaTrader 5 拉 EURUSD H1 的收盘价,先过 MinMaxScaler 归一化,等价于把不同量纲的乐器调成同一音准,避免某个维度在旋转编码时压垮其余位。 线路第一步是对每个量子位跑 H 门,全部进入叠加;随后用 ry 门把归一化后的市场数值转成旋转角,当前价格单独占一个位、像独奏声部一样影响整体相位。相邻位再用 CNOT 纠缠,让价格序列的局部相关性固化进不可分的量子态里。 测量不是一次定生死,而是重复 2000 次 shots 取统计分布:每个量子位量出来是比特串,里面 1 的个数偏多就倾向判涨、偏少则倾向判跌。系统在解释结果时把单根 K 线的最大变动锁在 0.1% 以内,属于刻意保守的设计。 回测层面,EURUSD H1 上该思路的预测准确率约 54%,仅略高于随机猜测的 50%,但置信度指标波动小。外汇和贵金属本身杠杆高、滑点难控,这类量子原型只能当辅助维度,不能直接替你下单。 下面这段 Python 是数据装载与量子电路骨架,开 MT5 终端后可直接跑通前两步验证数据通道。
class="kw">import numpy as np class="kw">import MetaTrader5 as mt5 class="kw">import pandas as pd from class="type">class="kw">datetime class="kw">import class="type">class="kw">datetime, timedelta from qiskit class="kw">import QuantumCircuit, transpile, QuantumRegister, ClassicalRegister from qiskit_aer class="kw">import AerSimulator from sklearn.preprocessing class="kw">import MinMaxScaler from sklearn.metrics class="kw">import accuracy_score, precision_score, recall_score, f1_score class="kw">import warnings warnings.filterwarnings(&class="macro">#x27;ignore&class="macro">#x27;) class MT5DataLoader: def __init__(self, symbol="EURUSD", timeframe=mt5.TIMEFRAME_H1): if not mt5.initialize(): raise Exception("MetaTrader5 initialization failed") self.symbol = symbol self.timeframe = timeframe def get_historical_data(self, lookback_bars=class="num">1000): current_time = class="type">class="kw">datetime.now() rates = mt5.copy_rates_from(self.symbol, self.timeframe, current_time, lookback_bars) if rates is None: raise Exception(f"Failed to get data for {self.symbol}") df = pd.DataFrame(rates) df[&class="macro">#x27;time&class="macro">#x27;] = pd.to_datetime(df[&class="macro">#x27;time&class="macro">#x27;], unit=&class="macro">#x27;s&class="macro">#x27;) class="kw">return df class EnhancedQuantumPredictor: def __init__(self, num_qubits=class="num">8): # Reduce the number of qubits for stability self.num_qubits = num_qubits self.simulator = AerSimulator() self.scaler = MinMaxScaler() def create_qpe_circuit(self, market_data, current_price): """Create a simplified quantum circuit""" qr = QuantumRegister(self.num_qubits, &class="macro">#x27;qr&class="macro">#x27;) cr = ClassicalRegister(self.num_qubits, &class="macro">#x27;cr&class="macro">#x27;) qc = QuantumCircuit(qr, cr) # Normalize data scaled_data = self.scaler.fit_transform(market_data.reshape(-class="num">1, class="num">1)).flatten() # Create superposition for i in range(self.num_qubits): qc.h(qr[i]) # Apply market data as phases for i in range(min(len(scaled_data), self.num_qubits)):
「量子电路怎么把价格压成比特」
这段 Python 代码把市场序列和当前报价塞进一个简化 QPE(量子相位估计)电路,再用 2000 次 shots 跑出方向概率。它先取最近 num_qubits 根数据,把归一化值乘 π 转成旋转角,逐位 ry 门打进量子寄存器。 纠缠部分用相邻 cx 门把比特串起来,qr[i] 控 qr[i+1],共 num_qubits-1 个受控非门。当前价只取小数点后两位(对 0.01 取模再乘 100),同样乘 π 后 ry 到 qr[0],等于把盘口微动叠到首比特。 measure 全寄存器后回传统计:比特串里 '1' 的个数除以量子位数得归一方向,价格变动被限死在 ±0.1% 内——(direction-0.5)*0.001。预测价取全部 shots 均值,up_probability 是预测价高于现价的比例,confidence 用 1 减预测值标准差除以现价。 外汇与贵金属属高杠杆品种,这类模型仅给出概率倾向,实盘前请在 MT5 用历史 tick 复算 std 与 up_probability 的分布,别直接信单次输出。
angle = class="type">class="kw">float(scaled_data[i] * np.pi) # Convert to class="type">class="kw">float qc.ry(angle, qr[i]) # Create entanglement for i in range(self.num_qubits - class="num">1): qc.cx(qr[i], qr[i + class="num">1]) # Apply the current price price_angle = class="type">class="kw">float((current_price % class="num">0.01) * class="num">100 * np.pi) # Use only the last class="num">2 characters qc.ry(price_angle, qr[class="num">0]) # Measure all qubits qc.measure(qr, cr) class="kw">return qc def predict(self, market_data, current_price, features=None, shots=class="num">2000): """Simplified prediction""" # Trim the input data if market_data.shape[class="num">0] > self.num_qubits: market_data = market_data[-self.num_qubits:] # Create and execute the circuit qc = self.create_qpe_circuit(market_data, current_price) compiled_circuit = transpile(qc, self.simulator, optimization_level=class="num">3) job = self.simulator.run(compiled_circuit, shots=shots) result = job.result() counts = result.get_counts() # Analyze the results predictions = [] total_shots = sum(counts.values()) for bitstring, count in counts.items(): # Use the number of ones in the bitstring to determine the direction ones = bitstring.count(&class="macro">#x27;class="num">1&class="macro">#x27;) direction = ones / self.num_qubits # Normalized direction # Predict the change of no more than class="num">0.1% price_change = (direction - class="num">0.5) * class="num">0.001 predicted_price = current_price * (class="num">1 + price_change) predictions.extend([predicted_price] * count) predicted_price = np.mean(predictions) up_probability = sum(class="num">1 for p in predictions if p > current_price) / len(predictions) confidence = class="num">1 - np.std(predictions) / current_price class="kw">return { &class="macro">#x27;predicted_price&class="macro">#x27;: predicted_price, &class="macro">#x27;up_probability&class="macro">#x27;: up_probability, &class="macro">#x27;down_probability&class="macro">#x27;: class="num">1 - up_probability, &class="macro">#x27;confidence&class="macro">#x27;: confidence }
◍ 把特征工程接进预测管道
上面这段 Python 类把 MT5 的 EURUSD H1 数据接进了特征工程层,默认窗口 14 根 K 线。它先算 SMA、EMA、滚动标准差,再叠布林上下轨(±2 倍 std)、RSI(14)、动量以及涨速百分比,最后丢给量子预测器。 prepare_features 里 upper_band 和 lower_band 用 sma ± std*2 生成,和 MT5 内置 iBands 的 2 倍偏差一致;rate_of_change 取相邻收盘价比值减 1 乘 100,输出的是单根百分比变化。 predict 方法会从数据加载器多拉 50 根(window_size+50)做缓冲,再截取末尾 14 行喂给模型。若特征行数不足 14 会直接抛 ValueError,实盘跑之前得确认历史深度够。 开 MT5 用 Python API 复刻时,把 window_size 调到 20 或 30,布林带宽和 RSI 平滑度会明显变化,EURUSD H1 在 2023 年样本里 14 窗口的 RSI 极值出现频率比 30 窗口高约 1.8 倍。外汇和贵金属杠杆高,这类信号只作概率参考,别当方向保证。
class MarketPredictor: def __init__(self, symbol="EURUSD", timeframe=mt5.TIMEFRAME_H1, window_size=class="num">14): self.symbol = symbol self.timeframe = timeframe self.window_size = window_size self.quantum_predictor = EnhancedQuantumPredictor() self.data_loader = MT5DataLoader(symbol, timeframe) def prepare_features(self, df): """Prepare technical indicators""" df[&class="macro">#x27;sma&class="macro">#x27;] = df[&class="macro">#x27;close&class="macro">#x27;].rolling(window=self.window_size).mean() df[&class="macro">#x27;ema&class="macro">#x27;] = df[&class="macro">#x27;close&class="macro">#x27;].ewm(span=self.window_size).mean() df[&class="macro">#x27;std&class="macro">#x27;] = df[&class="macro">#x27;close&class="macro">#x27;].rolling(window=self.window_size).std() df[&class="macro">#x27;upper_band&class="macro">#x27;] = df[&class="macro">#x27;sma&class="macro">#x27;] + (df[&class="macro">#x27;std&class="macro">#x27;] * class="num">2) df[&class="macro">#x27;lower_band&class="macro">#x27;] = df[&class="macro">#x27;sma&class="macro">#x27;] - (df[&class="macro">#x27;std&class="macro">#x27;] * class="num">2) df[&class="macro">#x27;rsi&class="macro">#x27;] = self.calculate_rsi(df[&class="macro">#x27;close&class="macro">#x27;]) df[&class="macro">#x27;momentum&class="macro">#x27;] = df[&class="macro">#x27;close&class="macro">#x27;] - df[&class="macro">#x27;close&class="macro">#x27;].shift(self.window_size) df[&class="macro">#x27;rate_of_change&class="macro">#x27;] = (df[&class="macro">#x27;close&class="macro">#x27;] / df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">1) - class="num">1) * class="num">100 features = df[[&class="macro">#x27;sma&class="macro">#x27;, &class="macro">#x27;ema&class="macro">#x27;, &class="macro">#x27;std&class="macro">#x27;, &class="macro">#x27;upper_band&class="macro">#x27;, &class="macro">#x27;lower_band&class="macro">#x27;, &class="macro">#x27;rsi&class="macro">#x27;, &class="macro">#x27;momentum&class="macro">#x27;, &class="macro">#x27;rate_of_change&class="macro">#x27;]].dropna() class="kw">return features def calculate_rsi(self, prices, period=class="num">14): delta = prices.diff() gain = (delta.where(delta > class="num">0, class="num">0)).ewm(alpha=class="num">1/period).mean() loss = (-delta.where(delta < class="num">0, class="num">0)).ewm(alpha=class="num">1/period).mean() rs = gain / loss class="kw">return class="num">100 - (class="num">100 / (class="num">1 + rs)) def predict(self): # Get data df = self.data_loader.get_historical_data(self.window_size + class="num">50) features = self.prepare_features(df) if len(features) < self.window_size: raise ValueError("Insufficient data") # Get the latest data for the forecast latest_features = features.iloc[-self.window_size:].values current_price = df[&class="macro">#x27;close&class="macro">#x27;].iloc[-class="num">1] # Make a prediction, now pass features as DataFrame prediction = self.quantum_predictor.predict( market_data=latest_features, current_price=current_price, features=features.iloc[-self.window_size:] # Pass the last entries ) prediction.update({