Застосування TQuant Lab: яка стратегія з коефіцієнтами володіння акціями директорами та керівниками є найкращою?

pic

Основні моменти

  • Складність статті: ★☆☆☆☆
  • Введення в вплив володіння акціями інсайдерів (директорів та наглядачів) на ефективність акцій.
  • Тестування стратегій володіння акціями інсайдерів за допомогою TQuant Lab для спостереження за ефективністю факторів імпульсу.

Передмова

На фондовому ринку частка акцій, що належать директорам та наглядачам, довгий час є важливим показником для інвесторів. Зміни у володінні акціями інсайдерів часто сигналізують про їхню впевненість у перспективах компанії, що може вплинути на ціну акцій. Однак, ідентифікація значущих змін у володінні акціями інсайдерів серед величезної кількості даних може бути складною для інвесторів.
Щоб вирішити цю проблему, ми використовуємо TQuant Lab для фільтрації компаній із значними змінами в пропорціях володіння акціями інсайдерів та проведення зворотного тестування. TQuant Lab дозволяє швидко виявляти потенційні кореляції між активністю керівництва та рухом ціни акцій, а також оцінювати ефективність стратегії, допомагаючи інвесторам знаходити приховані можливості на ринку.
Чи цікаво вам дізнатися, як використовувати пропорції володіння акціями інсайдерів для прогнозування тенденцій на ринку та отримання переваги в інвестуванні? Давайте зануримося!

Простий вибір акцій

Торгова логіка

Ми аналізуємо зміни в володінні акціями інсайдерів за допомогою трьох стратегій, тестуючи дані з 1 січня 2020 року по 31 грудня 2023 року. Згідно з пунктом 25, параграф 2 《Закон про цінні папери та біржі》, пропорції володіння акціями інсайдерів повинні розкриватися до 15 числа кожного місяця за попередній місяць. Тому ми встановлюємо дату перезбалансування стратегії на 16 число кожного місяця, щоб уникнути упередженості на основі майбутніх даних.

Критерії вибору стратегії

  1. Пропорція володіння понад 40%: Забезпечує, щоб інсайдери володіли значною часткою акцій, фільтруючи компанії з високим рівнем володіння.
  2. Топ-30 пропорцій володіння: Використовує ефект імпульсу, вибираючи 30 компаній з найвищими пропорціями володіння для спостереження за потенційно вищими доходами.
  3. Пропорція володіння збільшилась протягом 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.Director
andSupervisorHoldings_Percentage.latest
return Pipeline(columns={
‘Dire’: Dire,
‘longs’ : Dire.top(30),
}, )

Стратегії 3

def compute_signals():

Отримуємо останні значення для двох факторів моментуму

MOM1 = CustomDataset.DifferenceinDirectorandSupervisorHoldingsPercentagefromPreviousPeriod.latest
MOM2 = CustomDataset.The
MonthBeforeLast.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")

pic

Результати 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

pic

Стратегії 2

pic

Стратегії 3

pic

Згідно з наведеними даними, річна доходність трьох стратегій становить 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

pic

Стратегії 2

pic

Стратегії 3

pic

Згідно з графіком вище, можна помітити, що кількість місяців з негативною доходністю для трьох стратегій становить 15, 16 і 16 відповідно, що менше, ніж для бенчмарку (20 місяців). Менша кількість місяців зі збитками порівняно з бенчмарком допомагає знизити психологічний тиск на інвесторів.

Стратегії 1

pic

Стратегії 2

pic

Стратегії 3

pic

Подальше спостереження за розподілом портфеля з часом для 10 найбільших активів у кожній стратегії показує, що середнє співвідношення до одного акції становить приблизно 0.004, 0.035 і 0.0125 відповідно. Це свідчить про відсутність ризику надмірної концентрації в окремих акціях. Крім того, користувачі можуть налаштувати кількість активів відповідно до своїх уподобань для проведення подальших тестувань.

Стратегії 1

pic

Стратегії 2

pic

Стратегії 3

pic

Нарешті, щодо максимального просідання (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--------------------------------)

Джерело коду

Розширене читання

Корисні посилання

Перекладено з: 【Application】TQuant Lab Director And Supervisor Holdings — Which Strategy Reigns Supreme?

Leave a Reply

Your email address will not be published. Required fields are marked *