Phase 4: Paper trading implementation
- Create PaperStrategyInstance extending StrategyInstance - Integrate PaperBroker for simulated order execution - Add trade_order() method translating Blockly calls to broker - Add mode selection in Strategies.create_strategy_instance() - Update execute_strategy() to support paper/backtest/live modes - Include comprehensive tests for paper trading functionality Paper trading now works with: - Market and limit orders - Position tracking with P&L - Balance management - Trade history - Reset functionality Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
1bb224b15d
commit
51ec74175d
|
|
@ -7,9 +7,10 @@ import datetime as dt
|
|||
import json
|
||||
import uuid
|
||||
import traceback
|
||||
from typing import Any
|
||||
from typing import Any, Optional
|
||||
from PythonGenerator import PythonGenerator
|
||||
from StrategyInstance import StrategyInstance
|
||||
from brokers import TradingMode
|
||||
|
||||
# Configure logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -61,7 +62,100 @@ class Strategies:
|
|||
self.default_exchange = 'Binance'
|
||||
self.default_symbol = 'BTCUSD'
|
||||
|
||||
self.active_instances: dict[tuple[int, str], StrategyInstance] = {} # Key: (user_id, strategy_id)
|
||||
self.active_instances: dict[tuple[int, str, str], StrategyInstance] = {} # Key: (user_id, strategy_id, mode)
|
||||
|
||||
def create_strategy_instance(
|
||||
self,
|
||||
mode: str,
|
||||
strategy_instance_id: str,
|
||||
strategy_id: str,
|
||||
strategy_name: str,
|
||||
user_id: int,
|
||||
generated_code: str,
|
||||
initial_balance: float = 10000.0,
|
||||
commission: float = 0.001,
|
||||
slippage: float = 0.0,
|
||||
price_provider: Any = None,
|
||||
) -> StrategyInstance:
|
||||
"""
|
||||
Factory method to create the appropriate strategy instance based on mode.
|
||||
|
||||
:param mode: Trading mode ('backtest', 'paper', 'live').
|
||||
:param strategy_instance_id: Unique instance identifier.
|
||||
:param strategy_id: Strategy identifier.
|
||||
:param strategy_name: Strategy name.
|
||||
:param user_id: User identifier.
|
||||
:param generated_code: Generated Python code from Blockly.
|
||||
:param initial_balance: Starting balance for paper/backtest.
|
||||
:param commission: Commission rate.
|
||||
:param slippage: Slippage rate.
|
||||
:param price_provider: Callable for getting current prices.
|
||||
:return: Strategy instance appropriate for the mode.
|
||||
"""
|
||||
mode = mode.lower()
|
||||
|
||||
if mode == TradingMode.PAPER:
|
||||
from paper_strategy_instance import PaperStrategyInstance
|
||||
return PaperStrategyInstance(
|
||||
strategy_instance_id=strategy_instance_id,
|
||||
strategy_id=strategy_id,
|
||||
strategy_name=strategy_name,
|
||||
user_id=user_id,
|
||||
generated_code=generated_code,
|
||||
data_cache=self.data_cache,
|
||||
indicators=self.indicators_manager,
|
||||
trades=self.trades,
|
||||
initial_balance=initial_balance,
|
||||
commission=commission,
|
||||
slippage=slippage if slippage > 0 else 0.0005,
|
||||
price_provider=price_provider,
|
||||
)
|
||||
|
||||
elif mode == TradingMode.BACKTEST:
|
||||
from backtest_strategy_instance import BacktestStrategyInstance
|
||||
return BacktestStrategyInstance(
|
||||
strategy_instance_id=strategy_instance_id,
|
||||
strategy_id=strategy_id,
|
||||
strategy_name=strategy_name,
|
||||
user_id=user_id,
|
||||
generated_code=generated_code,
|
||||
data_cache=self.data_cache,
|
||||
indicators=self.indicators_manager,
|
||||
trades=self.trades,
|
||||
)
|
||||
|
||||
elif mode == TradingMode.LIVE:
|
||||
# Live trading not yet implemented - fall back to paper for safety
|
||||
logger.warning("Live trading mode not yet implemented. Using paper trading instead.")
|
||||
from paper_strategy_instance import PaperStrategyInstance
|
||||
return PaperStrategyInstance(
|
||||
strategy_instance_id=strategy_instance_id,
|
||||
strategy_id=strategy_id,
|
||||
strategy_name=strategy_name,
|
||||
user_id=user_id,
|
||||
generated_code=generated_code,
|
||||
data_cache=self.data_cache,
|
||||
indicators=self.indicators_manager,
|
||||
trades=self.trades,
|
||||
initial_balance=initial_balance,
|
||||
commission=commission,
|
||||
slippage=slippage,
|
||||
price_provider=price_provider,
|
||||
)
|
||||
|
||||
else:
|
||||
# Default to standard StrategyInstance for unknown modes
|
||||
logger.warning(f"Unknown mode '{mode}'. Using standard StrategyInstance.")
|
||||
return StrategyInstance(
|
||||
strategy_instance_id=strategy_instance_id,
|
||||
strategy_id=strategy_id,
|
||||
strategy_name=strategy_name,
|
||||
user_id=user_id,
|
||||
generated_code=generated_code,
|
||||
data_cache=self.data_cache,
|
||||
indicators=self.indicators_manager,
|
||||
trades=self.trades,
|
||||
)
|
||||
|
||||
def _save_strategy(self, strategy_data: dict, default_source: dict) -> dict:
|
||||
"""
|
||||
|
|
@ -351,11 +445,24 @@ class Strategies:
|
|||
except Exception as e:
|
||||
logger.error(f"Error updating stats for strategy '{strategy_id}': {e}", exc_info=True)
|
||||
|
||||
def execute_strategy(self, strategy_data: dict[str, Any]) -> dict[str, Any]:
|
||||
def execute_strategy(
|
||||
self,
|
||||
strategy_data: dict[str, Any],
|
||||
mode: str = 'backtest',
|
||||
initial_balance: float = 10000.0,
|
||||
commission: float = 0.001,
|
||||
slippage: float = 0.0,
|
||||
price_provider: Any = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Executes a strategy based on the provided strategy data.
|
||||
|
||||
:param strategy_data: A dictionary containing strategy details.
|
||||
:param mode: Trading mode ('backtest', 'paper', 'live').
|
||||
:param initial_balance: Starting balance for paper/backtest modes.
|
||||
:param commission: Commission rate.
|
||||
:param slippage: Slippage rate for market orders.
|
||||
:param price_provider: Callable for getting current prices (paper/live).
|
||||
:return: A dictionary indicating success or failure with relevant messages.
|
||||
"""
|
||||
try:
|
||||
|
|
@ -367,9 +474,9 @@ class Strategies:
|
|||
if not strategy_id or not strategy_name or not user_id:
|
||||
return {"success": False, "message": "Strategy data is incomplete."}
|
||||
|
||||
# Generate a deterministic strategy_instance_id
|
||||
strategy_instance_id = f"{user_id}_{strategy_name}"
|
||||
instance_key = (user_id, strategy_id) # Unique key for the strategy-user pair
|
||||
# Generate a deterministic strategy_instance_id (include mode for uniqueness)
|
||||
strategy_instance_id = f"{user_id}_{strategy_name}_{mode}"
|
||||
instance_key = (user_id, strategy_id, mode) # Include mode in key
|
||||
|
||||
# Retrieve or create StrategyInstance
|
||||
if instance_key not in self.active_instances:
|
||||
|
|
@ -377,16 +484,18 @@ class Strategies:
|
|||
if not generated_code:
|
||||
return {"success": False, "message": "No 'next()' method defined for the strategy."}
|
||||
|
||||
# Instantiate StrategyInstance
|
||||
strategy_instance = StrategyInstance(
|
||||
# Use factory method to create appropriate instance for mode
|
||||
strategy_instance = self.create_strategy_instance(
|
||||
mode=mode,
|
||||
strategy_instance_id=strategy_instance_id,
|
||||
strategy_id=strategy_id,
|
||||
strategy_name=strategy_name,
|
||||
user_id=user_id,
|
||||
generated_code=generated_code,
|
||||
data_cache=self.data_cache,
|
||||
indicators=self.indicators_manager,
|
||||
trades=self.trades
|
||||
initial_balance=initial_balance,
|
||||
commission=commission,
|
||||
slippage=slippage,
|
||||
price_provider=price_provider,
|
||||
)
|
||||
|
||||
# Store in active_instances
|
||||
|
|
|
|||
|
|
@ -0,0 +1,261 @@
|
|||
"""
|
||||
Paper Trading Strategy Instance for BrighterTrading.
|
||||
|
||||
Extends StrategyInstance with paper trading capabilities using
|
||||
the PaperBroker for simulated order execution.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Any, Optional
|
||||
import datetime as dt
|
||||
|
||||
from StrategyInstance import StrategyInstance
|
||||
from brokers import PaperBroker, OrderSide, OrderType
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PaperStrategyInstance(StrategyInstance):
|
||||
"""
|
||||
Strategy instance for paper trading mode.
|
||||
|
||||
Uses PaperBroker for simulated order execution with live price data.
|
||||
Maintains full strategy execution context while simulating trades.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
strategy_instance_id: str,
|
||||
strategy_id: str,
|
||||
strategy_name: str,
|
||||
user_id: int,
|
||||
generated_code: str,
|
||||
data_cache: Any,
|
||||
indicators: Any | None,
|
||||
trades: Any | None,
|
||||
initial_balance: float = 10000.0,
|
||||
commission: float = 0.001,
|
||||
slippage: float = 0.0005,
|
||||
price_provider: Any = None,
|
||||
):
|
||||
"""
|
||||
Initialize the PaperStrategyInstance.
|
||||
|
||||
:param strategy_instance_id: Unique identifier for this instance.
|
||||
:param strategy_id: Strategy identifier.
|
||||
:param strategy_name: Strategy name.
|
||||
:param user_id: User identifier.
|
||||
:param generated_code: Python code generated from Blockly.
|
||||
:param data_cache: DataCache instance.
|
||||
:param indicators: Indicators manager.
|
||||
:param trades: Trades manager (not used directly, kept for compatibility).
|
||||
:param initial_balance: Starting paper trading balance.
|
||||
:param commission: Commission rate for paper trades.
|
||||
:param slippage: Slippage rate for market orders.
|
||||
:param price_provider: Callable to get current prices.
|
||||
"""
|
||||
# Initialize the paper broker
|
||||
self.paper_broker = PaperBroker(
|
||||
initial_balance=initial_balance,
|
||||
commission=commission,
|
||||
slippage=slippage,
|
||||
price_provider=price_provider,
|
||||
data_cache=data_cache
|
||||
)
|
||||
|
||||
# Set broker before calling parent __init__
|
||||
self.broker = self.paper_broker
|
||||
|
||||
super().__init__(
|
||||
strategy_instance_id, strategy_id, strategy_name, user_id,
|
||||
generated_code, data_cache, indicators, trades
|
||||
)
|
||||
|
||||
# Initialize balance attributes from paper broker
|
||||
self.starting_balance = initial_balance
|
||||
self.current_balance = initial_balance
|
||||
self.available_balance = initial_balance
|
||||
self.available_strategy_balance = initial_balance
|
||||
|
||||
# Update exec_context with balance attributes
|
||||
self.exec_context['starting_balance'] = self.starting_balance
|
||||
self.exec_context['current_balance'] = self.current_balance
|
||||
self.exec_context['available_balance'] = self.available_balance
|
||||
self.exec_context['available_strategy_balance'] = self.available_strategy_balance
|
||||
|
||||
logger.info(f"PaperStrategyInstance created with balance: {initial_balance}")
|
||||
|
||||
def trade_order(
|
||||
self,
|
||||
trade_type: str,
|
||||
size: float,
|
||||
order_type: str,
|
||||
source: dict = None,
|
||||
tif: str = 'GTC',
|
||||
stop_loss: dict = None,
|
||||
trailing_stop: dict = None,
|
||||
take_profit: dict = None,
|
||||
limit: dict = None,
|
||||
trailing_limit: dict = None,
|
||||
target_market: dict = None,
|
||||
name_order: dict = None
|
||||
):
|
||||
"""
|
||||
Place an order via the paper broker.
|
||||
|
||||
This method translates the Blockly-generated order call to
|
||||
the PaperBroker interface.
|
||||
"""
|
||||
# Extract symbol from source
|
||||
symbol = 'BTC/USDT' # Default
|
||||
if source:
|
||||
symbol = source.get('symbol') or source.get('market', 'BTC/USDT')
|
||||
|
||||
# Map trade_type to OrderSide
|
||||
if trade_type.lower() == 'buy':
|
||||
side = OrderSide.BUY
|
||||
elif trade_type.lower() == 'sell':
|
||||
side = OrderSide.SELL
|
||||
else:
|
||||
logger.error(f"Invalid trade_type '{trade_type}'. Order not executed.")
|
||||
return
|
||||
|
||||
# Map order_type to OrderType
|
||||
order_type_upper = order_type.upper()
|
||||
if order_type_upper == 'MARKET':
|
||||
bt_order_type = OrderType.MARKET
|
||||
price = None
|
||||
elif order_type_upper == 'LIMIT':
|
||||
bt_order_type = OrderType.LIMIT
|
||||
price = limit.get('value') if limit else self.get_current_price()
|
||||
else:
|
||||
bt_order_type = OrderType.MARKET
|
||||
price = None
|
||||
|
||||
# Extract stop loss and take profit
|
||||
stop_loss_price = stop_loss.get('value') if stop_loss else None
|
||||
take_profit_price = take_profit.get('value') if take_profit else None
|
||||
|
||||
# Place the order
|
||||
result = self.paper_broker.place_order(
|
||||
symbol=symbol,
|
||||
side=side,
|
||||
order_type=bt_order_type,
|
||||
size=size,
|
||||
price=price,
|
||||
stop_loss=stop_loss_price,
|
||||
take_profit=take_profit_price,
|
||||
time_in_force=tif
|
||||
)
|
||||
|
||||
if result.success:
|
||||
message = f"{trade_type.upper()} order placed: {size} {symbol} @ {order_type_upper}"
|
||||
self.notify_user(message)
|
||||
logger.info(message)
|
||||
|
||||
# Track order in history
|
||||
self.orders.append({
|
||||
'order_id': result.order_id,
|
||||
'symbol': symbol,
|
||||
'side': trade_type,
|
||||
'size': size,
|
||||
'type': order_type,
|
||||
'status': result.status.value,
|
||||
'timestamp': dt.datetime.now().isoformat()
|
||||
})
|
||||
else:
|
||||
logger.warning(f"Order failed: {result.message}")
|
||||
self.notify_user(f"Order failed: {result.message}")
|
||||
|
||||
return result
|
||||
|
||||
def update_prices(self, price_data: dict):
|
||||
"""
|
||||
Update current prices in the paper broker.
|
||||
|
||||
:param price_data: Dict mapping symbols to prices.
|
||||
"""
|
||||
for symbol, price in price_data.items():
|
||||
self.paper_broker.update_price(symbol, price)
|
||||
|
||||
# Process any pending orders
|
||||
events = self.paper_broker.update()
|
||||
for event in events:
|
||||
if event['type'] == 'fill':
|
||||
self.trade_history.append(event)
|
||||
logger.info(f"Order filled: {event}")
|
||||
|
||||
# Update balance attributes
|
||||
self._update_balances()
|
||||
|
||||
def _update_balances(self):
|
||||
"""Update balance attributes from paper broker."""
|
||||
self.current_balance = self.paper_broker.get_balance()
|
||||
self.available_balance = self.paper_broker.get_available_balance()
|
||||
|
||||
self.exec_context['current_balance'] = self.current_balance
|
||||
self.exec_context['available_balance'] = self.available_balance
|
||||
|
||||
def get_current_price(self, timeframe: str = '1h', exchange: str = 'binance',
|
||||
symbol: str = 'BTC/USDT') -> float:
|
||||
"""Get current price from paper broker."""
|
||||
return self.paper_broker.get_current_price(symbol)
|
||||
|
||||
def get_available_balance(self) -> float:
|
||||
"""Get available cash balance."""
|
||||
self.available_balance = self.paper_broker.get_available_balance()
|
||||
self.exec_context['available_balance'] = self.available_balance
|
||||
return self.available_balance
|
||||
|
||||
def get_current_balance(self) -> float:
|
||||
"""Get current total balance including unrealized P&L."""
|
||||
self.current_balance = self.paper_broker.get_balance()
|
||||
self.exec_context['current_balance'] = self.current_balance
|
||||
return self.current_balance
|
||||
|
||||
def get_starting_balance(self) -> float:
|
||||
"""Get starting balance."""
|
||||
return self.starting_balance
|
||||
|
||||
def get_active_trades(self) -> int:
|
||||
"""Get number of active positions."""
|
||||
return len(self.paper_broker.get_all_positions())
|
||||
|
||||
def get_filled_orders(self) -> int:
|
||||
"""Get number of filled orders."""
|
||||
return len([o for o in self.paper_broker._orders.values()
|
||||
if o.status.value == 'filled'])
|
||||
|
||||
def get_position(self, symbol: str):
|
||||
"""Get position for a symbol."""
|
||||
return self.paper_broker.get_position(symbol)
|
||||
|
||||
def close_position(self, symbol: str):
|
||||
"""Close a position."""
|
||||
return self.paper_broker.close_position(symbol)
|
||||
|
||||
def close_all_positions(self):
|
||||
"""Close all positions."""
|
||||
return self.paper_broker.close_all_positions()
|
||||
|
||||
def get_trade_history(self):
|
||||
"""Get all executed trades."""
|
||||
return self.paper_broker.get_trade_history()
|
||||
|
||||
def reset(self):
|
||||
"""Reset the paper trading state."""
|
||||
self.paper_broker.reset()
|
||||
self._update_balances()
|
||||
self.trade_history = []
|
||||
self.orders = []
|
||||
logger.info("PaperStrategyInstance reset")
|
||||
|
||||
def save_context(self):
|
||||
"""Save strategy context including paper trading state."""
|
||||
self._update_balances()
|
||||
super().save_context()
|
||||
|
||||
def notify_user(self, message: str):
|
||||
"""Send notification to user."""
|
||||
logger.info(f"[Paper] {message}")
|
||||
# Could emit via SocketIO if available
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
"""
|
||||
Tests for paper trading functionality.
|
||||
"""
|
||||
import pytest
|
||||
from paper_strategy_instance import PaperStrategyInstance
|
||||
from brokers import OrderSide, OrderType, OrderStatus
|
||||
|
||||
|
||||
class TestPaperStrategyInstance:
|
||||
"""Tests for PaperStrategyInstance."""
|
||||
|
||||
def test_create_instance(self):
|
||||
"""Test creating a paper strategy instance."""
|
||||
instance = PaperStrategyInstance(
|
||||
strategy_instance_id='test-instance-1',
|
||||
strategy_id='test-strategy-1',
|
||||
strategy_name='Test Strategy',
|
||||
user_id=1,
|
||||
generated_code='def next(self): pass',
|
||||
data_cache=None,
|
||||
indicators=None,
|
||||
trades=None,
|
||||
initial_balance=10000.0,
|
||||
)
|
||||
|
||||
assert instance.strategy_instance_id == 'test-instance-1'
|
||||
assert instance.starting_balance == 10000.0
|
||||
assert instance.get_current_balance() == 10000.0
|
||||
assert instance.get_available_balance() == 10000.0
|
||||
|
||||
def test_update_prices(self):
|
||||
"""Test updating prices in paper broker."""
|
||||
instance = PaperStrategyInstance(
|
||||
strategy_instance_id='test-1',
|
||||
strategy_id='strat-1',
|
||||
strategy_name='Test',
|
||||
user_id=1,
|
||||
generated_code='def next(self): pass',
|
||||
data_cache=None,
|
||||
indicators=None,
|
||||
trades=None,
|
||||
)
|
||||
|
||||
instance.update_prices({'BTC/USDT': 50000.0, 'ETH/USDT': 3000.0})
|
||||
|
||||
assert instance.get_current_price(symbol='BTC/USDT') == 50000.0
|
||||
assert instance.get_current_price(symbol='ETH/USDT') == 3000.0
|
||||
|
||||
def test_trade_order_market_buy(self):
|
||||
"""Test placing a market buy order."""
|
||||
instance = PaperStrategyInstance(
|
||||
strategy_instance_id='test-1',
|
||||
strategy_id='strat-1',
|
||||
strategy_name='Test',
|
||||
user_id=1,
|
||||
generated_code='def next(self): pass',
|
||||
data_cache=None,
|
||||
indicators=None,
|
||||
trades=None,
|
||||
initial_balance=10000.0,
|
||||
commission=0.001,
|
||||
)
|
||||
|
||||
# Set price
|
||||
instance.update_prices({'BTC/USDT': 50000.0})
|
||||
|
||||
# Place order
|
||||
result = instance.trade_order(
|
||||
trade_type='buy',
|
||||
size=0.1,
|
||||
order_type='MARKET',
|
||||
source={'symbol': 'BTC/USDT'}
|
||||
)
|
||||
|
||||
assert result.success
|
||||
assert result.status == OrderStatus.FILLED
|
||||
|
||||
# Check position exists
|
||||
pos = instance.get_position('BTC/USDT')
|
||||
assert pos is not None
|
||||
assert pos.size == 0.1
|
||||
|
||||
# Check balance reduced
|
||||
assert instance.get_available_balance() < 10000.0
|
||||
|
||||
def test_trade_order_sell(self):
|
||||
"""Test selling a position."""
|
||||
instance = PaperStrategyInstance(
|
||||
strategy_instance_id='test-1',
|
||||
strategy_id='strat-1',
|
||||
strategy_name='Test',
|
||||
user_id=1,
|
||||
generated_code='def next(self): pass',
|
||||
data_cache=None,
|
||||
indicators=None,
|
||||
trades=None,
|
||||
initial_balance=10000.0,
|
||||
commission=0,
|
||||
slippage=0,
|
||||
)
|
||||
|
||||
# Buy first
|
||||
instance.update_prices({'BTC/USDT': 50000.0})
|
||||
instance.trade_order(
|
||||
trade_type='buy',
|
||||
size=0.1,
|
||||
order_type='MARKET',
|
||||
source={'symbol': 'BTC/USDT'}
|
||||
)
|
||||
|
||||
# Now sell at higher price
|
||||
instance.update_prices({'BTC/USDT': 51000.0})
|
||||
result = instance.trade_order(
|
||||
trade_type='sell',
|
||||
size=0.1,
|
||||
order_type='MARKET',
|
||||
source={'symbol': 'BTC/USDT'}
|
||||
)
|
||||
|
||||
assert result.success
|
||||
|
||||
# Position should be closed
|
||||
pos = instance.get_position('BTC/USDT')
|
||||
assert pos is None
|
||||
|
||||
def test_close_position(self):
|
||||
"""Test closing a position via close_position method."""
|
||||
instance = PaperStrategyInstance(
|
||||
strategy_instance_id='test-1',
|
||||
strategy_id='strat-1',
|
||||
strategy_name='Test',
|
||||
user_id=1,
|
||||
generated_code='def next(self): pass',
|
||||
data_cache=None,
|
||||
indicators=None,
|
||||
trades=None,
|
||||
)
|
||||
|
||||
instance.update_prices({'BTC/USDT': 50000.0})
|
||||
instance.trade_order(
|
||||
trade_type='buy',
|
||||
size=0.1,
|
||||
order_type='MARKET',
|
||||
source={'symbol': 'BTC/USDT'}
|
||||
)
|
||||
|
||||
result = instance.close_position('BTC/USDT')
|
||||
assert result.success
|
||||
|
||||
def test_close_all_positions(self):
|
||||
"""Test closing all positions."""
|
||||
instance = PaperStrategyInstance(
|
||||
strategy_instance_id='test-1',
|
||||
strategy_id='strat-1',
|
||||
strategy_name='Test',
|
||||
user_id=1,
|
||||
generated_code='def next(self): pass',
|
||||
data_cache=None,
|
||||
indicators=None,
|
||||
trades=None,
|
||||
)
|
||||
|
||||
instance.update_prices({'BTC/USDT': 50000.0, 'ETH/USDT': 3000.0})
|
||||
|
||||
instance.trade_order(
|
||||
trade_type='buy',
|
||||
size=0.1,
|
||||
order_type='MARKET',
|
||||
source={'symbol': 'BTC/USDT'}
|
||||
)
|
||||
instance.trade_order(
|
||||
trade_type='buy',
|
||||
size=1.0,
|
||||
order_type='MARKET',
|
||||
source={'symbol': 'ETH/USDT'}
|
||||
)
|
||||
|
||||
assert instance.get_active_trades() == 2
|
||||
|
||||
results = instance.close_all_positions()
|
||||
assert len(results) == 2
|
||||
assert all(r.success for r in results)
|
||||
assert instance.get_active_trades() == 0
|
||||
|
||||
def test_reset(self):
|
||||
"""Test resetting paper trading state."""
|
||||
instance = PaperStrategyInstance(
|
||||
strategy_instance_id='test-1',
|
||||
strategy_id='strat-1',
|
||||
strategy_name='Test',
|
||||
user_id=1,
|
||||
generated_code='def next(self): pass',
|
||||
data_cache=None,
|
||||
indicators=None,
|
||||
trades=None,
|
||||
initial_balance=10000.0,
|
||||
)
|
||||
|
||||
instance.update_prices({'BTC/USDT': 50000.0})
|
||||
instance.trade_order(
|
||||
trade_type='buy',
|
||||
size=0.1,
|
||||
order_type='MARKET',
|
||||
source={'symbol': 'BTC/USDT'}
|
||||
)
|
||||
|
||||
assert instance.get_available_balance() < 10000.0
|
||||
|
||||
instance.reset()
|
||||
|
||||
assert instance.get_available_balance() == 10000.0
|
||||
assert instance.get_active_trades() == 0
|
||||
|
||||
def test_get_trade_history(self):
|
||||
"""Test getting trade history."""
|
||||
instance = PaperStrategyInstance(
|
||||
strategy_instance_id='test-1',
|
||||
strategy_id='strat-1',
|
||||
strategy_name='Test',
|
||||
user_id=1,
|
||||
generated_code='def next(self): pass',
|
||||
data_cache=None,
|
||||
indicators=None,
|
||||
trades=None,
|
||||
)
|
||||
|
||||
instance.update_prices({'BTC/USDT': 50000.0})
|
||||
instance.trade_order(
|
||||
trade_type='buy',
|
||||
size=0.1,
|
||||
order_type='MARKET',
|
||||
source={'symbol': 'BTC/USDT'}
|
||||
)
|
||||
|
||||
history = instance.get_trade_history()
|
||||
assert len(history) == 1
|
||||
assert history[0]['symbol'] == 'BTC/USDT'
|
||||
assert history[0]['side'] == 'buy'
|
||||
|
||||
|
||||
class TestStrategiesModeSeletion:
|
||||
"""Tests for strategy mode selection."""
|
||||
|
||||
def test_mode_selection_imports(self):
|
||||
"""Test that mode selection imports work."""
|
||||
from Strategies import Strategies
|
||||
from brokers import TradingMode
|
||||
|
||||
assert TradingMode.PAPER == 'paper'
|
||||
assert TradingMode.BACKTEST == 'backtest'
|
||||
assert TradingMode.LIVE == 'live'
|
||||
Loading…
Reference in New Issue