Основні моменти
- Складність статті: ★☆☆☆☆
- Введення в вплив володіння акціями інсайдерів (директорів та наглядачів) на ефективність акцій.
- Тестування стратегій володіння акціями інсайдерів за допомогою TQuant Lab для спостереження за ефективністю факторів імпульсу.
Передмова
На фондовому ринку частка акцій, що належать директорам та наглядачам, довгий час є важливим показником для інвесторів. Зміни у володінні акціями інсайдерів часто сигналізують про їхню впевненість у перспективах компанії, що може вплинути на ціну акцій. Однак, ідентифікація значущих змін у володінні акціями інсайдерів серед величезної кількості даних може бути складною для інвесторів.
Щоб вирішити цю проблему, ми використовуємо TQuant Lab для фільтрації компаній із значними змінами в пропорціях володіння акціями інсайдерів та проведення зворотного тестування. TQuant Lab дозволяє швидко виявляти потенційні кореляції між активністю керівництва та рухом ціни акцій, а також оцінювати ефективність стратегії, допомагаючи інвесторам знаходити приховані можливості на ринку.
Чи цікаво вам дізнатися, як використовувати пропорції володіння акціями інсайдерів для прогнозування тенденцій на ринку та отримання переваги в інвестуванні? Давайте зануримося!
Простий вибір акцій
Торгова логіка
Ми аналізуємо зміни в володінні акціями інсайдерів за допомогою трьох стратегій, тестуючи дані з 1 січня 2020 року по 31 грудня 2023 року. Згідно з пунктом 25, параграф 2 《Закон про цінні папери та біржі》, пропорції володіння акціями інсайдерів повинні розкриватися до 15 числа кожного місяця за попередній місяць. Тому ми встановлюємо дату перезбалансування стратегії на 16 число кожного місяця, щоб уникнути упередженості на основі майбутніх даних.
Критерії вибору стратегії
- Пропорція володіння понад 40%: Забезпечує, щоб інсайдери володіли значною часткою акцій, фільтруючи компанії з високим рівнем володіння.
- Топ-30 пропорцій володіння: Використовує ефект імпульсу, вибираючи 30 компаній з найвищими пропорціями володіння для спостереження за потенційно вищими доходами.
- Пропорція володіння збільшилась протягом 2 послідовних місяців: Вказує на те, що інсайдери мають оптимістичні очікування щодо майбутнього компанії, оскільки їхні володіння зростають.
Для простоти ці стратегії будуть позначені як Стратегії 1, 2 та 3 протягом статті.
Симульовані торгові витрати
Щоб підвищити реалістичність стратегій, модель враховує ковзання цін та транзакційні витрати для імітації впливу цінового тертя на результати торгівлі.
Середовище редагування та вимоги до модулів
Ця стаття використовує операційну систему Windows 11 з Visual Studio Code (VS Code) як основним редактором для аналізу та написання.
Використовувана версія zipline-tej - 2.0.0. Зверніть увагу, що стаття зосереджена на ключових частинах коду.
Для повного коду зверніться до GitHub репозиторію проекту!
Повний процес зворотного тестування
Імпорт необхідних пакетів
import os
import pandas as pd
os.environ['TEJAPI_KEY'] = "Yourkey"
os.environ['TEJAPI_BASE'] = "https://api.tej.com.tw"
from zipline.pipeline import Pipeline
from logbook import Logger, StderrHandler, INFO
log_handler = StderrHandler(format_string='[{record.time:%Y-%m-%d %H:%M:%S.%f}]: ' +
'{record.level_name}: {record.func_name}: {record.message}',
level=INFO)
log_handler.push_application()
log = Logger('Algorithm')
Отримання пулу акцій
На цьому етапі ми перевіряємо, чи містить пул акцій акції з характеристиками, які не відповідають вимогам стратегії, і вносимо відповідні коригування. Вибір стратегії включає як звичайні, так і KY акції (компанії, зареєстровані за кордоном, але котуються на Тайвані). Оскільки компанії з невеликими капіталами зазвичай мають вищі пропорції володіння інсайдерами, ми не обмежували вибір лише 200 найбільш ліквідними акціями Тайваню.
Цей дизайн дозволяє точніше зосередитися на акціях малих та середніх компаній, які відповідають критеріям стратегії, що, в свою чергу, підвищує ефективність стратегії.
from zipline.sources.TEJ_Api_Data import get_universe
start = '2020-01-01'
end = '2023-12-31'
pool = get_universe(start, end, mkt = ['TWSE', 'OTC'], stktp_e =['Common Stock-Foreign', 'Common Stock'])
Імпорт пулу акцій та даних ціни-обсягу
Перiод даних охоплює 2020-01-01 до 2023-12-31. Дані про ціну та обсяг акцій обраних компаній, а також зважений індекс доходності цін на акції (IR0001) імпортуються як еталон ефективності.
# Встановіть змінні середовища для визначення часового діапазону зворотного тестування та вибраного пулу акцій.
start_dt = pd.Timestamp(start, tz='utc')
end_dt = pd.Timestamp(end, tz='utc')
tickers = ' '.join(pool)
os.environ['mdate'] = start+' '+end
os.environ['ticker'] = tickers+' IR0001'
!zipline ingest -b tquant
Побудова історичних даних
import tejapi
# Отримуємо дані про володіння акціями директорів та наглядачів за допомогою API TWN/APIBSTN1
data = tejapi.fastget(
'TWN/APIBSTN1',
coid=pool,
mdate={'gte': pd.Timestamp(start) - pd.DateOffset(months=3), 'lte': pd.Timestamp(end)},
opts={'columns': ['coid', 'mdate', 'fld005', 'fld005l']},
paginate=True
).rename(columns={
'fld005': 'Director_and_Supervisor_Holdings_Percentage',
'fld005l': 'Difference_in_Director_and_Supervisor_Holdings_Percentage_from_Previous_Period'
})
# Розрахунок `The_Month_Before_Last` (fld005l попереднього періоду)
shift_data = (
data[['coid', 'mdate', 'Difference_in_Director_and_Supervisor_Holdings_Percentage_from_Previous_Period']]
.rename(columns={'Difference_in_Director_and_Supervisor_Holdings_Percentage_from_Previous_Period': 'The_Month_Before_Last'})
)
shift_data['mdate'] = shift_data['mdate'] + pd.DateOffset(months=1)
data = data.merge(shift_data, on=['coid', 'mdate'], how='left')
# Згідно з нормативними вимогами, дані про володіння акціями директорів та наглядачів повинні бути розкриті до 15 числа кожного місяця за попередній місяць.
# Тому потрібно відкоригувати `mdate` до 15 числа наступного місяця.
data['mdate'] = data['mdate'] + pd.DateOffset(months=1)
data['mdate'] = data['mdate'].apply(lambda x: x.replace(day=15))
# Замінюємо NA значення на 'N/A', щоб уникнути некоректного заповнення даних за допомогою forward-filling
for i in [
'Director_and_Supervisor_Holdings_Percentage',
'Difference_in_Director_and_Supervisor_Holdings_Percentage_from_Previous_Period',
'The_Month_Before_Last'
]:
data[i] = np.where(data[i].isnull(), 'N/A', data[i])
# Генеруємо DataFrame, що містить усі календарні дати для кожної компанії
days = pd.date_range(pd.Timestamp(start) - pd.DateOffset(months=2), end).tolist()
df_tradeday = pd.DataFrame({
'coid': [tick for tick in pool for day in days],
'mdate': days * len(pool)
})
# Об’єднуємо згенерований DataFrame з даними API, заповнюємо пропущені значення за допомогою forward-filling та замінюємо 'N/A' на np.nan
data = (
df_tradeday
.merge(data, on=['coid', 'mdate'], how='left')
.set_index(['coid', 'mdate'])
.sort_index()
.groupby(['coid'])
.ffill()
.reset_index()
.replace({'N/A': np.nan})
)
Трансформація даних у необхідний формат для Pipeline
from zipline.data import bundles
# Завантажуємо бандл 'tquant'
bundle = bundles.load('tquant')
sids = bundle.asset_finder.equities_sids
assets = bundle.asset_finder.retrieve_all(sids)
symbol_mapping_sid = {i.symbol: i.sid for i in assets}
# shift(1): Дані, опубліковані 15 числа, можна використовувати тільки з 16 числа, щоб уникнути forward-looking bias
transform_data = data.set_index(['coid', 'mdate']).unstack('coid').shift(1)
transform_data = transform_data.rename(columns=symbol_mapping_sid)
transform_data.index = transform_data.index.tz_localize('UTC')
transform_data.tail()
Створення бази даних CustomDataset
Після отримання даних про ціну-обсяг та історичних даних, ми можемо використати CustomDataset для допомоги у побудові необхідних торгових сигналів. Оскільки поля даних, які необхідні для Стратегії 1 та 2, ідентичні, вони можуть використовувати один і той самий CustomDataset, що підвищує ефективність обробки даних і спрощує робочі процеси.
Стратегії 1 та 2
from zipline.pipeline.data.dataset import Column, DataSet
from zipline.pipeline.domain import TW_EQUITIES
from zipline.pipeline.loaders.frame import DataFrameLoader
class CustomDataset(DataSet):
Director_and_Supervisor_Holdings_Percentage = Column(dtype=float)
domain = TW_EQUITIES
inputs=[CustomDataset.Director_and_Supervisor_Holdings_Percentage]
Custom_loader = {i:DataFrameLoader(column=i, baseline=transform_data[i.name]) for i in inputs}
Custom_loader
Стратегія 3
from zipline.pipeline.loaders.frame import DataFrameLoader
from zipline.pipeline.data.dataset import Column, DataSet
from zipline.pipeline.domain import TW_EQUITIES
class CustomDataset(DataSet):
Difference_in_Director_and_Supervisor_Holdings_Percentage_from_Previous_Period = Column(dtype=float)
The_Month_Before_Last = Column(dtype=float)
domain = TW_EQUITIES
inputs=[CustomDataset.Difference_in_Director_and_Supervisor_Holdings_Percentage_from_Previous_Period,
CustomDataset.The_Month_Before_Last]
Custom_loader = {i:DataFrameLoader(column=i, baseline=transform_data[i.name]) for i in inputs}
Custom_loader
Генерація торгових сигналів
Оскільки торгові сигнали для трьох стратегій відрізняються, їх потрібно налаштовувати окремо. Однак основні налаштування цих стратегій є однаковими, що дозволяє використовувати одну й ту саму базову структуру для всіх стратегій. Такий підхід покращує ефективність налаштувань і забезпечує послідовність.
Стратегія 1
def compute_signals():
Дозволяє Pipeline показувати дані, що відповідають конкретній даті
Dire = CustomDataset.DirectorandSupervisorHoldingsPercentage.latest
Фільтруємо DirectorandSupervisorHoldingsPercentagefilter = (CustomDataset.DirectorandSupervisorHoldings_Percentage.latest > 40) # Коли відсоток володіння акціями директора та наглядачів більший за 40, повертається True.
return Pipeline(columns={ ‘Dire’: Dire, # Стовпець для відсотка володіння акціями директора та наглядачів ‘longs’: DirectorandSupervisorHoldingsPercentage_filter # Вказує на акції, які потрібно купувати, коли True }, ) # Алгоритм купуватиме акції, де стовпець “longs” є True, і виходитиме з позицій, де він False.
Стратегії 2
def computesignals():
Dire = CustomDataset.DirectorandSupervisorHoldings_Percentage.latest
return Pipeline(columns={
‘Dire’: Dire,
‘longs’ : Dire.top(30),
}, )
Стратегії 3
def compute_signals():
Отримуємо останні значення для двох факторів моментуму
MOM1 = CustomDataset.DifferenceinDirectorandSupervisorHoldingsPercentagefromPreviousPeriod.latest
MOM2 = CustomDataset.TheMonthBeforeLast.latest
Умови фільтрації
DifferenceinDirectorandSupervisorHoldingsPercentagefromPreviousPeriodfilter = \
(CustomDataset.DifferenceinDirectorandSupervisorHoldingsPercentagefromPrevious_Period.latest > 0)
Фільтр для MOM-1: True, якщо різниця у відсотку володіння акціями директора та наглядачів від попереднього періоду є позитивною.
TheMonthBeforeLastfilter = (CustomDataset.TheMonthBefore_Last.latest > 0)
Фільтр для MOM-2: True, якщо різниця у відсотку володіння акціями за попередній місяць є позитивною.
Об’єднуємо фільтри
mask = (DifferenceinDirectorandSupervisorHoldingsPercentagefromPreviousPeriodfilter & TheMonthBeforeLastfilter)
Маска буде True тільки тоді, коли і MOM-1, і MOM-2 позитивні, що вказує на послідовні місячні збільшення.
return Pipeline(columns={
‘MOM-1’: MOM1, # MOM-1: Різниця у відсотку володіння акціями директора та наглядачів від попереднього періоду
‘MOM-1 Positive’: DifferenceinDirectorandSupervisorHoldingsPercentagefromPreviousPeriodfilter, # Чи є MOM-1 позитивним
‘MOM-2’: MOM2, # MOM-2: Різниця у відсотку володіння акціями за два місяці тому
‘MOM-2 Positive’: TheMonthBeforeLastfilter, # Чи є MOM-2 позитивним
‘longs’: mask, # Сигнал на покупку: True, якщо обидва MOM-1 і MOM-2 позитивні
},
)
Перевірка налаштувань Pipeline
Ручна перевірка результатів Pipeline, щоб переконатися, що вони відповідають очікуваним налаштуванням. Якщо будуть виявлені розбіжності, поверніться до функції compute_signals
, щоб внести необхідні корективи. Ось базовий метод для перевірки результатів Pipeline:
pipeline_result.query("longs == 1")
Результати Pipeline за прикладом Стратегії 3
Згідно з статтею 25, пункт 2 Закону про цінні папери та фондові ринки, відсоток володіння акціями директора та наглядачів повинен бути розкритий до 15 числа кожного місяця, відображаючи дані за попередній місяць.
Щоб уникнути упередженості щодо погляду в майбутнє (look-ahead bias), ми встановили дату ребалансування стратегії на 16-те число кожного місяця.
# Отримуємо всі торгові сесії
from zipline.utils.calendar_utils import get_calendar
cal = get_calendar('TEJ').all_sessions # Отримуємо всі сесії з календаря 'TEJ'
cal = cal[(cal >= '2020-01-01') & (cal <= '2023-12-31')] # Фільтруємо торгові сесії до бажаного діапазону дат
# Обчислюємо відстань кожного торгового дня від 16-го числа кожного місяця
cal = pd.DataFrame(cal).rename(columns={0: 'date'}) # Перетворюємо календар на DataFrame і перейменовуємо стовпець
cal['diff'] = cal['date'].transform(lambda x: x - pd.Timestamp(year=x.year, month=x.month, day=16, tz='UTC'))
# Для кожної дати обчислюємо різницю між торговим днем і 16-м числом відповідного місяця
# Вибираємо дати ребалансування (перший торговий день на або після 16-го числа кожного місяця) і перетворюємо їх у рядки
tradeday = cal.groupby([cal['date'].dt.year, cal['date'].dt.month]) \
.apply(lambda x: x[x['diff'].ge(pd.Timedelta(days=0))].head(1)).date.tolist()
# Групуємо торгові дні за роком і місяцем, і знаходимо перший торговий день, де різниця від 16-го числа >= 0
tradeday = [str(i.date()) for i in tradeday] # Перетворюємо вибрані дати в рядковий формат
Проведення простого бектесту за допомогою Algo
Встановлюємо початковий капітал на 1,000,000 і не використовуємо важелі.
from zipline.algo.pipeline_algo import *
algo = TargetPercentPipeAlgo(
start_session=start_dt,
end_session=end_dt,
capital_base=1e6,
tradeday=tradeday,
max_leverage=1,
slippage_model=slippage.VolumeShareSlippage(volume_limit=0.15, price_impact=0.01),
pipeline=compute_signals,
custom_loader = Custom_loader
)
results = algo.run()
Оцінка ефективності за допомогою Pyfolio
from pyfolio.utils import extract_rets_pos_txn_from_zipline
import pyfolio as pf
# Отримуємо доходність, позиції та транзакції з результатів бектесту
returns, positions, transactions = extract_rets_pos_txn_from_zipline(results)
benchmark_rets = results.benchmark_return # Отримуємо доходність бенчмарку
# Генеруємо всі доступні графіки, надані Pyfolio
pf.tears.create_full_tear_sheet(
returns=returns,
positions=positions,
transactions=transactions,
benchmark_rets=benchmark_rets
)
Стратегії 1
Стратегії 2
Стратегії 3
Згідно з наведеними даними, річна доходність трьох стратегій становить 15,145%, 17,988% і 20,04% відповідно, перевищуючи бенчмарк ринку (14,931%). Однак, аналізуючи графіки тенденцій, можна помітити, що ефективність трьох стратегій була або стабільною, або навіть відставала від бенчмарку більшу частину часу до 2022 року. Лише в 2022 році вони почали поступово відновлюватися. У майбутньому, подовження періоду спостереження може допомогти проаналізувати, чи відрізняється ефективність цієї інвестиційної стратегії у бичому та ведмежому ринках, що дозволить визначити сильні та слабкі сторони стратегії в різних ринкових циклах.
Окрім чистої доходності, важливим є аналіз доходності з урахуванням ризику. Шарп-індекси трьох методів становлять 0,99, 1,17 і 1,28 відповідно, перевищуючи бенчмарк (0,86). Аналогічно, індекси Сортіно для трьох стратегій становлять 1,26, 1,56 і 1,68, також перевищуючи бенчмарк (1,22).
Ці цифри свідчать про те, що Стратегії 2 і 3 демонструють кращі результати за доходністю та доходністю з урахуванням ризику.
Стратегії 1
Стратегії 2
Стратегії 3
Згідно з графіком вище, можна помітити, що кількість місяців з негативною доходністю для трьох стратегій становить 15, 16 і 16 відповідно, що менше, ніж для бенчмарку (20 місяців). Менша кількість місяців зі збитками порівняно з бенчмарком допомагає знизити психологічний тиск на інвесторів.
Стратегії 1
Стратегії 2
Стратегії 3
Подальше спостереження за розподілом портфеля з часом для 10 найбільших активів у кожній стратегії показує, що середнє співвідношення до одного акції становить приблизно 0.004, 0.035 і 0.0125 відповідно. Це свідчить про відсутність ризику надмірної концентрації в окремих акціях. Крім того, користувачі можуть налаштувати кількість активів відповідно до своїх уподобань для проведення подальших тестувань.
Стратегії 1
Стратегії 2
Стратегії 3
Нарешті, щодо максимального просідання (MDD), максимальні просідання для кожної стратегії становлять -30.345%, -22.702% і -27.511% відповідно. Лише Стратегії 2 і 3 перевершують бенчмарк ринку (-28.553%). З графіка п’яти найбільших просідань видно, що періоди максимальних просідань відбулися під час пандемії COVID-19 у 2020 році. Щодо тривалості просідання, максимальні періоди просідання для стратегій складають 273, 266 і 160 днів, що коротше за бенчмарк (359 днів).
Висновок
З результатів трьох стратегій очевидно, що характеристики ризику та доходності різняться в залежності від методу. Стратегії 2 і 3 демонструють найкращі результати як за чистою доходністю, так і за доходністю з урахуванням ризику, при цьому Стратегія 2 має найменше максимальне просідання. Загалом, стратегії, побудовані на основі коефіцієнтів володіння акціями директорами та керівниками, перевищують ринок за доходністю, маючи показники Шарпа, що наближаються до 1 або перевищують його. Це свідчить про те, що коефіцієнти володіння акціями директорами та керівниками мають певну прогностичну силу.
Серед трьох стратегій, Стратегії 2 і 3 обидві заслуговують на увагу. Однак конкретні умови входу та виходу можна налаштувати відповідно до вподобань окремих інвесторів та розширити за допомогою інструментів TQuant Lab. Цей приклад є лише простим демонстраційним тестом.
TQuant Lab надає зручне середовище для тестування факторів, що робить вибір, тестування та бектестинг стратегій на основі коефіцієнтів володіння акціями директорами та керівниками більш інтуїтивно зрозумілим.
Застосовуючи цей інструмент у практичних інвестиційних процесах, інвестори можуть більш впевнено досліджувати потенційні можливості на ринку, які виникають через зміни в коефіцієнтах володіння акціями директорами та керівниками, тим самим підвищуючи наукову основу прийняття інвестиційних рішень.
[
TQuant Lab|Все в одному місці для кількісних досліджень, створіть власний інвестиційний інструмент
TQuant Lab надає високоякісні дані та аналітичні інструменти, поєднуючи всеосяжний двигун для тестування стратегій та професійні інструменти для аналізу факторів, що дозволяє швидко генерувати візуалізовані звіти, і є незамінним інструментом на вашому шляху в кількісному інвестуванні.
tquant.tejwin.com
](https://tquant.tejwin.com/?source=post_page-----c7e07ac4d395--------------------------------)
Джерело коду
Розширене читання
- SДослідження факторів відбору акцій: поєднання володіння акціями інсайдерів і факторів моментуму
- AПосібник з алгоритмічної торгівлі: використання стратегій TQuant Lab з API SinoPac для автоматизованої торгівлі
Корисні посилання
Перекладено з: 【Application】TQuant Lab Director And Supervisor Holdings — Which Strategy Reigns Supreme?