[TOC]
FMZ身为量化交易平台,主要是为了服务程序化交易者。但也提供了基础的交易终端,虽然功能简单,有时候也能起到大用,比如交易所繁忙无法打开,而API还是可以工作的,此时可以通过终端就可以撤单,下单,查看行情账户等。为了完善交易终端的体验,现在增加了插件功能。有时候,我们需要一个小功能来辅助交易,如阶梯挂单、冰山委托、一键对冲、一键平仓等操作,并不太需要看执行日志,新建一个机器人有些繁琐,直接在终端点击一下插件,就能够立即实现相应的功能,能大大方便手动交易。插件位置如下:
插件运行有两种模式,立即运行与后台运行。后台运行等同于创建机器人(正常收费)。立即运行的原理和调试工具相同:发送一段代码到交易终端页面的托管者执行,并且支持返回图表和表格(调试工具目前也升级支持),同样的只能执行5分钟,不收取费用,不限制语言。执行时间很短的插件可以用立即运行模式,复杂的、需要长时间运行的策略还是需要运行机器人。
在策略编写时,需要将策略类型选择为插件。 插件的main函数return的结果会在运行结束后,在终端弹出,支持字符串、画图和表格。因为插件执行看不到日志,可以将插件的执行结果return返回。
如图直接在搜索框里搜索,注意只能运行交易插件类型策略,然后点击添加。公开的插件可以在策略广场找到:https://www.fmz.com/square/21/1
点击策略即进入参数设置界面,如过没有参数会直接运行,交易终端所选的托管者、交易对、K线周期即为默认的相应参数。点击执行策略就开始执行,在选择“立即执行”模式(可记住默认运行方式)。插件不会显示出日志。
点击图示位置即停止插件,由于所有插件是在一个调试工具进程中执行,会停止所有插件。
插件可以执行一段时间的代码,可执行一些简单的操作,很多时候手动操作需要重复执行的操作都可以用插件实现,方便交易。下面将以具体的例子来介绍,给出的源码可供参考定制自己的策略。
期货跨期对冲是很常见的策略,由于频率不是很高,很多人会手动操作,需要一个合约做多,一个合约做空,还好分析差价走势。在交易终端使用插件将节省你的精力。
首先介绍的是画跨期差价插件:
var chart = {
__isStock: true,
title : { text : '差价分析图'},
xAxis: { type: 'datetime'},
yAxis : {
title: {text: '差价'},
opposite: false,
},
series : [
{name : "diff", data : []},
]
}
function main() {
exchange.SetContractType('quarter')
var recordsA = exchange.GetRecords(PERIOD_M5) //周期可以自行定制
exchange.SetContractType('this_week')
var recordsB = exchange.GetRecords(PERIOD_M5)
for(var i=0;i<Math.min(recordsA.length,recordsB.length);i++){
var diff = recordsA[recordsA.length-Math.min(recordsA.length,recordsB.length)+i].Close - recordsB[recordsB.length-Math.min(recordsA.length,recordsB.length)+i].Close
chart.series[0].data.push([recordsA[recordsA.length-Math.min(recordsA.length,recordsB.length)+i].Time, diff])
}
return chart
}
点击一下,近期的跨期差价一目了然,插件源码复制地址:https://www.fmz.com/strategy/187755
有了差价分析,发现差价正在收敛,是一个做空季度合约,做多当周的机会,这是就可以使用一键对冲插件,点击一下,自动帮你空季度多当周,比手动操作快上不少。策略的实现原理是加滑价开相同数量的仓位,可以多运行几次,慢慢达到自己所需仓位,避免冲击市场,可以更改默认参数,达到更快速度下单。策略复制地址:https://www.fmz.com/strategy/191348
function main(){
exchange.SetContractType(Reverse ? Contract_B : Contract_A)
var ticker_A = exchange.GetTicker()
if(!ticker_A){return 'Unable to get quotes'}
exchange.SetDirection('buy')
var id_A = exchange.Buy(ticker_A.Sell+Slip, Amount)
exchange.SetContractType(Reverse ? Contract_B : Contract_A)
var ticker_B = exchange.GetTicker()
if(!ticker_B){return 'Unable to get quotes'}
exchange.SetDirection('sell')
var id_B = exchange.Sell(ticker_B.Buy-Slip, Amount)
if(id_A){
exchange.SetContractType(Reverse ? Contract_B : Contract_A)
exchange.CancelOrder(id_A)
}
if(id_B){
exchange.SetContractType(Reverse ? Contract_B : Contract_A)
exchange.CancelOrder(id_B)
}
return 'Position: ' + JSON.stringify(exchange.GetPosition())
}
等待差价收敛,需要平仓,可以运行一键平仓插件,最快的速度平仓。
function main(){
while(ture){
var pos = exchange.GetPosition()
var ticker = exchange.GetTicekr()
if(!ticker){return '无法获取ticker'}
if(!pos || pos.length == 0 ){return '已无持仓'}
for(var i=0;i<pos.length;i++){
if(pos[i].Type == PD_LONG){
exchange.SetContractType(pos[i].ContractType)
exchange.SetDirection('closebuy')
exchange.Sell(ticker.Buy, pos[i].Amount - pos[i].FrozenAmount)
}
if(pos[i].Type == PD_SHORT){
exchange.SetContractType(pos[i].ContractType)
exchange.SetDirection('closesell')
exchange.Buy(ticker.Sell, pos[i].Amount - pos[i].FrozenAmount)
}
}
var orders = exchange.Getorders()
Sleep(500)
for(var j=0;j<orders.length;j++){
if(orders[i].Status == ORDER_STATE_PENDING){
exchange.CancelOrder(orders[i].Id)
}
}
}
}
最常见的就是冰山委托,把大单拆成小单,虽然可以运行成机器人,但5分钟的插件其实也足够了。冰山委托有两种,一种是吃单,一种是挂单,如果有手续费优惠,可以选择挂单,就是执行时间更长。
下面代码是冰山委托买入插件源码:https://www.fmz.com/strategy/191771 。卖出源码:https://www.fmz.com/strategy/191772
function main(){
var initAccount = _C(exchange.GetAccount)
while(true){
var account = _C(exchange.GetAccount)
var dealAmount = account.Stocks - initAccount.Stocks
var ticker = _C(exchange.GetTicker)
if(BUYAMOUNT - dealAmount >= BUYSIZE){
var id = exchange.Buy(ticker.Sell, BUYSIZE)
Sleep(INTERVAL*1000)
if(id){
exchange.CancelOrder(id) // May cause error log when the order is completed, which is all right.
}else{
throw 'buy error'
}
}else{
account = _C(exchange.GetAccount)
var avgCost = (initAccount.Balance - account.Balance)/(account.Stocks - initAccount.Stocks)
return 'Iceberg order to buy is done, avg cost is '+avgCost
}
}
}
一直占据买一或者卖一也是慢慢出货的一种方式,对市场的冲击比较小。这个策略还有一些改进的地方,可以手动改一下最小交易量或者精度。 买入:https://www.fmz.com/strategy/191582 卖出:https://www.fmz.com/strategy/191730
function GetPrecision(){
var precision = {price:0, amount:0}
var depth = exchange.GetDepth()
for(var i=0;i<exchange.GetDepth().Asks.length;i++){
var amountPrecision = exchange.GetDepth().Asks[i].Amount.toString().indexOf('.') > -1 ? exchange.GetDepth().Asks[i].Amount.toString().split('.')[1].length : 0
precision.amount = Math.max(precision.amount,amountPrecision)
var pricePrecision = exchange.GetDepth().Asks[i].Price.toString().indexOf('.') > -1 ? exchange.GetDepth().Asks[i].Price.toString().split('.')[1].length : 0
precision.price = Math.max(precision.price,pricePrecision)
}
return precision
}
function main(){
var initAccount = exchange.GetAccount()
if(!initAccount){return '无法获取账户信息'}
var precision = GetPrecision()
var buyPrice = 0
var lastId = 0
var done = false
while(true){
var account = _C(exchange.GetAccount)
var dealAmount = account.Stocks - initAccount.Stocks
var ticker = _C(exchange.GetTicker)
if(BuyAmount - dealAmount > 1/Math.pow(10,precision.amount) && ticker.Buy > buyPrice){
if(lastId){exchange.CancelOrder(lastId)}
var id = exchange.Buy(ticker.Buy, _N(BuyAmount - dealAmount,precision.amount))
if(id){
lastId = id
}else{
done = true
}
}
if(BuyAmount - dealAmount <= 1/Math.pow(10,precision.amount)){done = true}
if(done){
var avgCost = (initAccount.Balance - account.Balance)/dealAmount
return 'order is done, avg cost is ' + avgCost // including fee cost
}
Sleep(Intervel*1000)
}
}
有时候为了能卖出一个更好的出货价格或者挂单等待捡漏,可以按照一定间距挂多个订单。此插件也可用于期货挂单。源码复制地址:https://www.fmz.com/strategy/190017
function main() {
var ticker = exchange.GetTicker()
if(!ticker){
return 'Unable to get price'
}
for(var i=0;i<N;i++){
if(Type == 0){
if(exchange.GetName().startsWith('Futures')){
exchange.SetDirection('buy')
}
exchange.Buy(Start_Price-i*Spread,Amount+i*Amount_Step)
}else if(Type == 1){
if(exchange.GetName().startsWith('Futures')){
exchange.SetDirection('sell')
}
exchange.Sell(Start_Price+i*Spread,Amount+i*Amount_Step)
}else if(Type == 2){
exchange.SetDirection('closesell')
exchange.Buy(Start_Price-i*Spread,Amount+i*Amount_Step)
}
else if(Type == 3){
exchange.SetDirection('closebuy')
exchange.Sell(Start_Price+i*Spread,Amount+i*Amount_Step)
}
Sleep(500)
}
return 'order complete'
}
常用的期货交易软件往往有许多高级的挂单功能,如挂止损单,挂条件单等等,都可以很方便的写成插件。这里分享一个挂单成交后立即挂单平仓的插件。复制地址:https://www.fmz.com/strategy/187736
var buy = false
var trade_amount = 0
function main(){
while(true){
if(exchange.IO("status")){
exchange.SetContractType(Contract)
if(!buy){
buy = true
if(Direction == 0){
exchange.SetDirection('buy')
exchange.Buy(Open_Price, Amount)
}else{
exchange.SetDirection('sell')
exchange.Sell(Open_Price, Amount)
}
}
var pos = exchange.GetPosition()
if(pos && pos.length > 0){
for(var i=0;i<pos.length;i++){
if(pos[i].ContractType == Contract && pos[i].Type == Direction && pos[i].Amount-pos[i].FrozenAmount>0){
var cover_amount = math.min(Amount-trade_amount, pos[i].Amount-pos[i].FrozenAmount)
if(cover_amount >= 1){
trade_amount += cover_amount
if(Direction == 0){
exchange.SetDirection('closebuy_today')
exchange.Sell(Close_Price, cover_amount)
}else{
exchange.SetDirection('closesell_today')
exchange.Buy(Close_Price, cover_amount)
}
}
}
}
}
} else {
LogStatus(_D(), "未连接CTP !")
Sleep(10000)
}
if(trade_amount >= Amount){
Log('任务完成')
return
}
Sleep(1000)
}
}
看了这么多小功能,你应该也有了自己的想法,不妨写成插件方便自己的手动交易。
shiyimjjcn 报错是什么原因? Error: Futures_OP 0: 400: {"error_message":"Open orders exist","code":35017,"error_code":"35017","message":"Open orders exist"} Buy(5000, 0.1): 400: {"error_message":"order_size error","result":"true","error_code":"35063","order_id":"-1"}
小草 检查交易所文档或者咨询交易所客服