← Back to Home
A Simple Long Momentum Portfolio Strategy 15% to 2400% Annual Returns!

A Simple Long Momentum Portfolio Strategy 15% to 2400% Annual Returns!

Momentum strategies have garnered much attention in the cryptocurrency space, where strong trends can produce significant gains. However, these strategies can be vulnerable to sudden downturns. One simple way to mitigate risk is by implementing a stop loss. I am using Binance USDC pairs and download the price data from yahoo finance using yfinance Python library. I then rank them using a risk‑adjusted return (30‑day return divided by the corresponding volatility) to select the top 10 trending assets. I hold these for one week and, if the portfolio’s return for that week falls below –5%, I liquidate the portfolio so the loss is capped at –5%.

For performance analysis, I am calculating the cumulative weekly returns for each year from 2020 to 2024. Each year’s returns are aligned by week number (Week 1, Week 2, …) and overlaid on a single chart to facilitate comparison across years.

It is worth noting that the strategy can be used for any asset class like stocks, currencies, or bonds for that matter. I usually use cryptocurrencies because I am mainly trading cryptocurrencies myself.

Below is the complete Python code so you can try it yourself:


import requests
import pandas as pd
import yfinance as yf
from binance_pairs import BinanceSymbols
import matplotlib.pyplot as plt

# Get Binance USDC pairs and convert them to ticker symbols in the format 'SYMBOL-USD'
pairs = BinanceSymbols().get_usdc_pairs()
tickers = [pair[:-4] + '-USD' for pair in pairs]

# Download data for the entire period (2020-01-01 to 2024-12-31)
data = yf.download(tickers, start='2020-01-01', end='2024-12-31')
# Extract the 'Close' prices and sort by date
data = data.xs('Close', axis=1, level=0).sort_index()

# Drop tickers with any missing data to ensure consistency across years
data = data.dropna(axis=1)

# Define the weekly stop loss threshold (if weekly return is below -5%, cap it at -5%)
stop_loss_threshold = -0.05

# List of years for analysis
years = [2020, 2021, 2022, 2023, 2024]
yearly_results = {}

# For each year, iterate over the data in 7-day steps to compute weekly returns
for year in years:
    start_date = pd.to_datetime(f"{year}-01-01")
    end_date = pd.to_datetime(f"{year}-12-31")
    results = []
    
    # Loop over the data, starting at index 30 to have a 30-day lookback period
    for i in range(30, len(data) - 7, 7):
        current_date = data.index[i]
        # Process only weeks within the current year
        if current_date < start_date or current_date > end_date:
            continue

        # Calculate the "monthly" return using a 30-day lookback
        monthly_returns = (data.iloc[i] / data.iloc[i - 30]) - 1

        # Calculate volatility over the past 30 days for each asset
        volatilities = {}
        for ticker in tickers:
            try:
                daily_returns = data[ticker].iloc[i-30:i].pct_change().dropna()
                vol = daily_returns.std()
                volatilities[ticker] = vol
            except Exception:
                continue
        vol_series = pd.Series(volatilities)
        if vol_series.empty:
            continue

        # Compute the risk-adjusted metric: monthly return divided by volatility
        risk_adjusted = monthly_returns[vol_series.index] / (vol_series + 1e-8)

        # Select the top 10 tickers based on the risk-adjusted metric
        top10_tickers = risk_adjusted.nlargest(10).index

        # Calculate the next week's (7-day) returns for these assets
        weekly_returns = (data.iloc[i + 7][top10_tickers] / data.iloc[i][top10_tickers]) - 1
        avg_weekly_return = weekly_returns.mean()

        # Apply the weekly stop loss: cap the weekly return at -5% if necessary
        avg_weekly_return_sl = avg_weekly_return if avg_weekly_return >= stop_loss_threshold else stop_loss_threshold

        results.append((current_date, avg_weekly_return_sl))
    
    # Create a DataFrame for the year and compute cumulative returns
    df_year = pd.DataFrame(results, columns=['Week Start', 'Avg 1 Week Return'])
    df_year['Cumulative Return'] = (df_year['Avg 1 Week Return'] + 1).cumprod() - 1
    df_year = df_year.sort_values('Week Start').reset_index(drop=True)
    # Assign sequential week numbers for plotting on the x-axis
    df_year['Week'] = df_year.index + 1
    yearly_results[year] = df_year

# Plot the cumulative weekly returns for each year on the same chart using week number as the x-axis
plt.figure(figsize=(12, 6))
for year, df_year in yearly_results.items():
    plt.plot(df_year['Week'], df_year['Cumulative Return'] * 100, marker='o', label=str(year))
    
plt.xlabel('Week Number')
plt.ylabel('Cumulative Return (%)')
plt.title('Cumulative Weekly Returns with Weekly Stop Loss (Risk-Adjusted Momentum)')
plt.legend()
plt.grid(True)
plt.show()

In this implementation:

Pasted image 20250402032457.png ## Conclusion

Overlaying cumulative weekly returns for multiple years provides a clear picture of seasonal or cyclical performance trends in a momentum strategy with a weekly stop loss. This visualization can help traders understand how the strategy behaves during various market conditions, making it easier to adjust parameters or risk management techniques as needed. We only considered long positions, however if we used a margin or futures account and used both long and short positions for top gaining and losing ones we could have definitely made even more phenomenal results. I might try it and share the results later in another article. By combining a risk‑adjusted momentum selection with a weekly stop loss, this simple strategy aims to capture trends while containing drawdowns—a valuable approach in the volatile world of cryptocurrencies.