[pshaiCmd] Position Statistics

stable
By pshai in Miscellaneous Published June 2020 👁 1,691 views 💬 0 comments

Description

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 &lt; 10 then m = "0" .. m end
    if d &lt; 10 then d = "0" .. d end
    if h &lt; 10 then h = "0" .. h end
    if mm &lt; 10 then mm = "0" .. mm end
    if s &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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!