ट्रेडिंग बॉट प्लेटफ़ॉर्म बनाने से पहले मैं खुद को 6 बातें बताता
यह Freya Finance बनाने के बारे में एक छोटी सीरीज़ का समापन है। लेखों को दोहराने की बजाय, यहाँ वह बातें हैं जो मैं शुरुआत में खुद से कहता, इस क्रम में कि हर बात सीखने की कितनी कीमत चुकानी पड़ी।
एक असहज सूत्र इन सबको जोड़ता है: इनमें से लगभग किसी भी विफलता ने अपनी घोषणा नहीं की। उन्होंने विश्वसनीय लगने वाले नंबर, सही दिखने वाले ऑर्डर, और बिल्कुल ठीक लगने वाली स्क्रीन दीं। ऐसे सिस्टम में जो किसी के पैसे पर कार्रवाई करता है, exception फेंकने वाला bug दरअसल दोस्ताना किस्म का है।
Key Takeaways
- Third-party APIs चुपचाप fail होती हैं। जिस failure mode के लिए design करना है वह HTTP 200 है जो माँगे गए से कम data लेकर आए, 500 नहीं।
- जो कुछ भी simulator simulate करता है, उससे पहले खुद simulator को test करें। टूटा हुआ simulator ऐसे नंबर देता है जो काम कर रहे simulator जैसे ही दिखते हैं।
- जो simulate नहीं हो सका, उसकी रिपोर्ट करें। एक stated gap वाला result उस साफ़-सुथरे result से कहीं बेहतर है जिसने चुपचाप कोई condition छोड़ दी।
- Precision एक पूरा domain है, कोई utility function नहीं। Step sizes, tick sizes और minimums हर pair, हर exchange और हर market type में अलग होते हैं।
- बंदिशें जल्दी चुनें। यूज़र फंड कभी न रखने का फ़ैसला करते ही समस्याओं की पूरी एक श्रेणी हल करने की बजाय ख़त्म हो गई।
- पैसे के interface में, audit करें कि प्लेटफ़ॉर्म किन gestures को edit मानता है। एक focused number input पर माउस wheel scroll करना उन्हीं में से एक है।
1. मान लें कि हर API कभी न कभी गलत तरीके से सफल होगी
सबसे महँगी धारणा जो मैंने बनाई वह यह थी कि एक विफल request विफलता जैसी दिखेगी।
लोड में होने पर exchanges HTTP 200 लौटाते हैं, लेकिन माँगी गई candles से कम देते हैं, कोई error field नहीं, कोई warning नहीं। हमारे एक deep backtest ने लगभग 536,000 candles माँगीं और 58,000 मिलीं। वही request एक घंटे बाद पूरा set लेकर आया। response में कुछ भी इन दोनों को अलग नहीं करता था।
अगर आप check नहीं कर रहे, तो engine इच्छित history के एक अंश पर strategy simulate करता है और आत्मविश्वास से भरा नंबर रिपोर्ट करता है। यह crash से भी बुरा है, क्योंकि crash कम से कम ईमानदार होता है।
इससे दो नियम निकले: सिर्फ़ status code नहीं, successful response की shape को validate करें, और कोई कार्रवाई तय करने से पहले transient failures को permanent failures से अलग पहचानें। घटने के वक़्त ये एक जैसी दिखती हैं, लेकिन इनका जवाब एक-दूसरे के विपरीत होना चाहिए।
पूरी कहानी: मैंने क्या कम आँका
2. Simulator को test करें, strategy को नहीं
हर सहज प्रवृत्ति आपको strategy logic test करने की तरफ़ खींचती है, क्योंकि वही दिलचस्प हिस्सा है। जाल यह है कि एक टूटा हुआ simulator ऐसा output देता है जो काम कर रहे simulator के output से अलग नहीं पहचाना जा सकता। एक run जिसने चुपचाप data का दसवाँ हिस्सा ही process किया, फिर भी एक नंबर लौटाता है। एक run जिसके exits कभी evaluate नहीं हुए, संदिग्ध रूप से सपाट नंबर लौटाता है।
जिस बदलाव ने इसे ठीक किया, वह था उन चीज़ों पर assert करना जो strategy बदल नहीं सकती: process की गई candles fetch की गई candles से मेल खाती हैं, एक ज्ञात अवधि पर चला run वास्तव में उसे span करता है, और एक ऐसा configuration जो trade trigger करने की गारंटी देता है, trades produce करता है।
जब ये invariants टिके रहते हैं, तो कोई अजीब result एक असली खोज है। जब नहीं टिकते, तो result सूट पहने हुए शोर है।
3. जो simulate नहीं हो सका, उसकी रिपोर्ट करें
यह bug की बजाय एक design choice है, और मेरे हिसाब से सबसे कम सराही गई बात है।
कुछ conditions को replay नहीं किया जा सकता। एक बाहरी webhook signal का कोई ऐतिहासिक रिकॉर्ड नहीं होता। आकर्षक कदम यह है कि उसे छोड़ दें और बाकी का evaluate करें, जिससे output पूरा दिखता है।
देखें कि यह A AND B ज़रूरत वाली strategy का क्या करता है। अगर B simulate नहीं हो सकता, तो A अकेला फ़ैसला करता है, इसलिए simulated bot वहाँ entry करता है जहाँ असली bot रुका रहता। यह error random नहीं है: AND logic के तहत एक condition हटाने से हकीकत से ज़्यादा trades ही पैदा हो सकते हैं, इसलिए bias हर बार बेहतर दिखने वाले result की तरफ़ इशारा करता है।
हमने simulation चलाना जारी रखा, लेकिन result के साथ एक स्पष्ट warning आती है जो बताती है कि क्या skip किया गया। ऐसा simulator जो ज़ोर से fail नहीं हो सकता, चुपचाप fail होगा, और नंबर produce करने वाली किसी चीज़ में चुपचाप fail होना सबसे बुरा failure mode है।
पूरी कहानी: backtests क्यों झूठ बोलते हैं
4. एक चिकना equity curve सवाल है, जवाब नहीं
इससे जुड़ी बात, जिसे अलग निकालना ज़रूरी है क्योंकि यह code की बजाय इंसानों को धोखा देती है।
बिना stop loss वाला DCA grid लगभग कभी नुकसान realize नहीं करता। वह hold करता है और जोड़ता रहता है। इसलिए बंद हुए trades लगभग सभी winner होते हैं, win rate शानदार दिखती है, और equity curve एक साफ़-सुथरी सीढ़ी बन जाता है।
जोखिम कहीं गया नहीं। वह स्थगित हो गया, और इस स्थगन की एक कठोर सीमा है: आगे के safety orders के लिए बचा हुआ capital। उपयोगी सवाल यह नहीं है कि "drawdown कितना था" बल्कि यह है कि "अगर price इसके खिलाफ़ जाती रहे, तो किस बिंदु पर strategy की जगह ख़त्म हो जाती है?"
खुद Drawdown को भी उसी संदेह से देखना चाहिए। इसे peak पर allocated capital के मुकाबले मापा जाता है, इसलिए वही 200 USDT का नुकसान 500 USDT allocation पर 40% दिखता है और 5,000 पर 4%। एक जैसे trade करने वाले दो bots बेतहाशा अलग नंबर दिखा सकते हैं।
5. Precision एक domain है, helper नहीं
मैंने "quantities को valid steps में round करो" को एक utility function की तरह दर्ज किया था। यह काम की एक पूरी श्रेणी है, और यह ऐसी जगह छिपी होती है जहाँ गलतियों की कीमत पैसे में चुकानी पड़ती है।
Floating point से शुरू होता है: 0.29 को 0.01 के step से scale करने पर 28.999999999999996 आता है, जो एक पूरे step नीचे floor हो जाता है, चुपचाप, छोटे ऑर्डर की तरफ़।
फिर exchanges खुद rules पर असहमत होते हैं। एक USDT में order value check करता है, एक बिना value check के contract count check करता है, एक दोनों check करता है। इस क्षेत्र में हमारा सबसे बुरा bug एक ऐसे minimum को गढ़ने से आया जो एक venue पर है ही नहीं, और फिर उसके ख़िलाफ़ valid orders reject करना। पीछे मुड़कर देखें तो संकेत यह था कि हमारी constraint का version price पर निर्भर था जबकि असली constraint नहीं था।
जहाँ भी आप किसी ऐसी चीज़ के लिए एक constant लिखते हैं जिसे exchange हर pair के लिए अलग define करता है, आप delayed fuse वाला bug लिख रहे हैं।
पूरी कहानी: floating point आपके पैसे खर्च कराएगा
6. अपनी बंदिशें जल्दी चुनें
आखिरी बात कोई गलती नहीं है। यह एक फ़ैसला है जिसके नतीजों को मैंने अच्छे अर्थ में कम आँका।
Freya कभी यूज़र के trading funds अपने पास नहीं रखता। Keys में read और trade permission होती है, कभी withdrawal नहीं, और जो key withdrawal permission के साथ आती है उसे स्वीकार करके ignore करने की बजाय अस्वीकार कर दिया जाता है। यह अंतर सुनने से ज़्यादा मायने रखता है: "हम withdraw नहीं करेंगे" हमारे व्यवहार के बारे में एक दावा है जिसे यूज़र verify नहीं कर सकता, जबकि "key withdraw कर ही नहीं सकती" exchange द्वारा enforce किया जाता है और यूज़र अपनी settings में खुद check कर सकता है।
मैंने उम्मीद की थी कि यह एक marketing point होगा। यह architectural निकला, और इसने सिस्टम को छोटा बना दिया: कोई custody ledger नहीं, कोई reconciliation नहीं, कोई withdrawal queue नहीं, trading capital के लिए कोई cold storage policy नहीं। Financial software की सबसे कठिन समस्याओं की पूरी एक श्रेणी अनुपस्थित है क्योंकि हमने वह चीज़ कभी अपनाई ही नहीं जो उन्हें पैदा करती है।
इसकी जगह जो आता है वह संकरा और तीखा है: सिस्टम को real time में सही होना होगा, क्योंकि वह कभी कुछ undo नहीं कर सकता। किसी और के exchange खाते पर fill हुआ ऑर्डर अंतिम है।
पूरी कहानी: non-custodial trading की design
वह बात जिसकी ज़रूरत मुझे उम्मीद नहीं थी
एक bonus, क्योंकि इसने मुझे सबसे ज़्यादा चौंकाया: पैसे के interface में, front end को भी engine जितने ही paranoia की ज़रूरत होती है।
एक focused <input type="number"> अपनी value तब बदलता है जब आप उस पर wheel scroll करते हैं। Quantity picker पर यह एक झुंझलाहट है। Leverage, order size और price पर यह चुपचाप वह बदल देता है जो यूज़र ने चुना था, और form फिर भी validate होता है। इस bug का कुछ भी screenshot में दिखाई नहीं देता।
पूरी कहानी: माउस wheel आपके number inputs को edit कर रहा है
अगर इन सबको एक लाइन में दबाऊँ
उन failures के लिए build करें जो अपनी घोषणा नहीं करतीं। जो exception फेंकती हैं, वे आपको खुद ढूँढ लेंगी।
