August 26, 2026 · মার্কেট ডেটা

একটি ১-মিনিট বারের অ্যানাটমি: OHLCV আপনার ব্যাকটেস্টকে যা বলে না

একটি ১-মিনিট বারের অ্যানাটমি: OHLCV আপনার ব্যাকটেস্টকে যা বলে না

এই তো একটি বার। Binance USDⓈ-M perpetual, BTCUSDT, 13:46:00 UTC থেকে শুরু হওয়া মিনিট। এটি klines এন্ডপয়েন্ট থেকে বারোটি মানের একটি খালি অ্যারে হিসেবে এসেছে, আর এটিই retail ও তার কাছাকাছি quant গবেষণায় সবচেয়ে সাধারণ ইনপুট একক। আমরা যে প্রায় প্রতিটি কৌশল তৈরি করি তা এরকম কিছু একটার সংস্পর্শে আসে।

তাহলে চলুন, ইনডেক্স ধরে ধরে এটি খুলে দেখি এবং দেখি একটি ব্যাকটেস্ট এর কতটা ভুল বোঝে।

ইনডেক্সমাননাম
01773495960000ওপেন টাইম (ms)
1"84120.50"ওপেন
2"84177.90"হাই
3"84098.10"লো
4"84163.40"ক্লোজ
5"38.417"বেস ভলিউম (BTC)
61773496019999ক্লোজ টাইম (ms)
7"3232108.94"কোট ভলিউম (USDT)
81204ট্রেডের সংখ্যা
9"21.883"টেকার বাই বেস ভলিউম
10"1841203.55"টেকার বাই কোট ভলিউম
11"0"ইগনোর

[0] এবং [6]: এটি কোন মিনিট, আর কার ঘড়ি তা বলছে

ওপেন টাইম 1773495960000, ক্লোজ টাইম 1773496019999। দ্বিতীয়টি লক্ষ্য করুন: এটি শেষ হয় 19999-এ, পরের বারের ওপেনের চেয়ে ঠিক এক মিলিসেকেন্ড কম। উইন্ডোটি half-open, আর এক্সচেঞ্জ স্পষ্টভাবে তা জানিয়ে দিচ্ছে। আমি যত ডেটা বাগ খুঁজে বের করেছি তার অর্ধেকই শুরু হয়েছিল কেউ দুটি প্রান্তবিন্দুকেই inclusive ধরে নিয়ে সীমানায় একটি ট্রেড দুবার গোনায়, অথবা একটি resample এমনভাবে align করায় যে প্রতিটি ৫-মিনিটের বার তার প্রতিবেশীর কাছ থেকে এক মিলিসেকেন্ড ধার করে নিত।

টাইমস্ট্যাম্পগুলো এক্সচেঞ্জের matching-engine সময়। এটি আপনার ঘড়ি নয়, আপনার ভেন্ডরের ইনজেশন ঘড়িও নয়, আর funding snapshot যে ঘড়ি ব্যবহার করে সেটিও নয়। যখন আপনি একটি kline সিরিজকে অন্য একটি এন্ডপয়েন্ট থেকে টানা funding-rate সিরিজ বা open-interest সিরিজের সাথে join করেন, তখন আপনি এমন সোর্সগুলো জোড়া দিচ্ছেন যেগুলো বেশিরভাগ সময় প্রায় এক সেকেন্ডের মধ্যে একমত হয়। একটি ১-মিনিটের কৌশলের জন্য এটি প্রতিটি joined সারিতে ১.৭% টাইমিং ত্রুটি। এক মিনিটের কমের যেকোনো কিছুর জন্য এটি মারাত্মক।

আর এই বারটি তার ওপেন-এ স্ট্যাম্প করা। এতে থাকা তথ্য 13:46:59.999 পর্যন্ত জানা সম্ভব নয়। প্রতিটি ইনডেক্সিং কনভেনশন প্রশ্ন — আমি কি এক ধাপ shift করব, ইভেন্ট টাইম হিসেবে bar-close নাকি bar-open টাইমস্ট্যাম্প ব্যবহার করব — আসলে একই lookahead প্রশ্নের ছদ্মবেশ মাত্র।

[1] "84120.50": যে ওপেনে আপনি ট্রেড করতে পারবেন না

ওপেন হলো উইন্ডোর ভেতরে প্রিন্ট হওয়া প্রথম ট্রেডের দাম। এটি অন্য দুজন মানুষের মধ্যে সম্পন্ন একটি লেনদেন। যতক্ষণে বারটি আপনার dataframe-এ একটি সারি হিসেবে অস্তিত্ব পায়, ততক্ষণে সেই দাম ষাট সেকেন্ডের পুরনো হয়ে গেছে।

আগের বারটি বন্ধ হয়েছিল 84109.80-এ, তাই দুটি পাশাপাশি ১-মিনিটের বারের মধ্যকার ফাঁকে $10.70 দামের নড়াচড়া বসে আছে। এটি ১.৩ বেসিস পয়েন্ট, চার্টে অদৃশ্য, আর taker ফি-র প্রায় এক-চতুর্থাংশ। ঠিক আছে। কিন্তু একই সিরিজ একটি alt perp-এ US CPI প্রিন্টের সময় দেখুন, তখন এই inter-bar গ্যাপগুলো ১৫ থেকে ৪০ bps পর্যন্ত হয়। একটি ব্যাকটেস্ট যেটি bar close-এ সিগন্যাল দেয় এবং next-bar open-এ ফিল করে, তা নীরবে ধরে নিচ্ছে গ্যাপটি শূন্য, আর এটি ঠিক তখনই শূন্য হয় যখন কোনো ফারাক তৈরি হয় না।

[2] এবং [3]: "84177.90" হাই, "84098.10" লো

এই দুটিই সেই জায়গা যেখানে বেশিরভাগ fill ইঞ্জিন মারা যায়, তাই এই অংশটি দীর্ঘ।

হাই এবং লো হলো স্পর্শ করা চরম দাম। এগুলোর কোনো সাইজ নেই, কোনো সময়কালও নেই। এই একই মিনিটের raw trade tape থেকে দেখা যায়, 84100.00 বা তার নিচে যা কিছু ছিল তা মোট 0.62 BTC, ১.৪ সেকেন্ডের একটি উইন্ডোর মধ্যে নয়টি প্রিন্ট জুড়ে। তাই 84100-এ একটি ব্যাকটেস্ট স্টপ "ফিল" হয় — আর আপনার পজিশন যদি 3 BTC হয়, তাহলে নিচে নামার পথে আপনি দৃশ্যমান পুরো বুক খেয়ে ফেলেছেন এবং আপনার বাকি সাইজ 84105–84130-এর walk-back-এর কোথাও সম্পন্ন হয়েছে। বারটি বলে লো ছিল 84098.10। বারটি এটা বলে না যে সেখানে মাত্র $52,000 নোশনাল ট্রেড হয়েছিল।

1204মিনিটে ট্রেডের সংখ্যা
0.032 BTCগড় প্রিন্ট সাইজ (~$2.7k)
0.62 BTCলো টিক বা তার নিচে মোট ট্রেড হওয়া পরিমাণ
57%ভলিউমের এই অংশ buyer-initiated ছিল

উল্টো দিকটা আরও খারাপ, কারণ এটি আপনাকে তোষামোদ করে। ধরুন আপনার 84175-এ একটি resting limit sell ছিল। বারের হাই হলো 84177.90, তাই একটি naive ইঞ্জিন আপনাকে 84175-এ ফিল করে এবং taker খরচের বদলে maker rebate বুক করে। সেই ফিল আসলে ঘটেছিল কিনা তা নির্ভর করে সেই প্রাইস লেভেলে queue position-এর উপর, যা বার জানতে পারে না এবং আপনি সম্ভবত কখনও রেকর্ডও করেননি। Touched মানে filled নয়।

আমাদের fill ইঞ্জিনে আমরা যে নিয়মে স্থির হয়েছি: একটি resting limit তখনই ফিল হয় যখন বারটি সেই লেভেলটি ট্রেড করে পার করে যায়, শুধু সেই পর্যন্ত পৌঁছালেই নয়। ঠিক চরম টিকে ফিলের জন্য trade tape থেকে volume-at-price প্রমাণ প্রয়োজন, নইলে সেগুলো বাতিল হয়ে যায়। এটি একটি সাধারণ mean-reversion বুক থেকে প্রায় 6% ট্রেড বাদ দিয়েছে এবং একটি candidate-এর ব্যাকটেস্ট করা Sharpe 1.9 থেকে 1.1-এ নামিয়ে এনেছে। সেই candidate কখনোই বাস্তব ছিল না; fill নিয়মটি ছিল প্রথম জিনিস যা এটা সততার সাথে বলার মতো যথেষ্ট সৎ ছিল।

সম্পর্কিত একটি ফাঁদ: intrabar path। যদি একটি বারের রেঞ্জ আপনার স্টপ এবং টেক-প্রফিট দুটোকেই বিস্তৃত করে, তাহলে OHLCV আপনাকে বলতে পারবে না কোনটা আগে এসেছিল। প্রতিটি ইঞ্জিনকে একটি কনভেনশন বেছে নিতে হয়। আমাদেরটা সবসময় stop-first ধরে নেয়, যা নৈরাশ্যবাদী এবং মাঝে মাঝে ভুল, কিন্তু কখনও নকল জয় তৈরি করে না। যদি আপনার ইঞ্জিন take-profit-first ধরে নেয়, তাহলে wide-range বারগুলো অস্পষ্টতা থেকে মুনাফা তৈরি করে ফেলবে, আর wide-range বারগুলোই ঠিক সেই বারগুলো যা আপনার PnL বণ্টনে আধিপত্য বিস্তার করে।

[4] "84163.40": বারের সবচেয়ে কম নির্ভরযোগ্য সংখ্যা

ক্লোজ হলো উইন্ডোর শেষ প্রিন্ট। এইটুকুই। এটি হয়তো 13:46:59.8-এ একটি বটের পজিশন গোল করার জন্য করা 0.002 BTC-এর odd-lot হতে পারে। এই একক, স্ট্রাকচারালি নির্বিচার টিকটিই বেশিরভাগ গবেষণা পাইপলাইন প্রতিটি সিগন্যাল হিসাব করতে, প্রতিটি পজিশন মার্ক করতে, এবং প্রতিটি এক্সিট মূল্যায়ন করতে ব্যবহার করে।

BTC perp-এ এটি প্রায় কোনো ব্যাপারই না; কিন্তু 04:00 UTC-তে একটি পাতলা altcoin perp-এ এটি বিপুলভাবে গুরুত্বপূর্ণ, এবং একই মিনিটের জন্য দুটি ভেন্যুর ক্লোজের মধ্যে পার্থক্য আপনার সম্পূর্ণ per-trade edge-কেও ছাড়িয়ে যেতে পারে। যখন একটি কৌশলের PnL নির্দিষ্টভাবে ক্লোজের উপর নির্ভর করে, তখন আমরা এর বদলে exchange mark price-এ মার্ক করে সেটি পুনরায় চালাই, যা index-derived এবং অনেক বেশি কঠিন নাড়াচাড়া করা। যদি ফলাফল আলাদা হয়ে যায়, তাহলে বুঝতে হবে কৌশলটি artifact-টাই ট্রেড করছিল।

[5] এবং [7]: "38.417" এবং "3232108.94", কীসের এককে ভলিউম

বেস ভলিউম হলো BTC; কোট ভলিউম হলো USDT। এখানে দুটোই আছে, যা একটি সৌজন্য যা সব ভেন্যু দেয় না। এই বিষয়ে যত্নবান হওয়ার কারণ হলো ভেন্যুজুড়ে aggregation। Coin-margined কন্ট্রাক্ট $100 নোশনালের কন্ট্রাক্টে কোট করা হয়। কিছু equities ফিড round lot রিপোর্ট করে। প্রেডিকশন মার্কেট ভেন্যুগুলো শেয়ার সংখ্যা রিপোর্ট করে, যেখানে একটি শেয়ার ডলার-ডিনোমিনেটেড একটি বাইনারি দাবি। একটি মিশ্র universe জুড়ে একটি একক নোশনাল এককে normalise না করে "ভলিউম" যোগ করলে একটি liquidity র‍্যাঙ্কিং পাওয়া যায় যা নিখাদ অর্থহীন, আর এটি এমনভাবে অর্থহীন হবে যে দেখতে স্থিতিশীল মনে হবে এবং রিভিউ পার হয়ে যাবে।

ইনজেস্টের সময় সবকিছু কোট নোশনালে normalise করুন। raw ফিল্ডটিও সংরক্ষণ করুন, কিন্তু কখনও একটি কৌশলকে তা দেখতে দেবেন না।

[8] 1204: যে ফিল্ডটি আপনার impact মডেল ঠিক করা উচিত

ট্রেড সংখ্যা দিয়ে বেস ভলিউম ভাগ করলে পাওয়া যায় 0.032 BTC-এর গড় প্রিন্ট, প্রায় $2,700। যদি আপনার candidate কৌশলটি একসাথে $250,000 এন্টার করতে চায়, তাহলে এটি সেই মিনিটের সাধারণ লেনদেনের আকারের প্রায় 92 গুণ হতে চাইছে। এই সংখ্যাটিই, কোনো সাধারণ 5-bps স্লিপেজ ধ্রুবক নয়, যা আপনার square-root impact টার্মকে চালিত করা উচিত। আমরা প্রতিটি বারের জন্য participation rate একটি first-class কলাম হিসেবে হিসাব করি এবং এমন কৌশল বাতিল করি যার median entry বারের নোশনালের কয়েক শতাংশ ছাড়িয়ে যায়, কারণ তার পরের সবকিছুই কল্পকাহিনী।

ট্রেড সংখ্যা সস্তায় অদ্ভুত মিনিটগুলোও চিহ্নিত করে দেয়। ভলিউম স্বাভাবিক, কিন্তু ট্রেড সংখ্যা নেমে গেছে 11-এ? কেউ একজন একটি block করেছে। ভলিউম স্বাভাবিক, ট্রেড সংখ্যা 9,000-এ? এটি একটি liquidation cascade যা ছোট ছোট টুকরোয় চিবানো হচ্ছে।

[9] এবং [10]: "21.883", যে ফিল্ডটি সবাই ফেলে দেয়

টেকার বাই বেস ভলিউম। এই বারের 38.417 BTC-এর মধ্যে 21.883 buyer-initiated ছিল, তাই signed volume delta হলো +5.349 BTC এবং aggressor split হলো 57/43 buyer-দের পক্ষে। এক্সচেঞ্জ আপনাকে বিনামূল্যে order flow imbalance দিয়ে দিচ্ছে, এমন একটি ফিল্ডে যা বেশিরভাগ মানুষ কখনও পড়েনই না কারণ pandas তাদের জন্য কলামটির নাম দেয়নি।

আমি দাবি করছি না যে এটি একা রিটার্ন predict করে; naive delta কৌশলগুলো ফি লেজারে দান করার অন্যতম নির্ভরযোগ্য উপায়। কিন্তু এটি দামের থেকে সত্যিকার অর্থে একটি ভিন্ন পরিমাপ, এটি একই রিকোয়েস্টে পাওয়া যায় যা আপনি ইতিমধ্যেই করছিলেন, এবং এটি আপনাকে এমন একটি rally আলাদা করতে দেয় যা কেনা হয়েছিল, আর এমন একটি rally যা ঘটেছিল কারণ বিক্রেতারা সরে গিয়েছিল। এই দুটি OHLC-তে অভিন্ন দেখায় কিন্তু দশ মিনিট পর ভিন্নভাবে আচরণ করে। আমাদের research agent এমন একটি hypothesis-কে যা taker split উপেক্ষা করে এমন একটি ভেন্যুতে যা এটি প্রকাশ করে, প্রমাণ টেবিলে ফেলে রাখা হিসেবে বিবেচনা করে।

[11] "0": ইগনোর — এবং যা কিছু এখানে নেই তার সবকিছু

ইনডেক্স 11 একটি deprecated ফিল্ড, স্থায়ীভাবে শূন্য। আরও আকর্ষণীয় হলো এই বারে না থাকা জিনিসগুলোর তালিকা: কোনো bid নেই, কোনো ask নেই, কোনো spread নেই, কোনো book depth নেই, কোনো funding rate নেই, কোনো open interest নেই, কোনো liquidation নেই, কোনো mark price নেই, কোনো index price নেই। আর সবচেয়ে গুরুত্বপূর্ণ, জানার কোনো উপায় নেই আপনার অর্ডারটি maker নাকি taker হতো, যা এই ভেন্যুতে 0.045% দেওয়া আর 0.01% আয় করার মধ্যকার পার্থক্য।

তাই শুধুমাত্র klines-এর উপর নির্মিত যেকোনো ফি মডেল একটি সংখ্যার পোশাক পরা অনুমান মাত্র। আমরা এর সমাধান করি প্রতিটি কৌশলকে আগে থেকেই তার execution style ঘোষণা করতে বাধ্য করে, এবং যা অন্যথা প্রমাণ করতে পারে না তার উপর taker চার্জ করে।

যে বারটি কখনও দেখা যায়নি

শেষ অংশ, আর এটিই মেজরদের বাইরে সবচেয়ে বেশি কামড় দেয়। শূন্য ট্রেডের একটি মিনিট কোনো kline-ই তৈরি করে না। ভেন্ডর এবং লাইব্রেরিগুলো সাধারণত এটি forward-fill করে: open = high = low = close = previous close, volume 0। আপনার ইন্ডিকেটর খুশিতে হিসাব করে ফেলে। আপনার কৌশল একটি বৈধ সারি দেখে এবং এমন একটি মিনিটে সিগন্যাল তৈরি করতে পারে যেখানে বিশ্বের কেউই সেই instrument ট্রেড করেনি।

আমরা ইনজেস্ট করা একটি mid-cap perp-এ, বারো মাসের একটি উইন্ডোতে ১-মিনিটের বারের 4.1% শূন্য ট্রেড ছিল। সেই সিম্বলের একটি mean-reversion candidate তার 38% এন্ট্রি synthetic বারগুলোতে বসাচ্ছিল, কারণ সমতল synthetic দাম moving average থেকে বিচ্যুতি মাপা যেকোনো কিছুর জন্য catnip। এটি সুন্দরভাবে ব্যাকটেস্ট করেছিল। এটি আসলে ডেটার ফাঁকগুলো ট্রেড করছিল।

এই কারণেই ইনজেস্ট স্তর এখন প্রতিটি বারে একটি synthetic বুলিয়ান বহন করে, প্রতিটি resample জুড়ে প্রচারিত হয়, এবং verification gauntlet এমন যেকোনো কৌশল বাতিল করে দেয় যার ট্রেড এর উপর ক্লাস্টার করে। সস্তা একটি কলাম। এটি আমাদের লেখা যেকোনো ইন্ডিকেটরের চেয়ে বেশি candidate মেরেছে।

বারোটি মান। এর মধ্যে চারটি নিয়মিতভাবে ভুল পড়া হয়, দুটি নিয়মিতভাবে ফেলে দেওয়া হয়, আর একটি সম্পূর্ণ বিভাগ সারিতে অনুপস্থিত থেকে ইঞ্জিন দ্বারা কল্পনা করা হয়। আপনার পরবর্তী ব্যাকটেস্টের আগে, আপনার নিজের স্টোর থেকে একটি raw বার টেনে বের করুন এবং আপনার fill logic এটি সম্পর্কে যা ধরে নেয় তার বিপরীতে প্রতিটি ফিল্ড জোরে জোরে পড়ুন। এটি একটি বিশ-মিনিটের অনুশীলন, আর আমি কাউকে এটি করে কিছু না পেতে দেখিনি।

OHLCVমার্কেট মাইক্রোস্ট্রাকচারডেটা ইঞ্জিনিয়ারিংব্যাকটেস্টিংক্রিপ্টো ফিউচার্স
← All posts