| Overall Statistics |
|
Total Orders 0 Average Win 0% Average Loss 0% Compounding Annual Return 0% Drawdown 0% Expectancy 0 Start Equity 100000 End Equity 100000 Net Profit 0% Sharpe Ratio 0 Sortino Ratio 0 Probabilistic Sharpe Ratio 0% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha 0 Beta 0 Annual Standard Deviation 0 Annual Variance 0 Information Ratio -0.729 Tracking Error 0.142 Treynor Ratio 0 Total Fees $0.00 Estimated Strategy Capacity $0 Lowest Capacity Asset Portfolio Turnover 0% Drawdown Recovery 0 |
# region imports
from AlgorithmImports import *
import time
# endregion
class TradingSignal:
"""Trading signal states"""
NO_SIGNAL = 0
BUY = 1
SELL = 2
class DualMovingAverageStrategy(QCAlgorithm):
"""
Dual Moving Average Strategy with Volatility Filters
Strategy Logic:
- Uses fast (20-period) and slow (50-period) moving averages
- Entry: Fast MA crosses above Slow MA + Volatility within range
- Exit: Fast MA crosses below Slow MA OR stop-loss/take-profit hit
- Risk Management: Fixed position sizing with stop-loss at 2% below entry
"""
def initialize(self):
"""Initialize algorithm parameters and indicators"""
self.set_start_date(2013, 1, 1)
self.set_end_date(2024, 1, 1)
self.set_cash(100000)
# Strategy parameters
self._symbol = "SPY"
self._fast_period = 20
self._slow_period = 50
self._volatility_period = 20
self._min_volatility = 0.01 # 1% minimum ATR
self._max_volatility = 0.03 # 3% maximum ATR
self._position_size = 0.95 # Use 95% of portfolio
self._stop_loss_percent = 0.02 # 2% stop loss
self._take_profit_percent = 0.05 # 5% take profit
# Add equity with minute resolution for detailed tracking
self.add_equity(self._symbol, Resolution.MINUTE)
# Initialize indicators
self._fast_ma = self.sma(self._symbol, self._fast_period, Resolution.DAILY)
self._slow_ma = self.sma(self._symbol, self._slow_period, Resolution.DAILY)
self._atr = self.atr(self._symbol, self._volatility_period)
# Track entry price and current signal
self._entry_price = 0
self._current_signal = TradingSignal.NO_SIGNAL
self._previous_signal = TradingSignal.NO_SIGNAL
# Scheduling
self.schedule.on(self.date_rules.every_day(self._symbol),
self.time_rules.after_market_open(self._symbol, 5),
self.check_trading_signals)
def check_trading_signals(self):
"""Check for trading signals every day after market open"""
if not self._fast_ma.is_ready or not self._slow_ma.is_ready or not self._atr.is_ready:
return
# Get current values
current_price = self.securities[self._symbol].price
fast_ma = self._fast_ma.current.value
slow_ma = self._slow_ma.current.value
atr_value = self._atr.current.value
volatility_pct = atr_value / current_price if current_price > 0 else 0
# Determine signal
self._current_signal = self._get_signal(fast_ma, slow_ma, volatility_pct)
# Log signal information
signal_name = self._get_signal_name(self._current_signal)
self.debug(f"Price: {current_price:.2f} | Fast MA: {fast_ma:.2f} | Slow MA: {slow_ma:.2f} | " +
f"Volatility: {volatility_pct:.4f} | Signal: {signal_name}")
# Execute trading logic
self._execute_trades(current_price)
# Update previous signal
self._previous_signal = self._current_signal
def _get_signal_name(self, signal: int) -> str:
"""Get human-readable signal name"""
if signal == TradingSignal.BUY:
return "BUY"
elif signal == TradingSignal.SELL:
return "SELL"
else:
return "NO_SIGNAL"
def _get_signal(self, fast_ma: float, slow_ma: float, volatility_pct: float) -> int:
"""
Determine trading signal based on moving average crossover and volatility
Args:
fast_ma: Fast moving average value
slow_ma: Slow moving average value
volatility_pct: ATR as percentage of price
Returns:
TradingSignal value
"""
# Check volatility filter
volatility_acceptable = self._min_volatility <= volatility_pct <= self._max_volatility
if not volatility_acceptable:
return TradingSignal.NO_SIGNAL
# Check for crossover signals
if fast_ma > slow_ma:
return TradingSignal.BUY
elif fast_ma < slow_ma:
return TradingSignal.SELL
else:
return TradingSignal.NO_SIGNAL
def _execute_trades(self, current_price: float):
"""
Execute trades based on signals and position status
Args:
current_price: Current price of the security
"""
holdings = self.portfolio[self._symbol]
# Entry signals
if self._current_signal == TradingSignal.BUY and not self.portfolio.invested:
self._enter_long(current_price)
# Exit signals
elif self._current_signal == TradingSignal.SELL and self.portfolio.invested:
self._exit_position("Signal crossover")
# Risk management: Check stop loss and take profit
elif self.portfolio.invested and holdings.quantity > 0:
pnl_pct = (current_price - self._entry_price) / self._entry_price
# Stop loss
if pnl_pct <= -self._stop_loss_percent:
self._exit_position(f"Stop loss triggered ({pnl_pct:.2%})")
# Take profit
elif pnl_pct >= self._take_profit_percent:
self._exit_position(f"Take profit triggered ({pnl_pct:.2%})")
def _enter_long(self, entry_price: float):
"""
Enter a long position
Args:
entry_price: Price at which to enter
"""
self._entry_price = entry_price
quantity = int((self.portfolio.total_portfolio_value * self._position_size) / entry_price)
if quantity > 0:
self.buy(self._symbol, quantity)
self.log(f"ENTRY: Buying {quantity} shares of {self._symbol} at ${entry_price:.2f}")
def _exit_position(self, reason: str):
"""
Exit current position
Args:
reason: Reason for exiting
"""
holdings = self.portfolio[self._symbol]
if holdings.quantity > 0:
exit_price = self.securities[self._symbol].price
pnl = (exit_price - self._entry_price) * holdings.quantity
pnl_pct = (exit_price - self._entry_price) / self._entry_price
self.sell(self._symbol, holdings.quantity)
self.log(f"EXIT: Selling {holdings.quantity} shares at ${exit_price:.2f} | " +
f"Reason: {reason} | PnL: ${pnl:.2f} ({pnl_pct:.2%})")
self._entry_price = 0
self._current_signal = TradingSignal.NO_SIGNAL
# region imports
from AlgorithmImports import *
# endregion
# test.py
# A new Python file
def main():
print("Hello, World!")
if __name__ == "__main__":
main()