[pshaiCmd] Position Statistics
stableDescription
A handy custom command for useful information in backtests and live bots!
Information displayed via CustomReport()'s.
Usage testample:
local c = ClosePrices()
local rsi = RSI(c, 14)
if rsi > 70 then
DoShort()
elseif rsi < 30 then
DoLong()
end
CC_PositionStatistics()
HaasScript
DefineCommand('PositionStatistics', 'Useful information as Custom Reports')
local positionId = DefineParameter(StringType, 'positionId', 'Position ID, must be defined if bot has multiple positions at once', false, '')
local positionName = DefineParameter(StringType, 'positionName', 'Custom name for a position', false, 'Position')
local position = PositionContainer(positionId)
if positionId == '' then positionId = position.positionId end
local info = {
roi = position.roi,
pnl = position.profit,
amt = position.amount,
mkt = position.market,
mkt2 = '',
contractName = '',
sde = position.isLong and 'Long' or position.isShort and 'Short' or 'NA'
}
info.mkt2 = BaseCurrency(info.mkt)..'_'..QuoteCurrency(info.mkt)
info.contractName = ContractName(info.mkt)
-----------------
local botStartTime = Load('bst', Time())
local isNewPos = false
local prevPosId = Load('prevPosId', '')
local startTime = Load('posst', Time())
local endTime = Load('poset', 0)
local positions = Load('posarr', {})
local positions_limit = 1000
-----------------
local worst = {
isWorst = true,
isBest = false,
roi = Load('wr', 0),
roiSde = Load('wrs', 'NA'),
roiAmt = Load('wra', 0),
roiMkt = Load('wrm', ''),
roiMkt2 = Load('wrm2', ''),
pnl = Load('wpnl', 0),
pnlSde = Load('wpnls', 'NA'),
pnlAmt = Load('wpnla', 0),
pnlMkt = Load('wpnlm', ''),
pnlMkt2 = Load('wpnlm2', ''),
}
-----------------
local best = {
isWorst = false,
isBest = true,
roi = Load('br', 0),
roiSde = Load('brs', 'NA'),
roiAmt = Load('bra', 0),
roiMkt = Load('brm', ''),
roiMkt2 = Load('brm2', ''),
pnl = Load('bpnl', 0),
pnlSde = Load('bpnls', 'NA'),
pnlAmt = Load('bpnla', 0),
pnlMkt = Load('bpnlm', ''),
pnlMkt2 = Load('bpnlm2', '')
}
-----------------
local misc = {
smallestAmt = Load('samt', 0),
biggestAmt = Load('bamt', 0),
longestOpenTime = Load('lot', 0),
shortestOpenTime = Load('sot', 0)
}
local resetInfo = function(obj)
obj.roi = 0
obj.roiSde = 'NA'
obj.roiAmt = 0
obj.roiMkt = ''
obj.roiMkt2 = ''
obj.pnl = 0
obj.pnlSde = 'NA'
obj.pnlAmt = 0
obj.pnlMkt = ''
obj.pnlMkt2 = ''
end
local getDatetime = function(unix)
local y = CurrentYear(unix)
local m = CurrentMonth(unix)
local d = CurrentDate(unix)
local h = CurrentHour(unix)
local mm = CurrentMinute(unix)
local s = CurrentSecond(unix)
if m < 10 then m = "0" .. m end
if d < 10 then d = "0" .. d end
if h < 10 then h = "0" .. h end
if mm < 10 then mm = "0" .. mm end
if s < 10 then s = "0" .. s end
return y .. '/' .. m .. '/' .. d .. ' - ' .. h .. ':' .. mm .. ':' .. s
end
local updateMisc = function(info)
if (misc.smallestAmt == 0 or info.amt < misc.smallestAmt) and info.amt > 0 then
misc.smallestAmt = info.amt
end
if info.amt > misc.biggestAmt then
misc.biggestAmt = info.amt
end
if prevPosId != '' and IsPositionClosed(prevPosId) then
if startTime > 0 then
local length = Time() - startTime
if length > misc.longestOpenTime then
misc.longestOpenTime = length
end
if misc.shortestOpenTime == 0 or length < misc.shortestOpenTime then
misc.shortestOpenTime = length
end
startTime = 0
-- save position ID
positions = ArrayUnshift(positions, prevPosId)
-- limit array size
if #positions > positions_limit then
positions = Grab(positions, 0, positions_limit)
end
end
end
if prevPosId != positionId then
if startTime == 0 then
startTime = Time()
end
prevPosId = positionId
end
end
local updateInfo = function(obj, info)
if (obj.isWorst and info.roi < obj.roi) or (obj.isBest and info.roi > obj.roi) then
obj.roi = info.roi
obj.roiSde = info.sde
obj.roiAmt = info.amt
obj.roiMkt2 = info.mkt2
obj.roiMkt = info.mkt
if #info.contractName > 0 and StringContains(info.contractName, '-') then
obj.roiMkt2 = obj.roiMkt2 .. ' (' .. info.contractName .. ')'
end
end
if (obj.isWorst and info.pnl < obj.pnl) or (obj.isBest and info.pnl > obj.pnl) then
obj.pnl = info.pnl
obj.pnlSde = info.sde
obj.pnlAmt = info.amt
obj.pnlMkt2 = info.mkt2
obj.pnlMkt = info.mkt
if #info.contractName > 0 and StringContains(info.contractName, '-') then
obj.pnlMkt2 = obj.pnlMkt2 .. ' (' .. info.contractName .. ')'
end
end
end
local getOrderAvgs = function(posid)
local orders = GetAllFilledOrders(posid)
local len = #orders
local totalExecAmt, totalFillAmt, totalEntryOpenTime, totalExitOpenTime, totalEntryCount, totalExitCount = 0,0,0,0,0,0
if len == 0 then
Log('PositionStatistics() : Position has no orders to calculate averages from.')
return
end
for i=1, len do
local od = orders[i]
if od != nil then
totalExecAmt = totalExecAmt + od.executedAmount
totalFillAmt = totalFillAmt + od.filledAmount
if od.isEnterOrder then
totalEntryCount = totalEntryCount + 1
totalEntryOpenTime = totalEntryOpenTime + od.openTime / 60
end
if od.isExitOrder then
totalExitCount = totalExitCount + 1
totalExitOpenTime = totalExitOpenTime + od.openTime / 60
end
end
end
return {
count = len,
execAmt = totalExecAmt,
fillAmt = totalFillAmt,
entryOpenTime = totalEntryOpenTime,
exitOpenTime = totalExitOpenTime,
entryCount = totalEntryCount,
exitCount = totalExitCount
}
end
local getAvgs = function(allPositions)
local len = #allPositions
local totalPnl, totalRoi = 0,0
local totalExecAmt, totalFillAmt, totalEntryOpenTime, totalExitOpenTime, totalEntryCount, totalExitCount = 0,0,0,0,0,0
local toc = 0 -- Total Order Count
if len == 0 then
Log('PositionStatistics() : No closed positions to calculate averages from.')
return
end
for i=1, len do
local posid = allPositions[i]
if posid != '' then
local pos = PositionContainer(posid)
local orders = getOrderAvgs(posid)
toc = toc + orders.count
totalExecAmt = totalExecAmt + orders.execAmt
totalFillAmt = totalFillAmt + orders.fillAmt
totalEntryOpenTime = totalEntryOpenTime + orders.entryOpenTime
totalExitOpenTime = totalExitOpenTime + orders.exitOpenTime
totalEntryCount = totalEntryCount + orders.entryCount
totalExitCount = totalExitCount + orders.exitCount
totalPnl = totalPnl + pos.profit
totalRoi = totalRoi + pos.roi
end
end
return {
pnl = totalPnl > 0 and Round(totalPnl / len, 8) or 0,
roi = totalRoi > 0 and Round(totalRoi / len, 4) or 0,
execAmt = toc > 0 and Round(totalExecAmt / toc, 8) or 0,
fillAmt = toc > 0 and Round(totalFillAmt / toc, 8) or 0,
entryOpenTime = toc > 0 and Round(totalEntryOpenTime / totalEntryCount, 2) or 0,
exitOpenTime = toc > 0 and Round(totalExitOpenTime / totalExitCount, 2) or 0,
entryCount = toc > 0 and Round(totalEntryCount / toc * 100, 2) or 0,
exitCount = toc > 0 and Round(totalExitCount / toc * 100, 2) or 0
}
end
local buildFinalData = function(allPositions, info)
local groupName = 'Closed Position Averages ('..positionName..')'
local avgs = getAvgs(allPositions)
if avgs == nil then
return
end
CustomReport('Avg. ROI', avgs.roi .. ' %', groupName)
CustomReport('Avg. PnL', avgs.pnl .. ' ' .. ProfitLabel(info.mkt), groupName)
CustomReport('Avg. Order Executed Amount', avgs.execAmt .. ' ' .. AmountLabel(info.mkt), groupName)
CustomReport('Avg. Order Filled Amount', avgs.fillAmt .. ' ' .. AmountLabel(info.mkt), groupName)
CustomReport('Avg. Entry Order Open Time', avgs.entryOpenTime .. ' minutes', groupName)
CustomReport('Avg. Exit Order Open Time', avgs.exitOpenTime .. ' minutes', groupName)
CustomReport('Entry Orders', avgs.entryCount .. ' %', groupName)
CustomReport('Exit Orders', avgs.exitCount .. ' %', groupName)
end
local buildReports = function(obj)
local group = obj.isBest and 'Best' or 'Worst'
local groupName = group .. ' Open Position ('..positionName..')'
CustomReport(group .. ' ROI', Round(obj.roi, 4) .. ' %', groupName)
CustomReport(group .. ' ROI (Side)', obj.roiSde, groupName)
CustomReport(group .. ' ROI (Amount)', obj.roiAmt .. ' ' .. AmountLabel(obj.roiMkt), groupName)
CustomReport(group .. ' ROI (Market)', obj.roiMkt2, groupName)
CustomReport(group .. ' PnL', Round(obj.pnl, 8) .. ' ' .. ProfitLabel(obj.pnlMkt), groupName)
CustomReport(group .. ' PnL (Side)', obj.pnlSde, groupName)
CustomReport(group .. ' PnL (Amount)', obj.pnlAmt .. ' ' .. AmountLabel(obj.pnlMkt), groupName)
CustomReport(group .. ' PnL (Market)', obj.pnlMkt2, groupName)
end
local buildMisc = function(obj, info)
local groupName ='Misc Position Info ('..positionName..')'
local bst = getDatetime(botStartTime)
CustomReport('Bot Started', bst, groupName)
CustomReport('Bot Uptime', (Time() - botStartTime) / 60 .. ' minutes', groupName)
CustomReport('Longest Open Time', misc.longestOpenTime / 60 .. ' minutes', groupName)
CustomReport('Shortest Open Time', misc.shortestOpenTime / 60 .. ' minutes', groupName)
CustomReport('Biggest Position', misc.biggestAmt ..' '.. AmountLabel(info.mkt), groupName)
CustomReport('Smallest Position', misc.smallestAmt ..' '.. AmountLabel(info.mkt), groupName)
end
local saveInfo = function(obj)
local group = obj.isBest and 'b' or 'w'
Save(group .. 'r', obj.roi)
Save(group .. 'rs', obj.roiSde)
Save(group .. 'ra', obj.roiAmt)
Save(group .. 'rm', obj.roiMkt)
Save(group .. 'rm2', obj.roiMkt2)
Save(group .. 'pnl', obj.pnl)
Save(group .. 'pnls', obj.pnlSde)
Save(group .. 'pnla', obj.pnlAmt)
Save(group .. 'pnlm', obj.pnlMkt)
Save(group .. 'pnlm2', obj.pnlMkt2)
end
local saveMisc = function(obj)
Save('posst', startTime)
Save('prevPosId', prevPosId)
Save('bst', botStartTime)
Save('posarr', positions)
Save('samt', obj.smallestAmt)
Save('bamt', obj.biggestAmt)
Save('lot', obj.longestOpenTime)
Save('sot', obj.shortestOpenTime)
end
updateInfo(worst, info)
updateInfo(best, info)
updateMisc(info)
--Log(best)
--Log(worst)
Finalize(function()
buildMisc(misc, info)
buildReports(worst)
buildReports(best)
buildFinalData(positions, info)
end)
saveInfo(worst)
saveInfo(best)
saveMisc(misc)
DefineOutput(VoidType)
0 Comments
Sign in to leave a comment.
No comments yet. Be the first!