2025-05-20 18:28:53 +08:00
|
|
|
import logging
|
|
|
|
|
import seaborn as sns
|
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
import pandas as pd
|
2025-05-23 15:22:03 +08:00
|
|
|
import datetime
|
2025-05-20 18:28:53 +08:00
|
|
|
|
|
|
|
|
from cycles.utils.storage import Storage
|
2025-05-23 12:47:59 +00:00
|
|
|
from cycles.Analysis.strategies import Strategy
|
2025-05-20 18:28:53 +08:00
|
|
|
|
|
|
|
|
logging.basicConfig(
|
|
|
|
|
level=logging.INFO,
|
|
|
|
|
format="%(asctime)s [%(levelname)s] %(message)s",
|
|
|
|
|
handlers=[
|
|
|
|
|
logging.FileHandler("backtest.log"),
|
|
|
|
|
logging.StreamHandler()
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
|
2025-05-22 17:57:04 +08:00
|
|
|
config = {
|
2025-05-23 15:22:03 +08:00
|
|
|
"start_date": "2025-03-01",
|
|
|
|
|
"stop_date": datetime.datetime.today().strftime('%Y-%m-%d'),
|
2025-05-20 18:28:53 +08:00
|
|
|
"data_file": "btcusd_1-min_data.csv"
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-22 16:44:59 +08:00
|
|
|
config_strategy = {
|
|
|
|
|
"bb_width": 0.05,
|
|
|
|
|
"bb_period": 20,
|
|
|
|
|
"rsi_period": 14,
|
|
|
|
|
"trending": {
|
|
|
|
|
"rsi_threshold": [30, 70],
|
|
|
|
|
"bb_std_dev_multiplier": 2.5,
|
|
|
|
|
},
|
|
|
|
|
"sideways": {
|
|
|
|
|
"rsi_threshold": [40, 60],
|
|
|
|
|
"bb_std_dev_multiplier": 1.8,
|
|
|
|
|
},
|
2025-05-22 18:16:23 +08:00
|
|
|
"strategy_name": "MarketRegimeStrategy", # CryptoTradingStrategy
|
2025-05-22 16:44:59 +08:00
|
|
|
"SqueezeStrategy": True
|
|
|
|
|
}
|
2025-05-20 18:28:53 +08:00
|
|
|
|
2025-05-22 16:44:59 +08:00
|
|
|
IS_DAY = False
|
2025-05-20 18:28:53 +08:00
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
2025-05-22 17:57:04 +08:00
|
|
|
# Load data
|
2025-05-20 18:28:53 +08:00
|
|
|
storage = Storage(logging=logging)
|
|
|
|
|
data = storage.load_data(config["data_file"], config["start_date"], config["stop_date"])
|
|
|
|
|
|
2025-05-22 17:57:04 +08:00
|
|
|
# Run strategy
|
2025-05-23 12:47:59 +00:00
|
|
|
strategy = Strategy(config=config_strategy, logging=logging)
|
2025-05-22 17:15:51 +08:00
|
|
|
processed_data = strategy.run(data.copy(), config_strategy["strategy_name"])
|
2025-05-20 18:28:53 +08:00
|
|
|
|
2025-05-22 17:57:04 +08:00
|
|
|
# Get buy and sell signals
|
2025-05-22 17:15:51 +08:00
|
|
|
buy_condition = processed_data.get('BuySignal', pd.Series(False, index=processed_data.index)).astype(bool)
|
|
|
|
|
sell_condition = processed_data.get('SellSignal', pd.Series(False, index=processed_data.index)).astype(bool)
|
2025-05-20 18:28:53 +08:00
|
|
|
|
2025-05-22 17:15:51 +08:00
|
|
|
buy_signals = processed_data[buy_condition]
|
|
|
|
|
sell_signals = processed_data[sell_condition]
|
2025-05-20 18:28:53 +08:00
|
|
|
|
2025-05-22 17:57:04 +08:00
|
|
|
# Plot the data with seaborn library
|
2025-05-22 17:15:51 +08:00
|
|
|
if processed_data is not None and not processed_data.empty:
|
2025-05-20 18:28:53 +08:00
|
|
|
# Create a figure with two subplots, sharing the x-axis
|
2025-05-22 16:44:59 +08:00
|
|
|
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(16, 8), sharex=True)
|
2025-05-20 18:28:53 +08:00
|
|
|
|
2025-05-22 17:57:04 +08:00
|
|
|
strategy_name = config_strategy["strategy_name"]
|
|
|
|
|
|
|
|
|
|
# Plot 1: Close Price and Strategy-Specific Bands/Levels
|
2025-05-22 17:15:51 +08:00
|
|
|
sns.lineplot(x=processed_data.index, y='close', data=processed_data, label='Close Price', ax=ax1)
|
2025-05-22 18:16:23 +08:00
|
|
|
|
|
|
|
|
# Use standardized column names for bands
|
|
|
|
|
if 'UpperBand' in processed_data.columns and 'LowerBand' in processed_data.columns:
|
|
|
|
|
# Instead of lines, shade the area between upper and lower bands
|
|
|
|
|
ax1.fill_between(processed_data.index,
|
|
|
|
|
processed_data['LowerBand'],
|
|
|
|
|
processed_data['UpperBand'],
|
|
|
|
|
alpha=0.1, color='blue', label='Bollinger Bands')
|
|
|
|
|
else:
|
|
|
|
|
logging.warning(f"{strategy_name}: UpperBand or LowerBand not found for plotting.")
|
|
|
|
|
|
|
|
|
|
# Add strategy-specific extra indicators if available
|
|
|
|
|
if strategy_name == "CryptoTradingStrategy":
|
2025-05-22 17:57:04 +08:00
|
|
|
if 'StopLoss' in processed_data.columns:
|
|
|
|
|
sns.lineplot(x=processed_data.index, y='StopLoss', data=processed_data, label='Stop Loss', ax=ax1, linestyle='--', color='orange')
|
|
|
|
|
if 'TakeProfit' in processed_data.columns:
|
|
|
|
|
sns.lineplot(x=processed_data.index, y='TakeProfit', data=processed_data, label='Take Profit', ax=ax1, linestyle='--', color='purple')
|
|
|
|
|
|
2025-05-20 18:28:53 +08:00
|
|
|
# Plot Buy/Sell signals on Price chart
|
|
|
|
|
if not buy_signals.empty:
|
2025-05-22 18:16:23 +08:00
|
|
|
ax1.scatter(buy_signals.index, buy_signals['close'], color='green', marker='o', s=20, label='Buy Signal', zorder=5)
|
2025-05-20 18:28:53 +08:00
|
|
|
if not sell_signals.empty:
|
2025-05-22 18:16:23 +08:00
|
|
|
ax1.scatter(sell_signals.index, sell_signals['close'], color='red', marker='o', s=20, label='Sell Signal', zorder=5)
|
2025-05-22 17:57:04 +08:00
|
|
|
ax1.set_title(f'Price and Signals ({strategy_name})')
|
2025-05-20 18:28:53 +08:00
|
|
|
ax1.set_ylabel('Price')
|
|
|
|
|
ax1.legend()
|
|
|
|
|
ax1.grid(True)
|
|
|
|
|
|
2025-05-22 17:57:04 +08:00
|
|
|
# Plot 2: RSI and Strategy-Specific Thresholds
|
2025-05-22 18:16:23 +08:00
|
|
|
if 'RSI' in processed_data.columns:
|
|
|
|
|
sns.lineplot(x=processed_data.index, y='RSI', data=processed_data, label=f'RSI (' + str(config_strategy.get("rsi_period", 14)) + ')', ax=ax2, color='purple')
|
2025-05-22 17:57:04 +08:00
|
|
|
if strategy_name == "MarketRegimeStrategy":
|
2025-05-22 18:16:23 +08:00
|
|
|
# Get threshold values
|
|
|
|
|
upper_threshold = config_strategy.get("trending", {}).get("rsi_threshold", [30,70])[1]
|
|
|
|
|
lower_threshold = config_strategy.get("trending", {}).get("rsi_threshold", [30,70])[0]
|
|
|
|
|
|
|
|
|
|
# Shade overbought area (upper)
|
|
|
|
|
ax2.fill_between(processed_data.index, upper_threshold, 100,
|
|
|
|
|
alpha=0.1, color='red', label=f'Overbought (>{upper_threshold})')
|
|
|
|
|
|
|
|
|
|
# Shade oversold area (lower)
|
|
|
|
|
ax2.fill_between(processed_data.index, 0, lower_threshold,
|
|
|
|
|
alpha=0.1, color='green', label=f'Oversold (<{lower_threshold})')
|
|
|
|
|
|
2025-05-22 17:57:04 +08:00
|
|
|
elif strategy_name == "CryptoTradingStrategy":
|
2025-05-22 18:16:23 +08:00
|
|
|
# Shade overbought area (upper)
|
|
|
|
|
ax2.fill_between(processed_data.index, 65, 100,
|
|
|
|
|
alpha=0.1, color='red', label='Overbought (>65)')
|
|
|
|
|
|
|
|
|
|
# Shade oversold area (lower)
|
|
|
|
|
ax2.fill_between(processed_data.index, 0, 35,
|
|
|
|
|
alpha=0.1, color='green', label='Oversold (<35)')
|
2025-05-22 17:57:04 +08:00
|
|
|
|
2025-05-20 18:28:53 +08:00
|
|
|
# Plot Buy/Sell signals on RSI chart
|
2025-05-22 18:16:23 +08:00
|
|
|
if not buy_signals.empty and 'RSI' in buy_signals.columns:
|
|
|
|
|
ax2.scatter(buy_signals.index, buy_signals['RSI'], color='green', marker='o', s=20, label='Buy Signal (RSI)', zorder=5)
|
|
|
|
|
if not sell_signals.empty and 'RSI' in sell_signals.columns:
|
|
|
|
|
ax2.scatter(sell_signals.index, sell_signals['RSI'], color='red', marker='o', s=20, label='Sell Signal (RSI)', zorder=5)
|
|
|
|
|
ax2.set_title('Relative Strength Index (RSI) with Signals')
|
|
|
|
|
ax2.set_ylabel('RSI Value')
|
2025-05-22 17:57:04 +08:00
|
|
|
ax2.set_ylim(0, 100)
|
2025-05-20 18:28:53 +08:00
|
|
|
ax2.legend()
|
|
|
|
|
ax2.grid(True)
|
|
|
|
|
else:
|
2025-05-22 18:16:23 +08:00
|
|
|
logging.info("RSI data not available for plotting.")
|
2025-05-22 17:57:04 +08:00
|
|
|
|
|
|
|
|
# Plot 3: Strategy-Specific Indicators
|
|
|
|
|
ax3.clear() # Clear previous plot content if any
|
2025-05-22 18:16:23 +08:00
|
|
|
if 'BBWidth' in processed_data.columns:
|
|
|
|
|
sns.lineplot(x=processed_data.index, y='BBWidth', data=processed_data, label='BB Width', ax=ax3)
|
|
|
|
|
|
2025-05-22 17:57:04 +08:00
|
|
|
if strategy_name == "MarketRegimeStrategy":
|
|
|
|
|
if 'MarketRegime' in processed_data.columns:
|
|
|
|
|
sns.lineplot(x=processed_data.index, y='MarketRegime', data=processed_data, label='Market Regime (Sideways: 1, Trending: 0)', ax=ax3)
|
|
|
|
|
ax3.set_title('Bollinger Bands Width & Market Regime')
|
|
|
|
|
ax3.set_ylabel('Value')
|
|
|
|
|
elif strategy_name == "CryptoTradingStrategy":
|
2025-05-22 18:16:23 +08:00
|
|
|
if 'VolumeMA' in processed_data.columns:
|
|
|
|
|
sns.lineplot(x=processed_data.index, y='VolumeMA', data=processed_data, label='Volume MA', ax=ax3)
|
|
|
|
|
if 'volume' in processed_data.columns:
|
|
|
|
|
sns.lineplot(x=processed_data.index, y='volume', data=processed_data, label='Volume', ax=ax3, alpha=0.5)
|
|
|
|
|
ax3.set_title('Volume Analysis')
|
2025-05-22 17:57:04 +08:00
|
|
|
ax3.set_ylabel('Volume')
|
|
|
|
|
|
2025-05-22 16:44:59 +08:00
|
|
|
ax3.legend()
|
|
|
|
|
ax3.grid(True)
|
|
|
|
|
|
2025-05-22 17:57:04 +08:00
|
|
|
plt.xlabel('Date')
|
|
|
|
|
fig.tight_layout()
|
2025-05-20 18:28:53 +08:00
|
|
|
plt.show()
|
|
|
|
|
else:
|
|
|
|
|
logging.info("No data to plot.")
|
|
|
|
|
|