基于Keras和TensorFlow的LSTM股票交易策略自定义损失函数咨询
Hey Sam, 我完全懂你现在的困惑——把传统的股价预测LSTM改成交易决策模型,确实是个思路上的大转变,毕竟目标从“预测数值”直接跳到了“最大化最终资金”,核心逻辑完全不一样了。我来给你拆解下可行的方向,帮你理清思路:
你不需要先训练一个股价预测模型再套决策层,这是绕远路。你的目标是收益最大化,所以应该让LSTM直接输出每一步的交易指令(比如仓位比例、买卖动作),然后用收益的相反数作为损失函数(因为模型要最小化损失,等价于最大化收益)。
第一步:设计模型的输出
首先得明确模型每一步输出什么,常见的两种方案:
- 仓位比例:输出一个0到1之间的数值(用
sigmoid激活),代表当前总资金中投入股票的比例(如果允许做空,可以用tanh输出-1到1,-1是空仓全卖,1是满仓全买)。这种方式更灵活,模型可以学习到不同市场环境下的仓位调整。 - 离散动作:输出三个概率(买/卖/持仓),用
softmax激活,然后根据概率选择动作。这种更贴近人类的交易决策,但可能不如连续仓位灵活。
通常推荐第一种,因为连续仓位能让模型更精细地调整策略。
第二步:自定义损失函数的关键——跟踪交易状态
这是最棘手的部分:交易是有状态的,当前的现金、持仓数量会影响下一步的决策,而普通的Keras损失函数是逐样本独立计算的,没法处理这种序列依赖。这里有两种解决方式:
方式1:用TensorFlow自定义训练循环(更灵活)
放弃Keras的model.fit(),自己写训练循环,用tf.GradientTape跟踪梯度,同时在循环里维护每个样本的现金、持仓状态。大致流程是:
- 遍历每个训练样本的时间步
- 根据模型输出的仓位比例,计算当前能买/卖的最大份额(不能超过现有现金或持仓)
- 更新现金和持仓数量,加入交易成本(比如千分之一的手续费,一定要加,不然模型会疯狂交易)
- 计算每个样本的最终资金(现金+最后一天持仓的市值),损失就是所有样本最终资金的平均值的相反数(因为要最小化损失,相当于最大化平均收益)
给你个简化的代码框架参考:
import tensorflow as tf from tensorflow.keras.layers import Input, LSTM, Dense # 构建模型:输入是时间窗口+特征(股价+技术指标),输出每一步的仓位比例 def build_trading_model(window_size, feature_num): inputs = Input(shape=(window_size, feature_num)) x = LSTM(64, return_sequences=True)(inputs) # return_sequences=True,输出每一步的决策 outputs = Dense(1, activation='sigmoid')(x) # 输出0-1的仓位比例 return tf.keras.Model(inputs=inputs, outputs=outputs) model = build_trading_model(window_size=10, feature_num=5) optimizer = tf.keras.optimizers.Adam(learning_rate=1e-4) transaction_cost = 0.001 # 千分之一手续费 @tf.function def train_step(x_batch, price_batch): with tf.GradientTape() as tape: positions = model(x_batch, training=True) # (batch_size, window_size, 1) # 用tf.scan处理序列状态更新,代替Python循环(效率更高) def update_state(state, step_data): cash, shares = state price_t, pos_t = step_data # 计算当前总市值 total_value = cash + shares * price_t # 目标持仓数量 target_shares = (pos_t * total_value) / price_t # 交易数量 delta = target_shares - shares # 处理买入:不能花超现金,扣手续费 if delta > 0: cost = delta * price_t * (1 + transaction_cost) if cost > cash: delta = cash / (price_t * (1 + transaction_cost)) cash -= delta * price_t * (1 + transaction_cost) else: cash -= cost # 处理卖出:不能卖超持仓,扣手续费 elif delta < 0: sell_shares = tf.abs(delta) if sell_shares > shares: sell_shares = shares cash += sell_shares * price_t * (1 - transaction_cost) shares += delta return (cash, shares) # 初始状态:现金100,持仓0 initial_state = (tf.constant(100.0, dtype=tf.float32), tf.constant(0.0, dtype=tf.float32)) # 对每个样本计算最终状态 final_states = [] for i in range(tf.shape(x_batch)[0]): step_data = (price_batch[i], tf.squeeze(positions[i], axis=-1)) final_cash, final_shares = tf.scan(update_state, elems=step_data, initializer=initial_state) # 取最后一步的状态 final_cash = final_cash[-1] final_shares = final_shares[-1] final_total = final_cash + final_shares * price_batch[i][-1] final_states.append(final_total) final_states = tf.stack(final_states) loss = -tf.reduce_mean(final_states) # 最小化负的平均收益 grads = tape.gradient(loss, model.trainable_variables) optimizer.apply_gradients(zip(grads, model.trainable_variables)) return loss, tf.reduce_mean(final_states)
方式2:用强化学习框架(更贴合场景)
其实你的需求完美匹配强化学习(RL)的场景:序列决策、最大化累积奖励(收益)。你可以把LSTM作为RL的策略网络,输入是当前的股价窗口、现金、持仓等状态,输出动作(仓位或买卖指令),然后用DQN、PPO等算法训练。
这类框架会帮你处理奖励累积、策略更新的细节,不需要自己写复杂的状态跟踪逻辑。这种方式的优势是RL天生就考虑了长期收益,而不是只看最终资金,能避免模型短视。
第三步:关键注意事项
- 数据预处理:一定要对股价和技术指标做归一化(比如
MinMaxScaler),LSTM对数值范围很敏感。除了收盘价,还可以加入MA、RSI、成交量等技术指标,帮助模型捕捉市场信号。 - 交易成本:千万不要忽略手续费、滑点,否则模型会学出频繁交易的策略,实际收益会惨不忍睹。
- 回测验证:训练完模型后,一定要用未见过的测试数据做回测,看实际收益曲线,避免过拟合。
总结
你不需要修改MSE这类传统损失函数,因为你的目标不是预测数值而是最大化收益。优先考虑两种路径:要么用自定义训练循环实现基于收益的损失函数,要么直接用强化学习框架结合LSTM做策略网络。后者可能更省心,也更贴合交易决策的本质。
内容的提问来源于stack exchange,提问作者Samantha




