मुख्य सामग्री पर जाएं
engineeringbacktestingtrading bots
No-Code Trading Bot प्लेटफ़ॉर्म बनाते समय मैंने क्या कम आँका

No-Code Trading Bot प्लेटफ़ॉर्म बनाते समय मैंने क्या कम आँका

No-code trading bot का प्रस्ताव सरल है: आप एक form में अपनी strategy बताते हैं, और software उसे आपके लिए चलाता है। मुश्किल हिस्सा form नहीं है। मुश्किल यह है कि आप यूज़र को जो भी सरलता देते हैं, उसकी कीमत सिस्टम में कहीं न कहीं चुकानी पड़ती है, और आम तौर पर वहीं जहाँ आपने सोचा नहीं होता।

ये तीन चीज़ें हैं जिनके बारे में मैं गलत था, और हर एक ने असल में हमें क्या सिखाया। इन्हें इस क्रम में रखा गया है कि मैंने इन्हें कितनी बुरी तरह गलत आँका।

मुख्य बातें

  • Exchange API जितनी बार खुलकर fail होते हैं, उससे कहीं ज़्यादा बार चुपचाप fail होते हैं। आपने जितना माँगा उससे कम data के साथ एक HTTP 200 वह failure mode है जिसके खिलाफ आपको डिज़ाइन करना होता है।
  • जब तक आप simulator को खुद test न कर लें, किसी backtest result पर भरोसा मत कीजिए। साफ-सुथरी दिखने वाली equity curve किसी टूटे हुए run से भी आ सकती है।
  • Order quantities पर floating point arithmetic ऐसी off-by-one-step गलतियाँ पैदा करती है जो सिर्फ खास pairs पर ही दिखती हैं।
  • यह तय करना कि यूज़र के फंड कभी नहीं रखने हैं, समस्याओं की एक पूरी श्रेणी हटा देता है और उसकी जगह एक छोटी पर ज़्यादा तीखी समस्या पैदा करता है: आपका सिस्टम सही होना ही चाहिए, बिना किसी चीज़ को कभी undo कर पाने की सुविधा के।
  • यूज़र को दी गई हर सुविधा engine में कहीं न कहीं एक बाधा बन जाती है। यह सौदा आम तौर पर करने लायक होता है, पर यह कभी मुफ्त नहीं होता।

1. मैंने मान लिया था कि fail हुई request, failure जैसी ही दिखेगी

यही वह चीज़ है जिसने सबसे ज़्यादा समय खर्च कराया, और यही वह चीज़ है जिसके बारे में मैं किसी भी developer को सबसे पहले आगाह करूँगा।

Backtests चलाने के लिए हम exchanges से historical candles fetch करते हैं। भोला-भाला मानसिक मॉडल यह है कि एक request या तो succeed होती है या error देती है: या तो आपको data मिलता है, या non-200 मिलता है जिसे आप handle कर लेते हैं। असल exchange API load के तहत ऐसा व्यवहार नहीं करते। वे आपने जितनी माँगी उससे कम candles के साथ HTTP 200 लौटाते हैं, और कोई संकेत नहीं देते कि कुछ गड़बड़ हुई।

यहाँ हमारे logs से एक असली मामला है, और आँकड़े बिल्कुल सटीक हैं। एक long-period backtest ने लगभग 536,000 candles माँगीं। 16:34 पर उसे 58,000 मिलीं। Status 200. कोई error field नहीं। कोई warning नहीं। वही request, बिना बदले, 17:54 पर पूरी 535,000 लौटा लाई।

सोचिए इसका क्या मतलब है अगर आप जाँच नहीं कर रहे। Engine खुशी-खुशी strategy को इच्छित history के महज ग्यारह प्रतिशत हिस्से पर simulate करता है, एक भरोसेमंद-सी दिखने वाली equity curve बनाता है, और एक नतीजा बता देता है। यूज़र एक ऐसा नंबर पढ़ता है जो गलत है, और गलत इस तरह कि output में कुछ भी उसे उजागर नहीं करता। यह crash से भी बुरा है। Crash तो ईमानदार होता है।

Fix सैद्धांतिक रूप से उबाऊ है और इसे छोड़ देना आसान था: fetch करने के बाद यह verify कीजिए कि जो data आपको मिला वह वाकई उस अवधि को cover करता है जो आपने माँगी थी, और कमी को failure मानिए, भले ही transport ने कहा हो कि सब ठीक है। Coverage एक ऐसी property है जिसे आपको assert करना पड़ता है, मान नहीं लेना चाहिए।

पहले सबक के पीछे एक दूसरे दर्जे का सबक छिपा था। जैसे ही हमने truncation पकड़ना शुरू किया, ज़ाहिर कदम यही था कि run को fail कर दें। पर ऊपर के आँकड़े दिखाते हैं कि ये truncations क्षणिक हैं: वही request एक घंटे बाद पूरा data लौटा लाई। सीधे fail कर देना यूज़र का quota जला देता, वह भी exchange की तरफ की एक अस्थायी अड़चन भर के लिए। तो सही व्यवहार है fail करने से पहले एक बार retry करना, जिसका मतलब था failure path को इस तरह दोबारा गढ़ना कि एक retryable स्थिति निकलते-निकलते हमेशा के लिए failure के रूप में चिह्नित न हो जाए।

इससे दो नियम निकले, और मैं दोनों को किसी भी ऐसे प्रोजेक्ट में ले जाऊँगा जो third-party API से बात करता हो:

  • सफल response की सिर्फ status code नहीं, उसकी बनावट को भी validate कीजिए। Status code आपको बताता है कि request पूरी हुई। यह नहीं बताता कि response वही है जो आपने माँगा था।
  • क्या करना है यह तय करने से पहले क्षणिक failures को स्थायी failures से अलग पहचानिए। होने के वक्त वे एक जैसी दिखती हैं और उनके जवाब बिल्कुल उलटे होने चाहिए।

2. मैंने मान लिया था कि backtest का नतीजा, नतीजा ही होता है

संबंधित, पर एक अलग गलती, और यह truncation की समस्या से भी गहरे तक जाती है।

जब आप एक simulator बनाते हैं, तो आप अपनी testing की मेहनत strategies पर लगाते हैं। क्या DCA logic सही व्यवहार करता है? क्या exits वहीं fire होते हैं जहाँ होने चाहिए? यही दिलचस्प सवाल है, इसलिए ध्यान स्वाभाविक रूप से वहीं जाता है।

समस्या यह है कि एक टूटा हुआ simulator ठीक वैसा ही output देता है जैसा एक काम करने वाला। एक run जिसने चुपचाप data का एक अंश ही process किया, फिर भी एक नंबर देता है। एक run जिसकी exit conditions कभी evaluate ही नहीं हुईं, फिर भी एक नंबर देता है, और वह भी शक पैदा करने वाला सपाट। हमने नतीजे ठीक +0.00% पर, शून्य trades के साथ आते देखे हैं, जो कि सबसे तेज़ संभव संकेत है कि मशीनरी में कुछ गड़बड़ है, और सबसे आसानी से गलत समझा जाने वाला भी, कि “यह strategy कुछ करती ही नहीं।”

जिस बदलाव ने इसे ठीक किया वह था यह बदलना कि हम test क्या करते हैं। Strategy नहीं, simulator को test कीजिए। ठोस रूप में इसका मतलब है उन चीज़ों पर assert करना जिन्हें strategy बदल न सके: कि process की गई candles की संख्या fetch की गई संख्या से मेल खाती है, कि किसी ज्ञात अवधि पर चला run वाकई उसी अवधि को समेटता है, कि trigger होने की गारंटी वाला एक configuration trades पैदा करता है। जब ये invariants कायम रहते हैं, तो एक अजीब दिखने वाला नतीजा strategy के बारे में एक असली खोज होता है। जब वे नहीं टिकते, तो नतीजा सूट पहने हुआ शोर भर है।

इसका आम रूप, किसी भी चीज़ के लिए जो simulate करती है: एक simulator जो खुलकर fail नहीं हो सकता, वह चुपचाप fail होगा, और नंबर पैदा करने वाले सिस्टम में चुपचाप fail होना सबसे बुरा failure mode है जो हो सकता है। लोग नंबरों पर अमल करते हैं। वे stack traces पर अमल नहीं करते।

3. मैंने कम आँका कि order sizing असल में कितना precision-काम है

यह बौद्धिक रूप से कठिन नहीं है। यह बस दिखने से कहीं बड़ा है, और यह ऐसी जगह छिपता है जहाँ गलतियाँ महँगी पड़ती हैं।

Exchanges मनमानी quantities स्वीकार नहीं करते। हर trading pair के लिए quantity का एक step size और price का एक tick size होता है, और जो orders align नहीं होते वे reject हो जाते हैं। तो सिस्टम जो भी quantity निकालता है, उसे बाहर भेजने से पहले एक valid step तक floor करना पड़ता है। यह एक utility function जैसा लगता है।

फिर floating point आता है। 0.29 को 0.01 के step तक floor करने की बात लीजिए। इसे scale up कीजिए और IEEE-754 आपको 28.999999999999996 देता है, जो 28 तक floor होता है, जो scale back होकर 0.28 बन जाता है। मान तो पहले से ही step-aligned था। Arithmetic ने उसे पूरा एक step नीचे खिसका दिया, और यह चुपचाप किया, एक छोटे order की दिशा में। इसे ठीक करने के लिए scaling step के दौरान एक epsilon चाहिए, इतना छोटा कि सचमुच नीचे का मान जैसे 0.289999 अब भी सही तरह नीचे floor हो, और इतना बड़ा कि representation का शोर सोख ले।

यह एक-लाइन का fix है जिसे ढूँढने में असली debugging लगी, क्योंकि लक्षण कोई exception नहीं है। यह एक ऐसा order है जो इरादे से थोड़ा छोटा है, कुछ pairs पर और कुछ पर नहीं, इस पर निर्भर करते हुए कि step size, binary representation के सापेक्ष कहाँ गिरता है।

बड़ी बात, और जिस वजह से मैं इसे उन चीज़ों में गिनता हूँ जिन्हें मैंने कम आँका: trading system में precision कोई utility function नहीं, यह काम की एक पूरी श्रेणी है। Step sizes और tick sizes हर pair, हर exchange, और spot व futures के बीच अलग होते हैं। Minimum order values कोई एक नंबर नहीं जिसे देखकर hardcode कर दिया जाए। वे exchange से हर symbol के हिसाब से आते हैं, और कुछ venues पर वे मौजूदा price पर निर्भर करते हैं। जब भी आप खुद को किसी ऐसी चीज़ के लिए एक अकेला constant लिखते हुए पाएँ जिसे exchange हर pair के हिसाब से तय करता है, तो आप एक ऐसा bug लिख रहे हैं जिसमें देर से फटने वाला fuse लगा है।

हम इसे यूज़र से छिपाने के बजाय सामने रखते हैं, क्योंकि विकल्प एक ऐसा order है जो ऐसी वजहों से reject हो जाता है जिन्हें वे देख नहीं सकते।

4. वह बाधा जो पसंद आने की मुझे उम्मीद नहीं थी

आखिरी वाली कोई गलती नहीं है। यह एक फैसला है जिसके नतीजों को मैंने अच्छे मायने में कम आँका।

Freya यूज़र के trading फंड कभी नहीं रखता। Bots exchange API keys के ज़रिए trade करते हैं, जिनके पास पढ़ने और trade करने की अनुमति होती है, withdraw करने की कभी नहीं। पैसा पूरे समय यूज़र के अपने exchange account में ही रहता है।

मुझे उम्मीद थी कि यह एक marketing बात होगी। यह निकली एक architectural बात, और इसने सिस्टम को छोटा कर दिया। कोई custody ledger नहीं, किसी बाहरी balance के सामने मिलान करने के लिए कोई internal balance नहीं, कोई withdrawal queue नहीं, trading capital के लिए कोई cold-wallet नीति नहीं। financial software की सबसे कठिन समस्याओं की एक पूरी श्रेणी बस गैरहाज़िर है, क्योंकि हमने वह चीज़ अपने ऊपर ली ही नहीं जो उन्हें पैदा करती है।

उसकी जगह जो आता है वह ज़्यादा संकरा और ज़्यादा तीखा है: आपका सिस्टम real time में सही होना ही चाहिए, क्योंकि यह किसी चीज़ को undo नहीं कर सकता। custody के साथ आप, सिद्धांततः, एक internal transfer को उलट सकते हैं। किसी और के exchange account पर एक order लगाना, fill होते ही अंतिम होता है। यह सारा engineering भार बाद के reconciliation के बजाय request बाहर जाने से पहले की correctness पर डाल देता है, और यह एक ऐसा सौदा है जिसे मैं दोबारा करूँगा।

शुरुआत में मैं खुद से क्या कहता

  • मान लीजिए कि हर third-party API कभी-कभी गलत तरीके से succeed होता है, और ज़रूरत पड़ने से पहले ही उसकी detection डिज़ाइन कर लीजिए।
  • Strategies के बारे में assertions लिखने से पहले अपने simulator के लिए invariant tests लिखिए। जब तक simulator के tests पास न हों, strategy के tests बेकार हैं।
  • Precision को एक domain मानिए, helper नहीं।
  • अपनी बाधाएँ जल्दी चुनिए। जो आप शुरू में स्वीकार करते हैं, वे उनसे कहीं सस्ती पड़ती हैं जो आप बाद में खोजते हैं।

इनमें से कोई भी अनोखी समस्या नहीं थी। ये आम इंजीनियरिंग समस्याएँ थीं जिन्हें मैंने planning के दौरान “छोटी” कहकर फाइल कर दिया था, और ठीक इसी वजह से ये महँगी पड़ीं।


संबंधित पठन

खाता बनाएं

आज ही Freya खाता खोलकर अपने ज्ञान को व्यवहार में लाएं।

अभी रजिस्टर करें
यह सामग्री केवल शैक्षिक उद्देश्यों के लिए है और वित्तीय सलाह नहीं है। क्रिप्टोकरेंसी ट्रेडिंग में महत्वपूर्ण जोखिम शामिल हैं। कभी भी अपनी क्षमता से अधिक निवेश न करें।