🌳DSL Script Structure and Best Practices

This documentation outlines the structure and best practices for using our Domain Specific Language (DSL) to develop trading strategies. As of now, it is beta and still being constantly improved. There is a considerable amount of tech debt.

Script Structure

  1. Initialization (Critical Section)

  2. User-Defined Strategy Logic

  3. Result Handling (Critical Section)

Each section serves a distinct purpose and is essential for the proper functioning of your strategy.


1. Initialization (Critical Section)

Purpose: The initialization section is responsible for setting up the environment required for your trading strategy. This includes initializing data providers, importing necessary modules, and configuring global settings.

Example:

# ===========================================
# BEGIN CRITICAL SECTION - DO NOT DELETE
# This section is essential for the application.
# ===========================================

from application.DSL.strategy_init import StrategyInitializer

initializer = StrategyInitializer(
    ticker=globals().get('ticker_input', 'AAPL'),
    start_date=globals().get('start_date'),
    end_date=globals().get('end_date'),
    pl_chart_container=globals().get('pl_chart_container'),
    ui_component=globals().get('ui_component'),
    labels=globals().get('labels')
)
context = initializer.initialize()

# ===========================================
# END CRITICAL SECTION
# ===========================================

Key Points:

  • Do Not Modify: This section is critical for the application. Any changes may result in incorrect initialization of the trading environment.

  • Global Variables: The globals() function retrieves global variables such as ticker_input, start_date, and end_date. These are essential for configuring the strategy.

  • Context: The context dictionary stores all initialized components, such as data providers, modules, and UI elements, which are accessible throughout the script.


2. User-Defined Strategy Logic

Purpose: This section is where you define and customize your trading strategy. It allows you to apply technical indicators, execute trading commands, and implement your logic for making trade decisions.

Example:

# ===========================================
# BEGIN USER DEFINED SECTION
# This section is intended for user customization.
# ===========================================

try:
    interval = "1h"  # Specify your preferred time interval

    # Example: MACD Command Execution
    macd_command = context['modules']['application.DSL.commands.technical_indicators'].MACDCommand(
        start=context['data_provider'].start_date,
        end=context['data_provider'].end_date
    )
    macd_data = macd_command.execute(context['data_provider'])

    # Example: Trading Logic based on MACD
    for i in range(1, len(macd_data)):
        # Your trading logic here

except Exception as e:
    context['modules']['logging'].error(f"Error in strategy execution: {e}")

# ===========================================
# END USER DEFINED SECTION
# ===========================================

Key Points:

  • Flexibility: This section is designed for maximum flexibility, allowing you to implement any trading logic you desire.

  • Error Handling: Use try-except blocks to catch and log any errors that occur during strategy execution.

  • Accessing Context: All necessary modules and data providers are available in the context dictionary. Use this to access pre-initialized components.


3. Result Handling (Critical Section)

Purpose: This section handles the output of your strategy, including saving trade history, calculating performance metrics, and plotting results. It is critical for generating and managing the results of your strategy.

Example:

# ===========================================
# CRITICAL SECTION FOR JSON OUTPUT - DO NOT DELETE
# ===========================================

from application.DSL.handle_results import StrategyResultHandler

result_handler = StrategyResultHandler(
    simulation=simulation,
    modules=context['modules'],
    ticker=context['ticker'],
    pl_plotter=context['pl_plotter'],
    ui_component=context['ui_component'],
    labels=context['labels']
)
result_handler.save_and_plot_results()

# ===========================================
# END CRITICAL SECTION FOR JSON OUTPUT
# ===========================================

Key Points:

  • Do Not Modify: Similar to the initialization section, this part is critical. Modifications should only be made if you fully understand the impact on result generation.

  • Result Management: The StrategyResultHandler manages the process of saving results to a file and plotting performance metrics. Customize this class if you need to alter how results are handled.

Best Practices

  • Keep Critical Sections Intact: The critical sections are essential for the correct operation of your strategy. Avoid making changes to these unless necessary.

  • Leverage the context Dictionary: All initialized components are stored in context. This allows for consistent access throughout your strategy script.

  • Modular Design: Import additional commands, indicators, or utilities as needed. This keeps your code clean and maintainable.

  • Error Logging: Ensure all errors are logged for easier debugging and strategy optimization.

Importing Additional Functions or Modules

If your strategy requires additional functionality, such as custom commands or indicators, you can import them as follows:

# Importing a custom command
from application.DSL.commands.technical_indicators import SMACommand

# Using the imported command
sma_command = SMACommand(start="2023-01-01", end="2023-06-30", period=14)
sma_result = sma_command.execute(context['data_provider'])

Conclusion

This structured approach ensures that your trading strategies are both powerful and maintainable. By following these guidelines, you can effectively leverage the DSL to develop and test a wide range of trading strategies.


Note on Using Modules in DSL Scripts

In your DSL scripts, there are two primary approaches to importing and utilizing modules: Standard Python Imports and Dynamic Imports via ImportCommand. Each method serves different purposes and offers distinct advantages depending on your use case. Below, we outline both methods to help you decide which is most appropriate for your needs.

1. Standard Python Import:

If you know in advance which modules your strategy requires, the conventional Python import statement is the most straightforward and efficient method.

Example:

from application.DSL.commands.technical_indicators import SMACommand
import numpy as np
  • How to Use: Once imported, these modules are immediately available for use throughout your script.

    sma_command = SMACommand(start="2023-01-01", end="2023-06-30", period=14)
    array_result = np.array([1, 2, 3])
  • Advantages: This method is clear, explicit, and easy to maintain. It ensures that all dependencies are loaded at the start of the script, making debugging simpler.

2. Dynamic Import Using ImportCommand:

Dynamic importing allows you to import modules at runtime, providing flexibility for scripts that need to adapt to different conditions or configurations.

Step 1: Defining Modules for Dynamic Import

Within the StrategyInitializer class, the ImportCommand is used to specify the modules that may be needed dynamically:

self.import_cmd = ImportCommand(
    'yfinance',
    'pandas',
    'logging',
    'json',
    'os',
    'datetime',
    'PyQt5.QtWidgets',
    'application.DSL.data_provider',
    'application.DSL.commands.core',
    'application.DSL.commands.technical_indicators',
    'application.DSL.commands.utility',
    'application.DSL.commands.trading',
    'application.DSL.trading_simulation.trading_simulation',
    'application.windows.elements.pl_plotter',
    'application.windows.elements.strategy_performance'
)
  • Explanation: This list covers a wide range of essential modules that are dynamically imported when your strategy is initialized. These modules are then accessible through the context dictionary.

Step 2: Executing Dynamic Import During Script Execution

If additional modules are required at runtime, they can be imported dynamically using the ImportCommand (here is how 'numpy' and 'scipy' would be imported using dynamic imports):

import_cmd = context['modules']['application.DSL.commands.utility'].ImportCommand('numpy', 'scipy')
imported_modules = import_cmd.execute()
  • Explanation:

    • Here, ImportCommand is accessed via the context dictionary.

    • Modules such as 'numpy' and 'scipy' are dynamically imported as needed during script execution.

    • The execute() method returns a dictionary (imported_modules) containing the imported modules, making them available for immediate use.

Step 3: Utilizing Dynamically Imported Modules

Once imported, these modules can be used in your strategy as follows:

result = imported_modules['numpy'].array([1, 2, 3])
  • Explanation: You can now access and use these dynamically imported modules just like any other standard imports.

Summary of Import Methods:

  • Standard Import: Ideal for scenarios where all required modules are known at the outset. This method promotes clarity and explicitness in your code, making it easier to read and maintain.

  • Dynamic Import: Best suited for strategies requiring flexibility, such as those that adapt based on runtime conditions or need to minimize memory usage by loading modules only when necessary.

Last updated