← Back
"""
Squeeze-VWAP Bot — Main Entry Point
=======================================
Главный цикл:
1. Nuclear cleanup при старте
2. Recovery позиций (Squeeze + Zatochki)
3. Squeeze: PAUSED (6 Apr 2026 — replaced by Zatochki)
4. Zatochki: каждые 60 сек — скан + entry
5. Каждые 5 сек — мониторинг позиций (both strategies)

Стратегии:
- Squeeze Momentum + Z-VWAP + Waddah Attar + ADX (PAUSED)
- Zatochki (Knife Catcher) — Volume exhaustion reversal 1m (ACTIVE)
"""

import asyncio
import logging
import sys
import os
import time

# Add project root to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from src.config import SCAN_INTERVAL_SEC, DATA_DIR
from src.zatochki_config import SCAN_INTERVAL_SEC as Z_SCAN_INTERVAL_SEC
from src.exchange import Exchange
from src.screener import Screener
from src.manager import TradeManager
from src.zatochki_screener import ZatochkiScreener
from src.zatochki_manager import ZatochkiManager
from src.bot import TelegramBot
from src.tmm_client import TMMClient

# Squeeze ON/OFF
SQUEEZE_ENABLED = False  # PAUSED 6 Apr 2026 — Zatochki replaces it
ZATOCHKI_ENABLED = True

# ============================================================
# LOGGING
# ============================================================

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger("main")

# Suppress noisy libs
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("telegram").setLevel(logging.WARNING)


# ============================================================
# MAIN
# ============================================================

async def main():
    logger.info("=" * 50)
    logger.info("Squeeze-VWAP Bot starting...")
    logger.info(f"Squeeze: {'ACTIVE' if SQUEEZE_ENABLED else 'PAUSED'}")
    logger.info(f"Zatochki: {'ACTIVE' if ZATOCHKI_ENABLED else 'PAUSED'}")
    logger.info("=" * 50)

    # Ensure data dir exists
    os.makedirs(DATA_DIR, exist_ok=True)

    # Init components
    exchange = Exchange()
    notify_queue = asyncio.Queue()

    def sync_notify(msg):
        """Sync wrapper — кладёт сообщение в очередь для async отправки."""
        try:
            notify_queue.put_nowait(msg)
        except Exception:
            pass

    tmm = TMMClient()

    # Squeeze components (even if paused — needed for recovery)
    screener = Screener(exchange, notifier=sync_notify)
    manager = TradeManager(exchange, screener, notifier=sync_notify, tmm=tmm)
    screener.get_open_positions = lambda: manager.positions

    # Zatochki components
    z_screener = ZatochkiScreener(exchange, notifier=sync_notify)
    z_manager = ZatochkiManager(exchange, z_screener, notifier=sync_notify, tmm=tmm)

    bot = TelegramBot(screener=screener, exchange=exchange, tmm=tmm)
    bot.manager = manager
    bot.z_manager = z_manager  # for /zstatus, /zstats commands

    # Start Telegram bot
    await bot.start()
    logger.info("Telegram bot started")

    # Startup notification
    balance = exchange.get_balance()
    squeeze_status = "ACTIVE" if SQUEEZE_ENABLED else "PAUSED"
    zatochki_status = "ACTIVE" if ZATOCHKI_ENABLED else "PAUSED"
    await bot.send_message(
        f"\U0001f680 *Bot started*\n"
        f"Balance: ${balance:.2f}\n"
        f"Squeeze: {squeeze_status} ({len(manager.positions)} pos)\n"
        f"\U0001f52a Zatochki: {zatochki_status} ({len(z_manager.positions)} pos)\n"
        f"$5\\*10x | SL cap 1\\.2% | TP1 0\\.7%"
    )

    # Nuclear cleanup (only if no open positions from either strategy)
    if not manager.positions and not z_manager.positions:
        logger.info("Nuclear cleanup...")
        exchange.nuclear_cleanup()
    else:
        logger.info("Skipping nuclear cleanup (open positions exist)")

    # Recovery
    logger.info("Recovery check (Squeeze)...")
    manager.recovery()
    logger.info("Recovery check (Zatochki)...")
    z_manager.recovery()

    # Main loop
    last_squeeze_scan = 0
    last_zatochki_scan = 0
    check_interval = 5  # seconds between position checks

    logger.info("Entering main loop...")

    try:
        while True:
            now = time.time()

            # === SQUEEZE SCAN (каждые 5 мин) — PAUSED ===
            if SQUEEZE_ENABLED and now - last_squeeze_scan >= SCAN_INTERVAL_SEC:
                try:
                    screener.run_scan()
                    last_squeeze_scan = now
                except Exception as e:
                    logger.error(f"Squeeze scan error: {e}")
                    last_squeeze_scan = now

            # === SQUEEZE ENTRIES ===
            if SQUEEZE_ENABLED:
                try:
                    manager.check_watchlist_for_entries()
                except Exception as e:
                    logger.error(f"Squeeze entry error: {e}")

            # === SQUEEZE MONITOR (always — might have recovery positions) ===
            if manager.positions:
                try:
                    manager.check_positions()
                except Exception as e:
                    logger.error(f"Squeeze position error: {e}")

            # === ZATOCHKI SCAN (каждые 60 сек) ===
            if ZATOCHKI_ENABLED and now - last_zatochki_scan >= Z_SCAN_INTERVAL_SEC:
                try:
                    signals = z_screener.run_scan()
                    if signals:
                        z_manager.process_signals(signals)
                    last_zatochki_scan = now
                except Exception as e:
                    logger.error(f"Zatochki scan error: {e}")
                    last_zatochki_scan = now

            # === ZATOCHKI MONITOR ===
            if z_manager.positions:
                try:
                    z_manager.check_positions()
                except Exception as e:
                    logger.error(f"Zatochki position error: {e}")

            # === TMM RETRY PENDING TAGS ===
            try:
                tmm.retry_pending_tags()
            except Exception:
                pass

            # === PROCESS NOTIFY QUEUE ===
            while not notify_queue.empty():
                try:
                    msg = notify_queue.get_nowait()
                    await bot.send_message(msg)
                except Exception:
                    break

            # Sleep
            await asyncio.sleep(check_interval)

    except KeyboardInterrupt:
        logger.info("Shutting down...")
    except Exception as e:
        logger.error(f"Fatal error: {e}", exc_info=True)
        await bot.send_message(f"\U0001f480 *Bot crashed:* {str(e)[:200]}")
    finally:
        await bot.stop()
        logger.info("Bot stopped")


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

📜 Git History

c6f6bd5chore: initial commit — version control setup5 weeks ago
Show last diff
Loading...