Consider these remarks about async programming in python-telegram-bot

PTB is async: Core telegram and telegram.ext are async. Use async/await for bot interactions and handler callbacks.

Applications are async context managers: Initialize/shutdown resources with async with application:. Avoid manual application.initialize()/application.shutdown() unless integrating with other async frameworks.

Non-blocking handlers: Set block=False in handlers for concurrency (e.g., long-running tasks). Use application.create_task() for background tasks; these are managed by the application. Don't rely on shared context attributes with non-blocking handlers.

Concurrent updates: ApplicationBuilder.concurrent_updates(num) enables processing num updates concurrently using SimpleUpdateProcessor. Stateful handlers (e.g., ConversationHandler) generally shouldn't use concurrent updates.

Async context manager for bot and update processor: Use async with bot: and async with update_processor:.

Asynchronous request objects: telegram.request provides async request objects for network operations.

Callback data caching: The cache for arbitrary callback data now must be explicitly enabled on application.

Timeouts: Timeouts for requests can be set on initialization of HTTPXRequest.

Utility functionality and concurrency: Certain utility functionalities and objects are not thread-safe and require caution when using in a multi-threaded setting.

Then consider this code:
import asyncio
import html
import json
import logging
import os
import re
from collections import defaultdict
from datetime import datetime
from typing import Any, DefaultDict, Dict, List, Optional, Set, Tuple, Union

import httpx
import websockets
from telegram import Update
from telegram.constants import ParseMode
from telegram.ext import (
    Application,
    CommandHandler,
    ContextTypes,
    CallbackContext
)

# Configuration (Ensure environment variables are set)
TELEGRAM_BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN')
TELEGRAM_CHAT_ID = os.getenv('TELEGRAM_CHAT_ID')
HYPERLIQUID_WS_URL = "wss://api.hyperliquid.xyz/ws"

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[logging.StreamHandler()]
)
logger = logging.getLogger(__name__)

# Global variables
tracked_wallets: Dict[str, str] = {}  # {address: nickname}
previous_pnls: defaultdict[str, defaultdict[str, float]] = defaultdict(lambda: defaultdict(float))  # {wallet_address: {coin: previous_pnl}}
previous_positions: defaultdict[str, set[str]] = defaultdict(set)  # {wallet_address: set of coins}


def escape_markdown(text: str) -> str:
    """Helper function to escape special characters for MarkdownV2."""
    escape_chars = r'_*[]()~`>#+-=|{}.!'
    return re.sub(f'([{re.escape(escape_chars)}])', r'\\\1', str(text))


def format_number(number: Union[str, float], decimals: int = 2, suffix: str = "") -> str:
    """Formats a number into a more readable string."""
    if isinstance(number, str):
        number = number.replace("$", "").replace(",", "")
    try:
        number = float(number)
    except ValueError:
        return "N/A"
    if abs(number) >= 1_000_000:
        return f"${number / 1_000_000:.{decimals}f}m{suffix}"
    if abs(number) >= 1_000:
        return f"${number / 1_000:.{decimals}f}k{suffix}"
    return f"${number:.{decimals}f}{suffix}"



def parse_formatted_number(s: str) -> float:
    """Parses formatted number string to float."""
    s = s.replace(',', '').strip().lower()
    multiplier = 1
    if s.endswith('k'):
        multiplier = 1_000
        s = s[:-1]
    elif s.endswith('m'):
        multiplier = 1_000_000
        s = s[:-1]
    try:
        return float(s) * multiplier
    except ValueError:
        logger.error(f"Failed to parse number: {s}")
        return 0.0


def format_crypto_value(value, coin):
    return format_number(value)


def safe_request(url: str, json_data: Dict[str, Any], timeout: int = 10) -> Optional[Dict[Any, Any]]:
    """Makes a safe request to the specified URL, handling potential errors."""
    try:
        response = httpx.post(url, json=json_data, timeout=timeout)
        response.raise_for_status()  # Raise HTTPError for bad responses (4xx or 5xx)
        return response.json()
    except httpx.HTTPError as e:
        logger.error(f"Request failed: {e}")
        return None
    except json.JSONDecodeError as e:
        logger.error(f"Failed to decode response: {e}")
        return None
    except httpx.TimeoutException as e:
        logger.error(f"Request timed out: {e}")
        return None


def fetch_user_state(wallet_address: str) -> Optional[Dict[str, Any]]:
    """Fetches the user state from the Hyperliquid API."""
    return safe_request("https://api.hyperliquid.xyz/info", {"type": "clearinghouseState", "user": wallet_address})


def fetch_all_mids() -> Optional[Dict[str, Any]]:
    """Fetches the allMids data from the Hyperliquid API."""
    return safe_request("https://api.hyperliquid.xyz/info", {"type": "allMids"})


def get_open_orders_count(wallet_address: str, return_data: bool = False) -> Union[int, List[Dict[str, Any]]]:
    """Fetches the number of open orders for a given wallet address."""
    data = safe_request("https://api.hyperliquid.xyz/info", {"type": "frontendOpenOrders", "user": wallet_address})
    if return_data:
        return data if data else []  # Return empty list if no data
    return len(data) if data else 0


def get_all_prices(all_mids: Optional[Dict[str, Any]]) -> Dict[str, float]:
    if all_mids is None:
        return {}

    prices = {}
    for coin, price_data in all_mids.items():
        try:
            price = float(price_data[0])
            prices[coin] = price
        except (IndexError, TypeError, ValueError) as e:
            logger.error(f"Error parsing price for {coin}: {e}")
    return prices


def format_user_state(
    wallet_address: str, all_mids: Optional[Dict[str, Any]]
) -> Tuple[List[List[str]], Dict[str, Any], float, float]:
    user_state = fetch_user_state(wallet_address)
    if not user_state or not user_state.get("assetPositions"):
        return [], {}, 0.0, 0.0

    formatted_output = []
    raw_data = {}
    total_upnl = 0.0
    all_prices = get_all_prices(all_mids)

    try:
        account_equity = float(user_state.get("marginSummary", {}).get("accountValue", 0))
    except (TypeError, ValueError):
        account_equity = 0.0

    for item in user_state.get("assetPositions", []):
        position = item.get("position", {})
        coin = position.get("coin", "N/A")

        try:
            entry_px = float(position.get("entryPx", 0))
            mark_price = float(all_prices.get(coin, 0))
            size = float(position.get("szi", 0))
            unrealized_pnl = float(position.get("unrealizedPnl", 0))
            leverage = float(position.get("leverage", {}).get("value", 1))
        except (TypeError, ValueError) as e:
            logger.error(f"Error parsing position data for {coin}: {e}")
            continue

        position_value = abs(size * mark_price)
        position_type = "LONG" if size > 0 else "SHORT" if size < 0 else "N/A"

        if position_type != "N/A":
            formatted_output.append(
                [
                    coin,
                    position_type,
                    format_number(entry_px, decimals=4),
                    format_number(mark_price, decimals=2),
                    format_number(unrealized_pnl, decimals=0),
                    format_number(position_value, decimals=0),
                    format_number(leverage, decimals=0),
                ]
            )
            raw_data[coin] = {
                "coin": coin,
                "type": position_type.upper(),
                "entry_price": entry_px,
                "mark_price": mark_price,
                "unrealized_pnl": unrealized_pnl,
                "size": size,
                "leverage": leverage,
            }
            total_upnl += unrealized_pnl

    return formatted_output, raw_data, total_upnl, account_equity


def check_position_changes(wallet_address: str, current_positions: Dict[str, Any]) -> Tuple[bool, bool]:
    """Checks for position changes and logs them."""
    position_opened = False
    position_closed = False

    current_coins = set(current_positions)
    previous_coins = previous_positions[wallet_address]

    if not previous_coins:
        position_opened = True
        logger.info("Initial positions detected for coins: %s", ", ".join(current_coins))
    else:
        opened = current_coins - previous_coins
        if opened:
            position_opened = True
            logger.info("Positions opened for coins: %s", ", ".join(opened))

        closed = previous_coins - current_coins
        if closed:
            position_closed = True
            logger.info("Positions closed for coins: %s", ", ".join(closed))

    return position_opened, position_closed


async def update_positions(
    wallet_address: str,
    current_positions: Dict[str, Any],
    formatted_positions: List[List[str]],
    nickname: str,
):
    """Sends Telegram alerts for significant PnL changes or position changes."""
    logger.info(f"Updating positions for {nickname} ({wallet_address})")

    message = f"<b>Wallet Positions Update for {html.escape(nickname)}:</b>\n"

    send_update = False
    pnl_change_threshold = 500.0
    position_change_message = ""

    for position in formatted_positions:
        coin, position_type, entry_price, mark_price, pnl, size, leverage = position
        logger.info(f"Processing position: {coin} {position_type}")

        # Retrieve the raw position data
        raw_position = current_positions.get(coin)
        if not raw_position:
            logger.warning(f"No raw data found for coin: {coin}")
            continue

        # Determine if this is the first time checking this position
        is_first_check = coin not in previous_pnls[wallet_address]

        # Calculate PnL difference
        previous_pnl = previous_pnls[wallet_address][coin]
        pnl_diff = raw_position["unrealized_pnl"] - previous_pnl

        if is_first_check or abs(pnl_diff) >= pnl_change_threshold:
            send_update = True

            pnl_diff_str = (
                f" (+{format_number(pnl_diff, decimals=0)[1:]})"
                if pnl_diff >= 0
                else f" (-{format_number(abs(pnl_diff), decimals=0)[1:]})"
            )
            mark_price_value = raw_position['mark_price']
            mark_price_formatted = f"{mark_price_value:.4f}" if mark_price_value < 1 else f"{mark_price_value:.2f}"
            entry_price_value = raw_position['entry_price']
            entry_price_formatted = f"{entry_price_value:.4f}" if entry_price_value < 1 else f"{entry_price_value:.2f}"
            formatted_pnl = format_number(raw_position["unrealized_pnl"], decimals=0)
            display_size = abs(raw_position['size']) # Show as positive for positions
            formatted_size = format_number(display_size, decimals=0)
            formatted_leverage = format_number(raw_position['leverage'], decimals=0)

            message += (
                f"- {html.escape(coin)} {formatted_size} {html.escape(position_type)}, "
                f"Entry: {entry_price_formatted}, Mark: {mark_price_formatted}, "
                f"PnL: {formatted_pnl}{pnl_diff_str}, Size: {formatted_size}x, Leverage: {formatted_leverage}x\n"
            )

        previous_pnls[wallet_address][coin] = raw_position["unrealized_pnl"]

    position_opened, position_closed = check_position_changes(wallet_address, current_positions)

    if position_opened:
        position_change_message += f"<b>ALERT: Position Opened</b>\n{html.escape(nickname)} | {', '.join(current_positions.keys())}\n\n" # Correctly format and send position changes

    if position_closed:
        closed_positions = previous_positions[wallet_address] - set(current_positions.keys())
        position_change_message += f"<b>ALERT: Position Closed</b>\n{html.escape(nickname)} | {', '.join(closed_positions)}\n\n" # Correctly format and send position changes


    if send_update or position_opened or position_closed:
        try:
            await application.bot.send_message(
                chat_id=TELEGRAM_CHAT_ID,
                text=position_change_message + message,
                parse_mode=ParseMode.HTML,
            )
            logger.info("Telegram update sent successfully.")
        except Exception as e:
            logger.error(f"Failed to send message: {e}")

    previous_positions[wallet_address] = set(current_positions)


async def websocket_handler(app: Application):
    """Handles websocket connections and subscriptions."""
    uri = HYPERLIQUID_WS_URL

    while True:
        try:
            async with websockets.connect(uri) as websocket:
                logger.info("WebSocket connection established.")

                for wallet_address, nickname in tracked_wallets.items():
                    await subscribe_to_wallet(websocket, wallet_address, nickname)

                async for message in websocket:
                    await process_websocket_message(message, app)

        except (
            websockets.ConnectionClosed,
            websockets.ConnectionClosedError,
            websockets.ConnectionClosedOK,
            httpx.ConnectTimeout,
        ) as e:
            logger.error("Websocket connection closed: %s. Reconnecting in 5 seconds...", e)
            await asyncio.sleep(5)
        except Exception as e:
            logger.error(f"Websocket handler error: {e}", exc_info=True)
            break


async def subscribe_to_wallet(websocket, wallet_address, nickname):
    try:
        subscription_message = {
            "method": "subscribe",
            "subscription": {
                "type": "userEvents",
                "user": wallet_address,
            },
        }
        await websocket.send(json.dumps(subscription_message))
        logger.info(f"Subscribed to userEvents for wallet: {nickname or wallet_address} ({wallet_address})") # Log nickname or address if no nickname
    except Exception as e:
        logger.error(f"Failed to subscribe to wallet {wallet_address}: {e}")


async def unsubscribe_from_wallet(websocket, wallet_address, nickname):
    """Unsubscribes from userEvents for a given wallet."""
    try:
        unsubscription_message = {
            "method": "unsubscribe",
            "subscription": {
                "type": "userEvents",
                "user": wallet_address,
            },
        }
        await websocket.send(json.dumps(unsubscription_message))
        logger.info(f"Unsubscribed from userEvents for wallet: {nickname or wallet_address} ({wallet_address})") # Log nickname if available, otherwise address
    except Exception as e:
        logger.error(f"Failed to unsubscribe from wallet {wallet_address}: {e}")



async def process_websocket_message(message: str, app: Application):
    try:
        ws_msg = json.loads(message)
        channel = ws_msg.get("channel")

        if channel == "user":
            data = ws_msg.get("data", {})
            fills = data.get("fills", [])
            # Process fills concurrently
            for fill in fills:
                app.create_task(handle_fill_event(fill, app), name=f"handle_fill:{fill.get('user')}")

    except json.JSONDecodeError as e:
        logger.error(f"Failed to decode websocket message: {e}")
    except Exception as e:
        logger.error(f"Error processing websocket message: {e}", exc_info=True)



async def handle_fill_event(fill: Dict[str, Any], app: Application):
    """Handles fill events from the websocket. Improved type safety."""
    wallet_address = fill.get("user")
    if wallet_address is None or not isinstance(wallet_address, str):
        logger.error("Invalid wallet address in fill event: %s", fill)
        return


    if wallet_address not in tracked_wallets:
        return

    nickname = tracked_wallets[wallet_address]
    coin = fill.get("coin")
    if coin is None or not isinstance(coin, str):
        logger.error("Invalid coin in fill event: %s", fill)
        return

    side = fill.get("side")
    if side is None or not isinstance(side, str) or side not in ("B", "S"): # Validate "side"
        logger.error("Invalid side in fill event: %s", fill)
        return


    try:
        px = float(fill.get("px", 0))
        sz = float(fill.get("sz", 0))
    except (TypeError, ValueError):
        logger.error("Invalid price or size in fill event: %s", fill)
        return


    time_of_trade = datetime.utcfromtimestamp(fill.get("time", 0) / 1000).strftime('%Y-%m-%d %H:%M:%S')

    message = (
        f"<b>Trade Executed for {html.escape(nickname)}:</b>\n"
        f"Coin: {html.escape(coin)}\n"
        f"Side: {'Buy' if side == 'B' else 'Sell'}\n" # Use "B" and "S" instead of 0 and 1
        f"Price: {px}\n"
        f"Size: {sz}\n"
        f"Time: {time_of_trade}"
    )

    try:
        await app.bot.send_message(
            chat_id=TELEGRAM_CHAT_ID, text=message, parse_mode=ParseMode.HTML
        )
        logger.info("Sent trade update to Telegram for wallet %s.", wallet_address)

        # Update positions and PnL after a fill
        all_mids = fetch_all_mids()
        formatted_positions, current_positions, total_upnl, account_equity = format_user_state(
            wallet_address, all_mids
        )
        await update_positions(
            wallet_address, current_positions, formatted_positions, nickname
        )
    except Exception as e:
        logger.error(f"Failed to send message: {e}")


# Telegram bot command handlers
async def start_command(update: Update, context: CallbackContext):
    start_message = (
        "Bot started. Use these commands:\n"
        "/add <wallet_address> [nickname] - Add a wallet to track\n"
        "/remove <wallet_address or nickname> - Remove a wallet\n"
        "/list - List tracked wallets\n"
        "/details <wallet_address or nickname> - Show wallet details"
    )
    await update.message.reply_text(start_message)
    logger.info("User %s started the bot.", update.effective_user.id)

async def add_wallet(update: Update, context: CallbackContext, wallet_address: Optional[str] = None, nickname: Optional[str] = None):
    """Adds a wallet address to the tracking list with an optional nickname."""
    if update and not context.args:
        await update.message.reply_text("Please provide a wallet address to add.")
        return

    if update:
        wallet_address = context.args[0]
        nickname = " ".join(context.args[1:]) if len(context.args) > 1 else wallet_address
    elif not wallet_address:
        raise ValueError("Either context.args or address has to be passed!")


    tracked_wallets[wallet_address] = nickname or wallet_address

    # Fetch initial positions
    all_mids = fetch_all_mids()
    formatted_positions, current_positions, total_upnl, account_equity = format_user_state(
        wallet_address, all_mids
    )
    if update:
        await update.message.reply_text(f"Added wallet {nickname} ({wallet_address}) to tracking.")
    logger.info(f"Added wallet {nickname} ({wallet_address}) to tracking.")

    if current_positions:
        await update_positions(wallet_address, current_positions, formatted_positions, nickname)


async def remove_wallet(update: Update, context: CallbackContext):
    """Removes a wallet address from the tracking list."""

    if not context.args:
        await update.message.reply_text("Please provide a wallet address or nickname to remove.")
        return

    identifier = context.args[0]

    wallet_address_to_remove = None
    for addr, name in list(tracked_wallets.items()):  # Iterate over a *copy* of the keys
        if identifier in [addr, name]:
            wallet_address_to_remove = addr
            break


    if wallet_address_to_remove:
        nickname = tracked_wallets.pop(wallet_address_to_remove)
        previous_pnls.pop(wallet_address_to_remove, None)
        previous_positions.pop(wallet_address_to_remove, None)

        async with websockets.connect(HYPERLIQUID_WS_URL) as websocket:
            await unsubscribe_from_wallet(websocket, wallet_address_to_remove, nickname)

        await update.message.reply_text(
            f"Removed wallet {nickname} ({wallet_address_to_remove}) from tracking."
        )
        logger.info("Removed wallet %s (%s) from tracking.", nickname, wallet_address_to_remove)
    else:
        await update.message.reply_text(f"Wallet {identifier} is not being tracked.")
        logger.warning("Attempted to remove non-tracked wallet %s.", identifier)



async def list_wallets(update: Update, context: CallbackContext):
    """Lists all tracked wallet addresses with their nicknames."""
    if tracked_wallets:
        wallet_list = "\n".join(
            [f"{html.escape(name)} ({address})" for address, name in tracked_wallets.items()]
        )
        await update.message.reply_text(
            f"Currently tracking these wallets:\n{wallet_list}", parse_mode=ParseMode.HTML
        )
        logger.info("Listed wallets for user %s.", update.effective_user.id)
    else:
        await update.message.reply_text("Not tracking any wallets yet.")
        logger.info(
            "User %s requested wallet list but none are being tracked.", update.effective_user.id
        )


async def wallet_details(update: Update, context: CallbackContext):
    """Shows detailed information about a specific wallet."""
    if not context.args:
        await update.message.reply_text("Please provide a wallet address or nickname to view details.")
        return

    identifier = context.args[0]
    wallet_address = None
    for address, nickname in tracked_wallets.items():
        if identifier in [address, nickname]:
            wallet_address = address
            break # Exit the loop if a match is found


    if not wallet_address:
        await update.message.reply_text(f"Wallet {identifier} is not being tracked.")
        logger.warning(f"User {update.effective_user.id} requested details for non-tracked wallet {identifier}.")
        return

    try:
        user_state = fetch_user_state(wallet_address)
        if user_state is None:
            raise ValueError("Failed to fetch user state.")

        open_orders = get_open_orders_count(wallet_address, return_data=True)
        all_mids = fetch_all_mids()
        if all_mids is None:
            raise ValueError("Failed to fetch all mids")


        formatted_positions, raw_data, total_upnl, account_equity = format_user_state(wallet_address, all_mids)

        wallet_name = tracked_wallets[wallet_address]
        message = f"<b>Wallet Details: {html.escape(wallet_name)}</b>\n"

        if not formatted_positions and account_equity == 0 and not open_orders:
            message += "Currently inactive"
        else:
            message += f"- Margin: {html.escape(format_number(account_equity))}\n\n"

            if formatted_positions:
                message += "<b>Positions:</b>\n"
                for position in formatted_positions:
                    coin, position_type, entry_price, mark_price, pnl, size, leverage = position

                    raw_position = raw_data.get(coin)
                    if raw_position is None:
                        raise ValueError(f"No raw_position data found for {coin}")

                    is_first_check = coin not in previous_pnls[wallet_address]

                    pnl_diff_str = ""
                    if not is_first_check:
                        previous_pnl = previous_pnls[wallet_address][coin]
                        pnl_diff = raw_position['unrealized_pnl'] - previous_pnl
                        pnl_diff_str = f" (+{format_number(pnl_diff, decimals=0)[1:]})" if pnl_diff >= 0 else f" (-{format_number(abs(pnl_diff), decimals=0)[1:]})"

                    previous_pnls[wallet_address][coin] = raw_position['unrealized_pnl']

                    mark_price_value = raw_position['mark_price']
                    mark_price_formatted = f"{mark_price_value:.4f}" if mark_price_value < 1 else f"{mark_price_value:.2f}"
                    entry_price_value = raw_position['entry_price']
                    entry_price_formatted = f"{entry_price_value:.4f}" if entry_price_value < 1 else f"{entry_price_value:.2f}"

                    formatted_pnl = format_number(raw_position['unrealized_pnl'], decimals=0)
                    formatted_size = format_number(abs(raw_position['size']), decimals=0)
                    formatted_leverage = format_number(raw_position['leverage'], decimals=0)


                    message += (
                        f"- <b>{html.escape(coin)}</b> {formatted_size} {html.escape(position_type)}, "
                        f"Entry: {entry_price_formatted}, "
                        f"Mark: {mark_price_formatted},\n"
                        f"  Size: {formatted_size}x, "
                        f"<b>PnL: {formatted_pnl}</b>{pnl_diff_str}, Leverage: {formatted_leverage}x\n"
                    )


            else:
                message += "<b>Positions:</b> N/A\n"

            if open_orders is not None:
                open_orders_message = (
                    "\n".join(
                        [
                            f"- <b>{html.escape(order.get('coin', 'N/A'))}</b>: "
                            f"Type: {html.escape(order.get('type', 'N/A'))}, "
                            f"Side: {html.escape(order.get('side', 'N/A'))}, "
                            f"Price: {html.escape(str(order.get('px', 'N/A')))}, "
                            f"Quantity: {html.escape(str(order.get('qty', 'N/A')))}"
                            for order in open_orders
                        ]
                    )
                    if open_orders
                    else "N/A"
                )
                message += f"\n<b>Open Orders:</b> {open_orders_message}\n"
            else:
                message += "\n<b>Open Orders:</b> Failed to fetch open order data\n"

        await context.bot.send_message(chat_id=chat_id, text=message, parse_mode=ParseMode.HTML)
        logger.info("Sent wallet details for %s to Telegram.", wallet_name)

    except Exception as e:
        logger.error(f"Error fetching wallet details for {wallet_address}: {e}", exc_info=True)
        error_message = f"Failed to fetch wallet details. Error: {str(e)}"
        await context.bot.send_message(chat_id=TELEGRAM_CHAT_ID, text=error_message)  # Use TELEGRAM_CHAT_ID constant
        logger.info("Sent error message to Telegram for wallet %s.", wallet_address)


async def error_handler(update: object, context: CallbackContext) -> None:
    """Handle errors caused by updates."""
    logger.error(msg="Exception while handling an update:", exc_info=context.error)
    if TELEGRAM_CHAT_ID:
        try:
            await context.bot.send_message(
                chat_id=TELEGRAM_CHAT_ID,
                text=f"An error occurred: {context.error}",
            )
        except Exception as e:
            logger.error(f"Failed to send error message to Telegram: {e}")



async def main():
    """Main function to initialize and run the bot and websocket handler."""
    global application  # Make application accessible globally

    application = Application.builder().token(TELEGRAM_BOT_TOKEN).build()

    application.add_handler(CommandHandler("start", start_command))
    application.add_handler(CommandHandler("add", add_wallet))
    application.add_handler(CommandHandler("remove", remove_wallet))
    application.add_handler(CommandHandler("list", list_wallets))
    application.add_handler(CommandHandler("details", wallet_details))
    application.add_error_handler(error_handler)  # Define error_handler here

    default_wallet = "0xcd278177b13eb5c269fed43960c4171b66df5b9c"
    default_nickname = "kPepe Shorter"

    mock_update = Update(0, message=Message(0, None, Chat(1, "private"), from_user=User(1, "test", False)))
    context = CallbackContext.from_update(mock_update, application)
    await add_wallet(update=None, context=context, wallet_address=default_wallet, nickname=default_nickname)


    async with application:
        application.create_task(websocket_handler(application), name="main_websocket_handler")
        await application.updater.start_polling()
        await application.run_forever()


if __name__ == "__main__":
    asyncio.run(main())

Then consider these errors:
Traceback (most recent call last):
  File "/Users/mcb/Library/Mobile Documents/com~apple~CloudDocs/Code/Python/crypto/archive/hl-specific-wallet/monitor-purr.py", line 681, in <module>
    asyncio.run(main())
  File "/opt/homebrew/Cellar/python@3.12/3.12.5/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.12/3.12.5/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.12/3.12.5/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/base_events.py", line 687, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/Users/mcb/Library/Mobile Documents/com~apple~CloudDocs/Code/Python/crypto/archive/hl-specific-wallet/monitor-purr.py", line 669, in main
    mock_update = Update(0, message=Message(0, None, Chat(1, "private"), from_user=User(1, "test", False)))
                                    ^^^^^^^
NameError: name 'Message' is not defined

Then provide amended methods or full code to rectify