Files
v2realbot/v2realbot/reporting/archive/optimizecutoffprofloss.py
2023-11-22 12:18:42 +01:00

167 lines
7.0 KiB
Python

import matplotlib
import matplotlib.dates as mdates
#matplotlib.use('Agg') # Set the Matplotlib backend to 'Agg'
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
from datetime import datetime
from typing import List
from enum import Enum
import numpy as np
import v2realbot.controller.services as cs
from rich import print
from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType
from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get#, print
from pathlib import Path
from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY
from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, OrderSide
from io import BytesIO
from v2realbot.utils.historicals import get_historical_bars
from alpaca.data.timeframe import TimeFrame, TimeFrameUnit
from collections import defaultdict
# Assuming Trade, TradeStatus, TradeDirection, TradeStoplossType classes are defined elsewhere
#LOSS and PROFIT without GRAPH
def find_optimal_cutoff(runner_ids: list = None, batch_id: str = None, stream: bool = False):
#TODO dopracovat drawdown a minimalni a maximalni profity nikoliv cumulovane, zamyslet se
#TODO list of runner_ids
#TODO pridelat na vytvoreni runnera a batche, samostatne REST API + na remove archrunnera
if runner_ids is None and batch_id is None:
return -2, f"runner_id or batch_id must be present"
if batch_id is not None:
res, runner_ids =cs.get_archived_runnerslist_byBatchID(batch_id)
if res != 0:
print(f"no batch {batch_id} found")
return -1, f"no batch {batch_id} found"
trades = []
cnt_max = len(runner_ids)
cnt = 0
#zatim zjistujeme start a end z min a max dni - jelikoz muze byt i seznam runner_ids a nejenom batch
end_date = None
start_date = None
for id in runner_ids:
cnt += 1
#get runner
res, sada =cs.get_archived_runner_header_byID(id)
if res != 0:
print(f"no runner {id} found")
return -1, f"no runner {id} found"
print("archrunner")
#print(sada)
if cnt == 1:
start_date = sada.bt_from if sada.mode in [Mode.BT,Mode.PREP] else sada.started
if cnt == cnt_max:
end_date = sada.bt_to if sada.mode in [Mode.BT or Mode.PREP] else sada.stopped
# Parse trades
trades_dicts = sada.metrics["prescr_trades"]
for trade_dict in trades_dicts:
trade_dict['last_update'] = datetime.fromtimestamp(trade_dict.get('last_update')).astimezone(zoneNY) if trade_dict['last_update'] is not None else None
trade_dict['entry_time'] = datetime.fromtimestamp(trade_dict.get('entry_time')).astimezone(zoneNY) if trade_dict['entry_time'] is not None else None
trade_dict['exit_time'] = datetime.fromtimestamp(trade_dict.get('exit_time')).astimezone(zoneNY) if trade_dict['exit_time'] is not None else None
trades.append(Trade(**trade_dict))
#print(trades)
# symbol = sada.symbol
# #hour bars for backtested period
# print(start_date,end_date)
# bars= get_historical_bars(symbol, start_date, end_date, TimeFrame.Hour)
# print("bars for given period",bars)
# """Bars a dictionary with the following keys:
# * high: A list of high prices
# * low: A list of low prices
# * volume: A list of volumes
# * close: A list of close prices
# * hlcc4: A list of HLCC4 indicators
# * open: A list of open prices
# * time: A list of times in UTC (ISO 8601 format)
# * trades: A list of number of trades
# * resolution: A list of resolutions (all set to 'D')
# * confirmed: A list of booleans (all set to True)
# * vwap: A list of VWAP indicator
# * updated: A list of booleans (all set to True)
# * index: A list of integers (from 0 to the length of the list of daily bars)
# """
# Filter to only use trades with status 'CLOSED'
closed_trades = [trade for trade in trades if trade.status == TradeStatus.CLOSED]
#print(closed_trades)
if len(closed_trades) == 0:
return -1, "image generation no closed trades"
# Group trades by date and calculate daily profits
trades_by_day = defaultdict(list)
for trade in trades:
if trade.status == TradeStatus.CLOSED and trade.exit_time:
trade_day = trade.exit_time.date()
trades_by_day[trade_day].append(trade)
# Define ranges for loss and profit cutoffs
min_profit = 0
max_profit = 1000
profit_cutoffs = np.linspace(min_profit, max_profit, 20)
min_loss = 0
max_loss = -1000
loss_cutoffs = np.linspace(min_loss, max_loss, 20)
#print(profit_cutoffs, loss_cutoffs)
# Function to calculate total profit for a given cutoff
def calculate_total_profit(profit_cutoff, loss_cutoff):
total_profit = 0
for day, day_trades in trades_by_day.items():
daily_profit = 0
for trade in day_trades:
daily_profit += trade.profit
if daily_profit >= profit_cutoff or daily_profit <= loss_cutoff:
break
total_profit += daily_profit
return total_profit
# Initialize variables to store optimal cutoffs and max total profit
optimal_profit_cutoff = max_profit
optimal_loss_cutoff = min_loss
max_total_profit = float('-inf')
# Initialize a matrix to store total profits for each combination of cutoffs
total_profits_matrix = np.zeros((len(profit_cutoffs), len(loss_cutoffs)))
for i, profit_cutoff in enumerate(profit_cutoffs):
for j, loss_cutoff in enumerate(loss_cutoffs):
total_profit = calculate_total_profit(profit_cutoff, loss_cutoff)
total_profits_matrix[i, j] = total_profit
if total_profit > max_total_profit:
max_total_profit = total_profit
optimal_profit_cutoff = profit_cutoff
optimal_loss_cutoff = loss_cutoff
# Plotting the results as a heatmap
plt.figure(figsize=(20, 15))
sns.heatmap(total_profits_matrix, xticklabels=np.round(loss_cutoffs, 2), yticklabels=np.round(profit_cutoffs, 2),
annot=True, fmt=".0f", cmap="viridis") #fmt=".0f",
#plasma, coolmap, inferno, viridis
plt.title("Total Profit for Combinations of Profit and Loss Cutoffs")
plt.xlabel("Loss Cutoff")
plt.ylabel("Profit Cutoff")
plt.savefig('optimal_cutoff.png')
return optimal_profit_cutoff, optimal_loss_cutoff, max_total_profit
# Example usage
# trades = [list of Trade objects]
if __name__ == '__main__':
# id_list = ["e8938b2e-8462-441a-8a82-d823c6a025cb"]
# generate_trading_report_image(runner_ids=id_list)
batch_id = "c76b4414"
optimal_profit_cutoff, optimal_loss_cutoff, max_profit = find_optimal_cutoff(batch_id=batch_id)
print(f"Optimal Profit Cutoff: {optimal_profit_cutoff}, Optimal Loss Cutoff: {optimal_loss_cutoff}, Max Profit: {max_profit}")