Анализ осцилляторов как технических торговых сигналов

Колебания, волны и графики MidJourney 2023.11.5

Осцилляторы — это индикаторы импульса, используемые в количественной торговле для определения точек разворота.

Используя скользящие средние и отклонения, они измеряют пики и впадины импульса. Колебания выше и ниже диапазонов или осевых линий сигнализируют о потенциальной перекупленности или перепроданности. Дивергенции между сигналами осциллятора и ценой могут угадать вероятностные точки разворота в тренде актива.

Общие осциллирующие сигналы

Схождение-расхождение скользящих средних (MACD) и абсолютный ценовой осциллятор (APO)

Они вычисляют разницу между двумя скользящими средними для определения импульса и изменения тренда:

  • APO — это осциллятор абсолютного импульса, в то время как MACD — осциллятор относительного импульса.
  • APO определяет изменения тренда, в то время как MACD ищет ускорение.
  • MACD не имеет фиксированной центральной линии и колеблется на основе своих входных данных, требуя других сигналов для синергии с этим.

Индекс относительной силы (RSI) Сравнивает величину недавних прибылей и убытков для определения условий перекупленности и перепроданности:

  • Вычисляет соотношение более высоких и более низких цен закрытия за заданный период. .
  • Значения ниже 30-х обычно считаются перепроданными, а выше 70-х сигнализируют о перекупленности. Оба дают диапазон от 0 до 100.
  • Сигналы, расхождения с ценовым действием, обычно сигнализируют об экстремумах импульса или разворотах.

Подготовка среды

Подготовьте среду jupyter и pip установите следующие библиотеки:

  • Нумпи
  • Панды
  • Ифинансы

Абсолютный ценовой осциллятор (APO)

Этот технический индикатор состоит из быстрой скользящей средней и медленной скользящей средней:

  1. Экспоненциальная скользящая средняя (EMA): Скользящая средняя, которая придает больший вес последним данным:
Латекс в блокноте
  1. быстрая EMA (fEMA): скользящая ценовая средняя с 12-дневным периодом. Быстро реагирует на краткосрочные изменения цен, чувствителен к последним событиям на рынке.
  2. медленная EMA (sEMA): Скользящая ценовая средняя с 24-дневным периодом. Обеспечивает долгосрочное представление о ценовых тенденциях.

Пересечение быстрой EMA выше медленной EMA может сигнализировать о бычьем тренде, в то время как обратное может указывать на медвежий тренд.

Ниже приведена визуализация, закодированная ниже:ticker[‘fEMA’] = ticker[‘Adj Close’].ewm(
span=APO_FAST_WINDOW, adjust=False).mean()
ticker[‘sEMA’] = ticker[‘Adj Close’].ewm(
span=APO_SLOW_WINDOW, adjust=False).mean()
ticker[‘APO’] = ticker[‘fEMA’] — ticker[‘sEMA’]
fig, (ax1, ax2) = plt.subplots(2, 1, gridspec_kw={
‘height_ratios’: (3, 1)}, figsize=(24, 12))

ax1.plot(ticker.index, ticker[‘Adj Close’], label=’AAPL Close Price’)
ax1.plot(ticker.index, ticker[‘fEMA’], label=’fEMA’, linestyle=’—‘)
ax1.plot(ticker.index, ticker[‘sEMA’], label=’sEMA’, linestyle=’—‘)
ax1.set_title(‘AAPL Price and EMAs’)
ax1.set_ylabel(‘Price’)
ax1.set_xticks([])

ax2.axhline(APO_BULL_SIGNAL)
ax2.axhline(0.0)
ax2.axhline(APO_BEAR_SIGNAL)
ax2.plot(ticker.index, ticker[‘APO’], label=’APO’, lw=1.25, color=’r’)
ax2.set_xlabel(‘APO’)

ax1.legend()
ax2.legend()
plt.tight_layout()
plt.show()

Исходя из этой информации, построим сигнал:def signal_apo_oscillator(ticker_ts, fast_window_size=APO_FAST_WINDOW, slow_window_size=APO_SLOW_WINDOW, buy_threshold=APO_BULL_SIGNAL, sell_threshold=APO_BEAR_SIGNAL):
«»»
Calculate signals using the Absolute Price Oscillator (APO) indicator for a given stock’s time series.
Parameters:
— ticker_ts (DataFrame): Time series data for the stock, typically containing ‘Adj Close’ prices.
— fast_window_size (int, optional): Fast EMA (Exponential Moving Average) window size. Default is APO_FAST_WINDOW.
— slow_window_size (int, optional): Slow EMA window size. Default is APO_SLOW_WINDOW.
— buy_threshold (float, optional): Buy signal threshold for the APO. Default is APO_BULL_SIGNAL.
— sell_threshold (float, optional): Sell signal threshold for the APO. Default is APO_BEAR_SIGNAL.
Returns:
— signals_df (DataFrame): DataFrame containing signals based on APO oscillator:
— ‘signal’: Signal values (1 for buy, -1 for sell, 0 for no signal).
— ‘orders’: Changes in signals (buy/sell orders) with None for no change.
«»»
fema = ticker_ts[‘Adj Close’].ewm(
span=fast_window_size, adjust=False).mean()
sma = ticker_ts[‘Adj Close’].ewm(
span=slow_window_size, adjust=False).mean()
apo = fema — sma

signals_df = pd.DataFrame(index=ticker_ts.index)
signals_df[‘signal’] = np.where(
apo >= buy_threshold, 1, np.where(apo <= sell_threshold, -1, 0))
signals_df[‘orders’] = signals_df[‘signal’].diff()
signals_df.loc[signals_df[‘orders’] == 0, ‘orders’] = None
return signals_df

signals_df = signal_apo_oscillator(ticker)
profit_series = calculate_profit(signals_df, ticker[«Adj Close»])
ax1, ax2 = plot_strategy(ticker[«Adj Close»], signals_df, profit_series)
ax1.plot(ticker.index, ticker[‘Adj Close’], label=’AAPL Close Price’)
ax1.plot(ticker.index, ticker[‘fEMA’], label=’fEMA’, linestyle=’—‘)
ax1.plot(ticker.index, ticker[‘sEMA’], label=’sEMA’, linestyle=’—‘)
plt.show()

Стратегия генерирует сигналы на покупку и продажу на основе наших пороговых значений: buy_threshold представляет собой порог бычьего сигнала, а sell_threshold представляет собой порог медвежьего сигнала.

Когда наш APO пересечет любой из этих порогов, мы создадим сигнал во временной серии.

Мы вернем кадр данных сигнала, состоящий из:

  • ‘signal’: Содержит значения 1 (покупка), -1 (продажа) или 0 (отсутствие сигнала) в зависимости от условий APO.
  • «orders»: В этом столбце представлены изменения в торговых позициях. Он вычисляется как разница в столбце ‘signal’ и устанавливается в None, когда не происходит изменения торговой позиции.

Так как все наши сигналы в этой серии статей только длинные, мы будем покупать по первому сигналу на покупку и держать до первого сигнала на продажу. Эта разница и есть прибыль, которую мы видим выше.

Эта стратегия принесла смоделированные ~40% за 2 года против 10% у ~S&P500 — и все это на бумаге!

Схождение-расхождение скользящих средних (MACD)

Этот индикатор фаст-фуда, созданный Джеральдом Аппелем, идет на шаг дальше, чем APO.

Он устанавливает разницу между быстрой экспоненциальной скользящей средней и медленной экспоненциальной скользящей средней, такой как APO, но также сглаживает разницу. Правильно настроенный сигнал MACD может зафиксировать направление, величину и продолжительность трендовой цены инструмента.ticker[‘fEMA’] = ticker[‘Adj Close’].ewm(
span=APO_FAST_WINDOW, adjust=False).mean()
ticker[‘sEMA’] = ticker[‘Adj Close’].ewm(
span=APO_SLOW_WINDOW, adjust=False).mean()
ticker[‘APO’] = ticker[‘fEMA’] — ticker[‘sEMA’]
ticker[‘MACD’] = ticker[‘APO’] .ewm(
span=APO_SLOW_WINDOW, adjust=False).mean()
ticker[‘MACDHIST’] = ticker[‘APO’] — ticker[‘MACD’]
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, gridspec_kw={
‘height_ratios’: (3, 1, 1)}, figsize=(24, 12))
ax1.plot(ticker.index, ticker[‘Adj Close’], label=’AAPL Close Price’)
ax1.plot(ticker.index, ticker[‘fEMA’], label=’fEMA’, linestyle=’—‘)
ax1.plot(ticker.index, ticker[‘sEMA’], label=’sEMA’, linestyle=’—‘)
ax1.set_title(‘AAPL Price and EMAs’)
ax1.set_ylabel(‘Price’)
ax1.set_xticks([])
ax2.axhline(0.0)
ax2.plot(ticker.index, ticker[‘APO’], label=’MACD’, lw=2.25, color=’g’)
ax2.plot(ticker.index, ticker[‘MACD’],
label=’Smoothed MACD’, lw=2.25, color=’b’)
ax2.set_xlabel(‘APO’)
ax3.axhline(0.0)
ax3.bar(ticker.index, ticker[‘MACDHIST’],
label=’MACD Histogram’, color=’r’, width=1.0, align=’center’)
ax3.set_xlabel(‘MACDHIST’)
ax1.legend()
ax2.legend()
ax3.legend()
plt.tight_layout()
plt.show()

Исходя из этой информации, построим сигнал:def signal_apo_oscillator(ticker_ts, fast_window_size=APO_FAST_WINDOW, slow_window_size=APO_SLOW_WINDOW, buy_threshold=APO_BULL_SIGNAL, sell_threshold=APO_BEAR_SIGNAL):
«»»
Calculate signals using the Absolute Price Oscillator (APO) indicator for a given stock’s time series.
Parameters:
— ticker_ts (DataFrame): Time series data for the stock, typically containing ‘Adj Close’ prices.
— fast_window_size (int, optional): Fast EMA (Exponential Moving Average) window size. Default is APO_FAST_WINDOW.
— slow_window_size (int, optional): Slow EMA window size. Default is APO_SLOW_WINDOW.
— buy_threshold (float, optional): Buy signal threshold for the APO. Default is APO_BULL_SIGNAL.
— sell_threshold (float, optional): Sell signal threshold for the APO. Default is APO_BEAR_SIGNAL.
Returns:
— signals_df (DataFrame): DataFrame containing signals based on APO oscillator:
— ‘signal’: Signal values (1 for buy, -1 for sell, 0 for no signal).
— ‘orders’: Changes in signals (buy/sell orders) with None for no change.
«»»
fema = ticker_ts[‘Adj Close’].ewm(
span=fast_window_size, adjust=False).mean()
sma = ticker_ts[‘Adj Close’].ewm(
span=slow_window_size, adjust=False).mean()
apo = fema — sma

signals_df = pd.DataFrame(index=ticker_ts.index)
signals_df[‘signal’] = np.where(
apo >= buy_threshold, 1, np.where(apo <= sell_threshold, -1, 0))
signals_df[‘orders’] = signals_df[‘signal’].diff()
signals_df.loc[signals_df[‘orders’] == 0, ‘orders’] = None
return signals_df

signals_df = signal_apo_oscillator(ticker)
profit_series = calculate_profit(signals_df, ticker[«Adj Close»])
ax1, ax2 = plot_strategy(ticker[«Adj Close»], signals_df, profit_series)
ax1.plot(ticker.index, ticker[‘Adj Close’], label=’AAPL Close Price’)
ax1.plot(ticker.index, ticker[‘fEMA’], label=’fEMA’, linestyle=’—‘)
ax1.plot(ticker.index, ticker[‘sEMA’], label=’sEMA’, linestyle=’—‘)
plt.show()

Стратегия генерирует сигналы на покупку и продажу на основе наших пороговых значений: buy_threshold представляет собой порог бычьего сигнала, а sell_threshold представляет собой порог медвежьего сигнала.

Когда наш APO пересекает любой из этих порогов MACD, мы создаем сигнал во временной серии.

Мы вернем кадр данных сигнала, состоящий из:

  • ‘signal’: Содержит значения 1 (покупка), -1 (продажа) или 0 (отсутствие сигнала) в зависимости от условий APO.
  • «orders»: В этом столбце представлены изменения в торговых позициях. Он вычисляется как разница в столбце ‘signal’ и устанавливается в None, когда не происходит изменения торговой позиции.

Так как все наши сигналы в этой серии статей только длинные, мы будем покупать по первому сигналу на покупку и держать до первого сигнала на продажу. Эта разница и есть прибыль, которую мы видим выше.

Эта стратегия принесла смоделированные ~40% за 2 года против 10% у ~S&P500 — и все это на бумаге!

Индикатор относительной силы (RSI)

Последний индикатор в этой статье. Разработанный Уэллсом Уайлдером, RSI работает с ценой, а не со средними ценами, и отслеживает изменения за периоды, чтобы уловить величину движений.

Используя скользящее окно, он вычисляет изменения за этот период, а также величину средних убытков/снижения цены. Затем он вычисляет, сколько еще прибылей по отношению к убыткам или убытков по отношению к прибылям было в сигнале от 0 до 100. Значения RSI выше 50% указывают на восходящий тренд, в то время как значения RSI ниже 50% указывают на нисходящий тренд.

Для последних n периодов применяется следующее:

LaTex в блокноте

Где Average Gain — это среднее значение роста (прироста) цены за указанный период, а Average Loss — среднее значение снижения цены за тот же период.

  • Абсолютный выигрыш — это текущая цена — предыдущая цена, IF, текущая цена > предыдущая цена. Абсолютный убыток здесь равен 0.
  • Абсолютным проигрышем считается предыдущая цена — текущая цена, если, текущая цена < предыдущая цена. Абсолютный выигрыш здесь равен 0.

Давайте визуализируем это на примере акции AAPL:from sklearn.preprocessing import MinMaxScaler

RSI_WINDOW = 14
scaler = MinMaxScaler()
ticker[‘pDELTA’] = ticker[‘Adj Close’].diff().fillna(
0) # index 0 is NAN in DIFF
ticker[‘pGAINS’] = ticker[‘pDELTA’].where(ticker[‘pDELTA’] > 0, 0)
ticker[‘pLOSSES’] = -ticker[‘pDELTA’].where(ticker[‘pDELTA’] < 0, 0)
ticker[‘pGAINS’] = ticker[‘pGAINS’].rolling(
window=RSI_WINDOW, min_periods=RSI_WINDOW).mean()
ticker[‘pLOSSES’] = ticker[‘pLOSSES’].rolling(
window=RSI_WINDOW, min_periods=RSI_WINDOW).mean()
ticker[‘RS’] = ticker[‘pGAINS’] / ticker[‘pLOSSES’]
g_scaled = scaler.fit_transform(ticker[‘pGAINS’].values.reshape(-1, 1))
l_scaled = scaler.fit_transform(ticker[‘pLOSSES’].values.reshape(-1, 1))
rs_scaled = scaler.fit_transform(ticker[‘RS’].values.reshape(-1, 1))
ticker[‘RSI’] = 100 — (100 / (1 + ticker[‘RS’]))
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, gridspec_kw={
‘height_ratios’: (2, 1, 1)}, figsize=(24, 12))
ax1.plot(ticker.index, ticker[‘Adj Close’], label=’AAPL Close Price’)
ax1.set_title(‘AAPL Price’)
ax1.set_ylabel(‘Price’)
ax1.set_xticks([])
ax2.plot(ticker.index, g_scaled,
label=’Price Absolute Gains’, lw=1.25, color=’g’, linestyle=’—‘)
ax2.plot(ticker.index, l_scaled,
label=’Price Absolute Losses’, lw=1.25, color=’r’, linestyle=’—‘)
ax2.plot(ticker.index, rs_scaled,
label=’Price Strength’, lw=2, color=’b’)
ax3.plot(ticker.index, ticker[‘RSI’],
label=’RSI’, lw=2.25, color=’black’)
ax1.legend()
ax2.legend()
ax3.legend()
plt.tight_layout()
plt.show()

Мы добавили средство масштабирования, чтобы можно было сравнить относительную силу со средними абсолютными прибылями и убытками.RSI_OVERBOUGH_SIGNAL = 70
RSI_OVERSOLD_SIGNAL = -30

def signal_rsi(ticker_ts, rsi_window=RSI_WINDOW, overbought=RSI_OVERBOUGH_SIGNAL, oversold=RSI_OVERSOLD_SIGNAL):
«»»
Calculate signals using the Relative Strength Index (RSI) indicator for a given stock’s time series.
Parameters:
— ticker_ts (DataFrame): Time series data for the stock, typically containing ‘Close’ prices.
— rsi_window (int, optional): RSI calculation window. Default is 14.
— overbought (int, optional): RSI threshold for overbought condition. Default is 70.
— oversold (int, optional): RSI threshold for oversold condition. Default is 30.
Returns:
— signals_df (DataFrame): DataFrame containing signals based on the RSI indicator:
— ‘signal’: Signal values (1 for buy, -1 for sell, 0 for no signal).
— ‘orders’: Changes in signals (buy/sell orders) with None for no change.
«»»
delta = ticker_ts[‘Close’].diff()
# Calculate gains (positive changes) and losses (negative changes)
gains = delta.where(delta > 0, 0)
losses = -delta.where(delta < 0, 0)
avg_gains = gains.rolling(window=rsi_window, min_periods=rsi_window).mean()
avg_losses = losses.rolling(
window=rsi_window, min_periods=rsi_window).mean()
rs = avg_gains / avg_losses
rsi = 100 — (100 / (1 + rs))
signals_df = pd.DataFrame(index=ticker_ts.index)
# Detect overbought (1) and oversold (-1) signals based on RSI thresholds
overbought_signal = rsi > overbought
oversold_signal = rsi < oversold
signals_df[‘signal’] = np.where(
oversold_signal, 1, np.where(overbought_signal, -1, 0))
signals_df[‘orders’] = signals_df[‘signal’].diff()
signals_df.loc[signals_df[‘orders’] == 0, ‘orders’] = None
return signals_df

signals_df = signal_macd(ticker)
profit_series = calculate_profit(signals_df, ticker[«Adj Close»])
plot_strategy(ticker[«Adj Close»], signals_df, profit_series)
plt.show()

Количественный трейдер может использовать индекс относительной силы (RSI) для определения условий перекупленности и перепроданности в движении цены актива:

  • Когда RSI падает ниже определенного порога (например, 30), он считается перепроданным. Может произойти разворот к среднему значению для покупки.
  • Когда RSI поднимается выше определенного порога (например, 70), он считается перекупленным. Может произойти возврат к среднему значению, следовательно, продажа.

Этот сигнал вернул ~60% на бумагах, мы делаем здесь что-то хорошее или у нас ошибка, которую мы не видим! Это также создало много заказов и, следовательно, сборов, которые съели бы прибыль.

Заключение

В заключение, с помощью моделирования с использованием акций Apple мы узнали, как кванты будут реализовывать эти сигналы осцилляторов, и как EMA помогают отфильтровать шум в наборе данных для получения более четкой картины трендов.

Теперь квант может использовать эти сигналы, чтобы уловить перегибы в тренде актива и помочь оценить момент закрытия или откат. Ваша толпа на Youtube будет использовать его, чтобы продавать вам курсы трейдинга, оставайтесь в безопасности.

MidJourney Инфлюенсер, смотрящий на волны 2023.11.

Ссылки

Github

Статья здесь также доступна на Github

Блокнот Kaggle доступен здесь

Источник