|
| 1 | +import numpy as np |
| 2 | +import pandas as pd |
| 3 | + |
| 4 | + |
| 5 | +def generate_signals(data, method, **kwargs): |
| 6 | + """Derive trading signals using the specified method and parameters. |
| 7 | +
|
| 8 | + Args: |
| 9 | + data (pd.DataFrame): DataFrame containing asset price data |
| 10 | + (must include 'Close' column). |
| 11 | + method (str): The signal generation method. Currently supported: |
| 12 | + - 'bollinger_bands': Uses Bollinger Bands for signal generation. |
| 13 | + **kwargs: Additional parameters specific to the chosen method |
| 14 | + (e.g., window size, number of standard deviations). |
| 15 | +
|
| 16 | + Returns: |
| 17 | + pd.Series: Trading signals (2: buy, 1: sell, 0: do nothing). |
| 18 | + """ |
| 19 | + if method == "flip": |
| 20 | + signal = _flip_signals(prices=data["Close"].squeeze()) |
| 21 | + if method == "bollinger": |
| 22 | + signal = _bollinger_signals(prices=data["Close"].squeeze(), **kwargs) |
| 23 | + return signal |
| 24 | + |
| 25 | + |
| 26 | +def _bollinger_signals(prices, window=20, num_std_dev=2): |
| 27 | + """Generate anticyclical trading signals based on Bollinger Bands. |
| 28 | +
|
| 29 | + Bollinger Bands are computed using a moving average and standard deviations |
| 30 | + to identify overbought (sell signal) and oversold (buy signal) conditions. |
| 31 | +
|
| 32 | + Args: |
| 33 | + prices (pd.Series): Series of asset prices. |
| 34 | + window (int): Window size for Bollinger Bands calculation (default is 20). |
| 35 | + num_std_dev (float): Number of standard deviations for the bands (default is 2). |
| 36 | +
|
| 37 | + Returns: |
| 38 | + pd.Series: Trading signals (2: buy, 1: sell, 0: do nothing). |
| 39 | + """ |
| 40 | + moving_avg = prices.rolling(window=window).mean().fillna(0) |
| 41 | + std_dev = prices.rolling(window=window).std() |
| 42 | + upper_band = moving_avg + (num_std_dev * std_dev) |
| 43 | + lower_band = moving_avg - (num_std_dev * std_dev) |
| 44 | + |
| 45 | + signals = pd.Series(0, index=prices.index) |
| 46 | + signals[prices < lower_band] = 2 |
| 47 | + signals[prices > upper_band] = 1 |
| 48 | + |
| 49 | + signals = _shift_signals_to_right(signals) |
| 50 | + return pd.Series(signals, index=prices.index) |
| 51 | + |
| 52 | + |
| 53 | +def _flip_signals(prices): |
| 54 | + """Generate trading signals based on price changes from the previous price. |
| 55 | +
|
| 56 | + Args: |
| 57 | + prices (np.Series): Series of asset prices without index. |
| 58 | +
|
| 59 | + Returns: |
| 60 | + np.ndarray: Trading signals (2: buy if previous price is lower, |
| 61 | + 1: sell if previous price is higher, |
| 62 | + 0: do nothing for the first price). |
| 63 | + """ |
| 64 | + price_diff = np.diff(prices, prepend=prices.iloc[0]) |
| 65 | + signals = np.zeros(len(prices), dtype=int) |
| 66 | + signals[1:][price_diff[1:] > 0] = 1 |
| 67 | + signals[1:][price_diff[1:] < 0] = 2 |
| 68 | + return signals |
| 69 | + |
| 70 | + |
| 71 | +def _shift_signals_to_right(signals, shift=1): |
| 72 | + return np.concatenate(([0] * shift, signals[:-shift])) |
0 commit comments