Source code for backtrader.bokeh.analyzers.plot

#!/usr/bin/env python
"""
Live plotting analyzer.

Provides real-time plotting functionality based on Bokeh Server.
"""

import asyncio
import logging
import threading
from threading import Lock

try:
    import tornado.ioloop

    TORNADO_AVAILABLE = True
except ImportError:
    TORNADO_AVAILABLE = False

import backtrader as bt

from ..app import BacktraderBokeh
from ..live.client import LiveClient
from ..schemes import Tradimo
from ..webapp import Webapp

_logger = logging.getLogger(__name__)


[docs] class LivePlotAnalyzer(bt.Analyzer): """Live plotting analyzer. Provides real-time plotting functionality based on Bokeh Server, including: - WebSocket real-time data push - Pause/resume functionality - Forward/backward navigation - Data lookback control Args: scheme: Theme instance, defaults to Tradimo style: Chart style, 'bar' or 'candle' lookback: Amount of historical data to retain address: Server address port: Server port title: Title autostart: Whether to auto-start the server Example: cerebro.addanalyzer(LivePlotAnalyzer, scheme=Blackly(), lookback=100, port=8999) """ params = ( ("scheme", None), # Theme ("style", "bar"), # Chart style ("lookback", 100), # Historical data retention ("address", "localhost"), # Server address ("port", 8999), # Server port ("title", None), # Title ("autostart", True), # Auto-start )
[docs] def __init__(self, **kwargs): """Initialize live plot analyzer. Args: **kwargs: Keyword arguments overriding default parameters: - scheme: Theme instance (defaults to Tradimo) - style: Chart style ('bar' or 'candle') - lookback: Number of bars to retain in display - address: Server address (default: 'localhost') - port: Server port (default: 8999) - title: Chart title (default: 'Live {StrategyName}') - autostart: Whether to auto-start server (default: True) """ super().__init__() # Set title title = self.p.title if title is None: title = f"Live {type(self.strategy).__name__}" # Set auto-start autostart = kwargs.get("autostart", self.p.autostart) # Get theme scheme = self.p.scheme if scheme is None: scheme = Tradimo() # Create Webapp self._webapp = Webapp( title=title, template="basic.html.j2", scheme=scheme, on_root_model=self._app_cb_build_root_model, on_session_destroyed=self._on_session_destroyed, autostart=autostart, address=self.p.address, port=self.p.port, ) self._lock = Lock() self._clients = {} self._app_kwargs = kwargs
def _create_app(self): """Create BacktraderBokeh application instance. Returns: BacktraderBokeh instance """ return BacktraderBokeh( style=self.p.style, scheme=self.p.scheme or Tradimo(), **self._app_kwargs ) def _on_session_destroyed(self, session_context): """Session destroyed callback. Args: session_context: Bokeh session context """ with self._lock: session_id = session_context.id if session_id in self._clients: self._clients[session_id].stop() del self._clients[session_id] def _t_server(self): """Server thread method.""" if not TORNADO_AVAILABLE: _logger.error("Tornado is not available. Cannot start Bokeh server.") return asyncio.set_event_loop(asyncio.new_event_loop()) loop = tornado.ioloop.IOLoop.current() self._webapp.start(loop) def _app_cb_build_root_model(self, doc): """Build root model callback. Args: doc: Bokeh document Returns: Root model """ client = LiveClient(doc, self._create_app(), self.strategy, self.p.lookback) with self._lock: self._clients[doc.session_context.id] = client return client.model
[docs] def start(self): """Start from backtrader. Starts the Bokeh Server. """ _logger.debug("Starting LivePlotAnalyzer...") t = threading.Thread(target=self._t_server) t.daemon = True t.start()
[docs] def stop(self): """Stop from backtrader.""" _logger.debug("Stopping LivePlotAnalyzer...") with self._lock: for client in self._clients.values(): client.stop()
[docs] def next(self): """Receive new data from backtrader. Updates all connected clients. """ with self._lock: for client in self._clients.values(): client.next()
[docs] def get_analysis(self): """Return analysis results. Returns: dict: Empty dict (this analyzer is for plotting, does not produce analysis data) """ return {}