From 30048364bb3b831762562f9bc8caef7fa9a734d9 Mon Sep 17 00:00:00 2001 From: David Brazda Date: Fri, 23 Aug 2024 14:16:51 +0200 Subject: [PATCH] first commit --- testy/fibonaccistoploss.py | 8 +- v2realbot/ENTRY_ClassicSL_v01.py | 34 +- v2realbot/backtesting/backtester.py | 101 +++--- v2realbot/common/PrescribedTradeModel.py | 41 --- v2realbot/common/model.py | 93 ++++- v2realbot/common/transform.py | 8 +- v2realbot/config.py | 16 +- v2realbot/controller/run_manager.py | 2 +- v2realbot/controller/services.py | 28 +- v2realbot/interfaces/backtest_interface.py | 22 +- v2realbot/interfaces/live_interface.py | 4 +- v2realbot/loader/order_updates_streamer.py | 6 +- v2realbot/main.py | 4 +- .../analyzer/WIP_daily_profit_distribution.py | 2 +- .../analyzer/daily_profit_distribution.py | 2 +- .../reporting/analyzer/example_plugin.py | 2 +- .../reporting/analyzer/find_optimal_cutoff.py | 2 +- .../analyzer/find_optimal_cutoff_REL.py | 2 +- .../analyzer/ls_profit_distribution.py | 2 +- .../analyzer/profit_distribution_by_month.py | 2 +- .../reporting/analyzer/profit_sum_by_hour.py | 2 +- .../analyzer/summarize_trade_metrics.py | 2 +- v2realbot/reporting/archive/optimizecutoff.py | 2 +- .../archive/optimizecutoffprofloss.py | 2 +- .../reporting/archive/optimizecutoffv2.py | 2 +- v2realbot/reporting/load_trades.py | 4 +- v2realbot/reporting/metricstoolsimage.py | 2 +- v2realbot/scheduler/ap_scheduler.py | 2 +- .../static/js/tables/archivetable/init.js | 2 +- v2realbot/strategy/StrategyClassicSL.py | 205 +++++------ v2realbot/strategy/base.py | 149 +++++--- .../activetrade/activetrade_hub.py | 16 +- .../activetrade/close/close_position.py | 39 ++- .../activetrade/close/conditions.py | 52 +-- .../activetrade/close/eod_exit.py | 20 +- .../activetrade/close/evaluate_close.py | 321 +++++++++--------- .../strategyblocks/activetrade/helpers.py | 54 +-- .../strategyblocks/activetrade/sl/optimsl.py | 24 +- .../strategyblocks/activetrade/sl/trailsl.py | 134 ++++---- .../indicators/indicators_hub.py | 6 +- .../strategyblocks/indicators/slopeLP.py | 8 +- .../strategyblocks/inits/init_directives.py | 2 +- .../strategyblocks/inits/init_indicators.py | 2 +- .../strategyblocks/newtrade/conditions.py | 2 +- .../newtrade/prescribedtrades.py | 171 +++++----- v2realbot/strategyblocks/newtrade/signals.py | 26 +- v2realbot/strategyblocks/newtrade/sizing.py | 5 +- v2realbot/tools/createbatchimage.py | 2 +- v2realbot/tools/loadbatch.py | 4 +- v2realbot/utils/utils.py | 219 +++++++++++- 50 files changed, 1130 insertions(+), 732 deletions(-) delete mode 100644 v2realbot/common/PrescribedTradeModel.py diff --git a/testy/fibonaccistoploss.py b/testy/fibonaccistoploss.py index 78c290f..7d0bdb4 100644 --- a/testy/fibonaccistoploss.py +++ b/testy/fibonaccistoploss.py @@ -1,9 +1,9 @@ import numpy as np -from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus +from v2realbot.common.model import Trade, TradeDirection, TradeStatus from typing import Tuple from copy import deepcopy from v2realbot.strategy.base import StrategyState -from v2realbot.strategyblocks.activetrade.helpers import get_max_profit_price, get_profit_target_price, get_override_for_active_trade, keyword_conditions_met +from v2realbot.strategyblocks.activetrade.helpers import get_max_profit_price, get_profit_target_price, get_signal_section_directive, keyword_conditions_met from v2realbot.utils.utils import safe_get # FIBONACCI PRO PROFIT A SL @@ -63,10 +63,10 @@ class SLOptimizer: def initialize_levels(self, state): directive_name = 'SL_opt_exit_levels_'+str(self.direction) - SL_opt_exit_levels = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None)) + SL_opt_exit_levels = get_signal_section_directive(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None)) directive_name = 'SL_opt_exit_sizes_'+str(self.direction) - SL_opt_exit_sizes = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None)) + SL_opt_exit_sizes = get_signal_section_directive(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None)) if SL_opt_exit_levels is None or SL_opt_exit_sizes is not None: print("no directives found: SL_opt_exit_levels/SL_opt_exit_sizes") diff --git a/v2realbot/ENTRY_ClassicSL_v01.py b/v2realbot/ENTRY_ClassicSL_v01.py index 3667906..2e2526f 100644 --- a/v2realbot/ENTRY_ClassicSL_v01.py +++ b/v2realbot/ENTRY_ClassicSL_v01.py @@ -59,25 +59,12 @@ Hlavní loop: """ def next(data, state: StrategyState): - ##print(10*"*","NEXT START",10*"*") - # important vars state.avgp, state.positions, state.vars, data - - #indicators moved to call_next in upper class + print(10*"*", state.account_variables) - #pokud mame prazdne pozice a neceka se na nic - if state.positions == 0 and state.vars.pending is None: - #vykoname trady ve fronte - execute_prescribed_trades(state, data) - #pokud se neaktivoval nejaky trade, poustime signal search - ale jen jednou za bar? - #if conf_bar == 1: - if state.vars.pending is None: - signal_search(state, data) - #pro jistotu ihned zpracujeme - execute_prescribed_trades(state, data) - - #mame aktivni trade a neceka se n anic - elif state.vars.activeTrade and state.vars.pending is None: - manage_active_trade(state, data) + execute_prescribed_trades(state, data) + signal_search(state, data) + execute_prescribed_trades(state, data) #pro jistotu ihned zpracujeme + manage_active_trade(state, data) def init(state: StrategyState): #place to declare new vars @@ -88,13 +75,13 @@ def init(state: StrategyState): #nove atributy na rizeni tradu #identifikuje provedenou změnu na Tradu (neděláme změny dokud nepřijde potvrzeni z notifikace) - state.vars.pending = None + #state.vars.pending = None #nahrazeno pebnding pod accountem state.account_variables[account.name].pending #obsahuje aktivni Trade a jeho nastaveni - state.vars.activeTrade = None #pending/Trade + #state.vars.activeTrade = None #pending/Trade moved to account_variables #obsahuje pripravene Trady ve frontě state.vars.prescribedTrades = [] #flag pro reversal - state.vars.requested_followup = None + #state.vars.requested_followup = None #nahrazeno pod accountem #TODO presunout inicializaci work_dict u podminek - sice hodnoty nepujdou zmenit, ale zlepsi se performance #pripadne udelat refresh kazdych x-iterací @@ -102,9 +89,8 @@ def init(state: StrategyState): state.vars.mode = None state.vars.last_50_deltas = [] state.vars.next_new = 0 - state.vars.last_buy_index = None - state.vars.last_exit_index = None - state.vars.last_in_index = None + state.vars.last_entry_index = None #mponechano obecne pro vsechny accounty + state.vars.last_exit_index = None #obecna varianta ponechana state.vars.last_update_time = 0 state.vars.reverse_position_waiting_amount = 0 #INIT promenne, ktere byly zbytecne ve stratvars diff --git a/v2realbot/backtesting/backtester.py b/v2realbot/backtesting/backtester.py index 47e20ec..47baba1 100644 --- a/v2realbot/backtesting/backtester.py +++ b/v2realbot/backtesting/backtester.py @@ -39,7 +39,7 @@ """ from uuid import UUID, uuid4 from alpaca.trading.enums import OrderSide, OrderStatus, TradeEvent, OrderType -from v2realbot.common.model import TradeUpdate, Order +from v2realbot.common.model import TradeUpdate, Order, Account from rich import print as printanyway import threading import asyncio @@ -61,6 +61,7 @@ import dash_bootstrap_components as dbc from dash.dependencies import Input, Output from dash import dcc, html, dash_table, Dash import v2realbot.utils.config_handler as cfh +from typing import Set """" LATENCY DELAYS .000 trigger - last_trade_time (.4246266) @@ -74,7 +75,20 @@ lock = threading.Lock #todo nejspis dat do classes, aby se mohlo backtestovat paralelne #ted je globalni promena last_time_now a self.account a cash class Backtester: - def __init__(self, symbol: str, order_fill_callback: callable, btdata: list, bp_from: datetime, bp_to: datetime, cash: float = 100000): + """ + Initializes a new instance of the Backtester class. + Args: + symbol (str): The symbol of the security being backtested. + accounts (set): A set of accounts to use for backtesting. + order_fill_callback (callable): A callback function to handle order fills. + btdata (list): A list of backtesting data. + bp_from (datetime): The start date of the backtesting period. + bp_to (datetime): The end date of the backtesting period. + cash (float, optional): The initial cash balance. Defaults to 100000. + Returns: + None + """ + def __init__(self, symbol: str, accounts: Set, order_fill_callback: callable, btdata: list, bp_from: datetime, bp_to: datetime, cash: float = 100000): #this TIME value determines true time for submit, replace, cancel order should happen (allowing past) #it is set by every iteration of BT or before fill callback - allowing past events to happen self.time = None @@ -83,6 +97,7 @@ class Backtester: self.btdata = btdata self.backtest_start = None self.backtest_end = None + self.accounts = accounts self.cash_init = cash #backtesting period self.bp_from = bp_from @@ -90,9 +105,11 @@ class Backtester: self.cash = cash self.cash_reserved_for_shorting = 0 self.trades = [] - self.account = { "BAC": [0, 0] } - # { "BAC": [avgp, size] } - self.open_orders =[] + def_acc_value = {self.symbol: [0, 0]} + self.internal_account = { account.name:def_acc_value for account in accounts } + # { "ACCOUNT1": {}"BAC": [avgp, size]}, .... } + self.open_orders =[] #open orders shared for all accounts, account being an attribute + # self.open_orders = [Order(id=uuid4(), # submitted_at = datetime(2023, 3, 17, 9, 30, 0, 0, tzinfo=zoneNY), # symbol = "BAC", @@ -110,6 +127,8 @@ class Backtester: # side = OrderSide.BUY)] # + + def execute_orders_and_callbacks(self, intime: float): """"" Voláno ze strategie před každou iterací s časem T. @@ -166,7 +185,7 @@ class Backtester: for order in self.open_orders: #pokud je vyplneny symbol, tak jedeme jen tyto, jinak vsechny - print(order.id, datetime.timestamp(order.submitted_at), order.symbol, order.side, order.order_type, order.qty, order.limit_price, order.submitted_at) + print(order.account.name, order.id, datetime.timestamp(order.submitted_at), order.symbol, order.side, order.order_type, order.qty, order.limit_price, order.submitted_at) if order.canceled_at: #ic("deleting canceled order",order.id) todel.append(order) @@ -348,21 +367,22 @@ class Backtester: #ic(o.filled_at, o.filled_avg_price) - a = self.update_account(o = o) + a = self.update_internal_account(o = o) if a < 0: tlog("update_account ERROR") return -1 - trade = TradeUpdate(order = o, + trade = TradeUpdate(account=o.account, + order = o, event = TradeEvent.FILL, execution_id = str(uuid4()), timestamp = datetime.fromtimestamp(fill_time), - position_qty= self.account[o.symbol][0], + position_qty= self.internal_account[o.symbol][0], price=float(fill_price), qty = o.qty, value = float(o.qty*fill_price), cash = self.cash, - pos_avg_price = self.account[o.symbol][1]) + pos_avg_price = self.internal_account[o.symbol][1]) self.trades.append(trade) @@ -379,49 +399,49 @@ class Backtester: self.time = time + float(cfh.config_handler.get_val('BT_DELAYS','fill_to_not')) print("current bt.time",self.time) #print("FILL NOTIFICATION: ", tradeupdate) - res = asyncio.run(self.order_fill_callback(tradeupdate)) + res = asyncio.run(self.order_fill_callback(tradeupdate, tradeupdate.account)) return 0 - def update_account(self, o: Order): + def update_internal_account(self, o: Order): #updatujeme self.account #pokud neexistuje klic v accountu vytvorime si ho - if o.symbol not in self.account: + if o.symbol not in self.internal_account[o.account.name]: # { "BAC": [size, avgp] } - self.account[o.symbol] = [0,0] + self.internal_account[o.account.name][o.symbol] = [0,0] if o.side == OrderSide.BUY: #[size, avgp] - newsize = (self.account[o.symbol][0] + o.qty) + newsize = (self.internal_account[o.account.name][o.symbol][0] + o.qty) #JPLNE UZAVRENI SHORT (avgp 0) if newsize == 0: newavgp = 0 #CASTECNE UZAVRENI SHORT (avgp puvodni) - elif newsize < 0: newavgp = self.account[o.symbol][1] + elif newsize < 0: newavgp = self.internal_account[o.account.name][o.symbol][1] #JDE O LONG (avgp nove) else: - newavgp = ((self.account[o.symbol][0] * self.account[o.symbol][1]) + (o.qty * o.filled_avg_price)) / (self.account[o.symbol][0] + o.qty) + newavgp = ((self.internal_account[o.account.name][o.symbol][0] * self.internal_account[o.account.name][o.symbol][1]) + (o.qty * o.filled_avg_price)) / (self.internal_account[account.name][o.symbol][0] + o.qty) - self.account[o.symbol] = [newsize, newavgp] + self.internal_account[o.account.name][o.symbol] = [newsize, newavgp] self.cash = self.cash - (o.qty * o.filled_avg_price) return 1 #sell elif o.side == OrderSide.SELL: - newsize = self.account[o.symbol][0]-o.qty + newsize = self.internal_account[o.account.name][o.symbol][0]-o.qty #UPLNE UZAVRENI LONGU (avgp 0) if newsize == 0: newavgp = 0 #CASTECNE UZAVRENI LONGU (avgp puvodni) - elif newsize > 0: newavgp = self.account[o.symbol][1] + elif newsize > 0: newavgp = self.internal_account[o.account.name][o.symbol][1] #jde o SHORT (avgp nove) else: #pokud je predchozi 0 - tzn. jde o prvni short - if self.account[o.symbol][1] == 0: + if self.internal_account[o.account.name][o.symbol][1] == 0: newavgp = o.filled_avg_price else: - newavgp = ((abs(self.account[o.symbol][0]) * self.account[o.symbol][1]) + (o.qty * o.filled_avg_price)) / (abs(self.account[o.symbol][0]) + o.qty) + newavgp = ((abs(self.internal_account[o.account.name][o.symbol][0]) * self.internal_account[o.account.name][o.symbol][1]) + (o.qty * o.filled_avg_price)) / (abs(self.internal_account[o.account.name][o.symbol][0]) + o.qty) - self.account[o.symbol] = [newsize, newavgp] + self.internal_account[o.account.name][o.symbol] = [newsize, newavgp] #pokud jde o prodej longu(nova pozice je>=0) upravujeme cash - if self.account[o.symbol][0] >= 0: + if self.internal_account[o.account.name][o.symbol][0] >= 0: self.cash = float(self.cash + (o.qty * o.filled_avg_price)) print("uprava cashe, jde o prodej longu") else: @@ -466,7 +486,7 @@ class Backtester: # #ic("get last price") # return self.btdata[i-1][1] - def submit_order(self, time: float, symbol: str, side: OrderSide, size: int, order_type: OrderType, price: float = None): + def submit_order(self, time: float, symbol: str, side: OrderSide, size: int, order_type: OrderType, account: Account, price: float = None): """submit order - zakladni validace - vloží do self.open_orders s daným časem @@ -499,9 +519,9 @@ class Backtester: return -1 #pokud neexistuje klic v accountu vytvorime si ho - if symbol not in self.account: + if symbol not in self.internal_account[account.name]: # { "BAC": [size, avgp] } - self.account[symbol] = [0,0] + self.internal_account[account.name][symbol] = [0,0] #check for available quantity if side == OrderSide.SELL: @@ -509,15 +529,15 @@ class Backtester: reserved_price = 0 #with lock: for o in self.open_orders: - if o.side == OrderSide.SELL and o.symbol == symbol and o.canceled_at is None: + if o.side == OrderSide.SELL and o.symbol == symbol and o.canceled_at is None and o.account==account: reserved += o.qty cena = o.limit_price if o.limit_price else self.get_last_price(time, o.symbol) reserved_price += o.qty * cena print("blokovano v open orders pro sell qty: ", reserved, "celkem:", reserved_price) - actual_minus_reserved = int(self.account[symbol][0]) - reserved + actual_minus_reserved = int(self.internal_account[account.name][symbol][0]) - reserved if actual_minus_reserved > 0 and actual_minus_reserved - int(size) < 0: - printanyway("not enough shares available to sell or shorting while long position",self.account[symbol][0],"reserved",reserved,"available",int(self.account[symbol][0]) - reserved,"selling",size) + printanyway("not enough shares available to sell or shorting while long position",self.internal_account[account.name][symbol][0],"reserved",reserved,"available",int(self.internal_account[account.name][symbol][0]) - reserved,"selling",size) return -1 #if is shorting - check available cash to short @@ -533,13 +553,13 @@ class Backtester: reserved_price = 0 #with lock: for o in self.open_orders: - if o.side == OrderSide.BUY and o.canceled_at is None: + if o.side == OrderSide.BUY and o.canceled_at is None and o.account==account: cena = o.limit_price if o.limit_price else self.get_last_price(time, o.symbol) reserved_price += o.qty * cena reserved_qty += o.qty print("blokovano v open orders for buy: qty, price", reserved_qty, reserved_price) - actual_plus_reserved_qty = int(self.account[symbol][0]) + reserved_qty + actual_plus_reserved_qty = int(self.internal_account[account.name][symbol][0]) + reserved_qty #jde o uzavreni shortu if actual_plus_reserved_qty < 0 and (actual_plus_reserved_qty + int(size)) > 0: @@ -555,6 +575,7 @@ class Backtester: id = str(uuid4()) order = Order(id=id, + account=account, submitted_at = datetime.fromtimestamp(float(time)), symbol=symbol, order_type = order_type, @@ -569,7 +590,7 @@ class Backtester: return id - def replace_order(self, id: str, time: float, size: int = None, price: float = None): + def replace_order(self, id: str, time: float, account: Account, size: int = None, price: float = None): """replace order - zakladni validace vrací synchronně - vrací číslo nové objednávky @@ -586,7 +607,7 @@ class Backtester: #with lock: for o in self.open_orders: print(o.id) - if str(o.id) == str(id): + if str(o.id) == str(id) and o.account == account: newid = str(uuid4()) o.id = newid o.submitted_at = datetime.fromtimestamp(time) @@ -597,7 +618,7 @@ class Backtester: print("BT: replacement order doesnt exist") return 0 - def cancel_order(self, time: float, id: str): + def cancel_order(self, time: float, id: str, account: Account): """cancel order - základní validace vrací synchronně - vymaže objednávku z open orders @@ -613,22 +634,22 @@ class Backtester: return 0 #with lock: for o in self.open_orders: - if str(o.id) == id: + if str(o.id) == id and o.account == account: o.canceled_at = time print("set as canceled in self.open_orders") return 1 print("BTC: cantchange. open order doesnt exist") return 0 - def get_open_position(self, symbol: str): + def get_open_position(self, symbol: str, account: Account): """get positions ->(avg,size)""" #print("BT:get open positions entry") try: - return self.account[symbol][1], self.account[symbol][0] + return self.internal_account[account.name][symbol][1], self.internal_account[account.name][symbol][0] except: return (0,0) - def get_open_orders(self, side: OrderSide, symbol: str): + def get_open_orders(self, side: OrderSide, symbol: str, account: Account): """get open orders ->list(OrderNotification)""" print("BT:get open orders entry") if len(self.open_orders) == 0: @@ -638,7 +659,7 @@ class Backtester: #with lock: for o in self.open_orders: #print(o) - if o.symbol == symbol and o.canceled_at is None: + if o.symbol == symbol and o.canceled_at is None and o.account == account: if side is None or o.side == side: res.append(o) return res diff --git a/v2realbot/common/PrescribedTradeModel.py b/v2realbot/common/PrescribedTradeModel.py deleted file mode 100644 index db14bff..0000000 --- a/v2realbot/common/PrescribedTradeModel.py +++ /dev/null @@ -1,41 +0,0 @@ -from enum import Enum -from datetime import datetime -from pydantic import BaseModel -from typing import Any, Optional, List, Union -from uuid import UUID -class TradeStatus(str, Enum): - READY = "ready" - ACTIVATED = "activated" - CLOSED = "closed" - #FINISHED = "finished" - -class TradeDirection(str, Enum): - LONG = "long" - SHORT = "short" - -class TradeStoplossType(str, Enum): - FIXED = "fixed" - TRAILING = "trailing" - -#Predpis obchodu vygenerovany signalem, je to zastresujici jednotka -#ke kteremu jsou pak navazany jednotlivy FILLy (reprezentovany model.TradeUpdate) - napr. castecne exity atp. -class Trade(BaseModel): - id: UUID - last_update: datetime - entry_time: Optional[datetime] = None - exit_time: Optional[datetime] = None - status: TradeStatus - generated_by: Optional[str] = None - direction: TradeDirection - entry_price: Optional[float] = None - goal_price: Optional[float] = None - size: Optional[int] = None - # size_multiplier je pomocna promenna pro pocitani relativniho denniho profit - size_multiplier: Optional[float] = None - # stoploss_type: TradeStoplossType - 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/model.py b/v2realbot/common/model.py index fc690b1..06e2590 100644 --- a/v2realbot/common/model.py +++ b/v2realbot/common/model.py @@ -5,10 +5,75 @@ from rich import print from typing import Any, Optional, List, Union from datetime import datetime, date from pydantic import BaseModel, Field -from v2realbot.enums.enums import Mode, Account, SchedulerStatus, Moddus, Market +from v2realbot.enums.enums import Mode, Account, SchedulerStatus, Moddus, Market, Followup from alpaca.data.enums import Exchange +from enum import Enum +from datetime import datetime +from pydantic import BaseModel +from typing import Any, Optional, List, Union +from uuid import UUID +#prescribed model +#from prescribed model +class InstantIndicator(BaseModel): + name: str + toml: str + + +class TradeStatus(str, Enum): + READY = "ready" + ACTIVATED = "activated" + CLOSED = "closed" + #FINISHED = "finished" + +class TradeDirection(str, Enum): + LONG = "long" + SHORT = "short" + +class TradeStoplossType(str, Enum): + FIXED = "fixed" + TRAILING = "trailing" + +#Predpis obchodu vygenerovany signalem, je to zastresujici jednotka +#ke kteremu jsou pak navazany jednotlivy FILLy (reprezentovany model.TradeUpdate) - napr. castecne exity atp. +class Trade(BaseModel): + account: Account + id: UUID + last_update: datetime + entry_time: Optional[datetime] = None + exit_time: Optional[datetime] = None + status: TradeStatus + generated_by: Optional[str] = None + direction: TradeDirection + entry_price: Optional[float] = None + goal_price: Optional[float] = None + size: Optional[int] = None + # size_multiplier je pomocna promenna pro pocitani relativniho denniho profit + size_multiplier: Optional[float] = None + # stoploss_type: TradeStoplossType + 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 + +#account variables that can be accessed by ACCOUNT key dictionary +class AccountVariables(BaseModel): + positions: float = 0 + avgp: float = 0 + pending: str = None + blockbuy: int = 0 + wait_for_fill: float = None + profit: float = 0 + docasny_rel_profit: list = [] + rel_profit_cum: list = [] + last_entry_index: int = None #acc varianta, mame taky obnecnou state.vars.last_entry_index + requested_followup: Followup = None + activeTrade: Trade = None + dont_exit_already_activated: bool = False + #activeTrade, prescribedTrades + #tbd transferables? #models for server side datatables @@ -91,7 +156,7 @@ class TestList(BaseModel): dates: List[Intervals] #for GUI to fetch historical trades on given symbol -class Trade(BaseModel): +class TradeView(BaseModel): symbol: str timestamp: datetime exchange: Optional[Union[Exchange, str]] = None @@ -189,8 +254,8 @@ class RunnerView(BaseModel): run_symbol: Optional[str] = None run_trade_count: Optional[int] = 0 run_profit: Optional[float] = 0 - run_positions: Optional[int] = 0 - run_avgp: Optional[float] = 0 + run_positions: Optional[dict] = 0 + run_avgp: Optional[dict] = 0 run_stopped: Optional[datetime] = None run_paused: Optional[datetime] = None @@ -208,8 +273,8 @@ class Runner(BaseModel): run_ilog_save: Optional[bool] = False run_trade_count: Optional[int] = None run_profit: Optional[float] = None - run_positions: Optional[int] = None - run_avgp: Optional[float] = None + run_positions: Optional[dict] = None + run_avgp: Optional[dict] = None run_strat_json: Optional[str] = None run_stopped: Optional[datetime] = None run_paused: Optional[datetime] = None @@ -247,6 +312,7 @@ class Bar(BaseModel): vwap: Optional[float] = 0 class Order(BaseModel): + account: Account id: UUID submitted_at: datetime filled_at: Optional[datetime] = None @@ -262,6 +328,7 @@ class Order(BaseModel): #entita pro kazdy kompletni FILL, je navazana na prescribed_trade class TradeUpdate(BaseModel): + account: Account event: Union[TradeEvent, str] execution_id: Optional[UUID] = None order: Order @@ -307,8 +374,8 @@ class RunArchive(BaseModel): ilog_save: Optional[bool] = False profit: float = 0 trade_count: int = 0 - end_positions: int = 0 - end_positions_avgp: float = 0 + end_positions: Union[dict,str] = None + end_positions_avgp: Union[dict,str] = None metrics: Union[dict, str] = None stratvars_toml: Optional[str] = None @@ -329,8 +396,8 @@ class RunArchiveView(BaseModel): ilog_save: Optional[bool] = False profit: float = 0 trade_count: int = 0 - end_positions: int = 0 - end_positions_avgp: float = 0 + end_positions: Union[dict,int] = None + end_positions_avgp: Union[dict,float] = None metrics: Union[dict, str] = None batch_profit: float = 0 # Total profit for the batch - now calculated during query batch_count: int = 0 # Count of runs in the batch - now calculated during query @@ -359,9 +426,3 @@ class RunArchiveDetail(BaseModel): trades: List[TradeUpdate] ext_data: Optional[dict] = None - -class InstantIndicator(BaseModel): - name: str - toml: str - - diff --git a/v2realbot/common/transform.py b/v2realbot/common/transform.py index bd17cfd..259a24e 100644 --- a/v2realbot/common/transform.py +++ b/v2realbot/common/transform.py @@ -51,8 +51,8 @@ def row_to_runarchiveview(row: dict) -> RunArchiveView: ilog_save=bool(row['ilog_save']), profit=float(row['profit']), trade_count=int(row['trade_count']), - end_positions=int(row['end_positions']), - end_positions_avgp=float(row['end_positions_avgp']), + end_positions=orjson.loads(row['end_positions']), + end_positions_avgp=orjson.loads(row['end_positions_avgp']), metrics=orjson.loads(row['metrics']) if row['metrics'] else None, batch_profit=int(row['batch_profit']) if row['batch_profit'] and row['batch_id'] else 0, batch_count=int(row['batch_count']) if row['batch_count'] and row['batch_id'] else 0, @@ -79,8 +79,8 @@ def row_to_runarchive(row: dict) -> RunArchive: ilog_save=bool(row['ilog_save']), profit=float(row['profit']), trade_count=int(row['trade_count']), - end_positions=int(row['end_positions']), - end_positions_avgp=float(row['end_positions_avgp']), + end_positions=str(row['end_positions']), + end_positions_avgp=str(row['end_positions_avgp']), metrics=orjson.loads(row['metrics']), stratvars_toml=row['stratvars_toml'], transferables=orjson.loads(row['transferables']) if row['transferables'] else None diff --git a/v2realbot/config.py b/v2realbot/config.py index f1643d2..f21d41a 100644 --- a/v2realbot/config.py +++ b/v2realbot/config.py @@ -66,10 +66,10 @@ def get_key(mode: Mode, account: Account): return None dict = globals() try: - API_KEY = dict[str.upper(str(account.value)) + "_" + str.upper(str(mode.value)) + "_API_KEY" ] - SECRET_KEY = dict[str.upper(str(account.value)) + "_" + str.upper(str(mode.value)) + "_SECRET_KEY" ] - PAPER = dict[str.upper(str(account.value)) + "_" + str.upper(str(mode.value)) + "_PAPER" ] - FEED = dict[str.upper(str(account.value)) + "_" + str.upper(str(mode.value)) + "_FEED" ] + API_KEY = dict[str.upper(str(account.name)) + "_" + str.upper(str(mode.name)) + "_API_KEY" ] + SECRET_KEY = dict[str.upper(str(account.name)) + "_" + str.upper(str(mode.name)) + "_SECRET_KEY" ] + PAPER = dict[str.upper(str(account.name)) + "_" + str.upper(str(mode.name)) + "_PAPER" ] + FEED = dict[str.upper(str(account.name)) + "_" + str.upper(str(mode.name)) + "_FEED" ] return Keys(API_KEY, SECRET_KEY, PAPER, FEED) except KeyError: print("Not valid combination to get keys for", mode, account) @@ -93,7 +93,7 @@ data_feed_type_str = os.environ.get('ACCOUNT1_PAPER_FEED', 'iex') # Default to # Convert the string to DataFeed enum try: ACCOUNT1_PAPER_FEED = DataFeed(data_feed_type_str) -except ValueError: +except nameError: # Handle the case where the environment variable does not match any enum member print(f"Invalid data feed type: {data_feed_type_str} in ACCOUNT1_PAPER_FEED defaulting to 'iex'") ACCOUNT1_PAPER_FEED = DataFeed.SIP @@ -111,7 +111,7 @@ data_feed_type_str = os.environ.get('ACCOUNT1_LIVE_FEED', 'iex') # Default to ' # Convert the string to DataFeed enum try: ACCOUNT1_LIVE_FEED = DataFeed(data_feed_type_str) -except ValueError: +except nameError: # Handle the case where the environment variable does not match any enum member print(f"Invalid data feed type: {data_feed_type_str} in ACCOUNT1_LIVE_FEED defaulting to 'iex'") ACCOUNT1_LIVE_FEED = DataFeed.IEX @@ -129,7 +129,7 @@ data_feed_type_str = os.environ.get('ACCOUNT2_PAPER_FEED', 'iex') # Default to # Convert the string to DataFeed enum try: ACCOUNT2_PAPER_FEED = DataFeed(data_feed_type_str) -except ValueError: +except nameError: # Handle the case where the environment variable does not match any enum member print(f"Invalid data feed type: {data_feed_type_str} in ACCOUNT2_PAPER_FEED defaulting to 'iex'") ACCOUNT2_PAPER_FEED = DataFeed.IEX @@ -148,7 +148,7 @@ except ValueError: # # Convert the string to DataFeed enum # try: # ACCOUNT2_LIVE_FEED = DataFeed(data_feed_type_str) -# except ValueError: +# except nameError: # # Handle the case where the environment variable does not match any enum member # print(f"Invalid data feed type: {data_feed_type_str} in ACCOUNT2_LIVE_FEED defaulting to 'iex'") # ACCOUNT2_LIVE_FEED = DataFeed.IEX diff --git a/v2realbot/controller/run_manager.py b/v2realbot/controller/run_manager.py index db01ac5..acf040c 100644 --- a/v2realbot/controller/run_manager.py +++ b/v2realbot/controller/run_manager.py @@ -3,7 +3,7 @@ from uuid import UUID, uuid4 from v2realbot.common.model import RunManagerRecord, StrategyInstance, RunDay, StrategyInstance, Runner, RunRequest, RunArchive, RunArchiveView, RunArchiveViewPagination, RunArchiveDetail, RunArchiveChange, Bar, TradeEvent, TestList, Intervals, ConfigItem, InstantIndicator, DataTablesRequest from v2realbot.utils.utils import validate_and_format_time, AttributeDict, zoneNY, zonePRG, safe_get, dict_replace_value, Store, parse_toml_string, json_serial, is_open_hours, send_to_telegram, concatenate_weekdays, transform_data from v2realbot.utils.ilog import delete_logs -from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType +from v2realbot.common.model import Trade, TradeDirection, TradeStatus, TradeStoplossType from datetime import datetime from v2realbot.loader.trade_offline_streamer import Trade_Offline_Streamer from threading import Thread, current_thread, Event, enumerate diff --git a/v2realbot/controller/services.py b/v2realbot/controller/services.py index 528a97e..f5e17d5 100644 --- a/v2realbot/controller/services.py +++ b/v2realbot/controller/services.py @@ -8,9 +8,9 @@ from alpaca.data.timeframe import TimeFrame from v2realbot.strategy.base import StrategyState from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, OrderSide from v2realbot.common.model import RunDay, StrategyInstance, Runner, RunRequest, RunArchive, RunArchiveView, RunArchiveViewPagination, RunArchiveDetail, RunArchiveChange, Bar, TradeEvent, TestList, Intervals, ConfigItem, InstantIndicator, DataTablesRequest -from v2realbot.utils.utils import AttributeDict, zoneNY, zonePRG, safe_get, dict_replace_value, Store, parse_toml_string, json_serial, is_open_hours, send_to_telegram, concatenate_weekdays, transform_data +from v2realbot.utils.utils import AttributeDict, zoneNY, zonePRG, safe_get, dict_replace_value, Store, parse_toml_string, json_serial, is_open_hours, send_to_telegram, concatenate_weekdays, transform_data, gaka from v2realbot.utils.ilog import delete_logs -from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType +from v2realbot.common.model import Trade, TradeDirection, TradeStatus, TradeStoplossType from datetime import datetime from v2realbot.loader.trade_offline_streamer import Trade_Offline_Streamer from threading import Thread, current_thread, Event, enumerate @@ -71,8 +71,8 @@ def get_all_runners(): if i.run_instance: i.run_profit = round(float(i.run_instance.state.profit),2) i.run_trade_count = len(i.run_instance.state.tradeList) - i.run_positions = i.run_instance.state.positions - i.run_avgp = round(float(i.run_instance.state.avgp),3) + i.run_positions = gaka(i.run_instance.state.account_variables, "positions") + i.run_avgp = gaka(i.run_instance.state.account_variables, "avgp", lambda x: round(float(x),3)) return (0, db.runners) else: return (0, []) @@ -94,8 +94,8 @@ def get_runner(id: UUID): if str(i.id) == str(id): i.run_profit = round(float(i.run_instance.state.profit),2) i.run_trade_count = len(i.run_instance.state.tradeList) - i.run_positions = i.run_instance.state.positions - i.run_avgp = round(float(i.run_instance.state.avgp),3) + i.run_positions =gaka(i.run_instance.state.account_variables, "positions") + i.run_avgp = gaka(i.run_instance.state.account_variables, "avgp", lambda x: round(float(x),3)) return (0, i) return (-2, "not found") @@ -738,13 +738,14 @@ def populate_metrics_output_directory(strat: StrategyInstance, inter_batch_param tradeList = strat.state.tradeList - trade_dict = AttributeDict(orderid=[],timestamp=[],symbol=[],side=[],order_type=[],qty=[],price=[],position_qty=[]) + trade_dict = AttributeDict(account=[],orderid=[],timestamp=[],symbol=[],side=[],order_type=[],qty=[],price=[],position_qty=[]) if strat.mode == Mode.BT: trade_dict["value"] = [] trade_dict["cash"] = [] trade_dict["pos_avg_price"] = [] for t in tradeList: if t.event == TradeEvent.FILL: + trade_dict.account.append(t.account) trade_dict.orderid.append(str(t.order.id)) trade_dict.timestamp.append(t.timestamp) trade_dict.symbol.append(t.order.symbol) @@ -768,10 +769,12 @@ def populate_metrics_output_directory(strat: StrategyInstance, inter_batch_param max_positions = max_positions[max_positions['side'] == OrderSide.SELL] max_positions = max_positions.drop(columns=['side'], axis=1) - res = dict(profit={}) + res = dict(account_variables={}, profit={}) #filt = max_positions['side'] == 'OrderSide.BUY' - res["pos_cnt"] = dict(zip(str(max_positions['qty']), max_positions['count'])) + res["account_variables"] = transform_data(strat.state.account_variables, json_serial) + + res["pos_cnt"] = dict(zip(str(max_positions['qty']), max_positions['count'])) #naplneni batch sum profitu if inter_batch_params is not None: res["profit"]["batch_sum_profit"] = int(inter_batch_params["batch_profit"]) @@ -923,8 +926,8 @@ def archive_runner(runner: Runner, strat: StrategyInstance, inter_batch_params: settings = settings, profit=round(float(strat.state.profit),2), trade_count=len(strat.state.tradeList), - end_positions=strat.state.positions, - end_positions_avgp=round(float(strat.state.avgp),3), + end_positions=gaka(strat.state.account_variables, "positions"), + end_positions_avgp=gaka(strat.state.account_variables, "avgp", lambda x: round(float(x),3)), metrics=results_metrics, stratvars_toml=runner.run_stratvars_toml, transferables=strat.state.vars["transferables"] @@ -1264,6 +1267,7 @@ def insert_archive_header(archeader: RunArchive): try: c = conn.cursor() #json_string = orjson.dumps(archeader, default=json_serial, option=orjson.OPT_PASSTHROUGH_DATETIME) + print(archeader) res = c.execute(""" INSERT INTO runner_header @@ -1271,7 +1275,7 @@ def insert_archive_header(archeader: RunArchive): VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, - (str(archeader.id), str(archeader.strat_id), archeader.batch_id, archeader.symbol, archeader.name, archeader.note, archeader.started, archeader.stopped, archeader.mode, archeader.account, archeader.bt_from, archeader.bt_to, orjson.dumps(archeader.strat_json).decode('utf-8'), orjson.dumps(archeader.settings).decode('utf-8'), archeader.ilog_save, archeader.profit, archeader.trade_count, archeader.end_positions, archeader.end_positions_avgp, orjson.dumps(archeader.metrics, default=json_serial, option=orjson.OPT_PASSTHROUGH_DATETIME).decode('utf-8'), archeader.stratvars_toml, orjson.dumps(archeader.transferables).decode('utf-8'))) + (str(archeader.id), str(archeader.strat_id), archeader.batch_id, archeader.symbol, archeader.name, archeader.note, archeader.started, archeader.stopped, archeader.mode, archeader.account, archeader.bt_from, archeader.bt_to, orjson.dumps(archeader.strat_json).decode('utf-8'), orjson.dumps(archeader.settings).decode('utf-8'), archeader.ilog_save, archeader.profit, archeader.trade_count, orjson.dumps(archeader.end_positions).decode('utf-8'), orjson.dumps(archeader.end_positions_avgp).decode('utf-8'), orjson.dumps(archeader.metrics, default=json_serial, option=orjson.OPT_PASSTHROUGH_DATETIME).decode('utf-8'), archeader.stratvars_toml, orjson.dumps(archeader.transferables).decode('utf-8'))) #retry not yet supported for statement format above #res = execute_with_retry(c,statement) diff --git a/v2realbot/interfaces/backtest_interface.py b/v2realbot/interfaces/backtest_interface.py index 6fd5dfa..e5ba51e 100644 --- a/v2realbot/interfaces/backtest_interface.py +++ b/v2realbot/interfaces/backtest_interface.py @@ -5,6 +5,7 @@ from v2realbot.backtesting.backtester import Backtester from datetime import datetime from v2realbot.utils.utils import zoneNY import v2realbot.utils.config_handler as cfh +from v2realbot.common.model import Account """" backtester methods can be called @@ -16,8 +17,9 @@ both should be backtestable if method are called for the past self.time must be set accordingly """ class BacktestInterface(GeneralInterface): - def __init__(self, symbol, bt: Backtester) -> None: + def __init__(self, symbol, bt: Backtester, account: Account) -> None: self.symbol = symbol + self.account = account self.bt = bt self.count_api_requests = cfh.config_handler.get_val('COUNT_API_REQUESTS') self.mincnt = list([dict(minute=0,count=0)]) @@ -43,48 +45,48 @@ class BacktestInterface(GeneralInterface): def buy(self, size = 1, repeat: bool = False): self.count() #add REST API latency - return self.bt.submit_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'),symbol=self.symbol,side=OrderSide.BUY,size=size,order_type = OrderType.MARKET) + return self.bt.submit_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'),symbol=self.symbol,side=OrderSide.BUY,size=size,order_type = OrderType.MARKET, account=self.account) """buy limit""" def buy_l(self, price: float, size: int = 1, repeat: bool = False, force: int = 0): self.count() - return self.bt.submit_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'),symbol=self.symbol,side=OrderSide.BUY,size=size,price=price,order_type = OrderType.LIMIT) + return self.bt.submit_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'),symbol=self.symbol,side=OrderSide.BUY,size=size,price=price,order_type = OrderType.LIMIT, account=self.account) """sell market""" def sell(self, size = 1, repeat: bool = False): self.count() - return self.bt.submit_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'),symbol=self.symbol,side=OrderSide.SELL,size=size,order_type = OrderType.MARKET) + return self.bt.submit_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'),symbol=self.symbol,side=OrderSide.SELL,size=size,order_type = OrderType.MARKET, account=self.account) """sell limit""" async def sell_l(self, price: float, size = 1, repeat: bool = False): self.count() - return self.bt.submit_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'),symbol=self.symbol,side=OrderSide.SELL,size=size,price=price,order_type = OrderType.LIMIT) + return self.bt.submit_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'),symbol=self.symbol,side=OrderSide.SELL,size=size,price=price,order_type = OrderType.LIMIT, account=self.account) """replace order""" async def repl(self, orderid: str, price: float = None, size: int = None, repeat: bool = False): self.count() - return self.bt.replace_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'),id=orderid,size=size,price=price) + return self.bt.replace_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'),id=orderid,size=size,price=price, account=self.account) """cancel order""" #TBD exec predtim? def cancel(self, orderid: str): self.count() - return self.bt.cancel_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'), id=orderid) + return self.bt.cancel_order(time=self.bt.time + cfh.config_handler.get_val('BT_DELAYS','strat_to_sub'), id=orderid, account=self.account) """get positions ->(size,avgp)""" #TBD exec predtim? def pos(self): self.count() - return self.bt.get_open_position(symbol=self.symbol) + return self.bt.get_open_position(symbol=self.symbol, account=self.account) """get open orders ->list(Order)""" def get_open_orders(self, side: OrderSide, symbol: str): self.count() - return self.bt.get_open_orders(side=side, symbol=symbol) + return self.bt.get_open_orders(side=side, symbol=symbol, account=self.account) def get_last_price(self, symbol: str): self.count() - return self.bt.get_last_price(time=self.bt.time) + return self.bt.get_last_price(time=self.bt.time, account=self.account) diff --git a/v2realbot/interfaces/live_interface.py b/v2realbot/interfaces/live_interface.py index 8e23adc..d5304ec 100644 --- a/v2realbot/interfaces/live_interface.py +++ b/v2realbot/interfaces/live_interface.py @@ -97,7 +97,7 @@ class LiveInterface(GeneralInterface): return -1 """sell limit""" - async def sell_l(self, price: float, size = 1, repeat: bool = False): + def sell_l(self, price: float, size = 1, repeat: bool = False): self.size = size self.repeat = repeat @@ -124,7 +124,7 @@ class LiveInterface(GeneralInterface): return -1 """order replace""" - async def repl(self, orderid: str, price: float = None, size: int = None, repeatl: bool = False): + def repl(self, orderid: str, price: float = None, size: int = None, repeatl: bool = False): if not price and not size: print("price or size has to be filled") diff --git a/v2realbot/loader/order_updates_streamer.py b/v2realbot/loader/order_updates_streamer.py index 62046dc..26c1689 100644 --- a/v2realbot/loader/order_updates_streamer.py +++ b/v2realbot/loader/order_updates_streamer.py @@ -1,6 +1,7 @@ from threading import Thread from alpaca.trading.stream import TradingStream from v2realbot.config import Keys +from v2realbot.common.model import Account #jelikoz Alpaca podporuje pripojeni libovolneho poctu websocket instanci na order updates #vytvorime pro kazdou bezici instanci vlastni webservisu (jinak bychom museli delat instanci pro kombinaci ACCOUNT1 - LIVE, ACCOUNT1 - PAPER, ACCOUNT2 - PAPER ..) @@ -14,15 +15,16 @@ As Alpaca supports connecting of any number of trade updates clients new instance of this websocket thread is created for each strategy instance. """"" class LiveOrderUpdatesStreamer(Thread): - def __init__(self, key: Keys, name: str) -> None: + def __init__(self, key: Keys, name: str, account: Account) -> None: self.key = key + self.account = account self.strategy = None self.client = TradingStream(api_key=key.API_KEY, secret_key=key.SECRET_KEY, paper=key.PAPER) Thread.__init__(self, name=name) #notif dispatcher - pouze 1 strategie async def distributor(self,data): - if self.strategy.symbol == data.order.symbol: await self.strategy.order_updates(data) + if self.strategy.symbol == data.order.symbol: await self.strategy.order_updates(data, self.account) # connects callback to interface object - responses for given symbol are routed to interface callback def connect_callback(self, st): diff --git a/v2realbot/main.py b/v2realbot/main.py index 813cc79..c495a2c 100644 --- a/v2realbot/main.py +++ b/v2realbot/main.py @@ -10,7 +10,7 @@ from fastapi.security import APIKeyHeader import uvicorn from uuid import UUID from v2realbot.utils.ilog import get_log_window -from v2realbot.common.model import RunManagerRecord, StrategyInstance, RunnerView, RunRequest, Trade, RunArchive, RunArchiveView, RunArchiveViewPagination, RunArchiveDetail, Bar, RunArchiveChange, TestList, ConfigItem, InstantIndicator, DataTablesRequest, AnalyzerInputs +from v2realbot.common.model import RunManagerRecord, StrategyInstance, RunnerView, RunRequest, TradeView, RunArchive, RunArchiveView, RunArchiveViewPagination, RunArchiveDetail, Bar, RunArchiveChange, TestList, ConfigItem, InstantIndicator, DataTablesRequest, AnalyzerInputs from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends, HTTPException, status, WebSocketException, Cookie, Query, Request from fastapi.responses import FileResponse, StreamingResponse, JSONResponse from fastapi.staticfiles import StaticFiles @@ -334,7 +334,7 @@ def stop_all_runners(): raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Error: {res}:{id}") @app.get("/tradehistory/{symbol}", dependencies=[Depends(api_key_auth)]) -def get_trade_history(symbol: str, timestamp_from: float, timestamp_to:float) -> list[Trade]: +def get_trade_history(symbol: str, timestamp_from: float, timestamp_to:float) -> list[TradeView]: res, set = cs.get_trade_history(symbol, timestamp_from, timestamp_to) if res == 0: return set diff --git a/v2realbot/reporting/analyzer/WIP_daily_profit_distribution.py b/v2realbot/reporting/analyzer/WIP_daily_profit_distribution.py index 4794a93..0c1ed23 100644 --- a/v2realbot/reporting/analyzer/WIP_daily_profit_distribution.py +++ b/v2realbot/reporting/analyzer/WIP_daily_profit_distribution.py @@ -12,7 +12,7 @@ import numpy as np import v2realbot.controller.services as cs from rich import print from v2realbot.common.model import AnalyzerInputs -from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType +from v2realbot.common.model 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 diff --git a/v2realbot/reporting/analyzer/daily_profit_distribution.py b/v2realbot/reporting/analyzer/daily_profit_distribution.py index 4794a93..0c1ed23 100644 --- a/v2realbot/reporting/analyzer/daily_profit_distribution.py +++ b/v2realbot/reporting/analyzer/daily_profit_distribution.py @@ -12,7 +12,7 @@ import numpy as np import v2realbot.controller.services as cs from rich import print from v2realbot.common.model import AnalyzerInputs -from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType +from v2realbot.common.model 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 diff --git a/v2realbot/reporting/analyzer/example_plugin.py b/v2realbot/reporting/analyzer/example_plugin.py index c1d71c8..9a73520 100644 --- a/v2realbot/reporting/analyzer/example_plugin.py +++ b/v2realbot/reporting/analyzer/example_plugin.py @@ -11,7 +11,7 @@ import numpy as np import v2realbot.controller.services as cs from rich import print from v2realbot.common.model import AnalyzerInputs -from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType +from v2realbot.common.model 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 diff --git a/v2realbot/reporting/analyzer/find_optimal_cutoff.py b/v2realbot/reporting/analyzer/find_optimal_cutoff.py index c61cccf..4385516 100644 --- a/v2realbot/reporting/analyzer/find_optimal_cutoff.py +++ b/v2realbot/reporting/analyzer/find_optimal_cutoff.py @@ -11,7 +11,7 @@ import numpy as np import v2realbot.controller.services as cs from rich import print from v2realbot.common.model import AnalyzerInputs -from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType +from v2realbot.common.model 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 diff --git a/v2realbot/reporting/analyzer/find_optimal_cutoff_REL.py b/v2realbot/reporting/analyzer/find_optimal_cutoff_REL.py index 8eff788..e8358e1 100644 --- a/v2realbot/reporting/analyzer/find_optimal_cutoff_REL.py +++ b/v2realbot/reporting/analyzer/find_optimal_cutoff_REL.py @@ -11,7 +11,7 @@ import numpy as np import v2realbot.controller.services as cs from rich import print from v2realbot.common.model import AnalyzerInputs -from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType +from v2realbot.common.model 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 diff --git a/v2realbot/reporting/analyzer/ls_profit_distribution.py b/v2realbot/reporting/analyzer/ls_profit_distribution.py index b89402a..8cc3877 100644 --- a/v2realbot/reporting/analyzer/ls_profit_distribution.py +++ b/v2realbot/reporting/analyzer/ls_profit_distribution.py @@ -11,7 +11,7 @@ import numpy as np import v2realbot.controller.services as cs from rich import print from v2realbot.common.model import AnalyzerInputs -from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType +from v2realbot.common.model 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 diff --git a/v2realbot/reporting/analyzer/profit_distribution_by_month.py b/v2realbot/reporting/analyzer/profit_distribution_by_month.py index c0535f3..35c1671 100644 --- a/v2realbot/reporting/analyzer/profit_distribution_by_month.py +++ b/v2realbot/reporting/analyzer/profit_distribution_by_month.py @@ -11,7 +11,7 @@ import numpy as np import v2realbot.controller.services as cs from rich import print from v2realbot.common.model import AnalyzerInputs -from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType +from v2realbot.common.model 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 diff --git a/v2realbot/reporting/analyzer/profit_sum_by_hour.py b/v2realbot/reporting/analyzer/profit_sum_by_hour.py index 8665c75..c613020 100644 --- a/v2realbot/reporting/analyzer/profit_sum_by_hour.py +++ b/v2realbot/reporting/analyzer/profit_sum_by_hour.py @@ -11,7 +11,7 @@ import numpy as np import v2realbot.controller.services as cs from rich import print from v2realbot.common.model import AnalyzerInputs -from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType +from v2realbot.common.model 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 diff --git a/v2realbot/reporting/analyzer/summarize_trade_metrics.py b/v2realbot/reporting/analyzer/summarize_trade_metrics.py index 2bec57c..d58067d 100644 --- a/v2realbot/reporting/analyzer/summarize_trade_metrics.py +++ b/v2realbot/reporting/analyzer/summarize_trade_metrics.py @@ -12,7 +12,7 @@ import numpy as np import v2realbot.controller.services as cs from rich import print from v2realbot.common.model import AnalyzerInputs -from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType +from v2realbot.common.model 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 diff --git a/v2realbot/reporting/archive/optimizecutoff.py b/v2realbot/reporting/archive/optimizecutoff.py index 2b36e60..c2683de 100644 --- a/v2realbot/reporting/archive/optimizecutoff.py +++ b/v2realbot/reporting/archive/optimizecutoff.py @@ -10,7 +10,7 @@ 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.common.model 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 diff --git a/v2realbot/reporting/archive/optimizecutoffprofloss.py b/v2realbot/reporting/archive/optimizecutoffprofloss.py index f47d6b6..38b9df5 100644 --- a/v2realbot/reporting/archive/optimizecutoffprofloss.py +++ b/v2realbot/reporting/archive/optimizecutoffprofloss.py @@ -10,7 +10,7 @@ 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.common.model 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 diff --git a/v2realbot/reporting/archive/optimizecutoffv2.py b/v2realbot/reporting/archive/optimizecutoffv2.py index a92fe73..e18ea0f 100644 --- a/v2realbot/reporting/archive/optimizecutoffv2.py +++ b/v2realbot/reporting/archive/optimizecutoffv2.py @@ -10,7 +10,7 @@ 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.common.model 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 diff --git a/v2realbot/reporting/load_trades.py b/v2realbot/reporting/load_trades.py index 3563923..981fb03 100644 --- a/v2realbot/reporting/load_trades.py +++ b/v2realbot/reporting/load_trades.py @@ -11,7 +11,7 @@ import numpy as np import v2realbot.controller.services as cs from rich import print from v2realbot.common.model import AnalyzerInputs -from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType +from v2realbot.common.model 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 @@ -23,7 +23,7 @@ from collections import defaultdict from scipy.stats import zscore from io import BytesIO from typing import Tuple, Optional, List -from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType +from v2realbot.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType def load_trades(runner_ids: List = None, batch_id: str = None) -> Tuple[int, List[Trade], int]: if runner_ids is None and batch_id is None: diff --git a/v2realbot/reporting/metricstoolsimage.py b/v2realbot/reporting/metricstoolsimage.py index 516b769..b9ba757 100644 --- a/v2realbot/reporting/metricstoolsimage.py +++ b/v2realbot/reporting/metricstoolsimage.py @@ -10,7 +10,7 @@ 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.common.model 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 diff --git a/v2realbot/scheduler/ap_scheduler.py b/v2realbot/scheduler/ap_scheduler.py index 42627cc..73030d9 100644 --- a/v2realbot/scheduler/ap_scheduler.py +++ b/v2realbot/scheduler/ap_scheduler.py @@ -4,7 +4,7 @@ from uuid import UUID, uuid4 from v2realbot.enums.enums import Moddus, SchedulerStatus, RecordType, StartBarAlign, Mode, Account, OrderSide from v2realbot.common.model import RunManagerRecord, StrategyInstance, RunDay, StrategyInstance, Runner, RunRequest, RunArchive, RunArchiveView, RunArchiveViewPagination, RunArchiveDetail, RunArchiveChange, Bar, TradeEvent, TestList, Intervals, ConfigItem, InstantIndicator, DataTablesRequest, Market from v2realbot.utils.utils import validate_and_format_time, AttributeDict, zoneNY, zonePRG, safe_get, dict_replace_value, Store, parse_toml_string, json_serial, is_open_hours, send_to_telegram, concatenate_weekdays, transform_data -from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType +from v2realbot.common.model import Trade, TradeDirection, TradeStatus, TradeStoplossType from datetime import datetime from v2realbot.config import JOB_LOG_FILE, STRATVARS_UNCHANGEABLES, ACCOUNT1_PAPER_API_KEY, ACCOUNT1_PAPER_SECRET_KEY, ACCOUNT1_LIVE_API_KEY, ACCOUNT1_LIVE_SECRET_KEY, DATA_DIR, MEDIA_DIRECTORY, RUNNER_DETAIL_DIRECTORY import numpy as np diff --git a/v2realbot/static/js/tables/archivetable/init.js b/v2realbot/static/js/tables/archivetable/init.js index 0bb2ef0..d85b592 100644 --- a/v2realbot/static/js/tables/archivetable/init.js +++ b/v2realbot/static/js/tables/archivetable/init.js @@ -172,7 +172,7 @@ function initialize_archiveRecords() { { targets: [13,14,15], render: function ( data, type, row ) { - return '
'+data+'
' + return '
'+JSON.stringify(data, null, 2)+'
' }, }, { diff --git a/v2realbot/strategy/StrategyClassicSL.py b/v2realbot/strategy/StrategyClassicSL.py index 70b584f..433271f 100644 --- a/v2realbot/strategy/StrategyClassicSL.py +++ b/v2realbot/strategy/StrategyClassicSL.py @@ -4,7 +4,7 @@ from v2realbot.utils.tlog import tlog, tlog_exception from v2realbot.enums.enums import Mode, Order, Account, RecordType, Followup #from alpaca.trading.models import TradeUpdate from v2realbot.common.model import TradeUpdate -from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus +from v2realbot.common.model import Trade, TradeDirection, TradeStatus from alpaca.trading.enums import TradeEvent, OrderStatus from v2realbot.indicators.indicators import ema import orjson @@ -44,7 +44,8 @@ class StrategyClassicSL(Strategy): msg = f"QUITTING {hard_cutoff=} MAX SUM REL PROFIT REACHED {max_sum_profit_to_quit_rel=} {self.state.profit=} {rel_profit=} relprofits:{str(self.state.rel_profit_cum)}" printanyway(msg) self.state.ilog(e=msg) - self.state.vars.pending = "max_sum_profit_to_quit_rel" + for account in self.accounts: + self.state.account_variables[account.name].pending = "max_sum_profit_to_quit_rel" if self.mode not in [Mode.BT, Mode.PREP]: send_to_telegram(msg) if hard_cutoff: @@ -57,7 +58,8 @@ class StrategyClassicSL(Strategy): msg=f"QUITTING {hard_cutoff=} MAX SUM REL LOSS REACHED {max_sum_loss_to_quit_rel=} {self.state.profit=} {rel_profit=} relprofits:{str(self.state.rel_profit_cum)}" printanyway(msg) self.state.ilog(e=msg) - self.state.vars.pending = "max_sum_loss_to_quit_rel" + for account in self.accounts: + self.state.account_variables[account.name].pending = "max_sum_loss_to_quit_rel" if self.mode not in [Mode.BT, Mode.PREP]: send_to_telegram(msg) if hard_cutoff: @@ -71,7 +73,8 @@ class StrategyClassicSL(Strategy): msg = f"QUITTING {hard_cutoff=} MAX SUM ABS PROFIT REACHED {max_sum_profit_to_quit=} {self.state.profit=} {rel_profit=} relprofits:{str(self.state.rel_profit_cum)}" printanyway(msg) self.state.ilog(e=msg) - self.state.vars.pending = "max_sum_profit_to_quit" + for account in self.accounts: + self.state.account_variables[account.name].pending = "max_sum_profit_to_quit" if self.mode not in [Mode.BT, Mode.PREP]: send_to_telegram(msg) if hard_cutoff: @@ -84,7 +87,8 @@ class StrategyClassicSL(Strategy): msg = f"QUITTING {hard_cutoff=} MAX SUM ABS LOSS REACHED {max_sum_loss_to_quit=} {self.state.profit=} {rel_profit=} relprofits:{str(self.state.rel_profit_cum)}" printanyway(msg) self.state.ilog(e=msg) - self.state.vars.pending = "max_sum_loss_to_quit" + for account in self.accounts: + self.state.account_variables[account.name].pending = "max_sum_loss_to_quit" if self.mode not in [Mode.BT, Mode.PREP]: send_to_telegram(msg) if hard_cutoff: @@ -95,9 +99,10 @@ class StrategyClassicSL(Strategy): return False - async def add_followup(self, direction: TradeDirection, size: int, signal_name: str): + async def add_followup(self, direction: TradeDirection, size: int, signal_name: str, account: Account): trade_to_add = Trade( id=uuid4(), + account=account, last_update=datetime.fromtimestamp(self.state.time).astimezone(zoneNY), status=TradeStatus.READY, size=size, @@ -108,45 +113,48 @@ class StrategyClassicSL(Strategy): self.state.vars.prescribedTrades.append(trade_to_add) - self.state.vars.requested_followup = None + self.state.account_variables[account.name].requested_followup = None - self.state.ilog(e=f"FOLLOWUP {direction} added to prescr.trades {signal_name=} {size=}", trade=trade_to_add) + self.state.ilog(e=f"FOLLOWUP {direction} - {account} added to prescr.trades {signal_name=} {size=}", trade=trade_to_add) async def orderUpdateBuy(self, data: TradeUpdate): o: Order = data.order signal_name = None + account = data.account ##nejak to vymyslet, aby se dal poslat cely Trade a serializoval se - self.state.ilog(e="Příchozí BUY notif", msg=o.status, trade=transform_data(data, json_serial)) + self.state.ilog(e="Příchozí BUY notif"+account, msg=o.status, trade=transform_data(data, json_serial)) if data.event == TradeEvent.FILL or data.event == TradeEvent.PARTIAL_FILL: #pokud jde o fill pred kterym je partail, muze se stat, ze uz budou vynulovany pozice, toto je pojistka #jde o uzavření short pozice - počítáme PROFIT - if int(self.state.positions) < 0 or (int(self.state.positions) == 0 and self.state.wait_for_fill is not None): + if int(self.state.account_variables[account.name].positions) < 0 or (int(self.state.account_variables[account.name].positions) == 0 and self.state.account_variables[account.name].wait_for_fill is not None): - if data.event == TradeEvent.PARTIAL_FILL and self.state.wait_for_fill is None: + if data.event == TradeEvent.PARTIAL_FILL and self.state.account_variables[account.name].wait_for_fill is None: #timto si oznacime, ze po partialu s vlivem na PROFIT musime cekat na FILL a zaroven ukladame prum cenu, kterou potrebujeme na vypocet profitu u fillu - self.state.wait_for_fill = float(self.state.avgp) + self.state.account_variables[account.name].wait_for_fill = float(self.state.account_variables[account.name].avgp) #PROFIT pocitame z TradeUpdate.price a TradeUpdate.qty - aktualne provedene mnozstvi a cena #naklady vypocteme z prumerne ceny, kterou mame v pozicich bought_amount = data.qty * data.price #podle prumerne vstupni ceny, kolik stalo toto mnozstvi - if float(self.state.avgp) > 0: - vstup_cena = float(self.state.avgp) - elif float(self.state.avgp) == 0 and self.state.wait_for_fill is not None: - vstup_cena = float(self.state.wait_for_fill) + if float(self.state.account_variables[account.name].avgp) > 0: + vstup_cena = float(self.state.account_variables[account.name].avgp) + elif float(self.state.account_variables[account.name].avgp) == 0 and self.state.account_variables[account.name].wait_for_fill is not None: + vstup_cena = float(self.state.account_variables[account.name].wait_for_fill) else: vstup_cena = 0 avg_costs = vstup_cena * float(data.qty) if avg_costs == 0: - self.state.ilog(e="ERR: Nemame naklady na PROFIT, AVGP je nula. Zaznamenano jako 0", msg="naklady=utrzena cena. TBD opravit.") + self.state.ilog(e="ERR: Nemame naklady na PROFIT, AVGP je nula. Zaznamenano jako 0"+account, msg="naklady=utrzena cena. TBD opravit.") avg_costs = bought_amount trade_profit = round((avg_costs-bought_amount),2) - self.state.profit += trade_profit + #celkovy profit + self.state.profit += trade_profit #overall abs profit + self.state.account_variables[account.name].profit += trade_profit #account profit rel_profit = 0 #spoctene celkovy relativni profit za trade v procentech ((trade_profit/vstup_naklady)*100) @@ -160,30 +168,32 @@ class StrategyClassicSL(Strategy): if data.event == TradeEvent.FILL: #jde o partial EXIT dvááme si rel.profit do docasne promenne, po poslednim exitu z nich vypocteme skutecny rel.profit if data.position_qty != 0: - self.state.docasny_rel_profit.append(rel_profit) + self.state.account_variables[account.name].docasny_rel_profit.append(rel_profit) partial_exit = True else: #jde o posledni z PARTIAL EXITU tzn.data.position_qty == 0 - if len(self.state.docasny_rel_profit) > 0: + if len(self.state.account_variables[account.name].docasny_rel_profit) > 0: #pricteme aktualni rel profit - self.state.docasny_rel_profit.append(rel_profit) + self.state.account_variables[account.name].docasny_rel_profit.append(rel_profit) #a z rel profitu tohoto tradu vypocteme prumer, ktery teprve ulozime - rel_profit = round(np.mean(self.state.docasny_rel_profit),5) - self.state.docasny_rel_profit = [] + rel_profit = round(np.mean(self.state.account_variables[account.name].docasny_rel_profit),5) + self.state.account_variables[account.name].docasny_rel_profit = [] partial_last = True - self.state.rel_profit_cum.append(rel_profit) + self.state.rel_profit_cum.append(rel_profit) #overall cum rel profit + self.state.account_variables[account.name].rel_profit_cum.append(rel_profit) #account cum rel profit + rel_profit_cum_calculated = round(np.sum(self.state.rel_profit_cum),5) - #pro martingale updatujeme loss_series_cnt + #pro martingale updatujeme loss_series_cnt - self.state.vars["transferables"]["martingale"]["cont_loss_series_cnt"] = 0 if rel_profit > 0 else self.state.vars["transferables"]["martingale"]["cont_loss_series_cnt"]+1 self.state.ilog(lvl=1, e=f"update cont_loss_series_cnt na {self.state.vars['transferables']['martingale']['cont_loss_series_cnt']}") - self.state.ilog(e=f"BUY notif - SHORT PROFIT: {partial_exit=} {partial_last=} {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)) + self.state.ilog(e=f"BUY notif {account} - SHORT PROFIT: {partial_exit=} {partial_last=} {round(float(trade_profit),3)} celkem abs:{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: - if trade.id == self.state.vars.pending: + if trade.id == self.state.account_variables[account.name].pending: trade.last_update = datetime.fromtimestamp(self.state.time).astimezone(zoneNY) trade.profit += trade_profit #pro ulozeni do tradeData scitame vsechen zisk z tohoto tradu (kvuli partialum) @@ -201,7 +211,7 @@ class StrategyClassicSL(Strategy): if data.event == TradeEvent.FILL: #mazeme self.state. - self.state.wait_for_fill = None + self.state.account_variables[account.name].wait_for_fill = None #zapsat update profitu do tradeList for tradeData in self.state.tradeList: if tradeData.execution_id == data.execution_id: @@ -209,7 +219,7 @@ class StrategyClassicSL(Strategy): setattr(tradeData, "profit", trade_profit) setattr(tradeData, "profit_sum", self.state.profit) setattr(tradeData, "signal_name", signal_name) - setattr(tradeData, "prescribed_trade_id", self.state.vars.pending) + setattr(tradeData, "prescribed_trade_id", self.state.account_variables[account.name].pending) #self.state.ilog(f"updatnut tradeList o profit", tradeData=orjson.loads(orjson.dumps(tradeData, default=json_serial, option=orjson.OPT_PASSTHROUGH_DATETIME))) setattr(tradeData, "rel_profit", rel_profit) setattr(tradeData, "rel_profit_cum", rel_profit_cum_calculated) @@ -220,16 +230,16 @@ class StrategyClassicSL(Strategy): #pIF REVERSAL REQUIRED - reverse position is added to prescr.Trades with same signal name #jen při celém FILLU - if data.event == TradeEvent.FILL and self.state.vars.requested_followup is not None: - if self.state.vars.requested_followup == Followup.REVERSE: - await self.add_followup(direction=TradeDirection.LONG, size=o.qty, signal_name=signal_name) - elif self.state.vars.requested_followup == Followup.ADD: + if data.event == TradeEvent.FILL and self.state.account_variables[account.name].requested_followup is not None: + if self.state.account_variables[account.name].requested_followup == Followup.REVERSE: + await self.add_followup(direction=TradeDirection.LONG, size=o.qty, signal_name=signal_name, account=account) + elif self.state.account_variables[account.name].requested_followup == Followup.ADD: #zatim stejna SIZE - await self.add_followup(direction=TradeDirection.SHORT, size=o.qty, signal_name=signal_name) + await self.add_followup(direction=TradeDirection.SHORT, size=o.qty, signal_name=signal_name, account=account) else: #zjistime nazev signalu a updatneme do tradeListu - abychom meli svazano for trade in self.state.vars.prescribedTrades: - if trade.id == self.state.vars.pending: + if trade.id == self.state.account_variables[account.name].pending: signal_name = trade.generated_by #zapiseme entry_time (jen pokud to neni partial add) - tzn. jen poprvé if data.event == TradeEvent.FILL and trade.entry_time is None: @@ -239,7 +249,7 @@ class StrategyClassicSL(Strategy): for tradeData in self.state.tradeList: if tradeData.execution_id == data.execution_id: setattr(tradeData, "signal_name", signal_name) - setattr(tradeData, "prescribed_trade_id", self.state.vars.pending) + setattr(tradeData, "prescribed_trade_id", self.state.account_variables[account.name].pending) self.state.ilog(e="BUY: Jde o LONG nakuú nepocitame profit zatim") @@ -248,43 +258,47 @@ class StrategyClassicSL(Strategy): self.state.last_entry_price["long"] = data.price #pokud neni nastaveno goal_price tak vyplnujeme defaultem - if self.state.vars.activeTrade.goal_price is None: + if self.state.account_variables[account.name].activeTrade.goal_price is None: dat = dict(close=data.price) - self.state.vars.activeTrade.goal_price = get_profit_target_price(self.state, dat, TradeDirection.LONG) + self.state.account_variables[account.name].activeTrade.goal_price = get_profit_target_price(self.state, dat, self.state.account_variables[account.name].activeTrade, TradeDirection.LONG) #ic("vstupujeme do orderupdatebuy") print(data) #dostavame zde i celkové akutální množství - ukládáme - self.state.positions = data.position_qty - self.state.avgp, self.state.positions = self.state.interface.pos() + self.state.account_variables[account.name].positions = data.position_qty + self.state.account_variables[account.name].avgp, self.state.account_variables[account.name].positions = self.state.interface.pos() if o.status == OrderStatus.FILLED or o.status == OrderStatus.CANCELED: #davame pryc pending - self.state.vars.pending = None + self.state.account_variables[account.name].pending = None - async def orderUpdateSell(self, data: TradeUpdate): - - self.state.ilog(e="Příchozí SELL notif", msg=data.order.status, trade=transform_data(data, json_serial)) + async def orderUpdateSell(self, data: TradeUpdate): + + account = data.account + #TODO tady jsem skoncil, pak projit vsechen kod na state.avgp a prehodit + + + self.state.ilog(e=f"Příchozí SELL notif - {account}", msg=data.order.status, trade=transform_data(data, json_serial)) #naklady vypocteme z prumerne ceny, kterou mame v pozicich if data.event == TradeEvent.FILL or data.event == TradeEvent.PARTIAL_FILL: #pokud jde o fill pred kterym je partail, muze se stat, ze uz budou vynulovany pozice, toto je pojistka #jde o uzavření long pozice - počítáme PROFIT - if int(self.state.positions) > 0 or (int(self.state.positions) == 0 and self.state.wait_for_fill is not None): + if int(self.state.account_variables[account.name].positions) > 0 or (int(self.state.account_variables[account.name].positions) == 0 and self.state.account_variables[account.name].wait_for_fill is not None): - if data.event == TradeEvent.PARTIAL_FILL and self.state.wait_for_fill is None: + if data.event == TradeEvent.PARTIAL_FILL and self.state.account_variables[account.name].wait_for_fill is None: #timto si oznacime, ze po partialu s vlivem na PROFIT musime cekat na FILL a zaroven ukladame prum cenu, kterou potrebujeme na vypocet profitu u fillu - self.state.wait_for_fill = float(self.state.avgp) + self.state.account_variables[account.name].wait_for_fill = float(self.state.account_variables[account.name].avgp) #PROFIT pocitame z TradeUpdate.price a TradeUpdate.qty - aktualne provedene mnozstvi a cena #naklady vypocteme z prumerne ceny, kterou mame v pozicich sold_amount = data.qty * data.price - if float(self.state.avgp) > 0: - vstup_cena = float(self.state.avgp) - elif float(self.state.avgp) == 0 and self.state.wait_for_fill is not None: - vstup_cena = float(self.state.wait_for_fill) + if float(self.state.account_variables[account.name].avgp) > 0: + vstup_cena = float(self.state.account_variables[account.name].avgp) + elif float(self.state.account_variables[account.name].avgp) == 0 and self.state.account_variables[account.name].wait_for_fill is not None: + vstup_cena = float(self.state.account_variables[account.name].wait_for_fill) else: vstup_cena = 0 @@ -292,12 +306,13 @@ class StrategyClassicSL(Strategy): avg_costs = vstup_cena * float(data.qty) if avg_costs == 0: - self.state.ilog(e="ERR: Nemame naklady na PROFIT, AVGP je nula. Zaznamenano jako 0", msg="naklady=utrzena cena. TBD opravit.") + self.state.ilog(e=f"ERR: {account} Nemame naklady na PROFIT, AVGP je nula. Zaznamenano jako 0", msg="naklady=utrzena cena. TBD opravit.") avg_costs = sold_amount trade_profit = round((sold_amount - avg_costs),2) - self.state.profit += trade_profit - + self.state.profit += trade_profit #celkový profit + self.state.account_variables[account.name].profit += trade_profit #account specific profit + 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: @@ -309,30 +324,31 @@ class StrategyClassicSL(Strategy): if data.event == TradeEvent.FILL: #jde o partial EXIT dvááme si rel.profit do docasne promenne, po poslednim exitu z nich vypocteme skutecny rel.profit if data.position_qty != 0: - self.state.docasny_rel_profit.append(rel_profit) + self.state.account_variables[account.name].docasny_rel_profit.append(rel_profit) partial_exit = True else: #jde o posledni z PARTIAL EXITU tzn.data.position_qty == 0 - if len(self.state.docasny_rel_profit) > 0: + if len(self.state.account_variables[account.name].docasny_rel_profit) > 0: #pricteme aktualni rel profit - self.state.docasny_rel_profit.append(rel_profit) + self.state.account_variables[account.name].docasny_rel_profit.append(rel_profit) #a z rel profitu tohoto tradu vypocteme prumer, ktery teprve ulozime - rel_profit = round(np.mean(self.state.docasny_rel_profit),5) - self.state.docasny_rel_profit = [] + rel_profit = round(np.mean(self.state.account_variables[account.name].docasny_rel_profit),5) + self.state.account_variables[account.name].docasny_rel_profit = [] partial_last = True - self.state.rel_profit_cum.append(rel_profit) + self.state.rel_profit_cum.append(rel_profit) #overall rel profit + self.state.account_variables[account.name].rel_profit_cum.append(rel_profit) #account cum rel profit rel_profit_cum_calculated = round(np.sum(self.state.rel_profit_cum),5) #pro martingale updatujeme loss_series_cnt self.state.vars["transferables"]["martingale"]["cont_loss_series_cnt"] = 0 if rel_profit > 0 else self.state.vars["transferables"]["martingale"]["cont_loss_series_cnt"]+1 self.state.ilog(lvl=1, e=f"update cont_loss_series_cnt na {self.state.vars['transferables']['martingale']['cont_loss_series_cnt']}") - self.state.ilog(e=f"SELL notif - LONG PROFIT {partial_exit=} {partial_last=}:{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)) + self.state.ilog(e=f"SELL notif {account}- LONG PROFIT {partial_exit=} {partial_last=}:{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: - if trade.id == self.state.vars.pending: + if trade.id == self.state.account_variables[account.name].pending: trade.last_update = datetime.fromtimestamp(self.state.time).astimezone(zoneNY) trade.profit += trade_profit #pro ulozeni do tradeData scitame vsechen zisk z tohoto tradu (kvuli partialum) @@ -349,7 +365,7 @@ class StrategyClassicSL(Strategy): if data.event == TradeEvent.FILL: #mazeme self.state. - self.state.wait_for_fill = None + self.state.account_variables[account.name].wait_for_fill = None #zapsat update profitu do tradeList for tradeData in self.state.tradeList: if tradeData.execution_id == data.execution_id: @@ -357,7 +373,7 @@ class StrategyClassicSL(Strategy): setattr(tradeData, "profit", trade_profit) setattr(tradeData, "profit_sum", self.state.profit) setattr(tradeData, "signal_name", signal_name) - setattr(tradeData, "prescribed_trade_id", self.state.vars.pending) + setattr(tradeData, "prescribed_trade_id", self.state.account_variables[account.name].pending) #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) @@ -367,17 +383,17 @@ class StrategyClassicSL(Strategy): if data.event == TradeEvent.FILL and await self.stop_when_max_profit_loss() is False: #IF REVERSAL REQUIRED - reverse position is added to prescr.Trades with same signal name - if data.event == TradeEvent.FILL and self.state.vars.requested_followup is not None: - if self.state.vars.requested_followup == Followup.REVERSE: + if data.event == TradeEvent.FILL and self.state.account_variables[account.name].requested_followup is not None: + if self.state.account_variables[account.name].requested_followup == Followup.REVERSE: await self.add_followup(direction=TradeDirection.SHORT, size=data.order.qty, signal_name=signal_name) - elif self.state.vars.requested_followup == Followup.ADD: + elif self.state.account_variables[account.name].requested_followup == Followup.ADD: #zatim stejna SIZE await self.add_followup(direction=TradeDirection.LONG, size=data.order.qty, signal_name=signal_name) else: #zjistime nazev signalu a updatneme do tradeListu - abychom meli svazano for trade in self.state.vars.prescribedTrades: - if trade.id == self.state.vars.pending: + if trade.id == self.state.account_variables[account.name].pending: signal_name = trade.generated_by #zapiseme entry_time (jen pokud to neni partial add) - tzn. jen poprvé if data.event == TradeEvent.FILL and trade.entry_time is None: @@ -387,7 +403,7 @@ class StrategyClassicSL(Strategy): for tradeData in self.state.tradeList: if tradeData.execution_id == data.execution_id: setattr(tradeData, "signal_name", signal_name) - setattr(tradeData, "prescribed_trade_id", self.state.vars.pending) + setattr(tradeData, "prescribed_trade_id", self.state.account_variables[account.name].pending) self.state.ilog(e="SELL: Jde o SHORT nepocitame profit zatim") @@ -395,32 +411,32 @@ class StrategyClassicSL(Strategy): #zapisujeme last entry price self.state.last_entry_price["short"] = data.price #pokud neni nastaveno goal_price tak vyplnujeme defaultem - if self.state.vars.activeTrade.goal_price is None: + if self.state.account_variables[account.name].activeTrade.goal_price is None: dat = dict(close=data.price) - self.state.vars.activeTrade.goal_price = get_profit_target_price(self.state, dat, TradeDirection.SHORT) + self.state.account_variables[account.name].activeTrade.goal_price = get_profit_target_price(self.state, dat, self.state.account_variables[account.name].activeTrade, TradeDirection.SHORT) #sem v budoucnu dat i update SL #if self.state.vars.activeTrade.stoploss_value is None: #update pozic, v trade update je i pocet zbylych pozic - old_avgp = self.state.avgp - old_pos = self.state.positions - self.state.positions = int(data.position_qty) + old_avgp = self.state.account_variables[account.name].avgp + old_pos = self.state.account_variables[account.name].positions + self.state.account_variables[account.name].positions = int(data.position_qty) if int(data.position_qty) == 0: - self.state.avgp = 0 + self.state.account_variables[account.name].avgp = 0 - self.state.ilog(e="SELL notifikace "+str(data.order.status), msg="update pozic", old_avgp=old_avgp, old_pos=old_pos, avgp=self.state.avgp, pos=self.state.positions, orderid=str(data.order.id)) - #self.state.avgp, self.state.positions = self.interface.pos() + self.state.ilog(e="SELL notifikace "+str(data.order.status), msg="update pozic", old_avgp=old_avgp, old_pos=old_pos, avgp=self.state.account_variables[account.name].avgp, pos=self.state.account_variables[account.name].positions, orderid=str(data.order.id)) + #self.state.account_variables[account.name].avgp, self.state.account_variables[account.name].positions = self.interface.pos() if data.event == TradeEvent.FILL or data.event == TradeEvent.CANCELED: print("Příchozí SELL notifikace - complete FILL nebo CANCEL", data.event) - self.state.vars.pending = None + self.state.account_variables[account.name].pending = None a,p = self.interface.pos() #pri chybe api nechavame puvodni hodnoty if a != -1: - self.state.avgp, self.state.positions = a,p + self.state.account_variables[account.name].avgp, self.state.account_variables[account.name].positions = a,p else: self.state.ilog(e=f"Chyba pri dotažení self.interface.pos() {a}") - #ic(self.state.avgp, self.state.positions) + #ic(self.state.account_variables[account.name].avgp, self.state.account_variables[account.name].positions) #this parent method is called by strategy just once before waiting for first data def strat_init(self): @@ -441,48 +457,47 @@ class StrategyClassicSL(Strategy): else: self.next(item, self.state) + #overidden methods # pouziva se pri vstupu long nebo exitu short # osetrit uzavreni s vice nez mam - def buy(self, size = None, repeat: bool = False): + def buy(self, account: Account, size = None, repeat: bool = False): print("overriden buy method") if size is None: sizer = self.state.vars.chunk else: sizer = size #jde o uzavreni short pozice - if int(self.state.positions) < 0 and (int(self.state.positions) + int(sizer)) > 0: - self.state.ilog(e="buy nelze nakoupit vic nez shortuji", positions=self.state.positions, size=size) + if int(self.state.account_variables[account.name].positions) < 0 and (int(self.state.account_variables[account.name].positions) + int(sizer)) > 0: + self.state.ilog(e="buy nelze nakoupit vic nez shortuji", positions=self.state.account_variables[account.name].positions, size=size) printanyway("buy nelze nakoupit vic nez shortuji") return -2 - if int(self.state.positions) >= self.state.vars.maxpozic: - self.state.ilog(e="buy Maxim mnozstvi naplneno", positions=self.state.positions) + if int(self.state.account_variables[account.name].positions) >= self.state.vars.maxpozic: + self.state.ilog(e="buy Maxim mnozstvi naplneno", positions=self.state.account_variables[account.name].positions) printanyway("max mnostvi naplneno") return 0 - self.state.blockbuy = 1 - self.state.vars.lastbuyindex = self.state.bars['index'][-1] #self.state.ilog(e="send MARKET buy to if", msg="S:"+str(size), ltp=self.state.interface.get_last_price(self.state.symbol)) self.state.ilog(e="send MARKET buy to if", msg="S:"+str(size), ltp=self.state.bars['close'][-1]) - return self.state.interface.buy(size=sizer) + return self.state.interface[account.name].buy(size=sizer) #overidden methods # pouziva se pri vstupu short nebo exitu long - def sell(self, size = None, repeat: bool = False): + def sell(self, account: Account, size = None, repeat: bool = False): print("overriden sell method") if size is None: - size = abs(int(self.state.positions)) + size = abs(int(self.state.account_variables[account.name].positions)) #jde o uzavreni long pozice - if int(self.state.positions) > 0 and (int(self.state.positions) - int(size)) < 0: - self.state.ilog(e="nelze prodat vic nez longuji", positions=self.state.positions, size=size) + if int(self.state.account_variables[account.name].positions) > 0 and (int(self.state.account_variables[account.name].positions) - int(size)) < 0: + self.state.ilog(e="nelze prodat vic nez longuji", positions=self.state.account_variables[account.name].positions, size=size) printanyway("nelze prodat vic nez longuji") return -2 #pokud shortuji a mam max pozic - if int(self.state.positions) < 0 and abs(int(self.state.positions)) >= self.state.vars.maxpozic: - self.state.ilog(e="short - Maxim mnozstvi naplneno", positions=self.state.positions, size=size) + if int(self.state.account_variables[account.name].positions) < 0 and abs(int(self.state.account_variables[account.name].positions)) >= self.state.vars.maxpozic: + self.state.ilog(e="short - Maxim mnozstvi naplneno", positions=self.state.account_variables[account.name].positions, size=size) printanyway("short - Maxim mnozstvi naplneno") return 0 @@ -490,4 +505,4 @@ class StrategyClassicSL(Strategy): #self.state.vars.lastbuyindex = self.state.bars['index'][-1] #self.state.ilog(e="send MARKET SELL to if", msg="S:"+str(size), ltp=self.state.interface.get_last_price(self.state.symbol)) self.state.ilog(e="send MARKET SELL to if", msg="S:"+str(size), ltp=self.state.bars['close'][-1]) - return self.state.interface.sell(size=size) \ No newline at end of file + return self.state.interface[account.name].sell(size=size) \ No newline at end of file diff --git a/v2realbot/strategy/base.py b/v2realbot/strategy/base.py index 6461c78..9728dc0 100644 --- a/v2realbot/strategy/base.py +++ b/v2realbot/strategy/base.py @@ -2,7 +2,7 @@ Strategy base class """ from datetime import datetime -from v2realbot.utils.utils import AttributeDict, zoneNY, is_open_rush, is_close_rush, json_serial, print +from v2realbot.utils.utils import AttributeDict, zoneNY, is_open_rush, is_close_rush, json_serial, print, gaka from v2realbot.utils.tlog import tlog from v2realbot.utils.ilog import insert_log, insert_log_multiple_queue from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Order, Account @@ -16,11 +16,11 @@ from v2realbot.loader.trade_ws_streamer import Trade_WS_Streamer from v2realbot.interfaces.general_interface import GeneralInterface from v2realbot.interfaces.backtest_interface import BacktestInterface from v2realbot.interfaces.live_interface import LiveInterface -import v2realbot.common.PrescribedTradeModel as ptm +import v2realbot.common.model as ptm from alpaca.trading.enums import OrderSide from v2realbot.backtesting.backtester import Backtester #from alpaca.trading.models import TradeUpdate -from v2realbot.common.model import TradeUpdate +from v2realbot.common.model import TradeUpdate, AccountVariables from alpaca.trading.enums import TradeEvent, OrderStatus from threading import Event, current_thread import orjson @@ -30,6 +30,7 @@ from collections import defaultdict import v2realbot.strategyblocks.activetrade.sl.optimsl as optimsl from tqdm import tqdm import v2realbot.utils.config_handler as cfh +from typing import Dict, Set if PROFILING_NEXT_ENABLED: from pyinstrument import Profiler @@ -50,7 +51,8 @@ class Strategy: self.rectype: RecordType = None self.nextnew = 1 self.btdata: list = [] - self.interface: GeneralInterface = None + self.interface: Dict[str, GeneralInterface] = {} + self.order_notifs: Dict[str, LiveOrderUpdatesStreamer] = {} self.state: StrategyState = None self.bt: Backtester = None self.debug = False @@ -60,8 +62,8 @@ class Strategy: self.open_rush = open_rush self.close_rush = close_rush self._streams = [] + #primary account from runReqs self.account = account - self.key = get_key(mode=self.mode, account=self.account) self.rtqueue = None self.runner_id = runner_id self.ilog_save = ilog_save @@ -69,6 +71,9 @@ class Strategy: self.secondary_res_start_index = dict() self.last_index = -1 + #set of all accounts (Account) including those from stratvars + self.accounts = self.get_accounts_in_stratvars_and_reqs() + #TODO predelat na dynamické queues self.q1 = queue.Queue() self.q2 = queue.Queue() @@ -83,6 +88,25 @@ class Strategy: self.hard_stop = False #indikuje hard stop, tedy vypnuti strategie self.soft_stop = False #indikuje soft stop (napr. při dosažení max zisku/ztráty), tedy pokracovani strategie, vytvareni dat, jen bez obchodu + def get_accounts_in_stratvars_and_reqs(self) -> Set: + """ + Helper that retrieves distinct account values used in stratvars and in runRequest. + + Returns: + set: A set of unique account values. + """ + account_keywords = ['account', 'account_long', 'account_short'] + account_values = set() + + for signal_value in self.stratvars.get('signals', {}).values(): + for key in account_keywords: + if key in signal_value: + account_values.add(Account(signal_value[key])) + + account_values.add(Account(self.account)) + printnow("Distinct account values:", account_values) + return account_values + #prdelat queue na dynamic - podle toho jak bud uchtit pracovat s multiresolutions #zatim jen jedna q1 #TODO zaroven strategie musi vedet o rectypu, protoze je zpracovava @@ -116,25 +140,33 @@ class Strategy: return -1 self.debug = debug - self.key = get_key(mode=mode, account=self.account) if mode == Mode.LIVE or mode == Mode.PAPER: #data loader thread self.dataloader = Trade_WS_Streamer(name="WS-LDR-"+self.name) - self.interface = LiveInterface(symbol=self.symbol, key=self.key) - # order notif thread - self.order_notifs = LiveOrderUpdatesStreamer(key=self.key, name="WS-STRMR-" + self.name) - #propojujeme notifice s interfacem (pro callback) - self.order_notifs.connect_callback(self) - self.state = StrategyState(name=self.name, symbol = self.symbol, stratvars = self.stratvars, interface=self.interface, rectype=self.rectype, runner_id=self.runner_id, ilog_save=self.ilog_save) + #populate interfaces for each account + for account in self.accounts: + #get key for account + key = get_key(mode=mode, account=Account(account)) + self.interface[account.name] = LiveInterface(symbol=self.symbol, key=key) + # order notif thread + self.order_notifs = LiveOrderUpdatesStreamer(key=key, name="WS-STRMR-" + account.name + "-" + self.name, account=account) + #propojujeme notifice s interfacem (pro callback) + self.order_notifs.connect_callback(self) + + self.state = StrategyState(name=self.name, accounts=self.accounts, account=self.account, symbol = self.symbol, stratvars = self.stratvars, interface=self.interface, rectype=self.rectype, runner_id=self.runner_id, ilog_save=self.ilog_save) elif mode == Mode.BT: self.dataloader = Trade_Offline_Streamer(start, end, btdata=self.btdata) - self.bt = Backtester(symbol = self.symbol, order_fill_callback= self.order_updates, btdata=self.btdata, cash=cash, bp_from=start, bp_to=end) + self.bt = Backtester(symbol = self.symbol, accounts=self.accounts, order_fill_callback= self.order_updates, btdata=self.btdata, cash=cash, bp_from=start, bp_to=end) - self.interface = BacktestInterface(symbol=self.symbol, bt=self.bt) - self.state = StrategyState(name=self.name, symbol = self.symbol, stratvars = self.stratvars, interface=self.interface, rectype=self.rectype, runner_id=self.runner_id, bt=self.bt, ilog_save=self.ilog_save) + #populate interfaces for each account + for account in self.accounts: + #pro backtest volame stejne oklicujeme interface + self.interface[account.name] = BacktestInterface(symbol=self.symbol, bt=self.bt, account=account) + self.state = StrategyState(name=self.name, accounts=self.accounts, account=self.account, symbol = self.symbol, stratvars = self.stratvars, interface=self.interface, rectype=self.rectype, runner_id=self.runner_id, bt=self.bt, ilog_save=self.ilog_save) + #no callback from bt, it is called directly self.order_notifs = None ##streamer bude plnit trady do listu trades - nad kterym bude pracovat paper trade @@ -142,10 +174,10 @@ class Strategy: self.dataloader.add_stream(TradeAggregator2List(symbol=self.symbol,btdata=self.btdata,rectype=RecordType.TRADE)) elif mode == Mode.PREP: #bt je zde jen pro udrzeni BT casu v logu atp. JInak jej nepouzivame. - self.bt = Backtester(symbol = self.symbol, order_fill_callback= self.order_updates, btdata=self.btdata, cash=cash, bp_from=start, bp_to=end) + self.bt = Backtester(symbol = self.symbol, accounts=self.accounts, order_fill_callback= self.order_updates, btdata=self.btdata, cash=cash, bp_from=start, bp_to=end) self.interface = None #self.interface = BacktestInterface(symbol=self.symbol, bt=self.bt) - self.state = StrategyState(name=self.name, symbol = self.symbol, stratvars = self.stratvars, interface=self.interface, rectype=self.rectype, runner_id=self.runner_id, bt=self.bt, ilog_save=self.ilog_save) + self.state = StrategyState(name=self.name, accounts=self.accounts, account=self.account, symbol = self.symbol, stratvars = self.stratvars, interface=self.interface, rectype=self.rectype, runner_id=self.runner_id, bt=self.bt, ilog_save=self.ilog_save) self.order_notifs = None else: @@ -314,13 +346,15 @@ class Strategy: """"refresh positions and avgp - for CBAR once per confirmed, for BARS each time""" def refresh_positions(self, item): if self.rectype == RecordType.BAR: - a,p = self.interface.pos() - if a != -1: - self.state.avgp, self.state.positions = a,p + for account in self.accounts: + a,p = self.interface[account.name].pos() + if a != -1: + self.state.account_variables[account.name].avgp, self.state.account_variables[account.name].positions = a, p elif self.rectype in (RecordType.CBAR, RecordType.CBARVOLUME, RecordType.CBARDOLLAR, RecordType.CBARRENKO) and item['confirmed'] == 1: - a,p = self.interface.pos() - if a != -1: - self.state.avgp, self.state.positions = a,p + for account in self.accounts: + a,p = self.interface[account.name].pos() + if a != -1: + self.state.account_variables[account.name].avgp, self.state.account_variables[account.name].positions = a, p """update state.last_trade_time a time of iteration""" def update_times(self, item): @@ -416,7 +450,11 @@ class Strategy: if self.mode == Mode.LIVE or self.mode == Mode.PAPER: #live notification thread - self.order_notifs.start() + #for all keys in self.order_notifs call start() + for key in self.order_notifs: + self.order_notifs[key].start() + + #self.order_notifs.start() elif self.mode == Mode.BT or self.mode == Mode.PREP: self.bt.backtest_start = datetime.now() @@ -486,7 +524,7 @@ class Strategy: self.stop() if self.mode == Mode.BT: - print("REQUEST COUNT:", self.interface.mincnt) + print("REQUEST COUNT:", {account_str:self.interface[account_str].mincnt for account_str in self.interface}) self.bt.backtest_end = datetime.now() #print(40*"*",self.mode, "BACKTEST RESULTS",40*"*") @@ -500,7 +538,9 @@ class Strategy: #disconnect strategy from websocket trader updates if self.mode == Mode.LIVE or self.mode == Mode.PAPER: - self.order_notifs.disconnect_callback(self) + for key in self.order_notifs: + self.order_notifs[key].disconnect_callback(self) + #self.order_notifs.disconnect_callback(self) #necessary only for shared loaders (to keep it running for other stratefies) for i in self._streams: @@ -541,11 +581,11 @@ class Strategy: #for order updates from LIVE or BACKTEST #updates are sent only for SYMBOL of strategy - async def order_updates(self, data: TradeUpdate): + async def order_updates(self, data: TradeUpdate, account: Account): if self.mode == Mode.LIVE or self.mode == Mode.PAPER: now = datetime.now().timestamp() #z alpakýho TradeEvent si udelame svuj rozsireny TradeEvent (obsahujici navic profit atp.) - data = TradeUpdate(**data.dict()) + data = TradeUpdate(**data.dict(), account=account) else: now = self.bt.time @@ -632,17 +672,14 @@ class Strategy: rt_out["statinds"] = dict() for key, value in self.state.statinds.items(): rt_out["statinds"][key] = value - - #vkladame average price and positions, pokud existuji - #self.state.avgp , self.state.positions #pro typ strategie Classic, posilame i vysi stoploss try: - sl_value = self.state.vars["activeTrade"].stoploss_value + sl_value = gaka(self.state.account_variables, "activeTrade", lambda x: x.stoploss_value) except (KeyError, AttributeError): sl_value = None - rt_out["positions"] = dict(time=self.state.time, positions=self.state.positions, avgp=self.state.avgp, sl_value=sl_value) + rt_out["positions"] = dict(time=self.state.time, positions=gaka(self.state.account_variables, "positions"), avgp=gaka(self.state.account_variables,), sl_value=sl_value) #vkladame limitku a pendingbuys try: @@ -718,17 +755,21 @@ class StrategyState: """Strategy Stat object that is passed to callbacks note: state.time - state.interface.time + state.interface[account.name].time + accounts = set of all accounts (strings) + account = enum of primary account (Account) většinou mají stejnou hodnotu, ale lišit se mužou např. v případě BT callbacku - kdy se v rámci okna končící state.time realizují objednávky, které triggerují callback, který následně vyvolá např. buy (ten se musí ale udít v čase fillu, tzn. callback si nastaví čas interfacu na filltime) po dokončení bt kroků před zahájením iterace "NEXT" se časy znovu updatnout na původni state.time """ - def __init__(self, name: str, symbol: str, stratvars: AttributeDict, bars: AttributeDict = {}, trades: AttributeDict = {}, interface: GeneralInterface = None, rectype: RecordType = RecordType.BAR, runner_id: UUID = None, bt: Backtester = None, ilog_save: bool = False): + def __init__(self, name: str, symbol: str, accounts: set, account: Account, stratvars: AttributeDict, bars: AttributeDict = {}, trades: AttributeDict = {}, interface: GeneralInterface = None, rectype: RecordType = RecordType.BAR, runner_id: UUID = None, bt: Backtester = None, ilog_save: bool = False): self.vars = stratvars self.interface = interface - self.positions = 0 - self.avgp = 0 - self.blockbuy = 0 + self.account = account #primary account + self.accounts = accounts + #populate account variables dictionary + self.account_variables: Dict[str, AccountVariables] = {account.name: AccountVariables() for account in self.accounts} + self.name = name self.symbol = symbol self.rectype = rectype @@ -737,11 +778,10 @@ class StrategyState: self.time = None #time of last trade processed self.last_trade_time = 0 - self.last_entry_price=dict(long=0,short=999) + self.last_entry_price={key:dict(long=0,short=999) for key in self.accounts} self.resolution = None self.runner_id = runner_id self.bt = bt - self.dont_exit_already_activated = False self.docasny_rel_profit = [] self.ilog_save = ilog_save self.sl_optimizer_short = optimsl.SLOptimizer(ptm.TradeDirection.SHORT) @@ -779,18 +819,14 @@ class StrategyState: #secondary resolution indicators #self.secondary_indicators = AttributeDict(time=[], sec_price=[]) self.statinds = AttributeDict() - #these methods can be overrided by StrategyType (to add or alter its functionality) - self.buy = self.interface.buy - self.buy_l = self.interface.buy_l - self.sell = self.interface.sell - self.sell_l = self.interface.sell_l + self.cancel_pending_buys = None self.iter_log_list = [] self.dailyBars = defaultdict(dict) #celkovy profit (prejmennovat na profit_cum) - self.profit = 0 + self.profit = 0 #TODO key by account? #celkovy relativni profit (obsahuje pole relativnich zisku, z jeho meanu se spocita celkovy rel_profit_cu,) - self.rel_profit_cum = [] + self.rel_profit_cum = []#TODO key by account? self.tradeList = [] #nova promenna pro externi data do ArchiveDetaili, napr. pro zobrazeni v grafu, je zde např. SL history self.extData = defaultdict(dict) @@ -799,6 +835,25 @@ class StrategyState: self.today_market_close = None self.classed_indicators = {} + #quick interface actions to access from state without having to write interface[account.name].buy_l + def buy_l(self, account: Account, price: float, size: int = 1, repeat: bool = False, force: int = 0): + self.interface[account.name].buy_l(price, size, repeat, force) + + def buy(self, account: Account, size = 1, repeat: bool = False): + self.interface[account.name].buy(size, repeat) + + def sell_l(self, account: Account, price: float, size: int = 1, repeat: bool = False): + self.interface[account.name].sell_l(price, size, repeat) + + def sell(self, account: Account, size = 1, repeat: bool = False): + self.interface[account.name].sell(size, repeat) + + def repl(self, account: Account, orderid: str, price: float = None, size: int = 1, repeat: bool = False): + self.interface[account.name].repl(orderid, price, size, repeat) + + def cancel(self, account: Account, orderid: str): + self.interface[account.name].cancel(orderid) + def release(self): #release large variables self.bars = None diff --git a/v2realbot/strategyblocks/activetrade/activetrade_hub.py b/v2realbot/strategyblocks/activetrade/activetrade_hub.py index da244dc..a03fad3 100644 --- a/v2realbot/strategyblocks/activetrade/activetrade_hub.py +++ b/v2realbot/strategyblocks/activetrade/activetrade_hub.py @@ -1,9 +1,13 @@ from v2realbot.strategyblocks.activetrade.sl.trailsl import trail_SL_management from v2realbot.strategyblocks.activetrade.close.evaluate_close import eval_close_position +from v2realbot.utils.utils import gaka +def manage_active_trade(state, data): + accountsWithActiveTrade = gaka(state.account_variables, "activeTrade", None, lambda x: x is not None) + # {"account1": activeTrade, + # "account2": activeTrade} -def manage_active_trade(state, data): - trade = state.vars.activeTrade - if trade is None: - return -1 - trail_SL_management(state, data) - eval_close_position(state, data) \ No newline at end of file + if len(accountsWithActiveTrade.values()) == 0: + return + + trail_SL_management(state, accountsWithActiveTrade, data) + eval_close_position(state, accountsWithActiveTrade, data) \ No newline at end of file diff --git a/v2realbot/strategyblocks/activetrade/close/close_position.py b/v2realbot/strategyblocks/activetrade/close/close_position.py index 502b7b6..666a43e 100644 --- a/v2realbot/strategyblocks/activetrade/close/close_position.py +++ b/v2realbot/strategyblocks/activetrade/close/close_position.py @@ -1,7 +1,7 @@ from v2realbot.strategy.base import StrategyState from v2realbot.strategy.StrategyOrderLimitVykladaciNormalizedMYSELL import StrategyOrderLimitVykladaciNormalizedMYSELL from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Followup -from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus +from v2realbot.common.model import Trade, TradeDirection, TradeStatus from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, print, safe_get, is_still, is_window_open, eval_cond_dict, crossed_down, crossed_up, crossed, is_pivot, json_serial, pct_diff, create_new_bars, slice_dict_lists from v2realbot.utils.directive_utils import get_conditions_from_configuration from v2realbot.common.model import SLHistory @@ -18,17 +18,20 @@ import os from traceback import format_exc from v2realbot.strategyblocks.activetrade.helpers import insert_SL_history +#TODO tady jsem taky skoncil a pak zpetna evaluate_close (mozna zde staci jen account?) + # - close means change status in prescribed Trends,update profit, delete from activeTrade -def close_position(state, data, direction: TradeDirection, reason: str, followup: Followup = None): +def close_position(state: StrategyState, activeTrade: Trade, data, direction: TradeDirection, reason: str, followup: Followup = None): followup_text = str(followup) if followup is not None else "" - state.ilog(lvl=1,e=f"CLOSING TRADE {followup_text} {reason} {str(direction)}", curr_price=data["close"], trade=state.vars.activeTrade) + positions = state.account_variables[activeTrade.account.name].positions + state.ilog(lvl=1,e=f"CLOSING TRADE {followup_text} {reason} {str(direction)}", curr_price=data["close"], trade=activeTrade) if direction == TradeDirection.SHORT: - res = state.buy(size=abs(int(state.positions))) + res = state.buy(size=abs(int(positions))) if isinstance(res, int) and res < 0: raise Exception(f"error in required operation {reason} {res}") elif direction == TradeDirection.LONG: - res = state.sell(size=state.positions) + res = state.sell(size=positions) if isinstance(res, int) and res < 0: raise Exception(f"error in required operation STOPLOSS SELL {res}") @@ -37,19 +40,21 @@ def close_position(state, data, direction: TradeDirection, reason: str, followup #pri uzavreni tradu zapisujeme SL history - lepsi zorbazeni v grafu insert_SL_history(state) - state.dont_exit_already_activated = False - state.vars.pending = state.vars.activeTrade.id - state.vars.activeTrade = None - state.vars.last_exit_index = data["index"] + state.account_variables[activeTrade.account.name].pending = activeTrade.id + state.account_variables[activeTrade.account.name].activeTrade = None + #state.account_variables[activeTrade.account.name].last_exit_index = data["index"] + state.vars.last_exit_index = data["index"] + state.account_variables[activeTrade.account.name].dont_exit_already_activated = False if followup is not None: - state.vars.requested_followup = followup + state.account_variables[activeTrade.account.name].requested_followup = followup #close only partial position - no followup here, size multiplier must be between 0 and 1 -def close_position_partial(state, data, direction: TradeDirection, reason: str, size: float): +def close_position_partial(state, activeTrade: Trade,data, direction: TradeDirection, reason: str, size: float): + positions = state.account_variables[activeTrade.account.name].positions if size <= 0 or size >=1: raise Exception(f"size must be betweem 0 and 1") - size_abs = abs(int(int(state.positions)*size)) - state.ilog(lvl=1,e=f"CLOSING TRADE PART: {size_abs} {size} {reason} {str(direction)}", curr_price=data["close"], trade=state.vars.activeTrade) + size_abs = abs(int(int(positions)*size)) + state.ilog(lvl=1,e=f"CLOSING TRADE PART: {size_abs} {size} {reason} {str(direction)}", curr_price=data["close"], trade=activeTrade) if direction == TradeDirection.SHORT: res = state.buy(size=size_abs) if isinstance(res, int) and res < 0: @@ -64,6 +69,10 @@ def close_position_partial(state, data, direction: TradeDirection, reason: str, #pri uzavreni tradu zapisujeme SL history - lepsi zorbazeni v grafu insert_SL_history(state) - state.vars.pending = state.vars.activeTrade.id + state.account_variables[activeTrade.account.name].pending = activeTrade.id + state.account_variables[activeTrade.account.name].activeTrade = None + state.account_variables[activeTrade.account.name].dont_exit_already_activated = False + #state.account_variables[activeTrade.account.name].last_exit_index = data["index"] + #state.vars.activeTrade = None - #state.vars.last_exit_index = data["index"] \ No newline at end of file + state.vars.last_exit_index = data["index"] #ponechano mimo account \ No newline at end of file diff --git a/v2realbot/strategyblocks/activetrade/close/conditions.py b/v2realbot/strategyblocks/activetrade/close/conditions.py index 1d7c72a..25edb77 100644 --- a/v2realbot/strategyblocks/activetrade/close/conditions.py +++ b/v2realbot/strategyblocks/activetrade/close/conditions.py @@ -1,7 +1,7 @@ from v2realbot.strategy.base import StrategyState from v2realbot.strategy.StrategyOrderLimitVykladaciNormalizedMYSELL import StrategyOrderLimitVykladaciNormalizedMYSELL from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Followup -from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus +from v2realbot.common.model import Trade, TradeDirection, TradeStatus from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, print, safe_get, is_still, is_window_open, eval_cond_dict, crossed_down, crossed_up, crossed, is_pivot, json_serial, pct_diff, create_new_bars, slice_dict_lists from v2realbot.utils.directive_utils import get_conditions_from_configuration from v2realbot.common.model import SLHistory @@ -12,9 +12,9 @@ from threading import Event import os from traceback import format_exc from v2realbot.strategyblocks.indicators.helpers import evaluate_directive_conditions -from v2realbot.strategyblocks.activetrade.helpers import get_override_for_active_trade, normalize_tick +from v2realbot.strategyblocks.activetrade.helpers import get_signal_section_directive, normalize_tick -def dontexit_protection_met(state, data, direction: TradeDirection): +def dontexit_protection_met(state, activeTrade: Trade, data, direction: TradeDirection): if direction == TradeDirection.LONG: smer = "long" else: @@ -24,58 +24,64 @@ def dontexit_protection_met(state, data, direction: TradeDirection): #vyreseno pri kazde aktivaci se vyplni flag already_activated #pri naslednem false podminky se v pripade, ze je aktivovany flag posle True - #take se vyrusi v closu - def process_result(result): + def process_result(result, account): if result: - state.dont_exit_already_activated = True + state.account_variables[account.name].dont_exit_already_activated = True return True else: return False def evaluate_result(): - mother_signal = state.vars.activeTrade.generated_by + mother_signal = activeTrade.generated_by + dont_exit_already_activated = state.account_variables[activeTrade.account.name].dont_exit_already_activated if mother_signal is not None: #TESTUJEME DONT_EXIT_ cond_dict = state.vars.conditions[KW.dont_exit][mother_signal][smer] #OR result, conditions_met = evaluate_directive_conditions(state, cond_dict, "OR") - state.ilog(lvl=1,e=f"DONT_EXIT {mother_signal} {smer} =OR= {result}", **conditions_met, cond_dict=cond_dict, already_activated=str(state.dont_exit_already_activated)) + state.ilog(lvl=1,e=f"DONT_EXIT {mother_signal} {smer} =OR= {result}", **conditions_met, cond_dict=cond_dict, already_activated=str(dont_exit_already_activated)) if result: return True #OR neprosly testujeme AND result, conditions_met = evaluate_directive_conditions(state, cond_dict, "AND") - state.ilog(lvl=1,e=f"DONT_EXIT {mother_signal} {smer} =AND= {result}", **conditions_met, cond_dict=cond_dict, already_activated=str(state.dont_exit_already_activated)) + state.ilog(lvl=1,e=f"DONT_EXIT {mother_signal} {smer} =AND= {result}", **conditions_met, cond_dict=cond_dict, already_activated=str(dont_exit_already_activated)) if result: return True cond_dict = state.vars.conditions[KW.dont_exit]["common"][smer] #OR result, conditions_met = evaluate_directive_conditions(state, cond_dict, "OR") - state.ilog(lvl=1,e=f"DONT_EXIT common {smer} =OR= {result}", **conditions_met, cond_dict=cond_dict, already_activated=str(state.dont_exit_already_activated)) + state.ilog(lvl=1,e=f"DONT_EXIT common {smer} =OR= {result}", **conditions_met, cond_dict=cond_dict, already_activated=str(dont_exit_already_activated)) if result: return True #OR neprosly testujeme AND result, conditions_met = evaluate_directive_conditions(state, cond_dict, "AND") - state.ilog(lvl=1,e=f"DONT_EXIT common {smer} =AND= {result}", **conditions_met, cond_dict=cond_dict, already_activated=str(state.dont_exit_already_activated)) + state.ilog(lvl=1,e=f"DONT_EXIT common {smer} =AND= {result}", **conditions_met, cond_dict=cond_dict, already_activated=str(dont_exit_already_activated)) return result #nejprve evaluujeme vsechny podminky result = evaluate_result() #pak evaluujeme vysledek a vracíme - return process_result(result) + return process_result(result, activeTrade.account) -def exit_conditions_met(state, data, direction: TradeDirection): +def exit_conditions_met(state: StrategyState, activeTrade: Trade, data, direction: TradeDirection): if direction == TradeDirection.LONG: smer = "long" else: smer = "short" + signal_name = activeTrade.generated_by + last_entry_index = state.account_variables[activeTrade.account.name].last_entry_index + avgp = state.account_variables[activeTrade.account.name].avgp + positions = state.account_variables[activeTrade.account.name].positions + directive_name = "exit_cond_only_on_confirmed" - exit_cond_only_on_confirmed = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False)) + exit_cond_only_on_confirmed = get_signal_section_directive(state, signal_name=signal_name, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False)) if exit_cond_only_on_confirmed and data['confirmed'] == 0: state.ilog(lvl=0,e="EXIT COND ONLY ON CONFIRMED BAR") @@ -83,20 +89,20 @@ def exit_conditions_met(state, data, direction: TradeDirection): ## minimální počet barů od vstupu directive_name = "exit_cond_req_bars" - exit_cond_req_bars = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, 1)) + exit_cond_req_bars = get_signal_section_directive(state, signal_name=signal_name, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, 1)) - if state.vars.last_in_index is not None: - index_to_compare = int(state.vars.last_in_index)+int(exit_cond_req_bars) + if last_entry_index is not None: + index_to_compare = int(last_entry_index)+int(exit_cond_req_bars) if int(data["index"]) < index_to_compare: - state.ilog(lvl=1,e=f"EXIT COND WAITING on required bars from IN {exit_cond_req_bars} TOO SOON", currindex=data["index"], index_to_compare=index_to_compare, last_in_index=state.vars.last_in_index) + state.ilog(lvl=1,e=f"EXIT COND WAITING on required bars from IN {exit_cond_req_bars} TOO SOON", currindex=data["index"], index_to_compare=index_to_compare, last_entry_index=last_entry_index) return False #POKUD je nastaven MIN PROFIT, zkontrolujeme ho a az pripadne pustime CONDITIONY directive_name = "exit_cond_min_profit" - exit_cond_min_profit_nodir = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None)) + exit_cond_min_profit_nodir = get_signal_section_directive(state, signal_name=signal_name, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None)) directive_name = "exit_cond_min_profit_" + str(smer) - exit_cond_min_profit = get_override_for_active_trade(state, directive_name=directive_name, default_value=exit_cond_min_profit_nodir) + exit_cond_min_profit = get_signal_section_directive(state, signal_name=signal_name,directive_name=directive_name, default_value=exit_cond_min_profit_nodir) #máme nastavený exit_cond_min_profit @@ -105,10 +111,10 @@ def exit_conditions_met(state, data, direction: TradeDirection): if exit_cond_min_profit is not None: exit_cond_min_profit_normalized = normalize_tick(state, data, float(exit_cond_min_profit)) - exit_cond_goal_price = price2dec(float(state.avgp)+exit_cond_min_profit_normalized,3) if int(state.positions) > 0 else price2dec(float(state.avgp)-exit_cond_min_profit_normalized,3) + exit_cond_goal_price = price2dec(float(avgp)+exit_cond_min_profit_normalized,3) if int(positions) > 0 else price2dec(float(avgp)-exit_cond_min_profit_normalized,3) curr_price = float(data["close"]) state.ilog(lvl=1,e=f"EXIT COND min profit {exit_cond_goal_price=} {exit_cond_min_profit=} {exit_cond_min_profit_normalized=} {curr_price=}") - if (int(state.positions) < 0 and curr_price<=exit_cond_goal_price) or (int(state.positions) > 0 and curr_price>=exit_cond_goal_price): + if (int(positions) < 0 and curr_price<=exit_cond_goal_price) or (int(positions) > 0 and curr_price>=exit_cond_goal_price): state.ilog(lvl=1,e=f"EXIT COND min profit PASS - POKRACUJEME") else: state.ilog(lvl=1,e=f"EXIT COND min profit NOT PASS") @@ -137,10 +143,10 @@ def exit_conditions_met(state, data, direction: TradeDirection): #bereme bud exit condition signalu, ktery activeTrade vygeneroval+ fallback na general state.ilog(lvl=0,e=f"EXIT CONDITIONS ENTRY {smer}", conditions=state.vars.conditions[KW.exit]) - mother_signal = state.vars.activeTrade.generated_by + mother_signal = signal_name if mother_signal is not None: - cond_dict = state.vars.conditions[KW.exit][state.vars.activeTrade.generated_by][smer] + cond_dict = state.vars.conditions[KW.exit][signal_name][smer] result, conditions_met = evaluate_directive_conditions(state, cond_dict, "OR") state.ilog(lvl=1,e=f"EXIT CONDITIONS of {mother_signal} =OR= {result}", **conditions_met, cond_dict=cond_dict) if result: diff --git a/v2realbot/strategyblocks/activetrade/close/eod_exit.py b/v2realbot/strategyblocks/activetrade/close/eod_exit.py index 1967032..f45a70b 100644 --- a/v2realbot/strategyblocks/activetrade/close/eod_exit.py +++ b/v2realbot/strategyblocks/activetrade/close/eod_exit.py @@ -1,7 +1,7 @@ from v2realbot.strategy.base import StrategyState from v2realbot.strategy.StrategyOrderLimitVykladaciNormalizedMYSELL import StrategyOrderLimitVykladaciNormalizedMYSELL from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Followup -from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus +from v2realbot.common.model import Trade, TradeDirection, TradeStatus from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, print, safe_get, is_still, is_window_open, eval_cond_dict, crossed_down, crossed_up, crossed, is_pivot, json_serial, pct_diff, create_new_bars, slice_dict_lists from v2realbot.utils.directive_utils import get_conditions_from_configuration from v2realbot.common.model import SLHistory @@ -18,10 +18,10 @@ import os from traceback import format_exc from v2realbot.strategyblocks.activetrade.helpers import insert_SL_history from v2realbot.strategyblocks.activetrade.close.conditions import dontexit_protection_met, exit_conditions_met -from v2realbot.strategyblocks.activetrade.helpers import get_max_profit_price, get_profit_target_price, get_override_for_active_trade, keyword_conditions_met +from v2realbot.strategyblocks.activetrade.helpers import get_max_profit_price, get_profit_target_price, get_signal_section_directive -def eod_exit_activated(state: StrategyState, data, direction: TradeDirection): +def eod_exit_activated(state: StrategyState, activeTrade: Trade, data, direction: TradeDirection): """ Function responsible for end of day management @@ -38,8 +38,10 @@ def eod_exit_activated(state: StrategyState, data, direction: TradeDirection): - 1 min forced immediate """ + avgp = state.account_variables[activeTrade.account.name].avgp + directive_name = "forced_exit_window_start" - forced_exit_window_start = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None)) + forced_exit_window_start = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None)) if forced_exit_window_start is None: state.ilog(lvl=0,e="Forced exit not required.") @@ -47,7 +49,7 @@ def eod_exit_activated(state: StrategyState, data, direction: TradeDirection): directive_name = "forced_exit_window_end" - forced_exit_window_end = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, 389)) + forced_exit_window_end = get_signal_section_directive(state, signal_name=activeTrade.generated_by,directive_name=directive_name, default_value=safe_get(state.vars, directive_name, 389)) if forced_exit_window_start>389: state.ilog(lvl=0,e="Forced exit window end max is 389") @@ -60,7 +62,7 @@ def eod_exit_activated(state: StrategyState, data, direction: TradeDirection): # #dokdy konci okno snizujiciho se profitu (zbytek je breakeven a posledni minuta forced) - default pulka okna # directive_name = "forced_exit_decreasing_profit_window_end" - # forced_exit_decreasing_profit_window_end = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, (forced_exit_window_end-forced_exit_window_end)/2)) + # forced_exit_decreasing_profit_window_end = get_signal_section_directive(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, (forced_exit_window_end-forced_exit_window_end)/2)) # if forced_exit_decreasing_profit_window_end > forced_exit_window_end-1: # state.ilog(lvl=0,e="Decreasing profit window must be less than window end -1.") @@ -72,7 +74,7 @@ def eod_exit_activated(state: StrategyState, data, direction: TradeDirection): state.ilog(lvl=1,e=f"Forced Exit Window OPEN - breakeven check", msg=f"{forced_exit_window_start=} {forced_exit_window_end=} ", time=str(datetime.fromtimestamp(data['updated']).astimezone(zoneNY))) directive_name = "forced_exit_breakeven_period" - forced_exit_breakeven_period = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, True)) + forced_exit_breakeven_period = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, True)) if forced_exit_breakeven_period is False: return False @@ -80,11 +82,11 @@ def eod_exit_activated(state: StrategyState, data, direction: TradeDirection): #zatim krom posledni minuty cekame alespon na breakeven curr_price = float(data['close']) #short smer - if direction == TradeDirection.SHORT and curr_price<=float(state.avgp): + if direction == TradeDirection.SHORT and curr_price<=float(avgp): state.ilog(lvl=1,e=f"Forced Exit - price better than avgp, dir SHORT") return True - if direction == TradeDirection.LONG and curr_price>=float(state.avgp): + if direction == TradeDirection.LONG and curr_price>=float(avgp): state.ilog(lvl=1,e=f"Forced Exit - price better than avgp, dir LONG") return True diff --git a/v2realbot/strategyblocks/activetrade/close/evaluate_close.py b/v2realbot/strategyblocks/activetrade/close/evaluate_close.py index b61fd6f..5543ed3 100644 --- a/v2realbot/strategyblocks/activetrade/close/evaluate_close.py +++ b/v2realbot/strategyblocks/activetrade/close/evaluate_close.py @@ -1,198 +1,207 @@ from v2realbot.strategyblocks.activetrade.close.close_position import close_position, close_position_partial from v2realbot.strategy.base import StrategyState from v2realbot.enums.enums import Followup -from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus +from v2realbot.common.model import Trade, TradeDirection, TradeStatus from v2realbot.utils.utils import safe_get from v2realbot.config import KW #from icecream import install, ic from rich import print as printanyway from threading import Event +#import gaka +from v2realbot.utils.utils import gaka import os from traceback import format_exc from v2realbot.strategyblocks.activetrade.close.eod_exit import eod_exit_activated from v2realbot.strategyblocks.activetrade.close.conditions import dontexit_protection_met, exit_conditions_met -from v2realbot.strategyblocks.activetrade.helpers import get_max_profit_price, get_profit_target_price, get_override_for_active_trade, keyword_conditions_met +from v2realbot.strategyblocks.activetrade.helpers import get_max_profit_price, get_profit_target_price, get_signal_section_directive, keyword_conditions_met from v2realbot.strategyblocks.activetrade.sl.optimsl import SLOptimizer -def eval_close_position(state: StrategyState, data): +#TODO tady odsud +def eval_close_position(state: StrategyState, accountsWithActiveTrade, data): + curr_price = float(data['close']) - state.ilog(lvl=0,e="Eval CLOSE", price=curr_price, pos=state.positions, avgp=state.avgp, pending=state.vars.pending, activeTrade=str(state.vars.activeTrade)) + state.ilog(lvl=0,e="Eval CLOSE", price=curr_price, pos=gaka(state.account_variables, "positions"), avgp=gaka(state.account_variables, "avgp"), pending=gaka(state.account_variables, "pending"), activeTrade=str(gaka(state.account_variables, "activeTrade"))) - if int(state.positions) != 0 and float(state.avgp)>0 and state.vars.pending is None: - - #close position handling - #TBD pridat OPTIMALIZACI POZICE - EXIT 1/2 - - #mame short pozice - (IDEA: rozlisovat na zaklade aktivniho tradu - umozni mi spoustet i pri soucasne long pozicemi) - if int(state.positions) < 0: - #get TARGET PRICE pro dany smer a signal - - #pokud existujeme bereme z nastaveni tradu a nebo z defaultu - if state.vars.activeTrade.goal_price is not None: - goal_price = state.vars.activeTrade.goal_price - else: - goal_price = get_profit_target_price(state, data, TradeDirection.SHORT) - - max_price = get_max_profit_price(state, data, TradeDirection.SHORT) - state.ilog(lvl=1,e=f"Def Goal price {str(TradeDirection.SHORT)} {goal_price} max price {max_price}") + #iterate over accountsWithActiveTrade + for account_str, activeTrade in accountsWithActiveTrade.items(): + positions = state.account_variables[account_str].positions + avgp = state.account_variables[account_str].avgp + pending = state.account_variables[account_str].pending + if int(positions) != 0 and float(avgp)>0 and pending is None: - #SL OPTIMALIZATION - PARTIAL EXIT - level_met, exit_adjustment = state.sl_optimizer_short.eval_position(state, data) - if level_met is not None and exit_adjustment is not None: - position = state.positions * exit_adjustment - state.ilog(lvl=1,e=f"SL OPTIMIZATION ENGAGED {str(TradeDirection.SHORT)} {position=} {level_met=} {exit_adjustment}", initial_levels=str(state.sl_optimizer_short.get_initial_abs_levels(state)), rem_levels=str(state.sl_optimizer_short.get_remaining_abs_levels(state)), exit_levels=str(state.sl_optimizer_short.exit_levels), exit_sizes=str(state.sl_optimizer_short.exit_sizes)) - printanyway(f"SL OPTIMIZATION ENGAGED {str(TradeDirection.SHORT)} {position=} {level_met=} {exit_adjustment}") - close_position_partial(state=state, data=data, direction=TradeDirection.SHORT, reason=F"SL OPT LEVEL {level_met} REACHED", size=exit_adjustment) - return - - #FULL SL reached - execution - if curr_price > state.vars.activeTrade.stoploss_value: + #close position handling + #TBD pridat OPTIMALIZACI POZICE - EXIT 1/2 - directive_name = 'reverse_for_SL_exit_short' - reverse_for_SL_exit = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, "no")) + #mame short pozice - (IDEA: rozlisovat na zaklade aktivniho tradu - umozni mi spoustet i pri soucasne long pozicemi) + if int(positions) < 0: + #get TARGET PRICE pro dany smer a signal - if reverse_for_SL_exit == "always": - followup_action = Followup.REVERSE - elif reverse_for_SL_exit == "cond": - followup_action = Followup.REVERSE if keyword_conditions_met(state, data, direction=TradeDirection.SHORT, keyword=KW.slreverseonly, skip_conf_validation=True) else None + #pokud existujeme bereme z nastaveni tradu a nebo z defaultu + if activeTrade.goal_price is not None: + goal_price = activeTrade.goal_price else: - followup_action = None - close_position(state=state, data=data, direction=TradeDirection.SHORT, reason="SL REACHED", followup=followup_action) - return + goal_price = get_profit_target_price(state, data, TradeDirection.SHORT, activeTrade) + + max_price = get_max_profit_price(state, data, TradeDirection.SHORT, activeTrade) + state.ilog(lvl=1,e=f"Def Goal price {str(TradeDirection.SHORT)} {goal_price} max price {max_price}") - - #REVERSE BASED ON REVERSE CONDITIONS - if keyword_conditions_met(state, data, direction=TradeDirection.SHORT, keyword=KW.reverse): - close_position(state=state, data=data, direction=TradeDirection.SHORT, reason="REVERSE COND MET", followup=Followup.REVERSE) - return + #SL OPTIMALIZATION - PARTIAL EXIT + level_met, exit_adjustment = state.sl_optimizer_short.eval_position(state, data, activeTrade) + if level_met is not None and exit_adjustment is not None: + position = positions * exit_adjustment + state.ilog(lvl=1,e=f"SL OPTIMIZATION ENGAGED {str(TradeDirection.SHORT)} {position=} {level_met=} {exit_adjustment}", initial_levels=str(state.sl_optimizer_short.get_initial_abs_levels(state, activeTrade)), rem_levels=str(state.sl_optimizer_short.get_remaining_abs_levels(state, activeTrade)), exit_levels=str(state.sl_optimizer_short.exit_levels), exit_sizes=str(state.sl_optimizer_short.exit_sizes)) + printanyway(f"SL OPTIMIZATION ENGAGED {str(TradeDirection.SHORT)} {position=} {level_met=} {exit_adjustment}") + close_position_partial(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.SHORT, reason=F"SL OPT LEVEL {level_met} REACHED", size=exit_adjustment) + return + + #FULL SL reached - execution + if curr_price > activeTrade.stoploss_value: - #EXIT ADD CONDITIONS MET (exit and add) - if keyword_conditions_met(state, data, direction=TradeDirection.SHORT, keyword=KW.exitadd): - close_position(state=state, data=data, direction=TradeDirection.SHORT, reason="EXITADD COND MET", followup=Followup.ADD) - return + directive_name = 'reverse_for_SL_exit_short' + reverse_for_SL_exit = get_signal_section_directive(state=state, activeTrade=activeTrade, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, "no")) - #CLOSING BASED ON EXIT CONDITIONS - if exit_conditions_met(state, data, TradeDirection.SHORT): - directive_name = 'reverse_for_cond_exit_short' - reverse_for_cond_exit_short = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False)) - directive_name = 'add_for_cond_exit_short' - add_for_cond_exit_short = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False)) - if reverse_for_cond_exit_short: + if reverse_for_SL_exit == "always": followup_action = Followup.REVERSE - elif add_for_cond_exit_short: - followup_action = Followup.ADD + elif reverse_for_SL_exit == "cond": + followup_action = Followup.REVERSE if keyword_conditions_met(state, data=data, activeTrade=activeTrade.generated_by, direction=TradeDirection.SHORT, keyword=KW.slreverseonly, skip_conf_validation=True) else None else: followup_action = None - close_position(state=state, data=data, direction=TradeDirection.SHORT, reason="EXIT COND MET", followup=followup_action) - return - - #PROFIT - if curr_price<=goal_price: - #TODO cekat az slope prestane intenzivn erust, necekat az na klesani - #TODO mozna cekat na nejaky signal RSI - #TODO pripadne pokud dosahne TGTBB prodat ihned - max_price_signal = curr_price<=max_price - #OPTIMALIZACE pri stoupajícím angle - if max_price_signal or dontexit_protection_met(state=state, data=data,direction=TradeDirection.SHORT) is False: - close_position(state=state, data=data, direction=TradeDirection.SHORT, reason=f"PROFIT or MAXPROFIT REACHED {max_price_signal=}") + close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.SHORT, reason="SL REACHED", followup=followup_action) return - #pokud je cena horsi, ale byla uz dont exit aktivovany - pak prodavame také - elif state.dont_exit_already_activated == True: - #TODO toto mozna take na direktivu, timto neprodavame pokud porkacuje trend - EXIT_PROT_BOUNCE_IMMEDIATE - #if dontexit_protection_met(state=state, data=data,direction=TradeDirection.SHORT) is False: - close_position(state=state, data=data, direction=TradeDirection.SHORT, reason=f"EXIT PROTECTION BOUNCE {state.dont_exit_already_activated=}") - state.dont_exit_already_activated = False - return + + + #REVERSE BASED ON REVERSE CONDITIONS + if keyword_conditions_met(state, data, activeTrade=activeTrade.generated_by, direction=TradeDirection.SHORT, keyword=KW.reverse): + close_position(state=state, activeTrade=activeTrade,data=data, direction=TradeDirection.SHORT, reason="REVERSE COND MET", followup=Followup.REVERSE) + return - #FORCED EXIT PRI KONCI DNE - if eod_exit_activated(state, data, TradeDirection.SHORT): - close_position(state=state, data=data, direction=TradeDirection.SHORT, reason="EOD EXIT ACTIVATED") - return - - #mame long - elif int(state.positions) > 0: + #EXIT ADD CONDITIONS MET (exit and add) + if keyword_conditions_met(state, data, activeTrade=activeTrade.generated_by, direction=TradeDirection.SHORT, keyword=KW.exitadd): + close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.SHORT, reason="EXITADD COND MET", followup=Followup.ADD) + return - #get TARGET PRICE pro dany smer a signal - #pokud existujeme bereme z nastaveni tradu a nebo z defaultu - if state.vars.activeTrade.goal_price is not None: - goal_price = state.vars.activeTrade.goal_price - else: - goal_price = get_profit_target_price(state, data, TradeDirection.LONG) - - max_price = get_max_profit_price(state, data, TradeDirection.LONG) - state.ilog(lvl=1,e=f"Goal price {str(TradeDirection.LONG)} {goal_price} max price {max_price}") + #CLOSING BASED ON EXIT CONDITIONS + if exit_conditions_met(state, data, TradeDirection.SHORT): + directive_name = 'reverse_for_cond_exit_short' + reverse_for_cond_exit_short = get_signal_section_directive(state=state, signal_name=activeTrade.signal_name, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False)) + directive_name = 'add_for_cond_exit_short' + add_for_cond_exit_short = get_signal_section_directive(state=state, signal_name=activeTrade.signal_name, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False)) + if reverse_for_cond_exit_short: + followup_action = Followup.REVERSE + elif add_for_cond_exit_short: + followup_action = Followup.ADD + else: + followup_action = None + close_position(state=state, data=data, direction=TradeDirection.SHORT, reason="EXIT COND MET", followup=followup_action) + return - #SL OPTIMALIZATION - PARTIAL EXIT - level_met, exit_adjustment = state.sl_optimizer_long.eval_position(state, data) - if level_met is not None and exit_adjustment is not None: - position = state.positions * exit_adjustment - state.ilog(lvl=1,e=f"SL OPTIMIZATION ENGAGED {str(TradeDirection.LONG)} {position=} {level_met=} {exit_adjustment}", initial_levels=str(state.sl_optimizer_long.get_initial_abs_levels(state)), rem_levels=str(state.sl_optimizer_long.get_remaining_abs_levels(state)), exit_levels=str(state.sl_optimizer_long.exit_levels), exit_sizes=str(state.sl_optimizer_long.exit_sizes)) - printanyway(f"SL OPTIMIZATION ENGAGED {str(TradeDirection.LONG)} {position=} {level_met=} {exit_adjustment}") - close_position_partial(state=state, data=data, direction=TradeDirection.LONG, reason=f"SL OPT LEVEL {level_met} REACHED", size=exit_adjustment) - return + #PROFIT + if curr_price<=goal_price: + #TODO cekat az slope prestane intenzivn erust, necekat az na klesani + #TODO mozna cekat na nejaky signal RSI + #TODO pripadne pokud dosahne TGTBB prodat ihned + max_price_signal = curr_price<=max_price + #OPTIMALIZACE pri stoupajícím angle + if max_price_signal or dontexit_protection_met(state=state, data=data,direction=TradeDirection.SHORT) is False: + close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.SHORT, reason=f"PROFIT or MAXPROFIT REACHED {max_price_signal=}") + return + #pokud je cena horsi, ale byla uz dont exit aktivovany - pak prodavame také + elif state.account_variables[activeTrade.account.name].dont_exit_already_activated == True: + #TODO toto mozna take na direktivu, timto neprodavame pokud porkacuje trend - EXIT_PROT_BOUNCE_IMMEDIATE + #if dontexit_protection_met(state=state, data=data,direction=TradeDirection.SHORT) is False: + close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.SHORT, reason=f"EXIT PROTECTION BOUNCE {state.account_variables[activeTrade.account.name].dont_exit_already_activated=}") + state.account_variables[activeTrade.account.name].dont_exit_already_activated = False + return - #SL FULL execution - if curr_price < state.vars.activeTrade.stoploss_value: - directive_name = 'reverse_for_SL_exit_long' - reverse_for_SL_exit = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, "no")) + #FORCED EXIT PRI KONCI DNE + if eod_exit_activated(state, activeTrade=activeTrade, data=data, direction=TradeDirection.SHORT): + close_position(state=state, activeTrade=activeTrade,data=data, direction=TradeDirection.SHORT, reason="EOD EXIT ACTIVATED") + return + + #mame long + elif int(positions) > 0: - state.ilog(lvl=1, e=f"reverse_for_SL_exit {reverse_for_SL_exit}") - - if reverse_for_SL_exit == "always": - followup_action = Followup.REVERSE - elif reverse_for_SL_exit == "cond": - followup_action = Followup.REVERSE if keyword_conditions_met(state, data, direction=TradeDirection.LONG, keyword=KW.slreverseonly, skip_conf_validation=True) else None + #get TARGET PRICE pro dany smer a signal + #pokud existujeme bereme z nastaveni tradu a nebo z defaultu + if activeTrade.goal_price is not None: + goal_price = activeTrade.goal_price else: - followup_action = None - - close_position(state=state, data=data, direction=TradeDirection.LONG, reason="SL REACHED", followup=followup_action) - return + goal_price = get_profit_target_price(state, data, activeTrade, TradeDirection.LONG) + max_price = get_max_profit_price(state, activeTrade, data, TradeDirection.LONG) + state.ilog(lvl=1,e=f"Goal price {str(TradeDirection.LONG)} {goal_price} max price {max_price}") - #REVERSE BASED ON REVERSE CONDITIONS - if keyword_conditions_met(state, data,TradeDirection.LONG, KW.reverse): - close_position(state=state, data=data, direction=TradeDirection.LONG, reason="REVERSE COND MET", followup=Followup.REVERSE) - return + #SL OPTIMALIZATION - PARTIAL EXIT + level_met, exit_adjustment = state.sl_optimizer_long.eval_position(state, data, activeTrade) + if level_met is not None and exit_adjustment is not None: + position = positions * exit_adjustment + state.ilog(lvl=1,e=f"SL OPTIMIZATION ENGAGED {str(TradeDirection.LONG)} {position=} {level_met=} {exit_adjustment}", initial_levels=str(state.sl_optimizer_long.get_initial_abs_levels(state, activeTrade)), rem_levels=str(state.sl_optimizer_long.get_remaining_abs_levels(state, activeTrade)), exit_levels=str(state.sl_optimizer_long.exit_levels), exit_sizes=str(state.sl_optimizer_long.exit_sizes)) + printanyway(f"SL OPTIMIZATION ENGAGED {str(TradeDirection.LONG)} {position=} {level_met=} {exit_adjustment}") + close_position_partial(state=state, data=data, direction=TradeDirection.LONG, reason=f"SL OPT LEVEL {level_met} REACHED", size=exit_adjustment) + return - #EXIT ADD CONDITIONS MET (exit and add) - if keyword_conditions_met(state, data, TradeDirection.LONG, KW.exitadd): - close_position(state=state, data=data, direction=TradeDirection.LONG, reason="EXITADD COND MET", followup=Followup.ADD) - return + #SL FULL execution + if curr_price < activeTrade.stoploss_value: + directive_name = 'reverse_for_SL_exit_long' + reverse_for_SL_exit = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, "no")) - #EXIT CONDITIONS - if exit_conditions_met(state, data, TradeDirection.LONG): - directive_name = 'reverse_for_cond_exit_long' - reverse_for_cond_exit_long = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False)) - directive_name = 'add_for_cond_exit_long' - add_for_cond_exit_long = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False)) - if reverse_for_cond_exit_long: + state.ilog(lvl=1, e=f"reverse_for_SL_exit {reverse_for_SL_exit}") + + if reverse_for_SL_exit == "always": followup_action = Followup.REVERSE - elif add_for_cond_exit_long: - followup_action = Followup.ADD + elif reverse_for_SL_exit == "cond": + followup_action = Followup.REVERSE if keyword_conditions_met(state, data, activeTrade, direction=TradeDirection.LONG, keyword=KW.slreverseonly, skip_conf_validation=True) else None else: followup_action = None - close_position(state=state, data=data, direction=TradeDirection.LONG, reason="EXIT CONDS MET", followup=followup_action) - return - #PROFIT - if curr_price>=goal_price: - #TODO cekat az slope prestane intenzivn erust, necekat az na klesani - #TODO mozna cekat na nejaky signal RSI - #TODO pripadne pokud dosahne TGTBB prodat ihned - max_price_signal = curr_price>=max_price - #OPTIMALIZACE pri stoupajícím angle - if max_price_signal or dontexit_protection_met(state, data, direction=TradeDirection.LONG) is False: - close_position(state=state, data=data, direction=TradeDirection.LONG, reason=f"PROFIT or MAXPROFIT REACHED {max_price_signal=}") + close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG, reason="SL REACHED", followup=followup_action) return - #pokud je cena horsi, ale byl uz dont exit aktivovany - pak prodavame také - elif state.dont_exit_already_activated == True: - #TODO toto mozna take na direktivu, timto neprodavame pokud porkacuje trend - EXIT_PROT_BOUNCE_IMMEDIATE - # if dontexit_protection_met(state=state, data=data,direction=TradeDirection.LONG) is False: - close_position(state=state, data=data, direction=TradeDirection.LONG, reason=f"EXIT PROTECTION BOUNCE {state.dont_exit_already_activated=}") - state.dont_exit_already_activated = False - return - - #FORCED EXIT PRI KONCI DNE - if eod_exit_activated(state, data, TradeDirection.LONG): - close_position(state=state, data=data, direction=TradeDirection.LONG, reason="EOD EXIT ACTIVATED") - return \ No newline at end of file + + + #REVERSE BASED ON REVERSE CONDITIONS + if keyword_conditions_met(state, data, activeTrade, TradeDirection.LONG, KW.reverse): + close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG, reason="REVERSE COND MET", followup=Followup.REVERSE) + return + + #EXIT ADD CONDITIONS MET (exit and add) + if keyword_conditions_met(state, data, activeTrade, TradeDirection.LONG, KW.exitadd): + close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG, reason="EXITADD COND MET", followup=Followup.ADD) + return + + #EXIT CONDITIONS + if exit_conditions_met(state, activeTrade, data, TradeDirection.LONG): + directive_name = 'reverse_for_cond_exit_long' + reverse_for_cond_exit_long = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False)) + directive_name = 'add_for_cond_exit_long' + add_for_cond_exit_long = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False)) + if reverse_for_cond_exit_long: + followup_action = Followup.REVERSE + elif add_for_cond_exit_long: + followup_action = Followup.ADD + else: + followup_action = None + close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG, reason="EXIT CONDS MET", followup=followup_action) + return + + #PROFIT + if curr_price>=goal_price: + #TODO cekat az slope prestane intenzivn erust, necekat az na klesani + #TODO mozna cekat na nejaky signal RSI + #TODO pripadne pokud dosahne TGTBB prodat ihned + max_price_signal = curr_price>=max_price + #OPTIMALIZACE pri stoupajícím angle + if max_price_signal or dontexit_protection_met(state, data, direction=TradeDirection.LONG) is False: + close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG, reason=f"PROFIT or MAXPROFIT REACHED {max_price_signal=}") + return + #pokud je cena horsi, ale byl uz dont exit aktivovany - pak prodavame také + elif state.account_variables[activeTrade.account.name].dont_exit_already_activated == True: + #TODO toto mozna take na direktivu, timto neprodavame pokud porkacuje trend - EXIT_PROT_BOUNCE_IMMEDIATE + # if dontexit_protection_met(state=state, data=data,direction=TradeDirection.LONG) is False: + close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG, reason=f"EXIT PROTECTION BOUNCE {state.account_variables[activeTrade.account.name].dont_exit_already_activated=}") + state.account_variables[activeTrade.account.name].dont_exit_already_activated = False + return + + #FORCED EXIT PRI KONCI DNE + if eod_exit_activated(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG): + close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG, reason="EOD EXIT ACTIVATED") + return \ No newline at end of file diff --git a/v2realbot/strategyblocks/activetrade/helpers.py b/v2realbot/strategyblocks/activetrade/helpers.py index 02fd4da..fefc6d6 100644 --- a/v2realbot/strategyblocks/activetrade/helpers.py +++ b/v2realbot/strategyblocks/activetrade/helpers.py @@ -1,5 +1,5 @@ from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Followup -from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus +from v2realbot.common.model import Trade, TradeDirection, TradeStatus from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, print, safe_get, is_still, is_window_open, eval_cond_dict, crossed_down, crossed_up, crossed, is_pivot, json_serial, pct_diff, create_new_bars, slice_dict_lists from v2realbot.utils.directive_utils import get_conditions_from_configuration from v2realbot.common.model import SLHistory @@ -18,17 +18,20 @@ from traceback import format_exc from v2realbot.strategyblocks.helpers import normalize_tick from v2realbot.strategyblocks.indicators.helpers import evaluate_directive_conditions +#TODO zde dodelat viz nize get get_signal_section_directive a pak pokracovat v close positions #otestuje keyword podminky (napr. reverse_if, nebo exitadd_if) -def keyword_conditions_met(state, data, direction: TradeDirection, keyword: KW, skip_conf_validation: bool = False): +def keyword_conditions_met(state, data, activeTrade: Trade, direction: TradeDirection, keyword: KW, skip_conf_validation: bool = False): action = str(keyword).upper() if direction == TradeDirection.LONG: smer = "long" else: smer = "short" + mother_signal = activeTrade.generated_by + if skip_conf_validation is False: directive_name = "exit_cond_only_on_confirmed" - exit_cond_only_on_confirmed = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False)) + exit_cond_only_on_confirmed = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False)) if exit_cond_only_on_confirmed and data['confirmed'] == 0: state.ilog(lvl=0,e=f"{action} CHECK COND ONLY ON CONFIRMED BAR") @@ -37,7 +40,7 @@ def keyword_conditions_met(state, data, direction: TradeDirection, keyword: KW, #TOTO zatim u REVERSU neresime # #POKUD je nastaven MIN PROFIT, zkontrolujeme ho a az pripadne pustime CONDITIONY # directive_name = "exit_cond_min_profit" - # exit_cond_min_profit = get_override_for_active_trade(directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None)) + # exit_cond_min_profit = get_signal_section_directive(directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None)) # #máme nastavený exit_cond_min_profit # # zjistíme, zda jsme v daném profit a případně nepustíme dál @@ -77,8 +80,6 @@ def keyword_conditions_met(state, data, direction: TradeDirection, keyword: KW, #bereme bud exit condition signalu, ktery activeTrade vygeneroval+ fallback na general state.ilog(lvl=0,e=f"{action} CONDITIONS ENTRY {smer}", conditions=state.vars.conditions[KW.reverse]) - mother_signal = state.vars.activeTrade.generated_by - if mother_signal is not None: cond_dict = state.vars.conditions[keyword][mother_signal][smer] result, conditions_met = evaluate_directive_conditions(state, cond_dict, "OR") @@ -108,12 +109,12 @@ def keyword_conditions_met(state, data, direction: TradeDirection, keyword: KW, #mozna do SL helpers tuto -def insert_SL_history(state): +def insert_SL_history(state, activeTrade: Trade): #insert stoploss history as key sl_history into runner archive extended data - state.extData["sl_history"].append(SLHistory(id=state.vars.activeTrade.id, time=state.time, sl_val=state.vars.activeTrade.stoploss_value)) + state.extData["sl_history"].append(SLHistory(id=activeTrade.id, time=state.time, sl_val=activeTrade.stoploss_value)) -def get_default_sl_value(state, direction: TradeDirection): +def get_default_sl_value(state, signal_name, direction: TradeDirection): if direction == TradeDirection.LONG: smer = "long" @@ -128,15 +129,16 @@ def get_default_sl_value(state, direction: TradeDirection): state.ilog(lvl=1,e="No options for exit in stratvars. Fallback.") return 0.01 directive_name = 'SL_defval_'+str(smer) - val = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(options, directive_name, 0.01)) + val = get_signal_section_directive(state, signal_name, directive_name=directive_name, default_value=safe_get(options, directive_name, 0.01)) return val #funkce pro direktivy, ktere muzou byt overridnute v signal sekci #tato funkce vyhleda signal sekci aktivniho tradu a pokusi se danou direktivu vyhledat tam, #pokud nenajde tak vrati default, ktery byl poskytnut -def get_override_for_active_trade(state, directive_name: str, default_value: str): +#TODO toto predelat na jiny nazev get_overide_for_directive_section (vstup muze byt opuze signal_name) +def get_signal_section_directive(state, signal_name: str, directive_name: str, default_value: str): val = default_value override = "NO" - mother_signal = state.vars.activeTrade.generated_by + mother_signal = signal_name if mother_signal is not None: override = "YES "+mother_signal @@ -145,30 +147,30 @@ def get_override_for_active_trade(state, directive_name: str, default_value: str state.ilog(lvl=0,e=f"{directive_name} OVERRIDE {override} NEWVAL:{val} ORIGINAL:{default_value} {mother_signal}", mother_signal=mother_signal,default_value=default_value) return val -def get_profit_target_price(state, data, direction: TradeDirection): +def get_profit_target_price(state, data, activeTrade, direction: TradeDirection): if direction == TradeDirection.LONG: smer = "long" else: smer = "short" directive_name = "profit" - def_profit_both_directions = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, 0.50)) + def_profit_both_directions = get_signal_section_directive(state, activeTrade=activeTrade, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, 0.50)) #profit pro dany smer directive_name = 'profit_'+str(smer) - def_profit = get_override_for_active_trade(state, directive_name=directive_name, default_value=def_profit_both_directions) + def_profit = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=def_profit_both_directions) #mame v direktivve ticky if isinstance(def_profit, (float, int)): - to_return = get_normalized_profitprice_from_tick(state, data, def_profit, direction) + to_return = get_normalized_profitprice_from_tick(state, data, activeTrade.account, def_profit, direction) #mame v direktive indikator elif isinstance(def_profit, str): to_return = float(value_or_indicator(state, def_profit)) #min profit (ochrana extremnich hodnot indikatoru) directive_name = 'profit_min_ind_tick_value' - profit_min_ind_tick_value = get_override_for_active_trade(state, directive_name=directive_name, default_value=def_profit_both_directions) - profit_min_ind_price_value = get_normalized_profitprice_from_tick(state, data, profit_min_ind_tick_value, direction) + profit_min_ind_tick_value = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=def_profit_both_directions) + profit_min_ind_price_value = get_normalized_profitprice_from_tick(state, data, activeTrade.account, profit_min_ind_tick_value, direction) #ochrana pri nastaveni profitu prilis nizko if direction == TradeDirection.LONG and to_return < profit_min_ind_price_value or direction == TradeDirection.SHORT and to_return > profit_min_ind_price_value: @@ -179,28 +181,32 @@ def get_profit_target_price(state, data, direction: TradeDirection): return to_return ##based on tick a direction, returns normalized prfoit price (LONG = avgp(nebo currprice)+norm.tick, SHORT=avgp(or currprice)-norm.tick) -def get_normalized_profitprice_from_tick(state, data, tick, direction: TradeDirection): +def get_normalized_profitprice_from_tick(state, data, tick, account, direction: TradeDirection): + avgp = state.account_variables[account.name].avgp normalized_tick = normalize_tick(state, data, float(tick)) - base_price = state.avgp if state.avgp != 0 else data["close"] + base_price = avgp if avgp != 0 else data["close"] returned_price = price2dec(float(base_price)+normalized_tick,3) if direction == TradeDirection.LONG else price2dec(float(base_price)-normalized_tick,3) state.ilog(lvl=0,e=f"NORMALIZED TICK {tick=} {normalized_tick=} NORM.PRICE {returned_price}") return returned_price -def get_max_profit_price(state, data, direction: TradeDirection): +def get_max_profit_price(state, activeTrade: Trade, data, direction: TradeDirection): if direction == TradeDirection.LONG: smer = "long" else: smer = "short" directive_name = "max_profit" - max_profit_both_directions = get_override_for_active_trade(state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, 0.35)) + max_profit_both_directions = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, 0.35)) + + avgp = state.account_variables[activeTrade.account.name].avgp + positions = state.account_variables[activeTrade.account.name].positions #max profit pro dany smer, s fallbackem na bez smeru directive_name = 'max_profit_'+str(smer) - max_profit = get_override_for_active_trade(state, directive_name=directive_name, default_value=max_profit_both_directions) + max_profit = get_signal_section_directive(state, signal_name=activeTrade.generated_by, directive_name=directive_name, default_value=max_profit_both_directions) normalized_max_profit = normalize_tick(state,data,float(max_profit)) state.ilog(lvl=0,e=f"MAX PROFIT {max_profit=} {normalized_max_profit=}") - return price2dec(float(state.avgp)+normalized_max_profit,3) if int(state.positions) > 0 else price2dec(float(state.avgp)-normalized_max_profit,3) + return price2dec(float(avgp)+normalized_max_profit,3) if int(positions) > 0 else price2dec(float(avgp)-normalized_max_profit,3) diff --git a/v2realbot/strategyblocks/activetrade/sl/optimsl.py b/v2realbot/strategyblocks/activetrade/sl/optimsl.py index 1d550da..f386fea 100644 --- a/v2realbot/strategyblocks/activetrade/sl/optimsl.py +++ b/v2realbot/strategyblocks/activetrade/sl/optimsl.py @@ -1,8 +1,8 @@ import numpy as np -from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus +from v2realbot.common.model import Trade, TradeDirection, TradeStatus from typing import Tuple from copy import deepcopy -from v2realbot.strategyblocks.activetrade.helpers import get_override_for_active_trade +from v2realbot.strategyblocks.activetrade.helpers import get_signal_section_directive from v2realbot.utils.utils import safe_get # FIBONACCI PRO PROFIT A SL @@ -49,8 +49,8 @@ class SLOptimizer: # self.exit_levels = self.init_exit_levels # self.exit_sizes = self.init_exit_sizes - def get_trade_details(self, state): - trade: Trade = state.vars.activeTrade + def get_trade_details(self, state, activeTrade): + trade: Trade = activeTrade #jde o novy trade - resetujeme levely if trade.id != self.last_trade: #inicializujeme a vymazeme pripadne puvodni @@ -58,14 +58,14 @@ class SLOptimizer: return None, None self.last_trade = trade.id #return cost_price, sl_price - return state.avgp, trade.stoploss_value + return state.account_variables[trade.account.name].avgp, trade.stoploss_value def initialize_levels(self, state): directive_name = 'SL_opt_exit_levels_'+str(self.direction.value) - SL_opt_exit_levels = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None)) + SL_opt_exit_levels = get_signal_section_directive(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None)) directive_name = 'SL_opt_exit_sizes_'+str(self.direction.value) - SL_opt_exit_sizes = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None)) + SL_opt_exit_sizes = get_signal_section_directive(state=state, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None)) if SL_opt_exit_levels is None or SL_opt_exit_sizes is None: #print("no directives found: SL_opt_exit_levels/SL_opt_exit_sizes") @@ -83,11 +83,11 @@ class SLOptimizer: print(f"new levels initialized {self.exit_levels=} {self.exit_sizes=}") return True - def get_initial_abs_levels(self, state): + def get_initial_abs_levels(self, state, activeTrade): """ Returns price levels corresponding to initial setting of exit_levels """ - cost_price, sl_price = self.get_trade_details(state) + cost_price, sl_price = self.get_trade_details(state, activeTrade) if cost_price is None or sl_price is None: return [] curr_sl_distance = np.abs(cost_price - sl_price) @@ -96,11 +96,11 @@ class SLOptimizer: else: return [cost_price - exit_level * curr_sl_distance for exit_level in self.init_exit_levels] - def get_remaining_abs_levels(self, state): + def get_remaining_abs_levels(self, state, activeTrade): """ Returns price levels corresponding to remaing exit_levels for current trade """ - cost_price, sl_price = self.get_trade_details(state) + cost_price, sl_price = self.get_trade_details(state, activeTrade) if cost_price is None or sl_price is None: return [] curr_sl_distance = np.abs(cost_price - sl_price) @@ -109,7 +109,7 @@ class SLOptimizer: else: return [cost_price - exit_level * curr_sl_distance for exit_level in self.exit_levels] - def eval_position(self, state, data) -> Tuple[float, float]: + def eval_position(self, state, data, activeTrade) -> Tuple[float, float]: """Evaluates optimalization for current position and returns if the given level was met and how to adjust exit position. """ diff --git a/v2realbot/strategyblocks/activetrade/sl/trailsl.py b/v2realbot/strategyblocks/activetrade/sl/trailsl.py index ea510e3..2264e83 100644 --- a/v2realbot/strategyblocks/activetrade/sl/trailsl.py +++ b/v2realbot/strategyblocks/activetrade/sl/trailsl.py @@ -1,13 +1,12 @@ from v2realbot.strategy.base import StrategyState -from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus -from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, print, safe_get +from v2realbot.common.model import Trade, TradeDirection, TradeStatus +from v2realbot.utils.utils import gaka, isrising, isfalling,zoneNY, price2dec, print, safe_get #from icecream import install, ic from rich import print as printanyway from threading import Event import os from traceback import format_exc -from v2realbot.strategyblocks.activetrade.helpers import get_override_for_active_trade, normalize_tick, insert_SL_history - +from v2realbot.strategyblocks.activetrade.helpers import get_signal_section_directive, normalize_tick, insert_SL_history #pokud se cena posouva nasim smerem olespon o (0.05) nad (SL + 0.09val), posuneme SL o offset #+ varianta - skoncit breakeven @@ -25,68 +24,75 @@ from v2realbot.strategyblocks.activetrade.helpers import get_override_for_active # SL_trailing_stop_at_breakeven_short = true # SL_trailing_stop_at_breakeven_long = true -def trail_SL_management(state: StrategyState, data): - if int(state.positions) != 0 and float(state.avgp)>0 and state.vars.pending is None: +def trail_SL_management(state: StrategyState, accountsWithActiveTrade, data): + #iterate over accountsWithActiveTrade + for account_str, activeTrade in accountsWithActiveTrade.items(): + positions = state.account_variables[account_str].positions + avgp = state.account_variables[account_str].avgp + pending = state.account_variables[account_str].pending + signal_name = activeTrade.generated_by + last_entry_index = state.account_variables[account_str].last_entry_index + if int(positions) != 0 and float(avgp)>0 and pending is None: - if int(state.positions) < 0: - direction = TradeDirection.SHORT - smer = "short" - else: - direction = TradeDirection.LONG - smer = "long" - - # zatim nastaveni SL plati pro vsechny - do budoucna per signal - pridat sekci + if int(positions) < 0: + direction = TradeDirection.SHORT + smer = "short" + else: + direction = TradeDirection.LONG + smer = "long" + + # zatim nastaveni SL plati pro vsechny - do budoucna per signal - pridat sekci - options = safe_get(state.vars, 'exit', None) - if options is None: - state.ilog(lvl=1,e="Trail SL. No options for exit conditions in stratvars.") - return - - directive_name = 'SL_trailing_enabled_'+str(smer) - sl_trailing_enabled = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(options, directive_name, False)) - - - #SL_trailing_protection_window_short - directive_name = 'SL_trailing_protection_window_'+str(smer) - SL_trailing_protection_window = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(options, directive_name, 0)) - index_to_compare = int(state.vars.last_in_index)+int(SL_trailing_protection_window) - if index_to_compare > int(data["index"]): - state.ilog(lvl=1,e=f"SL trail PROTECTION WINDOW {SL_trailing_protection_window} - TOO SOON", currindex=data["index"], index_to_compare=index_to_compare, last_in_index=state.vars.last_in_index) - return - - - - if sl_trailing_enabled is True: - directive_name = 'SL_trailing_stop_at_breakeven_'+str(smer) - stop_breakeven = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(options, directive_name, False)) - directive_name = 'SL_defval_'+str(smer) - def_SL = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(options, directive_name, 0.01)) - directive_name = "SL_trailing_offset_"+str(smer) - offset = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(options, directive_name, 0.01)) - directive_name = "SL_trailing_step_"+str(smer) - step = get_override_for_active_trade(state=state, directive_name=directive_name, default_value=safe_get(options, directive_name, offset)) - - #pokud je pozadovan trail jen do breakeven a uz prekroceno - if (direction == TradeDirection.LONG and stop_breakeven and state.vars.activeTrade.stoploss_value >= float(state.avgp)) or (direction == TradeDirection.SHORT and stop_breakeven and state.vars.activeTrade.stoploss_value <= float(state.avgp)): - state.ilog(lvl=1,e=f"SL trail STOP at breakeven {str(smer)} SL:{state.vars.activeTrade.stoploss_value} UNCHANGED", stop_breakeven=stop_breakeven) + options = safe_get(state.vars, 'exit', None) + if options is None: + state.ilog(lvl=1,e="Trail SL. No options for exit conditions in stratvars.") return - #Aktivace SL pokud vystoupa na "offset", a nasledne posunuti o "step" + directive_name = 'SL_trailing_enabled_'+str(smer) + sl_trailing_enabled = get_signal_section_directive(state=state, signal_name=signal_name, directive_name=directive_name, default_value=safe_get(options, directive_name, False)) + - offset_normalized = normalize_tick(state, data, offset) #to ticks and from options - step_normalized = normalize_tick(state, data, step) - def_SL_normalized = normalize_tick(state, data, def_SL) - if direction == TradeDirection.LONG: - move_SL_threshold = state.vars.activeTrade.stoploss_value + offset_normalized + def_SL_normalized - state.ilog(lvl=1,e=f"SL TRAIL EVAL {smer} SL:{round(state.vars.activeTrade.stoploss_value,3)} TRAILGOAL:{move_SL_threshold}", def_SL=def_SL, offset=offset, offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized) - if (move_SL_threshold) < data['close']: - state.vars.activeTrade.stoploss_value += step_normalized - insert_SL_history(state) - state.ilog(lvl=1,e=f"SL TRAIL TH {smer} reached {move_SL_threshold} SL moved to {state.vars.activeTrade.stoploss_value}", offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized) - elif direction == TradeDirection.SHORT: - move_SL_threshold = state.vars.activeTrade.stoploss_value - offset_normalized - def_SL_normalized - state.ilog(lvl=0,e=f"SL TRAIL EVAL {smer} SL:{round(state.vars.activeTrade.stoploss_value,3)} TRAILGOAL:{move_SL_threshold}", def_SL=def_SL, offset=offset, offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized) - if (move_SL_threshold) > data['close']: - state.vars.activeTrade.stoploss_value -= step_normalized - insert_SL_history(state) - state.ilog(lvl=1,e=f"SL TRAIL GOAL {smer} reached {move_SL_threshold} SL moved to {state.vars.activeTrade.stoploss_value}", offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized) + #SL_trailing_protection_window_short + directive_name = 'SL_trailing_protection_window_'+str(smer) + SL_trailing_protection_window = get_signal_section_directive(state=state, signal_name=signal_name, directive_name=directive_name, default_value=safe_get(options, directive_name, 0)) + index_to_compare = int(last_entry_index)+int(SL_trailing_protection_window) + if index_to_compare > int(data["index"]): + state.ilog(lvl=1,e=f"SL trail PROTECTION WINDOW {SL_trailing_protection_window} - TOO SOON", currindex=data["index"], index_to_compare=index_to_compare, last_entry_index=last_entry_index) + return + + + + if sl_trailing_enabled is True: + directive_name = 'SL_trailing_stop_at_breakeven_'+str(smer) + stop_breakeven = get_signal_section_directive(state=state, signal_name=signal_name, directive_name=directive_name, default_value=safe_get(options, directive_name, False)) + directive_name = 'SL_defval_'+str(smer) + def_SL = get_signal_section_directive(state=state, signal_name=signal_name, directive_name=directive_name, default_value=safe_get(options, directive_name, 0.01)) + directive_name = "SL_trailing_offset_"+str(smer) + offset = get_signal_section_directive(state=state, signal_name=signal_name, directive_name=directive_name, default_value=safe_get(options, directive_name, 0.01)) + directive_name = "SL_trailing_step_"+str(smer) + step = get_signal_section_directive(state=state, signal_name=signal_name, directive_name=directive_name, default_value=safe_get(options, directive_name, offset)) + + #pokud je pozadovan trail jen do breakeven a uz prekroceno + if (direction == TradeDirection.LONG and stop_breakeven and activeTrade.stoploss_value >= float(avgp)) or (direction == TradeDirection.SHORT and stop_breakeven and activeTrade.stoploss_value <= float(avgp)): + state.ilog(lvl=1,e=f"SL trail STOP at breakeven {str(smer)} SL:{activeTrade.stoploss_value} UNCHANGED", stop_breakeven=stop_breakeven) + return + + #Aktivace SL pokud vystoupa na "offset", a nasledne posunuti o "step" + + offset_normalized = normalize_tick(state, data, offset) #to ticks and from options + step_normalized = normalize_tick(state, data, step) + def_SL_normalized = normalize_tick(state, data, def_SL) + if direction == TradeDirection.LONG: + move_SL_threshold = activeTrade.stoploss_value + offset_normalized + def_SL_normalized + state.ilog(lvl=1,e=f"SL TRAIL EVAL {smer} SL:{round(activeTrade.stoploss_value,3)} TRAILGOAL:{move_SL_threshold}", def_SL=def_SL, offset=offset, offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized) + if (move_SL_threshold) < data['close']: + activeTrade.stoploss_value += step_normalized + insert_SL_history(state) + state.ilog(lvl=1,e=f"SL TRAIL TH {smer} reached {move_SL_threshold} SL moved to {activeTrade.stoploss_value}", offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized) + elif direction == TradeDirection.SHORT: + move_SL_threshold = activeTrade.stoploss_value - offset_normalized - def_SL_normalized + state.ilog(lvl=0,e=f"SL TRAIL EVAL {smer} SL:{round(activeTrade.stoploss_value,3)} TRAILGOAL:{move_SL_threshold}", def_SL=def_SL, offset=offset, offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized) + if (move_SL_threshold) > data['close']: + activeTrade.stoploss_value -= step_normalized + insert_SL_history(state) + state.ilog(lvl=1,e=f"SL TRAIL GOAL {smer} reached {move_SL_threshold} SL moved to {activeTrade.stoploss_value}", offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized) diff --git a/v2realbot/strategyblocks/indicators/indicators_hub.py b/v2realbot/strategyblocks/indicators/indicators_hub.py index f326997..78d5134 100644 --- a/v2realbot/strategyblocks/indicators/indicators_hub.py +++ b/v2realbot/strategyblocks/indicators/indicators_hub.py @@ -55,7 +55,11 @@ def populate_all_indicators(data, state: StrategyState): #TODO tento lof patri spis do nextu classic SL - je poplatny typu stratefie #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} GP:{state.vars.activeTrade.goal_price if state.vars.activeTrade is not None else None} profit:{round(float(state.profit),2)} profit_rel:{round(np.sum(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=transform_data(state.vars.activeTrade, json_serial), prescribedTrades=transform_data(state.vars.prescribedTrades, json_serial), pending=str(state.vars.pending)) + positions = state.account_variables[state.account].positions + avgp = state.account_variables[state.account].avgp + #state.ilog(lvl=1,e="ENTRY", msg=f"LP:{lp} P:{positions}/{round(float(avgp),3)} SL:{state.vars.activeTrade.stoploss_value if state.vars.activeTrade is not None else None} GP:{state.vars.activeTrade.goal_price if state.vars.activeTrade is not None else None} profit:{round(float(state.profit),2)} profit_rel:{round(np.sum(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=transform_data(state.vars.activeTrade, json_serial), prescribedTrades=transform_data(state.vars.prescribedTrades, json_serial), pending=str(state.vars.pending)) + + state.ilog(lvl=1,e="ENTRY", msg=f"LP:{lp} ", accountVars=transform_data(state.account_variables, json_serial), prescribedTrades=transform_data(state.vars.prescribedTrades, json_serial)) #kroky pro CONFIRMED BAR only if conf_bar == 1: diff --git a/v2realbot/strategyblocks/indicators/slopeLP.py b/v2realbot/strategyblocks/indicators/slopeLP.py index 41a846d..1c0c0cc 100644 --- a/v2realbot/strategyblocks/indicators/slopeLP.py +++ b/v2realbot/strategyblocks/indicators/slopeLP.py @@ -46,6 +46,10 @@ def populate_dynamic_slopeLP_indicator(data, state: StrategyState, name): #typ leveho bodu [lastbuy - cena posledniho nakupu, baropen - cena otevreni baru] leftpoint = safe_get(options, 'leftpoint', "lastbuy") + #REFACTOR multiaccount + #avgp bereme z primarni accountu (state.account) + avgp = state.account_variables[state.account].avgp + #lookback has to be even if lookback_offset % 2 != 0: lookback_offset += 1 @@ -65,8 +69,8 @@ def populate_dynamic_slopeLP_indicator(data, state: StrategyState, name): #pokud mame aktivni pozice, nastavime lookbackprice a time podle posledniho tradu #pokud se ale dlouho nenakupuje (uplynulo od posledniho nakupu vic nez back_to_standard_after baru), tak se vracime k prumeru - if state.avgp > 0 and state.bars.index[-1] < int(state.vars.last_buy_index)+back_to_standard_after: - lb_index = -1 - (state.bars.index[-1] - int(state.vars.last_buy_index)) + if avgp > 0 and state.bars.index[-1] < int(state.vars.last_entry_index)+back_to_standard_after: + lb_index = -1 - (state.bars.index[-1] - int(state.vars.last_entry_index)) lookbackprice = state.bars.vwap[lb_index] state.ilog(lvl=0,e=f"IND {name} slope {leftpoint}- LEFT POINT OVERRIDE bereme ajko cenu lastbuy {lookbackprice=} {lookbacktime=} {lb_index=}") else: diff --git a/v2realbot/strategyblocks/inits/init_directives.py b/v2realbot/strategyblocks/inits/init_directives.py index bb3dc1b..cffe3ff 100644 --- a/v2realbot/strategyblocks/inits/init_directives.py +++ b/v2realbot/strategyblocks/inits/init_directives.py @@ -1,7 +1,7 @@ from v2realbot.strategy.base import StrategyState from v2realbot.strategy.StrategyOrderLimitVykladaciNormalizedMYSELL import StrategyOrderLimitVykladaciNormalizedMYSELL from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Followup -from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus +from v2realbot.common.model import Trade, TradeDirection, TradeStatus from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, print, safe_get, is_still, is_window_open, eval_cond_dict, crossed_down, crossed_up, crossed, is_pivot, json_serial, pct_diff, create_new_bars, slice_dict_lists from v2realbot.utils.directive_utils import get_conditions_from_configuration from v2realbot.common.model import SLHistory diff --git a/v2realbot/strategyblocks/inits/init_indicators.py b/v2realbot/strategyblocks/inits/init_indicators.py index 7d66f64..9fdc0c1 100644 --- a/v2realbot/strategyblocks/inits/init_indicators.py +++ b/v2realbot/strategyblocks/inits/init_indicators.py @@ -1,7 +1,7 @@ from v2realbot.strategy.base import StrategyState from v2realbot.strategy.StrategyOrderLimitVykladaciNormalizedMYSELL import StrategyOrderLimitVykladaciNormalizedMYSELL from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Followup -from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus +from v2realbot.common.model import Trade, TradeDirection, TradeStatus from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, print, safe_get, is_still, is_window_open, eval_cond_dict, crossed_down, crossed_up, crossed, is_pivot, json_serial, pct_diff, create_new_bars, slice_dict_lists from v2realbot.utils.directive_utils import get_conditions_from_configuration #import mlroom.utils.mlutils as ml diff --git a/v2realbot/strategyblocks/newtrade/conditions.py b/v2realbot/strategyblocks/newtrade/conditions.py index 44d7aad..b121436 100644 --- a/v2realbot/strategyblocks/newtrade/conditions.py +++ b/v2realbot/strategyblocks/newtrade/conditions.py @@ -1,7 +1,7 @@ from v2realbot.strategy.base import StrategyState from v2realbot.strategy.StrategyOrderLimitVykladaciNormalizedMYSELL import StrategyOrderLimitVykladaciNormalizedMYSELL from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Followup -from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus +from v2realbot.common.model import Trade, TradeDirection, TradeStatus from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, print, safe_get, is_still, is_window_open, eval_cond_dict, crossed_down, crossed_up, crossed, is_pivot, json_serial, pct_diff, create_new_bars, slice_dict_lists from v2realbot.utils.directive_utils import get_conditions_from_configuration from v2realbot.common.model import SLHistory diff --git a/v2realbot/strategyblocks/newtrade/prescribedtrades.py b/v2realbot/strategyblocks/newtrade/prescribedtrades.py index 99752e2..ae66360 100644 --- a/v2realbot/strategyblocks/newtrade/prescribedtrades.py +++ b/v2realbot/strategyblocks/newtrade/prescribedtrades.py @@ -1,6 +1,6 @@ from v2realbot.strategy.base import StrategyState -from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus -from v2realbot.utils.utils import zoneNY, json_serial,transform_data +from v2realbot.common.model import TradeDirection, TradeStatus +from v2realbot.utils.utils import zoneNY, json_serial,transform_data, gaka from datetime import datetime #import random import orjson @@ -10,97 +10,108 @@ from v2realbot.strategyblocks.indicators.helpers import value_or_indicator #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 + + #for multiaccount setup we check if there is active trade for each account - if state.vars.activeTrade is not None or len(state.vars.prescribedTrades) == 0: + if len(state.vars.prescribedTrades) == 0 : return + + accountsWithNoActiveTrade = gaka(state.account_variables, "activeTrade", None, lambda x: x is None) + + if len(accountsWithNoActiveTrade.values()) == 0: + print("active trades on all accounts") + + #returns true if all values are not None + #all(v is not None for v in d.keys()) + #evaluate long (price/market) + #support multiaccount trades state.ilog(lvl=1,e="evaluating prescr trades", trades=transform_data(state.vars.prescribedTrades, json_serial)) for trade in state.vars.prescribedTrades: + if trade.account.name not in accountsWithNoActiveTrade.keys() or state.account_variables[trade.account.name].pending is not None: #availability or pending + continue if trade.status == TradeStatus.READY and trade.direction == TradeDirection.LONG and (trade.entry_price is None or trade.entry_price >= data['close']): trade.status = TradeStatus.ACTIVATED trade.last_update = datetime.fromtimestamp(state.time).astimezone(zoneNY) state.ilog(lvl=1,e=f"evaluated LONG", trade=transform_data(trade, json_serial), prescrTrades=transform_data(state.vars.prescribedTrades, json_serial)) - state.vars.activeTrade = trade - state.vars.last_buy_index = data["index"] - state.vars.last_in_index = data["index"] - break + execute_trade(state, data, trade) #TBD ERROR HANDLING + del accountsWithNoActiveTrade[trade.account.name] #to avoid other entries on the same account + continue #evaluate shorts - if not state.vars.activeTrade: - for trade in state.vars.prescribedTrades: - if trade.status == TradeStatus.READY and trade.direction == TradeDirection.SHORT and (trade.entry_price is None or trade.entry_price <= data['close']): - state.ilog(lvl=1,e=f"evaluaed SHORT", trade=transform_data(trade, json_serial), prescrTrades=transform_data(state.vars.prescribedTrades, json_serial)) - trade.status = TradeStatus.ACTIVATED - trade.last_update = datetime.fromtimestamp(state.time).astimezone(zoneNY) - state.vars.activeTrade = trade - state.vars.last_buy_index = data["index"] - state.vars.last_in_index = data["index"] - break + if trade.status == TradeStatus.READY and trade.direction == TradeDirection.SHORT and (trade.entry_price is None or trade.entry_price <= data['close']): + state.ilog(lvl=1,e=f"evaluaed SHORT", trade=transform_data(trade, json_serial), prescrTrades=transform_data(state.vars.prescribedTrades, json_serial)) + trade.status = TradeStatus.ACTIVATED + trade.last_update = datetime.fromtimestamp(state.time).astimezone(zoneNY) + execute_trade(state, data, trade) #TBD ERROR HANDLING + del accountsWithNoActiveTrade[trade.account.name] #to avoid other entries on the same account + continue - #odeslani ORDER + NASTAVENI STOPLOSS (zatim hardcoded) - if state.vars.activeTrade: - if state.vars.activeTrade.direction == TradeDirection.LONG: - state.ilog(lvl=1,e="odesilame LONG ORDER", trade=transform_data(state.vars.activeTrade, json_serial)) - if state.vars.activeTrade.size is not None: - size = state.vars.activeTrade.size - else: - size = state.vars.chunk - res = state.buy(size=size) - if isinstance(res, int) and res < 0: - raise Exception(f"error in required operation LONG {res}") - #defaultni goal price pripadne nastavujeme az v notifikaci + #TODO konzolidovat nize na spolecny kod pro short a long +#odeslani ORDER + NASTAVENI STOPLOSS (zatim hardcoded) +#TODO doplnit error management +def execute_trade(state, data, trade): + if trade.direction == TradeDirection.LONG: + state.ilog(lvl=1,e="odesilame LONG ORDER", trade=transform_data(trade, json_serial)) + size = trade.size if trade.size is not None else state.vars.chunk + res = state.buy(size=size, account=trade.account) + #TODO ukládáme někam ID objednávky? už zde je vráceno v res + #TODO error handling + if isinstance(res, int) and res < 0: + raise Exception(f"error in required operation LONG {res}") + #TODO error handling + #defaultni goal price pripadne nastavujeme az v notifikaci + state.account_variables[trade.account.name].activeTrade = trade - #TODO nastaveni SL az do notifikace, kdy je známá - #pokud neni nastaveno SL v prescribe, tak nastavuji default dle stratvars - if state.vars.activeTrade.stoploss_value is None: - sl_defvalue = get_default_sl_value(state, direction=state.vars.activeTrade.direction) + #TODO nastaveni SL az do notifikace, kdy je známá + #pokud neni nastaveno SL v prescribe, tak nastavuji default dle stratvars + if trade.stoploss_value is None: + sl_defvalue = get_default_sl_value(state=state, signal_name=trade.generated_by, direction=trade.direction) - if isinstance(sl_defvalue, (float, int)): - #normalizuji dle aktualni ceny - sl_defvalue_normalized = normalize_tick(state, data,sl_defvalue) - state.vars.activeTrade.stoploss_value = float(data['close']) - sl_defvalue_normalized - state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue}, priced normalized: {sl_defvalue_normalized} price: {state.vars.activeTrade.stoploss_value }") - elif isinstance(sl_defvalue, str): - #from indicator - ind = sl_defvalue - sl_defvalue_abs = float(value_or_indicator(state, sl_defvalue)) - if sl_defvalue_abs >= float(data['close']): - raise Exception(f"error in stoploss {ind} {sl_defvalue_abs} >= curr price") - state.vars.activeTrade.stoploss_value = sl_defvalue_abs - state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue_abs} dle indikatoru {ind}") - insert_SL_history(state) - state.vars.pending = state.vars.activeTrade.id - elif state.vars.activeTrade.direction == TradeDirection.SHORT: - state.ilog(lvl=1,e="odesilame SHORT ORDER", trade=transform_data(state.vars.activeTrade, json_serial)) - if state.vars.activeTrade.size is not None: - size = state.vars.activeTrade.size - else: - size = state.vars.chunk - res = state.sell(size=size) - if isinstance(res, int) and res < 0: - print(f"error in required operation SHORT {res}") - raise Exception(f"error in required operation SHORT {res}") - #defaultní goalprice nastavujeme az v notifikaci + if isinstance(sl_defvalue, (float, int)): + #normalizuji dle aktualni ceny + sl_defvalue_normalized = normalize_tick(state, data,sl_defvalue) + state.account_variables[trade.account.name].activeTrade.stoploss_value = float(data['close']) - sl_defvalue_normalized + state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue}, priced normalized: {sl_defvalue_normalized} price: {state.account_variables[trade.account.name].activeTrade.stoploss_value }") + elif isinstance(sl_defvalue, str): + #from indicator + ind = sl_defvalue + sl_defvalue_abs = float(value_or_indicator(state, sl_defvalue)) + if sl_defvalue_abs >= float(data['close']): + raise Exception(f"error in stoploss {ind} {sl_defvalue_abs} >= curr price") + state.account_variables[trade.account.name].activeTrade.stoploss_value = sl_defvalue_abs + state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue_abs} dle indikatoru {ind}") + insert_SL_history(state, state.account_variables[trade.account.name].activeTrade) + elif trade.direction == TradeDirection.SHORT: + state.ilog(lvl=1,e="odesilame SHORT ORDER", trade=transform_data(trade, json_serial)) + size = trade.size if trade.size is not None else state.vars.chunk + res = state.sell(size=size, account=trade.account) + if isinstance(res, int) and res < 0: + print(f"error in required operation SHORT {res}") + raise Exception(f"error in required operation SHORT {res}") + #defaultní goalprice nastavujeme az v notifikaci - #pokud neni nastaveno SL v prescribe, tak nastavuji default dle stratvars - if state.vars.activeTrade.stoploss_value is None: - sl_defvalue = get_default_sl_value(state, direction=state.vars.activeTrade.direction) + state.account_variables[trade.account.name].activeTrade = trade + #pokud neni nastaveno SL v prescribe, tak nastavuji default dle stratvars + if trade.stoploss_value is None: + sl_defvalue = get_default_sl_value(state, signal_name=trade.generated_by,direction=trade.direction) - if isinstance(sl_defvalue, (float, int)): - #normalizuji dle aktualni ceny - sl_defvalue_normalized = normalize_tick(state, data,sl_defvalue) - state.vars.activeTrade.stoploss_value = float(data['close']) + sl_defvalue_normalized - state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue}, priced normalized: {sl_defvalue_normalized} price: {state.vars.activeTrade.stoploss_value }") - elif isinstance(sl_defvalue, str): - #from indicator - ind = sl_defvalue - sl_defvalue_abs = float(value_or_indicator(state, sl_defvalue)) - if sl_defvalue_abs <= float(data['close']): - raise Exception(f"error in stoploss {ind} {sl_defvalue_abs} <= curr price") - state.vars.activeTrade.stoploss_value = sl_defvalue_abs - state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue_abs} dle indikatoru {ind}") - insert_SL_history(state) - state.vars.pending = state.vars.activeTrade.id - else: - state.ilog(lvl=1,e="unknow direction") - state.vars.activeTrade = None + if isinstance(sl_defvalue, (float, int)): + #normalizuji dle aktualni ceny + sl_defvalue_normalized = normalize_tick(state, data,sl_defvalue) + state.account_variables[trade.account.name].activeTrade.stoploss_value = float(data['close']) + sl_defvalue_normalized + state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue}, priced normalized: {sl_defvalue_normalized} price: {state.account_variables[trade.account.name].activeTrade.stoploss_value }") + elif isinstance(sl_defvalue, str): + #from indicator + ind = sl_defvalue + sl_defvalue_abs = float(value_or_indicator(state, sl_defvalue)) + if sl_defvalue_abs <= float(data['close']): + raise Exception(f"error in stoploss {ind} {sl_defvalue_abs} <= curr price") + state.account_variables[trade.account.name].activeTrade.stoploss_value = sl_defvalue_abs + state.ilog(lvl=1,e=f"Nastaveno SL na {sl_defvalue_abs} dle indikatoru {ind}") + insert_SL_history(state, state.account_variables[trade.account.name].activeTrade) + + state.account_variables[trade.account.name].pending = trade.id + state.account_variables[trade.account.name].activeTrade = trade + state.account_variables[trade.account.name].last_entry_index =data["index"] #last_entry_index per account + state.vars.last_entry_index = data["index"] #spolecne pro vsechny accounty \ No newline at end of file diff --git a/v2realbot/strategyblocks/newtrade/signals.py b/v2realbot/strategyblocks/newtrade/signals.py index 3ca9d74..4cc4ef5 100644 --- a/v2realbot/strategyblocks/newtrade/signals.py +++ b/v2realbot/strategyblocks/newtrade/signals.py @@ -1,6 +1,6 @@ from v2realbot.strategy.base import StrategyState -from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus -from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, print, safe_get +from v2realbot.common.model import Trade, TradeDirection, TradeStatus +from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, print, safe_get, gaka from v2realbot.config import KW from uuid import uuid4 from datetime import datetime @@ -31,6 +31,12 @@ def signal_search(state: StrategyState, data): # slope10.out_short_if_above = 0 # ema.AND.short_if_below = 28 + accountsWithNoActiveTrade = gaka(state.account_variables, "activeTrade", None, lambda x: x is None) + + if len(accountsWithNoActiveTrade.values()) == 0: + print("active trades on all accounts") + return + for signalname, signalsettings in state.vars.signals.items(): execute_signal_generator(state, data, signalname) @@ -38,14 +44,21 @@ def signal_search(state: StrategyState, data): # pokud je s cenou ceka se na cenu, pokud immmediate tak se hned provede # to vse za predpokladu, ze neni aktivni trade -def execute_signal_generator(state, data, name): +def execute_signal_generator(state: StrategyState, data, name): state.ilog(lvl=1,e=f"SIGNAL SEARCH for {name}", cond_go=state.vars.conditions[KW.go][name], cond_dontgo=state.vars.conditions[KW.dont_go][name], cond_activate=state.vars.conditions[KW.activate][name] ) options = safe_get(state.vars.signals, name, None) + #add account from stratvars (if there) or default to self.state.account + if options is None: state.ilog(lvl=1,e=f"No options for {name} in stratvars") return + #get account of the signal, fallback to default + account = safe_get(options, "account", state.account) + account_long = safe_get(options, "account_long", account) + account_short = safe_get(options, "account_short", account) + if common_go_preconditions_check(state, data, signalname=name, options=options) is False: return @@ -71,9 +84,11 @@ def execute_signal_generator(state, data, name): state.ilog(lvl=1,e=f"{name} SHORT DISABLED") if long_enabled is False: state.ilog(lvl=1,e=f"{name} LONG DISABLED") - if long_enabled and go_conditions_met(state, data,signalname=name, direction=TradeDirection.LONG): + #predkontroloa zda neni pending na accountu nebo aktivni trade + if state.account_variables[account_long].pending is None and state.account_variables[account_long].activeTrade is None and long_enabled and go_conditions_met(state, data,signalname=name, direction=TradeDirection.LONG): multiplier = get_multiplier(state, data, options, TradeDirection.LONG) state.vars.prescribedTrades.append(Trade( + account = account_long, id=uuid4(), last_update=datetime.fromtimestamp(state.time).astimezone(zoneNY), status=TradeStatus.READY, @@ -83,9 +98,10 @@ def execute_signal_generator(state, data, name): direction=TradeDirection.LONG, entry_price=None, stoploss_value = None)) - elif short_enabled and go_conditions_met(state, data, signalname=name, direction=TradeDirection.SHORT): + elif state.account_variables[account_short].pending is None and state.account_variables[account_short].activeTrade is None and short_enabled and go_conditions_met(state, data, signalname=name, direction=TradeDirection.SHORT): multiplier = get_multiplier(state, data, options, TradeDirection.SHORT) state.vars.prescribedTrades.append(Trade( + account=account_short, id=uuid4(), last_update=datetime.fromtimestamp(state.time).astimezone(zoneNY), status=TradeStatus.READY, diff --git a/v2realbot/strategyblocks/newtrade/sizing.py b/v2realbot/strategyblocks/newtrade/sizing.py index 2d81962..cdb434b 100644 --- a/v2realbot/strategyblocks/newtrade/sizing.py +++ b/v2realbot/strategyblocks/newtrade/sizing.py @@ -1,5 +1,5 @@ from v2realbot.strategy.base import StrategyState -from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus +from v2realbot.common.model import Trade, TradeDirection, TradeStatus import v2realbot.utils.utils as utls from v2realbot.config import KW from uuid import uuid4 @@ -99,7 +99,8 @@ def get_multiplier(state: StrategyState, data, signaloptions: dict, direction: T if probe_enabled: #zatim pouze probe number 1 natvrdo, tzn. nesmi byt trade pro aktivace - if state.vars.last_in_index is None: + #zatim funguje pouze pro primarni + if state.account_variables[state.account].last_entry_index is None: #probe_number = utls.safe_get(options, "probe_number",1) probe_size = float(utls.safe_get(options, "probe_size", 0.1)) state.ilog(lvl=1,e=f"SIZER - PROBE - setting multiplier to {probe_size}", options=options) diff --git a/v2realbot/tools/createbatchimage.py b/v2realbot/tools/createbatchimage.py index 5bb9ad6..337f9e4 100644 --- a/v2realbot/tools/createbatchimage.py +++ b/v2realbot/tools/createbatchimage.py @@ -12,7 +12,7 @@ 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.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, safe_get from pathlib import Path from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY diff --git a/v2realbot/tools/loadbatch.py b/v2realbot/tools/loadbatch.py index e5b2e7a..f68c423 100644 --- a/v2realbot/tools/loadbatch.py +++ b/v2realbot/tools/loadbatch.py @@ -11,7 +11,7 @@ import numpy as np import v2realbot.controller.services as cs from rich import print as richprint from v2realbot.common.model import AnalyzerInputs -from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType +from v2realbot.common.model 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 @@ -23,7 +23,7 @@ from collections import defaultdict from scipy.stats import zscore from io import BytesIO from typing import Tuple, Optional, List -from v2realbot.common.PrescribedTradeModel import TradeDirection, TradeStatus, Trade, TradeStoplossType +from v2realbot.common.model import TradeDirection, TradeStatus, Trade, TradeStoplossType from collections import Counter import vectorbtpro as vbt diff --git a/v2realbot/utils/utils.py b/v2realbot/utils/utils.py index 2390f9f..5723676 100644 --- a/v2realbot/utils/utils.py +++ b/v2realbot/utils/utils.py @@ -9,8 +9,8 @@ import decimal from v2realbot.enums.enums import RecordType, Mode, StartBarAlign import pickle import os -from v2realbot.common.model import StrategyInstance, Runner, RunArchive, RunArchiveDetail, Intervals, SLHistory, InstantIndicator -from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType +from v2realbot.common.model import StrategyInstance, Runner, RunArchive, RunArchiveDetail, Intervals, SLHistory, InstantIndicator, AccountVariables +from v2realbot.common.model import Trade, TradeDirection, TradeStatus, TradeStoplossType from typing import List import tomli from v2realbot.config import DATA_DIR, ACCOUNT1_PAPER_API_KEY, ACCOUNT1_PAPER_SECRET_KEY @@ -36,6 +36,220 @@ import shutil from filelock import FileLock import v2realbot.utils.config_handler as cfh import pandas_market_calendars as mcal +from typing import Dict, Any, Callable, Optional +from pydantic import BaseModel + +def get_attribute(obj: Any, attr: str) -> Any: + """ + Returns the value of given attribute from the object being it dict or BaseModel + """ + if isinstance(obj, dict): + return obj.get(attr) + if isinstance(obj, BaseModel): + return getattr(obj, attr, None) + return None + +def gaka( + account_variables: Dict[str, Any], + name_of_attribute: str, + transform_function: Optional[Callable[[Any], Any]] = None, + condition_function: Optional[Callable[[Any], bool]] = None +) -> Dict[str, Any]: + """ + Gets Account Keyed Attribute + Extracts the specified attribute from each account variable in the given dictionary. + It also contains transformation function and condition function. + + ``` + avgps = gaka(account_variables, "avgp", + transform_function=lambda x: round(x, 3), + condition_function=lambda x: x > 3) + + returns: + { + 'account2': 5000.654, + 'account3': 3000.789, + 'account4': 8000.235 + } + ``` + + Args: + account_variables (Dict[str, BaseModel]): A dictionary of account variables. + name_of_attribute (str): The name of the attribute to extract. + transform_function (Optional[Callable[[Any], Any]]): Optional function to transform the attribute value. + condition_function (Optional[Callable[[Any], bool]]): Optional function to filter the results. + + Returns: + Dict[str, Any]: A dictionary containing the extracted attribute for each account that meets the condition. + """ + result = {} + for account_str, acc_vars in account_variables.items(): + value = get_attribute(acc_vars, name_of_attribute) + + if value is None and not hasattr(acc_vars, name_of_attribute): + continue + + transformed_value = transform_function(value) if transform_function else value + + if condition_function is None or condition_function(transformed_value): + result[account_str] = transformed_value + + return result + +def gaka_unoptimized( + account_variables: Dict[str, Any], + name_of_attribute: str, + transform_function: Optional[Callable[[Any], Any]] = None, + condition_function: Optional[Callable[[Any], bool]] = None +) -> Dict[str, Any]: + """ + Gets Account Keyed Attribute + Extracts the specified attribute from each account variable in the given dictionary. + It also contains transformation function and condition function. + + ``` + avgps = gaka(account_variables, "avgp", + transform_function=lambda x: round(x, 3), + condition_function=lambda x: x > 3) + + returns: + { + 'account2': 5000.654, + 'account3': 3000.789, + 'account4': 8000.235 + } + ``` + + Args: + account_variables (Dict[str, BaseModel]): A dictionary of account variables. + name_of_attribute (str): The name of the attribute to extract. + transform_function (Optional[Callable[[Any], Any]]): Optional function to transform the attribute value. + condition_function (Optional[Callable[[Any], bool]]): Optional function to filter the results. + + Returns: + Dict[str, Any]: A dictionary containing the extracted attribute for each account that meets the condition. + """ + result = {} + for account, acc_vars in account_variables.items(): + if isinstance(acc_vars, BaseModel): + value = getattr(acc_vars, name_of_attribute, None) + elif isinstance(acc_vars, dict): + value = acc_vars.get(name_of_attribute, None) + else: + continue # Skip if acc_vars is neither BaseModel nor dict + + if value is None and not hasattr(acc_vars, name_of_attribute): + continue # Skip if attribute doesn't exist + + transformed_value = transform_function(value) if transform_function else value + + if condition_function is None or condition_function(transformed_value): + result[account] = transformed_value + + return result + +def gaka_old_with_comprehesion( + account_variables: Dict[str, Any], + name_of_attribute: str, + transform_function: Optional[Callable[[Any], Any]] = None, + condition_function: Optional[Callable[[Any], bool]] = None +) -> Dict[str, Any]: + """ + Gets Account Keyed Attribute + Extracts the specified attribute from each account variable in the given dictionary. + + It also contains transformation function and condition function. + + ``` + avgps = gaka(account_variables, "avgp", + transform_function=lambda x: round(x, 3), + condition_function=lambda x: x > 3) + + returns: + { + 'account2': 5000.654, + 'account3': 3000.789, + 'account4': 8000.235 + } + ``` + + Args: + account_variables (Dict[str, BaseModel]): A dictionary of account variables. + name_of_attribute (str): The name of the attribute to extract. + transform_function (Optional[Callable[[Any], Any]]): Optional function to transform the attribute value. + condition_function (Optional[Callable[[Any], bool]]): Optional function to filter the results. + + Returns: + Dict[str, Any]: A dictionary containing the extracted attribute for each account that meets the condition. + """ + return { + account: transformed_value + for account, acc_vars in account_variables.items() + if (value := ( + getattr(acc_vars, name_of_attribute, None) + if isinstance(acc_vars, BaseModel) + else acc_vars.get(name_of_attribute, None) + if isinstance(acc_vars, dict) + else None + )) is not None or name_of_attribute in acc_vars.__dict__ + and (transformed_value := ( + transform_function(value) if transform_function else value + )) is not None + and (not condition_function or condition_function(transformed_value)) + } + +def gaka_old(account_variables: Dict[str, Any], name_of_attribute: str, transform_function: Optional[Callable[[Any], Any]] = None) -> Dict[str, Any]: + """ + Gets Account Keyed Attribute + Extracts the specified attribute from each account variable in the given dictionary. + + It also contain transformation function. + + ``` + avgps = extract_attribute(account_variables, "avgp", lambda x: round(x, 3)) + + returns: + { + 'account1': 1000.123, + 'account2': 5000.654, + 'account3': 3000.789, + 'account4': 8000.235 + } + ``` + + Args: + account_variables (Dict[str, BaseModel]): A dictionary of account variables. + name_of_attribute (str): The name of the attribute to extract. + + Returns: + Dict[str, Any]: A dictionary containing the extracted attribute for each account. + """ + #return {account: getattr(acc_vars, name_of_attribute) for account, acc_vars in account_variables.items()} + return { + account: ( + transform_function(value) if transform_function and value is not None else value + ) + for account, acc_vars in account_variables.items() + if (value := ( + getattr(acc_vars, name_of_attribute, None) + if isinstance(acc_vars, BaseModel) + else acc_vars.get(name_of_attribute, None) + if isinstance(acc_vars, dict) + else None + )) is not None + } + +def empty_lists_in_dict(d: dict): + """ + Assumes all values of dict are list. Returns true if all lists are empty. + + Args: + d (dict): The dictionary to check. + + Returns: + bool: True if all lists in the dictionary are empty, False otherwise. + """ + return all(len(v) == 0 for v in d.values()) def validate_and_format_time(time_string): """ @@ -675,6 +889,7 @@ def json_serial(obj): SLHistory: lambda obj: obj.__dict__, InstantIndicator: lambda obj: obj.__dict__, StrategyInstance: lambda obj: obj.__dict__, + AccountVariables: lambda obj: obj.__dict__ } serializer = type_map.get(type(obj))