quantdom

repository·master·Indexed 20 days ago

https://github.com/constverum/quantdom

A Python-based backtesting framework for modeling financial strategies, portfolio management, and analyzing trading performance. Version 0.1.1 includes a GUI via MainWindow, a CLI for running backtests, and tools for visualizing price data (QuotesChart) and portfolio performance (EquityChart). It supports multiple data loaders (Yahoo, IEX, Stooq, etc.), technical indicator configuration, and comprehensive performance metrics including Sharpe and Sortino ratios.

Tokens
9.3K
Snippets
37
Records
45
Agent score
72%

What's inside quantdom

  1. Requirements for Quantdom

    master

    To run Quantdom, ensure your environment meets the following requirements:

    • Python: version 3.6 or higher
    • Dependencies:
      • PyQt5
      • PyQtGraph
      • NumPy

    For a complete list of dependencies, refer to the pyproject.toml file in the repository.

  2. Install Quantdom

    master

    You can install Quantdom using one of the following methods:

    Install stable release via PyPI

    Use pip to install the latest stable version:

    $ pip install quantdom

    Install development version from GitHub

    To install the latest development version directly from the source:

    $ pip install -U git+https://github.com/constverum/Quantdom.git

    Run the application

    Once installed, you can launch the Quantdom application by executing:

    $ quantdom

    Binary Downloads

    Binary packages are also available for specific operating systems via GitHub Releases:

    • Windows: .exe installer
    • MacOS: .dmg installer
    • Linux: .zip archive
  3. How to use Quantdom for backtesting

    master

    Follow these steps to perform a backtest within the Quantdom application:

    1. Launch: Run the quantdom command.
    2. Select Instrument: Go to the Data tab and choose a market instrument (symbol) for backtesting.
    3. Load Strategy: Go to the Quotes tab, specify the file containing your strategies, and select the strategy you wish to use.
    4. Execute: Run the backtest. After completion, you can analyze the performance results and optimize your strategy parameters.
  4. How the AbstractStrategy lifecycle works

    master

    The AbstractStrategy follows a specific execution lifecycle when the run() method is called:

    1. Initialization: The start() method is invoked, which immediately calls your implementation of init(). This is where you set up your state and parameters.
    2. Iteration: The strategy enters a loop where it iterates through available Quotes. For every quote received, your implementation of handle(quote) is executed.
    3. Completion: Once all quotes have been processed, the run() method finishes.

    When initializing your class, you can provide a name, a period, and a list of symbols. The strategy will automatically use the first symbol in the list as self.symbol.

    # Example of instantiation
    strategy = MyStrategy(name="TrendFollower", period=14, symbols=["AAPL", "MSFT"])
    strategy.run()
  5. Manage trading positions with BasePortfolio

    master

    The BasePortfolio class (exported as Portfolio) is the central manager for tracking account balance, leverage, and active/closed positions.

    Key capabilities:

    • Tracking: Maintains balance, equity, and a list of positions.
    • Position Management: Use add_position(position) to manually add a position or use the Order helper to open them.
    • Summarization: Call summarize() to calculate performance statistics (stats), performance metrics (performance), and equity/balance curves.
    • Optimization: Use run_optimization(strategy, params) to perform parameter sweeps across a strategy, utilizing optimization_mode() to ensure the portfolio state is backed up and restored between runs.
    from quantdom.lib.portfolio import Portfolio
    
    # Initialize with custom balance and leverage
    portfolio = BasePortfolio(balance=50_000, leverage=10)
    
    # After running a strategy or manual trades, summarize results
    portfolio.summarize()
    print(portfolio.performance)
    print(portfolio.equity_curve)
  6. Configure QuotesChart chart styles

    master

    The QuotesChart uses the ChartType enumeration to determine how price data is rendered. Supported styles include:

    • ChartType.CANDLESTICK: Renders OHLC data as candlesticks.
    • ChartType.BAR: Renders OHLC data as bars.
    • Default: If no specific style is matched in _get_chart_points, it falls back to a standard line plot of the closing prices.
  7. How the data loading lifecycle works

    master

    To prevent UI freezing during symbol lookups, Quantdom uses a background thread mechanism:

    1. SymbolsLoaderThread: A QtCore.QThread that calls get_symbols().
    2. Signal Flow: When the thread finishes, it emits symbols_loaded(object).
    3. UI Update: The DataTabWidget connects this signal to on_symbols_loaded, which populates a QtGui.QStandardItemModel used by the symbol combo box and a QCompleter for easy searching.
  8. Implement a custom strategy using AbstractStrategy

    master

    To create a custom trading strategy, inherit from AbstractStrategy. You must implement the init method for setup and the handle method to process incoming market data (quote).

    Key Components:

    • init(self, ...): Initialize strategy parameters, state variables (like counters or signal flags), and set the Portfolio.initial_balance.
    • handle(self, quote): This method is called for every new quote. Use the quote object to access market data (e.g., quote.open, quote.close, quote.time, quote.symbol).
    • Order: Use Order.open(**props) to open a new position and Order.close(position, ...) to close an existing one.
    • Portfolio: Access global portfolio settings like Portfolio.initial_balance.

    Example: Three-bar strategy

    This strategy enters a position after a specific sequence of bullish or bearish bars.

    from quantdom import AbstractStrategy, Order, Portfolio
    
    class ThreeBarStrategy(AbstractStrategy):
    
        def init(self, high_bars=3, low_bars=3):
            Portfolio.initial_balance = 100000  # default value
            self.seq_low_bars = 0
            self.seq_high_bars = 0
            self.signal = None
            self.last_position = None
            self.volume = 100  # shares
            self.high_bars = high_bars
            self.low_bars = low_bars
    
        def handle(self, quote):
            if self.signal:
                props = {
                    'symbol': self.symbol,  # current selected symbol
                    'otype': self.signal,
                    'price': quote.open,
                    'volume': self.volume,
                    'time': quote.time,
                }
                if not self.last_position:
                    self.last_position = Order.open(**kwargs)
                elif self.last_position.type != self.signal:
                    Order.close(self.last_position, price=quote.open, time=quote.time)
                    self.last_position = Order.open(**props)
                self.signal = False
                self.seq_high_bars = self.seq_low_bars = 0
    
            if quote.close > quote.open:
                self.seq_high_bars += 1
                self.seq_low_bars = 0
            else:
                self.seq_high_bars = 0
                self.seq_low_bars += 1
    
            if self.seq_high_bars == self.high_bars:
                self.signal = Order.BUY
            elif self.seq_low_bars == self.low_bars:
                self.signal = Order.SELL
    from quantdom import AbstractStrategy, Order, Portfolio
    
    class ThreeBarStrategy(AbstractStrategy):
    
        def init(self, high_bars=3, low_bars=3):
            Portfolio.initial_balance = 100000  # default value
            self.seq_low_bars = 0
            self.seq_high_bars = 0
            self.signal = None
            self.last_position = None
            self.volume = 100  # shares
            self.high_bars = high_bars
            self.low_bars = low_bars
    
        def handle(self, quote):
            if self.signal:
                props = {
                    'symbol': self.symbol,  # current selected symbol
                    'otype': self.signal,
                    'price': quote.open,
                    'volume': self.volume,
                    'time': quote.time,
                }
                if not self.last_position:
                    self.last_position = Order.open(**props)
                elif self.last_position.type != self.signal:
                    Order.close(self.last_position, price=quote.open, time=quote.time)
                    self.last_position = Order.open(**props)
                self.signal = False
                self.seq_high_bars = self.seq_low_bars = 0
    
            if quote.close > quote.open:
                self.seq_high_bars += 1
                self.seq_low_bars = 0
            else:
                self.seq_high_bars = 0
                self.seq_low_bars += 1
    
            if self.seq_high_bars == self.high_bars:
                self.signal = Order.BUY
            elif self.seq_low_bars == self.low_bars:
                self.signal = Order.SELL
  9. Calculate Sharpe and Sortino ratios

    master

    The module provides utility functions to calculate risk-adjusted return ratios based on daily percentage returns derived from Stats.

    • annualized_sharpe_ratio(stats): Calculates the Sharpe ratio using the formula: sqrt(ANNUAL_PERIOD) * mean(returns) / std(returns).
    • annualized_sortino_ratio(stats): Calculates the Sortino ratio, which only considers the standard deviation of negative returns (downside deviation).

    Both functions rely on day_percentage_returns(stats) to aggregate trade profits into daily return buckets.

    from quantdom.lib.performance import annualized_sharpe_ratio, annualized_sortino_ratio
    
    # stats is a Stats object
    sharpe = annualized_sharpe_ratio(stats)
    sortino = annualized_sortino_ratio(stats)
  10. Open and close trades using the Order class

    master

    The Order class provides static methods to interact with the global Portfolio instance to execute trades.

    • Order.open(symbol, otype, price, volume, time, sl=None, tp=None): Creates a new Position and automatically adds it to the Portfolio.
    • Order.close(position, price, time, volume=None): Closes an existing position at a specific price and time, updating the portfolio balance.

    OrderType constants include BUY, SELL, BUY_LIMIT, SELL_LIMIT, BUY_STOP, and SELL_STOP.

    from quantdom.lib.portfolio import Order, OrderType
    
    # Open a long position
    pos = Order.open(
        symbol=my_symbol,
        otype=OrderType.BUY,
        price=1.05850,
        volume=1.0,
        time=timestamp,
        sl=1.05500,
        tp=1.06500
    )
    
    # Close the position later
    Order.close(pos, price=1.06000, time=later_timestamp)
  11. Display portfolio performance with ResultsTable

    master

    Use ResultsTable to display high-level performance metrics from the Portfolio object. The table automatically configures its columns based on Portfolio.performance.columns. When calling .plot(), it populates the table with values, units, and headers defined in the portfolio's performance data. It supports 'separated' rows for grouping and applies color coding (blue for positive, red for negative) to specific metrics if configured.

    from quantdom.lib.tables import ResultsTable
    
    # Assuming a Portfolio instance has been initialized elsewhere
    table = ResultsTable()
    table.plot()
  12. Display system logs with LogTable

    master

    The LogTable is a simple UI component designed to show a list of log messages with associated timestamps. It uses a two-column format: Time and Message.

    from quantdom.lib.tables import LogTable
    
    table = LogTable()
    # Note: plot() is currently a no-op in the source