From 47e2e255c7a1fe5736e987329027c922cb8b54a1 Mon Sep 17 00:00:00 2001 From: David Brazda Date: Fri, 20 Oct 2023 21:48:46 +0200 Subject: [PATCH] rel profit first version --- v2realbot/common/PrescribedTradeModel.py | 3 ++ v2realbot/common/db.py | 2 +- v2realbot/common/model.py | 2 + v2realbot/controller/services.py | 22 ++++++--- v2realbot/static/js/archivetables.js | 2 +- v2realbot/static/js/libs/prism/prism-log.js | 2 +- v2realbot/strategy/StrategyClassicSL.py | 45 +++++++++++++++++-- v2realbot/strategy/base.py | 3 ++ .../indicators/indicators_hub.py | 4 +- .../strategyblocks/newtrade/conditions.py | 2 +- .../newtrade/prescribedtrades.py | 1 + 11 files changed, 73 insertions(+), 15 deletions(-) diff --git a/v2realbot/common/PrescribedTradeModel.py b/v2realbot/common/PrescribedTradeModel.py index 3d247ca..5f8a178 100644 --- a/v2realbot/common/PrescribedTradeModel.py +++ b/v2realbot/common/PrescribedTradeModel.py @@ -6,6 +6,7 @@ from uuid import UUID class TradeStatus(str, Enum): READY = "ready" ACTIVATED = "activated" + CLOSED = "closed" #FINISHED = "finished" class TradeDirection(str, Enum): @@ -28,4 +29,6 @@ class Trade(BaseModel): stoploss_value: Optional[float] = None profit: Optional[float] = 0 profit_sum: Optional[float] = 0 + rel_profit: Optional[float] = 0 + rel_profit_cum: Optional[float] = 0 diff --git a/v2realbot/common/db.py b/v2realbot/common/db.py index 72d6c48..a16e073 100644 --- a/v2realbot/common/db.py +++ b/v2realbot/common/db.py @@ -79,7 +79,7 @@ def row_to_runarchiveview(row: dict) -> RunArchiveView: trade_count=int(row['trade_count']), end_positions=int(row['end_positions']), end_positions_avgp=float(row['end_positions_avgp']), - metrics=json.loads(row['metrics']), + metrics=json.loads(row['metrics']) if row['metrics'] else None ) #prevede dict radku zpatky na objekt vcetme retypizace diff --git a/v2realbot/common/model.py b/v2realbot/common/model.py index ba2f1a4..a9f04c9 100644 --- a/v2realbot/common/model.py +++ b/v2realbot/common/model.py @@ -191,6 +191,8 @@ class TradeUpdate(BaseModel): pos_avg_price: Optional[float] profit: Optional[float] profit_sum: Optional[float] + rel_profit: Optional[float] + rel_profit_cum: Optional[float] signal_name: Optional[str] diff --git a/v2realbot/controller/services.py b/v2realbot/controller/services.py index 295ed32..ce8f3b1 100644 --- a/v2realbot/controller/services.py +++ b/v2realbot/controller/services.py @@ -7,7 +7,7 @@ from alpaca.data.enums import DataFeed from alpaca.data.timeframe import TimeFrame from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, OrderSide from v2realbot.common.model import RunDay, StrategyInstance, Runner, RunRequest, RunArchive, RunArchiveView, RunArchiveDetail, RunArchiveChange, Bar, TradeEvent, TestList, Intervals, ConfigItem -from v2realbot.utils.utils import AttributeDict, zoneNY, dict_replace_value, Store, parse_toml_string, json_serial, is_open_hours, send_to_telegram +from v2realbot.utils.utils import AttributeDict, zoneNY, zonePRG, dict_replace_value, Store, parse_toml_string, json_serial, is_open_hours, send_to_telegram from v2realbot.utils.ilog import delete_logs from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType from datetime import datetime @@ -21,6 +21,7 @@ from queue import Queue from tinydb import TinyDB, Query, where from tinydb.operations import set import json +import numpy as np from numpy import ndarray from rich import print import pandas as pd @@ -374,9 +375,10 @@ def run_batch_stratin(id: UUID, runReq: RunRequest): cal_list = [] #interval dame do formatu list(RunDays) #TODO do budoucna predelat Interval na RunDays a na zone aware datetime + #zatim testlisty dávám v cz casu for intrvl in testlist.dates: - start_time = zoneNY.localize(datetime.fromisoformat(intrvl.start)) - end_time = zoneNY.localize(datetime.fromisoformat(intrvl.end)) + start_time = zonePRG.localize(datetime.fromisoformat(intrvl.start)) + end_time = zonePRG.localize(datetime.fromisoformat(intrvl.end)) cal_list.append(RunDay(start = start_time, end = end_time, note=intrvl.note, id=testlist.id)) print(f"Getting intervals - RESULT ({len(cal_list)}): {cal_list}") @@ -443,7 +445,7 @@ def batch_run_manager(id: UUID, runReq: RunRequest, rundays: list[RunDay]): cnt_max = len(rundays) cnt = 0 #promenna pro sdileni mezi runy jednotlivych batchů (např. daily profit) - inter_batch_params = dict(batch_profit=0) + inter_batch_params = dict(batch_profit=0, batch_rel_profit=0) note_from_run_request = runReq.note for day in rundays: cnt += 1 @@ -636,6 +638,12 @@ def populate_metrics_output_directory(strat: StrategyInstance, inter_batch_param #naplneni batch sum profitu if inter_batch_params is not None: res["profit"]["batch_sum_profit"] = inter_batch_params["batch_profit"] + res["profit"]["batch_sum_rel_profit"] = inter_batch_params["batch_rel_profit"] + + #rel_profit rozepsane zisky + res["profit"]["rel_profits"] = strat.state.rel_profit_cum + #rel_profit zprumerovane + res["profit"]["rel_profit_cum"] = float(np.mean(strat.state.rel_profit_cum)) if len(strat.state.rel_profit_cum) > 0 else 0 #metrikz z prescribedTrades, pokud existuji try: @@ -691,8 +699,10 @@ def populate_metrics_output_directory(strat: StrategyInstance, inter_batch_param mpt_string = "PT"+str(max_profit_time.hour)+":"+str(max_profit_time.minute) if max_profit_time is not None else "" mlt_string ="LT"+str(max_loss_time.hour)+":"+str(max_loss_time.minute) if max_loss_time is not None else "" + rp_string = "RP" + str(float(np.mean(strat.state.rel_profit_cum))) if len(strat.state.rel_profit_cum) >0 else "noRP" + ##summary pro rychle zobrazeni P333L-222 PT9:30 PL10:30 - res["profit"]["sum"]="P"+str(int(max_profit))+"L"+str(int(max_loss))+" "+ mpt_string+" " + mlt_string + res["profit"]["sum"]="P"+str(int(max_profit))+"L"+str(int(max_loss))+" "+ mpt_string+" " + mlt_string + rp_string + " "+str(strat.state.rel_profit_cum) #vlozeni celeho listu res["prescr_trades"]=json.loads(json.dumps(strat.state.vars.prescribedTrades, default=json_serial)) @@ -729,6 +739,8 @@ def archive_runner(runner: Runner, strat: StrategyInstance, inter_batch_params: #add profit of this batch iteration to batch_sum_profit if inter_batch_params is not None: inter_batch_params["batch_profit"] += round(float(strat.state.profit),2) + inter_batch_params["batch_rel_profit"] += float(np.mean(strat.state.rel_profit_cum)) if len(strat.state.rel_profit_cum) > 0 else 0 + #WIP #populate result metrics dictionary (max drawdown etc.) diff --git a/v2realbot/static/js/archivetables.js b/v2realbot/static/js/archivetables.js index 622e065..1f31bf9 100644 --- a/v2realbot/static/js/archivetables.js +++ b/v2realbot/static/js/archivetables.js @@ -710,7 +710,7 @@ var archiveRecords = //zobrazujeme jen kratkou summary pokud mame, jinak davame vse, do titlu davame vzdy vse //console.log(data) short = null - if ((data.profit) && (data.profit.sum)) { + if ((data) && (data.profit) && (data.profit.sum)) { short = data.profit.sum } else { diff --git a/v2realbot/static/js/libs/prism/prism-log.js b/v2realbot/static/js/libs/prism/prism-log.js index c4e0355..69133e8 100644 --- a/v2realbot/static/js/libs/prism/prism-log.js +++ b/v2realbot/static/js/libs/prism/prism-log.js @@ -28,7 +28,7 @@ Prism.languages.log = { alias: ['error', 'important'] }, { - pattern: /\b(?:WARN|WARNING|WRN|ENTRY|LP|SL)\b/, + pattern: /\b(?:WARN|WARNING|WRN|ENTRY|LP|SL|PROFIT|profit|profit_rel)\b/, alias: ['warning', 'important'] }, { diff --git a/v2realbot/strategy/StrategyClassicSL.py b/v2realbot/strategy/StrategyClassicSL.py index 5ae7bfe..90d3b70 100644 --- a/v2realbot/strategy/StrategyClassicSL.py +++ b/v2realbot/strategy/StrategyClassicSL.py @@ -12,7 +12,7 @@ from datetime import datetime #from rich import print from random import randrange from alpaca.common.exceptions import APIError -import copy +import numpy as np from threading import Event from uuid import UUID, uuid4 from v2realbot.strategyblocks.indicators.indicators_hub import populate_all_indicators @@ -100,7 +100,19 @@ class StrategyClassicSL(Strategy): trade_profit = round((avg_costs-bought_amount),2) self.state.profit += trade_profit - self.state.ilog(e=f"BUY notif - SHORT PROFIT:{round(float(trade_profit),3)} celkem:{round(float(self.state.profit),3)}", msg=str(data.event), bought_amount=bought_amount, avg_costs=avg_costs, trade_qty=data.qty, trade_price=data.price, orderid=str(data.order.id)) + + rel_profit = 0 + #spoctene celkovy relativni profit za trade v procentech ((trade_profit/vstup_naklady)*100) + if vstup_cena != 0 and int(data.order.qty) != 0: + rel_profit = (trade_profit / (vstup_cena * float(data.order.qty))) * 100 + + #pokud jde o finalni FILL - pridame do pole tento celkovy relativnich profit (ze ktereho se pocita kumulativni relativni profit) + rel_profit_cum_calculated = 0 + if data.event == TradeEvent.FILL: + self.state.rel_profit_cum.append(rel_profit) + rel_profit_cum_calculated = np.mean(self.state.rel_profit_cum) + + self.state.ilog(e=f"BUY notif - SHORT PROFIT:{round(float(trade_profit),3)} celkem:{round(float(self.state.profit),3)} rel:{float(rel_profit)} rel_cum:{round(rel_profit_cum_calculated,7)}", msg=str(data.event), rel_profit_cum=str(self.state.rel_profit_cum), bought_amount=bought_amount, avg_costs=avg_costs, trade_qty=data.qty, trade_price=data.price, orderid=str(data.order.id)) #zapsat profit do prescr.trades for trade in self.state.vars.prescribedTrades: @@ -110,7 +122,11 @@ class StrategyClassicSL(Strategy): #pro ulozeni do tradeData scitame vsechen zisk z tohoto tradu (kvuli partialum) trade_profit = trade.profit trade.profit_sum = self.state.profit + trade.rel_profit = rel_profit + trade.rel_profit_cum = rel_profit_cum_calculated signal_name = trade.generated_by + if data.event == TradeEvent.FILL: + trade.status == TradeStatus.CLOSED break if data.event == TradeEvent.FILL: @@ -124,6 +140,8 @@ class StrategyClassicSL(Strategy): setattr(tradeData, "profit_sum", self.state.profit) setattr(tradeData, "signal_name", signal_name) #self.state.ilog(f"updatnut tradeList o profit", tradeData=json.loads(json.dumps(tradeData, default=json_serial))) + setattr(tradeData, "rel_profit", rel_profit) + setattr(tradeData, "rel_profit_cum", rel_profit_cum_calculated) #test na maximalni profit/loss, pokud vypiname pak uz nedelame pripdany reverzal if await self.stop_when_max_profit_loss() is False: @@ -142,7 +160,7 @@ class StrategyClassicSL(Strategy): if trade.id == self.state.vars.pending: signal_name = trade.generated_by - #zapsat update profitu do tradeList + #zapsat do tradeList for tradeData in self.state.tradeList: if tradeData.execution_id == data.execution_id: setattr(tradeData, "signal_name", signal_name) @@ -194,7 +212,19 @@ class StrategyClassicSL(Strategy): trade_profit = round((sold_amount - avg_costs),2) self.state.profit += trade_profit - self.state.ilog(e=f"SELL notif - PROFIT:{round(float(trade_profit),3)} celkem:{round(float(self.state.profit),3)}", msg=str(data.event), sold_amount=sold_amount, avg_costs=avg_costs, trade_qty=data.qty, trade_price=data.price, orderid=str(data.order.id)) + + rel_profit = 0 + #spoctene celkovy relativni profit za trade v procentech ((trade_profit/vstup_naklady)*100) + if vstup_cena != 0 and data.order.qty != 0: + rel_profit = (trade_profit / (vstup_cena * float(data.order.qty))) * 100 + + rel_profit_cum_calculated = 0 + #pokud jde o finalni FILL - pridame do pole relativnich profit (ze ktereho se pocita kumulativni relativni profit) + if data.event == TradeEvent.FILL: + self.state.rel_profit_cum.append(rel_profit) + rel_profit_cum_calculated = np.mean(self.state.rel_profit_cum) + + self.state.ilog(e=f"SELL notif - PROFIT:{round(float(trade_profit),3)} celkem:{round(float(self.state.profit),3)} rel:{float(rel_profit)} rel_cum:{round(rel_profit_cum_calculated,7)}", msg=str(data.event), rel_profit_cum = str(self.state.rel_profit_cum), sold_amount=sold_amount, avg_costs=avg_costs, trade_qty=data.qty, trade_price=data.price, orderid=str(data.order.id)) #zapsat profit do prescr.trades for trade in self.state.vars.prescribedTrades: @@ -204,7 +234,11 @@ class StrategyClassicSL(Strategy): #pro ulozeni do tradeData scitame vsechen zisk z tohoto tradu (kvuli partialum) trade_profit = trade.profit trade.profit_sum = self.state.profit + trade.rel_profit = rel_profit + trade.rel_profit_cum = rel_profit_cum_calculated signal_name = trade.generated_by + if data.event == TradeEvent.FILL: + trade.status == TradeStatus.CLOSED break if data.event == TradeEvent.FILL: @@ -218,6 +252,9 @@ class StrategyClassicSL(Strategy): setattr(tradeData, "profit_sum", self.state.profit) setattr(tradeData, "signal_name", signal_name) #self.state.ilog(f"updatnut tradeList o profi {str(tradeData)}") + setattr(tradeData, "rel_profit", rel_profit) + setattr(tradeData, "rel_profit_cum", rel_profit_cum_calculated) + #sem nejspis update skutecne vstupni ceny (celk.mnozstvi(order.qty) a avg_costs), to same i druhy smer if await self.stop_when_max_profit_loss() is False: diff --git a/v2realbot/strategy/base.py b/v2realbot/strategy/base.py index 049cf18..c2d6405 100644 --- a/v2realbot/strategy/base.py +++ b/v2realbot/strategy/base.py @@ -705,7 +705,10 @@ class StrategyState: self.sell_l = self.interface.sell_l self.cancel_pending_buys = None self.iter_log_list = [] + #celkovy profit (prejmennovat na profit_cum) self.profit = 0 + #celkovy relativni profit (obsahuje pole relativnich zisku, z jeho meanu se spocita celkovy rel_profit_cu,) + self.rel_profit_cum = [] self.tradeList = [] #nova promenna pro externi data do ArchiveDetaili, napr. pro zobrazeni v grafu, je zde např. SL history self.extData = {} diff --git a/v2realbot/strategyblocks/indicators/indicators_hub.py b/v2realbot/strategyblocks/indicators/indicators_hub.py index 6c73bc6..5b9bb1e 100644 --- a/v2realbot/strategyblocks/indicators/indicators_hub.py +++ b/v2realbot/strategyblocks/indicators/indicators_hub.py @@ -53,9 +53,9 @@ def populate_all_indicators(data, state: StrategyState): state.ilog(lvl=1,e=f"{conf} {data['index']}-{conf_bar}--delta:{last_update_delta}---AVGdelta:{avg_delta}", data=data) #TODO tento lof patri spis do nextu classic SL - je poplatny typu stratefie - #TODO na toto se podivam, nejak moc zajasonovani a zpatky + #TODO na toto se podivam, nejak moc zajasonovani a zpatky - #PERF PROBLEM - state.ilog(lvl=1,e="ENTRY", msg=f"LP:{lp} P:{state.positions}/{round(float(state.avgp),3)} SL:{state.vars.activeTrade.stoploss_value if state.vars.activeTrade is not None else None} profit:{round(float(state.profit),2)} Trades:{len(state.tradeList)} pend:{state.vars.pending}", activeTrade=json.loads(json.dumps(state.vars.activeTrade, default=json_serial)), prescribedTrades=json.loads(json.dumps(state.vars.prescribedTrades, default=json_serial)), pending=str(state.vars.pending)) + state.ilog(lvl=1,e="ENTRY", msg=f"LP:{lp} P:{state.positions}/{round(float(state.avgp),3)} SL:{state.vars.activeTrade.stoploss_value if state.vars.activeTrade is not None else None} profit:{round(float(state.profit),2)} profit_rel:{round(np.mean(state.rel_profit_cum),6) if len(state.rel_profit_cum)>0 else 0} Trades:{len(state.tradeList)} pend:{state.vars.pending}", rel_profit_cum=str(state.rel_profit_cum), activeTrade=json.loads(json.dumps(state.vars.activeTrade, default=json_serial)), prescribedTrades=json.loads(json.dumps(state.vars.prescribedTrades, default=json_serial)), pending=str(state.vars.pending)) #kroky pro CONFIRMED BAR only if conf_bar == 1: diff --git a/v2realbot/strategyblocks/newtrade/conditions.py b/v2realbot/strategyblocks/newtrade/conditions.py index b60c82f..963dde8 100644 --- a/v2realbot/strategyblocks/newtrade/conditions.py +++ b/v2realbot/strategyblocks/newtrade/conditions.py @@ -97,7 +97,7 @@ def common_go_preconditions_check(state, data, signalname: str, options: dict): window_close = safe_get(options, "window_close",safe_get(state.vars, "window_close",390)) if is_window_open(datetime.fromtimestamp(data['updated']).astimezone(zoneNY), window_open, window_close) is False: - state.ilog(lvl=1,e=f"SIGNAL {signalname} - WINDOW CLOSED", msg=f"{window_open=} {window_close=} ") + state.ilog(lvl=1,e=f"SIGNAL {signalname} - WINDOW CLOSED", msg=f"{window_open=} {window_close=} ", time=str(datetime.fromtimestamp(data['updated']).astimezone(zoneNY))) return False min_bar_index = safe_get(options, "min_bar_index",safe_get(state.vars, "min_bar_index",0)) diff --git a/v2realbot/strategyblocks/newtrade/prescribedtrades.py b/v2realbot/strategyblocks/newtrade/prescribedtrades.py index d4637cd..1c15227 100644 --- a/v2realbot/strategyblocks/newtrade/prescribedtrades.py +++ b/v2realbot/strategyblocks/newtrade/prescribedtrades.py @@ -6,6 +6,7 @@ from datetime import datetime import json from v2realbot.strategyblocks.activetrade.helpers import insert_SL_history, get_default_sl_value, normalize_tick +#TODO nad prescribed trades postavit vstupni funkce def execute_prescribed_trades(state: StrategyState, data): ##evaluate prescribed trade, prvni eligible presuneme do activeTrade, zmenime stav and vytvorime objednavky