From 222c85e465b11b764e8ee9ece464f9fbf1ff16be Mon Sep 17 00:00:00 2001 From: David Brazda Date: Tue, 21 Nov 2023 11:47:33 +0100 Subject: [PATCH] pozn do runu, prgbar na queu,report --- v2realbot/ENTRY_ClassicSL_v01.py | 10 +- v2realbot/controller/services.py | 4 +- v2realbot/reporting/metricstoolsimage.py | 259 +++++++++++++++-------- v2realbot/strategy/base.py | 65 +++--- 4 files changed, 211 insertions(+), 127 deletions(-) diff --git a/v2realbot/ENTRY_ClassicSL_v01.py b/v2realbot/ENTRY_ClassicSL_v01.py index ca9d991..f0fa5dd 100644 --- a/v2realbot/ENTRY_ClassicSL_v01.py +++ b/v2realbot/ENTRY_ClassicSL_v01.py @@ -157,7 +157,7 @@ def init(state: StrategyState): today = time_to.date() several_days_ago = today - timedelta(days=40) - printanyway(f"{today=}",f"{several_days_ago=}") + #printanyway(f"{today=}",f"{several_days_ago=}") clientTrading = TradingClient(ACCOUNT1_PAPER_API_KEY, ACCOUNT1_PAPER_SECRET_KEY, raw_data=False) #get all market days from here to 40days ago calendar_request = GetCalendarRequest(start=several_days_ago,end=today) @@ -174,13 +174,13 @@ def init(state: StrategyState): #history_datetime_to = zoneNY.localize(session.close) history_datetime_to = session.close break - printanyway("Previous Market Day Close:", history_datetime_to) - printanyway("Market day 40days ago Open:", history_datetime_from) + #printanyway("Previous Market Day Close:", history_datetime_to) + #printanyway("Market day 40days ago Open:", history_datetime_from) - printanyway(history_datetime_from, history_datetime_to) + #printanyway(history_datetime_from, history_datetime_to) #az do predchziho market dne dne state.dailyBars = get_historical_bars(state.symbol, history_datetime_from, history_datetime_to, TimeFrame.Day) - printanyway("daily bars FILLED", state.dailyBars) + #printanyway("daily bars FILLED", state.dailyBars) #zatim ukladame do extData - pro instant indicatory a gui state.extData["dailyBars"] = state.dailyBars diff --git a/v2realbot/controller/services.py b/v2realbot/controller/services.py index 6962aee..3b075a1 100644 --- a/v2realbot/controller/services.py +++ b/v2realbot/controller/services.py @@ -476,6 +476,8 @@ def batch_run_manager(id: UUID, runReq: RunRequest, rundays: list[RunDay]): note_from_run_request = runReq.note first = None last = None + first_frm = runReq.bt_from.strftime("%d.%m.") + last_frm = runReq.bt_to.strftime("%d.%m.") for day in rundays: cnt += 1 if cnt == 1: @@ -486,7 +488,7 @@ def batch_run_manager(id: UUID, runReq: RunRequest, rundays: list[RunDay]): print("Datum do", day.end) runReq.bt_from = day.start runReq.bt_to = day.end - runReq.note = f"Batch {batch_id} #{cnt}/{cnt_max} {day.name} N:{day.note} {note_from_run_request}" + runReq.note = f"{first_frm}-{last_frm} Batch {batch_id} #{cnt}/{cnt_max} {day.name} N:{day.note} {note_from_run_request}" #protoze jsme v ridicim vlaknu, poustime za sebou jednotlive stratiny v synchronnim modu res, id_val = run_stratin(id=id, runReq=runReq, synchronous=True, inter_batch_params=inter_batch_params) diff --git a/v2realbot/reporting/metricstoolsimage.py b/v2realbot/reporting/metricstoolsimage.py index 140afcf..6c2c5ec 100644 --- a/v2realbot/reporting/metricstoolsimage.py +++ b/v2realbot/reporting/metricstoolsimage.py @@ -16,6 +16,8 @@ from pathlib import Path from v2realbot.config import WEB_API_KEY, DATA_DIR, MEDIA_DIRECTORY from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, OrderSide from io import BytesIO +from v2realbot.utils.historicals import get_historical_bars +from alpaca.data.timeframe import TimeFrame, TimeFrameUnit # Assuming Trade, TradeStatus, TradeDirection, TradeStoplossType classes are defined elsewhere def generate_trading_report_image(runner_ids: list = None, batch_id: str = None, stream: bool = False): @@ -35,7 +37,13 @@ def generate_trading_report_image(runner_ids: list = None, batch_id: str = None, return -1, f"no batch {batch_id} found" trades = [] + cnt_max = len(runner_ids) + cnt = 0 + #zatim zjistujeme start a end z min a max dni - jelikoz muze byt i seznam runner_ids a nejenom batch + end_date = None + start_date = None for id in runner_ids: + cnt += 1 #get runner res, sada =cs.get_archived_runner_header_byID(id) if res != 0: @@ -45,8 +53,11 @@ def generate_trading_report_image(runner_ids: list = None, batch_id: str = None, print("archrunner") print(sada) + if cnt == 1: + start_date = sada.bt_from if sada.mode in [Mode.BT,Mode.PREP] else sada.started + if cnt == cnt_max: + end_date = sada.bt_to if sada.mode in [Mode.BT or Mode.PREP] else sada.stopped # Parse trades - #trades = [Trade(**trade_dict) for trade_dict in set.metrics["prescr_trades"]] trades_dicts = sada.metrics["prescr_trades"] @@ -58,6 +69,27 @@ def generate_trading_report_image(runner_ids: list = None, batch_id: str = None, print(trades) + symbol = sada.symbol + #hour bars for backtested period + print(start_date,end_date) + bars= get_historical_bars(symbol, start_date, end_date, TimeFrame.Hour) + print("bars for given period",bars) + """Bars a dictionary with the following keys: + * high: A list of high prices + * low: A list of low prices + * volume: A list of volumes + * close: A list of close prices + * hlcc4: A list of HLCC4 indicators + * open: A list of open prices + * time: A list of times in UTC (ISO 8601 format) + * trades: A list of number of trades + * resolution: A list of resolutions (all set to 'D') + * confirmed: A list of booleans (all set to True) + * vwap: A list of VWAP indicator + * updated: A list of booleans (all set to True) + * index: A list of integers (from 0 to the length of the list of daily bars) + """ + # Filter to only use trades with status 'CLOSED' closed_trades = [trade for trade in trades if trade.status == TradeStatus.CLOSED] @@ -83,7 +115,7 @@ def generate_trading_report_image(runner_ids: list = None, batch_id: str = None, long_profits = [trade.profit for trade in closed_trades if trade.direction == TradeDirection.LONG and trade.profit is not None] short_profits = [trade.profit for trade in closed_trades if trade.direction == TradeDirection.SHORT and trade.profit is not None] - # # Setting up dark mode for the plots + # Setting up dark mode for the plots plt.style.use('dark_background') # Optionally, you can further customize colors, labels, and axes @@ -104,45 +136,44 @@ def generate_trading_report_image(runner_ids: list = None, batch_id: str = None, } plt.rcParams.update(params) - #NEW LOOK - # # Set the style to dark mode with custom settings + + #Custom dark theme similar to the provided image + # dark_finance_theme = { + # 'background': '#1a1a1a', # Very dark (almost black) background + # 'text': '#eaeaea', # Light grey text for readability + # 'grid': '#333333', # Dark grey grid lines + # 'accent': '#2e91e5', # Bright blue accent for main elements + # 'secondary': '#e15f99', # Secondary pink/magenta color for highlights + # 'highlight': '#fcba03', # Gold-like color for special highlights + # } + + # # Apply the theme settings # plt.style.use('dark_background') + # plt.rcParams.update({ + # 'figure.facecolor': dark_finance_theme['background'], + # 'axes.facecolor': dark_finance_theme['background'], + # 'axes.edgecolor': dark_finance_theme['text'], + # 'axes.labelcolor': dark_finance_theme['text'], + # 'axes.titlesize': 12, + # 'axes.labelsize': 10, + # 'xtick.color': dark_finance_theme['text'], + # 'xtick.labelsize': 8, + # 'ytick.color': dark_finance_theme['text'], + # 'ytick.labelsize': 8, + # 'grid.color': dark_finance_theme['grid'], + # 'grid.linestyle': '-', + # 'grid.linewidth': 0.6, + # 'legend.facecolor': dark_finance_theme['background'], + # 'legend.edgecolor': dark_finance_theme['background'], + # 'legend.fontsize': 10, + # 'text.color': dark_finance_theme['text'], + # 'lines.color': dark_finance_theme['accent'], + # 'patch.edgecolor': dark_finance_theme['accent'], + # }) - # # Define a custom dark theme color palette - # dark_theme_colors = { - # 'background': '#1c1c1c', # Dark gray - # 'text': '#d6d6d6', # Light gray for a subtle contrast - # 'grid': '#414141', # Slightly lighter gray than background for grid - # 'highlight': '#3498db', # Bright blue for highlights (max/min points, etc.) - # 'warning': '#e74c3c', # Red color for warnings or important highlights - # 'neutral': '#7f8c8d', # Neutral color for less important elements - # } - # # Customize the color scheme - # params = { - # 'figure.facecolor': dark_theme_colors['background'], - # 'axes.titlesize': 10, - # 'axes.labelsize': 9, - # 'xtick.labelsize': 9, - # 'ytick.labelsize': 9, - # 'axes.labelcolor': dark_theme_colors['text'], - # 'axes.facecolor': dark_theme_colors['background'], - # 'axes.grid': False, # Control grid visibility - # 'axes.edgecolor': dark_theme_colors['text'], - # 'xtick.color': dark_theme_colors['text'], - # 'ytick.color': dark_theme_colors['text'], - # 'text.color': dark_theme_colors['text'], - # 'legend.facecolor': dark_theme_colors['background'], - # 'legend.edgecolor': dark_theme_colors['text'], - # 'legend.fontsize': 8, - # 'legend.title_fontsize': 9, - # } - - # # Apply the custom color scheme - # plt.rcParams.update(params) - - # Create a combined figure for all plots - fig, axs = plt.subplots(3, 4, figsize=(11, 7)) + # Create a combined figure for all plots 11,7 ideal na 3,4 + fig, axs = plt.subplots(3, 4, figsize=(12, 7)) #TITLE title = "" @@ -229,43 +260,7 @@ def generate_trading_report_image(runner_ids: list = None, batch_id: str = None, place_annotation(axs[0, 3], 0, long_count, offset) place_annotation(axs[0, 3], 1, short_count, offset) - - #Cumulative profit - bud 1 den nebo vice dni - if len(runner_ids)== 1: - if cumulative_profits.size > 0: - # Plot 3: Cumulative Profit Over Time with Max Profit Point - max_profit_time = exit_times[np.argmax(cumulative_profits)] - max_profit = max(cumulative_profits) - min_profit_time = exit_times[np.argmin(cumulative_profits)] - min_profit = min(cumulative_profits) - sns.lineplot(x=exit_times, y=cumulative_profits, label='Cumulative Profit', ax=axs[1, 3]) - axs[1, 3].scatter(max_profit_time, max_profit, color='green', label='Max Profit') - axs[1, 3].scatter(min_profit_time, min_profit, color='red', label='Min Profit') - # Format dates on the x-axis - axs[1, 3].xaxis.set_major_formatter(mdates.DateFormatter('%H', tz=zoneNY)) - axs[1, 3].set_title('Cumulative Profit Over Time') - axs[1, 3].legend() - else: - # Handle the case where cumulative_profits is empty - axs[1, 3].text(0.5, 0.5, 'No profit data available', - horizontalalignment='center', - verticalalignment='center', - transform=axs[1, 3].transAxes) - axs[1, 3].set_title('Cumulative Profit Over Time') - else: - # Calculate cumulative profit - # Additional Plot: Cumulative Profit Over Time - # Sort trades by exit time - sorted_trades = sorted([trade for trade in trades if trade.status == TradeStatus.CLOSED], - key=lambda x: x.exit_time) - cumulative_profits_sorted = np.cumsum([trade.profit for trade in sorted_trades]) - exit_times_sorted = [trade.exit_time for trade in sorted_trades if trade.exit_time is not None] - axs[1, 3].plot(exit_times_sorted, cumulative_profits_sorted, color='blue') - axs[1, 3].set_title('Cumulative Profit Over Time') - axs[1, 3].set_xlabel('Time') - axs[1, 3].set_ylabel('Cumulative Profit') - axs[1, 3].xaxis.set_major_formatter(mdates.DateFormatter('%d', tz=zoneNY)) - + #PLOT 5 - Heatman (exit time) # Creating a DataFrame for the heatmap heatmap_data_list = [] for trade in trades: @@ -281,7 +276,7 @@ def generate_trading_report_image(runner_ids: list = None, batch_id: str = None, heatmap_data = heatmap_data.groupby(['Day', 'Hour']).sum().reset_index() heatmap_pivot = heatmap_data.pivot(index='Day', columns='Hour', values='Profit') - # Plot 3: Heatmap of Profits + # Heatmap of Profits sns.heatmap(heatmap_pivot, cmap='viridis', ax=axs[1, 0]) axs[1, 0].set_title('Heatmap of Profits (based on Exit time)') axs[1, 0].set_xlabel('Hour of Day') @@ -294,15 +289,15 @@ def generate_trading_report_image(runner_ids: list = None, batch_id: str = None, transform=axs[1, 0].transAxes) axs[1, 0].set_title('Heatmap of Profits (based on Exit time)') - # Plot 9: Profit/Loss Distribution Histogram + # Plot 6: Profit/Loss Distribution Histogram sns.histplot(profits, bins=30, ax=axs[1, 1], kde=True, color='skyblue') axs[1, 1].set_title('Profit/Loss Distribution') axs[1, 1].set_xlabel('Profit/Loss') axs[1, 1].set_ylabel('Frequency') - # Plot 5 - # - pro 1 den: Position Size Distribution - # - pro vice dnu: Trade Duration vs. Profit/Loss + # Plot 7 + # - for 1 den: Position Size Distribution + # - for more days: Trade Duration vs. Profit/Loss if len(runner_ids) == 1: sizes = [trade.size for trade in closed_trades if trade.size is not None] @@ -331,7 +326,7 @@ def generate_trading_report_image(runner_ids: list = None, batch_id: str = None, ##trade_volumes.append(trade.size) # or any other measure of trade size trade_types.append('Long' if trade.direction == TradeDirection.LONG else 'Short') - # Plot 8: Trade Duration vs. Profit/Loss + # Trade Duration vs. Profit/Loss scatter_data = pd.DataFrame({ 'Duration': trade_durations, 'Profit': trade_profits, @@ -345,7 +340,91 @@ def generate_trading_report_image(runner_ids: list = None, batch_id: str = None, axs[1, 2].set_ylabel('Profit/Loss') - # Plot 6: Daily Relative Profit Chart + #Plot 8 Cumulative profit - bud 1 den nebo vice dni + pridame pod to vyvoj ceny + # Extract the closing prices and times + closing_prices = bars['close'] + #times = bars['time'] # Assuming this is a list of pandas Timestamp objects + times = pd.to_datetime(bars['time']) # Ensure this is a Pandas datetime series + # # Plot the closing prices over time + # axs[0, 4].plot(times, closing_prices, color='blue') + # axs[0, 4].tick_params(axis='x', rotation=45) # Rotate date labels if necessar + # axs[0, 4].xaxis.set_major_formatter(mdates.DateFormatter('%H', tz=zoneNY)) + + if len(runner_ids)== 1: + if cumulative_profits.size > 0: + # Plot 3: Cumulative Profit Over Time with Max Profit Point + max_profit_time = exit_times[np.argmax(cumulative_profits)] + max_profit = max(cumulative_profits) + min_profit_time = exit_times[np.argmin(cumulative_profits)] + min_profit = min(cumulative_profits) + + #Plot Cumulative Profit Over Time with Max Profit Point on the primary y-axis + # Create a secondary y-axis for the closing prices + ax2 = axs[1, 3].twinx() + ax2.plot(times, closing_prices, label='Closing Price', color='orange') + ax2.set_ylabel('Closing Price', color='orange') + ax2.tick_params(axis='y', labelcolor='orange') + + # Set the limits for the x-axis to cover the full range of 'times' + axs[1, 3].set_xlim(times.min(), times.max()) + sns.lineplot(x=exit_times, y=cumulative_profits, ax=axs[1, 3], color='limegreen') + axs[1, 3].scatter(max_profit_time, max_profit, color='green', label='Max Profit') + axs[1, 3].scatter(min_profit_time, min_profit, color='red', label='Min Profit') + axs[1, 3].set_xlabel('Time') + axs[1, 3].set_ylabel('Cumulative Profit', color='limegreen') + axs[1, 3].tick_params(axis='y', labelcolor='limegreen') + axs[1, 3].xaxis.set_major_formatter(mdates.DateFormatter('%H', tz=zoneNY)) + # Add legends to the plot + # lines, labels = axs[1, 3].get_legend_handles_labels() + # lines2, labels2 = ax2.get_legend_handles_labels() + # axs[1, 3].legend(lines + lines2, labels + labels2, loc='upper left') + else: + # Handle the case where cumulative_profits is empty + axs[1, 3].text(0.5, 0.5, 'No profit data available', + horizontalalignment='center', + verticalalignment='center', + transform=axs[1, 3].transAxes) + axs[1, 3].set_title('Cumulative Profit Over Time') + else: + # Calculate cumulative profit + # Additional Plot: Cumulative Profit Over Time + # Sort trades by exit time + + # # Set the limits for the x-axis to cover the full range of 'times' + # axs[1, 3].set_xlim(times.min(), times.max()) + + sorted_trades = sorted([trade for trade in trades if trade.status == TradeStatus.CLOSED], + key=lambda x: x.exit_time) + cumulative_profits_sorted = np.cumsum([trade.profit for trade in sorted_trades]) + exit_times_sorted = [trade.exit_time for trade in sorted_trades if trade.exit_time is not None] + + # Create a secondary y-axis for the closing prices + ax2 = axs[1, 3].twinx() + ax2.plot(times, closing_prices, label='Closing Price', color='orange') + ax2.set_ylabel('Closing Price', color='orange') + ax2.tick_params(axis='y', labelcolor='orange') + + axs[1, 3].set_xlim(times.min(), times.max()) + # Plot Cumulative Profit Over Time on the primary y-axis + axs[1, 3].plot(exit_times_sorted, cumulative_profits_sorted, label='Cumulative Profit', color='blue') + axs[1, 3].set_xlabel('Time') + axs[1, 3].set_ylabel('Cumulative Profit', color='blue') + axs[1, 3].tick_params(axis='y', labelcolor='blue') + + # Format dates on the x-axis + axs[1, 3].xaxis.set_major_formatter(mdates.DateFormatter('%d.%m.', tz=zoneNY)) + axs[1, 3].tick_params(axis='x', rotation=45) # Rotate date labels if necessary + + # Set the title + axs[1, 3].set_title('Cumulative Profit and Closing Price Over Time') + + # Add legends to the plot + # axs[1, 3].legend(loc='upper left') + # ax2.legend(loc='upper right') + + # Plot 9 + # - for 1 day: Daily Relative Profit Chart + # - for more days: Heatmap of Profits (based on Entry time) if len(runner_ids) == 1: daily_rel_profits = [trade.rel_profit for trade in closed_trades if trade.rel_profit is not None] sns.lineplot(x=range(len(daily_rel_profits)), y=daily_rel_profits, ax=axs[2, 0]) @@ -365,13 +444,13 @@ def generate_trading_report_image(runner_ids: list = None, batch_id: str = None, heatmap_data = heatmap_data.groupby(['Day', 'Hour']).sum().reset_index() heatmap_pivot = heatmap_data.pivot(index='Day', columns='Hour', values='Profit') - # Plot 3: Heatmap of Profits + # Heatmap of Profits sns.heatmap(heatmap_pivot, cmap='viridis', ax=axs[2, 0]) axs[2, 0].set_title('Heatmap of Profits (based on Entry time)') axs[2, 0].set_xlabel('Hour of Day') axs[2, 0].set_ylabel('Day') - # Plot 8: Profits Based on Hour of the Day (Entry) + # Plot 10: Profits Based on Hour of the Day (Entry) entry_hours = [trade.entry_time.hour for trade in closed_trades if trade.entry_time is not None] profits_by_hour = {} for hour, trade in zip(entry_hours, closed_trades): @@ -396,7 +475,7 @@ def generate_trading_report_image(runner_ids: list = None, batch_id: str = None, transform=axs[2, 1].transAxes) axs[2, 1].set_title('Profits by Hour of Day (Entry)') - # Plot 9: Profits Based on Hour of the Day - based on Exit + # Plot 11: Profits Based on Hour of the Day - based on Exit exit_hours = [trade.exit_time.hour for trade in closed_trades if trade.exit_time is not None] profits_by_hour = {} for hour, trade in zip(exit_hours, closed_trades): @@ -421,7 +500,7 @@ def generate_trading_report_image(runner_ids: list = None, batch_id: str = None, transform=axs[2, 2].transAxes) axs[2, 2].set_title('Profits by Hour of Day (Exit)') - # Calculate profits by day of the week + # Plot 12: Calculate profits by day of the week day_of_week_profits = {i: 0 for i in range(7)} # Dictionary to store profits for each day of the week for trade in trades: @@ -459,7 +538,7 @@ def generate_trading_report_image(runner_ids: list = None, batch_id: str = None, # Example usage # trades = [list of Trade objects] if __name__ == '__main__': - id_list = ["c3e31cb5-ddf9-467e-a932-2118f6844355"] - generate_trading_report_image(runner_ids=id_list) - # batch_id = "90973e57" - # generate_trading_report_image(batch_id=batch_id) + # id_list = ["e8938b2e-8462-441a-8a82-d823c6a025cb"] + # generate_trading_report_image(runner_ids=id_list) + batch_id = "90973e57" + generate_trading_report_image(batch_id=batch_id) diff --git a/v2realbot/strategy/base.py b/v2realbot/strategy/base.py index 64f16c4..4842f5b 100644 --- a/v2realbot/strategy/base.py +++ b/v2realbot/strategy/base.py @@ -28,6 +28,7 @@ from uuid import UUID from rich import print as printnow from collections import defaultdict import v2realbot.strategyblocks.activetrade.sl.optimsl as optimsl +from tqdm import tqdm if PROFILING_NEXT_ENABLED: from pyinstrument import Profiler @@ -418,40 +419,42 @@ class Strategy: #main strat loop print(self.name, "Waiting for DATA") - while True: - try: - #block 5s, after that check signals - item = self.q1.get(timeout=HEARTBEAT_TIMEOUT) - #printnow(current_thread().name, "Items waiting in queue:", self.q1.qsize()) - except queue.Empty: - #check signals - if self.se.is_set(): - print(current_thread().name, "Stopping signal") + with tqdm(total=self.q1.qsize()) as pbar: + while True: + try: + #block 5s, after that check signals + item = self.q1.get(timeout=HEARTBEAT_TIMEOUT) + #printnow(current_thread().name, "Items waiting in queue:", self.q1.qsize()) + except queue.Empty: + #check signals + if self.se.is_set(): + print(current_thread().name, "Stopping signal") + break + if self.pe.is_set(): + print(current_thread().name, "Paused.") + continue + else: + print(current_thread().name, "HEARTBEAT - no trades or signals") + continue + #prijde posledni zaznam nebo stop event signal + if item == "last" or self.se.is_set(): + print(current_thread().name, "stopping") break - if self.pe.is_set(): + elif self.pe.is_set(): print(current_thread().name, "Paused.") continue - else: - print(current_thread().name, "HEARTBEAT - no trades or signals") - continue - #prijde posledni zaznam nebo stop event signal - if item == "last" or self.se.is_set(): - print(current_thread().name, "stopping") - break - elif self.pe.is_set(): - print(current_thread().name, "Paused.") - continue - #self.state.iter_log(event="INGEST",msg="New data ingested", item=item) - print("New data ingested", item) - print("bars list - previous", self.state.bars) - #TODO sem pridat ochranu kulometu - #pokud je updatetime aktualniho baru mensi nez LIMIT a nejde o potvrzovaci bar - #tak jej vyhodit - #zabraní se tím akcím na než bych stejně nešlo reagovat - #TODO jeste promyslet - - #calling main loop - self.strat_loop(item=item) + #self.state.iter_log(event="INGEST",msg="New data ingested", item=item) + print("New data ingested", item) + print("bars list - previous", self.state.bars) + #TODO sem pridat ochranu kulometu + #pokud je updatetime aktualniho baru mensi nez LIMIT a nejde o potvrzovaci bar + #tak jej vyhodit + #zabraní se tím akcím na než bych stejně nešlo reagovat + #TODO jeste promyslet + + #calling main loop + self.strat_loop(item=item) + pbar.update(1) tlog(f"FINISHED") print(40*"*",self.mode, "STRATEGY ", self.name,"STOPPING",40*"*")