4. 策略基础 - 交易进出

4.策略基础 - 交易进出

交易进出规则是量化策略的骨架,决定了何时开仓、何时平仓。Jesse 把这套逻辑拆分成几个清晰的方法,让策略编写变得像搭积木一样直观。这一章我们把这些核心方法掰开揉碎,看看它们到底怎么工作。

定义做多规则

策略要不要做多,本质上就是一个是非判断。Jesse 用 should_long() 方法来回答这个问题。这个方法必须返回一个布尔值——True 或者 False,没有中间地带。

should_long() 的基本用法

should_long() 的调用时机很讲究:只有在当前没有持仓、也没有挂单的情况下,Jesse 才会在每个新 K 线到来时调用它。这意味着它专门负责"是否开新仓"这个决策,不会干扰已有的仓位管理。

来看一个最简单的例子:

def should_long(self):
    # 如果当前 K 线是阳线(收盘价高于开盘价),就返回 True
    if self.close > self.open:
        return True
    
    return False

这段代码的逻辑直白得不能再直白:看到阳线就想做多。实际策略中当然不会这么简单,但框架就是这样。我们可以在里面写任意复杂的判断条件,比如检查均线排列、RSI 数值、成交量变化等等。只要最后返回 True,Jesse 就会认为"现在应该做多"。

关键约束

这里有个硬性规定:should_long()should_short() 不能同时返回 True。从逻辑上讲,我们不可能在同一个时刻既做多又做空。Jesse 会强制检查这一点,如果两个方法都返回 True,策略会直接报错终止。

另外要注意,should_long() 只负责入场判断。如果已经持有仓位,想动态调整出场点,那不是它的职责范围。这类需求要交给 update_position() 方法处理,这个我们会在后面的章节详细展开。

定义做空规则

做空规则与做多规则完全对称。should_short() 方法同样返回布尔值,决定是否在空仓状态下开空单。

should_short() 的实现方式

def should_short(self):
    # 如果当前 K 线是阴线(收盘价低于开盘价),就返回 True
    if self.close < self.open:
        return True
    
    return False

这段代码和做多例子正好相反,看到阴线就准备做空。实际应用中,这里可以写各种判断市场弱势的信号,比如价格跌破关键支撑、均线空头排列、MACD 死叉等等。

现货模式的限制

需要特别说明的是,做空功能只在期货模式下有效。如果正在开发现货策略,可以简单地让 should_short() 永远返回 False,或者直接留空:

def should_short(self):
    return False

def go_short(self):
    pass

这样 Jesse 就不会尝试执行任何做空操作,策略可以安全地运行在现货环境中。

执行做多订单

决定做多之后,下一步是精确设定入场价格、仓位大小和出场点位。go_long() 方法就是干这个的。

go_long() 的核心结构

在这个方法里,我们要设置三个关键属性:

def go_long(self):
    self.buy = 数量, 入场价格
    self.stop_loss = 数量, 止损价格
    self.take_profit = 数量, 止盈价格

这三个属性名是 Jesse 规定的,不能更改。它们分别对应买入订单、止损订单和止盈订单。每个属性都需要一个元组,包含两个元素:交易数量和价格。

完整示例

来看一个能直接运行的例子:

def go_long(self):
    qty = 1
    
    # 用当前价格市价买入 1 个单位
    self.buy = qty, self.price
    # 止损设在当前 K 线最低价再低 10 美元的位置
    self.stop_loss = qty, self.low - 10
    # 止盈设在当前 K 线最高价再高 10 美元的位置
    self.take_profit = qty, self.high + 10

这段代码展示了基本用法。qty 变量存储交易数量,self.price 是当前价格,self.lowself.high 分别是当前 K 线的最低价和最高价。止损和止盈都设置了 10 美元的缓冲空间。

智能订单系统

Jesse 的订单系统很聪明,能自动判断订单类型,不用我们手动指定。对于做多订单:

  • 如果入场价格等于当前价格,自动使用市价单(MARKET)
  • 如果入场价格低于当前价格,自动使用限价单(LIMIT)
  • 如果入场价格高于当前价格,自动使用止损单(STOP)

这个设计省了不少事。比如上面的例子中,self.buy = qty, self.price 因为价格相等,会自动以市价单执行。如果想在回调时买入,可以写成 self.buy = qty, self.price - 5,这时 Jesse 会生成一个限价单,等待价格跌到指定位置再成交。

执行做空订单

做空订单的设置与做多几乎一样,只是入口属性从 self.buy 变成了 self.sell

go_short() 的实现

def go_short(self):
    self.sell = 数量, 入场价格
    self.stop_loss = 数量, 止损价格
    self.take_profit = 数量, 止盈价格

结构完全一致,只是 self.sell 表示卖出开仓。

做空示例

def go_short(self):
    qty = 1
    
    # 用当前价格市价卖出 1 个单位(开空)
    self.sell = qty, self.price
    # 止损设在当前 K 线最高价再高 10 美元的位置
    self.stop_loss = qty, self.high + 10
    # 止盈设在当前 K 线最低价再低 10 美元的位置
    self.take_profit = qty, self.low - 10

注意做空的止损止盈逻辑与做多相反。因为是先高价卖出,希望低价买回,所以止损要设在更高位置防止价格上涨,止盈要设在更低位置等待价格下跌。

智能订单系统对做空同样适用:价格等于当前价用市价单,高于当前价用止损单,低于当前价用限价单。

取消入场订单

有时候我们提交了入场订单,但市场走势发生变化,之前的入场条件不再成立。这时就需要 should_cancel_entry() 方法来取消未成交的挂单。

适用场景

这个方法只在特定情况下触发:已经提交了开仓订单,但订单还没成交。常见于使用限价单或止损单入场时。市价单会立即执行,所以不需要取消。

想象一个场景:策略想在价格突破前高时追多,于是提交了一个止损买单,入场价设在前高上方 2 美元。但下一个 K 线出来后,价格没涨反而跌了,前高位置也下移了。这时之前的挂单价格就显得过高,应该取消重挂。

具体实现

def should_long(self):
    return True

def go_long(self):
    qty = 1
    entry = self.high + 2  # 在前高上方 2 美元挂单
    
    self.buy = qty, entry  # 这会生成一个止损买单

def should_cancel_entry(self):
    return True

这个例子中,should_cancel_entry() 直接返回 True,表示"只要订单没成交,就取消"。实际策略中通常需要加判断条件,比如检查当前价格与挂单价格的距离、市场趋势是否改变等。

重要限制

should_cancel_entry() 只影响入场订单,不会影响已经设置的止损或止盈订单。那些订单的修改需要通过 update_position() 方法来完成。

多点进出管理

前面的例子都是一次性入场、一次性出场。实际交易中,我们可能想分批建仓、分批止盈,甚至动态调整出场点。

分批止盈示例

def go_long(self):
    qty = 1
    
    self.buy = qty, 100
    self.stop_loss = qty, 80
    
    # 在 120 和 140 两个价位分批止盈
    self.take_profit = [
        (qty/2, 120),  # 平掉一半仓位
        (qty/2, 140)   # 平掉剩下的一半
    ]

这里 self.take_profit 接收一个列表,每个元素是一个元组,包含(数量, 价格)。Jesse 会自动为每个价位生成对应的止盈订单。同理,self.stop_loss 也可以这样设置多个出场点。

分批入场示例

def go_long(self):
    qty = 1
    
    # 先在 120 买入一半,价格继续上涨到 140 时再买入另一半
    self.buy = [
        (qty/2, 120),
        (qty/2, 140)
    ]
    self.stop_loss = qty, 100
    self.take_profit = qty, 160

这种写法让策略能在不同价位分批建仓,降低单次入场的风险。Jesse 会按顺序处理这些订单,成交一个再处理下一个。

动态出场需求

有时候入场时并不知道确切的止盈价位,比如趋势跟踪策略,可能要等趋势反转信号出现才出场。这种情况下,需要在 update_position() 方法中动态更新出场点。这个高级用法会在第 6 章详细讲解。

策略生命周期钩子

除了核心的交易进出方法,Jesse 还提供了几个生命周期钩子,让我们能在特定时机执行自定义逻辑。

before() 方法

这是每个 K 线周期最先调用的方法,适合更新自定义变量或做一些预处理:

def before(self):
    # 在策略逻辑执行前更新自定义变量
    self.vars.counter = self.vars.counter + 1

after() 方法

这是每个 K 线周期最后调用的方法,适合收尾工作或记录状态:

def after(self):
    # 在策略逻辑执行后记录当前仓位信息
    if self.position.is_open:
        self.log(f"当前盈亏:{self.position.pnl}")

这两个方法在复杂策略中很有用,可以把准备工作和清理工作与核心交易逻辑分开,代码更清晰。

初始化与终止

策略类作为 Python 类,也支持构造函数和资源清理。

init() 初始化

def __init__(self):
    super().__init__()
    
    print('策略类初始化完成')
    # 这里可以加载模型、初始化变量等

注意:必须首先调用 super().__init__(),否则 Jesse 会报错。这个方法在整个回测或实盘过程中只执行一次,适合放一些只需要初始化一次的操作,比如加载机器学习模型、计算长期指标等。

terminate() 终止处理

当策略即将结束时,Jesse 会调用 terminate() 方法。这适合保存数据、记录日志等清理工作:

def terminate(self):
    self.log('执行即将终止...')
    # 保存训练好的模型
    # 导出统计结果

还有一个 before_terminate() 方法,在 terminate() 之前调用,区别是这个方法里还可以提交订单,比如想在策略停止前平掉部分仓位,可以在这里操作。

实战建议

把这些方法组合起来,就能构建完整的交易策略。建议从简单开始:

  1. 先在 should_long() 里写一两个清晰的入场条件
  2. go_long() 里设置固定的止损止盈
  3. 跑回测看看效果
  4. 逐步加入 should_cancel_entry() 处理挂单
  5. 需要动态调整时,再研究 update_position()

记住,策略不是越复杂越好。把核心逻辑写清楚,比堆砌几十个指标更有价值。Jesse 的 API 设计得很克制,每个方法职责单一,这迫使我们把策略结构梳理得更清晰。

下一章我们会把这些交易规则放到回测引擎里,看看它们在历史数据上的表现如何。回测不仅能验证策略逻辑是否正确,还能暴露很多意想不到的问题。