Best Practices for Traderion Bot Development¶
This guide outlines best practices for developing robust, maintainable, and efficient trading bots with the Traderion framework.
Architecture and Design¶
graph TD
A[Bot Architecture] --> B[Modular Design]
A --> C[Separation of Concerns]
A --> D[Event-Driven Approach]
B --> B1[Strategy Modules]
B --> B2[Risk Management Modules]
B --> B3[Monitoring Modules]
C --> C1[Data Processing]
C --> C2[Decision Logic]
C --> C3[Execution Logic]
D --> D1[Event Handlers]
D --> D2[Callbacks]
D --> D3[State Management]
style A fill:#f5f5f5,stroke:#333,stroke-width:2px
style B fill:#d5e8d4,stroke:#333
style C fill:#d4f1f9,stroke:#333
style D fill:#ffe6cc,stroke:#333
Modular Design Principles¶
- Single Responsibility Principle
- Each class or module should have one specific responsibility
-
Strategies should focus on trading logic, not infrastructure concerns
-
Dependency Injection
- Pass dependencies (like API clients) to classes rather than creating them internally
-
Makes testing and configuration easier
-
Composition Over Inheritance
- Prefer composing functionality from smaller components
- Use strategies as modular components rather than deep inheritance hierarchies
Example: Well-Structured Bot¶
class WellStructuredBot(TraderionBot):
def __init__(self, username, password, room_id):
super().__init__(username, password, room_id)
# Strategy components
self.signal_generator = SignalGenerator()
self.risk_manager = RiskManager(max_position=50000)
self.position_manager = PositionManager(self.api)
self.monitor = PerformanceMonitor()
# State
self.price_history = []
self.current_position = None
def on_market_price_change(self, old_prices, new_prices):
# Update state
self.price_history.append(new_prices)
self.current_position = self.api.get_position()
# Log event
self.monitor.log_price_change(old_prices, new_prices)
def main_loop(self):
# Generate signal
signal = self.signal_generator.generate_signal(self.price_history)
# Apply risk management
adjusted_signal = self.risk_manager.adjust_signal(
signal,
self.current_position
)
# Execute trade if needed
if adjusted_signal != 0:
self.position_manager.execute_signal(adjusted_signal)
self.monitor.log_trade(adjusted_signal)
Code Organization¶
Project Structure¶
traderion_bot/
├── main.py # Entry point
├── config.py # Configuration
├── bots/
│ ├── __init__.py
│ ├── base_bot.py # Base bot implementation
│ ├── ema_bot.py # EMA strategy bot
│ └── composite_bot.py # Multi-strategy bot
├── strategies/
│ ├── __init__.py
│ ├── base.py # Base strategy class
│ ├── technical/ # Technical analysis strategies
│ │ ├── __init__.py
│ │ ├── ema.py
│ │ ├── macd.py
│ │ └── rsi.py
│ └── client/ # Client interaction strategies
│ ├── __init__.py
│ └── market_maker.py
├── utils/
│ ├── __init__.py
│ ├── logging.py # Logging utilities
│ └── indicators.py # Technical indicators
└── tests/ # Unit tests
├── __init__.py
├── test_strategies.py
└── test_indicators.py
Coding Standards¶
- Follow PEP 8 Style Guide
- Consistent indentation (4 spaces)
- Maximum line length of 79 characters
- Descriptive variable names
-
Proper docstrings
-
Use Type Hints
def calculate_position_size(price: float, volatility: float) -> int: """ Calculate position size based on price and volatility. Args: price: Current market price volatility: Market volatility (standard deviation) Returns: Position size in number of shares/contracts """ return int(10000 / (price * volatility)) -
Separate Configuration from Code
# config.py STRATEGY_PARAMS = { 'ema': { 'small_period': 5, 'large_period': 20, 'target_amount': 10000 }, 'rsi': { 'period': 14, 'overbought': 70, 'oversold': 30 } } # bot.py from config import STRATEGY_PARAMS class MyBot(TraderionBot): def __init__(self, username, password, room_id): super().__init__(username, password, room_id) self.strategy = EMA(self.api, STRATEGY_PARAMS['ema'])
Error Handling and Resilience¶
Robust Error Handling¶
from traderion.exceptions import LoginError, RoomNotFound, ServerError, ValidationError, TradingError
def execute_trade(self, direction, amount, price):
try:
# Attempt to execute trade
result = self.api.hit_price(direction, amount, price)
logger.info(f"Trade executed: {direction}, {amount}, {price}")
return result
except TradingError as e:
# Handle specific trading errors
logger.warning(f"Trading error: {e}")
self.handle_trading_error(e)
except ServerError as e:
# Handle server errors
logger.error(f"Server error: {e}")
self.retry_queue.append((direction, amount, price))
except Exception as e:
# Catch-all for unexpected errors
logger.critical(f"Unexpected error: {e}", exc_info=True)
# Implement fallback behavior
return None
Graceful Degradation¶
class ResilientBot(TraderionBot):
def __init__(self, username, password, room_id):
super().__init__(username, password, room_id)
self.fallback_mode = False
self.retry_queue = []
def main_loop(self):
# Check if we should be in fallback mode
if self.fallback_mode:
self.execute_fallback_strategy()
return
try:
# Normal operation
self.execute_primary_strategy()
except Exception as e:
logger.error(f"Switching to fallback mode due to: {e}")
self.fallback_mode = True
self.execute_fallback_strategy()
def execute_fallback_strategy(self):
"""Simple fallback strategy that just maintains current position."""
# Don't take new positions, just manage existing ones
position = self.api.get_position()
if abs(position['amount']) > 0:
# Check if we need to reduce risk
if position['pnl_percentage'] < -0.05: # 5% loss
self.reduce_position(position)
Comprehensive Logging¶
Structured Logging¶
import logging
import json
from datetime import datetime
class StructuredLogger:
def __init__(self, bot_name, log_file=None):
self.bot_name = bot_name
# Configure logger
self.logger = logging.getLogger(bot_name)
self.logger.setLevel(logging.INFO)
# Console handler
console = logging.StreamHandler()
console.setLevel(logging.INFO)
# File handler (optional)
if log_file:
file_handler = logging.FileHandler(log_file)
file_handler.setLevel(logging.DEBUG)
self.logger.addHandler(file_handler)
self.logger.addHandler(console)
def log_event(self, event_type, data):
"""Log a structured event."""
log_entry = {
"timestamp": datetime.now().isoformat(),
"bot": self.bot_name,
"event_type": event_type,
"data": data
}
self.logger.info(json.dumps(log_entry))
def log_trade(self, direction, amount, price, reason=None):
"""Log a trade event."""
self.log_event("trade", {
"direction": "buy" if direction == 1 else "sell",
"amount": amount,
"price": price,
"reason": reason
})
def log_position(self, position):
"""Log current position."""
self.log_event("position", {
"amount": position["amount"],
"rate": position["rate"],
"pnl": position["pnl"],
"pnl_percentage": position["pnl_percentage"]
})
def log_error(self, error_type, message, exception=None):
"""Log an error."""
error_data = {
"type": error_type,
"message": message
}
if exception:
error_data["exception"] = str(exception)
self.log_event("error", error_data)
Usage Example¶
class LoggingBot(TraderionBot):
def __init__(self, username, password, room_id):
super().__init__(username, password, room_id)
self.logger = StructuredLogger("EMABot", "ema_bot.log")
def on_market_price_change(self, old_prices, new_prices):
self.logger.log_event("price_change", {
"old": old_prices,
"new": new_prices,
"delta": {
"bid": new_prices["bid"] - old_prices["bid"],
"ask": new_prices["ask"] - old_prices["ask"]
}
})
def on_position_change(self, old_position, new_position):
self.logger.log_position(new_position)
def log_info(self):
while True:
position = self.api.get_position()
self.logger.log_position(position)
time.sleep(60) # Log position every minute
Testing Strategies¶
flowchart TD
A[Testing Strategy] --> B[Unit Tests]
A --> C[Integration Tests]
A --> D[Simulation Tests]
B --> B1[Test Indicators]
B --> B2[Test Signal Generation]
B --> B3[Test Risk Management]
C --> C1[Test Bot with Mock API]
C --> C2[Test Strategy Combinations]
D --> D1[Test with Historical Data]
D --> D2[Test in Simulator]
style A fill:#f5f5f5,stroke:#333,stroke-width:2px
style B fill:#d5e8d4,stroke:#333
style C fill:#d4f1f9,stroke:#333
style D fill:#ffe6cc,stroke:#333
Unit Testing¶
import unittest
from unittest.mock import Mock, patch
from traderion.strategies.ema import CrossingEMAs
class TestEMAStrategy(unittest.TestCase):
def setUp(self):
# Create mock API
self.mock_api = Mock()
# Configure mock to return test data
self.mock_api.get_eb_depth.return_value = {
0: [{'price': 100.0, 'amount': 10}],
1: [{'price': 101.0, 'amount': 10}]
}
# Create strategy with mock API
self.strategy = CrossingEMAs(self.mock_api, {
'target_amount': 1000,
'small_ema_period': 5,
'big_ema_period': 20
})
def test_compute_emas(self):
# Test EMA calculation
self.strategy.price_curve = [100.0, 101.0, 102.0, 101.5, 101.8]
self.strategy.compute_emas()
# Check results (approximate values)
self.assertAlmostEqual(self.strategy.ema_5, 101.26, places=2)
def test_get_cross_direction_bullish(self):
# Set up a bullish cross
self.strategy.ema_5 = 101.0
self.strategy.ema_20 = 100.0
self.strategy.last_cross = 0 # BEARISH_CROSS
# Test detection
direction = self.strategy.get_new_cross_direction()
self.assertEqual(direction, 1) # BULLISH_CROSS
def test_load_executes_trades(self):
# Set up strategy state
self.strategy.last_cross = 1 # BULLISH_CROSS
self.mock_api.ticket_unit = 1
self.mock_api.max_ticket = 10
# Mock position
self.mock_api.get_position.return_value = {
'amount': 0,
'limit': 10000
}
# Mock hit_price to return successful trade
self.mock_api.hit_price.return_value = {'amount': 10}
# Call load method
self.strategy.load(1) # BULLISH_CROSS
# Verify hit_price was called with correct parameters
self.mock_api.hit_price.assert_called_with(
1, # direction (BULLISH_CROSS)
10, # amount (max_ticket)
100.0 # price from mock depth
)
Integration Testing¶
class TestBotIntegration(unittest.TestCase):
def setUp(self):
# Create a test bot with mock API
self.api_mock = Mock()
self.bot = TestBot('test', 'test', 123)
self.bot.api = self.api_mock
# Set up mock API responses
self.api_mock.get_market_prices.return_value = {
'bid': 100.0,
'ask': 101.0,
'open': 99.0
}
self.api_mock.get_price_curve.return_value = [
(datetime.now(), 99.0),
(datetime.now(), 100.0),
(datetime.now(), 101.0)
]
self.api_mock.get_position.return_value = {
'amount': 0,
'rate': 0,
'pnl': 0,
'limit': 10000
}
def test_market_price_change_triggers_strategy(self):
# Create a spy on the strategy method
strategy_spy = Mock()
self.bot.strategy.on_price_curve_change = strategy_spy
# Trigger the event
self.bot.on_market_price_change(
{'bid': 99.0, 'ask': 100.0, 'open': 99.0},
{'bid': 100.0, 'ask': 101.0, 'open': 99.0}
)
# Verify strategy method was called
strategy_spy.assert_called_once()
``` -->
## Configuration Management
```python
import os
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
# Configuration settings
CONFIG = {
"credentials": {
"username": os.getenv("TRADERION_USERNAME"),
"password": os.getenv("TRADERION_PASSWORD"),
},
"room_id": int(os.getenv("ROOM_ID", 0)),
"risk_parameters": {
"max_position": int(os.getenv("MAX_POSITION", 50000)),
"max_drawdown": float(os.getenv("MAX_DRAWDOWN", 0.05)),
},
"strategy_parameters": {
"ema_small": int(os.getenv("EMA_SMALL", 5)),
"ema_big": int(os.getenv("EMA_BIG", 20)),
}
}