UPBTC是一款为中高端专业投资者开发的数字货币交易客户端。
软件整合了多家数字货币交易所的实时行情和账户体系,方便的查看多平台行情,快速下单。
提供专业的k线图功能,投资者可以自行编写个性化的技术指标,实时输出到k线图进行技术分析。
条件单帮您监控行情,实现跟踪止盈、止损止盈。
下单长时间不成交,或下单后价格偏离过大时,交易助手自动撤单重发,保证成交。
使用历史K线模拟运行策略交易,生成回测报告,策略历史绩效和优劣一目了然。
量化交易,编写自己独特的交易策略,24小时监控行情,满足策略条件时,实时下单。
专用的交易脚本语言,语法简单,无需编程基础快速上手。内置大量交易函数,简单快速的将自己的交易系统转化为代码,让电脑帮您赚钱。
持仓监控,自动监控账户真实持仓是否与任务总持仓一致,不一致时自动纠正同步。
我们致力于提供辅助加密货币投资者盈利的工具,希望使用者能够通过使用本软件,建立并优化自己的交易系统,形成自己稳定盈利的交易策略。
在解释语法规则之前,为了更好的理解,我们先简单的说下交易策略运行的整个流程。
策略运行流程
一个完整的交易流程可以分为三个部分,接收行情、思考判断、下单(或不下单)。同样的,量化交易系统也是这样运行的。
首先我们需要选择想交易的标的,然后加载交易策略,交易策略就是把我们人脑对行情的思考判断量化下来,每当收到新的行情,就判断是否达到设定的下单条件,如果达到就发单,没达到就继续监控行情。
我们用伪代码把整个流程梳理一下。
循环:每当(收到新的报价)时 执行{ 任务读取行情 运算策略 如果(满足策略下单条件) 执行 { 下单 K线上标记下单信号 }}
UPBTC平台会帮您做除交易策略以外的所有事情,您只需要把交易思路,用专用的Up Language语言写成策略,然后就可以完全自动的盯盘交易了,是不是很简单?
示例策略
我们在任意一个标的k线上,加载一个已经内置的示例策略(Strategy_DualMA),现在我们来看看这个策略是怎么运行的。
下面是这个策略的代码(您也可以自己在“量化-策略公式”页面查看),并对策略做了一些简单的注释。
如果中间有看不懂的内容,您可以先跳过,继续往下看。先大体理解运行原理,后面仔细看Up Language语法规则。
策略代码由参数(Params),变量(Vars)以及Begin与End之间的策略主体三部分组成。所有代码内容都不区分大小写。
这里我们将代码逐行翻译成自然语言,以便理解。
在编译器输入框编写代码时,可以使用//+注释,方便以后自己调试修改时,快速理解代码。//之后的内容,编译器不会运行,不需要遵守策略语法。
// 参数(Params)部分。参数是可以在量化任务中修改,传给策略使用的值。 // Numeric是声明参数的类型,表示这个是数值型的参数。FastLength是参数的名称,我们用来表示短均线的根数长度,任何时候使用这个名称就会获取到对应的值。小括号内是具体的值。一行用分号结束。Params // 告诉策略编译器,下面是声明的参数 Numeric FastLength(5); // 短均线使用的长度 Numeric SlowLength(20); // 这个是长均线的长度 Numeric Lots(1); // 下单时使用的交易数量 // 变量(Vars)部分,变量可以理解为存放值的盒子,我们可以把参数或者其他变量,通过运算后得到的值赋给一个变量。然后我们直接使用变量名称就会获取到存进去的值 // 序列变量是一长串变量,在每根k线上可以存为不同的值Vars // 告诉策略编译器,下面是声明变量 NumericSeries AvgValue1; // 数值型序列变量。平均值变量1,用来存放计算出的,短均线的值。 NumericSeries AvgValue2; // 平均值变量2,用来存放长均线的值 // Begin和End之间的是代码的主体部分Begin // 策略主体开始 AvgValue1 = AverageFC(Close,FastLength); // 使用快速求平均函数,传入k线的收盘价和要计算的短均线的长度。然后将求得的平均值赋给平均值变量1 AvgValue2 = AverageFC(Close,SlowLength); // 同理,计算长均线的值并赋值给变量2 PlotNumeric("MA1",AvgValue1); // 在k线上画线,画短均线,名称为MA1 PlotNumeric("MA2",AvgValue2); // 画长均线 // 如果(现在策略持仓不是多头,并且上一根k线上的短均线的值,大于长均线的值),则运行下面花括号内的代码。序列变量后面加中括号是取回溯值,[1]表示取上一根k线上的这个序列变量的值,后面会详细说明 If(MarketPosition != 1 && AvgValue1[1] > AvgValue2[1]) { Buy(Lots,Open); // 开多单,如果有空单,系统会自动平掉空单再开多。 } // 如果(现在策略持仓不是空头,并且上一根k线上的短均线的值,小于等于长均线的值),则运行下面花括号内的代码 If(MarketPosition != -1 && AvgValue1[1] < AvgValue2[1]) { SellShort(Lots,Open); // 开空,如果有多单,系统会自动平掉多单再开空。 }End // 策略结束 https://www.UpBTC.com 示例策略
通过对这个策略的解释,您应该对交易策略的运行原理有一个大体的理解了,有没有明白的地方也没关系。下面我们仔细的学习Up Language语法规则。
Up Language语法规则
基础定义
Bar数据(非常重要)
每一根k线,都是一个bar。策略建立在Bar数据这个基本数据源之上。
Bar跟周期相关,在日线周期上,一天是一个bar。在15分钟周期上,15分钟是一个bar
Bar按时间排列,形成序列数据。最新的一个bar我们定义为Bar[0],往前回溯,为bar[1],bar[2]…,使用最新的信息时,索引[0]可以省略。
行情可以理解为数据和时间的序列,k线从左往右按时间排序,交易策略同样从左往右逐根运行。
在设定的时间段内的所有k线,从第一根到最后一根,在每一根k线上,从上到下计算运行一次策略内的代码。
在盘中时,每收到一个新的行情报价,都会驱动运行一次策略。
每个bar上都包含时间、行情信息、标的的基本属性信息,以及策略的一些信息等。
Bar和bar上的信息,都是序列数据,支持回溯。即对数据的向前引用。比如我们要获取上一个bar的收盘价信息,可以使用Close[1];前10个bar的行情小时信息,可以使用hour[10]。仅可为正整数。回溯是交易策略的非常重要的机制。
序列数据作为普通计算机语言和Up Language的重要区别,是进行金融序列数据计算的核心。此文档多处都会提到并使用序列数据,需要仔细理解。如果回溯的长度超过数据的有效长度,将返回第一个bar的值。
例如一共只有10个bar,close[10]超出了第一根bar(最新的bar是bar[0],10根bar的话,第一根bar是bar[9]),最多只能回溯到close[9]。所以close[10]将返回close[9]的值。
数据类型
三种基本的数据类型:数值型、字符串、布尔型。每种都支持引用和回溯。如下表:
类型 | 描述 |
---|---|
Bool | 布尔型 |
BoolRef | 布尔型引用 |
BoolSeries | 和周期长度一致的布尔型序列值,可以回溯 |
Numeric | 数值型 |
NumericRef | 数值型引用 |
NumericSeries | 和周期长度一致的数值型序列值,可以回溯 |
String | 字符串 |
StringRef | 字符串引用 |
StringSeries | 和周期长度一致的字符串序列值,可以回溯 |
引用类型仅可作为参数,在用户函数需要输出多个值时来使用。用户函数执行完毕后,函数对引用参数赋值,调用的策略会通过传给引用参数的变量获得修改后的值。具体的用法可以参考内置函数Sar和parabolicsar。
1.0.2
增加全局数值变量类型GlobalNumeric,全局变量会保存最后一次的赋值,在打开软件没有关闭的整个周期一直有效,并且可以跨策略传递值,只需要策略声明了同一个全局变量即可。全局变量的声明和普通变量一致,GlobalNumeric
MyNumeric;全局数值变量默认值是0,最好不要自己赋默认值,因为有可能多个策略的默认值造成冲突。
在任务A的策略中对MyNumeric赋值,任务B的策略可以直接取MyNumeric,得到任务A赋的值。
通过全局变量可以实现跨周期取值,多标的对冲套利能功能。
命名规则
参数、变量和策略的命名规则:
仅支持英文,不区分大小写;
不能超过32个字符;
所有公式的名字不可重复,一个策略内不能有相同的参数和变量名字;
不能和系统内部的保留字重复;
参数和变量不能和策略公式名重复。
语句
一行语句以“;”英文的分号结束;
仅支持英文半角符号;
可以将True、False和判断表达式赋值给布尔变量,myBool = true;或myBool1 = Open > Close;
可以将数值型或计算结果为数值型的公式赋值给数值变量,myNumeric = 100;或myNumeric1 = (high+low)/2;
可以将文本字符赋值给字符串变量,myString = “text(value)”;或myString1 = “文字”;
运算符和功能关键字
运算符:
类型 | 描述 |
---|---|
算数运算符 | 加+ 减- 乘* 除/ 求模% |
关系运算符 | 大于>,小于<,大于等于>=,小于等于<=,相等==,不相等!=和<> |
赋值 | = (注意:在程序中,一个等号代表的意思不是等于,而是对变量赋值。要判断两个值是否相等,需要使用“==”) |
逻辑运算符 | and &&(逻辑与),or (逻辑或),not !(逻辑非) |
括号 | 方法的参数和计算优先权(),代码块 {},回溯 [] |
标点 | 当方法有多个参数时,用逗号“,”分割。用分号 “;”结束一行语句。用双引号 “text”表示字符串。“.”保留未定 |
注释 | //或者 /* */ |
功能关键字
关键字 | 描述 |
---|---|
Params | 宣告参数起始,参数必须有默认值 |
Vars | 宣告变量起始,变量可以没有初始值。变量不可使用引用型数据。 |
IF | 条件语句 |
Else | 条件语句 |
Begin | 策略主体开始 |
End | 策略主体结束 |
For | 循环 |
To | 递增循环 |
Downto | 递减循环 |
While | 循环直到满足条件 |
Break | 跳出循环 |
Continue | 结束本次循环继续运行循环 |
True | 布尔型数据的真 |
False | 布尔型数据的假 |
Return | 返回并结算这一轮代码运行。用户函数用Return 变量名,来返回变量输出。 |
ReturnThisBar | 返回并结束这跟bar上的代码运行,直到新的bar产生。 |
控制语句
If条件语句,如果满足条件,则执行后面花括号内的代码。如果公式只有一行,可以省略花括号。两行或以上必须有花括号。
If(Condition)
{
公式;
}
If-Else语句,如果满足条件,就执行公式1,否则执行公式2。
If(Condition)
{
公式1;
}
Else
{
公式2;
}
If-Else if-Else语句,如果满足条件1,就执行公式1;如果不满足条件1,满足条件2,就执行公式2;不满足条件1和条件2,就执行公式3。
满足条件1时,执行完公式1后,整个语句就结束了,不会再判断条件2。不满足条件1,满足条件2,执行公式2后,不会再执行公式3。If(Condition1)
{
公式1;
}
Else If(condition2)
{
公式2;
}
Else
{
公式3;
}
条件语句可以嵌套。
If(Condition1)
{
If(Condition2)
{
公式1;
}
Else
{
公式2;
}
}
Else
{
公式3;
}
For循环,循环变量为已声明的变量,循环将循环变量从初始值到结束值,每次以1为步长递增(to)或递减(downto),重复执行公式。直到循环变量超出结束值。
For 循环变量=初始值 to/downto 结束值
{
公式;
}
While循环,条件为真,则重复运行公式,直到条件为假。注意如果条件恒为真,会造成死循环。需要有跳出循环的出口。
While(Condition)
{
公式;
}
Break,跳出循环。While判断条件恒为真,直到condition条件为真时,执行break,跳出此循环,继续往下执行其他的代码。
While(True)
{
公式;
If(Condition)
Break;
}
Continue,当条件2为真时,不会执行公式2,直接进入下一个循环。
While(Condition1)
{
公式1;
If(Condition2)
Continue;
公式2;
}
逻辑运算
有时候我们需要将多个布尔变量进行运算,例如需要条件A和条件B同时满足时,或者条件A与条件B只要有一个满足时,下表列出了不同条件下逻辑运算符的结果。
表达式1 | 表达式2 | 表达式1 and 表达式2的结果 |
---|---|---|
True | True | True |
True | False | False |
False | True | False |
False | False | False |
表达式1 | 表达式2 | 表达式1 or 表达式2的结果 |
---|---|---|
True | True | True |
True | False | True |
False | True | True |
False | False | False |
表达式1 | not 表达式1的结果 |
---|---|
True | False |
False | True |
从上面的表格我们可以看出,两个表达式都为True时,And才为True。而OR可以增加表达式的运算结果为True的可能性,两个表达式中只要有一个的值为True,那么表达式的运算结果就为True。
策略的结构
策略代码由参数定义(Params),变量定义(Vars)以及Begin与End之间的策略主体三部分组成,三部分顺序不能错乱。语法如下:
Params 参数定义语句;Vars 变量定义语句;Begin 策略主体;End
参数:
参数是一个预先声明的地址,用来存放输入参数的值,我们可以理解为一个带名称的盒子,里面放了值。在声明之后,您就可以在这个策略公式接下来的部分中使用该参数的名称来引用其值。参数的优势是可以在量化任务和k线图上直接修改,传给策略使用而不需要再重新编译策略公式。
一个策略公式中只能出现一个Params宣告,并且要放在公式最开始的部分,变量定义语句之前。
参数必须有初始值。参数在策略内部不能修改,一直保持不变,不能赋值(引用参数除外)。
引用参数是在策略公式作为函数使用时,调用的时候传入一个变量的地址(变量名),在函数内部会修改引用参数的值,在函数执行完毕,上层调用的公式会通过变量获得修改后的值,引用参数对于需要通过用户函数返回多个值的情况非常有用。
参数可以在个股和量化任务的界面直接修改,而无须重新编译策略。
参数可以使用所有9种数据类型。
序列参数可以通过回溯获取以前Bar的值。在作为函数使用时,将序列变量作为参数传递过来即可。
另外,在参数优化时,我们通过修改公式应用不同的参数,穷举测试交易策略的性能优劣,挑选最符合目标的参数值,达到优化参数的目的。
参数定义的语法:
Params 参数类型 参数名1(初始值); 参数类型 参数名2(初始值); 参数类型 参数名3(初始值);
例子:
Params Numberic Length(10); // 定义数值型参数Length,默认值为10 Bool Con(False); // 定义布尔型参数Con,默认值为False; String Str("Hello World"); // 定义字符串参数Str,默认值为Hello World;
变量
变量也是一个存储值的地址,我们同样也可以把它理解为一个带名称的盒子,和参数不同的是变量可以在策略公式中对其赋值。当变量被声明之后,就可以在这个策略公式中直接使用变量名称。变量用来存放计算或比较的结果,用”=”等号对变量赋值,然后在之后需要的地方直接引用计算所得的值,而无需再次计算。
使用变量可以提高策略的运行速度并节约内存空间,也可以提高程序的可读性,避免输入错误。如:
If(High >= Average(Close,20) * 1.1) Buy(1,Average(Close,20) * 1.1);
就可以用变量重写为
myValue= Average(Close,20) * 1.1;If(High >= myValue) Buy(1,myValue);
变量仅可在声明的策略公式内部使用,如果一类计算方法会经常需要在不同的策略中调用,可以建立函数来实现。函数的使用方式在后文会说明。
一个策略公式中只能出现一个Vars宣告,并且要放到公式的参数定义之后,主体之前。
变量可以没有初始值,如果没有赋初始值,则使用默认值。数值型默认值是0,布尔型默认值是false,字符串默认是空串。
变量可以在策略内部被赋值,主要用来存放计算或比较的结果。变量不能声明为引用数据类型。
序列变量和普通变量一样,可以在声明时赋默认值,也可在策略公式中对其赋值。声明时使用NumericSeries,就是数值型序列变量。另外还有布尔/字符串序列变量,BoolSeries/StringSeries。
序列变量可以进行回溯,获取在当前策略运行的k线之前的k线上的变量数据。除了支持全部简单变量的功能之外,序列变量还可以在变量名后加中括号”[nOffset]”来回溯以前的变量值,该偏移值必须是大于等于0的整数。
在新的一根bar产生时,序列变量会将上一根bar上的值传递到最新bar上,作为新bar的初始值。所以在声明序列变量时赋的初始值,只是对第一根bar生效,后面都是在传递值。
在开头示例的双均线策略,开仓条件就用到了序列变量。If(MarketPosition != 1 && AvgValue1[1] > AvgValue2[1])
序列变量在最新的一根k线上的索引值为0,也就是AvgValue1[0],取最新的这个值时,[0]可以省略。
索引值在k线上从后往前递增,所以AvgValue1[1]返回的值为当前运行策略的k线的前一根k线上的AvgValue1的值。
同样的,我们计算均线的值的时候,使用的Close,因为要用到之前很多根k线的收盘价,所以Close其实也是一个序列值。尽管序列变量可以实现普通变量的所有功能,但如果只需要使用普通变量就可满足需求的地方,我们建议您使用普通变量,速度更快。
我们内置了很多系统变量,可以快速的取到行情、账户和策略持仓等交易策略需要获取到的信息。系统变量由系统来赋值供您在策略中直接调用,您不可赋值,声明的变量名称也不可与系统变量重名。
变量定义的语法:
Vars 变量类型 变量名1(初始值); 变量类型 变量名2(初始值); 变量类型 变量名3(初始值);
例子:
Vars Numberic Length(10); // 定义数值型变量Length,默认值为10 NumbericSeries Length; // 定义数值型序列变量参数Length,不声明默认值 Bool Con(False); // 定义布尔型变量Con,默认值为False; String Str("Hello World"); // 定义字符串变量Str,默认值为Hello World;
策略主体
策略主体部分将输入参数进行计算,根据条件执行对应的画线或下单动作,并且可以通过Return返回值来作为函数使用。
Begin与End之间的部分为策略的主体代码,每次策略被驱动,都运行一次该区域的代码。
在策略主体部分,您可以进行表达式运算、逻辑运算、条件判断、变量赋值、调用下单/画线等函数、输出返回值等操作。
函数
UPBTC的策略公式可以调用其他公式,有返回值的公式我们称为函数。函数是可以通过名称进行调用的一组语句的集合,函数会返回一个值,这个值可以是Numeric,Bool,String三种基本类型中的任何一种。您可以在策略公式内主体部分调用函数来完成相应的功能。
例如我们在最上面示例的双均线策略,就使用到了快速求平均函数AverageFC,传入需要使用的价格序列Price(这里我们传了Close),需要计算的长度Length,就会计算并返回之前多个k线的收盘价的平均值。每次用户需要进行求平均计算的时候,都可以调用AverageFC代替冗长的求平均代码,输入参数并获取返回值。我们内置了很多内置函数,还有一些底层来支持的系统函数,您都可以直接使用。并且,您也可以将经常需要用的方法自己编写为函数来使用。
函数通过参数传递输入数据,通过引用参数或返回值传递输出数据,AverageFC函数在示例策略被调用的时候格式如下:
AvgValue1 = AverageFC(Close,FastLength);
在调用AverageFC的时候,需要根据定义时候的参数列表和顺序,输入相应的输入参数。有默认值的参数可以省略输入参数,但是不能省略函数后面的括号()。
函数须通过Return返回数据,返回数据类型为三种基本类型之一。
函数在调用时需要将返回值赋予类型相同的变量。
函数可以调用其他策略公式/函数,函数自身也可以递归调用其自身。
按照函数的实现机制不同,可分为普通函数和序列函数。普通函数输入参数,执行一段程序代码,返回需要的值。如Abs(-2); 返回2。序列函数是输入参数中有序列数据类型的用户函数。
为了保证序列数据的正确计算,序列函数需要每个Bar都被调用,如果有些Bar没有调用序列函数,序列函数中的序列数据则是空值。所以非常不建议在条件语句,条件语句的判断表达式,循环语句中使用序列函数。可以先在策略主体部分将序列函数赋值到变量,再使用变量。
交易策略和函数没有明确的区分,都可以被其他的策略调用。(效果等同于策略默认载入了所有策略函数)
策略的运算
普通变量的作用域、生命周期是当前tick(每一个报价更新,称为一个tick)。序列变量的作用域和生命周期是所有bar。
序列变量也可以理解为数组,和bar的根数一样,一一对应。一个bar上,序列变量只有一个值。
策略在历史bar上,从第一个bar到最后一个bar,依次计算,每个bar驱动策略计算一次。
实时行情时,最新的bar会随行情的波动继续更新,策略的运行机制不同于历史数据。每当有新的tick进来,策略都会被驱动,执行Begin-end之间的代码运算,但不再回去计算历史数据。简而言之就是历史bar每个bar计算一次策略,实时bar每个tick计算一次策略。
在最新的bar上,每次新的tick进来,普通变量都是以这个bar开始时的初始值开始计算,且无论这次计算怎么改变变量的值,都不影响下一个tick运行时普通变量的初始值。也即是说在最新bar内,普通变量不保留运算结果。
序列变量在一根bar上可以被多次赋值,并且会保留下来。在bar内取序列变量的值时,会取得最后一次的赋值。在这跟bar的最后一个tick走完时,会将值和这个bar的索引号保存在一起。
在策略从上往下运行的过程中,可以对一个变量多次赋值。
跳Tick
实时行情中,如果策略加载的标的的tick间隔非常短,或者策略的运算量非常大,有可能会发生两个tick的间隔时间内无法完整执行一次策略运算。
这时新的ticker虽然进来,但是不会触发新的一轮策略运行。策略依然会继续执行之前未完的策略计算,直到策略的最后一行执行完毕。之后,直到再有新的tick进来,才会开始执行新的一轮计算。
也就是说,在这样的情况下,有tick没有触发策略计算,被跳过了。
交易下单
下单方法有如下四个。Buy(买入,买入开多),Sell(卖出,卖出平多),SellShort(开空),BuyToCover(平空)。
委托的单位为张(okex合约)、个(现货),输入参数为数量、价格。对策略对应标的下单,无需传入标的的代码等参数。
在持多单时(buy),直接使用sellshort开空,平台会自动处理,平掉所有策略持仓的多单,再开空sellshort指定的数量。如果标的不支持开空,则sellshort会变成sell。注意:现货持多单时,直接使用SellShort反手,首先会平策略所有多单,并且再发Sell委托单,有可能平掉您账户里面的持仓。
sellshort开空时,使用buy下单方法同理。下单方法除了可以在实时行情中发出下单指令外,还会在k线图表的k线上对应价格位置做出信号标识,持仓函数和回测、参数优化也是通过对这些方法的历史记录进行计算得出。
如果下单条件一直满足,策略会在每个tick运行时都重复发单,为了对重复发单做控制,可以使用MarketPosition之类的变量,下单之后,变量的值会改变,就不会再满足下单条件,不会重复再次发出委托单。可以参考常见问题里面的示例。
可能看到这里,您还是对Up Language策略语言理解有点模糊,没关系,您可以直接打开“函数手册-内置函数”,或者在客户端的“量化-策略公式”下,查看内置的策略、指标和函数。我们内置了大量的计算函数、指标,和一些比较简单的策略。通过阅读学习内置策略公式的写法,理解含义,然后动手写自己的交易策略试试吧。