AM (Alpha Model)

Creating Your Portfolio Position in a Very Simple Way: Embracing a Bias-Free Approach

Develop AM (Alpha Model)

Helper Function: Calculating Previous Start Date

from datetime import datetime, timedelta


def calculate_previous_start_date(start_date, lookback_days):
    start = datetime.strptime(str(start_date), "%Y%m%d")
    previous_start = start - timedelta(days=lookback_days)
    return int(previous_start.strftime("%Y%m%d"))

Very simple AM script

from finter import BaseAlpha


# Define the lookback period in days
LOOKBACK_DAYS = 365

start, end = 20200101, 20220101

# Alpha class inheriting from BaseAlpha
class Alpha(BaseAlpha):
    # Method to generate alpha
    def get(self, start, end):
        # Calculate the start date for data retrieval
        pre_start = calculate_previous_start_date(start, LOOKBACK_DAYS)
        
        
        # Retrieve daily closing prices
        self.close = self.get_cm(
            "content.fnguide.ftp.price_volume.price_close.1d"
        ).get_df(pre_start, end)
        # Calculate momentum
        momentum_21d = self.close.pct_change(21)
        # Rank stocks by momentum
        stock_rank = momentum_21d.rank(pct=True, axis=1)
        # Select top 10% of stocks
        stock_top10 = stock_rank[stock_rank>=0.9]
        # Apply rolling mean to smooth data
        stock_top10_rolling = stock_top10.rolling(21).apply(lambda x: x.mean())
        # Normalize and scale to position sizes
        stock_ratio = stock_top10_rolling.div(stock_top10_rolling.sum(axis=1), axis=0)
        
        
        position = stock_ratio * 1e8
        # Shift positions to avoid look-ahead bias
        alpha = position.shift(1)
        # Alpha position must be a value between start and end
        return alpha.loc[str(start): str(end)]

# Run the alpha generation process
alpha = Alpha().get(start, end)

Detail code description.

Alpha Class Definition

LOOKBACK_DAYS = 365
class Alpha(BaseAlpha):

We define a constant LOOKBACK_DAYS set to 365, representing one year of historical data. The Alpha class inherits from BaseAlpha, which contains methods and properties useful for alpha development.

Alpha Generation Method

    def get(self, start, end):
        pre_start = calculate_previous_start_date(start, LOOKBACK_DAYS)

The get method is the core function that generates the alpha. It takes a start and end date for the analysis period. The calculate_previous_start_date function is used to extend the start date further back by the number of LOOKBACK_DAYS.

Data Retrieval

        # Retrieve daily closing prices for the specified date range
        self.close = self.get_cm(
            "content.fnguide.ftp.price_volume.price_close.1d"
        ).get_df(pre_start, end)

This line retrieves the daily closing prices of stocks between the pre_start and end dates. The get_cm method is a part of the BaseAlpha class and is used to call data modules.

Momentum Calculation

        # Calculate 21-day momentum for closing prices
        momentum_21d = self.close.pct_change(21)

We calculate the 21-day momentum of the closing prices, which is the percentage change over the last 21 days.

Stock Ranking

        # Rank stocks based on their momentum
        stock_rank = momentum_21d.rank(pct=True, axis=1)

The momentum values are then ranked on a percentile scale (0 to 1) across all stocks for each day.

Selecting Top 10% Stocks

        # Filter to select the top 10% of stocks by momentum
        stock_top10 = stock_rank[stock_rank>=0.9]

We filter the stocks to select only the top 10% based on their momentum ranks.

Rolling Mean of Top Stocks

        # Apply a rolling mean to smooth the top stocks' data
        stock_top10_rolling = stock_top10.rolling(21).apply(lambda x: x.mean())

A rolling mean with a 21-day window is applied to the top 10% stocks to smooth out the data.

Position Sizing

        # Normalize the rolling mean values and scale to position sizes
        stock_ratio = stock_top10_rolling.div(stock_top10_rolling.sum(axis=1), axis=0)
        position = stock_ratio * 1e8

The rolling mean values are normalized by dividing by the sum across all stocks to get a ratio. This ratio is then scaled up to determine the position sizes, here represented by a hypothetical amount of 100 million (1e8).

Generating Alpha

        # Shift positions to avoid look-ahead bias and generate the alpha
        alpha = position.shift(1)
        # Alpha position must be a value between start and end
        return alpha.loc[str(start): str(end)]

The final alpha is generated by shifting the positions by one day to avoid look-ahead bias. This means we use yesterday's positions to determine today's trades.

Running the Alpha

# Create an instance of the Alpha class and run the alpha generation
alpha = Alpha().get(20230101, 20230201)

Finally, we create an instance of the Alpha class and call the get method with a specific start and end date to run the alpha generation process.

This guide provides a basic framework for writing an alpha in Python. It is important to note that this is a simplified example, and real-world alpha development involves more complex data analysis, risk management, and performance evaluation.

Last updated