در عرصه پرتلاطم سرمایهگذاری، مدیریت پرتفوی و کاهش ریسک همواره از اهمیت بالایی برخوردار بودهاند. این پروژه، تلاشی است برای ارائه یک راهحل جامع و علمی جهت بهینهسازی پرتفوی سرمایهگذاری با استفاده از روش نوین Hierarchical Risk Parity (HRP). در این روایت، نه تنها تلاشهای دقیق برای پیادهسازی این راهکارها را به اشتراک خواهم گذاشت، بلکه جزئیات فنی و تصمیمات کلیدی مرتبط با فرآیند توسعه را نیز تشریح خواهم کرد.
بهینهسازی پرتفوی از مسائل اصلی فعالان مالی و سرمایهگذاران بوده است. هدف اصلی، ایجاد توازن بین بازده و ریسک است؛ توازنی که با توجه به شرایط متغیر بازار، بیشترین بازده را در سطح قابل قبولی از ریسک به ارمغان آورد. این پروژه در جستجوی پاسخی علمی برای این نیاز است؛ پاسخی که از تکنیکهای نوین و مبتنی بر داده استفاده میکند.
اولین گام در این فرآیند، انتخاب ترکیبهایی از نمادهای موجود برای تحلیل بود. با استفاده از روشهای برنامهنویسی و کتابخانههایی مانند itertools، تمامی ترکیبهای ممکن از نمادها تولید شد. این رویکرد به ما امکان داد تمامی حالات ممکن را به دقت بررسی کنیم:
def generate_combinations(symbol_ids: list, portfolio_size: int = 10) -> list:
return list(itertools.combinations(symbol_ids, portfolio_size))هرچند این رویکرد از نظر تئوری کامل بود، اما به دلیل تعداد زیاد ترکیبها، نیاز به ایجاد محدودیتهایی در تعداد نمونهها احساس شد.
به دلیل منابع محدود محاسباتی، تعداد ترکیبهای انتخابشده برای تحلیل، به کمک روشهای نمونهگیری تصادفی به یک مقدار قابل مدیریت محدود شد. این رویکرد ضمن حفظ دقت، کارایی سیستم را نیز افزایش داد:
desired_samples = 100000 # تعداد نمونههای مورد نظر
if total_combinations > desired_samples:
sampled_combinations = random.sample(portfolio_combinations, desired_samples)
else:
sampled_combinations = portfolio_combinationsبرای شروع، نیاز به دادههایی از قیمتهای تاریخی نمادها بود. این دادهها به کمک کلاسی با عنوان DataFetcher و از طریق APIهای موجود جمعآوری شدند. این کلاس مسئولیت داشت که دادهها را پاکسازی و استانداردسازی کند تا برای تحلیلهای بعدی آماده شوند:
class DataFetcher:
"""کلاسی برای دریافت و پردازش دادهها از API."""- دریافت دادههای تاریخی: این دادهها بهصورت روزانه برای هر نماد جمعآوری شدند.
- تبدیل تاریخها: تاریخهای جلالی به میلادی تبدیل شدند تا سازگاری دادهها تضمین شود.
- پاکسازی دادهها: دادههای نامعتبر حذف شدند.
- بازگشت دادهها: خروجی، یک
DataFrameاستاندارد و آماده برای استفاده در تحلیلها بود.
برای مدیریت همزمان دادههای چندین نماد، از رویکرد پردازش موازی استفاده شد. این روش امکان بارگذاری سریعتر و کارآمدتر دادهها را فراهم کرد:
@staticmethod
async def fetch_all_symbols(symbol_ids: list) -> dict:
tasks = [DataFetcher.fetch_daily_history(symbol_id) for symbol_id in symbol_ids]
results = await asyncio.gather(*tasks)
return dict(zip(symbol_ids, results))در این مرحله، دادههای خام جمعآوریشده از منابع مختلف باید به اطلاعاتی تبدیل شوند که برای تحلیلهای پیشرفته مناسب باشند. این فرآیند شامل مراحل کلیدی مانند محاسبه بازدهها، پاکسازی دادهها و همترازی مجموعههای مختلف است. در این بخش، کلاسهای اصلی برای مدیریت و آمادهسازی دادهها معرفی میشوند.
این کلاس مسئول اصلی پیشپردازش دادهها است. هدف آن، استانداردسازی دادههای ورودی و آمادهسازی مجموعههای آموزشی و تست است. پیشپردازش دقیق، بنیان تحلیلهای موفق در مراحل بعدی را شکل میدهد.
برای تحلیلهای مالی، بازدههای روزانه نمادها یکی از ورودیهای کلیدی هستند. کلاس Preprocessor از یک متد برای محاسبه بازدههای درصدی استفاده میکند:
@staticmethod
def calculate_returns(prices: pd.DataFrame) -> pd.DataFrame:
"""محاسبه بازدههای روزانه از دادههای قیمت."""
returns = prices.pct_change().dropna()
return returns- محاسبه بازدههای درصدی: با استفاده از
pct_changeبازدههای روزانه بهدست آمد. - حذف دادههای نامعتبر: مقادیر خالی حذف شدند تا دقت محاسبات تضمین شود.
با توجه به اینکه دادههای ورودی از منابع مختلف ممکن است دارای بازههای زمانی متفاوت باشند، لازم است که همه مجموعهها بر اساس تاریخهای مشترک همتراز شوند. این کار به کمک متدی اختصاصی انجام میشود:
@staticmethod
def align_datasets(*datasets: pd.DataFrame) -> pd.DataFrame:
"""همترازی مجموعههای داده بر اساس تاریخهای مشترک."""
common_dates = set.intersection(*(set(ds.index) for ds in datasets))
aligned_datasets = [ds.loc[common_dates].sort_index() for ds in datasets]
return pd.concat(aligned_datasets, axis=1).dropna()- یافتن تاریخهای مشترک: تاریخهای موجود در تمامی مجموعههای داده مشخص شدند.
- استخراج دادههای همتراز: دادههای موجود در تاریخهای مشترک استخراج شدند.
- ترکیب دادهها: مجموعههای داده در یک ساختار یکپارچه ترکیب و دادههای نامعتبر حذف شدند.
برای آموزش و ارزیابی مدلها، دادهها باید به دو بخش مجزا تقسیم شوند. متد زیر این وظیفه را بر عهده دارد:
@staticmethod
def process_data(prices: pd.DataFrame, market_returns: pd.Series, risk_free_rate: pd.Series,
market_cap: pd.Series, usd_to_rial: pd.Series) -> tuple:
"""پردازش دادهها و آمادهسازی مجموعههای آموزشی و تست."""
returns = Preprocessor.calculate_returns(prices)
aligned_data = Preprocessor.align_datasets(returns, market_returns, risk_free_rate, market_cap, usd_to_rial)
X = aligned_data.iloc[:, :-3]
y = aligned_data.iloc[:, -3:]
return train_test_split(X, y, test_size=0.33, random_state=42)- محاسبه بازدهها: بازدههای روزانه نمادها به کمک متد
calculate_returnsمحاسبه شدند. - همترازی دادهها: تمامی مجموعههای داده بر اساس تاریخهای مشترک همتراز شدند.
- تعریف ویژگیها و متغیر هدف:
- ویژگیها (X): بازدههای نمادها.
- متغیر هدف (y): بازدههای اضافی بازار و سایر متغیرهای اقتصادی.
- تقسیم دادهها: دادهها به دو بخش آموزشی و تست تقسیم شدند.
اکنون که دادههای ما آماده شدهاند، زمان آن است که مدلهای بهینهسازی پیشرفتهای مانند Hierarchical Risk Parity (HRP) و Conditional Value at Risk (CVaR) را معرفی کرده و به کار ببریم. این مدلها با هدف توزیع بهینه ریسک و کاهش خطرات سیستماتیک، برای ایجاد پرتفوهایی با بازدهی مطلوب طراحی شدهاند.
کلاس OptimizerModel ابزاری جامع برای تعریف، آموزش و پیشبینی مدلهای بهینهسازی است. این کلاس به شکلی طراحی شده که بتواند به طور پویا از مدلهای مختلفی مانند HRP یا CVaR پشتیبانی کند.
در ابتدای تعریف هر نمونه از این کلاس، مشخصات مدل مانند نوع بهینهسازی و نام آن تعیین میشود:
class OptimizerModel:
def __init__(self, optimizer, name="Optimizer-Model"):
self.name = name
self.optimizer = optimizerاین متد، مدل را بر روی دادههای آموزشی برازش میدهد. برای مدلهایی مانند HRP، فرآیند خوشهبندی سلسلهمراتبی و محاسبه ماتریس همبستگی در این مرحله انجام میشود:
def fit(self, X_train: pd.DataFrame, y_train: pd.DataFrame = None):
if "HRP" in self.name:
correlation_matrix = X_train.corr(method='pearson')
distance_matrix = 1 - correlation_matrix
self.optimizer.fit(distance_matrix)
else:
self.optimizer.fit(X_train, y_train)- محاسبه ماتریس همبستگی: برای مدل HRP، ماتریس همبستگی بین نمادها محاسبه میشود.
- محاسبه ماتریس فاصله: ماتریس فاصله از طریق تبدیل همبستگیها محاسبه میشود.
- خوشهبندی سلسلهمراتبی: خوشهبندی سلسلهمراتبی بر اساس ماتریس فاصله اجرا میشود.
- برازش مدل: مدلهای دیگر مانند CVaR از دادههای بازده و متغیرهای هدف برای برازش استفاده میکنند.
پس از برازش مدل، وزنهای پرتفو با استفاده از دادههای تست پیشبینی میشوند. این وزنها نشاندهنده سهم بهینه هر نماد در پرتفو هستند:
def predict(self, X: pd.DataFrame):
return self.optimizer.predict(X)- وزنهای پرتفو متناسب با معیارهای ریسک و بازده محاسبه میشوند.
- وزنها به صورت یک
Seriesبازگردانده میشوند که شاخصهای آن نمادها هستند.
برای تعریف مدلهای HRP، از ترکیبی از روشهای لینکج و تخمینگرهای فاصله استفاده میشود. هر ترکیب، رویکردی متفاوت برای خوشهبندی و توزیع ریسک ارائه میدهد:
linkage_methods = [LinkageMethod.SINGLE, LinkageMethod.COMPLETE, LinkageMethod.AVERAGE, LinkageMethod.WARD]
distance_estimators = [(None, "Pearson"), (KendallDistance(absolute=True), "Kendall")]
for linkage in linkage_methods:
for distance_estimator, distance_name in distance_estimators:
model_name = f"HRP-{linkage.value.capitalize()}-{distance_name}"
optimizer = HierarchicalRiskParity(
risk_measure=RiskMeasure.CVAR,
distance_estimator=distance_estimator,
hierarchical_clustering_estimator=HierarchicalClustering(linkage_method=linkage)
)
model = OptimizerModel(optimizer=optimizer, name=model_name)
models.append(model)- تعریف روشهای لینکج: شامل روشهایی مانند
SINGLE,COMPLETE,AVERAGE, وWARD. - تعریف تخمینگرهای فاصله: شامل
PearsonوKendall. - ایجاد مدل HRP برای هر ترکیب: هر مدل با یک روش لینکج و یک تخمینگر فاصله مشخص تعریف میشود.
این مدل بهعنوان یک رویکرد پیشرفتهتر، از ترکیبی از معیارهای ریسک و خوشهبندی بهره میبرد. در اینجا، از تخمینگر Factor Model برای ارزیابی دادههای پرتفو استفاده شده است:
optimizer4 = DistributionallyRobustCVaR(
risk_aversion=1.0,
cvar_beta=0.95,
wasserstein_ball_radius=0.02,
prior_estimator=FactorModel(),
min_weights=0.0,
max_weights=1.0,
budget=1.0
)
model4 = OptimizerModel(optimizer=optimizer4, name="DistributionallyRobustCVaR-Factor-Model")
models.append(model4)- از معیارهای ریسک CVaR و رویکرد توزیعمحور استفاده میکند.
- با استفاده از
FactorModel، اولویتها در خوشهبندی مشخص میشوند.
پس از تعریف و برازش مدلها، نوبت به ارزیابی عملکرد آنها میرسد. این مرحله شامل پیشبینی وزنهای پرتفو، تحلیل مشارکت ریسک، و بررسی متریکهای عملکردی مانند Sharpe Ratio و Sortino Ratio است. ارزیابی دقیق به ما کمک میکند تا میزان کارایی مدلهای بهینهسازی را بسنجیم و پرتفوهای برتر را شناسایی کنیم.
هدف از این تحلیل، شناسایی سهم هر نماد در کل ریسک پرتفو است. با استفاده از معیارهایی مانند CVaR یا Variance، میتوان مشخص کرد که کدام نمادها بیشترین سهم از ریسک پرتفو را به خود اختصاص دادهاند.
برای هر مدل، پس از پیشبینی وزنهای پرتفو، سهم ریسک هر نماد محاسبه میشود:
portfolio = model.predict(X_train)
if portfolio is not None:
evaluator.analyze_risk_contribution(portfolio, measure=RiskMeasure.CVAR)- پیشبینی وزنها: با استفاده از دادههای آموزشی، وزنهای پرتفو برای هر مدل محاسبه میشوند.
- محاسبه مشارکت ریسک: سهم هر نماد از کل ریسک پرتفو، متناسب با وزن و معیار انتخابشده (مانند CVaR)، تعیین میشود.
- ترسیم نمودار مشارکت ریسک: نتایج بهصورت بصری نمایش داده میشوند تا تحلیل دقیقتری انجام شود.
پس از برازش مدلها، وزنهای پرتفو با استفاده از دادههای تست پیشبینی میشوند. این وزنها مشخص میکنند که چه میزان از سرمایه باید به هر نماد اختصاص یابد.
portfolio = model.predict(X_test)
if portfolio is not None:
logger.info(f"Weights for model {model.name}: {portfolio.weights}")- وزنهای پیشبینیشده با توجه به دادههای تست محاسبه میشوند.
- نتایج به صورت وزنهای درصدی ارائه میشوند و مجموع وزنها برابر با ۱ خواهد بود.
متریکهای عملکردی به ما کمک میکنند تا میزان موفقیت هر مدل در بهینهسازی پرتفو را بسنجیم. در اینجا از متریکهایی مانند Sharpe Ratio, Sortino Ratio, VaR و CVaR استفاده شده است.
metrics = Evaluator.calculate_performance_metrics(portfolio_returns, risk_free_rate=avg_risk_free_rate)- محاسبه بازده اضافی: بازدههای پرتفو نسبت به نرخ بدون ریسک محاسبه میشوند.
- Sharpe Ratio: نسبت بازده اضافی به نوسانات کلی پرتفو.
- Sortino Ratio: نسبت بازده اضافی به نوسانات نزولی پرتفو.
- VaR و CVaR: حد ضرر و انتظار ضرر در شرایطی که VaR نقض شده است.
یکی از بهترین روشها برای ارزیابی عملکرد پرتفو، بررسی بازدههای تجمعی در طول زمان است. این بازدهها به ما نشان میدهند که پرتفو در مجموع چه میزان بازدهی ایجاد کرده است.
evaluator.plot_cumulative_returns(population_test)ترکیب پرتفو نیز میتواند اطلاعات مفیدی درباره تنوع و توزیع سرمایهگذاری ارائه دهد:
evaluator.plot_composition(population_test)پس از محاسبه متریکهای عملکردی، پرتفوها بر اساس معیارهایی مانند Sharpe Ratio رتبهبندی میشوند.
ranking_df = summary.loc["Annualized Sharpe Ratio"].sort_values(ascending=False).head(5)- پرتفوها بر اساس معیار انتخابشده مرتب میشوند.
- پنج پرتفو برتر بهصورت جداگانه مشخص و تحلیل میشوند.
برای دسترسی آسانتر، نتایج بهصورت فایلهای CSV و Excel ذخیره میشوند:
weights_df.to_csv('Optimized_Portfolio_Weights.csv', index=False, encoding='utf-8-sig')
weights_df.to_excel('Optimized_Portfolio_Weights.xlsx', index=False, engine='openpyxl')ابزارهایی مانند نمودار دندروگرام و ماتریس همبستگی، دریچهای جدید به درک عمیقتر روابط میان داراییها میگشایند. این ابزارها، نقشهای روشن از نحوه خوشهبندی داراییها و میزان ارتباط میان آنها ارائه میدهند و به سرمایهگذاران کمک میکنند پرتفوهایی بهینهتر و کمریسکتر طراحی کنند. به زبان سادهتر، با کمک این تحلیلها، میتوان میان انبوهی از داراییها، روابط پنهان را شناسایی کرد و تصمیمهایی دقیقتر و هوشمندانهتر اتخاذ نمود. قصد دارم شما را با جنبههای مختلف این تحلیل آشنا کنم.
دندروگرام، نموداری است که با ساختار سلسلهمراتبی خود، داراییها را بر اساس میزان شباهت یا همبستگی خوشهبندی میکند. در اینجا، هر خوشه مانند شاخهای از یک درخت است که گروهی از داراییهای مرتبط را در خود جای میدهد. در کنار آن، ماتریس همبستگی نقش تکمیلکنندهای دارد و با رنگآمیزی دقیق، شدت ارتباط میان داراییها را نشان میدهد. هر مربع در این ماتریس، داستانی از همبستگی را روایت میکند؛ مربعهای روشنتر از ارتباط قویتر و مربعهای تیرهتر از ارتباط ضعیفتر حکایت دارند. این ابزارها به ما امکان میدهند که ریسک را بهتر توزیع کنیم و داراییهایی با کمترین ارتباط را برای کاهش خطرات در پرتفو انتخاب نماییم.
نکته جذابتر، نحوه تغییرات خوشهبندی در تصاویر مختلف است. در برخی نمودارها، خوشهها کوچکتر و دقیقترند، گویی ذرهبینی بر جزئیات ارتباط داراییها نهاده شده است. در دیگر تصاویر، خوشهها بزرگتر و فراگیرترند، که برای مدیریت کلیتر پرتفو بسیار کارآمد هستند. مربعهای زرد در ماتریس همبستگی، مرزهایی واضح میان این خوشهها ترسیم میکنند و نشان میدهند که کدام داراییها رفتار مشابهی دارند. این تحلیلها، بهویژه برای سرمایهگذاران حرفهای، ابزاری بیبدیل به شمار میآیند که میتوانند بر اساس آن، استراتژیهای متنوعتری طراحی کرده و پرتفوهای خود را از تهدیدهای ناگهانی مصون نگه دارند.
دندروگرام و ماتریس همبستگی، زبان پنهان داراییها را آشکار میسازند و به سرمایهگذاران اجازه میدهند تا با تکیه بر دادههای دقیق، تصمیماتی مبتنی بر عقلانیت و مدیریت ریسک اتخاذ کنند. این ابزارها، نقشهای برای یافتن مسیرهای کمخطرتر در میان نوسانات بازار هستند و راهحلی برای ایجاد تعادل میان تنوع داراییها و کاهش خطرات به شمار میآیند. سرمایهگذارانی که این زبان را میفهمند، در میدان پرچالش اقتصاد، گامی مطمئنتر برمیدارند.
اکنون که تمامی مراحل از جمعآوری دادهها تا تعریف، برازش، ارزیابی و تحلیل مدلها انجام شده است، میتوان تصویری جامع از این پروژه ارائه داد. هدف ما از این مسیر، طراحی و پیادهسازی ابزارهایی برای بهینهسازی پرتفو بود که بتوانند در شرایط متغیر بازار، عملکردی قابلاعتماد و مطمئن ارائه دهند.
-
پیادهسازی دقیق و جامع روشهای نوین بهینهسازی:
- استفاده از روشهای پیشرفتهای همچون Hierarchical Risk Parity (HRP) و Conditional Value at Risk (CVaR)، امکان توزیع ریسک بهینه و مدیریت آن را فراهم کرد.
- مدلهای مبتنی بر خوشهبندی سلسلهمراتبی و اولویتبندی فاکتوری توانستند وزنهای دقیقی برای پرتفوها ارائه دهند.
-
تحلیل مشارکت ریسک و ارزیابی متریکهای عملکردی:
- محاسبه متریکهایی مانند Sharpe Ratio, Sortino Ratio, و CVaR، بینشی عمیق درباره عملکرد هر مدل ارائه داد.
- تحلیل مشارکت ریسک نشان داد که کدام نمادها سهم بیشتری در ریسک پرتفو دارند و چگونه میتوان این سهمها را بهینه کرد.
-
رشد بازدهی و کاهش نوسانات:
- پرتفوهای پیشنهادی توانستند بازدهی بالاتری نسبت به معیارهای مرجع ارائه دهند، در حالی که سطح ریسک در محدودهای معقول نگه داشته شد.
-
کارایی بالا با پردازش موازی:
- استفاده از پردازش موازی و غیرهمزمان، زمان محاسبات را بهطور چشمگیری کاهش داد و کارایی سیستم را بهبود بخشید.
-
حجم زیاد ترکیبها:
- چالش اصلی در این پروژه، حجم بالای ترکیبهای ممکن از نمادها بود. با اعمال نمونهگیری تصادفی، این چالش مدیریت شد و امکان تحلیل ترکیبهای متنوع فراهم شد.
-
کیفیت دادههای ورودی:
- در برخی موارد، دادههای ناقص یا نامعتبر وجود داشتند. با اعمال پیشپردازش دقیق، این دادهها پاکسازی و استانداردسازی شدند.
-
تنظیم دقیق مدلها:
- مدلهایی مانند Distributionally Robust CVaR نیاز به پارامترهای دقیق داشتند. با تست و ارزیابی مکرر، تنظیمات بهینه برای هر مدل بهدست آمد.
این پروژه گامی مهم در راستای استفاده از تکنولوژیهای پیشرفته برای بهینهسازی پرتفوهای سرمایهگذاری بود. روشهای مورد استفاده نشان دادند که چگونه میتوان با ترکیب دادهها، الگوریتمهای پیشرفته و تحلیلهای دقیق، راهحلهایی علمی برای چالشهای بازار ارائه کرد.
در نهایت، ابزار طراحیشده در این پروژه نه تنها برای سرمایهگذاران حرفهای، بلکه برای مدیران پرتفو و پژوهشگران مالی نیز ارزشمند است و میتواند بهعنوان نقطه شروعی برای بهینهسازیهای پیشرفتهتر مورد استفاده قرار گیرد.
از توجه شما به این پروژه متشکرم. اگر سؤالی دارید یا به اطلاعات بیشتری نیاز دارید، خوشحال خواهم شد که به آنها پاسخ دهم.
دوستدار شما نوید رمضانی



