资源加载中... loading...

使用JavaScript入门商品期货量化交易

Author: 雨幕(youquant), Created: 2023-08-15 17:04:31, Updated: 2024-09-12 09:04:45

略在起始阶段,会使用默认周期为20,然后判断k线的数量是否满足计算均值,然后打印“当前周期为:20”,输出20周期的均值;然后在代码中书写交互控件控制语句,当点击交互控件修改period周期以后,就会调整均线计算的周期,然后打印出来对应period的值,进行新的period均线的计算。

function main() {
    while (true) {
        if (exchange.IO("status")) {
            LogStatus(_D(), "已连接CTP!");
            exchange.SetContractType('rb888')
            bars = exchange.GetRecords()
            
            
            var cmd = GetCommand();
            if (cmd) {
                Log("cmd:", cmd);
                var arr = cmd.split(":");
                period = parseInt(arr[1])
                
                Log('当前周期为:', period)
                
                if (!bars || bars.length <= period) {
                    continue
                }
                base = TA.MA(bars, period)
                Log('均线period: ', base[base.length - 1])
                Sleep(1000);
            }
            
        } else {
            LogStatus(_D(), "未连接CTP!");
            Sleep(500);
        }
    }
}

我们在实盘中看下,为了快速呈现结果,我们使用秒为周期,策略开始,日志输出的是20周期的均线,这里我们修改一下,周期变为10,可以看到日志信息中开始输出10周期的均线。

这就是实时修改参数的一个小例子,确实实现了不需要停掉实盘,重新修改策略的参数。这种实时修改参数的技术可以帮助你根据市场情况、风险偏好等因素进行灵活的调整,从而优化和改进你的交易策略。与停止机器人并重新编译代码相比,它提供了更方便快捷的方式来调整参数,同时也可以避免中断实盘交易。

但需要注意的是,在实时修改参数时,要确保对参数的修改是合理和稳妥的。过于频繁或不恰当的参数调整可能会导致策略性能下降或产生意外的结果。因此,在使用这类功能时,建议谨慎评估和测试相关参数的变化对策略表现的影响,并确保在真实环境下具备足够的安全保障和风险控制措施。

实时调试功能

量化策略的实时调试功能可以帮助你在开发和优化策略过程中快速验证和调整策略逻辑,以及观察策略的实际表现。在策略运行过程中,你可以插入调试语句,查询关键变量、指标和决策点的值,这样就可以查看日志并分析策略逻辑的执行路径,以找出潜在的问题和改进点。

结合eval()函数,交互控件可以实现实时调试功能。eval()函数的定义和功能是可计算某个字符串,并执行其中的JavaScript代码,这样就相当于给策略一个后门,你可以随时调用JavaScript语句修改,或者查询策略的状态。当然这要在确保没有异常的情况下。

这里涉及到了交互控件的字符型控件。首先我们设置这个JavaScript交互控件。定义为字符型,然后设置默认代码为Log(_C(exchange.GetAccount))。_C是内置函数,该函数为重试函数,用于获取行情、获取未完成订单等接口的容错,该接口会一直调用指定函数直到成功返回。

回到代码主体,我们首先设置价格和数量都为0;在主函数中,首先打印初始的价格和数量;然后获取界面交互控件的消息。在判断有交互信息的时候,输出JavaScript的代码并打印出来。

控件代码的运行用到了try和catch语句。try和catch常常被用于异常处理。try关键字:try关键字标记一个代码块,表示要检测异常的范围。在这个代码块中,你可以放置可能会抛出异常的代码。这里我们执行eval函数。在这里,eval传入的参数JavaScript是一个字符串形式的代码。

catch关键字:如果在try中的代码执行过程中发生了异常,控制流将会跳转到catch块中。catch块用于捕获并处理异常。 e参数:在catch块中,e是一个表示异常的变量。通过使用e,你可以访问到抛出的异常对象,从而获取异常的相关信息。 整体来说,当eval(JavaScript)执行时,如果JavaScript包含的代码发生了异常,程序将会跳转到catch块,catch块中的代码将被执行。通过捕获异常并输出错误信息,你可以对异常进行处理,例如打印错误日志或采取其他适当的措施。

var price = 0;
var amount = 0;
function main() {
    Log("初始 price:", price, "初始 amount", amount);
    while(true){
        var cmd = GetCommand();             // 调用API  获取界面交互控件的消息。 
        if (cmd) {                          // 判断是否有消息
            var JavaScript = cmd.split(':', 2)[1];  // 分割 返回的消息 字符串,限制返回2个,把索引为1的 元素 赋值给 名为JavaScript 的变量 
            Log("执行调试代码:", JavaScript);         // 输出 执行的代码
            try {                           // 异常检测
                eval(JavaScript);                   // 执行 eval函数,该函数执行传入的参数(代码)。
            } catch(e) {                    // 抛出异常
                Log("Exception", e);        // 输出错误信息
            }
        }
        Sleep(1000);
    }
}

我们在实盘中进行测试,启动实盘,可以看到首先打印出来原始设置的价格和数量,都为0.点击控件进行调试,在这里我们首先使用默认的语句,exchange.GetAccount可以输出当前的账户信息。

Log(_C(exchange.GetAccount))

然后修改控件语句,设置新的价格和数量信息

price = 100; amount = 1;

现在我们查看下新的价格和数量是多少,日志里是不会呈现的,继续使用控件语句查看我们的修改是否成功

Log(price, amount)

可以看到日志信息打印出来了新的价格和数量。 我们也可以设置一个错误语句试试看:

log(price, amount)

这里使用了小写的log,是一个错误的语句。所以会抛出异常。 这就是一个简单的在线调试功能。eval()函数是一个JavaScript内置函数,用于将字符串作为代码进行执行。它可以实现动态执行代码的功能,但需要谨慎使用,因为存在安全风险。 在上面的代码中,通过eval(JavaScript)执行了传入的JavaScript代码,如果代码有异常,则会被catch块捕获,并输出异常信息。 虽然eval()函数提供了便利的功能,但由于它可以执行任意的JavaScript代码,所以可能会被滥用和攻击。你可以实现更多的功能,大家可以挖掘下。

状态栏交互功能

如果你认为交互控件的界面太过于死板,没关系,我们还可以定制个性化多功能交互控件页面,这里可以使用状态栏制定多功能交互面板。

我们先前使用状态栏,更多的是打印时间或者链接ctp信息,其实状态栏是可以定制化的,通过链接交互控件功能,我们就可以在状态栏进行我们需要的交互操作。

我们举一个比较粗糙的例子,在状态栏进行开仓和平仓的操作。

function main() {
    var tbl = {
        type: "table",
        title: "状态拦交互",
        cols: ["操作", "按钮"],
        rows: [
            ["开仓操作", {"type": "button", "cmd": "open", "name": "开仓", "input": {"name": "开仓数量", "type": "number", "defValue": 1}}],
            ["平仓操作", {"type": "button", "cmd": "coverAll", "name": "全部平仓"}]
        ] 
    }
    LogStatus(_D(), "\n", "`" + JavaScriptON.stringify(tbl) + "`")
    var p = $.NewPositionManager();
    while (true) {
        if (exchange.IO("status")) {
            
            var cmd = GetCommand()
            if (cmd) {
                var arr = cmd.split(":");
                if (arr[0] === "open") {
                    p.OpenLong('rb2309', parseInt(arr[1]));
                } else if (arr[0] === "coverAll") {
                    p.CoverAll('rb2309');
                }
            }
            Sleep(1000);
        }else {
            
            Sleep(500);
        }
    }
}

状态栏定制首先我们需要一个用于描述表格的对象tbl,这设计到了JavaScript语言网页设计中用于展示交互的功能,我们设计的很简单,

title: 表格的标题,这里是 “状态栏交互”。 cols: 表格的列名数组,这里是 [“操作”, “按钮”]。 rows: 表格的行数据数组,每一行是一个数组,包含相应的单元格数据。 在这段代码中,tbl 定义了一个简单的表格,包括两列和两行。 第一列名为 “操作,第二列名为"按钮”。 第一行行名开仓操作,包含一个按钮,点击按钮时触发的命令是 “open”,按钮的名称为"开仓",这里设置了一个input,需要用户手动输入开仓数量。 第二行行名是平仓操作,也包含一个按钮,点击按钮时触发的命令是 “coverAll”,按钮的名称为 “全部平仓”,这个按钮不需要提供其他的输入。 接下来我们在LogStatus中,通过这行代码输出表格对象的结构和内容。 交互控件的操作设置就是我们上述讲过的内容。 我们在实盘中看下,在状态栏里,可以看到点击open,填写数量,我们就可以开对应数量的多仓,然后点击coverall就可以全部平仓。 这里只是一个粗糙的展示,通过自定义的状态栏,我们可以进行更多分页,比如化工类,黑色系和农产品,操作可以有开仓,平仓和止盈等。这些功能的设计我们都可以实现。

总之,交互控件为交易提供了更多的灵活性和可操控性,使得交易变得更加智能化和个性化。通过合理地设计和使用交互控件,你可以实现更灵活的量化交易,提高交易的便捷性和效率。

20:策略中的定时设计

对于期货新手朋友来说,扛单是一个重要的话题。面对小额盈利时,新手会选择迫不及待的卖出。然而,在遭遇小额亏损时,由于人们不愿意承受亏损,常常坚信价格会回升,结果时间拉长,造成更大的亏损,只能忍痛割肉。如果有一个策略定时闹钟,可以帮助你在日内交易的结尾时间段,平仓所有仓位,无论盈利还是亏损。这个工具可以帮助你遵循纪律,避免情绪主导决策,有助于保护资金并控制风险,你愿意尝试一下吗?

其实类似的需求还有很多。如何给策略设计定时功能,让策略在指定的时间去处理某些任务。这样的需求在策略中要如何设计才好。一个策略里面可能要用到很多时间控制,这样来说我们把时间控制功能封装起来最好,这样可以最大程度的降低时间控制代码与策略的耦合性,让这个时间控制模块可以复用,并且在使用方面简洁易懂。

本节课我们来试着用代码创建一个闹钟。这个闹钟对象,用来设置指定的小时和分钟触发闹钟。该函数返回一个包含具有相关功能的对象。通过调用对象的方法,可以检查当前时间是否满足触发条件,并在满足条件时返回true。这个函数可以用于创建多个不同时间的闹钟对象,每个对象可以独立触发。

function CreateAlarmClock(triggerHour, triggerMinute) { 
    var self = {}                           // 构造的对象
    // 以下给构造的对象设置成员、函数
    
    self.isTrigger = false                  // 当天是否触发过
    self.triggerHour = triggerHour          // 计划触发的小时
    self.triggerMinute = triggerMinute      // 计划触发的分钟
    self.nowDay = new Date().getDay()       // 当前时间是哪日
    
    self.Check = function() {               // 检查函数,检查触发,触发返回true,未触发返回false    
        var t = new Date()                  // 获取当前时间对象
        var hour = t.getHours()             // 获取当前小数:0~23
        var minute = t.getMinutes()         // 获取当前分钟:0~59
        var day = t.getDay()                // 获取当前天数

        if (day != self.nowDay) {           // 判断,如果当前天,不等于记录的当天,重置触发标记为未触发,更新记录的天数
            self.isTrigger = false
            self.nowDay = day
        }

        if (self.isTrigger == false && hour == self.triggerHour && minute >= self.triggerMinute) {  
            // 判断时间是否触发,如果符合条件,设置标记isTrigger为true表示已经触发过
            self.isTrigger = true
            return true
        }

        return false    // 不符合触发条件,即为未触发
    }

    return self         // 返回构造完成的对象
}

function main() {
    var q = $.NewTaskQueue()
    var p = $.NewPositionManager()
    
    var t_m = CreateAlarmClock(9, 5)
    var t_a = CreateAlarmClock(14, 58)
    
    var symbol = "MA888"  
    while (true) {
        if (exchange.IO("status")) {
            exchange.SetContractType(symbol)
            var r = exchange.GetRecords()
            if(!r || r.length < 20) {
                Sleep(500)
                continue   
            }
            if (t_m.Check()) {     // 可以写: t1.Check()
                var fast = TA.MA(r, 10)
                var slow = TA.MA(r, 20)
                
                var direction = ""
                if (_Cross(fast, slow) == 1) {
                    direction = "buy"
                } else if(_Cross(fast, slow) == -1) {
                    direction = "sell"
                }
                if(direction != "") {
                    q.pushTask(exchange, symbol, direction, 1, function(task, ret) {
                        Log(task.desc, ret)
                    })
                }
            }

            if (t_a.Check()) {   // 可以写: t.Check()
                p.CoverAll()
            }

            q.poll()
            LogStatus(_D())
        } else {
            LogStatus(_D())
        }

        Sleep(500)
    }
}

首先创建函数名,包含两个参数,triggerhour和triggerminute,代表指定触发的小时和分钟数。

接着定义构造的对象self,然后给构造的对象设置成员、函数。

第一个isTrigger,判断当天是否触发过 第二个triggerHour,计划触发的小时 第三个triggerMinute,计划触发的分钟

nowDay,获取当前时间是哪日 接着我们定义一个检查函数check,检查触发,触发返回true,未触发返回false, 首先使用new Date获取当前时间对象t, getHours获取当前小数:0~23 getMinutes获取当前分钟:0~59 getDay获取当前天 这是为了获取实时的时间,去和指定的时间进行比较,检查是否触发。

接下来我们使用if语句判断,如果当前天,不等于记录的当天(day != self.nowDay),重置触发标记为未触发(self.isTrigger = false),更新记录的天数(self.nowDay = day)

下面我们就要判断具体的时间是否触发,如果isTrigger为假,但是触发时间已经过去了(当前小时等于触发的小时 并且 当前的分钟大于等于触发的分钟),这个时候设置标记isTrigger为true表示已经触发过,使用return语句check会返回true,证明已经触发。

不符合触发条件,使用return返回false,就是未触发。 总结一下,这个Check方法,用于检查是否满足触发条件。该方法获取当前时间并与预设的触发时间进行比较。如果满足触发条件且当天尚未触发过闹钟,则将isTrigger标志设置为true,表示已触发,并返回true。否则,返回false表示未触发。

所以这里大家理解的关键点是,这里的check函数只是在精准达到触发时间点,返回一个true,在触发时间点之前和之后,check函数都是false,而istrigger在触发时间前一直是false,而在触发时间后一直是true。

最后我们返回构造完成的对象self。

通过使用构造函数,可以方便地创建多个独立的闹钟对象,并且每个对象都具有自己的触发时间和触发状态。 设计好构造“闹钟”对象的函数,在使用时只需一行代码即可创建一个“闹钟”对象。

例如创建一个对象t,每天下午两点五十八分触发。

var t = CreateAlarmClock(14, 58)

我们来示范一下这个闹钟的效果。在主函数中,通过不断调用t1对象的Check方法来检查是否触发闹钟。如果触发时间到达,则打印"触发时间到达"的消息;否则,打印"触发时间未达或已过"的消息。这样就实现了一个简单的闹钟程序,可以不断检查是否达到设定的触发时间。

设置回测时间是9点整到9点十分,可以看到,在9点5分,check返回true,打印“触发时间到达”,而在其他时间段,check返回false,打印“触发时间未达或已过”。而istrigger在触发时间前,一直是false,在触发时间后,一直是true。

闹钟定好以后,我们就要使用它去设计一个定时启停的策略。 我们的思路是这样的:期货在开盘的分钟以内,价格波动比较剧烈,容易造成技术指标的跳跃去形成虚假的交易信号,我们可以选择跳过这段时间,减少错误的操作,所以我们策略开始的时间不再是早上的九点钟,而是九点零五分。而作为日内的交易策略,在下午即将要收盘的时候(两点五十八分),平掉所有的仓位。

我们使用一个简单的日内趋势线交易策略,以天数为周期,如果快线(周期为10的均线)大于慢线(周期为20的均线),代表最近呈现上升的趋势,我们制定的日内策略就要开多仓;相反情况,如果快线小于慢线,代表最近呈现下降的趋势,我们就要开空仓。然后,在下午两点五十八分,不论盈亏,准时平掉日内的仓位。这个策略利用了最近的趋势进行交易,同时严格控制风险。在下午收盘之前,无论盈亏情况如何,我们都要准时平仓,以避免持仓过夜的风险。通过这样的操作,我们能够在日内交易中抓住短期趋势并及时离场,以保护资金并获取较稳定的收益。

在主函数中,我们创建两个对象,t_m,早上的闹钟(9点5分),t_a,下午的闹钟(2点58分)。

使用交易类库创建一个单品种对象p,和多品种控制对象q。 设定合约symbol,玻璃主力。 接着进入我们的循环,获取k线,如果数量不足,跳出。 判断是否满足开仓的时间点,当t_m.Check()为true,代表触发时间到,说明到了开仓时间9点5分,开始我们的日内趋势性交易策略。 定义快线和慢线,使用TA.MA,分别设置period是10和20 。 设置一个变量direction方向为空。 使用我们前面在交易类库中讲过的cross函数,判断金叉,就是快线超过慢线,设置方向为buy,开多头。 如果是死叉,快线下穿慢线,设置方向为sell,开空头。 当存在明显趋势,就是direction不为空的时候,利用多品种控制对象q进行对应开多或者开空的交易。 不要忘了这里多品种控制对象还需要q.poll轮询任务。

最后在下午结尾时间段,使用第二个闹钟t_a.Check(),如果触发,说明到了指定的时间两点五十八分,使用coverall全部平仓。

这个策略适用于趋势比较强的品种,我们试一下。在回测页面,设置时间为2022年11月份,到2023年7月13日,请注意,这里的底层K线周期需要设置为分钟,不能设置过大,否则可能直接跳过时间检测的点导致没有触发。运行这个策略,我们获得了1500多元的收益。

我们看一下回测结果,可以看到九点五分,我们开始了策略,下午两点五十八分,平掉了所有的仓位。这就是一个使用定时控制的日内交易策略。我们的闹钟实现了在固定时间点定时做某事的初衷。这个闹钟也可以用于多品种策略,在多品种策略中可以创建多个这样的“闹钟”对象,用于多品种的时间控制,互不影响。

我们的策略是一个日内的趋势性策略,重点是让大家了解策略的定时设计。如果我们想真正的实现一个日内的波段策略,可以从istrigger方面考虑。因为istrigger在触发时间前是false,而在触发时间后是true。两个闹钟的交叉时间段,第一个istrigger为true,第二个istrigger为false的时候,我们在这中间的时间段,可以编写我们的日内策略。

但是需要注意的是,回测测试运行,底层K线周期不能设置过大,否则可能直接跳过时间检测的点导致没有触发。

其实这个功能可以应用于很多方面,比如实盘定时启停机器人,后续课程我们将利用这一个策略,创建一个管理机器人的机器人教大家如何四折使用优宽量化。

21:策略中的止盈止损设计(一)

交易界有句古老的谚语:“会买是徒弟,会卖是师傅”。顾名思义怎么卖比怎么买更难,因为在买入的时候只需要判断行情是否开始就可以了;但是一旦买进之后,不但需要判断行情是否转向,还需要时刻控制风险。相信很多交易者都经历过过山车的行情,明明上车了最后还是以小赚甚至亏损的结果出局。或者本来可以以小亏出局,结果从小亏损积累成大亏损。所以从这点看,卖出比买入更为重要。

简单的说,卖出无非是两种情况:止盈和止损。如果运气很好,买入后价格开始上涨,这时就要考虑止盈的问题,否则可能账面上赚到了钱,没有在合适的位置获利了结,最后平亏出局。如果运气不好,买入后不久价格就开始下跌,这时就要考虑止损了,或者在开仓买入之前就应该考虑好止损的位置,否则小亏损会积累成大亏损。

从统计学的角度看,大部分亏损的单子,在之后的行情里都会回到成本价附近。但是如果遇到一次小概率大幅反向走势,可能会损失之前所有的利润或本金。因此对我们散户来说,可以大赚,可以小赚,也可以小亏,但是永远不能大亏。所以有这么一句话:止损让我们活着,止盈让我们活得更好。

今天我们就来介绍五种常见的止盈止损策略,分别是

  • 固定比例止盈止损
  • 移动止盈止损
  • 动态止盈止损
  • 波动性止盈止损
  • 时间止盈止损

固定比例止盈止损

固定比例止盈止损策略,是一种基于盈利和亏损的比例,设定止盈点和止损点的交易策略。它的思想很简单,该策略基于设定的固定比例来确定止盈和止损点。当价格达到预设的盈利目标时,进行止盈;当价格下跌达到预设的损失限制时,进行止损。

为了方便演示,本节课我们使用的都是多头止盈和止损的策略设计,对于空头的止盈止损策略,大家只需要改变少许参数就可以。

这个策略的思路是基于固定百分位止盈止损设计的。对于多头开仓,我们使用的都是当最新价格超越20均线时,进行入场。大家也可以设置别的入场信号,比如macd,双均线等等。 具体代码实现是这个样子的:

/*backtest
start: 2023-07-10 09:00:00
end: 2023-07-10 11:10:00
period: 1m
basePeriod: 1m
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES","depthDeep":20}]
mode: 1
*/

// 固定百分位止盈止损设计
function main(){
    var mp = 0;
    var buy_price = 0;
    var takeProfitPrice = 0;
    var stopLossPrice = 0;
    var p = $.NewPositionManager();
    var c = KLineChart();
    
    while(true){
        if(exchange.IO("status")){
            LogStatus(_D(), "已经连接CTP !");
            exchange.SetContractType("MA888");
            var records = exchange.GetRecords();
            
            if (!records || records.length < 20) {
                Sleep(1000); // 等待获取足够多的K线数据
                continue;
            }

            ma20 = TA.MA(records, 20)

            if (mp == 0 && records[records.length-1].Close > ma20[ma20.length-1]) { // 大于20均线,下多单
                p.OpenLong('MA888', 1);
                pos = p.GetPosition("MA888", PD_LONG);
                buy_price = pos.Price;
                mp = pos.Amount;
            }

            var takeProfitRatio = 0.005; // 止盈比例
            var stopLossRatio = 0.005;   // 止损比例
    
            // 根据开仓价计算止盈和止损价格
            var takeProfitPrice = buy_price * (1 + takeProfitRatio);
            var stopLossPrice = buy_price * (1 - stopLossRatio);

            if (mp == 1 && records[records.length-1].Close > takeProfitPrice) { 
                Log('止盈时间到');
                p.Cover('MA888');
                var buy_price = 0;
                var takeProfitPrice = 0;
                var stopLossPrice = 0;
                var mp = 0;
            }

            if (mp == 1 && records[records.length-1].Close < stopLossPrice) { 
                Log('止损时间到');
                p.Cover('MA888');
                var buy_price = 0;
                var takeProfitPrice = 0;
                var stopLossPrice = 0;
                var mp = 0;
            }

            var buy_price_dw = buy_price == 0 ? NaN : buy_price;
            var takeProfitPrice_dw = takeProfitPrice == 0 ? NaN: takeProfitPrice;
            var stopLossPrice_dw = stopLossPrice == 0 ? NaN : stopLossPrice;

            for (var i = 0; i < records.length; i++) {
                var bar = records[i];
                c.begin(bar);
                c.plot(ma20[ma20.length - 1], "均线20", { overlay: true });
                c.plot(buy_price_dw, "买入线", { overlay: true });
                c.plot(takeProfitPrice_dw, "止盈线", { overlay: true });
                c.plot(stopLossPrice_dw, "止损线", { overlay: true });
                c.close();
            }
            
        } else {
            LogStatus(_D(), "未连接CTP !")
        }
    }
}

首先设置一些变量,包括持仓变量,mp。实时买入价格buy_price,止盈价格takeProfitPrice,止损价格stopLossPrice,为了提升交易的成功率,我们这里使用了交易类库,同样这里为了方便画图,使用KLineChart函数。

按照固定的流程,首先检查ctp协议是否连接,接着设定合约甲醛主力,然后获取最新的K线数据和Ticker数据。 等收集够足够的k线数量后,使用移动平均线(MA)计算20均线(ma20)。

如果当前持仓为空(mp == 0),并且当前Ticker的买入价格大于20均线的最新值,就使用openlong执行下多单操作。下单的价格和实时的仓位信息的获取,可以通过getposition函数,通过price和amount属性,获取实时的买入价格和持仓数量。

固定的止盈止损比例是我们自己设定的,这里我们设定止盈比例(takeProfitRatio)和止损比例(stopLossRatio),都是0.005,这两个比例大家可以根据自己的交易理念进行修改。接下来根据买入价计算出止盈价格(takeProfitPrice)和止损价格(stopLossPrice)。这就是我们设定的止盈点位和止损点位。

接着进入我们的止盈和止损设计了。对于止盈操作, 如果持仓不为空(mp == 1)且K线数据的最新收盘价大于止盈价格,则执行止盈操作。使用交易类库的cover函数进行平仓。由于这一单已经完结,因此可以重新设置买入价,止盈价和止损价都为0,并将mp标记为0表示无持仓。

对于止损操作,如果持仓不为空(mp == 1)且K线数据的收盘价小于止损价格,则执行止损操作。止损平仓操作完成,同样重新设置mp,买入价,止盈价和止损价。

最后,使用klinechart函数,将20均线、买入价、止盈价和止损价绘制在图表上。在图表中,当不下单的时候,买入价、止盈价和止损价都是0,图表的跨度会非常大,比较难看。所以这里我们使用了三元表达式,当买入价、止盈价和止损价是0的时候,设置为nan。

循环进行上述操作,实现实时的交易决策和止盈止损操作。

在回测结果中可以看到,我们设置周期为分钟,当达到开仓条件,会执行开仓的操作,并且设置相对应的止盈和止损线,当最新的价格达到固定比例的止盈或者止损价格的时候,会进行对应的止盈或者止损的平仓操作。

总体来说,这个策略通过固定百分位止盈止损设计,根据价格与均线的关系进行交易决策,并且根据买入价设定止盈和止损价格,以限制风险和保护收益。

除去固定比例之外,我们也可以使用固定的点位变化进行固定点数止盈止损操作。当盈利或者亏损达到一定点数之后,进行迅速的平仓,实现止盈止损的目的。

移动止盈止损

固定的比例或者点位进行止盈止损有时候太过于死板。当我们主动止盈时,可能会遇到一大波行情,只赚到其中一小部分就错过了后续更大的利润。虽然这样的交易并不亏损,但心理上会有一种错失良机的遗憾感。为了解决这个问题,移动止盈止损策略采用了浮动止损的方法,就是在获得不同级别的浮动盈利之后,开启下一级主动止损模式。这样一方面可以持续享受较高的浮动盈利,同时也不断提高止损点位,用来实现更多盈利的目标。

这个策略实际上只有止损,没有止盈,随着盈利比例的提高,止损点也随之上移。这样可以更好地抓住市场的上涨趋势,同时尽量的减少亏损。

/*backtest
start: 2023-07-10 09:00:00
end: 2023-07-11 15:00:00
period: 1m
basePeriod: 1m
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES","depthDeep":20}]
*/

// 移动止盈止损

function main() {
    var mp = 0;        // 持仓状态,0表示空仓,1表示持仓
    var buy_price = 0; // 开仓价格
    var takeProfitRatio = 0.005; // 初始盈利比例
    var stopLossRatio = 0.01;    // 初始止盈止损比例
    var p = $.NewPositionManager();
    var c = KLineChart();

    while (true) {
        if (exchange.IO("status")) {
            LogStatus(_D(), "已经连接到CTP!");

            exchange.SetContractType("MA888");
            var records = exchange.GetRecords();

            if (!records || records.length < 20) {
                Sleep(1000); // 等待获取足够多的K线数据
                continue;
            }

            var ma20 = TA.MA(records, 20); // 计算20日均线

            if (mp == 0 && records[records.length - 1].Close > ma20[ma20.length - 1]) { // 大于20日均线,下多单
                p.OpenLong('MA888', 1);
                pos = p.GetPosition("MA888", PD_LONG);
                buy_price = pos.Price;
                mp = pos.Amount;
            }

            var takeProfitPrice = buy_price * (1 + takeProfitRatio);   // 计算盈利价格
            var stopLossPrice = takeProfitPrice * (1 - stopLossRatio); // 计算止盈止损价格

            if (mp == 1 && records[records.length - 1].Close > takeProfitPrice) {
                Log("达到新的赢利点,更新比例和价格");
                takeProfitRatio += 0.005;                  // 增加盈利比例
                var takeProfitRatio = _N(takeProfitRatio); // 格式化浮点数
                var takeProfitPrice = buy_price * (1 + takeProfitRatio);     // 更新盈利价格
                stopLossRatio -= 0.001;                    // 更新止盈止损比例
                stopLossRatio = _N(stopLossRatio);         // 格式化浮点数
                stopLossPrice = takeProfitPrice * (1 - stopLossRatio);       // 更新止盈止损价格
                Log("更新盈利比例:",takeProfitRatio);
                Log("更新止盈止损比例:",stopLossRatio);
            }

            if (mp == 1 && records[records.length - 1].Close < stopLossPrice) {
                Log("达到止盈止损点,平仓并退出交易");
                p.Cover('MA888')
                buy_price = 0; // 重置开仓价格
                takeProfitRatio = 0.005;
                stopLossRatio = 0.01;
                mp = 0;
            }

            var buy_price_dw = buy_price == 0 ? NaN : buy_price;
            var takeProfitPrice_dw = takeProfitPrice == 0 ? NaN: takeProfitPrice;
            var stopLossPrice_dw = stopLossPrice == 0 ? NaN : stopLossPrice;

            

            for (var i = 0; i < records.length; i++) {
                var bar = records[i];
                c.begin(bar);
                c.plot(ma20[ma20.length - 1], "均线20",  { overlay: true });
                c.plot(buy_price_dw, "买入线", { overlay: true });
                c.plot(takeProfitPrice_dw, "止盈线", { overlay: true });
                c.plot(stopLossPrice_dw, "止损线", { overlay: true });
                c.close();
            }

            Sleep(1000)
        } else {
            LogStatus(_D(), "未连接CTP!");

            Sleep(1000)
        }
    }
}

这个策略是一个移动止盈止损的交易策略。下面是这个策略的思路:

  • 初始化一些变量,包括持仓状态(mp)、开仓价格(buy_price)、初始盈利比例(takeProfitRatio)和止盈止损比例(stopLossRatio)。
  • 同样的固定程序,连接交易所,获取K线和行情数据。检查K线数据数量。
  • 计算20日均线(ma20)。
  • 空仓状态(mp == 0)使用20均线下多单,记录开仓价格并将持仓状态置为持仓(mp = 1)。
  • 根据初始盈利比例和止盈止损比例计算盈利价格(takeProfitPrice)和止损价格(stopLossPrice)。
  • 如果当前处于持仓状态(mp == 1)且最新的收盘价大于止盈价格,就要增加盈利比例,并更新盈利价格和止盈止损价格。

这里的关键是盈利比例和止盈止损比例的更新。

首先通过条件判断确认当前处于持仓状态(mp == 1)且当前收盘价超过上一个阶段的盈利价格。 如果满足条件,那么表示达到了新的盈利点,打印“达到新的赢利点,更新比例和价格”。 接着,将止盈比例(takeProfitRatio)增加0.005。 根据新的止盈比例,更新止盈价格(takeProfitPrice),计算方法为买入价格(buy_price)乘以(1 + 新的takeProfitRatio)。 新的盈利价格更新,这个时候减少止盈止损比例(stopLossRatio)0.001。

这里使用了_N格式化浮点数。最后,根据新的止盈价格和止损比例,更新止损价格(stopLossPrice),计算方法为止盈价格乘以(1 - stopLossRatio)。

最后显示更新后的止盈比例和止损比例。所以当价格呈现一定的上涨趋势,止盈止损的价格也会一直提升。一旦出现当前处于持仓状态且最新的收盘价小于止损价格的状况时,就会平仓并退出交易,重置开仓价格和相关变量。最后使用klinechart进行绘图的操作。

该策略的核心思想是根据价格与均线的关系判断买入和卖出时机,并根据盈利情况逐步调整比例和止盈止损比例。 在回测结果中可以看到,当价格达到新的盈利价格时,会增加盈利比例并更新盈利价格和止盈止损价格,从而保护已实现的盈利。而当价格达到止盈止损价格时,就会平仓并退出交易,可以限制损失。图表中我们也可以看到,相对于固定的止盈和止损点位,移动的止盈和止损线是会实时更新的。

通过使用移动止盈止损策略,我们可以更加灵活地应对市场变化,逐步提高止盈点位以获取更多的收益。这种策略能够在保护利润的同时,也给予市场更多的空间,以便捕捉更大的行情。当然,在实际操作中,需要基于市场情况和自身风险承受能力进行合理的设置和调整。 总之,通过采用浮动止损的移动止盈止损策略,我们可以在控制风险的同时,最大限度地提高利润。这种策略的灵活性和适应性,使得我们能够更好地应对市场的变化,规避踏空的遗憾感,并在交易中获得更多的盈利。

22:策略中的止盈止损设计(二)

本节课我们继续策略中的止盈和止损设计,本节课我们要介绍的三种策略是:动态止盈止损,波动性止盈止损和时间止盈止损。

动态止盈止损

动态止盈止损策略利用技术指标或其他市场信号来确定止盈和止损点。通过分析市场趋势指标,决定何时进行止盈和止损操作。因为指标实时计算的,所以这种策略能够根据市场情况作出灵活的决策。

移动止盈止损和动态止盈止损虽然有些相似,但在概念和实施方式上存在一些区别。移动止盈止损是通过逐步调整止盈和止损点位的数值来实现的。比如,当获得一定的浮动盈利后,将止盈点位从初始设定的水平A移动到更高的水平B,以获取更多的利润。而动态止盈止损是根据市场行情和交易情况进行实时调整的。对于止盈点位,可以根据技术指标或者其他因素来设定触发条件,当满足条件时进行止盈操作。对于止损点位,可以根据风险管理原则和市场情况来灵活设定。

动态止盈止损的策略有很多种,以下是其中几种常见的策略: 移动平均线策略:这个大家都很熟悉,该策略利用移动平均线来确定止盈和止损点位。

  • 波动带宽策略:这个前面我们也讲解过,代表策略,布林带等波动带宽指标来确定止盈和止损点位。
  • 技术指标策略:该策略使用技术指标作为判断依据来设定止盈和止损点位。常用的技术指标包括相对强弱指标(RSI)、移动平均收敛/扩展指标(MACD)等。根据技术指标的信号来调整止盈和止损点位,例如当RSI超过一定阈值时设定止盈点位,低于一定阈值设置止损点位。
  • 支撑阻力位策略:该策略基于价格的支撑位和阻力位来设定止盈和止损点位。

相对于直接使用价格来判断与止盈线和止损线的交叉,我们也可以使用技术指标来辅助判断止盈时机和止损时机。其中,相对强弱指标(RSI)是一种常用的技术指标之一。

RSI指标衡量了一段时间内价格上涨和下跌的强度,其数值范围在0到100之间。一般来说,当RSI数值超过70时,说明市场可能过热,即将出现调整或反转信号。此时可以考虑平仓并获利,进行止盈。当RSI数值低于30时,说明市场可能过冷,可能会有反弹或反转信号。此时可以考虑平仓止损,以防价格进一步下跌。

所以RSI指标进行止盈和止损时,可以将以下条件作为参考:

  • 止盈条件:当RSI指标的数值超过一个预设的阈值(如70)时,可以考虑进行止盈操作。
  • 止损条件:当RSI指标的数值低于一个预设的阈值(如30)时,可以考虑进行止损操作。

我们来看一下代码,因为我们使用rsi作为止盈和止损的信号判断,这里不需要设置买入价,止盈价和止损价,只需要设置持仓变量。

在连接ctp协议,设定主力合约获取k线数据,然后使用K线数据进行均线和rsi信号的计算,这里的rsi参数是14。同样的使用20均线作为多头开仓的信号,但是需要注意的是,由于我们使用了rsi信号,因此可以增加一个判断条件,当不处于超买区域,就是rsi小于70的时候,再进行开仓,这样可以避免在rsi在70左右徘徊的时候,频繁的开仓平仓造成手续费的浪费。我们使用的是倒数第二根rsi的值,这样是为了防止最新的k线没有走完,rsi会发生频繁变动。

然后我们设置开仓和平仓的信号,当持有仓位并且rsi大于70的时候,说明进入了超买区域,这个时候需要进行止盈,进行平仓。

另一方面,当rsi小于30的时候,进行止损。最后使用klinechart进行图表的绘制,我们将均线画在主图上,rsi和上限70还有下限30画在附图上,使用overlay=false就可以。

最后在回测结果中看到,当满足开仓条件后,使用实时计算出来的rsi数据,去判断止盈和止损的信号,当达到设定的阈值,会进行相应的止盈和止损操作。

可以发现,使用成熟的指标进行止盈止损的操作,似乎更加方便一点,不需要考虑买入线,止盈线和止损线的关系,只需要使用指标进行止盈止损的信号判断。但是需要注意的是,成熟指标可能在特定市场环境下失效或产生误导性的信号。并且单一指标的使用可能会导致误判或信号滞后。因此,必须对当前市场进行充分的分析和判断,结合其他技术指标和因素来确认止盈止损策略。

这些策略仅为示例,实际上还有许多其他动态止盈止损的策略,如基于市场走势形态、价格动能等的策略。选择适合自己的策略需要考虑个人的交易目标、风险承受能力和市场特点,同时建议在实践中进行更多策略参数的验证和调整。

波动性止盈止损策略

该策略基于市场的波动性来确定止盈和止损点。当市场波动性较高时,设定较宽松的止盈和止损点;而当市场波动性较低时,设定较紧密的止盈和止损点。这样可以根据市场行情的不同情况来动态调整止盈止损的幅度。

波动性止盈止损的原理是这样的:

  • 波动性测量:首先,需要度量市场的波动性。常用的指标包括标准差、平均真实范围(ATR)等。这些指标可以帮助确定价格的波动范围。

  • 止盈/止损点位:接着根据波动性指标,可以设定一个合理的止盈/止损点位。止盈/止损点位应该超过当前价格的波动范围,以确保能够捕捉到足够的利润和限制损失。例如,可以将点位设定在标准差或ATR的倍数之外。

随着市场的波动性变化,波动性止盈止损策略的止盈和止损点位是动态调整的。例如,如果市场波动增加,可以适当扩大止盈和止损点位;如果市场波动减小,可以适当缩小止盈和止损点位。

/*backtest
start: 2023-07-10 09:00:00
end: 2023-07-10 11:10:00
period: 1m
basePeriod: 1m
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES","depthDeep":20}]
mode: 1
*/

// 波动止盈止损
function main(){
    var mp = 0
    var buy_price = 0
    var takeProfitPrice = 0
    var stopLossPrice = 0
    var c = KLineChart()
    while(true){
        if(exchange.IO("status")){
            LogStatus(_D(), "已经连接CTP !")
            exchange.SetContractType("MA888")
            var records = exchange.GetRecords();
            var ticker = exchange.GetTicker();
            if (!records || records.length < 20) {
                Sleep(1000); // 等待获取足够多的K线数据
                continue;
            }
            
            ma20 = TA.MA(records, 20)

            if (mp == 0 && ticker.Buy > ma20[ma20.length-1]) { // 大于20均线,下多单
                exchange.SetDirection("buy")
                id = exchange.Buy(ticker.Buy + 2, 1)
                var order = exchange.GetOrder(id)  
                var buy_price = order.Price
                mp = 1
            }

            var std = talib.STDDEV(records, 14) //计算标准差
            current_std = std[std.length-1]
    
            // 根据开仓价计算止盈和止损价格
            var takeProfitPrice = buy_price + 2*current_std; //两倍标准差区间
            var stopLossPrice = buy_price - 2*current_std;

            if (mp == 1 && records[records.length-1].Close > takeProfitPrice) { 
                Log('止盈时间到')
                exchange.SetDirection("closebuy")
                exchange.Sell(records[records.length-1].Close, 1)
                buy_price = 0; // 重置开仓价格
                var takeProfitPrice = 0;
                var stopLossPrice = 0;
                mp = 0
            }
            if (mp == 1 && records[records.length-1].Close < stopLossPrice) { 
                Log('止损时间到')
                exchange.SetDirection("closebuy")
                exchange.Sell(records[records.length-1].Close, 1)
                buy_price = 0; // 重置开仓价格
                var takeProfitPrice = 0;
                var stopLossPrice = 0;
                mp = 0
            }
            buy_price_dw = buy_price == 0 ? NaN : buy_price
            takeProfitPrice_dw = takeProfitPrice < 100 ? NaN: takeProfitPrice
            stopLossPrice_dw = stopLossPrice < 100 ? NaN : stopLossPrice
            for (var i = 0; i < records.length; i++) {
                var bar = records[i];
                c.begin(bar);
                c.plot(ma20[ma20.length - 1], "均线20", { overlay: true });
                c.plot(buy_price_dw, "买入线", { overlay: true });
                c.plot(takeProfitPrice_dw, "止盈线", { overlay: true });
                c.plot(stopLossPrice_dw, "止损线", { overlay: true });
                c.plot(current_std, "std", { overlay: false });
                c.close();
            }
            
        } else {
            LogStatus(_D(), "未连接CTP !")
        }
    }
}

这个代码是一个基于波动止盈止损的简单示例策略,适用于期货交易市场。以下是该策略的整体思路: 同样的,使用20周期均线作为基准,超过均线进行开多的(做多)操作。 使用标准差来计算止盈和止损价格。标准差是衡量价格波动性的指标,这里取当前标准差的两倍作为止盈和止损的价格区间。

实时波动性体现在这段代码中的两个方面:

标准差(std):在代码中使用了 talib.STDDEV() 函数计算标准差,以衡量价格的波动性。标准差是一种统计指标,用于测量数据的离散程度或波动性。通过计算价格数据的标准差,代码可以获取最近一段时间内价格的波动性信息。 波动性止盈和止损价格:在代码中根据开仓价计算了止盈(takeProfitPrice)和止损(stopLossPrice)价格,用于设置交易的止盈和止损条件。这里使用了当前标准差的两倍作为止盈和止损线的位置,即买入价加上两倍标准差和减去两倍标准差。 通过实时计算标准差并基于标准差设置止盈和止损价格,代码可以根据实时价格的波动性进行交易决策。如果当前价格超过止盈价格,则执行止盈操作;如果当前价格低于止损价格,则执行止损操作。这样就能够根据实时波动情况进行风险控制和收益保护,提高交易的效果。

可以在回测结果中看到,止盈和止损之间的距离并不是固定不变的,会随着价格的波动程度,区间进行动态的调整,因此在一定程度上具有更强的适用性。

这里的标准差周期(period)和标准差倍数(multiple)都是可以实时调整的,可以针对于不同种类的期货品种,例如波动性较大的化工类(纯碱,甲醇等),和波动性较小的农产品类(玉米和淀粉类)进行参数的调整。

时间止盈止损策略

时间止盈止损策略是一种基于持仓时间来设定止盈和止损条件的交易策略。它与传统的价格或指标止盈止损策略不同,它主要根据持仓时间来判断是否平仓。在时间止盈止损策略中,交易者会设定一个特定的时间阈值作为止盈时间和止损时间。当持仓时间达到设定的时间阈值时,交易者会平仓并退出交易。这种策略的核心思想是,在一段时间内,交易市场可能会出现波动和反转,因此设定一个时间限制来控制交易的持续时间,以避免过长时间的持仓可能导致的风险增加。

需要注意的是,时间止盈止损策略可能适用于某些特定的市场情况,但并不适用于所有情况。在使用该策略时,务必要结合市场趋势、价格波动等因素进行综合考虑,并严格控制风险,避免无谓的损失。同时,交易者也可以根据自己的交易风格和偏好,灵活调整时间止盈止损的阈值,并进行回测和优化,以寻找最适合自己的策略参数。我们前面课程讲到的《策略定时设计》中提到的日内趋势性策略就是这样的交易理念。大家可以重温复习下。

特定市场情况:
短期趋势交易:当市场处于短期明确的上升或下降趋势时,时间止盈止损策略可以在趋势延续的初期进行交易,并在设定的时间阈值内通过止盈或止损来退出交易。
波动性较高的市场:在市场波动性较高的情况下,价格会快速上涨或下跌,因此通过设置时间止盈止损可以避免在持仓时间过长时受到大幅波动的影响。
震荡市场:当市场处于震荡状态且没有明显的趋势时,价格反复上下波动,时间止盈止损策略可以帮助交易者利用市场的震荡进行频繁小幅的利润获取。

今天我们讲了五种止盈止损的设计,大家也不一定要严格纠结于这些概念的区别,并且市面上还存在许多止盈止损策略,重要的是理解这些策略的思想,以及如何用代码实现这些策略。希望通过本课程的学习,大家可以了解止盈止损策略的基本原理,理清其思路,并根据自己的交易理念,构建更适合自己的止盈止损策略。希望本课程能为你提供有益的指导和启发,帮助你在交易中更加有效地运用止盈止损策略。

23:定时启停机器人

大家都知道,由于中国商品期货交易时间规则限制,白天只有4个小时的交易时间,夜盘只有2~4个小时的交易时间,再加上某些品种还没有夜盘交易时间,综合下来平均一天只有6个小时的交易时间。如果使用优宽量化机器人24小时不停运行策略,则有一半多的时间是在无谓的消耗。所以很多人定个闹钟来启动和停止机器人,但这么做并不能保证每次的准时,而且每次需要登录网页并不方便。本节课我们就创


更多内容