The test are running and returning feedback. I am just about to implement a better solution for multithreading

This commit is contained in:
Rob 2024-11-13 22:15:35 -04:00
parent 0ae835d096
commit 4f11778b09
3 changed files with 91 additions and 105 deletions

View File

@ -1201,7 +1201,7 @@ class PythonGenerator:
:param indent_level: Current indentation level.
:return: A string representing the variable retrieval.
"""
variable_name = node.get('variable_name')
variable_name = node.get('variable_name', '').strip()
if not variable_name:
logger.error("get_variable node missing 'variable_name'.")
return 'None'
@ -1217,7 +1217,7 @@ class PythonGenerator:
"""
code_lines = []
indent = ' ' * indent_level
variable_name = node.get('variable_name')
variable_name = node.get('variable_name', '').strip()
value_node = node.get('values')
if not variable_name:
@ -1391,9 +1391,9 @@ class PythonGenerator:
if operator not in operator_map:
logger.error(f"Invalid operator for math_operation: {operator}. Defaulting to 'ADD'.")
left_expr = self.process_numeric_list(left_operand, indent_level)
right_expr = self.process_numeric_list(right_operand, indent_level)
expr = f"{left_expr} {python_operator} {right_expr}"
left_expr = self.generate_condition_code(left_operand, indent_level)
right_expr = self.generate_condition_code(right_operand, indent_level)
expr = f"({left_expr} {python_operator} {right_expr})"
logger.debug(f"Generated math_operation expression: {expr}")
return expr

View File

@ -197,13 +197,60 @@ class Backtester:
for name in self.indicator_names:
self.indicator_pointers[name] = 0 # Start at the first row
# Initialize an empty list to store orders
self.orders = []
self.trade_list = []
# Initialize any other needed variables
self.starting_balance = self.broker.getvalue()
# Existing code...
self.current_step = 0
self.last_progress = 0 # Initialize last_progress
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
# Order has been submitted/accepted by broker - nothing to do
return
if order.status in [order.Completed]:
if order.isbuy():
self.log(f"BUY EXECUTED, Price: {order.executed.price}, Size: {order.executed.size}")
elif order.issell():
self.log(f"SELL EXECUTED, Price: {order.executed.price}, Size: {order.executed.size}")
self.bar_executed = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected')
# Remove the order from the list
if order in self.orders:
self.orders.remove(order)
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log(f"TRADE CLOSED, GROSS P/L: {trade.pnl}, NET P/L: {trade.pnlcomm}")
# Convert datetime objects to ISO-formatted strings
open_datetime = bt.num2date(trade.dtopen).isoformat() if trade.dtopen else None
close_datetime = bt.num2date(trade.dtclose).isoformat() if trade.dtclose else None
# Store the trade details for later use
trade_info = {
'ref': trade.ref,
'size': trade.size,
'price': trade.price,
'pnl': trade.pnl,
'pnlcomm': trade.pnlcomm,
'open_datetime': open_datetime,
'close_datetime': close_datetime
}
self.trade_list.append(trade_info)
def log(self, txt, dt=None):
""" Logging function for this strategy"""
dt = dt or self.datas[0].datetime.datetime(0)
logger.info(f"{dt.isoformat()} - {txt}")
def next(self):
self.current_step += 1
# Execute the strategy logic
@ -257,19 +304,6 @@ class Backtester:
"""
Custom trade_order method for backtesting.
Executes trades within the Backtrader environment.
:param trade_type: Type of trade ('buy' or 'sell').
:param size: Size of the trade.
:param order_type: Type of order (e.g., 'market').
:param source: Dictionary containing additional trade information, including 'market'.
:param tif: Time in Force for the order.
:param stop_loss: Dictionary with stop loss parameters.
:param trailing_stop: Dictionary with trailing stop parameters.
:param take_profit: Dictionary with take profit parameters.
:param limit: Dictionary with limit order parameters.
:param trailing_limit: Dictionary with trailing limit parameters.
:param target_market: Dictionary with target market parameters.
:param name_order: Dictionary with order name parameters.
"""
# Validate and extract 'symbol' from 'source'
if source and 'market' in source:
@ -279,42 +313,44 @@ class Backtester:
logger.error("Symbol not provided in source. Order not executed.")
return # Abort the order execution
price = strategy_instance.backtrader_strategy.data.close[0]
if trade_type.lower() == 'buy':
logger.info(f"Executing BUY order: Size={size}, Symbol={symbol}, Order Type={order_type}")
# Execute a buy order in Backtrader via Cerebro
order = strategy_instance.backtrader_strategy.buy(size=size, exectype=bt.Order.Market, name=symbol)
# Prepare bracket order parameters
stop_loss_price = stop_loss.get('value') if stop_loss else None
take_profit_price = take_profit.get('value') if take_profit else None
# Create bracket order and store the orders
bracket_orders = strategy_instance.backtrader_strategy.buy_bracket(
size=size,
price=price,
stopprice=stop_loss_price,
limitprice=take_profit_price,
exectype=bt.Order.Market
)
elif trade_type.lower() == 'sell':
logger.info(f"Executing SELL order: Size={size}, Symbol={symbol}, Order Type={order_type}")
# Execute a sell order in Backtrader via Cerebro
order = strategy_instance.backtrader_strategy.sell(size=size, exectype=bt.Order.Market, name=symbol)
# Prepare bracket order parameters
stop_loss_price = stop_loss.get('value') if stop_loss else None
take_profit_price = take_profit.get('value') if take_profit else None
# Create bracket order and store the orders
bracket_orders = strategy_instance.backtrader_strategy.sell_bracket(
size=size,
price=price,
stopprice=stop_loss_price,
limitprice=take_profit_price,
exectype=bt.Order.Market
)
else:
logger.error(f"Invalid trade_type '{trade_type}'. Order not executed.")
return # Abort the order execution
# Handle trade options like stop_loss and take_profit
if stop_loss or take_profit:
if stop_loss:
stop_price = stop_loss.get('value')
if stop_price is not None:
logger.info(f"Setting STOP LOSS at {stop_price} for order {order.ref}")
strategy_instance.backtrader_strategy.sell(
size=size,
exectype=bt.Order.Stop,
price=stop_price,
parent=order,
name=f"StopLoss_{order.ref}"
)
if take_profit:
take_profit_price = take_profit.get('value')
if take_profit_price is not None:
logger.info(f"Setting TAKE PROFIT at {take_profit_price} for order {order.ref}")
strategy_instance.backtrader_strategy.sell(
size=size,
exectype=bt.Order.Limit,
price=take_profit_price,
parent=order,
name=f"TakeProfit_{order.ref}"
)
# Store the orders for tracking
strategy_instance.backtrader_strategy.orders.extend(bracket_orders)
# Notify user about the trade execution
strategy_instance.notify_user(
@ -706,12 +742,12 @@ class Backtester:
final_value = cerebro.broker.getvalue()
run_duration = (end_time - start_time).total_seconds()
# Extract equity curve from analyzers
equity_curve = results[0].analyzers.equity_curve.get_analysis().get('equity_curve', [])
strategy = results[0]
# Extract trade data from TradeAnalyzer
trade_analyzer = results[0].analyzers.trade_analyzer.get_analysis()
trades = self.parse_trade_analyzer(trade_analyzer)
# Extract equity curve from analyzers
equity_curve = strategy.analyzers.equity_curve.get_analysis().get('equity_curve', [])
trades = strategy.trade_list
# Send 100% completion
self.socketio.emit('message', {'reply': 'progress', 'data': {'progress': 100}}, room=socket_conn_id)
@ -1052,54 +1088,4 @@ class Backtester:
# Kept here for backward compatibility or future use
return []
def parse_trade_analyzer(self, trade_analyzer: dict) -> list:
"""
Parse the TradeAnalyzer results from Backtrader and return a list of trades.
:param trade_analyzer: Dictionary containing trade analysis.
:return: List of trade dictionaries with relevant information.
"""
trades = []
if not trade_analyzer:
logger.warning("No trade data available in TradeAnalyzer.")
return trades
# TradeAnalyzer stores trades under 'trades'
trade_list = trade_analyzer.get('trades', {})
# Check if 'trades' is a dict (with trade references) or a list
if isinstance(trade_list, dict):
for ref, trade in trade_list.items():
trade_info = {
'ref': ref,
'size': trade.get('size'),
'price': trade.get('price'),
'value': trade.get('value'),
'pnl': trade.get('pnl'),
'commission': trade.get('commission'),
'opendate': trade.get('opendate'),
'closedate': trade.get('closedate'),
'status': trade.get('status'),
}
trades.append(trade_info)
logger.debug(f"Parsed trade: {trade_info}")
elif isinstance(trade_list, list):
for trade in trade_list:
trade_info = {
'ref': trade.get('ref'),
'size': trade.get('size'),
'price': trade.get('price'),
'value': trade.get('value'),
'pnl': trade.get('pnl'),
'commission': trade.get('commission'),
'opendate': trade.get('opendate'),
'closedate': trade.get('closedate'),
'status': trade.get('status'),
}
trades.append(trade_info)
logger.debug(f"Parsed trade: {trade_info}")
else:
logger.error("Unexpected format for 'trades' in TradeAnalyzer.")
logger.info(f"Parsed {len(trades)} trades from TradeAnalyzer.")
return trades

View File

@ -165,13 +165,13 @@ export function defineVAFGenerators() {
// Retrieve the 'variable_name' field input
const variableName = currentBlock.getFieldValue('variable_name');
const trimmedName = variableName && variableName.trim() !== "" ? variableName.trim() : 'undefined_var';
if (variableName.trim() === "") {
if (variableName === "") {
console.warn("Empty variable_name in get_variable block. Defaulting to 'undefined_var'.");
}
variables.push({
type: 'get_variable',
variable_name: variableName
variable_name: trimmedName
});
// Process the 'NEXT' connection