- 策略广场
- 加仓策略(原版)
加仓策略(原版)
Author:
ianzeng123, Date: 2024-07-15 17:48:35
Tags:
/*backtest
start: 2024-01-03 09:00:00
end: 2024-07-16 14:01:00
period: 1h
basePeriod: 15m
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}]
args: [["MaxLots",3],["ATRPeriod",30]]
*/
var _q = $.NewTaskQueue();
var openNumber = 0 //开仓次数
var cumaddNumber = 0 //累计加仓次数
var curaddNumber = 0 //本轮加仓次数
var winNumber = 0 //成功次数
var lossNumber = 0 //失败次数
var cumProfit = 0 //累计利润
var cumLoss = 0 //累计损失
var holdSymbol = '' //持仓品种
var holdAmount = 0 //持仓数量
var holdPrice = 0 //持仓价格
var holdProfit = 0 //持仓利润
var holdDir = '' //持仓方向
var openPrice = 0 //最新开仓价格,判断加仓
var winRate = 0 //胜率
var plRatio = 0 //盈亏比
var InitBalance = 0 //未开仓金额
let openContracts = {}; // 追踪所有未平仓的合约
let profitLossHistory = {}; // 追踪每个合约的盈亏历史
let processedOrderIds = new Set(); // 记录已处理的订单 ID
let winLossCount = { win: 0, loss: 0, total: 0 }; // 统计胜败和总交易数量
function updateContracts(newOrders, commissionFee) {
let totalProfit = 0;
let totalLoss = 0;
for (let order of newOrders) {
let symbol = order.Symbol;
let orderId = order.Id;
// 如果订单已处理,跳过
if (processedOrderIds.has(orderId)) {
continue;
}
// 初始化合约追踪信息
if (!openContracts[symbol]) {
openContracts[symbol] = {
longPositions: [], // 保存多单开仓的价格和数量
shortPositions: [] // 保存空单开仓的价格和数量
};
}
// 处理开仓和加仓订单
if (order.Type === 0 && order.Offset === 0) { // 买入开仓(多单)
openContracts[symbol].longPositions.push({ amount: order.Amount, price: order.AvgPrice });
} else if (order.Type === 1 && order.Offset === 0) { // 卖出开仓(空单)
openContracts[symbol].shortPositions.push({ amount: order.Amount, price: order.AvgPrice });
}
// 处理平仓订单
if (order.Offset === 1) {
let remainingAmount = order.Amount;
let profitOrLoss = 0;
let isError = false;
// 平多单
if (order.Type === 1) {
if (openContracts[symbol].longPositions.length === 0) {
Log(`错误: 没有多单开仓记录,无法平仓,订单ID ${orderId}`);
isError = true;
} else {
while (remainingAmount > 0 && openContracts[symbol].longPositions.length > 0) {
let position = openContracts[symbol].longPositions[0];
let amountToClose = Math.min(position.amount, remainingAmount);
// 计算盈亏
profitOrLoss += (order.AvgPrice - position.price) * amountToClose;
remainingAmount -= amountToClose;
openContracts[symbol].longPositions[0].amount -= amountToClose;
if (openContracts[symbol].longPositions[0].amount === 0) {
openContracts[symbol].longPositions.shift();
}
}
// 如果仍然有未平仓的数量,记录错误
if (remainingAmount > 0) {
Log(`错误: 平仓数量大于开仓数量,订单ID ${orderId}`);
isError = true;
}
}
}
// 平空单
if (order.Type === 0) {
if (openContracts[symbol].shortPositions.length === 0) {
Log(`错误: 没有空单开仓记录,无法平仓,订单ID ${orderId}`);
isError = true;
} else {
while (remainingAmount > 0 && openContracts[symbol].shortPositions.length > 0) {
let position = openContracts[symbol].shortPositions[0];
let amountToClose = Math.min(position.amount, remainingAmount);
// 计算盈亏
profitOrLoss += (position.price - order.AvgPrice) * amountToClose;
remainingAmount -= amountToClose;
openContracts[symbol].shortPositions[0].amount -= amountToClose;
if (openContracts[symbol].shortPositions[0].amount === 0) {
openContracts[symbol].shortPositions.shift();
}
}
// 如果仍然有未平仓的数量,记录错误
if (remainingAmount > 0) {
Log(`错误: 平仓数量大于开仓数量,订单ID ${orderId}`);
isError = true;
}
}
}
if (!isError) {
// 扣除手续费并更新盈亏
let volumeMultiple = exchange.SetContractType(symbol).VolumeMultiple // 合约乘数
let totalCommissionFee = commissionFee * order.Amount * 2 / volumeMultiple; // 买入和卖出的手续费
profitOrLoss -= totalCommissionFee;
// 更新盈亏历史
if (profitOrLoss > 0) {
totalProfit += profitOrLoss;
winLossCount.win++;
} else {
totalLoss += Math.abs(profitOrLoss);
winLossCount.loss++;
}
winLossCount.total++;
}
}
// 记录订单 ID 为已处理
processedOrderIds.add(orderId);
}
// 记录盈亏历史
profitLossHistory.totalProfit = (profitLossHistory.totalProfit || 0) + totalProfit;
profitLossHistory.totalLoss = (profitLossHistory.totalLoss || 0) + totalLoss;
// 计算盈亏比
let winLossRatio = profitLossHistory.totalLoss > 0
? profitLossHistory.totalProfit > 0 ? profitLossHistory.totalProfit / profitLossHistory.totalLoss : -1
: profitLossHistory.totalProfit > 0 ? 1 : 0; // 避免除以0
// 计算胜率
let winRate = winLossCount.total > 0
? winLossCount.win / winLossCount.total
: 0;
return [winLossRatio, winRate];
}
function main(){
SetErrorFilter("login|ready|流控|连接失败|初始|Timeout");
Log("风险系数:", RiskRatio, "N值周期:", ATRPeriod, "加仓系数:", IncSpace, "单品种最多开仓:", MaxLots, "次");
var pretime = 0
var checklock = true
var prewinlossratio = 0
var prewinrate = 0
const commissionFee = 5
while (true) {
if(exchange.IO("status")) {
var currentOrder = exchange.GetHistoryOrders()
var result = updateContracts(currentOrder, commissionFee);
if(result[0] != prewinlossratio || result[1] != prewinrate){
prewinlossratio = result[0]
prewinrate = result[1]
Log('盈亏比:', result[0], '#ff0000')
Log('胜率:', result[1], '#00ff00')
}
var symbolDetail = _C(exchange.SetContractType, Instruments)
var record = exchange.GetRecords()
var lastPrice = record[record.length - 1].Close
var shortMean = TA.MA(record, shortPeriod)
var longMean = TA.MA(record, longPeriod)
var longSignal = shortMean[shortMean.length - 2] > longMean[longMean.length - 2]
var shortSignal = shortMean[shortMean.length - 2] < longMean[longMean.length - 2]
// 检查主力合约
var DRecords = exchange.GetRecords(PERIOD_D1)
var curTime = DRecords[DRecords.length - 1].Time
var mainSymbol = symbolDetail.InstrumentID;
var curPos = exchange.GetPosition()
var atr = TA.ATR(DRecords, ATRPeriod)
var N = atr[atr.length - 1]
// 开仓/加仓手数计算
var account = _q.GetAccount(exchange);
var availmoney = account.Balance * (1 - KeepRatio/100)
var unit = parseInt((availmoney) * (RiskRatio / 100) / N / symbolDetail.VolumeMultiple); //风险制约手数
var canOpen = parseInt((availmoney) / (longSignal ? symbolDetail.LongMarginRatio : symbolDetail.ShortMarginRatio) / (lastPrice * 1.2) / symbolDetail.VolumeMultiple); //金额制约手数
unit = Math.min(unit, canOpen);
if (unit < symbolDetail.MinLimitOrderVolume) {
Log("可开 " + unit + " 手 无法开仓, " + (canOpen >= symbolDetail.MinLimitOrderVolume ? "风控触发" : "资金限制") + "可用: " + account.Balance);
}
if(checklock){
var initAccount = _q.GetAccount(exchange);
var initMargin = JSON.parse(exchange.GetRawJSON()).CurrMargin;
InitBalance = initAccount.Balance + initMargin;
}
// 开仓
if(curPos && curPos.length == 0 && (longSignal || shortSignal)){
_q.pushTask(exchange, mainSymbol, (longSignal ? "buy" : "sell"), unit, function(task, ret) {
Log('现有仓位:',curPos.length, "做多信号:", longSignal, "做空信号:", shortSignal)
if(ret){
checklock = false
openNumber += 1
openPrice = ret.price
}
});
}
// 移仓+加仓+平仓
if(curPos && curPos.length > 0){
holdPrice = curPos[0].Price
holdAmount = curPos[0].Amount
holdProfit = (curPos[0].Type % 2) == 0 ? (lastPrice - holdPrice) * holdAmount * symbolDetail.VolumeMultiple : (holdPrice - lastPrice) * holdAmount * symbolDetail.VolumeMultiple
holdDir = curPos[0].Type % 2 == 0 ? "多" : "空"
holdSymbol = curPos[0].ContractType
// 移仓
var moveSignal = pretime != curTime && holdSymbol != mainSymbol
if (moveSignal) {
_q.pushTask(exchange, holdSymbol, "coverall", holdAmount, function(task, ret) {
if (ret) {
Log(holdSymbol, "移仓平仓成功 #ff0000");
pretime = curTime
curaddNumber = 0 //本轮加仓次数归零
var afterAccount = _q.GetAccount(exchange);
var afterMargin = JSON.parse(exchange.GetRawJSON()).CurrMargin;
var afterBalance = afterAccount.Balance + afterMargin;
var curprofit = afterBalance - InitBalance
if(curprofit > 0){
cumProfit += curprofit
winNumber += 1
}else{
cumLoss += curprofit
lossNumber += 1
}
curaddNumber = 0
holdSymbol = ''
holdAmount = 0
holdPrice = 0
holdProfit = 0
holdDir = ''
openPrice = 0
checklock = true
}
})
}
//加仓
var spread = holdDir == '多' ? (openPrice - lastPrice) : (lastPrice - openPrice)
var addInterval = IncSpace * N
var addLongSignal = -spread > addInterval && holdProfit > 0 && longSignal && curaddNumber < MaxLots && holdDir == '多'
var addShortSignal = -spread > addInterval && holdProfit > 0 && shortSignal && cumaddNumber < MaxLots && holdDir == '空'
if(addLongSignal){
_q.pushTask(exchange, mainSymbol, "buy", unit, function(task, ret) {
Log('满足加仓条件,进行多头方向加仓#ff0000')
if(ret){
openPrice = ret.price
curaddNumber += 1
cumaddNumber += 1
}
});
}
if(addShortSignal){
_q.pushTask(exchange, mainSymbol, "sell", unit, function(task, ret) {
Log('满足加仓条件,进行空头方向加仓#ff0000')
if(ret){
openPrice = ret.price
curaddNumber += 1
cumaddNumber += 1
}
});
}
//平仓
var coverLongSignal = shortSignal && holdDir == '多'
var coverShortSignal = longSignal && holdDir == '空'
if(coverLongSignal || coverShortSignal){
_q.pushTask(exchange, mainSymbol, "coverall", 0, function(task, ret) {
if(ret){
var afterAccount = _q.GetAccount(exchange);
var afterMargin = JSON.parse(exchange.GetRawJSON()).CurrMargin;
var afterBalance = afterAccount.Balance + afterMargin;
var curprofit = afterBalance - InitBalance
Log('平仓后资金:', afterAccount.Balance, afterMargin, afterBalance)
Log('平仓后利润:', afterBalance - InitBalance)
if(curprofit > 0){
cumProfit += curprofit
winNumber += 1
}else{
cumLoss += curprofit
lossNumber += 1
}
curaddNumber = 0
holdSymbol = ''
holdAmount = 0
holdPrice = 0
holdProfit = 0
holdDir = ''
openPrice = 0
checklock = true
}
})
}
}
var tblStatus = {
type: "table",
title: "持仓信息",
cols: ["合约名称", "持仓方向", "持仓均价", "持仓数量", "持仓盈亏", "本轮加仓次数"],
rows: []
};
var tblStrategy = {
type: "table",
title: "策略评价",
cols: ["开仓次数", "成功次数", "累计利润", "失败次数", "累计亏损", "胜率", "盈亏比"],
rows: []
};
if(openNumber){
winRate = winNumber / openNumber
}
winRate = openNumber ? winNumber / openNumber : 0
if(cumLoss == 0 && cumProfit != 0 ) plRatio = 1
if(cumLoss != 0 && cumProfit == 0) plRatio = -1
if(cumProfit && cumLoss) plRatio = cumProfit / cumLoss
tblStatus.rows.push([holdSymbol, holdDir, holdPrice, holdAmount, holdProfit, curaddNumber]);
tblStrategy.rows.push([openNumber, winNumber, cumProfit, lossNumber, cumLoss, winRate, Math.abs(plRatio)]);
var now = new Date();
lastStatus = '`' + JSON.stringify([tblStatus, tblStrategy]) + '`\n' + '`' + ' 当前时间: ' + _D() + ', 星期' + ['日', '一', '二', '三', '四', '五', '六'][now.getDay()] + ", 交易任务: " + _q.size();
LogStatus(lastStatus);
_q.poll();
Sleep(LoopInterval * 1000);
}
}
}
template: strategy.tpl:40:21: executing "strategy.tpl" at <.api.GetStrategyListByName>: wrong number of args for GetStrategyListByName: want 7 got 6