ساخت ربات معامله گر رمزارز با استفاده از اسیلاتور استوکستیک – پیاده سازی در پایتون


در مطالب گذشته مجله فرادرس، به ساخت ربات معاملهگر با استفاده از میانگین متحرک ساده (Simple Moving Average | SMA) و تقسیم مجموعه داده پرداختیم. در این مطلب، قصد داریم یک ربات معاملهگر با استفاده از اسیلاتور استوکستیک (Stochastic Oscillator) میپردازیم که یک اندیکاتور (Indicator) نوسانگر است.
اسیلاتور استوکستیک
این اندیکاتور موقعیت قیمت فعلی نسبت به بیشترین و کمترین قیمت مشاهده شده در L دوره گذشته را نشان میدهد. برای محاسبه خط K خواهیم داشت:
خط K همواره عددی بین 0 و 100 است. اعداد بین 0 تا 30 نشاندهنده بیشفروش (Oversold) است و انتظار صعود قیمت را در آینده داریم. اعداد بین 70 تا 100 نیز نشاندهنده بیشخرید (Overbought) است و انتظار نزول قیمت را در آینده داریم.
سپس، یک خط D به صورت میانگین متحرک ساده 3 روزه از روی K محاسبه میشود:
به خط K استوکستیک سریع (Fast Stochastic) و به خط D استوکستیک آرام (Slow Stochastic) گفته میشود.
تقاطع خط K با خط D به سمت بالا، سیگنال برای خرید میباشد و برعکس آن، تقاطع خط K با خط D به سمت پایین، سیگنال برای فروش است.
به این ترتیب، اختلاف خط K و خط D میتواند معیار مناسبی به عنوان سیگنال باشد:
برای پیادهسازی ربات، وارد محیط برنامهنویسی پایتون میشویم و کتابخانههای مورد نیاز را فراخوانی میکنیم:
این کتابخانهها در کدنویسی به ترتیب برای موارد زیر استفاده خواهند شد:
- محاسبات برداری (Vectorized Calculation) و استفاده از آرایهها (Array)
- کار با دیتافریمها (Dataframe)
- دریافت آنلاین (Online) مجموعه داده مربوط به تاریخچه قیمتی (Historical Price) نمادها
- رسم نمودار قیمت، سیگنال و نقاط خرید و فروش ربات
حال تنظیمات مربوط به Randomness و Style را اعمال میکنیم:
پیادهسازی کلاس
یک کلاس مربوط به ربات ایجاد میکنیم:
این کلاس شامل 8 متد (Method) خواهد بود.
پیادهسازی متد سازنده
با توجه به اینکه طول پنجره محاسبه خط K به صورت دقیق معلوم نیست، باید بهینهسازی شود. به همین دلیل، حد بالا و پایین طول پنجره خط K، به همراه طول میانگین متحرک مربوط به خط D و در نهایت نسبت اندازه مجموعه داده آموزش (Train Dataset) به کل مجموعه داده در ورودی متن سازنده دریافت خواهند شد:
حال موارد دریافتشده را در شی (Object) که با نام self میشناسیم، ذخیره میکنیم:
به این ترتیب، کد این متد کامل میشود.
پیادهسازی متد دریافت داده
این متد در ورودی اسم نماد، تاریخ شروع داده و تاریخ اتمام داده را دریافت میکند:
حال ورودیهای دریافتشده را ذخیره و سپس مجموعه داده را با استفاده از کتابخانه Pandas Datareader دریافت میکنیم:
دو ستون Volume و Adj Close مورد نیاز نبوده و آنها را حذف میکنیم:
به این ترتیب، مجموعه داده به راحتی دریافت و در شی ذخیره میشود.
پیادهسازی متد پردازش داده
این متد عملیاتی روی مجموعه داده خام (Raw Dataset) انجام میدهد و آن را به شکل قابل استفاده درمیآورد. با توجه به اینکه نیاز داریم تا تمامی Lهای بین$$L_\min$$
حال دو ستون LL و HH را با استفاده از متدهای rolling, min, max محاسبه میکنیم:
حال میتوانیم خط K، خط D و سیگنال نهایی را محاسبه کنیم:
سپس ستونهای اضافی را حذف میکنیم:
به این ترتیب، برای هر L خط سیگنال محاسبه و به دیتافریم افزوده میشود.
حال نیاز داریم تا قیمت اولین فرصت خرید در روز مربوط را محاسبه کنیم:
با توجه به اینکه برخی ستونها برای برخی سطرها از مجموعه داده، مقدار Nan یا Not a Number به خود میگیرند، باید آنها را حذف کنیم. بنابراین، خواهیم داشت:
حال میتوانیم اندازه نهایی مجموعه داده را محاسبه و سپس مجموعه داده را به دو قسمت آموزش (Train) و آزمایش (Test) تقسیم میکنیم:
به این ترتیب، این تابع سیگنالهای مورد نیاز را محاسبه، به دیتافریم اضافه و در نهایت مجموعه داده آموزش و آزمایش را ایجاد میکند.
پیادهسازی متد معامله
این متد در ورودی دیتافریم مربوط به مجموعه داده و طول پنجره اندیکاتور را دریافت میکند:
سپس، اندازه دیتافریم ورودی را محاسبه و آرایههای مورد نیاز برای ذخیره تاریخچه را ایجاد میکنیم:
حال، سرمایه اولیه و سهام اولیه را تعیین میکنیم:
سپس، به ازای هر روز از مجموعه داده، قیمت اولین فرصت خرید و سیگنال روز مربوطه را محاسبه میکنیم:
حال میتوانیم فرایند تصمیمگیری ربات را پیادهسازی کنیم. تصمیمگیری ربات در شرایط مختلف به شکل زیر خواهد بود:
Signal<0 | Signal=0 | Signal>0 | |
Sell | Hold | Hold | Share>0 |
Hold | Hold | Buy | Share=0 |
به این ترتیب، با پیادهسازی دو شرط منتهی به خرید و فروش، روند کامل خواهد بود:
حال میتوانیم تاریخچه و میانگین درصد سود روزانه را محاسبه و در خروجی متد برگردانیم:
به این ترتیب، این متد کامل میشود.
پیادهسازی متد آموزش ربات
فرایند آموزش مدل، شامل تعیین بهترین مقدار L برای ربات است. طی این فرایند، به ازای هر L عملیات مربوط به معامله در مجموعه داده آموزش انجام و میانگین درصد سود روزانه ذخیره میشود.
ابتدا تمامی مقادیر L را محاسبه میکنیم و یک لیست خالی برای ذخیره میانگین درصد سود روزانه ایجاد میکنیم:
حال با استفاده از یک حلقه، به ازای هر L تابع Trade فراخوانی و میانگین درصد سود روزانه به لیست Rs اضافه میکنیم:
پس از اتمام حلقه، لیست Rs را به آرایه Numpy تبدیل میکنیم، سپس بیشترین میانگین درصد سود حاصل و بهترین L را ذخیره میکنیم و خروجی را نمایش میدهیم:
به این ترتیب، این متد بهترین L را برای ربات انتخاب و در شی ذخیره میکند.
تا به اینجا، 5 متد اصلی و مهم کلاس پیادهسازی شد. 3 متد بعدی مربوط به مصورسازی (Visualization) ربات هستند.
پیادهسازی متد رسم نمودار Return-L
این نمودار رابطه بین میانگین درصد سود روزانه با طول پنجره خط K را نشان میدهد. برای رسم این نمودار از دو آرایه Ls و Rs استفاده میکنیم:
به این ترتیب، نمودار رسم شده و بهترین حالت مشخص میشود. این نمودار تنها با توجه به مجوعه داده آموزش رسم شده است.
پیادهسازی متد رسم نمودار Price-Time و Value-Time
این متد در ورودی مجموعه داده مورد نظر را دریافت خواهد کرد و سپس متد Trade روی آن اجرا خواهد شد:
حال میتوانیم با استفاده از subplot در یک نمودار قیمت و میانگین سود حاصل را رسم کنیم. در نمودار دیگر نیز عملکرد ربات را نمایش میدهیم:
به این ترتیب، با استفاده از این متد میتوانیم برای مجموعه داده آموزش و آزمایش عملکرد ربات را در کنار عملکرد نماد رسم کنیم.
پیادهسازی متد رسم سیگنال
این متد، نمودار قیمت، نقاط ورود و خروج از نماد را به همراه نمودار سیگنال در زیر آن رسم میکند:
به این ترتیب، هر 8 متد مورد نیاز برای کلاس stcBot پیادهسازی شد. حال میتوانیم از کلاس ایجادشده استفاده کنیم.
استفاده از کلاس
حال یک شی از کلاس ایجاد میکنیم:
سپس، مجموعه داده را دریافت میکنیم و پردازشهای مورد نیاز را انجام میدهیم:
سپس، مدل را آموزش میدهیم:
که پس از اتمام آن، نتیجه به شکل زیر برگردانده میشود:
به این ترتیب، مشاهده میکنیم که مقدار L=80 به عنوان بهترین طول پنجره استوکستیک سریع انتخاب شده است. در نتیجه استفاده از این طول پنجره، میانگین درصد سود روزانه برابر با 0.2914 % حاصل شده که مناسب است. باید توجه داشته که این سود تنها نشاندهنده عملکرد روی مجموعه داده آموزش است.
حال میتوانیم نمودار Return-L را رسم کنیم:
که شکل زیر را خواهیم داشت.

به این ترتیب، مشاهده میکنیم که این استراتژی به ازای تمامی Lها سودده است. همچنین، کمترین و بیشترین میانگین درصد سود روزانه به ترتیب مربوط به L=2 و L=80 است. توجه داشته باشید که L=41 نیز اختلاف ناچیزی با L=80 دارد. نکته مهم دیگر که باید به آن توجه کرد، اهمیت Ld است. طول میانگین متحرک ساده مربوط به خط D در سیگنال حاصل اثرگذار است، به همین دلیل با تغییر Ld نمودار فوق نیز تغییر خواهد کرد.
حال میتوانیم نمودار مربوط به قیمت در مقابل ارزش پرتفوی را نیز رسم کنیم:
که دو نمودار حاصل خواهد شد. نمودار اول بهصورت زیر است.

نمودار بعدی بهشکل زیر است.

به این ترتیب، مشاهده میکنیم که ربات روی مجموعه داده آموزش به میانگین درصد سود روزانه 0.2915 % رسیده که نسبت به رسد خود نماد اندکی بهتر است. روی مجموعه داده آزمایش نیز ربات به میانگین درصد سود روزانه 0.1201 % رسیده است که شاید در نگاه اول مناسب نباشد، اما با در نظر گرفتن عملکرد نماد در مجموعه داده آزمایش، میتوان به این نتیجه رسید که ربات با توجه به شرایط موجود، عملکرد مناسب خود را حفظ کرده است. اگر نسبت میانگین درصد سود روزانه ربات با به میانگین درصد سود نماد حساب کنیم، خواهیم داشت:
به این ترتیب، عملکرد مثبت ربات قابل مشاهده است.
حال، آخرین نمودار که برای سیگنال و نقاط خرید و فروش هست را رسم میکنیم:
که در نتیجه آن، شکلهای زیر را خواهیم داشت. نمودار اول به صورت زیر است.

نمودار دوم نیز در ادامه آورده شده است.

با توجه به استفاده از روش سیگنالگیری به کمک میانگین متحرک، سیگنالهای فراوانی از اندیکاتور دریافت میشود که در نتیجه آن معاملات با فرکانس بالاتری انجام میشوند.
نکته مهم دیگری که باید به آن پرداخت، نرخ بُرد (Win Rate) است. این معیار نشاندهنده نسبت تعداد معاملات برنده به کل معاملات است. برای محاسبه این معیار میتوانیم یک تابع ایجاد کنیم که در ورودی دیکشنری مربوط به نقاط خرید و نقاط فروش را دریافت میکند:
حال، یک متغیر برای ذخیره تعداد معاملات و تعداد معاملات موفق ایجاد میکنیم:
اکنون یک حلقه ایجاد میکنیم و تعداد معاملات را بهروز (Update) میکنیم:
در نهایت نیز نسبت را محاسبه میکنیم و برمیگردانیم:
بدین صورت، این تابع کامل میشود. این تابع را در انتهای متد Trade استفاده میکنیم:
به این ترتیب، این متد در هر بار اجرا، نرخ بُرد را نیز برخواهد گرداند. توجه داشته باشید که به دلیل تغییر در خروجیهای متد Trade باید در مواردی که این متد فراخوانی شده، اصلاحاتی انجام شود تا شاهد بروز خطا در کد نباشم.
اکنون برای دریافت نرخ بُرد میتوانیم بنویسیم:
پس از اجرا خواهیم داشت:
Train Win Rate: 47.66 % Test Win Rate: 37.25 %
به این ترتیب، مشاهده میکنیم که نرخ بُرد معاملات کم است و در مجموعه داده آزمایش کمتر نیز شده که به دلیل ضعیف شدن روند صعودی است. دلیل اصلی کم بودن نرخ بُرد، فرکانس بالای معاملات است. با اینکه ربات سود خوبی از معاملات گرفته است، ولی اغلب معاملات با ضرر بسته شدهاند. بنابراین، میتوان با راحتی متوجه شد که اغلب معاملاتی که با شکست روبهرو شدهاند، ضررهای کوچکی داشتهاند.
میتوان تنظیم L را با استفاده از نرخ بُرد نیز انجام داد، اما باید توجه داشته که ممکن است به اندازه میانگین درصد سود روزانه کاربردی نباشد.
جمعبندی
در این مطلب توانستیم یک ربات معاملهگر بر پایه اسیلاتور استوکستیک ایجاد کنیم و نتایج آن را به شکل نمودار و اعداد نشان دهیم. برای مطالعه بیشتر در این باره، میتوان موارد زیر را بررسی کرد:
- اندیکاتور استوکستیک RSI (Stochastic RSI) چیست و چه مزایایی دارد؟
- چگونه از انجام معاملات فراوان توسط ربات جلوگیری کنیم؟
- چرا پیادهسازی متدهای رسم نمودار به شکل متد، میتواند بهتر از پیادهسازی آنها به شکل تابع باشد؟
- کد ربات را بهگونهای تغییر دهید که علاوه بر بهینهسازی، مقدار را نیز بهینه کند.
- تابع بهینهساز Brute را از کتابخانه Scipy مطالعه کرده و شباهت آن به فرایند پیادهسازی شده در برنامه را بیابید.