[pshaiCmd] Position Statistics

1 315 Views No Comments 4 weeks ago
  • 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()
    • This topic was modified 4 weeks ago by pshai.
    HaasScript Code
    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)
Login or Register to Comment

Unlock your crypto trading potential

Create a free account and enjoy everything we have to offer.

Join for Free