11 سپتامبر 2026 · بازتولیدپذیری

همان کد، همان داده‌ها، دو شارپ متفاوت: ممیزی عدم‌قطعیتی که بک‌تست شما لازم دارد

همان کد، همان داده‌ها، دو شارپ متفاوت: ممیزی عدم‌قطعیتی که بک‌تست شما لازم دارد

همان کامیت، همان فایل‌های parquet، همان دستگاه، دو اجرا با فاصله یک ساعت. شارپ 1.34 و شارپ 1.19. بازده کل 41.2% و 37.8%. تعداد معاملات 1,418 و 1,421. و دو منحنی سرمایه تا 11,205 کندل پیاپی، در تمام رقم‌های اعشاری چاپ‌شده، با هم یکی بودند و بعد از هم جدا شدند.

0.15اختلاف شارپ، ورودی‌های یکسان
3 / 1,418معاملات متفاوت
11,205کندل یکسان پیش از جدایی

سه عدد اول آزاردهنده‌اند. عدد آخر جالب است، چون می‌گوید مسئله خطای ممیز شناورِ نامحسوسی نیست که در سراسر اجرا پخش شده باشد. اتفاقی گسسته در یک کندل مشخص رخ داده و باقی ماجرا حاصل انباشته‌شدن اثر آن بوده است. این مطلب درباره پیدا کردن آن کندل است و این‌که مقدار 0.15 چه اثری بر هر پیمایش پارامتری‌ای دارد که تا به حال اجرا کرده‌اید.

استراتژی: مومنتوم مقطعی روی 30 پرپ USDⓈ-M با بیشترین حجم، بازمتوازن‌سازی هر 4 ساعت، خرید 5 نماد برتر بر اساس بازده 12 ساعته، فروش استقراضی 5 نماد انتهایی، 18 ماه سابقه، با احتساب کارمزد و فاندینگ برای هر سمت معامله.

پیدا کردن کندلی که اجراها در آن از هم جدا می‌شوند

اگر ارزش سرمایه را برای هر کندل با دقت کامل ثبت کنید، این کار حدود چهار دقیقه طول می‌کشد. هر دو اجرا را در یک CSV از (bar_index, equity, open_positions_hash) خروجی بگیرید، هر دو را بارگذاری کنید و نخستین اندیسی را پیدا کنید که در آن اختلاف دارند. این اسکریپت را قبلاً برای یک باگ دیگر نوشته بودیم؛ تنها به همین دلیل بود که یک صبح کامل را صرف این کار نکردم.

کندل 11,206، در 2025-03-14T08:00 UTC. ارزش سرمایه در کندل قبلی تا 13 رقم معنادار یکسان بود. در 11,206 هش موقعیت‌ها فرق می‌کند: اجرای A در SOL خرید دارد و اجرای B در AVAX. جهت و ارزش اسمی یکسان، نماد متفاوت. هرچه بعد از آن می‌آید، پیامد همین تفاوت است.

پس ورودی‌های رتبه‌بندی همان کندل را در هر دو اجرا چاپ کردم. یکسان بودند. مو به مو یکسان؛ همان 30 نماد و همان 30 امتیاز. دو امتیاز 0.0 بودند. دقیقاً صفر؛ چون هر دو نماد در بازه بررسی کندلی 4 ساعته با صفر معامله داشتند، بنابراین نسبت قیمت پایانی به قیمت پایانی برابر یک شد و لگاریتم آن صفر درآمد. نه صفرشده بر اثر گرد کردن؛ صفر.

دو نماد در رتبه پنجم مساوی شدند. انتخاب، 5 نماد برتر را برمی‌داشت. این‌که کدام‌یک از آن دو انتخاب شود، به ترتیب باقی‌ماندنشان پس از مرتب‌سازی بستگی داشت و مرتب‌سازی برخلاف چیزی عمل کرد که تصور می‌کردم.

تساوی، مرتب‌سازی ناپایدار و چیزی که دو سال نادیده گرفته بودم

رتبه‌بندی با یک تجمیع گروهی انجام می‌شد و داده‌های ورودی آن از یک عبارت سازنده دیکشنری روی مجموعه‌ای از نمادها می‌آمد که در هر کندل از نو و با یک دریافت ناهمگام ساخته می‌شد. ترتیب پیمایش مجموعه با مقدار seed هش تغییر می‌کند و پایتون، مگر آن‌که PYTHONHASHSEED را تثبیت کنید، seed هش رشته‌ها را برای هر فرایند تصادفی می‌کند. بنابراین ترتیب ردیف‌ها پیش از مرتب‌سازی بین اجراها فرق داشت و چون مرتب‌سازی برای کلید مساوی پایدار نبود، تساوی به شکل متفاوتی شکسته شد.

تساوی‌هایی از این دست اتفاقی نادر نیستند؛ ساختاری‌اند. هرجا یک ویژگی به سقف برسد یا برش بخورد، برابری دقیق ایجاد می‌کنید: کندل‌های بی‌حجم بازده دقیقاً صفر می‌دهند، z-score بریده‌شده روی ±3.0 ثابت می‌شود، تبدیل رتبه‌ای با مقادیر متمایز اندک ده‌ها تساوی می‌سازد و یک فیلتر بولی به همه موارد پذیرفته‌شده امتیاز 1.0 می‌دهد. این اجرا طی 18 ماه با کندل‌های 4 ساعته، 47 کندل داشت که در مرز انتخاب تساوی رخ داده بود. در 3 مورد از آن‌ها سبد انتخابی عوض شد. در باقی موارد، دو نمادی مساوی شدند که هر دو از قبل داخل سبد بودند یا هر دو بیرون آن.

حدود یک روز مطمئن بودم بارگذار داده‌ها رفتار غیرقطعی دارد، چون این جواب هیجان‌انگیزتر بود. این‌طور نبود. هیچ‌وقت هم نیست. یک مجموعه، یک تساوی و فرضی درباره پایداری مرتب‌سازی که کسی جایی ثبتش نکرده بود.

چرا 3 معامله ارزش 0.15 شارپ دارند

این همان بخشی است که معمولاً با آن مخالفت می‌کنند. پاسخ این است که بک‌تستی با تعیین اندازه موقعیت بر اساس کسری از سرمایه، سامانه‌ای وابسته به مسیر است. وقتی برای هر سمت معامله 8% از سرمایه فعلی را به کار می‌گیرید، تفاوت ارزش سرمایه در کندل n یعنی تفاوت در تمام ارزش‌های اسمی از کندل n به بعد.

واگرایی اول به‌تنهایی هزینه چندانی نداشت. موقعیت AVAX در اجرای B طی 9 ساعت 2.1% زیان داد؛ موقعیت SOL در اجرای A، 0.4% سود کرد. اختلاف سرمایه پس از آن معامله: 0.21%. ناچیز. اما از آن‌جا به بعد، دو اجرا دیگر یک استراتژی واحد نیستند. اندازه موقعیت‌هایشان کمی فرق می‌کند، پس فاندینگ انباشته‌شده‌شان هم اندکی متفاوت است و دو تساوی مرزی بعدی نیز دوباره به شکل متفاوتی شکسته شدند، چون امتیازهای ورودی‌شان از موقعیت‌های بازِ کمی متفاوتی به دست می‌آمد. یکی از آن‌ها در 2025-03-27 رخ داد، یک روز پیش از روندی 6 روزه که حدود یک‌سوم PnL کل اجرا را ساخت. اجرای A در تمام طول حرکت وارد بازار بود؛ اجرای B با یک بازمتوازن‌سازی تأخیر وارد شد.

اختلاف بازده: 3.4 واحد درصد. اختلاف شارپ از چیزی که اختلاف بازده نشان می‌دهد بزرگ‌تر است، چون معاملات جابه‌جاشده اجرای B با دوره‌ای پرنوسان‌تر هم‌پوشانی داشتند؛ در نتیجه صورت کسر کاهش یافت و مخرج افزایش پیدا کرد. علت کوچک بود؛ دو عامل تشدیدکننده داشت.

اگر اندازه موقعیت‌تان بر اساس ارزش اسمی ثابت باشد و ورودهایتان به موقعیت‌های فعلی وابسته نباشند، تا حد زیادی از این اثر در امانید. بیشتر استراتژی‌های جالب هیچ‌کدام از این دو ویژگی را ندارند.

پنج جایی که این مشکل واقعاً رخ می‌دهد

منشأنشانهراهکار
مقدار تثبیت‌نشده PYTHONHASHSEED با ترتیب پیمایش مجموعه/دیکشنری که به مرتب‌سازی ورودی می‌دهدشکستن تساوی بین اجراها فرق می‌کند؛ واگرایی نخست در کندلی مشخص رخ می‌دهدمقدار seed را تثبیت کنید؛ مرتب‌سازی را با کلید ثانویه صریح (نماد) انجام دهید تا تساوی‌ها قطعی شکسته شوند
مرتب‌سازی ناپایدار بر اساس کلیدی با مقادیر مساوی (quicksort در NumPy/pandas پیش‌فرض است)همان نشانه بالا، حتی پس از تثبیت seed هم باقی می‌ماندkind="stable" یا کلید را یکتا کنید
مولد اعداد تصادفی بدون seed در بوت‌استرپ، جابه‌جایی داده‌های آموزش/آزمون یا نوسان مصنوعی قیمت اجرای سفارشانحراف در کل اجرا، بدون نقطه واگرایی مشخصبرای هر مؤلفه یک seed صریح تعیین کنید و در مانیفست اجرا ثبت کنید
کاهش موازی مقادیر اعشاری (ترتیب جمع وابسته به تعداد رشته‌های اجرایی)اختلاف در چند بیت کم‌ارزش، معمولاً بی‌ضرر است تا وقتی از آستانه یک مقایسه عبور کندتعداد رشته‌های اجرایی را برای پژوهش تثبیت کنید؛ هیچ‌وقت در مرز تصمیم، مقادیر اعشاری را با == مقایسه نکنید
نسخه‌های تثبیت‌نشده کتابخانه‌هاامروز بازتولیدپذیر است، اما در نوامبر نههش فایل قفل وابستگی‌ها را کنار هش snapshot داده‌ها در مانیفست ثبت کنید

ردیف چهارم آن‌قدرها که مردم می‌ترسند مهم نیست؛ ردیف اول مدام دردسرساز می‌شود.

بازتولیدپذیری بیت‌به‌بیت ابزار است، نه فضیلت

به قطعیت نیاز دارید تا وقتی یک خط را عوض می‌کنید، بتوانید تغییر منحنی سرمایه را به همان خط نسبت دهید. تمام دلیل همین است. حالا هر اجرای عامل در Stratmill مانیفستی با هش snapshot داده‌ها، هش فایل قفل وابستگی‌ها و تمام seedها می‌نویسد؛ اجرای مجددی که منحنی قبلی را بیت‌به‌بیت بازتولید نکند، یک بیلد ناموفق است، نه اتفاقی جالب.

اما وقتی توانستید نتیجه را بازتولید کنید، عمداً قطعیت را به هم بزنید. استراتژی را با 64 seed، 64 بار اجرا کنید و پراکندگی نتایج را ببینید:

بازه نوسان. همان استراتژی، همان داده‌ها، با 64 جایگشتِ seedدار برای شکستن تساوی و ترتیب اجرای سفارش‌ها. صدک 5 شارپ 1.12، میانه 1.27 و صدک 95 شارپ 1.41 است. پهنای بازه 0.29 است.

حالا به پیمایش پارامتری برگردید. بهترین پیکربندی امتیاز 1.46 گرفت. پیکربندی رتبه 40 از 96 امتیاز 1.31 گرفت. فاصله‌شان 0.15 است؛ نصف پهنای بازه. پیمایش این دو پیکربندی را رتبه‌بندی نکرد. از توزیع هرکدام یک نمونه گرفت و نمونه‌ها را مرتب کرد.

این بازنگری شیوه انتخاب ما را عوض کرد. نتیجه پیمایش فقط وقتی یک رتبه‌بندی است که فاصله بین پیکربندی‌ها از نوسان یک پیکربندی بیشتر باشد؛ در استراتژی وابسته به مسیر با 1,400 معامله، نوسان معمولاً آن‌قدر زیاد است که یک‌سوم بالای جدول را به تساوی می‌کشاند. وقتی این اتفاق افتاد، بر اساس معیاری انتخاب کنید که بازه نوسان نمی‌تواند پنهانش کند: گردش کمتر، پارامترهای کمتر، فرض هزینه‌ای که بتوانید در برابر یک منتقد از آن دفاع کنید، یا رفتار بهتر در بخشی از آزمون پیش‌رونده که کمترین علاقه را به آن دارید. این‌ها معیارهای واقعی برای شکستن تساوی‌اند. برتری 0.15 شارپ چنین چیزی نیست.

یک کار دیگر هم هست که پیش از اعتماد به هر نتیجه‌ای باید انجام دهید. همین حالا بک‌تست‌تان را دو بار اجرا کنید، ارزش سرمایه را برای هر کندل مقایسه کنید و ببینید در گروه نتایج کاملاً یکسان هستید یا در گروه اختلاف 0.15. این آزمایش 15 دقیقه طول می‌کشد و نشان می‌دهد چه مقدار از سابقه پژوهشی‌تان، به‌جای سنجش استراتژی، در واقع seed هش را سنجیده است.

قطعیت، بک‌تست، مهندسی داده، بیش‌برازش، پایتون
اشتراک‌گذاریXLinkedInFacebookRedditHacker NewsWhatsAppTelegramEmail
← همهٔ مطالب