The test are running and returning feedback. I am just about to implement a better solution for multithreading
This commit is contained in:
parent
0ae835d096
commit
4f11778b09
|
|
@ -1201,7 +1201,7 @@ class PythonGenerator:
|
||||||
:param indent_level: Current indentation level.
|
:param indent_level: Current indentation level.
|
||||||
:return: A string representing the variable retrieval.
|
:return: A string representing the variable retrieval.
|
||||||
"""
|
"""
|
||||||
variable_name = node.get('variable_name')
|
variable_name = node.get('variable_name', '').strip()
|
||||||
if not variable_name:
|
if not variable_name:
|
||||||
logger.error("get_variable node missing 'variable_name'.")
|
logger.error("get_variable node missing 'variable_name'.")
|
||||||
return 'None'
|
return 'None'
|
||||||
|
|
@ -1217,7 +1217,7 @@ class PythonGenerator:
|
||||||
"""
|
"""
|
||||||
code_lines = []
|
code_lines = []
|
||||||
indent = ' ' * indent_level
|
indent = ' ' * indent_level
|
||||||
variable_name = node.get('variable_name')
|
variable_name = node.get('variable_name', '').strip()
|
||||||
value_node = node.get('values')
|
value_node = node.get('values')
|
||||||
|
|
||||||
if not variable_name:
|
if not variable_name:
|
||||||
|
|
@ -1391,9 +1391,9 @@ class PythonGenerator:
|
||||||
if operator not in operator_map:
|
if operator not in operator_map:
|
||||||
logger.error(f"Invalid operator for math_operation: {operator}. Defaulting to 'ADD'.")
|
logger.error(f"Invalid operator for math_operation: {operator}. Defaulting to 'ADD'.")
|
||||||
|
|
||||||
left_expr = self.process_numeric_list(left_operand, indent_level)
|
left_expr = self.generate_condition_code(left_operand, indent_level)
|
||||||
right_expr = self.process_numeric_list(right_operand, indent_level)
|
right_expr = self.generate_condition_code(right_operand, indent_level)
|
||||||
expr = f"{left_expr} {python_operator} {right_expr}"
|
expr = f"({left_expr} {python_operator} {right_expr})"
|
||||||
logger.debug(f"Generated math_operation expression: {expr}")
|
logger.debug(f"Generated math_operation expression: {expr}")
|
||||||
return expr
|
return expr
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -197,13 +197,60 @@ class Backtester:
|
||||||
for name in self.indicator_names:
|
for name in self.indicator_names:
|
||||||
self.indicator_pointers[name] = 0 # Start at the first row
|
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
|
# Initialize any other needed variables
|
||||||
self.starting_balance = self.broker.getvalue()
|
self.starting_balance = self.broker.getvalue()
|
||||||
|
|
||||||
# Existing code...
|
|
||||||
self.current_step = 0
|
self.current_step = 0
|
||||||
self.last_progress = 0 # Initialize last_progress
|
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):
|
def next(self):
|
||||||
self.current_step += 1
|
self.current_step += 1
|
||||||
# Execute the strategy logic
|
# Execute the strategy logic
|
||||||
|
|
@ -257,19 +304,6 @@ class Backtester:
|
||||||
"""
|
"""
|
||||||
Custom trade_order method for backtesting.
|
Custom trade_order method for backtesting.
|
||||||
Executes trades within the Backtrader environment.
|
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'
|
# Validate and extract 'symbol' from 'source'
|
||||||
if source and 'market' in source:
|
if source and 'market' in source:
|
||||||
|
|
@ -279,42 +313,44 @@ class Backtester:
|
||||||
logger.error("Symbol not provided in source. Order not executed.")
|
logger.error("Symbol not provided in source. Order not executed.")
|
||||||
return # Abort the order execution
|
return # Abort the order execution
|
||||||
|
|
||||||
|
price = strategy_instance.backtrader_strategy.data.close[0]
|
||||||
|
|
||||||
if trade_type.lower() == 'buy':
|
if trade_type.lower() == 'buy':
|
||||||
logger.info(f"Executing BUY order: Size={size}, Symbol={symbol}, Order Type={order_type}")
|
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':
|
elif trade_type.lower() == 'sell':
|
||||||
logger.info(f"Executing SELL order: Size={size}, Symbol={symbol}, Order Type={order_type}")
|
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:
|
else:
|
||||||
logger.error(f"Invalid trade_type '{trade_type}'. Order not executed.")
|
logger.error(f"Invalid trade_type '{trade_type}'. Order not executed.")
|
||||||
return # Abort the order execution
|
return # Abort the order execution
|
||||||
|
|
||||||
# Handle trade options like stop_loss and take_profit
|
# Store the orders for tracking
|
||||||
if stop_loss or take_profit:
|
strategy_instance.backtrader_strategy.orders.extend(bracket_orders)
|
||||||
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}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Notify user about the trade execution
|
# Notify user about the trade execution
|
||||||
strategy_instance.notify_user(
|
strategy_instance.notify_user(
|
||||||
|
|
@ -706,12 +742,12 @@ class Backtester:
|
||||||
final_value = cerebro.broker.getvalue()
|
final_value = cerebro.broker.getvalue()
|
||||||
run_duration = (end_time - start_time).total_seconds()
|
run_duration = (end_time - start_time).total_seconds()
|
||||||
|
|
||||||
# Extract equity curve from analyzers
|
strategy = results[0]
|
||||||
equity_curve = results[0].analyzers.equity_curve.get_analysis().get('equity_curve', [])
|
|
||||||
|
|
||||||
# Extract trade data from TradeAnalyzer
|
# Extract equity curve from analyzers
|
||||||
trade_analyzer = results[0].analyzers.trade_analyzer.get_analysis()
|
equity_curve = strategy.analyzers.equity_curve.get_analysis().get('equity_curve', [])
|
||||||
trades = self.parse_trade_analyzer(trade_analyzer)
|
|
||||||
|
trades = strategy.trade_list
|
||||||
|
|
||||||
# Send 100% completion
|
# Send 100% completion
|
||||||
self.socketio.emit('message', {'reply': 'progress', 'data': {'progress': 100}}, room=socket_conn_id)
|
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
|
# Kept here for backward compatibility or future use
|
||||||
return []
|
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
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -165,13 +165,13 @@ export function defineVAFGenerators() {
|
||||||
// Retrieve the 'variable_name' field input
|
// Retrieve the 'variable_name' field input
|
||||||
const variableName = currentBlock.getFieldValue('variable_name');
|
const variableName = currentBlock.getFieldValue('variable_name');
|
||||||
const trimmedName = variableName && variableName.trim() !== "" ? variableName.trim() : 'undefined_var';
|
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'.");
|
console.warn("Empty variable_name in get_variable block. Defaulting to 'undefined_var'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
variables.push({
|
variables.push({
|
||||||
type: 'get_variable',
|
type: 'get_variable',
|
||||||
variable_name: variableName
|
variable_name: trimmedName
|
||||||
});
|
});
|
||||||
|
|
||||||
// Process the 'NEXT' connection
|
// Process the 'NEXT' connection
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue