diff --git a/lib/pt_strategy/trading_strategy.py b/lib/pt_strategy/trading_strategy.py index 8dcf0b3..3d851be 100644 --- a/lib/pt_strategy/trading_strategy.py +++ b/lib/pt_strategy/trading_strategy.py @@ -309,96 +309,3 @@ class PtResearchStrategy: def day_trades(self) -> pd.DataFrame: return pd.concat(self.trades_, ignore_index=True) - - -def main() -> None: - import argparse - - from tools.config import expand_filename, load_config - - parser = argparse.ArgumentParser(description="Run pairs trading backtest.") - parser.add_argument( - "--config", type=str, required=True, help="Path to the configuration file." - ) - parser.add_argument( - "--date_pattern", - type=str, - required=True, - help="Date YYYYMMDD, allows * and ? wildcards", - ) - parser.add_argument( - "--instruments", - type=str, - required=True, - help="Comma-separated list of instrument symbols (e.g., COIN:EQUITY,GBTC:CRYPTO)", - ) - parser.add_argument( - "--result_db", - type=str, - required=True, - help="Path to SQLite database for storing results. Use 'NONE' to disable database output.", - ) - - args = parser.parse_args() - - config: Dict = load_config(args.config) - - # Resolve data files (CLI takes priority over config) - instruments = get_instruments(args, config) - datafiles = resolve_datafiles(config, args.date_pattern, instruments) - - days = list(set([day for day, _ in datafiles])) - print(f"Found {len(datafiles)} data files to process:") - for df in datafiles: - print(f" - {df}") - - # Create result database if needed - if args.result_db.upper() != "NONE": - args.result_db = expand_filename(args.result_db) - create_result_database(args.result_db) - - # Initialize a dictionary to store all trade results - all_results: Dict[str, Dict[str, Any]] = {} - is_config_stored = False - # Process each data file - - results = PairResearchResult(config=config) - for day in sorted(days): - md_datafiles = [datafile for md_day, datafile in datafiles if md_day == day] - if not all([os.path.exists(datafile) for datafile in md_datafiles]): - print(f"WARNING: insufficient data files: {md_datafiles}") - continue - print(f"\n====== Processing {day} ======") - - if not is_config_stored: - store_config_in_database( - db_path=args.result_db, - config_file_path=args.config, - config=config, - datafiles=datafiles, - instruments=instruments, - ) - is_config_stored = True - - pt_strategy = PtResearchStrategy( - config=config, datafiles=md_datafiles, instruments=instruments - ) - pt_strategy.run() - results.add_day_results( - day=day, - trades=pt_strategy.day_trades(), - outstanding_positions=pt_strategy.outstanding_positions(), - ) - - - results.analyze_pair_performance() - - - if args.result_db.upper() != "NONE": - print(f"\nResults stored in database: {args.result_db}") - else: - print("No results to display.") - - -if __name__ == "__main__": - main() diff --git a/research/backtest_new.py b/research/backtest_new.py new file mode 100644 index 0000000..daad2fb --- /dev/null +++ b/research/backtest_new.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +import os +from typing import Any, Dict + +from pt_strategy.results import ( + PairResearchResult, + create_result_database, + store_config_in_database, +) +from pt_strategy.trading_strategy import PtResearchStrategy +from tools.filetools import resolve_datafiles +from tools.instruments import get_instruments + + +def main() -> None: + import argparse + + from tools.config import expand_filename, load_config + + parser = argparse.ArgumentParser(description="Run pairs trading backtest.") + parser.add_argument( + "--config", type=str, required=True, help="Path to the configuration file." + ) + parser.add_argument( + "--date_pattern", + type=str, + required=True, + help="Date YYYYMMDD, allows * and ? wildcards", + ) + parser.add_argument( + "--instruments", + type=str, + required=True, + help="Comma-separated list of instrument symbols (e.g., COIN:EQUITY,GBTC:CRYPTO)", + ) + parser.add_argument( + "--result_db", + type=str, + required=True, + help="Path to SQLite database for storing results. Use 'NONE' to disable database output.", + ) + + args = parser.parse_args() + + config: Dict = load_config(args.config) + + # Resolve data files (CLI takes priority over config) + instruments = get_instruments(args, config) + datafiles = resolve_datafiles(config, args.date_pattern, instruments) + + days = list(set([day for day, _ in datafiles])) + print(f"Found {len(datafiles)} data files to process:") + for df in datafiles: + print(f" - {df}") + + # Create result database if needed + if args.result_db.upper() != "NONE": + args.result_db = expand_filename(args.result_db) + create_result_database(args.result_db) + + # Initialize a dictionary to store all trade results + all_results: Dict[str, Dict[str, Any]] = {} + is_config_stored = False + # Process each data file + + results = PairResearchResult(config=config) + for day in sorted(days): + md_datafiles = [datafile for md_day, datafile in datafiles if md_day == day] + if not all([os.path.exists(datafile) for datafile in md_datafiles]): + print(f"WARNING: insufficient data files: {md_datafiles}") + continue + print(f"\n====== Processing {day} ======") + + if not is_config_stored: + store_config_in_database( + db_path=args.result_db, + config_file_path=args.config, + config=config, + datafiles=datafiles, + instruments=instruments, + ) + is_config_stored = True + + pt_strategy = PtResearchStrategy( + config=config, datafiles=md_datafiles, instruments=instruments + ) + pt_strategy.run() + results.add_day_results( + day=day, + trades=pt_strategy.day_trades(), + outstanding_positions=pt_strategy.outstanding_positions(), + ) + + + results.analyze_pair_performance() + + + if args.result_db.upper() != "NONE": + print(f"\nResults stored in database: {args.result_db}") + else: + print("No results to display.") + + +if __name__ == "__main__": + main() diff --git a/research/notebooks/pair_trading_test_NEW.ipynb b/research/notebooks/pair_trading_test_NEW.ipynb new file mode 100644 index 0000000..2a44221 --- /dev/null +++ b/research/notebooks/pair_trading_test_NEW.ipynb @@ -0,0 +1,1125 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "vscode": { + "languageId": "raw" + } + }, + "source": [ + "\n", + "# Settings" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Trading Parameters Configuration\n", + "# Specify your configuration file, trading symbols and date here\n", + "\n", + "# Configuration file selection\n", + "global CONFIG_FILE\n", + "global SYMBOL_A\n", + "global SYMBOL_B\n", + "global TRADING_DATE\n", + "global TRD_DATE\n", + "global PT_BT_CONFIG\n", + "global DATA_FILE\n", + "global FIT_METHOD_TYPE\n", + "global pair\n", + "global pair_trades\n", + "global bt_result\n", + "global INSTRUMENTS\n", + "\n", + "import os\n", + "\n", + "ROOT_DIR = \"/home/oleg/develop/pairs_trading\"\n", + "os.chdir(ROOT_DIR)\n", + "\n", + "CONFIG_FILE = f\"{ROOT_DIR}/configuration/new_zscore.cfg\"\n", + "\n", + "# Date for data file selection (format: YYYYMMDD)\n", + "TRADING_DATE = \"20250605\" # Change this to your desired date\n", + "\n", + "# ================================ E Q U I T Y ================================\n", + "# INSTRUMENTS = {\n", + "# \"A\": {\n", + "# \"symbol\": \"COIN\",\n", + "# \"exchange_id\": \"ALPACA\",\n", + "# \"instrument_type\": \"EQUITY\",\n", + "# \"instrument_id_pfx\": \"STOCK-\",\n", + "# },\n", + "# \"B\": {\n", + "# \"symbol\": \"MSTR\",\n", + "# \"exchange_id\": \"ALPACA\",\n", + "# \"instrument_type\": \"EQUITY\",\n", + "# \"instrument_id_pfx\": \"STOCK-\",\n", + "# },\n", + "# }\n", + "# ================================ E Q U I T Y ================================\n", + "\n", + "# ================================ C R Y P T O ================================\n", + "INSTRUMENTS = {\n", + " \"A\": {\n", + " \"symbol\": \"ADA-USDT\",\n", + " \"exchange_id\": \"BNBSPOT\",\n", + " \"instrument_type\": \"CRYPTO\",\n", + " \"instrument_id_pfx\": \"PAIR-\",\n", + " },\n", + " \"B\": {\n", + " \"symbol\": \"SOL-USDT\",\n", + " \"exchange_id\": \"BNBSPOT\",\n", + " \"instrument_type\": \"CRYPTO\",\n", + " \"instrument_id_pfx\": \"PAIR-\",\n", + " },\n", + "}\n", + "# ================================ C R Y P T O ================================\n", + "\n", + "# ================================ E Q U I T Y VS. C R Y P T O ================================\n", + "# INSTRUMENTS = {\n", + "# \"A\": {\n", + "# \"symbol\": \"MSTR\",\n", + "# \"exchange_id\": \"ALPACA\",\n", + "# \"instrument_type\": \"EQUITY\",\n", + "# \"instrument_id_pfx\": \"STOCK-\",\n", + "# },\n", + "# \"B\": {\n", + "# \"symbol\": \"ETH-USDT\",\n", + "# \"exchange_id\": \"BNBSPOT\",\n", + "# \"instrument_type\": \"CRYPTO\",\n", + "# \"instrument_id_pfx\": \"PAIR-\",\n", + "# },\n", + "# }\n", + "# ================================ E Q U I T Y VS. C R Y P T O ================================\n", + "\n", + "SYMBOL_A = INSTRUMENTS[\"A\"][\"symbol\"]\n", + "SYMBOL_B = INSTRUMENTS[\"B\"][\"symbol\"]\n", + "TRD_DATE = f\"{TRADING_DATE[0:4]}-{TRADING_DATE[4:6]}-{TRADING_DATE[6:8]}\"\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Setup and Configuration" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def setup() -> None:\n", + " import sys\n", + " import os\n", + " sys.path.append('/home/oleg/develop/pairs_trading/lib')\n", + " sys.path.append('/home/coder/pairs_trading/lib')\n", + " \n", + "\n", + " import pandas as pd\n", + " import numpy as np\n", + " import importlib\n", + " from typing import Dict, List, Optional\n", + " from IPython.display import clear_output\n", + "\n", + " # Import our modules\n", + " from pt_strategy.trading_pair import TradingPair, PairState\n", + " from pt_strategy.results import PairResearchResult\n", + "\n", + " pd.set_option('display.width', 400)\n", + " pd.set_option('display.max_colwidth', None)\n", + " pd.set_option('display.max_columns', None)\n", + "\n", + " print(\"Setup complete!\")\n", + " os.chdir(os.path.abspath(os.path.join(os.getcwd(), \"..\", \"..\")))\n", + " print(f\"Current working directory: {os.getcwd()}\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "vscode": { + "languageId": "raw" + } + }, + "source": [ + "## Load Configuration\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load Configuration from Configuration Files using HJSON\n", + "from typing import Dict, Optional\n", + "import hjson\n", + "import os\n", + "import importlib\n", + "\n", + "def load_config_from_file() -> Optional[Dict]:\n", + " global DB_TABLE_NAME\n", + " global PT_BT_CONFIG\n", + " \"\"\"Load configuration from configuration files using HJSON\"\"\"\n", + " config_file = CONFIG_FILE\n", + " config = None\n", + " \n", + " try:\n", + " with open(config_file, 'r') as f:\n", + " # HJSON handles comments, trailing commas, and other human-friendly features\n", + " config = hjson.load(f)\n", + " \n", + " # Convert relative paths to absolute paths from notebook perspective\n", + " if 'data_directory' in config:\n", + " data_dir = config['data_directory']\n", + " if data_dir.startswith('./'):\n", + " # Convert relative path to absolute path from notebook's perspective\n", + " config['data_directory'] = os.path.abspath(f\"../../{data_dir[2:]}\")\n", + " \n", + " \n", + " except FileNotFoundError:\n", + " print(f\"Configuration file not found: {config_file}\")\n", + " except hjson.HjsonDecodeError as e:\n", + " print(f\"HJSON parsing error in {config_file}: {e}\")\n", + " except Exception as e:\n", + " print(f\"Unexpected error loading config from {config_file}: {e}\")\n", + " \n", + " assert config is not None\n", + " PT_BT_CONFIG = dict(config)\n", + " DB_TABLE_NAME = PT_BT_CONFIG[\"market_data_loading\"][INSTRUMENTS[\"A\"][\"instrument_type\"]][\"db_table_name\"]\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Print Configuration" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "def prepare_config() -> None:\n", + " global PT_BT_CONFIG\n", + " global CONFIG_FILE\n", + " global SYMBOL_A\n", + " global SYMBOL_B\n", + " global TRD_DATE\n", + " global DATA_FILES\n", + " global FIT_MODEL\n", + "\n", + " print(f\"Trading Parameters:\")\n", + " print(f\" Configuration: {CONFIG_FILE}\")\n", + " print(f\" Symbol A: {SYMBOL_A}\")\n", + " print(f\" Symbol B: {SYMBOL_B}\")\n", + " print(f\" Trading Date: {TRD_DATE}\")\n", + "\n", + " # Load the specified configuration\n", + " print(f\"\\nLoading {CONFIG_FILE} configuration using HJSON...\")\n", + "\n", + " load_config_from_file()\n", + "\n", + " if PT_BT_CONFIG:\n", + " print(f\"✓ Successfully loaded configuration\")\n", + " # print(f\" Data directory: {PT_BT_CONFIG['data_directory']}\")\n", + " # print(f\" Database table: {PT_BT_CONFIG['db_table_name']}\")\n", + " # print(f\" Exchange: {PT_BT_CONFIG['exchange_id']}\")\n", + " print(f\" Training window: {PT_BT_CONFIG['training_size']} minutes\")\n", + " print(f\" Open threshold: {PT_BT_CONFIG['dis-equilibrium_open_trshld']}\")\n", + " print(f\" Close threshold: {PT_BT_CONFIG['dis-equilibrium_close_trshld']}\")\n", + " \n", + " # Instantiate strategy from config\n", + " # FIT_MODEL = instantiate_fit_method_from_config(PT_BT_CONFIG)\n", + " # print(f\" Fit Method: {type(FIT_MODEL).__name__}\")\n", + " \n", + " # Automatically construct data file name based on date and config type\n", + " DATA_FILE = f\"{TRADING_DATE}.mktdata.ohlcv.db\"\n", + " data_directory_a = PT_BT_CONFIG[\"market_data_loading\"][INSTRUMENTS[\"A\"][\"instrument_type\"]][\"data_directory\"]\n", + " data_directory_b = PT_BT_CONFIG[\"market_data_loading\"][INSTRUMENTS[\"B\"][\"instrument_type\"]][\"data_directory\"]\n", + " DATA_FILE_A = f\"{data_directory_a}/{TRADING_DATE}.mktdata.ohlcv.db\"\n", + " DATA_FILE_B = f\"{data_directory_b}/{TRADING_DATE}.mktdata.ohlcv.db\"\n", + "\n", + " PT_BT_CONFIG[\"datafiles\"] = list(set([DATA_FILE_A, DATA_FILE_B]))\n", + " \n", + " print(f\"\\nData Configuration:\")\n", + " print(f\" Data File: {DATA_FILE}\")\n", + " \n", + " # Verify data file exists\n", + " os.chdir(ROOT_DIR)\n", + " for data_file_path in PT_BT_CONFIG[\"datafiles\"]:\n", + " if os.path.exists(data_file_path):\n", + " print(f\" ✓ Data file found: {data_file_path}\")\n", + " else:\n", + " raise FileNotFoundError(\n", + " f\" ⚠ Data file not found: {data_file_path}\\n\"\n", + " f\" Please check if the date and file exist in the data directory\"\n", + " )\n", + " \n", + " else:\n", + " print(\"⚠ Failed to load configuration. Please check the configuration file.\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "vscode": { + "languageId": "raw" + } + }, + "source": [ + "## Run Strategy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "def run_strategy() -> None: # Load market data\n", + " global PT_BT_CONFIG\n", + " global INSTRUMENTS\n", + " global DATA_FILE\n", + " global SYMBOL_A\n", + " global SYMBOL_B\n", + " global pair\n", + " global DB_TABLE_NAME\n", + " global PT_RESEARCH_STRATEGY\n", + " global PT_RESULTS\n", + "\n", + " import pandas as pd\n", + " from tools.data_loader import load_market_data\n", + " from pt_strategy.trading_pair import TradingPair\n", + " from pt_strategy.trading_strategy import PtResearchStrategy\n", + " from pt_strategy.results import PairResearchResult\n", + " from research.research_tools import create_pairs\n", + "\n", + " # Create trading pair\n", + " PT_RESULTS = PairResearchResult(config=PT_BT_CONFIG)\n", + " PT_RESEARCH_STRATEGY = PtResearchStrategy(\n", + " config=PT_BT_CONFIG, datafiles=PT_BT_CONFIG[\"datafiles\"], instruments=list(INSTRUMENTS.values())\n", + " )\n", + "\n", + " PT_RESEARCH_STRATEGY.run()\n", + " PT_RESULTS.add_day_results(\n", + " day=TRADING_DATE,\n", + " trades=PT_RESEARCH_STRATEGY.day_trades(),\n", + " outstanding_positions=PT_RESEARCH_STRATEGY.outstanding_positions(),\n", + " )\n", + "\n", + "\n", + " pair = PT_RESEARCH_STRATEGY.trading_pair_\n", + " \n", + " print(f\"\\nCreated trading pair: {pair}\")\n", + " print(f\"Market data shape: {pair.market_data_.shape}\")\n", + " print(f\"Column names: {pair.colnames()}\")\n", + "\n", + " # Display sample data\n", + " print(f\"\\nSample data:\")\n", + " # with pd.option_context('display.max_rows', None, 'display.max_columns', None):\n", + " # print(pair.market_data_)\n", + " display(pair.market_data_.head())\n", + "\n", + " display(pair.market_data_.tail())\n", + "\n", + "# setup()\n", + "# prepare_config()\n", + "# run_strategy()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "vscode": { + "languageId": "raw" + } + }, + "source": [ + "## Visualize Raw Price Data\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def visualize_prices() -> None:\n", + " # Plot raw price data\n", + " global price_data\n", + " \n", + " import matplotlib.pyplot as plt\n", + " # Set plotting style\n", + " import seaborn as sns\n", + "\n", + " plt.style.use('seaborn-v0_8')\n", + " sns.set_palette(\"husl\")\n", + " plt.rcParams['figure.figsize'] = (15, 10)\n", + "\n", + " # Get column names for the trading pair\n", + " colname_a, colname_b = pair.colnames()\n", + " price_data = pair.market_data_.copy()\n", + "\n", + " # # 1. Price data - separate plots for each symbol\n", + " # colname_a, colname_b = pair.colnames()\n", + " # price_data = pair.market_data_.copy()\n", + "\n", + " # Create separate subplots for better visibility\n", + " fig_price, price_axes = plt.subplots(2, 1, figsize=(18, 10))\n", + "\n", + " # Plot SYMBOL_A\n", + " price_axes[0].plot(price_data['tstamp'], price_data[colname_a], alpha=0.7, \n", + " label=f'{SYMBOL_A}', linewidth=1, color='blue')\n", + " price_axes[0].set_title(f'{SYMBOL_A} Price Data ({TRD_DATE})')\n", + " price_axes[0].set_ylabel(f'{SYMBOL_A} Price')\n", + " price_axes[0].legend()\n", + " price_axes[0].grid(True)\n", + "\n", + " # Plot SYMBOL_B\n", + " price_axes[1].plot(price_data['tstamp'], price_data[colname_b], alpha=0.7, \n", + " label=f'{SYMBOL_B}', linewidth=1, color='red')\n", + " price_axes[1].set_title(f'{SYMBOL_B} Price Data ({TRD_DATE})')\n", + " price_axes[1].set_ylabel(f'{SYMBOL_B} Price')\n", + " price_axes[1].set_xlabel('Time')\n", + " price_axes[1].legend()\n", + " price_axes[1].grid(True)\n", + "\n", + " plt.tight_layout()\n", + " plt.show()\n", + " \n", + "\n", + " # Plot individual prices\n", + " fig, axes = plt.subplots(2, 1, figsize=(18, 12))\n", + "\n", + " # Normalized prices for comparison\n", + " norm_a = price_data[colname_a] / price_data[colname_a].iloc[0]\n", + " norm_b = price_data[colname_b] / price_data[colname_b].iloc[0]\n", + "\n", + " axes[0].plot(price_data['tstamp'], norm_a, label=f'{SYMBOL_A} (normalized)', alpha=0.8, linewidth=1)\n", + " axes[0].plot(price_data['tstamp'], norm_b, label=f'{SYMBOL_B} (normalized)', alpha=0.8, linewidth=1)\n", + " axes[0].set_title(f'Normalized Price Comparison (Base = 1.0) ({TRD_DATE})')\n", + " axes[0].set_ylabel('Normalized Price')\n", + " axes[0].legend()\n", + " axes[0].grid(True)\n", + "\n", + " # Price ratio\n", + " price_ratio = price_data[colname_a] / price_data[colname_b]\n", + " axes[1].plot(price_data['tstamp'], price_ratio, label=f'{SYMBOL_A}/{SYMBOL_B} Ratio', color='green', alpha=0.8, linewidth=1)\n", + " axes[1].set_title(f'Price Ratio Px({SYMBOL_A})/Px({SYMBOL_B}) ({TRD_DATE})')\n", + " axes[1].set_ylabel('Ratio')\n", + " axes[1].set_xlabel('Time')\n", + " axes[1].legend()\n", + " axes[1].grid(True)\n", + "\n", + " plt.tight_layout()\n", + " plt.show()\n", + "\n", + " # Print basic statistics\n", + " print(f\"\\nPrice Statistics:\")\n", + " print(f\" {SYMBOL_A}: Mean=${price_data[colname_a].mean():.2f}, Std=${price_data[colname_a].std():.2f}\")\n", + " print(f\" {SYMBOL_B}: Mean=${price_data[colname_b].mean():.2f}, Std=${price_data[colname_b].std():.2f}\")\n", + " print(f\" Price Ratio: Mean={price_ratio.mean():.2f}, Std={price_ratio.std():.2f}\")\n", + " print(f\" Correlation: {price_data[colname_a].corr(price_data[colname_b]):.4f}\")\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Visualization" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def visualize_trades() -> None:\n", + " # global price_data\n", + " # global pair_trades\n", + " global PT_BT_CONFIG\n", + " global SYMBOL_A\n", + " global SYMBOL_B\n", + " global TRD_DATE\n", + " global PREDICTED_RESULT\n", + " global PT_RESEARCH_STRATEGY\n", + " global PT_RESULTS\n", + "\n", + " import plotly.graph_objects as go\n", + " from plotly.subplots import make_subplots\n", + " import plotly.express as px\n", + " import plotly.offline as pyo\n", + " from IPython.display import HTML\n", + " import pandas as pd\n", + "\n", + "\n", + " pair = PT_RESEARCH_STRATEGY.trading_pair_\n", + " pair_trades = PT_RESULTS.trades_[TRADING_DATE]\n", + " outstanding_positions = PT_RESULTS.outstanding_positions_[TRADING_DATE]\n", + "\n", + " print(f\"\\nCreated trading pair: {pair}\")\n", + " print(f\"Market data shape: {pair.market_data_.shape}\")\n", + " print(f\"Column names: {pair.colnames()}\")\n", + "\n", + " # Configure plotly for offline mode\n", + " pyo.init_notebook_mode(connected=True)\n", + "\n", + " # Strategy-specific interactive visualization\n", + " assert PT_BT_CONFIG is not None\n", + "\n", + " print(\"=== SLIDING FIT INTERACTIVE VISUALIZATION ===\")\n", + " print(\"Note: Rolling Fit strategy visualization with interactive plotly charts\")\n", + "\n", + " # Create consistent timeline - superset of timestamps from both dataframes\n", + " market_timestamps = set(pair.market_data_['tstamp'])\n", + " predicted_timestamps = set(PREDICTED_RESULT['tstamp'])\n", + "\n", + " # Create superset of all timestamps\n", + " all_timestamps = sorted(market_timestamps.union(predicted_timestamps))\n", + "\n", + " # Create a unified timeline dataframe for consistent plotting\n", + " timeline_df = pd.DataFrame({'tstamp': all_timestamps})\n", + "\n", + " # Merge with predicted data to get dis-equilibrium values\n", + " timeline_df = timeline_df.merge(PREDICTED_RESULT[['tstamp', 'disequilibrium', 'scaled_disequilibrium', 'signed_scaled_disequilibrium']], \n", + " on='tstamp', how='left')\n", + "\n", + " # Get Symbol_A and Symbol_B market data\n", + " colname_a, colname_b = pair.colnames()\n", + " symbol_a_data = pair.original_market_data_[['tstamp', colname_a]].copy()\n", + " symbol_b_data = pair.original_market_data_[['tstamp', colname_b]].copy()\n", + "\n", + " norm_a = price_data[colname_a] / price_data[colname_a].iloc[0]\n", + " norm_b = price_data[colname_b] / price_data[colname_b].iloc[0]\n", + "\n", + " print(f\"Using consistent timeline with {len(timeline_df)} timestamps\")\n", + " print(f\"Timeline range: {timeline_df['tstamp'].min()} to {timeline_df['tstamp'].max()}\")\n", + "\n", + " # Create subplots with price charts at bottom\n", + " fig = make_subplots(\n", + " rows=4, cols=1,\n", + " row_heights=[0.3, 0.4, 0.15, 0.15],\n", + " subplot_titles=[\n", + " f'Dis-equilibrium with Trading Thresholds ({TRD_DATE})',\n", + " f'Normalized Price Comparison with BUY/SELL Signals - {SYMBOL_A}&{SYMBOL_B} ({TRD_DATE})',\n", + " f'{SYMBOL_A} Market Data with Trading Signals ({TRD_DATE})',\n", + " f'{SYMBOL_B} Market Data with Trading Signals ({TRD_DATE})',\n", + " ],\n", + " vertical_spacing=0.06,\n", + " specs=[[{\"secondary_y\": False}],\n", + " [{\"secondary_y\": False}],\n", + " [{\"secondary_y\": False}],\n", + " [{\"secondary_y\": False}]]\n", + " )\n", + "\n", + " # 1. Scaled dis-equilibrium with thresholds - using consistent timeline\n", + " fig.add_trace(\n", + " go.Scatter(\n", + " x=timeline_df['tstamp'],\n", + " y=timeline_df['scaled_disequilibrium'],\n", + " name='Absolute Scaled Dis-equilibrium',\n", + " line=dict(color='green', width=2),\n", + " opacity=0.8\n", + " ),\n", + " row=1, col=1\n", + " )\n", + "\n", + " fig.add_trace(\n", + " go.Scatter(\n", + " x=timeline_df['tstamp'],\n", + " y=timeline_df['signed_scaled_disequilibrium'],\n", + " name='Scaled Dis-equilibrium',\n", + " line=dict(color='darkmagenta', width=2),\n", + " opacity=0.8\n", + " ),\n", + " row=1, col=1\n", + " )\n", + "\n", + " # Add threshold lines to first subplot\n", + " fig.add_shape(\n", + " type=\"line\",\n", + " x0=timeline_df['tstamp'].min(),\n", + " x1=timeline_df['tstamp'].max(),\n", + " y0=PT_BT_CONFIG['dis-equilibrium_open_trshld'],\n", + " y1=PT_BT_CONFIG['dis-equilibrium_open_trshld'],\n", + " line=dict(color=\"purple\", width=2, dash=\"dot\"),\n", + " opacity=0.7,\n", + " row=1, col=1\n", + " )\n", + "\n", + " fig.add_shape(\n", + " type=\"line\",\n", + " x0=timeline_df['tstamp'].min(),\n", + " x1=timeline_df['tstamp'].max(),\n", + " y0=-PT_BT_CONFIG['dis-equilibrium_open_trshld'],\n", + " y1=-PT_BT_CONFIG['dis-equilibrium_open_trshld'],\n", + " line=dict(color=\"purple\", width=2, dash=\"dot\"),\n", + " opacity=0.7,\n", + " row=1, col=1\n", + " )\n", + "\n", + " fig.add_shape(\n", + " type=\"line\",\n", + " x0=timeline_df['tstamp'].min(),\n", + " x1=timeline_df['tstamp'].max(),\n", + " y0=PT_BT_CONFIG['dis-equilibrium_close_trshld'],\n", + " y1=PT_BT_CONFIG['dis-equilibrium_close_trshld'],\n", + " line=dict(color=\"brown\", width=2, dash=\"dot\"),\n", + " opacity=0.7,\n", + " row=1, col=1\n", + " )\n", + "\n", + " fig.add_shape(\n", + " type=\"line\",\n", + " x0=timeline_df['tstamp'].min(),\n", + " x1=timeline_df['tstamp'].max(),\n", + " y0=-PT_BT_CONFIG['dis-equilibrium_close_trshld'],\n", + " y1=-PT_BT_CONFIG['dis-equilibrium_close_trshld'],\n", + " line=dict(color=\"brown\", width=2, dash=\"dot\"),\n", + " opacity=0.7,\n", + " row=1, col=1\n", + " )\n", + "\n", + " fig.add_shape(\n", + " type=\"line\",\n", + " x0=timeline_df['tstamp'].min(),\n", + " x1=timeline_df['tstamp'].max(),\n", + " y0=0,\n", + " y1=0,\n", + " line=dict(color=\"black\", width=1, dash=\"solid\"),\n", + " opacity=0.5,\n", + " row=1, col=1\n", + " )\n", + "\n", + " # Add normalized price lines\n", + " fig.add_trace(\n", + " go.Scatter(\n", + " x=price_data['tstamp'],\n", + " y=norm_a,\n", + " name=f'{SYMBOL_A} (Normalized)',\n", + " line=dict(color='blue', width=2),\n", + " opacity=0.8\n", + " ),\n", + " row=2, col=1\n", + " )\n", + "\n", + " fig.add_trace(\n", + " go.Scatter(\n", + " x=price_data['tstamp'],\n", + " y=norm_b,\n", + " name=f'{SYMBOL_B} (Normalized)',\n", + " line=dict(color='orange', width=2),\n", + " opacity=0.8,\n", + " ),\n", + " row=2, col=1\n", + " )\n", + "\n", + " # Add BUY and SELL signals if available\n", + " if pair_trades is not None and len(pair_trades) > 0:\n", + " # Define signal groups to avoid legend repetition\n", + " signal_groups = {}\n", + " \n", + " # Process all trades and group by signal type (ignore OPEN/CLOSE status)\n", + " for _, trade in pair_trades.iterrows():\n", + " symbol = trade['symbol']\n", + " side = trade['side']\n", + " # status = trade['status']\n", + " action = trade['action']\n", + " \n", + " # Create signal group key (without status to combine OPEN/CLOSE)\n", + " signal_key = f\"{symbol} {side} {action}\"\n", + " \n", + " # Find normalized price for this trade\n", + " trade_time = trade['time']\n", + " if symbol == SYMBOL_A:\n", + " closest_idx = price_data['tstamp'].searchsorted(trade_time)\n", + " if closest_idx < len(norm_a):\n", + " norm_price = norm_a.iloc[closest_idx]\n", + " else:\n", + " norm_price = norm_a.iloc[-1]\n", + " else: # SYMBOL_B\n", + " closest_idx = price_data['tstamp'].searchsorted(trade_time)\n", + " if closest_idx < len(norm_b):\n", + " norm_price = norm_b.iloc[closest_idx]\n", + " else:\n", + " norm_price = norm_b.iloc[-1]\n", + " \n", + " # Initialize group if not exists\n", + " if signal_key not in signal_groups:\n", + " signal_groups[signal_key] = {\n", + " 'times': [],\n", + " 'prices': [],\n", + " 'actual_prices': [],\n", + " 'symbol': symbol,\n", + " 'side': side,\n", + " # 'status': status,\n", + " 'action': trade['action']\n", + " }\n", + " \n", + " # Add to group\n", + " signal_groups[signal_key]['times'].append(trade_time)\n", + " signal_groups[signal_key]['prices'].append(norm_price)\n", + " signal_groups[signal_key]['actual_prices'].append(trade['price'])\n", + " \n", + " # Add each signal group as a single trace\n", + " for signal_key, group_data in signal_groups.items():\n", + " symbol = group_data['symbol']\n", + " side = group_data['side']\n", + " # status = group_data['status']\n", + " \n", + " # Determine marker properties (same for all OPEN/CLOSE of same side)\n", + " is_close: bool = (group_data['action'] == \"CLOSE\")\n", + " \n", + " if 'BUY' in side:\n", + " marker_color = 'green'\n", + " marker_symbol = 'triangle-up'\n", + " marker_size = 14\n", + " else: # SELL\n", + " marker_color = 'red'\n", + " marker_symbol = 'triangle-down'\n", + " marker_size = 14\n", + " \n", + " # Create hover text for each point in the group\n", + " hover_texts = []\n", + " for i, (time, norm_price, actual_price) in enumerate(zip(group_data['times'], \n", + " group_data['prices'], \n", + " group_data['actual_prices'])):\n", + " # Find the corresponding trade to get the status for hover text\n", + " trade_info = pair_trades[(pair_trades['time'] == time) & \n", + " (pair_trades['symbol'] == symbol) & \n", + " (pair_trades['side'] == side)]\n", + " if len(trade_info) > 0:\n", + " action = trade_info.iloc[0]['action']\n", + " hover_texts.append(f'{signal_key} {action}
' +\n", + " f'Time: {time}
' +\n", + " f'Normalized Price: {norm_price:.4f}
' +\n", + " f'Actual Price: ${actual_price:.2f}')\n", + " else:\n", + " hover_texts.append(f'{signal_key}
' +\n", + " f'Time: {time}
' +\n", + " f'Normalized Price: {norm_price:.4f}
' +\n", + " f'Actual Price: ${actual_price:.2f}')\n", + " \n", + " fig.add_trace(\n", + " go.Scatter(\n", + " x=group_data['times'],\n", + " y=group_data['prices'],\n", + " mode='markers',\n", + " name=signal_key,\n", + " marker=dict(\n", + " color=marker_color,\n", + " size=marker_size,\n", + " symbol=marker_symbol,\n", + " line=dict(width=2, color='black') if is_close else None\n", + " ),\n", + " showlegend=True,\n", + " hovertemplate='%{text}',\n", + " text=hover_texts\n", + " ),\n", + " row=2, col=1\n", + " )\n", + "\n", + " # ----------------------------- \n", + " \n", + " fig.add_trace(\n", + " go.Scatter(\n", + " x=symbol_a_data['tstamp'],\n", + " y=symbol_a_data[colname_a],\n", + " name=f'{SYMBOL_A} Price',\n", + " line=dict(color='blue', width=2),\n", + " opacity=0.8\n", + " ),\n", + " row=3, col=1\n", + " )\n", + "\n", + " # Filter trades for Symbol_A\n", + " symbol_a_trades = pair_trades[pair_trades['symbol'] == SYMBOL_A]\n", + " print(f\"\\nSymbol_A trades:\\n{symbol_a_trades}\")\n", + " \n", + " if len(symbol_a_trades) > 0:\n", + " # Separate trades by action and status for different colors\n", + " buy_open_trades = symbol_a_trades[(symbol_a_trades['side'].str.contains('BUY', na=False)) & \n", + " (symbol_a_trades['action'].str.contains('OPEN', na=False))]\n", + " buy_close_trades = symbol_a_trades[(symbol_a_trades['side'].str.contains('BUY', na=False)) & \n", + " (symbol_a_trades['action'].str.contains('CLOSE', na=False))]\n", + " \n", + " sell_open_trades = symbol_a_trades[(symbol_a_trades['side'].str.contains('SELL', na=False)) & \n", + " (symbol_a_trades['action'].str.contains('OPEN', na=False))]\n", + " sell_close_trades = symbol_a_trades[(symbol_a_trades['side'].str.contains('SELL', na=False)) & \n", + " (symbol_a_trades['action'].str.contains('CLOSE', na=False))]\n", + " \n", + " # Add BUY OPEN signals\n", + " if len(buy_open_trades) > 0:\n", + " fig.add_trace(\n", + " go.Scatter(\n", + " x=buy_open_trades['time'],\n", + " y=buy_open_trades['price'],\n", + " mode='markers',\n", + " name=f'{SYMBOL_A} BUY OPEN',\n", + " marker=dict(color='green', size=12, symbol='triangle-up'),\n", + " showlegend=True\n", + " ),\n", + " row=3, col=1\n", + " )\n", + " \n", + " # Add BUY CLOSE signals\n", + " if len(buy_close_trades) > 0:\n", + " fig.add_trace(\n", + " go.Scatter(\n", + " x=buy_close_trades['time'],\n", + " y=buy_close_trades['price'],\n", + " mode='markers',\n", + " name=f'{SYMBOL_A} BUY CLOSE',\n", + " marker=dict(color='green', size=12, symbol='triangle-up'),\n", + " line=dict(width=2, color='black'),\n", + " showlegend=True\n", + " ),\n", + " row=3, col=1\n", + " )\n", + " \n", + " # Add SELL OPEN signals\n", + " if len(sell_open_trades) > 0:\n", + " fig.add_trace(\n", + " go.Scatter(\n", + " x=sell_open_trades['time'],\n", + " y=sell_open_trades['price'],\n", + " mode='markers',\n", + " name=f'{SYMBOL_A} SELL OPEN',\n", + " marker=dict(color='red', size=12, symbol='triangle-down'),\n", + " showlegend=True\n", + " ),\n", + " row=3, col=1\n", + " )\n", + " \n", + " # Add SELL CLOSE signals\n", + " if len(sell_close_trades) > 0:\n", + " fig.add_trace(\n", + " go.Scatter(\n", + " x=sell_close_trades['time'],\n", + " y=sell_close_trades['price'],\n", + " mode='markers',\n", + " name=f'{SYMBOL_A} SELL CLOSE',\n", + " marker=dict(color='red', size=12, symbol='triangle-down'),\n", + " line=dict(width=2, color='black'),\n", + " showlegend=True\n", + " ),\n", + " row=3, col=1\n", + " )\n", + " \n", + " # 4. Symbol_B Market Data with Trading Signals\n", + " fig.add_trace(\n", + " go.Scatter(\n", + " x=symbol_b_data['tstamp'],\n", + " y=symbol_b_data[colname_b],\n", + " name=f'{SYMBOL_B} Price',\n", + " line=dict(color='orange', width=2),\n", + " opacity=0.8\n", + " ),\n", + " row=4, col=1\n", + " )\n", + " \n", + " # Add trading signals for Symbol_B if available\n", + " symbol_b_trades = pair_trades[pair_trades['symbol'] == SYMBOL_B]\n", + " print(f\"\\nSymbol_B trades:\\n{symbol_b_trades}\")\n", + " \n", + " if len(symbol_b_trades) > 0:\n", + " # Separate trades by action and status for different colors\n", + " buy_open_trades = symbol_b_trades[(symbol_b_trades['side'].str.contains('BUY', na=False)) & \n", + " (symbol_b_trades['action'].str.startswith('OPEN', na=False))]\n", + " buy_close_trades = symbol_b_trades[(symbol_b_trades['side'].str.contains('BUY', na=False)) & \n", + " (symbol_b_trades['action'].str.startswith('CLOSE', na=False))]\n", + " \n", + " sell_open_trades = symbol_b_trades[(symbol_b_trades['side'].str.contains('SELL', na=False)) & \n", + " (symbol_b_trades['action'].str.contains('OPEN', na=False))]\n", + " sell_close_trades = symbol_b_trades[(symbol_b_trades['side'].str.contains('SELL', na=False)) & \n", + " (symbol_b_trades['action'].str.contains('CLOSE', na=False))]\n", + " \n", + " # Add BUY OPEN signals\n", + " if len(buy_open_trades) > 0:\n", + " fig.add_trace(\n", + " go.Scatter(\n", + " x=buy_open_trades['time'],\n", + " y=buy_open_trades['price'],\n", + " mode='markers',\n", + " name=f'{SYMBOL_B} BUY OPEN',\n", + " marker=dict(color='darkgreen', size=12, symbol='triangle-up'),\n", + " showlegend=True\n", + " ),\n", + " row=4, col=1\n", + " )\n", + " \n", + " # Add BUY CLOSE signals\n", + " if len(buy_close_trades) > 0:\n", + " fig.add_trace(\n", + " go.Scatter(\n", + " x=buy_close_trades['time'],\n", + " y=buy_close_trades['price'],\n", + " mode='markers',\n", + " name=f'{SYMBOL_B} BUY CLOSE',\n", + " marker=dict(color='green', size=12, symbol='triangle-up'),\n", + " line=dict(width=2, color='black'),\n", + " showlegend=True\n", + " ),\n", + " row=4, col=1\n", + " )\n", + " \n", + " # Add SELL OPEN signals\n", + " if len(sell_open_trades) > 0:\n", + " fig.add_trace(\n", + " go.Scatter(\n", + " x=sell_open_trades['time'],\n", + " y=sell_open_trades['price'],\n", + " mode='markers',\n", + " name=f'{SYMBOL_B} SELL OPEN',\n", + " marker=dict(color='red', size=12, symbol='triangle-down'),\n", + " showlegend=True\n", + " ),\n", + " row=4, col=1\n", + " )\n", + " \n", + " # Add SELL CLOSE signals\n", + " if len(sell_close_trades) > 0:\n", + " fig.add_trace(\n", + " go.Scatter(\n", + " x=sell_close_trades['time'],\n", + " y=sell_close_trades['price'],\n", + " mode='markers',\n", + " name=f'{SYMBOL_B} SELL CLOSE',\n", + " marker=dict(color='red', size=12, symbol='triangle-down'),\n", + " line=dict(width=2, color='black'),\n", + " showlegend=True\n", + " ),\n", + " row=4, col=1\n", + " )\n", + " \n", + " # Update layout\n", + " fig.update_layout(\n", + " height=1600,\n", + " title_text=f\"Strategy Analysis - {SYMBOL_A} & {SYMBOL_B} ({TRD_DATE})\",\n", + " showlegend=True,\n", + " template=\"plotly_white\",\n", + " plot_bgcolor='lightgray',\n", + " )\n", + " \n", + " # Update y-axis labels\n", + " fig.update_yaxes(title_text=\"Scaled Dis-equilibrium\", row=1, col=1)\n", + " fig.update_yaxes(title_text=f\"{SYMBOL_A} Price ($)\", row=2, col=1)\n", + " fig.update_yaxes(title_text=f\"{SYMBOL_B} Price ($)\", row=3, col=1)\n", + " fig.update_yaxes(title_text=\"Normalized Price (Base = 1.0)\", row=4, col=1)\n", + " \n", + " # Update x-axis labels and ensure consistent time range\n", + " time_range = [timeline_df['tstamp'].min(), timeline_df['tstamp'].max()]\n", + " fig.update_xaxes(range=time_range, row=1, col=1)\n", + " fig.update_xaxes(range=time_range, row=2, col=1)\n", + " fig.update_xaxes(range=time_range, row=3, col=1)\n", + " fig.update_xaxes(title_text=\"Time\", range=time_range, row=4, col=1)\n", + " \n", + " # Display using plotly offline mode\n", + " # pyo.iplot(fig)\n", + " fig.show()\n", + " \n", + " else:\n", + " print(\"No interactive visualization data available - strategy may not have run successfully\")\n", + "\n", + " print(f\"\\nChart shows:\")\n", + " print(f\"- {SYMBOL_A} and {SYMBOL_B} prices normalized to start at 1.0\")\n", + " print(f\"- BUY signals shown as green triangles pointing up\")\n", + " print(f\"- SELL signals shown as orange triangles pointing down\")\n", + " print(f\"- All BUY signals per symbol grouped together, all SELL signals per symbol grouped together\")\n", + " print(f\"- Hover over markers to see individual trade details (OPEN/CLOSE status)\")\n", + "\n", + " if pair_trades is not None and len(pair_trades) > 0:\n", + " print(f\"- Total signals displayed: {len(pair_trades)}\")\n", + " print(f\"- {SYMBOL_A} signals: {len(pair_trades[pair_trades['symbol'] == SYMBOL_A])}\")\n", + " print(f\"- {SYMBOL_B} signals: {len(pair_trades[pair_trades['symbol'] == SYMBOL_B])}\")\n", + " else:\n", + " print(\"- No trading signals to display\")\n", + "\n", + "# visualization()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "vscode": { + "languageId": "raw" + } + }, + "source": [ + "## Summary\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def summary() -> None:\n", + " global PT_RESULTS\n", + " \n", + " PT_RESULTS.analyze_pair_performance()\n", + " \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Performance" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def performance_results() -> None:\n", + " global pair_trades\n", + " global bt_result\n", + " global SYMBOL_A\n", + " global SYMBOL_B\n", + " global FIT_METHOD_TYPE\n", + " global PT_BT_CONFIG\n", + "\n", + " from pt_trading.results import BacktestResult\n", + "\n", + " if pair_trades is not None and len(pair_trades) > 0:\n", + " # Print detailed results using BacktestResult methods\n", + " # bt_result.print_single_day_results()\n", + " \n", + " # Print trading signal details\n", + " print(f\"\\nDetailed Trading Signals:\")\n", + " print(f\"{'Time':<20} {'Action':<15} {'Symbol':<10} {'Price':<12} {'Scaled Dis-eq':<15} {'Status':<10}\")\n", + " print(\"-\" * 90)\n", + " \n", + " for _, trade in pair_trades.head(10).iterrows(): # Show first 10 trades\n", + " time_str = str(trade['time'])[:19] \n", + " action_str = str(trade['action'])[:14]\n", + " symbol_str = str(trade['symbol'])[:9]\n", + " price_str = f\"${trade['price']:.2f}\"\n", + " diseq_str = f\"{trade.get('scaled_disequilibrium', 'N/A'):.3f}\" if 'scaled_disequilibrium' in trade else 'N/A'\n", + " status = trade.get('status', 'N/A')\n", + " \n", + " print(f\"{time_str:<20} {action_str:<15} {symbol_str:<10} {price_str:<12} {diseq_str:<15} {status:<10}\")\n", + " \n", + " if len(pair_trades) > 10:\n", + " print(f\"... and {len(pair_trades)-10} more trading signals\")\n", + " \n", + " bt_result.collect_single_day_results([pair_trades])\n", + "\n", + " # bt_result.print_grand_totals()\n", + " # bt_result.print_outstanding_positions() \n", + " else:\n", + " print(f\"\\nNo trading signals generated\")\n", + " print(f\"Backtest completed with no trades\")\n", + " \n", + " # Still print any outstanding information\n", + " print(f\"\\nConfiguration Summary:\")\n", + " print(f\" Pair: {SYMBOL_A} & {SYMBOL_B}\")\n", + " print(f\" Strategy: {FIT_METHOD_TYPE}\")\n", + " print(f\" Open threshold: {PT_BT_CONFIG['dis-equilibrium_open_trshld']}\")\n", + " print(f\" Close threshold: {PT_BT_CONFIG['dis-equilibrium_close_trshld']}\")\n", + " print(f\" Training window: {PT_BT_CONFIG['training_minutes']} minutes\")\n", + " \n", + " print(\"\\n\" + \"=\"*80)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "def print_summary():\n", + " global pair_trades\n", + "\n", + " from pt_trading.results import BacktestResult\n", + "\n", + " if pair_trades is not None and len(pair_trades) > 0:\n", + " all_results: Dict[str, Dict[str, Any]] = {}\n", + " all_results[f\"{TRADING_DATE}-{pair.name()}\"] = {\n", + " \"trades\": bt_result.trades.copy(), \n", + " \"outstanding_positions\": bt_result.outstanding_positions.copy()\n", + " }\n", + "\n", + " if all_results:\n", + " aggregate_bt_results = BacktestResult(config=PT_BT_CONFIG)\n", + " aggregate_bt_results.calculate_returns(all_results)\n", + " aggregate_bt_results.print_grand_totals()\n", + " aggregate_bt_results.print_outstanding_positions()\n", + "\n", + "\n", + " \n", + "# performance_results()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Run" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "setup()\n", + "load_config_from_file()\n", + "prepare_config()\n", + "run_strategy()\n", + "visualize_prices()\n", + "visualize_trades()\n", + "summary() \n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "python3.12-venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/research/notebooks/single_pair_test.ipynb b/research/notebooks/single_pair_test.ipynb index 1340ba1..aa08284 100644 --- a/research/notebooks/single_pair_test.ipynb +++ b/research/notebooks/single_pair_test.ipynb @@ -393,40 +393,40 @@ " 2025-06-02 11:30:00\n", " 0.6670\n", " 154.01\n", - " 0.6673\n", - " 154.12\n", + " 0.6670\n", + " 154.01\n", " \n", " \n", " 1\n", " 2025-06-02 11:31:00\n", " 0.6673\n", " 154.12\n", - " 0.6677\n", - " 154.16\n", + " 0.6673\n", + " 154.12\n", " \n", " \n", " 2\n", " 2025-06-02 11:32:00\n", " 0.6677\n", " 154.16\n", - " 0.6679\n", - " 154.22\n", + " 0.6677\n", + " 154.16\n", " \n", " \n", " 3\n", " 2025-06-02 11:33:00\n", " 0.6679\n", " 154.22\n", - " 0.6670\n", - " 154.06\n", + " 0.6679\n", + " 154.22\n", " \n", " \n", " 4\n", " 2025-06-02 11:34:00\n", " 0.6670\n", " 154.06\n", - " 0.6680\n", - " 154.20\n", + " 0.6670\n", + " 154.06\n", " \n", " \n", "\n", @@ -434,11 +434,11 @@ ], "text/plain": [ " tstamp close_ADA-USDT close_SOL-USDT exec_price_ADA-USDT exec_price_SOL-USDT\n", - "0 2025-06-02 11:30:00 0.6670 154.01 0.6673 154.12\n", - "1 2025-06-02 11:31:00 0.6673 154.12 0.6677 154.16\n", - "2 2025-06-02 11:32:00 0.6677 154.16 0.6679 154.22\n", - "3 2025-06-02 11:33:00 0.6679 154.22 0.6670 154.06\n", - "4 2025-06-02 11:34:00 0.6670 154.06 0.6680 154.20" + "0 2025-06-02 11:30:00 0.6670 154.01 0.6670 154.01\n", + "1 2025-06-02 11:31:00 0.6673 154.12 0.6673 154.12\n", + "2 2025-06-02 11:32:00 0.6677 154.16 0.6677 154.16\n", + "3 2025-06-02 11:33:00 0.6679 154.22 0.6679 154.22\n", + "4 2025-06-02 11:34:00 0.6670 154.06 0.6670 154.06" ] }, "metadata": {}, @@ -474,44 +474,44 @@ " \n", " \n", " \n", - " 654\n", + " 504\n", " 2025-06-02 22:25:00\n", " 0.6919\n", " 156.70\n", - " 0.6917\n", - " 156.72\n", + " 0.6919\n", + " 156.70\n", " \n", " \n", - " 655\n", + " 505\n", " 2025-06-02 22:26:00\n", " 0.6917\n", " 156.72\n", - " 0.6909\n", - " 156.57\n", + " 0.6917\n", + " 156.72\n", " \n", " \n", - " 656\n", + " 506\n", " 2025-06-02 22:27:00\n", " 0.6909\n", " 156.57\n", - " 0.6908\n", - " 156.65\n", + " 0.6909\n", + " 156.57\n", " \n", " \n", - " 657\n", + " 507\n", " 2025-06-02 22:28:00\n", " 0.6908\n", " 156.65\n", - " 0.6910\n", - " 156.75\n", + " 0.6908\n", + " 156.65\n", " \n", " \n", - " 658\n", + " 508\n", " 2025-06-02 22:29:00\n", " 0.6910\n", " 156.75\n", - " 0.6908\n", - " 156.70\n", + " 0.6910\n", + " 156.75\n", " \n", " \n", "\n", @@ -519,11 +519,11 @@ ], "text/plain": [ " tstamp close_ADA-USDT close_SOL-USDT exec_price_ADA-USDT exec_price_SOL-USDT\n", - "654 2025-06-02 22:25:00 0.6919 156.70 0.6917 156.72\n", - "655 2025-06-02 22:26:00 0.6917 156.72 0.6909 156.57\n", - "656 2025-06-02 22:27:00 0.6909 156.57 0.6908 156.65\n", - "657 2025-06-02 22:28:00 0.6908 156.65 0.6910 156.75\n", - "658 2025-06-02 22:29:00 0.6910 156.75 0.6908 156.70" + "504 2025-06-02 22:25:00 0.6919 156.70 0.6919 156.70\n", + "505 2025-06-02 22:26:00 0.6917 156.72 0.6917 156.72\n", + "506 2025-06-02 22:27:00 0.6909 156.57 0.6909 156.57\n", + "507 2025-06-02 22:28:00 0.6908 156.65 0.6908 156.65\n", + "508 2025-06-02 22:29:00 0.6910 156.75 0.6910 156.75" ] }, "metadata": {}, @@ -1539,40 +1539,40 @@ " 2025-06-02 11:30:00\n", " 0.6670\n", " 154.01\n", - " 0.6673\n", - " 154.12\n", + " 0.6670\n", + " 154.01\n", " \n", " \n", " 1\n", " 2025-06-02 11:31:00\n", " 0.6673\n", " 154.12\n", - " 0.6677\n", - " 154.16\n", + " 0.6673\n", + " 154.12\n", " \n", " \n", " 2\n", " 2025-06-02 11:32:00\n", " 0.6677\n", " 154.16\n", - " 0.6679\n", - " 154.22\n", + " 0.6677\n", + " 154.16\n", " \n", " \n", " 3\n", " 2025-06-02 11:33:00\n", " 0.6679\n", " 154.22\n", - " 0.6670\n", - " 154.06\n", + " 0.6679\n", + " 154.22\n", " \n", " \n", " 4\n", " 2025-06-02 11:34:00\n", " 0.6670\n", " 154.06\n", - " 0.6680\n", - " 154.20\n", + " 0.6670\n", + " 154.06\n", " \n", " \n", "\n", @@ -1580,11 +1580,11 @@ ], "text/plain": [ " tstamp close_ADA-USDT close_SOL-USDT exec_price_ADA-USDT exec_price_SOL-USDT\n", - "0 2025-06-02 11:30:00 0.6670 154.01 0.6673 154.12\n", - "1 2025-06-02 11:31:00 0.6673 154.12 0.6677 154.16\n", - "2 2025-06-02 11:32:00 0.6677 154.16 0.6679 154.22\n", - "3 2025-06-02 11:33:00 0.6679 154.22 0.6670 154.06\n", - "4 2025-06-02 11:34:00 0.6670 154.06 0.6680 154.20" + "0 2025-06-02 11:30:00 0.6670 154.01 0.6670 154.01\n", + "1 2025-06-02 11:31:00 0.6673 154.12 0.6673 154.12\n", + "2 2025-06-02 11:32:00 0.6677 154.16 0.6677 154.16\n", + "3 2025-06-02 11:33:00 0.6679 154.22 0.6679 154.22\n", + "4 2025-06-02 11:34:00 0.6670 154.06 0.6670 154.06" ] }, "metadata": {}, @@ -1620,44 +1620,44 @@ " \n", " \n", " \n", - " 654\n", + " 504\n", " 2025-06-02 22:25:00\n", " 0.6919\n", " 156.70\n", - " 0.6917\n", - " 156.72\n", + " 0.6919\n", + " 156.70\n", " \n", " \n", - " 655\n", + " 505\n", " 2025-06-02 22:26:00\n", " 0.6917\n", " 156.72\n", - " 0.6909\n", - " 156.57\n", + " 0.6917\n", + " 156.72\n", " \n", " \n", - " 656\n", + " 506\n", " 2025-06-02 22:27:00\n", " 0.6909\n", " 156.57\n", - " 0.6908\n", - " 156.65\n", + " 0.6909\n", + " 156.57\n", " \n", " \n", - " 657\n", + " 507\n", " 2025-06-02 22:28:00\n", " 0.6908\n", " 156.65\n", - " 0.6910\n", - " 156.75\n", + " 0.6908\n", + " 156.65\n", " \n", " \n", - " 658\n", + " 508\n", " 2025-06-02 22:29:00\n", " 0.6910\n", " 156.75\n", - " 0.6908\n", - " 156.70\n", + " 0.6910\n", + " 156.75\n", " \n", " \n", "\n", @@ -1665,11 +1665,11 @@ ], "text/plain": [ " tstamp close_ADA-USDT close_SOL-USDT exec_price_ADA-USDT exec_price_SOL-USDT\n", - "654 2025-06-02 22:25:00 0.6919 156.70 0.6917 156.72\n", - "655 2025-06-02 22:26:00 0.6917 156.72 0.6909 156.57\n", - "656 2025-06-02 22:27:00 0.6909 156.57 0.6908 156.65\n", - "657 2025-06-02 22:28:00 0.6908 156.65 0.6910 156.75\n", - "658 2025-06-02 22:29:00 0.6910 156.75 0.6908 156.70" + "504 2025-06-02 22:25:00 0.6919 156.70 0.6919 156.70\n", + "505 2025-06-02 22:26:00 0.6917 156.72 0.6917 156.72\n", + "506 2025-06-02 22:27:00 0.6909 156.57 0.6909 156.57\n", + "507 2025-06-02 22:28:00 0.6908 156.65 0.6908 156.65\n", + "508 2025-06-02 22:29:00 0.6910 156.75 0.6910 156.75" ] }, "metadata": {}, @@ -1737,69 +1737,86 @@ "OPEN_TRADES: 2025-06-02 13:37:00 open_scaled_disequilibrium=2.1525256289273242\n", "OPEN TRADES:\n", " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", - "0 2025-06-02 13:37:00 ADA-USDT BUY OPEN 0.6754 -2.152526 2.152526 -2.152526 ADA-USDT & SOL-USDT OPEN\n", - "1 2025-06-02 13:37:00 SOL-USDT SELL OPEN 154.3200 -2.152526 2.152526 -2.152526 ADA-USDT & SOL-USDT OPEN\n", + "0 2025-06-02 13:37:00 ADA-USDT BUY OPEN 0.6743 -2.152526 2.152526 -2.152526 ADA-USDT & SOL-USDT OPEN\n", + "1 2025-06-02 13:37:00 SOL-USDT SELL OPEN 154.1400 -2.152526 2.152526 -2.152526 ADA-USDT & SOL-USDT OPEN\n", "CLOSE TRADES:\n", " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", - "0 2025-06-02 14:37:00 ADA-USDT SELL CLOSE 0.6734 -0.280199 0.280199 -0.280199 ADA-USDT & SOL-USDT CLOSE\n", - "1 2025-06-02 14:37:00 SOL-USDT BUY CLOSE 153.7000 -0.280199 0.280199 -0.280199 ADA-USDT & SOL-USDT CLOSE\n", + "0 2025-06-02 14:37:00 ADA-USDT SELL CLOSE 0.6746 -0.280199 0.280199 -0.280199 ADA-USDT & SOL-USDT CLOSE\n", + "1 2025-06-02 14:37:00 SOL-USDT BUY CLOSE 154.0000 -0.280199 0.280199 -0.280199 ADA-USDT & SOL-USDT CLOSE\n", "OPEN_TRADES: 2025-06-02 15:09:00 open_scaled_disequilibrium=2.5218125398969375\n", "OPEN TRADES:\n", " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", - "0 2025-06-02 15:09:00 ADA-USDT BUY OPEN 0.6691 -2.521813 2.521813 -2.521813 ADA-USDT & SOL-USDT OPEN\n", - "1 2025-06-02 15:09:00 SOL-USDT SELL OPEN 152.1800 -2.521813 2.521813 -2.521813 ADA-USDT & SOL-USDT OPEN\n", + "0 2025-06-02 15:09:00 ADA-USDT BUY OPEN 0.6689 -2.521813 2.521813 -2.521813 ADA-USDT & SOL-USDT OPEN\n", + "1 2025-06-02 15:09:00 SOL-USDT SELL OPEN 152.1200 -2.521813 2.521813 -2.521813 ADA-USDT & SOL-USDT OPEN\n", "CLOSE TRADES:\n", " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", - "0 2025-06-02 15:41:00 ADA-USDT SELL CLOSE 0.6734 0.014633 0.014633 0.014633 ADA-USDT & SOL-USDT CLOSE\n", - "1 2025-06-02 15:41:00 SOL-USDT BUY CLOSE 153.1000 0.014633 0.014633 0.014633 ADA-USDT & SOL-USDT CLOSE\n", + "0 2025-06-02 15:41:00 ADA-USDT SELL CLOSE 0.6735 0.014633 0.014633 0.014633 ADA-USDT & SOL-USDT CLOSE\n", + "1 2025-06-02 15:41:00 SOL-USDT BUY CLOSE 153.0800 0.014633 0.014633 0.014633 ADA-USDT & SOL-USDT CLOSE\n", "OPEN_TRADES: 2025-06-02 16:44:00 open_scaled_disequilibrium=2.364778510607668\n", "OPEN TRADES:\n", " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", - "0 2025-06-02 16:44:00 ADA-USDT BUY OPEN 0.6712 -2.364779 2.364779 -2.364779 ADA-USDT & SOL-USDT OPEN\n", - "1 2025-06-02 16:44:00 SOL-USDT SELL OPEN 152.5100 -2.364779 2.364779 -2.364779 ADA-USDT & SOL-USDT OPEN\n", + "0 2025-06-02 16:44:00 ADA-USDT BUY OPEN 0.6708 -2.364779 2.364779 -2.364779 ADA-USDT & SOL-USDT OPEN\n", + "1 2025-06-02 16:44:00 SOL-USDT SELL OPEN 152.4700 -2.364779 2.364779 -2.364779 ADA-USDT & SOL-USDT OPEN\n", "CLOSE TRADES:\n", " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", - "0 2025-06-02 17:01:00 ADA-USDT SELL CLOSE 0.6744 -0.45725 0.45725 -0.45725 ADA-USDT & SOL-USDT CLOSE\n", - "1 2025-06-02 17:01:00 SOL-USDT BUY CLOSE 153.0700 -0.45725 0.45725 -0.45725 ADA-USDT & SOL-USDT CLOSE\n", + "0 2025-06-02 17:01:00 ADA-USDT SELL CLOSE 0.6745 -0.45725 0.45725 -0.45725 ADA-USDT & SOL-USDT CLOSE\n", + "1 2025-06-02 17:01:00 SOL-USDT BUY CLOSE 153.2100 -0.45725 0.45725 -0.45725 ADA-USDT & SOL-USDT CLOSE\n", "OPEN_TRADES: 2025-06-02 17:06:00 open_scaled_disequilibrium=2.191024540541887\n", "OPEN TRADES:\n", - " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", - "0 2025-06-02 17:06:00 ADA-USDT BUY OPEN 0.674 -2.191025 2.191025 -2.191025 ADA-USDT & SOL-USDT OPEN\n", - "1 2025-06-02 17:06:00 SOL-USDT SELL OPEN 153.030 -2.191025 2.191025 -2.191025 ADA-USDT & SOL-USDT OPEN\n", + " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", + "0 2025-06-02 17:06:00 ADA-USDT BUY OPEN 0.6738 -2.191025 2.191025 -2.191025 ADA-USDT & SOL-USDT OPEN\n", + "1 2025-06-02 17:06:00 SOL-USDT SELL OPEN 152.9000 -2.191025 2.191025 -2.191025 ADA-USDT & SOL-USDT OPEN\n", "CLOSE TRADES:\n", " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", - "0 2025-06-02 17:17:00 ADA-USDT SELL CLOSE 0.6743 -0.152501 0.152501 -0.152501 ADA-USDT & SOL-USDT CLOSE\n", - "1 2025-06-02 17:17:00 SOL-USDT BUY CLOSE 153.0900 -0.152501 0.152501 -0.152501 ADA-USDT & SOL-USDT CLOSE\n", + "0 2025-06-02 17:17:00 ADA-USDT SELL CLOSE 0.6726 -0.152501 0.152501 -0.152501 ADA-USDT & SOL-USDT CLOSE\n", + "1 2025-06-02 17:17:00 SOL-USDT BUY CLOSE 152.7800 -0.152501 0.152501 -0.152501 ADA-USDT & SOL-USDT CLOSE\n", "OPEN_TRADES: 2025-06-02 17:24:00 open_scaled_disequilibrium=2.748538160528875\n", "OPEN TRADES:\n", " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", - "0 2025-06-02 17:24:00 ADA-USDT BUY OPEN 0.6759 -2.748538 2.748538 -2.748538 ADA-USDT & SOL-USDT OPEN\n", - "1 2025-06-02 17:24:00 SOL-USDT SELL OPEN 153.7000 -2.748538 2.748538 -2.748538 ADA-USDT & SOL-USDT OPEN\n", + "0 2025-06-02 17:24:00 ADA-USDT BUY OPEN 0.6755 -2.748538 2.748538 -2.748538 ADA-USDT & SOL-USDT OPEN\n", + "1 2025-06-02 17:24:00 SOL-USDT SELL OPEN 153.5600 -2.748538 2.748538 -2.748538 ADA-USDT & SOL-USDT OPEN\n", "CLOSE TRADES:\n", " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", - "0 2025-06-02 17:35:00 ADA-USDT SELL CLOSE 0.6715 -0.413061 0.413061 -0.413061 ADA-USDT & SOL-USDT CLOSE\n", - "1 2025-06-02 17:35:00 SOL-USDT BUY CLOSE 152.9900 -0.413061 0.413061 -0.413061 ADA-USDT & SOL-USDT CLOSE\n", + "0 2025-06-02 17:35:00 ADA-USDT SELL CLOSE 0.6712 -0.413061 0.413061 -0.413061 ADA-USDT & SOL-USDT CLOSE\n", + "1 2025-06-02 17:35:00 SOL-USDT BUY CLOSE 153.0300 -0.413061 0.413061 -0.413061 ADA-USDT & SOL-USDT CLOSE\n", "OPEN_TRADES: 2025-06-02 18:02:00 open_scaled_disequilibrium=2.0472288892294728\n", "OPEN TRADES:\n", " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", - "0 2025-06-02 18:02:00 ADA-USDT SELL OPEN 0.6743 2.047229 2.047229 2.047229 ADA-USDT & SOL-USDT OPEN\n", - "1 2025-06-02 18:02:00 SOL-USDT BUY OPEN 153.6400 2.047229 2.047229 2.047229 ADA-USDT & SOL-USDT OPEN\n", + "0 2025-06-02 18:02:00 ADA-USDT SELL OPEN 0.6741 2.047229 2.047229 2.047229 ADA-USDT & SOL-USDT OPEN\n", + "1 2025-06-02 18:02:00 SOL-USDT BUY OPEN 153.5900 2.047229 2.047229 2.047229 ADA-USDT & SOL-USDT OPEN\n", "CLOSE TRADES:\n", " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", - "0 2025-06-02 18:06:00 ADA-USDT BUY CLOSE 0.6747 -0.089168 0.089168 -0.089168 ADA-USDT & SOL-USDT CLOSE\n", - "1 2025-06-02 18:06:00 SOL-USDT SELL CLOSE 153.8400 -0.089168 0.089168 -0.089168 ADA-USDT & SOL-USDT CLOSE\n", + "0 2025-06-02 18:06:00 ADA-USDT BUY CLOSE 0.6746 -0.089168 0.089168 -0.089168 ADA-USDT & SOL-USDT CLOSE\n", + "1 2025-06-02 18:06:00 SOL-USDT SELL CLOSE 153.7900 -0.089168 0.089168 -0.089168 ADA-USDT & SOL-USDT CLOSE\n", "OPEN_TRADES: 2025-06-02 19:35:00 open_scaled_disequilibrium=2.016877535891162\n", "OPEN TRADES:\n", " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", - "0 2025-06-02 19:35:00 ADA-USDT BUY OPEN 0.6721 -2.016878 2.016878 -2.016878 ADA-USDT & SOL-USDT OPEN\n", - "1 2025-06-02 19:35:00 SOL-USDT SELL OPEN 152.1300 -2.016878 2.016878 -2.016878 ADA-USDT & SOL-USDT OPEN\n", - "ADA-USDT & SOL-USDT: *** Position is NOT CLOSED. ***\n", - "CLOSE_POSITION TRADES:\n", - " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", - "0 2025-06-02 22:28:00 ADA-USDT SELL CLOSE 0.691 0.0 0.0 0.0 ADA-USDT & SOL-USDT CLOSE_POSITION\n", - "1 2025-06-02 22:28:00 SOL-USDT BUY CLOSE 156.750 0.0 0.0 0.0 ADA-USDT & SOL-USDT CLOSE_POSITION\n", - "***ADA-USDT & SOL-USDT*** FINISHED *** Num Trades:28\n", - "Generated 28 trading signals\n", + "0 2025-06-02 19:35:00 ADA-USDT BUY OPEN 0.6719 -2.016878 2.016878 -2.016878 ADA-USDT & SOL-USDT OPEN\n", + "1 2025-06-02 19:35:00 SOL-USDT SELL OPEN 151.9900 -2.016878 2.016878 -2.016878 ADA-USDT & SOL-USDT OPEN\n", + "CLOSE TRADES:\n", + " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", + "0 2025-06-02 21:04:00 ADA-USDT SELL CLOSE 0.6845 -0.316305 0.316305 -0.316305 ADA-USDT & SOL-USDT CLOSE\n", + "1 2025-06-02 21:04:00 SOL-USDT BUY CLOSE 154.7200 -0.316305 0.316305 -0.316305 ADA-USDT & SOL-USDT CLOSE\n", + "OPEN_TRADES: 2025-06-02 21:33:00 open_scaled_disequilibrium=2.1162146947771068\n", + "OPEN TRADES:\n", + " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", + "0 2025-06-02 21:33:00 ADA-USDT SELL OPEN 0.6819 2.116215 2.116215 2.116215 ADA-USDT & SOL-USDT OPEN\n", + "1 2025-06-02 21:33:00 SOL-USDT BUY OPEN 154.5100 2.116215 2.116215 2.116215 ADA-USDT & SOL-USDT OPEN\n", + "CLOSE TRADES:\n", + " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", + "0 2025-06-02 21:40:00 ADA-USDT BUY CLOSE 0.6833 -0.195611 0.195611 -0.195611 ADA-USDT & SOL-USDT CLOSE\n", + "1 2025-06-02 21:40:00 SOL-USDT SELL CLOSE 154.9200 -0.195611 0.195611 -0.195611 ADA-USDT & SOL-USDT CLOSE\n", + "OPEN_TRADES: 2025-06-02 21:58:00 open_scaled_disequilibrium=2.0841568077931436\n", + "OPEN TRADES:\n", + " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", + "0 2025-06-02 21:58:00 ADA-USDT BUY OPEN 0.6842 -2.084157 2.084157 -2.084157 ADA-USDT & SOL-USDT OPEN\n", + "1 2025-06-02 21:58:00 SOL-USDT SELL OPEN 155.2900 -2.084157 2.084157 -2.084157 ADA-USDT & SOL-USDT OPEN\n", + "CLOSE TRADES:\n", + " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", + "0 2025-06-02 22:06:00 ADA-USDT SELL CLOSE 0.6887 -0.396354 0.396354 -0.396354 ADA-USDT & SOL-USDT CLOSE\n", + "1 2025-06-02 22:06:00 SOL-USDT BUY CLOSE 155.8300 -0.396354 0.396354 -0.396354 ADA-USDT & SOL-USDT CLOSE\n", + "***ADA-USDT & SOL-USDT*** FINISHED *** Num Trades:36\n", + "Generated 36 trading signals\n", "\n", "Strategy execution completed!\n", "\n", @@ -1832,38 +1849,46 @@ "Timeline range: 2025-06-02 11:30:00 to 2025-06-02 22:29:00\n", "\n", "Symbol_A trades:\n", - " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", - "0 2025-06-02 13:37:00 ADA-USDT BUY OPEN 0.6754 -2.152526 2.152526 -2.152526 ADA-USDT & SOL-USDT OPEN\n", - "2 2025-06-02 14:37:00 ADA-USDT SELL CLOSE 0.6734 -0.280199 0.280199 -0.280199 ADA-USDT & SOL-USDT CLOSE\n", - "4 2025-06-02 15:09:00 ADA-USDT BUY OPEN 0.6691 -2.521813 2.521813 -2.521813 ADA-USDT & SOL-USDT OPEN\n", - "6 2025-06-02 15:41:00 ADA-USDT SELL CLOSE 0.6734 0.014633 0.014633 0.014633 ADA-USDT & SOL-USDT CLOSE\n", - "8 2025-06-02 16:44:00 ADA-USDT BUY OPEN 0.6712 -2.364779 2.364779 -2.364779 ADA-USDT & SOL-USDT OPEN\n", - "10 2025-06-02 17:01:00 ADA-USDT SELL CLOSE 0.6744 -0.457250 0.457250 -0.457250 ADA-USDT & SOL-USDT CLOSE\n", - "12 2025-06-02 17:06:00 ADA-USDT BUY OPEN 0.6740 -2.191025 2.191025 -2.191025 ADA-USDT & SOL-USDT OPEN\n", - "14 2025-06-02 17:17:00 ADA-USDT SELL CLOSE 0.6743 -0.152501 0.152501 -0.152501 ADA-USDT & SOL-USDT CLOSE\n", - "16 2025-06-02 17:24:00 ADA-USDT BUY OPEN 0.6759 -2.748538 2.748538 -2.748538 ADA-USDT & SOL-USDT OPEN\n", - "18 2025-06-02 17:35:00 ADA-USDT SELL CLOSE 0.6715 -0.413061 0.413061 -0.413061 ADA-USDT & SOL-USDT CLOSE\n", - "20 2025-06-02 18:02:00 ADA-USDT SELL OPEN 0.6743 2.047229 2.047229 2.047229 ADA-USDT & SOL-USDT OPEN\n", - "22 2025-06-02 18:06:00 ADA-USDT BUY CLOSE 0.6747 -0.089168 0.089168 -0.089168 ADA-USDT & SOL-USDT CLOSE\n", - "24 2025-06-02 19:35:00 ADA-USDT BUY OPEN 0.6721 -2.016878 2.016878 -2.016878 ADA-USDT & SOL-USDT OPEN\n", - "26 2025-06-02 22:28:00 ADA-USDT SELL CLOSE 0.6910 0.000000 0.000000 0.000000 ADA-USDT & SOL-USDT CLOSE_POSITION\n", + " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", + "0 2025-06-02 13:37:00 ADA-USDT BUY OPEN 0.6743 -2.152526 2.152526 -2.152526 ADA-USDT & SOL-USDT OPEN\n", + "2 2025-06-02 14:37:00 ADA-USDT SELL CLOSE 0.6746 -0.280199 0.280199 -0.280199 ADA-USDT & SOL-USDT CLOSE\n", + "4 2025-06-02 15:09:00 ADA-USDT BUY OPEN 0.6689 -2.521813 2.521813 -2.521813 ADA-USDT & SOL-USDT OPEN\n", + "6 2025-06-02 15:41:00 ADA-USDT SELL CLOSE 0.6735 0.014633 0.014633 0.014633 ADA-USDT & SOL-USDT CLOSE\n", + "8 2025-06-02 16:44:00 ADA-USDT BUY OPEN 0.6708 -2.364779 2.364779 -2.364779 ADA-USDT & SOL-USDT OPEN\n", + "10 2025-06-02 17:01:00 ADA-USDT SELL CLOSE 0.6745 -0.457250 0.457250 -0.457250 ADA-USDT & SOL-USDT CLOSE\n", + "12 2025-06-02 17:06:00 ADA-USDT BUY OPEN 0.6738 -2.191025 2.191025 -2.191025 ADA-USDT & SOL-USDT OPEN\n", + "14 2025-06-02 17:17:00 ADA-USDT SELL CLOSE 0.6726 -0.152501 0.152501 -0.152501 ADA-USDT & SOL-USDT CLOSE\n", + "16 2025-06-02 17:24:00 ADA-USDT BUY OPEN 0.6755 -2.748538 2.748538 -2.748538 ADA-USDT & SOL-USDT OPEN\n", + "18 2025-06-02 17:35:00 ADA-USDT SELL CLOSE 0.6712 -0.413061 0.413061 -0.413061 ADA-USDT & SOL-USDT CLOSE\n", + "20 2025-06-02 18:02:00 ADA-USDT SELL OPEN 0.6741 2.047229 2.047229 2.047229 ADA-USDT & SOL-USDT OPEN\n", + "22 2025-06-02 18:06:00 ADA-USDT BUY CLOSE 0.6746 -0.089168 0.089168 -0.089168 ADA-USDT & SOL-USDT CLOSE\n", + "24 2025-06-02 19:35:00 ADA-USDT BUY OPEN 0.6719 -2.016878 2.016878 -2.016878 ADA-USDT & SOL-USDT OPEN\n", + "26 2025-06-02 21:04:00 ADA-USDT SELL CLOSE 0.6845 -0.316305 0.316305 -0.316305 ADA-USDT & SOL-USDT CLOSE\n", + "28 2025-06-02 21:33:00 ADA-USDT SELL OPEN 0.6819 2.116215 2.116215 2.116215 ADA-USDT & SOL-USDT OPEN\n", + "30 2025-06-02 21:40:00 ADA-USDT BUY CLOSE 0.6833 -0.195611 0.195611 -0.195611 ADA-USDT & SOL-USDT CLOSE\n", + "32 2025-06-02 21:58:00 ADA-USDT BUY OPEN 0.6842 -2.084157 2.084157 -2.084157 ADA-USDT & SOL-USDT OPEN\n", + "34 2025-06-02 22:06:00 ADA-USDT SELL CLOSE 0.6887 -0.396354 0.396354 -0.396354 ADA-USDT & SOL-USDT CLOSE\n", "\n", "Symbol_B trades:\n", - " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", - "1 2025-06-02 13:37:00 SOL-USDT SELL OPEN 154.32 -2.152526 2.152526 -2.152526 ADA-USDT & SOL-USDT OPEN\n", - "3 2025-06-02 14:37:00 SOL-USDT BUY CLOSE 153.70 -0.280199 0.280199 -0.280199 ADA-USDT & SOL-USDT CLOSE\n", - "5 2025-06-02 15:09:00 SOL-USDT SELL OPEN 152.18 -2.521813 2.521813 -2.521813 ADA-USDT & SOL-USDT OPEN\n", - "7 2025-06-02 15:41:00 SOL-USDT BUY CLOSE 153.10 0.014633 0.014633 0.014633 ADA-USDT & SOL-USDT CLOSE\n", - "9 2025-06-02 16:44:00 SOL-USDT SELL OPEN 152.51 -2.364779 2.364779 -2.364779 ADA-USDT & SOL-USDT OPEN\n", - "11 2025-06-02 17:01:00 SOL-USDT BUY CLOSE 153.07 -0.457250 0.457250 -0.457250 ADA-USDT & SOL-USDT CLOSE\n", - "13 2025-06-02 17:06:00 SOL-USDT SELL OPEN 153.03 -2.191025 2.191025 -2.191025 ADA-USDT & SOL-USDT OPEN\n", - "15 2025-06-02 17:17:00 SOL-USDT BUY CLOSE 153.09 -0.152501 0.152501 -0.152501 ADA-USDT & SOL-USDT CLOSE\n", - "17 2025-06-02 17:24:00 SOL-USDT SELL OPEN 153.70 -2.748538 2.748538 -2.748538 ADA-USDT & SOL-USDT OPEN\n", - "19 2025-06-02 17:35:00 SOL-USDT BUY CLOSE 152.99 -0.413061 0.413061 -0.413061 ADA-USDT & SOL-USDT CLOSE\n", - "21 2025-06-02 18:02:00 SOL-USDT BUY OPEN 153.64 2.047229 2.047229 2.047229 ADA-USDT & SOL-USDT OPEN\n", - "23 2025-06-02 18:06:00 SOL-USDT SELL CLOSE 153.84 -0.089168 0.089168 -0.089168 ADA-USDT & SOL-USDT CLOSE\n", - "25 2025-06-02 19:35:00 SOL-USDT SELL OPEN 152.13 -2.016878 2.016878 -2.016878 ADA-USDT & SOL-USDT OPEN\n", - "27 2025-06-02 22:28:00 SOL-USDT BUY CLOSE 156.75 0.000000 0.000000 0.000000 ADA-USDT & SOL-USDT CLOSE_POSITION\n" + " time symbol side action price disequilibrium scaled_disequilibrium signed_scaled_disequilibrium pair status\n", + "1 2025-06-02 13:37:00 SOL-USDT SELL OPEN 154.14 -2.152526 2.152526 -2.152526 ADA-USDT & SOL-USDT OPEN\n", + "3 2025-06-02 14:37:00 SOL-USDT BUY CLOSE 154.00 -0.280199 0.280199 -0.280199 ADA-USDT & SOL-USDT CLOSE\n", + "5 2025-06-02 15:09:00 SOL-USDT SELL OPEN 152.12 -2.521813 2.521813 -2.521813 ADA-USDT & SOL-USDT OPEN\n", + "7 2025-06-02 15:41:00 SOL-USDT BUY CLOSE 153.08 0.014633 0.014633 0.014633 ADA-USDT & SOL-USDT CLOSE\n", + "9 2025-06-02 16:44:00 SOL-USDT SELL OPEN 152.47 -2.364779 2.364779 -2.364779 ADA-USDT & SOL-USDT OPEN\n", + "11 2025-06-02 17:01:00 SOL-USDT BUY CLOSE 153.21 -0.457250 0.457250 -0.457250 ADA-USDT & SOL-USDT CLOSE\n", + "13 2025-06-02 17:06:00 SOL-USDT SELL OPEN 152.90 -2.191025 2.191025 -2.191025 ADA-USDT & SOL-USDT OPEN\n", + "15 2025-06-02 17:17:00 SOL-USDT BUY CLOSE 152.78 -0.152501 0.152501 -0.152501 ADA-USDT & SOL-USDT CLOSE\n", + "17 2025-06-02 17:24:00 SOL-USDT SELL OPEN 153.56 -2.748538 2.748538 -2.748538 ADA-USDT & SOL-USDT OPEN\n", + "19 2025-06-02 17:35:00 SOL-USDT BUY CLOSE 153.03 -0.413061 0.413061 -0.413061 ADA-USDT & SOL-USDT CLOSE\n", + "21 2025-06-02 18:02:00 SOL-USDT BUY OPEN 153.59 2.047229 2.047229 2.047229 ADA-USDT & SOL-USDT OPEN\n", + "23 2025-06-02 18:06:00 SOL-USDT SELL CLOSE 153.79 -0.089168 0.089168 -0.089168 ADA-USDT & SOL-USDT CLOSE\n", + "25 2025-06-02 19:35:00 SOL-USDT SELL OPEN 151.99 -2.016878 2.016878 -2.016878 ADA-USDT & SOL-USDT OPEN\n", + "27 2025-06-02 21:04:00 SOL-USDT BUY CLOSE 154.72 -0.316305 0.316305 -0.316305 ADA-USDT & SOL-USDT CLOSE\n", + "29 2025-06-02 21:33:00 SOL-USDT BUY OPEN 154.51 2.116215 2.116215 2.116215 ADA-USDT & SOL-USDT OPEN\n", + "31 2025-06-02 21:40:00 SOL-USDT SELL CLOSE 154.92 -0.195611 0.195611 -0.195611 ADA-USDT & SOL-USDT CLOSE\n", + "33 2025-06-02 21:58:00 SOL-USDT SELL OPEN 155.29 -2.084157 2.084157 -2.084157 ADA-USDT & SOL-USDT OPEN\n", + "35 2025-06-02 22:06:00 SOL-USDT BUY CLOSE 155.83 -0.396354 0.396354 -0.396354 ADA-USDT & SOL-USDT CLOSE\n" ] }, { @@ -2544,7 +2569,7 @@ ], "xaxis": "x", "y": { - "bdata": "AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAA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AAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fw==", + "bdata": "AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/BVMBjW/09z9zQRGpTWX6Pw4xzj0Orvk/MtiiQtiB+z+FgrWEDCz9Pw7Q8bGjc/w/PQ2aAf+n/z+GW2BbXzgBQHlAaKw4Hv0/SQ8D81fi/j/nQ3dMW/X9Px2+QaY89vs/aCl1rqPX/D87yoGl2tL6P45IQE6kO/k/Gw5bE3oY+D/mOoYUjyT6P+/jx5x+HPU/v3veDaC09z9O6p4RDcb1P7AOYfcHY/Q/FEDtnK0s9T+lKv7pgNzyP0I03sf0zfE/8XrYx9a+6D8hR+4KMrDxP7jFBBqBV+0/VQnQ2dah8z83tB9lM4n2P+Hx0oO9yPY/eo2yXOOl8z9HhFUkIRf3Pzex2R88S/g/EoXzSVTP/T+YpZfHetr/PzFS3vWWgf8/J4FqFPbLAEBJ3nlbOYIBQEBpWlydtgFApVhD5M4jA0A5ulqxGNcBQJQ43po7egJAV8AgzUx0/T+kY2JbuQb6P7T3N+Ogf/o/ZNE7iiyQ/z+fyvfH/FgCQBlr7I9SywFA3Y5lx5oNAkDmANDGEAUGQDzmK+8WZQZAI3S8Fao/B0CEdWOkyusFQEEqFIRrwgVAgQitVySlBUAchKXSgcEDQCIm/mtPHwJA5/v6XjlDAkB6jxa7IP7/P5seoDX1fP0/i3fQUh54/T8YC2HHN3n2P6pghZ4f6/c/c0B5LG9m6D/J80nOAnjlP+rbiLQsguk/LS4U6w075z/tTIghye7RP5im29eNLd8/iRcDawW+4D+3HpUeDnTEP7VAO42a3sw/mS8pSN6rwj9kuxvLdybTP5EFVr9GY98/uKXpxMT42z89aZv0Z2HmPyA+WD0fDOY/FPNVj66Z8T82Wq7oIW3vP35vQy47NfE/Vn9K9CUk8j/QfMj362PsPw9TnV3Eo+o/zdCwFVGC2D+/qwzrOj3pP0egiAAxbO8/fug93evG7j9WQ2hOjKzrPzMOmw2RiO0/I+zNjWxi5D80c56x3aPhPy1THnBaVec/Hybpct7S3z+Y/JBTIhPXP+iKuvbKqPQ/dFuf/zXb+T8rP7Ziw7H+PxYAjA2sLARAJ+ZUzpMcCUBImnDnWxsHQDZmwOk6WgJAXabxsU9oAUBW4FY6y+39PxdryYSsuPs/12xP7AQB9j+GK4JSDL/2P+vCG+8jIfQ/8Yl93ibw+T/OIswfEyP9PxtPpSyHD/w/KTstOLZ/BEA4CjU0c2sFQMsnzBOaOQZAq8swx9H6B0B4b3ssocIEQHy0Xhq5DQlAzUgbOpwOB0Bq83+oSZYEQBeQLmuRuwVAHeIwLPsiB0D7ZI6+zL4GQAaixX4o+vg/hf6wLTje/j/KOlUbmWsAQHLTDR2zBPw/uATLujJF9j/P618gfKP2PyVdrQZw4uw/mSHqqkxI5z9xm/fT9PeNP6byz/FGAdQ/C1vwLHfY5D8TLAxij0TvPy7ZLx00b+I/sGKIWqoB8j9M7TqLGwzQP8kGos2fB9M/hK7iO+ka2T+2NNJiQeXuPxQTd/9uZNA/3byNxIJXrD8Iki2Hq4fVP0EBjTCymlE/aRx+z7VU2j+w79xITkLZP1k0U5zQhtY/wrKku5v8kD/lI+TIIxuyP9yCErE/ZYE/VYkgN+JO5z93mU6K3ArZPzT5PulqbNw/l+MyFJ7Z2T9R5rtJmbzeP7foE2F0vuo/Q/a24nY66T8LSiSwPxvaP1Y6ZBBVseU/U31DvB/21j/c+rGOScjrPxGIAWZ1c+s/P0aQAZkk4T8C9Er0sNvjP5oUDamnFPc/hW2jpq2G+j+l+7qkqv31P70BZ6/V9PY/OmAT8FcD9D9iLZnyWxPdP7r9NLHpkM8/up+masDU0j8jSLRV0EpiPwza73ybMZs/YVbpZP0Lxj9jJ7iCAWzYP29a4DCH7Oc/qHG57zQ9vz/sYIYDJEGuP3kXI6G2moQ/s9Ur82Orwz9rOdHTvgZjP2iE08u2vsI/VVyM6TFM0z8j550TRmK2P0SzK4KaMdc/qA4qpdMe5T9fXQMfflLoP9arayVuq/E/z5vrKGxy+T9lljDXzEjzP0F4jEqhmPk/6bOnER5B/T+Jv+r+EOsCQJVO4zuk6whABSa1ZpGYCEDtSq7M9U4QQGANCTYHFw9AgifssmsKEUCp/KxbDswOQLzF806S8QtAYgPn6qXYCkCHTq5FofYFQIDjJcY8zQNAl3tTyh/KB0BAXXf35xwDQBeq5/sbzQRACwIgUAGhA0DYKA8lAj/5P0KygQMRJvE/JJIGAJRD3T+thr80mcD0P5s6MjZTiu0/aG8DxAu69z8v0E6t/qD2P/vi0t83hwFAbxsgDflcAkCBL+d3qKwCQDBlPYWc0Po/vKp6SY3H/T8ZtAdUAhjhP43MBzuPyvg/1z/ag0tN9T/cLlZZHRDuPzJma7ZdW/Y/EfOAnyf28z860WUuJYXDP8yt07Dp99Y/Dp2ILiFd5z8YbtWVeO7yPxV2LBpJmPs/OYFUHHJx8D/8RJjKuKT3P3M8OpMB/QVAmvGy2nyP+z9WExKx5X/2P8SfwAMDqvo/FnrbtlRn+z8YhZs7PTr3P1WO3coVIvQ/DPPWlE/c+j/nn3pG6ZP5P/6W7QLwsfI/p0YxD7X14T/axuM4mW/aP+P/Eo+OMMQ/dtJSGo4lwj+dqVsucIXVPx3a4VDgfdY/uNOE8Vg14D+VG+6+JQG9P51hZ49VINQ/8uX5a0TOuD+A0KLtV1DgP5/6xnfeSec/eiWWQien2z+YjgGBQZ7tP3WFjUbxjfQ/I38ZsMfG6z9ynbEeXy75P1l4HkcvNPQ/5dgk0hQj8z+FbdiFWO3wPykhzwJoSvc/hLszsduu9j/qsnXtaZrxP/Axtrx7b+g/2zXqEpZH8j/PEjwYxDnyP3RfMeQtWPI/w3CF4f3k9z8AVjWKuWAAQEi90tCJgfY/nzAazOi44T+lYZhkomPjP40uR6O+07Y/HdubnYPk3D+mGUWJuXPZP+NUFyi/geU//203TiNv4D+4WKuE/iXvP8GQlIabjtw/HZjeQF996D/97JfHkUDlP+hmFtLp2e0/kQoQ6ONW5T80cn6nOPDvP4PQ75UVEvA/eVD8KVs04T8iI3ZJf+7gPxzwa4NsLuo/pzwZ6tCG3D8wCTN+cbDPP7Y8mrw1C9Q/CMzw8cOruj+pKeiJ0CzpP8gwl8JlINQ/BP88P3iWxT+BiXyfouvQP9U7nQvjRuE/t4wwxnHh4j/aHOU+XQTsPyt+TwnQqe8/f+bHQlb95T8etrS91inSP6AYpYhmMdU/GlMKiRAajz9YZ5oIYvrTP79uc2QBpcw/vDuSi8P21j+HrxA1JbbYPxOLTF2sQNM/sWtF1yLV0D+cUdZIelbdP+GuH8Knz8I/YVPgv0gE0j+nnwNwFS/hP3NomQntItQ/6D3JAYx3wj8LvjVD0v3VP+t7K1iSP90/pGVktwzLzj+5xoEr5X/dP1rtUEyp4+k//lvc4xMv1z+SNL2ie+vkP10w3LJfiu4/OZXP1f5G6D/01EP/dE3mP93+f7Sn1ec/3oap72uk8T9lnj2bi5D2PyUR43WwufI/yMPzAdi18j+DrJDo9Df1PwlII8mTMPI/FeUTLEEr7T8GmbFpMAzYP8ll5TLBw+U/67s3II7L8D8N01hdvwHmPy/tVysPfc0/sayJBiW21D9q4Pdz6s3UP2+FFAGni+E/ehZYdDOi1D/3FQIIRWvUP6iTpl20qdY/mNiF9k9i3D/E0+4pBK3gPzUqyrPgL9c/GgIqJ5go1D9TXBqhJ0rLP9AkCmx6uNg/pisNqd8z3j+4uvOumT3FP1yb46GT270/sMNqXrjTrD8pcA7D6ePGP2HuyxMuVOc/xbD+Ijj17j/PQALl1tzoP0IrYgrPYfg/jHz51bn89z9RhIWwkCIAQJgzAOieEP0/AJqpjHF9+j9LUYMhimX5Pw1H0ml04/g/1qxDMU8SAUDSsOCVmHYBQLsC8FaFoQJACKnOHr6SA0AGliUUaG4AQNKQC2OjlQFAFwLm2ToQAEDpnmttiTsAQI88sPAmJgFAxgH0AiSt+z8ckTbf7kL7P+t/NS3PEvw/ulXPcZ6Y/T+zImkorzD3P5/H2U5zpfs/H0WYvLfT+z9uXREKwPr4PwSXzFE5z/M/ULTRznEC9z8t9dZdRGH0P1MLoIn/CfM/46INfJsC8j+9DOyiZ/zyP/d18C5M/fE/x+X+Hqbx6j/NAHppx6DsPx2542HrwfI/2eu+3YoP8z/lcQnuhxvxP6Lt/FW46fE/U4RN36ym9j9eMMMi4C/5P5UXTUG7ifo/NelTEWNn+j/jTYd7a9z2P5FJoFaQCPQ/bkSGqhgj8j+h6dFeWejtP2f44IytQOk//ohLfouI5z9CebPdWo7pP7VNlwsuzOs/syFcPEmW4z/rAkl685DvP3i5zz0lI/A/xehx3/R98D9+XHZcVDvwP3ztbJDJ+fM/equhmQUa9j8I8OI9Zx34PyzHEX3Eh/s/q8F8pzgz/j/YIB+sWfQAQLS1e7h4yv8/M5vxQ6W4/z+hgGutwaIAQJGlIEvoaABAdpJ2p1C8AUDpBALh8hYCQJS4U508/QNADqiosrmaA0BDAOqgnDgDQCceIfh04QNAXQbgWKkRBUBZdQs1CVMFQBeLBuk7pgRAgoXwQIvZAEBs34d60fL+P32svKSqpv8/5eO6uQWC/z/8cV5ebrr7P5sxwaJTdP4/lWfIyYud/z+iRzfsAG//P2BTgOBX1ABAiJs0nsnr+j9IHQAGS8/6P3kH2QX9ZPo/FgC5hk+n/z9nv5+HMHH9Px3MCx8NI/4/rDerZ9u//j/RgQu/1d/1P4/NHn75Fu8/KOSlaVc+1D+lWRFoCdDdP4Ju64VVIa8/Zhr0YHt+1T+9/FpGU0PVPyB5SD9jhdY/LwDAbfBiwD/yM7F2mKnhP9QDTXufI/k/vE+PU8xd+T/BXhDlTuH8Pxg7aHjbsfg/ZKk6jDcz9j+iPtEBa+f6P7vqfxXz0v0/LSodqPkw/T/F6XgFNKH4PzmzlbcaL/U/mTPyw4q3+D8QQRF9TIjuP7z0wEPF+uo/RM3ce7sO8j9XsYtaCvfwPybezzV1Svs/a6PZyTdi/T/MeHubH0D/P1JaPoR7F/4/aHeF5JKo/D+s+k6lRPD5P/EOS/gB7gBA3UXVC+onAEC8yBgVpJX4P/f1MVEIp/E/hb5Wzo5U7D+iQSXplNbjP128c8lj3ew/JLFobcsJyT8GGxtZ5IqjP1BcGzE8BdQ/2NHKwwSz1T/j92sqXQzEPzb/P5XW47M/ykT9pUuF6T/4008LAiHwP5RyHzWeltY/YXeSA0GQ1z/1kUnfEma8Px7HJ1a+xeQ/ZEFsaeVd4z9lEqGPqVPzP6T8yVcnZ/Q/AMw4+PIt7j8qXf3yHpn2P72Oa0rIyP0/kqiJZ1qsAEC00uH4MCoGQPRZC/SiSAVAZKFAauRDA0AP/aG7r1ICQE42NBOJ1ANAz2bI/hHF4z8uaCaGH1rkPyJW5o/eXdk/+Ayoh3W/3T8QuHUCb/fgPznnc4A+V9k/jj3ZX0RLwj8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fw==", "dtype": "f8" }, "yaxis": "y" @@ -3220,7 +3245,7 @@ ], "xaxis": "x", "y": { - "bdata": "AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAA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AAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fw==", + "bdata": "AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAAAAD4fwAAAAAAAPh/AAAAAAAA+H8AAAAA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", "dtype": "f8" }, "yaxis": "y" @@ -4588,12 +4613,13 @@ "name": "ADA-USDT BUY OPEN", "showlegend": true, "text": [ - "ADA-USDT BUY OPEN OPEN
Time: 2025-06-02 13:37:00
Normalized Price: 1.0109
Actual Price: $0.68", + "ADA-USDT BUY OPEN OPEN
Time: 2025-06-02 13:37:00
Normalized Price: 1.0109
Actual Price: $0.67", "ADA-USDT BUY OPEN OPEN
Time: 2025-06-02 15:09:00
Normalized Price: 1.0028
Actual Price: $0.67", "ADA-USDT BUY OPEN OPEN
Time: 2025-06-02 16:44:00
Normalized Price: 1.0057
Actual Price: $0.67", "ADA-USDT BUY OPEN OPEN
Time: 2025-06-02 17:06:00
Normalized Price: 1.0102
Actual Price: $0.67", "ADA-USDT BUY OPEN OPEN
Time: 2025-06-02 17:24:00
Normalized Price: 1.0127
Actual Price: $0.68", - "ADA-USDT BUY OPEN OPEN
Time: 2025-06-02 19:35:00
Normalized Price: 1.0073
Actual Price: $0.67" + "ADA-USDT BUY OPEN OPEN
Time: 2025-06-02 19:35:00
Normalized Price: 1.0073
Actual Price: $0.67", + "ADA-USDT BUY OPEN OPEN
Time: 2025-06-02 21:58:00
Normalized Price: 1.0258
Actual Price: $0.68" ], "type": "scatter", "x": [ @@ -4602,7 +4628,8 @@ "2025-06-02T16:44:00", "2025-06-02T17:06:00", "2025-06-02T17:24:00", - "2025-06-02T19:35:00" + "2025-06-02T19:35:00", + "2025-06-02T21:58:00" ], "xaxis": "x2", "y": [ @@ -4611,7 +4638,8 @@ 1.0056971514242878, 1.0101949025487256, 1.012743628185907, - 1.0073463268365817 + 1.0073463268365817, + 1.0257871064467765 ], "yaxis": "y2" }, @@ -4626,12 +4654,13 @@ "name": "SOL-USDT SELL OPEN", "showlegend": true, "text": [ - "SOL-USDT SELL OPEN OPEN
Time: 2025-06-02 13:37:00
Normalized Price: 1.0008
Actual Price: $154.32", - "SOL-USDT SELL OPEN OPEN
Time: 2025-06-02 15:09:00
Normalized Price: 0.9877
Actual Price: $152.18", - "SOL-USDT SELL OPEN OPEN
Time: 2025-06-02 16:44:00
Normalized Price: 0.9900
Actual Price: $152.51", - "SOL-USDT SELL OPEN OPEN
Time: 2025-06-02 17:06:00
Normalized Price: 0.9928
Actual Price: $153.03", - "SOL-USDT SELL OPEN OPEN
Time: 2025-06-02 17:24:00
Normalized Price: 0.9971
Actual Price: $153.70", - "SOL-USDT SELL OPEN OPEN
Time: 2025-06-02 19:35:00
Normalized Price: 0.9869
Actual Price: $152.13" + "SOL-USDT SELL OPEN OPEN
Time: 2025-06-02 13:37:00
Normalized Price: 1.0008
Actual Price: $154.14", + "SOL-USDT SELL OPEN OPEN
Time: 2025-06-02 15:09:00
Normalized Price: 0.9877
Actual Price: $152.12", + "SOL-USDT SELL OPEN OPEN
Time: 2025-06-02 16:44:00
Normalized Price: 0.9900
Actual Price: $152.47", + "SOL-USDT SELL OPEN OPEN
Time: 2025-06-02 17:06:00
Normalized Price: 0.9928
Actual Price: $152.90", + "SOL-USDT SELL OPEN OPEN
Time: 2025-06-02 17:24:00
Normalized Price: 0.9971
Actual Price: $153.56", + "SOL-USDT SELL OPEN OPEN
Time: 2025-06-02 19:35:00
Normalized Price: 0.9869
Actual Price: $151.99", + "SOL-USDT SELL OPEN OPEN
Time: 2025-06-02 21:58:00
Normalized Price: 1.0083
Actual Price: $155.29" ], "type": "scatter", "x": [ @@ -4640,7 +4669,8 @@ "2025-06-02T16:44:00", "2025-06-02T17:06:00", "2025-06-02T17:24:00", - "2025-06-02T19:35:00" + "2025-06-02T19:35:00", + "2025-06-02T21:58:00" ], "xaxis": "x2", "y": [ @@ -4649,7 +4679,8 @@ 0.9900006493084865, 0.9927926758002728, 0.9970781118109214, - 0.9868839685734694 + 0.9868839685734694, + 1.0083111486267125 ], "yaxis": "y2" }, @@ -4673,7 +4704,8 @@ "ADA-USDT SELL CLOSE CLOSE
Time: 2025-06-02 17:01:00
Normalized Price: 1.0112
Actual Price: $0.67", "ADA-USDT SELL CLOSE CLOSE
Time: 2025-06-02 17:17:00
Normalized Price: 1.0084
Actual Price: $0.67", "ADA-USDT SELL CLOSE CLOSE
Time: 2025-06-02 17:35:00
Normalized Price: 1.0063
Actual Price: $0.67", - "ADA-USDT SELL CLOSE CLOSE
Time: 2025-06-02 22:28:00
Normalized Price: 1.0357
Actual Price: $0.69" + "ADA-USDT SELL CLOSE CLOSE
Time: 2025-06-02 21:04:00
Normalized Price: 1.0262
Actual Price: $0.68", + "ADA-USDT SELL CLOSE CLOSE
Time: 2025-06-02 22:06:00
Normalized Price: 1.0325
Actual Price: $0.69" ], "type": "scatter", "x": [ @@ -4682,7 +4714,8 @@ "2025-06-02T17:01:00", "2025-06-02T17:17:00", "2025-06-02T17:35:00", - "2025-06-02T22:28:00" + "2025-06-02T21:04:00", + "2025-06-02T22:06:00" ], "xaxis": "x2", "y": [ @@ -4691,7 +4724,8 @@ 1.0112443778110944, 1.0083958020989505, 1.0062968515742128, - 1.0356821589205396 + 1.0262368815592204, + 1.0325337331334332 ], "yaxis": "y2" }, @@ -4710,12 +4744,13 @@ "name": "SOL-USDT BUY CLOSE", "showlegend": true, "text": [ - "SOL-USDT BUY CLOSE CLOSE
Time: 2025-06-02 14:37:00
Normalized Price: 0.9999
Actual Price: $153.70", - "SOL-USDT BUY CLOSE CLOSE
Time: 2025-06-02 15:41:00
Normalized Price: 0.9940
Actual Price: $153.10", - "SOL-USDT BUY CLOSE CLOSE
Time: 2025-06-02 17:01:00
Normalized Price: 0.9948
Actual Price: $153.07", - "SOL-USDT BUY CLOSE CLOSE
Time: 2025-06-02 17:17:00
Normalized Price: 0.9920
Actual Price: $153.09", - "SOL-USDT BUY CLOSE CLOSE
Time: 2025-06-02 17:35:00
Normalized Price: 0.9936
Actual Price: $152.99", - "SOL-USDT BUY CLOSE CLOSE
Time: 2025-06-02 22:28:00
Normalized Price: 1.0171
Actual Price: $156.75" + "SOL-USDT BUY CLOSE CLOSE
Time: 2025-06-02 14:37:00
Normalized Price: 0.9999
Actual Price: $154.00", + "SOL-USDT BUY CLOSE CLOSE
Time: 2025-06-02 15:41:00
Normalized Price: 0.9940
Actual Price: $153.08", + "SOL-USDT BUY CLOSE CLOSE
Time: 2025-06-02 17:01:00
Normalized Price: 0.9948
Actual Price: $153.21", + "SOL-USDT BUY CLOSE CLOSE
Time: 2025-06-02 17:17:00
Normalized Price: 0.9920
Actual Price: $152.78", + "SOL-USDT BUY CLOSE CLOSE
Time: 2025-06-02 17:35:00
Normalized Price: 0.9936
Actual Price: $153.03", + "SOL-USDT BUY CLOSE CLOSE
Time: 2025-06-02 21:04:00
Normalized Price: 1.0046
Actual Price: $154.72", + "SOL-USDT BUY CLOSE CLOSE
Time: 2025-06-02 22:06:00
Normalized Price: 1.0118
Actual Price: $155.83" ], "type": "scatter", "x": [ @@ -4724,7 +4759,8 @@ "2025-06-02T17:01:00", "2025-06-02T17:17:00", "2025-06-02T17:35:00", - "2025-06-02T22:28:00" + "2025-06-02T21:04:00", + "2025-06-02T22:06:00" ], "xaxis": "x2", "y": [ @@ -4733,7 +4769,8 @@ 0.9948055321083048, 0.9920135056165185, 0.9936367768326733, - 1.0171417440425947 + 1.0046100902538797, + 1.011817414453607 ], "yaxis": "y2" }, @@ -4748,15 +4785,18 @@ "name": "ADA-USDT SELL OPEN", "showlegend": true, "text": [ - "ADA-USDT SELL OPEN OPEN
Time: 2025-06-02 18:02:00
Normalized Price: 1.0106
Actual Price: $0.67" + "ADA-USDT SELL OPEN OPEN
Time: 2025-06-02 18:02:00
Normalized Price: 1.0106
Actual Price: $0.67", + "ADA-USDT SELL OPEN OPEN
Time: 2025-06-02 21:33:00
Normalized Price: 1.0223
Actual Price: $0.68" ], "type": "scatter", "x": [ - "2025-06-02T18:02:00" + "2025-06-02T18:02:00", + "2025-06-02T21:33:00" ], "xaxis": "x2", "y": [ - 1.0106446776611695 + 1.0106446776611695, + 1.0223388305847074 ], "yaxis": "y2" }, @@ -4771,15 +4811,18 @@ "name": "SOL-USDT BUY OPEN", "showlegend": true, "text": [ - "SOL-USDT BUY OPEN OPEN
Time: 2025-06-02 18:02:00
Normalized Price: 0.9973
Actual Price: $153.64" + "SOL-USDT BUY OPEN OPEN
Time: 2025-06-02 18:02:00
Normalized Price: 0.9973
Actual Price: $153.59", + "SOL-USDT BUY OPEN OPEN
Time: 2025-06-02 21:33:00
Normalized Price: 1.0032
Actual Price: $154.51" ], "type": "scatter", "x": [ - "2025-06-02T18:02:00" + "2025-06-02T18:02:00", + "2025-06-02T21:33:00" ], "xaxis": "x2", "y": [ - 0.99727290435686 + 0.99727290435686, + 1.0032465424323096 ], "yaxis": "y2" }, @@ -4798,15 +4841,18 @@ "name": "ADA-USDT BUY CLOSE", "showlegend": true, "text": [ - "ADA-USDT BUY CLOSE CLOSE
Time: 2025-06-02 18:06:00
Normalized Price: 1.0114
Actual Price: $0.67" + "ADA-USDT BUY CLOSE CLOSE
Time: 2025-06-02 18:06:00
Normalized Price: 1.0114
Actual Price: $0.67", + "ADA-USDT BUY CLOSE CLOSE
Time: 2025-06-02 21:40:00
Normalized Price: 1.0244
Actual Price: $0.68" ], "type": "scatter", "x": [ - "2025-06-02T18:06:00" + "2025-06-02T18:06:00", + "2025-06-02T21:40:00" ], "xaxis": "x2", "y": [ - 1.0113943028485757 + 1.0113943028485757, + 1.0244377811094452 ], "yaxis": "y2" }, @@ -4825,15 +4871,18 @@ "name": "SOL-USDT SELL CLOSE", "showlegend": true, "text": [ - "SOL-USDT SELL CLOSE CLOSE
Time: 2025-06-02 18:06:00
Normalized Price: 0.9986
Actual Price: $153.84" + "SOL-USDT SELL CLOSE CLOSE
Time: 2025-06-02 18:06:00
Normalized Price: 0.9986
Actual Price: $153.79", + "SOL-USDT SELL CLOSE CLOSE
Time: 2025-06-02 21:40:00
Normalized Price: 1.0059
Actual Price: $154.92" ], "type": "scatter", "x": [ - "2025-06-02T18:06:00" + "2025-06-02T18:06:00", + "2025-06-02T21:40:00" ], "xaxis": "x2", "y": [ - 0.9985715213297838 + 0.9985715213297838, + 1.0059087072268034 ], "yaxis": "y2" }, @@ -5529,11 +5578,12 @@ "2025-06-02T16:44:00.000000000", "2025-06-02T17:06:00.000000000", "2025-06-02T17:24:00.000000000", - "2025-06-02T19:35:00.000000000" + "2025-06-02T19:35:00.000000000", + "2025-06-02T21:58:00.000000000" ], "xaxis": "x3", "y": { - "bdata": "Iv32deCc5T9+HThnRGnlPwpoImx4euU/xSCwcmiR5T+MuWsJ+aDlP/2H9NvXgeU/", + "bdata": "ayv2l92T5T+6awn5oGflP4EExY8xd+U/AG+BBMWP5T8EVg4tsp3lPznWxW00gOU/24r9Zffk5T8=", "dtype": "f8" }, "yaxis": "y3" @@ -5553,11 +5603,12 @@ "showlegend": true, "type": "scatter", "x": [ - "2025-06-02T18:06:00.000000000" + "2025-06-02T18:06:00.000000000", + "2025-06-02T21:40:00.000000000" ], "xaxis": "x3", "y": { - "bdata": "845TdCSX5T8=", + "bdata": "ETY8vVKW5T/oaiv2l93lPw==", "dtype": "f8" }, "yaxis": "y3" @@ -5573,11 +5624,12 @@ "showlegend": true, "type": "scatter", "x": [ - "2025-06-02T18:02:00.000000000" + "2025-06-02T18:02:00.000000000", + "2025-06-02T21:33:00.000000000" ], "xaxis": "x3", "y": { - "bdata": "ayv2l92T5T8=", + "bdata": "p3nHKTqS5T+KjuTyH9LlPw==", "dtype": "f8" }, "yaxis": "y3" @@ -5602,11 +5654,12 @@ "2025-06-02T17:01:00.000000000", "2025-06-02T17:17:00.000000000", "2025-06-02T17:35:00.000000000", - "2025-06-02T22:28:00.000000000" + "2025-06-02T21:04:00.000000000", + "2025-06-02T22:06:00.000000000" ], "xaxis": "x3", "y": { - "bdata": "eAskKH6M5T94CyQofozlP02EDU+vlOU/ayv2l92T5T+wcmiR7XzlP+kmMQisHOY/", + "bdata": "ETY8vVKW5T9aZDvfT43lPy/dJAaBleU/Z0Rpb/CF5T8KaCJseHrlP4GVQ4ts5+U/mSoYldQJ5j8=", "dtype": "f8" }, "yaxis": "y3" @@ -6298,11 +6351,12 @@ "showlegend": true, "type": "scatter", "x": [ - "2025-06-02T18:02:00.000000000" + "2025-06-02T18:02:00.000000000", + "2025-06-02T21:33:00.000000000" ], "xaxis": "x4", "y": { - "bdata": "FK5H4Xo0Y0A=", + "bdata": "exSuR+EyY0C4HoXrUVBjQA==", "dtype": "f8" }, "yaxis": "y4" @@ -6327,11 +6381,12 @@ "2025-06-02T17:01:00.000000000", "2025-06-02T17:17:00.000000000", "2025-06-02T17:35:00.000000000", - "2025-06-02T22:28:00.000000000" + "2025-06-02T21:04:00.000000000", + "2025-06-02T22:06:00.000000000" ], "xaxis": "x4", "y": { - "bdata": "ZmZmZmY2Y0AzMzMzMyNjQArXo3A9ImNAexSuR+EiY0BI4XoUrh9jQAAAAAAAmGNA", + "bdata": "AAAAAABAY0DD9ShcjyJjQB+F61G4JmNAKVyPwvUYY0ApXI/C9SBjQNejcD0KV2NAw/UoXI96Y0A=", "dtype": "f8" }, "yaxis": "y4" @@ -6352,11 +6407,12 @@ "2025-06-02T16:44:00.000000000", "2025-06-02T17:06:00.000000000", "2025-06-02T17:24:00.000000000", - "2025-06-02T19:35:00.000000000" + "2025-06-02T19:35:00.000000000", + "2025-06-02T21:58:00.000000000" ], "xaxis": "x4", "y": { - "bdata": "CtejcD1KY0D2KFyPwgVjQLgehetREGNAKVyPwvUgY0BmZmZmZjZjQFyPwvUoBGNA", + "bdata": "FK5H4XpEY0CkcD0K1wNjQNejcD0KD2NAzczMzMwcY0BSuB6F6zFjQEjhehSu/2JA4XoUrkdpY0A=", "dtype": "f8" }, "yaxis": "y4" @@ -6376,11 +6432,12 @@ "showlegend": true, "type": "scatter", "x": [ - "2025-06-02T18:06:00.000000000" + "2025-06-02T18:06:00.000000000", + "2025-06-02T21:40:00.000000000" ], "xaxis": "x4", "y": { - "bdata": "exSuR+E6Y0A=", + "bdata": "4XoUrkc5Y0A9CtejcF1jQA==", "dtype": "f8" }, "yaxis": "y4" @@ -7391,9 +7448,9 @@ }, "text/html": [ "
\n", - "