raulikeda / Backtesting

GNU General Public License v3.0
1 stars 10 forks source link

Fixes issue #28 #31

Closed elijose55 closed 4 years ago

elijose55 commented 4 years ago

Criei a função parcialPnl que recebe um objeto event e calcula de acordo com o preço do instrumento o P&L da estratégia no momento do evento recebido. A seguinte fórmula corresponde ao cálculo do P&L:

P&L = position size * (sell price - buy price)

Porém como era preciso calcular o valor cumulativo do P&L a fórmula abaixo foi utilizada:

P&L = position size * (sell price - buy price) + old P&L

Para aplica-la no código foi utilizado o valor contido em self.result[event.instrument], já existente no programa, considerando que:

self.result[event.instrument] == - (position * buy price + pnl)

Então, chegou-se no resultado abaixo, sendo que a função é chamada dentro da função event, ou seja, ela é executada toda vez que um evento novo é recebido.

    def parcialPnl(self, event):

        if (isinstance(event.price, float)):
            price = event.price
        else:
            price = event.price[3]

        if (event.instrument in self.position):
            if (self.position[event.instrument] != 0): # Bought or Sold
                pnl = (self.position[event.instrument] * price) + self.result[event.instrument]

            self.pnl_dict[event.instrument].append(pnl)

        return 

Os valores de P&L foram armazenados em um atributo da classe, sendo este um dicionário que contém as listas de P&L de cada instrumento utilizado na simulação.

self.pnl_dict = {}
raulikeda commented 4 years ago

Já existe uma função chamada partialResult que calcula o resultado parcial. Ela deveria ser usada para popular uma lista a cada evento recebido do instrumento. Não usar "_" no nome da variável, seguir o padrão.

elijose55 commented 4 years ago

Realizei a correção. A classe Strategy utiliza a função partialResult para realizar o cálculo do P&L parcial para cada instrumento. Esses valores são armazenados no dicionário parcialResult que é composto pelas listas de cada instrumento contendo os seus respectivos P&L parciais.

    def partialResult(self):
        '''
        P&L: position size * (sell price - buy price)
        P&L for bought position: position * (mark price - entry price)
        P&L for sold position: position * (entry price - mark price)

        As in this case the P&L is cumulative, it is necessary to add the old P&L value:
            pnl = position * sell price - position * buy price + pnl

        '''

        for instrument, result in self.result.items():
            if (isinstance(self.last[instrument], float)):
                price = self.last[instrument]
            else:
                price = self.last[instrument][3]

            if (self.position[instrument] != 0):
                pnl = result + \
                    self.position[instrument] * price

            self.parcialResult[instrument].append(pnl)

        return
elijose55 commented 4 years ago

Realizei outra correção e adicionei outra feature. A classe Strategy utiliza a função partialResult para realizar o cálculo do P&L parcial para cada instrumento. Esses valores são armazenados no dicionário parcialResult que é composto pelas listas de cada instrumento contendo os seus respectivos P&L parciais.

    def partialResult(self):
        '''
        P&L: position size * (sell price - buy price)
        P&L for bought position: position * (mark price - entry price)
        P&L for sold position: position * (entry price - mark price)

        As in this case the P&L is cumulative, it is necessary to add the old P&L value:
            pnl = position * sell price - position * buy price + pnl

        '''

        for instrument in self.eventPrices.keys():

            if instrument not in self.parcialResult:
                self.parcialResult[instrument] = []

            if isinstance(self.last[instrument], float):
                price = self.last[instrument]
            else:
                price = self.last[instrument][3]

            if instrument in self.position:
                if self.position[instrument] != 0:
                    pnl = self.result[instrument] + \
                        self.position[instrument] * price
                else:
                    pnl = self.parcialResult[instrument][-1]
            else:
                pnl = 0                

            self.parcialResult[instrument].append(pnl)

        return

O código do commit anterior possui um bug em que a lista não era preenchida caso a estratégia não realizasse nenhum trade. Além disso, adicionei um atributo eventPrices a classe que é um dicionário com os instrumentos recebidos na simulação como chaves e a lista de todos seus preços como os valores. Dessa maneira, é possível plotar o gráfico que mostram o preço do instrumento ao longo do tempo.