Compare commits

..

2 Commits

5 changed files with 176 additions and 224 deletions

View File

@ -3,7 +3,6 @@
"stop_date": "2025-03-15", "stop_date": "2025-03-15",
"initial_usd": 10000, "initial_usd": 10000,
"timeframes": ["1min"], "timeframes": ["1min"],
"stop_loss_pcts": [0.05],
"strategies": [ "strategies": [
{ {
"name": "bbrs", "name": "bbrs",
@ -17,7 +16,8 @@
"sideways_rsi_threshold": [40, 60], "sideways_rsi_threshold": [40, 60],
"sideways_bb_multiplier": 1.8, "sideways_bb_multiplier": 1.8,
"strategy_name": "MarketRegimeStrategy", "strategy_name": "MarketRegimeStrategy",
"SqueezeStrategy": true "SqueezeStrategy": true,
"stop_loss_pct": 0.05
} }
} }
], ],

View File

@ -3,12 +3,14 @@
"stop_date": "2025-03-15", "stop_date": "2025-03-15",
"initial_usd": 10000, "initial_usd": 10000,
"timeframes": ["15min"], "timeframes": ["15min"],
"stop_loss_pcts": [0.04],
"strategies": [ "strategies": [
{ {
"name": "default", "name": "default",
"weight": 0.6, "weight": 0.6,
"params": {} "params": {
"timeframe": "15min",
"stop_loss_pct": 0.03
}
}, },
{ {
"name": "bbrs", "name": "bbrs",
@ -17,7 +19,13 @@
"bb_width": 0.05, "bb_width": 0.05,
"bb_period": 20, "bb_period": 20,
"rsi_period": 14, "rsi_period": 14,
"strategy_name": "MarketRegimeStrategy" "trending_rsi_threshold": [30, 70],
"trending_bb_multiplier": 2.5,
"sideways_rsi_threshold": [40, 60],
"sideways_bb_multiplier": 1.8,
"strategy_name": "MarketRegimeStrategy",
"SqueezeStrategy": true,
"stop_loss_pct": 0.05
} }
} }
], ],

View File

@ -3,12 +3,14 @@
"stop_date": "2025-03-15", "stop_date": "2025-03-15",
"initial_usd": 10000, "initial_usd": 10000,
"timeframes": ["15min"], "timeframes": ["15min"],
"stop_loss_pcts": [0.03, 0.05],
"strategies": [ "strategies": [
{ {
"name": "default", "name": "default",
"weight": 1.0, "weight": 1.0,
"params": {} "params": {
"timeframe": "15min",
"stop_loss_pct": 0.03
}
} }
], ],
"combination_rules": { "combination_rules": {

View File

@ -3,13 +3,13 @@
"stop_date": "2024-01-31", "stop_date": "2024-01-31",
"initial_usd": 10000, "initial_usd": 10000,
"timeframes": ["5min"], "timeframes": ["5min"],
"stop_loss_pcts": [0.03, 0.05],
"strategies": [ "strategies": [
{ {
"name": "default", "name": "default",
"weight": 1.0, "weight": 1.0,
"params": { "params": {
"timeframe": "5min" "timeframe": "5min",
"stop_loss_pct": 0.03
} }
} }
], ],

140
main.py
View File

@ -38,39 +38,25 @@ def strategy_manager_exit(backtester: Backtest, df_index: int):
"""Strategy Manager exit function""" """Strategy Manager exit function"""
return backtester.strategy_manager.get_exit_signal(backtester, df_index) return backtester.strategy_manager.get_exit_signal(backtester, df_index)
def process_timeframe_data(min1_df, df, stop_loss_pcts, rule_name, initial_usd, strategy_config=None, debug=False): def process_timeframe_data(data_1min, timeframe, config, debug=False):
"""Process the entire timeframe with all stop loss values using Strategy Manager""" """Process a timeframe using Strategy Manager with configuration"""
results_rows = [] results_rows = []
trade_rows = [] trade_rows = []
for stop_loss_pct in stop_loss_pcts: # Extract values from config
# Create and initialize strategy manager initial_usd = config['initial_usd']
if strategy_config: strategy_config = {
# Use provided strategy configuration "strategies": config['strategies'],
strategy_manager = create_strategy_manager(strategy_config) "combination_rules": config['combination_rules']
else:
# Default to single default strategy for backward compatibility
default_strategy_config = {
"strategies": [
{
"name": "default",
"weight": 1.0,
"params": {"stop_loss_pct": stop_loss_pct}
} }
],
"combination_rules": {
"entry": "any",
"exit": "any",
"min_confidence": 0.5
}
}
strategy_manager = create_strategy_manager(default_strategy_config)
# Inject stop_loss_pct into all strategy params if not present # Create and initialize strategy manager
for strategy in strategy_manager.strategies: if not strategy_config:
if "stop_loss_pct" not in strategy.params: logging.error("No strategy configuration provided")
strategy.params["stop_loss_pct"] = stop_loss_pct return results_rows, trade_rows
strategy_manager = create_strategy_manager(strategy_config)
# Get the primary timeframe from the first strategy for backtester setup # Get the primary timeframe from the first strategy for backtester setup
primary_strategy = strategy_manager.strategies[0] primary_strategy = strategy_manager.strategies[0]
@ -81,15 +67,11 @@ def process_timeframe_data(min1_df, df, stop_loss_pcts, rule_name, initial_usd,
if primary_strategy.name == "bbrs": if primary_strategy.name == "bbrs":
# BBRS strategy processes 1-minute data and outputs signals on its internal timeframes # BBRS strategy processes 1-minute data and outputs signals on its internal timeframes
# Use 1-minute data for backtester working dataframe # Use 1-minute data for backtester working dataframe
working_df = min1_df.copy() working_df = data_1min.copy()
else: else:
# Other strategies specify their preferred timeframe # Other strategies specify their preferred timeframe
# Create backtester working data from the primary strategy's primary timeframe
temp_backtester = type('temp', (), {})()
temp_backtester.original_df = min1_df
# Let the primary strategy resample the data to get the working dataframe # Let the primary strategy resample the data to get the working dataframe
primary_strategy._resample_data(min1_df) primary_strategy._resample_data(data_1min)
working_df = primary_strategy.get_primary_timeframe_data() working_df = primary_strategy.get_primary_timeframe_data()
# Prepare working dataframe for backtester (ensure timestamp column) # Prepare working dataframe for backtester (ensure timestamp column)
@ -101,7 +83,7 @@ def process_timeframe_data(min1_df, df, stop_loss_pcts, rule_name, initial_usd,
backtester = Backtest(initial_usd, working_df_for_backtest, working_df_for_backtest, strategy_manager_init) backtester = Backtest(initial_usd, working_df_for_backtest, working_df_for_backtest, strategy_manager_init)
# Store original min1_df for strategy processing # Store original min1_df for strategy processing
backtester.original_df = min1_df backtester.original_df = data_1min
# Attach strategy manager to backtester and initialize # Attach strategy manager to backtester and initialize
backtester.strategy_manager = strategy_manager backtester.strategy_manager = strategy_manager
@ -144,9 +126,13 @@ def process_timeframe_data(min1_df, df, stop_loss_pcts, rule_name, initial_usd,
total_fees_usd = sum(trade.get('fee_usd', 0.0) for trade in trades) total_fees_usd = sum(trade.get('fee_usd', 0.0) for trade in trades)
# Get stop_loss_pct from the first strategy for reporting
# In multi-strategy setups, strategies can have different stop_loss_pct values
stop_loss_pct = primary_strategy.params.get("stop_loss_pct", "N/A")
# Update row to include timeframe information # Update row to include timeframe information
row = { row = {
"timeframe": f"{rule_name}({primary_timeframe})", # Show actual timeframe used "timeframe": f"{timeframe}({primary_timeframe})", # Show actual timeframe used
"stop_loss_pct": stop_loss_pct, "stop_loss_pct": stop_loss_pct,
"n_trades": n_trades, "n_trades": n_trades,
"n_stop_loss": sum(1 for trade in trades if 'type' in trade and trade['type'] == 'STOP_LOSS'), "n_stop_loss": sum(1 for trade in trades if 'type' in trade and trade['type'] == 'STOP_LOSS'),
@ -164,7 +150,7 @@ def process_timeframe_data(min1_df, df, stop_loss_pcts, rule_name, initial_usd,
for trade in trades: for trade in trades:
trade_rows.append({ trade_rows.append({
"timeframe": f"{rule_name}({primary_timeframe})", "timeframe": f"{timeframe}({primary_timeframe})",
"stop_loss_pct": stop_loss_pct, "stop_loss_pct": stop_loss_pct,
"entry_time": trade.get("entry_time"), "entry_time": trade.get("entry_time"),
"exit_time": trade.get("exit_time"), "exit_time": trade.get("exit_time"),
@ -177,7 +163,7 @@ def process_timeframe_data(min1_df, df, stop_loss_pcts, rule_name, initial_usd,
# Log strategy summary # Log strategy summary
strategy_summary = strategy_manager.get_strategy_summary() strategy_summary = strategy_manager.get_strategy_summary()
logging.info(f"Timeframe: {rule_name}({primary_timeframe}), Stop Loss: {stop_loss_pct}, " logging.info(f"Timeframe: {timeframe}({primary_timeframe}), Stop Loss: {stop_loss_pct}, "
f"Trades: {n_trades}, Strategies: {[s['name'] for s in strategy_summary['strategies']]}") f"Trades: {n_trades}, Strategies: {[s['name'] for s in strategy_summary['strategies']]}")
if debug: if debug:
@ -209,12 +195,12 @@ def process_timeframe_data(min1_df, df, stop_loss_pcts, rule_name, initial_usd,
return results_rows, trade_rows return results_rows, trade_rows
def process(timeframe_info, debug=False): def process(timeframe_info, debug=False):
"""Process a single (timeframe, stop_loss_pct) combination with strategy config""" """Process a single timeframe with strategy config"""
rule, data_1min, stop_loss_pct, initial_usd, strategy_config = timeframe_info timeframe, data_1min, config = timeframe_info
# Pass the original 1-minute data - strategies will handle their own timeframe resampling # Pass the essential data and full config
results_rows, all_trade_rows = process_timeframe_data( results_rows, all_trade_rows = process_timeframe_data(
data_1min, data_1min, [stop_loss_pct], rule, initial_usd, strategy_config, debug=debug data_1min, timeframe, config, debug=debug
) )
return results_rows, all_trade_rows return results_rows, all_trade_rows
@ -272,68 +258,25 @@ if __name__ == "__main__":
parser.add_argument("config", type=str, nargs="?", help="Path to config JSON file.") parser.add_argument("config", type=str, nargs="?", help="Path to config JSON file.")
args = parser.parse_args() args = parser.parse_args()
# Default values (from config.json) # Use config_default.json as fallback if no config provided
default_config = { config_file = args.config or "configs/config_default.json"
"start_date": "2025-03-01",
"stop_date": datetime.datetime.today().strftime('%Y-%m-%d'),
"initial_usd": 10000,
"timeframes": ["15min"],
"stop_loss_pcts": [0.03],
"strategies": [
{
"name": "default",
"weight": 1.0,
"params": {}
}
],
"combination_rules": {
"entry": "any",
"exit": "any",
"min_confidence": 0.5
}
}
if args.config: try:
with open(args.config, 'r') as f: with open(config_file, 'r') as f:
config = json.load(f) config = json.load(f)
elif not debug: print(f"Using config: {config_file}")
print("No config file provided. Please enter the following values (press Enter to use default):") except FileNotFoundError:
print(f"Error: Config file '{config_file}' not found.")
start_date = input(f"Start date [{default_config['start_date']}]: ") or default_config['start_date'] print("Available configs: configs/config_default.json, configs/config_bbrs.json, configs/config_combined.json")
stop_date = input(f"Stop date [{default_config['stop_date']}]: ") or default_config['stop_date'] exit(1)
except json.JSONDecodeError as e:
initial_usd_str = input(f"Initial USD [{default_config['initial_usd']}]: ") or str(default_config['initial_usd']) print(f"Error: Invalid JSON in config file '{config_file}': {e}")
initial_usd = float(initial_usd_str) exit(1)
timeframes_str = input(f"Timeframes (comma separated) [{', '.join(default_config['timeframes'])}]: ") or ','.join(default_config['timeframes'])
timeframes = [tf.strip() for tf in timeframes_str.split(',') if tf.strip()]
stop_loss_pcts_str = input(f"Stop loss pcts (comma separated) [{', '.join(str(x) for x in default_config['stop_loss_pcts'])}]: ") or ','.join(str(x) for x in default_config['stop_loss_pcts'])
stop_loss_pcts = [float(x.strip()) for x in stop_loss_pcts_str.split(',') if x.strip()]
config = {
'start_date': start_date,
'stop_date': stop_date,
'initial_usd': initial_usd,
'timeframes': timeframes,
'stop_loss_pcts': stop_loss_pcts,
'strategies': default_config['strategies'],
'combination_rules': default_config['combination_rules']
}
else:
config = default_config
start_date = config['start_date'] start_date = config['start_date']
stop_date = config['stop_date'] stop_date = config['stop_date']
initial_usd = config['initial_usd'] initial_usd = config['initial_usd']
timeframes = config['timeframes'] timeframes = config['timeframes']
stop_loss_pcts = config['stop_loss_pcts']
# Extract strategy configuration
strategy_config = {
"strategies": config.get('strategies', default_config['strategies']),
"combination_rules": config.get('combination_rules', default_config['combination_rules'])
}
timestamp = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M") timestamp = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M")
@ -351,11 +294,10 @@ if __name__ == "__main__":
f"Initial USD\t{initial_usd}" f"Initial USD\t{initial_usd}"
] ]
# Create tasks for each (timeframe, stop_loss_pct) combination # Create tasks for each timeframe
tasks = [ tasks = [
(name, data_1min, stop_loss_pct, initial_usd, strategy_config) (name, data_1min, config)
for name in timeframes for name in timeframes
for stop_loss_pct in stop_loss_pcts
] ]
if debug: if debug: