Strasmore Research
গভীর বিশ্লেষণ Matt Connorদ্বারা Matt Connor · আপডেট করা হয়েছে 2026-08-08

Python-এ API Key ছাড়া পুনরুৎপাদনযোগ্য Backtest

Python-এ API key ছাড়া পুনরুৎপাদনযোগ্য backtest চালান: নির্দিষ্ট install ও deterministic sample data দিয়ে একই ফল পান, এবং equity curve-এর সীমাবদ্ধতা বুঝুন।

Python-এ reproducible backtest বলতে এমন backtest বোঝায়, যা কোনো অপরিচিত ব্যক্তি একটি পরিষ্কার মেশিনে আবার চালিয়ে আপনার একই সংখ্যাগত ফলাফল পেতে পারেন। পথে কোনো account বা API key-এর প্রয়োজন থাকে না। বেশিরভাগ tutorial প্রথম লাইনেই এই পরীক্ষায় ব্যর্থ হয়। কারণ সেখানে live download ব্যবহার করা হয়, ফলে পরবর্তী পাঠক লেখকের সময়ে পাওয়া price history-এর তুলনায় সামান্য ভিন্ন price history পান। এই walkthrough-এ একটি open-source engine নির্দিষ্ট করা হয়েছে—quantjourney-bt version 0.12.4। এরপর কোনো credential ছাড়াই engine-এর bundled example চালানো হয়েছে। শেষে real market data ব্যবহার করে দেখানো হয়েছে, একটি মাত্র পরিষ্কার run থেকেও কী জানা যায় না।

ব্যাকটেস্ট পুনরুৎপাদনযোগ্য করে তোলে কী?

এখানে reproducibility-এর অর্থ সংকীর্ণ এবং যাচাইযোগ্য: দ্বিতীয় একজন ব্যক্তি পরিষ্কার মেশিনে একটি কমান্ড চালিয়ে দশমিক পর্যন্ত আপনার একই সংখ্যা পাবেন। দুটি সাধারণ কারণে এটি নষ্ট হয়।

প্রথমটি হলো code। 0.x সংস্করণের কোনো library minor release-গুলোর মধ্যে সামঞ্জস্যের নিশ্চয়তা দেয় না। কোনো নাম বদলে যেতে পারে, default পরিবর্তিত হতে পারে, column-এর ক্রম পাল্টাতে পারে। আপনার script চলতে থাকবে, কিন্তু নীরবে অন্য ফলাফল জানাবে।

দ্বিতীয়টি হলো data। যে tutorial-এর প্রথম লাইনেই live download থাকে, সেটি কখনো reproducible ছিল না। Vendor-রা historical data সংশোধন করে, stock split-এর জন্য সমন্বয় করে এবং অনুপস্থিত data পরে যোগ করে। ফলে এক মাস পর একই script ভিন্ন সংখ্যা দেখাতে পারে। ঘটনার পরে code পরিবর্তনের কারণে ফল বদলেছে, নাকি data পরিবর্তনের কারণে বদলেছে, তা আলাদা করা যায় না।

এই project থেকে অনুসরণ করার মতো পদ্ধতি হলো নির্দিষ্ট engine version-এর সঙ্গে package-এর ভেতরেই থাকা ছোট একটি bundled dataset ব্যবহার করা।

ইনস্টলেশন নির্দিষ্ট সংস্করণে স্থির রাখুন: pip install quantjourney-bt==0.12.4

quantjourney-bt হলো Apache License 2.0-এর অধীনে প্রকাশিত QuantJourney backtester। এটি Python 3.11 বা পরবর্তী সংস্করণ প্রয়োজন। 0.12.4 সংস্করণটি 21 July 2026-এ প্রকাশিত হয়। August 2026-এ নথিভুক্ত তথ্য অনুযায়ী, নিচের প্রতিটি কমান্ড এই সংস্করণটিকেই নির্দেশ করে।

একটি বিচ্ছিন্ন environment-এর মধ্যে কাজ করুন। python3 -m venv .venv এটি তৈরি করে, source .venv/bin/activate-এর মাধ্যমে এতে প্রবেশ করা যায়, আর python -m pip install -U pip এর ভেতরের installer আপডেট করে। এরপর নির্দিষ্ট সংস্করণটি নিন: pip install quantjourney-bt==0.12.4

প্রকল্পের নথিতে pin না করা রূপটি রয়েছে: pip install quantjourney-bt==0.12.4 অংশটি আপনার দায়িত্ব। 0.x সংস্করণে এটি বিশেষভাবে গুরুত্বপূর্ণ। পরবর্তী ব্যবহারকারী যাতে এটি খুঁজে পান, সে জন্য pin-টি সংরক্ষণ করুন: pip freeze > requirements.txt আপনার নাম উল্লেখ না করা dependency-সহ প্রতিটি resolved dependency নথিবদ্ধ করে।

দুটি ঐচ্ছিক extra রয়েছে। pip install "quantjourney-bt[wf]" walk-forward এবং optimization উদাহরণের জন্য Optuna যোগ করে। pip install "quantjourney-bt[data]" benchmark-এর জন্য ব্যবহৃত yfinance fallback যোগ করে।

Apache-2.0 একটি permissive licence। আপনি code বাণিজ্যিকভাবে ব্যবহার ও পরিবর্তন করতে পারেন। তবে পুনর্বণ্টনের সময় licence এবং notice ফাইলগুলো সঙ্গে রাখতে হবে। Contributors স্পষ্টভাবে patent rights প্রদান করেন।

API key ছাড়া bundled SMA উদাহরণ কীভাবে চালাবেন

রিপোজিটরিতে পঞ্চাশটি চালানো যায় এমন example strategy-এর পাশাপাশি একটি launcher script রয়েছে। এগুলো weight-based path এবং order-based path—এই দুই ভাগে বিভক্ত। এর মধ্যে fiveটি walk-forward workflow রয়েছে। ./strategy.sh --list catalog দেখায়। ./strategy.sh example_weights_01_sma_daily --check একটি strategy import করে এবং কোনো data-তে হাত দেয় না। ইনস্টলেশন সঠিক হয়েছে কি না যাচাই করার এটিই দ্রুততম উপায়।

Demo run চালানোর কমান্ডটি এক লাইনের: ./strategy.sh example_weights_01_sma_daily --sample-data --output /tmp/qj-sample

--sample-data flag-টির মধ্যেই মূল বিষয়টি রয়েছে। প্রজেক্টটি এর পেছনের dataset-কে এভাবে বর্ণনা করেছে:

Sample dataset-টি ইচ্ছাকৃতভাবে ছোট এবং পুনরুৎপাদনযোগ্য। Account তৈরি না করেই installation check, report generation এবং engine flow বোঝার জন্য এটি উপযোগী।
Source: quantjourney-bt README, version 0.12.4, পড়া হয়েছে 6 August 2026।

Run-টি console verdict-এর বদলে একটি directory-তে output লেখে: summary.txt এবং summary.json, একটি metrics.csv, তার equity_curve.png-এর পাশে একটি equity_curve.csv, একটি dashboard.html, একটি plots/ folder এবং run-টি কীভাবে configured ছিল তা record করা একটি run_metadata.json। অধিকাংশ মানুষ শেষের file-টি এড়িয়ে যান। অথচ এক বছর পর কোনো result audit করার সময় এটিই সবচেয়ে গুরুত্বপূর্ণ file।

ফলাফলের metrics সঠিকভাবে ব্যাখ্যা করুন। Bundled dataset-টি ছোট এবং illustrative। তাই summary.txt-এ দেখানো Sharpe ratio এবং সর্বোচ্চ drawdown একটি sample file-এর বৈশিষ্ট্য বর্ণনা করে। এগুলো কোনো strategy সম্পর্কে প্রমাণ নয়। এগুলোকে result হিসেবে ধরে নেওয়াই আপনার প্রথম ভুল হবে।

Run-টি যে বিষয়গুলো নিশ্চিত করে, সেগুলো গুরুত্বপূর্ণ: installation কাজ করছে। Signal থেকে target weights এবং reconstructed portfolio value পর্যন্ত সম্পূর্ণ engine flow আপনার machine-এ কোনো credential ছাড়াই artifacts তৈরি করছে। Real history-র backtest-এর জন্য project-এর নিজস্ব data service-এ credential ব্যবহার করে যাওয়ার একটি path রয়েছে। সেই path-এর documentation আছে। তবে এই walkthrough এখানেই থামছে, কারণ এই অংশে কারও কাছ থেকে কিছুই প্রয়োজন হয় না।

একটি in-sample equity curve যা জানায় না

নমুনা রান একটি equity curve দেখায়। বাস্তব market data ব্যবহার করে, demo file নয়, নিচে দেখানো হলো ওই curve কী করতে পারে না।

নিচের panel-এ উদাহরণ strategy-তে ব্যবহৃত একই ধারণা নেওয়া হয়েছে: একটি 20-session moving average, একটি 50-session moving average অতিক্রম করে। এটি SPY-তে প্রয়োগ করে 2017 থেকে 2025 পর্যন্ত প্রতিটি calendar year-এর ফল আলাদাভাবে দেখানো হয়েছে। প্রতিটি session-এ position আগের session-এর close দিয়ে নির্ধারিত হয়। তাই rule কখনও এমন কোনো সংখ্যা ব্যবহার করে trade করে না, যা তখনও জানা ছিল না।

কুয়েরিSPY-তে 20/50 মুভিং-অ্যাভারেজ ক্রসওভার, বছরভিত্তিক, ধরে রাখার কৌশলের তুলনায়
প্রতিটি সংখ্যার পেছনের সঠিক SQL
WITH daily AS
(
    SELECT
        toDate(toTimeZone(window_start, 'America/New_York')) AS d,
        toFloat64(argMax(close, window_start))               AS px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker = 'SPY'
      AND window_start >= '2016-01-01 00:00:00'
      AND window_start <  '2026-01-01 05:00:00'
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) >= 570
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) < 960
    GROUP BY d
),
averaged AS
(
    SELECT
        d,
        px,
        avg(px) OVER (ORDER BY d ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) AS fast_ma,
        avg(px) OVER (ORDER BY d ROWS BETWEEN 49 PRECEDING AND CURRENT ROW) AS slow_ma,
        row_number() OVER (ORDER BY d)                                      AS session_no
    FROM daily
),
positioned AS
(
    SELECT
        d,
        px,
        if(session_no >= 50 AND fast_ma > slow_ma, 1, 0) AS long_today,
        lagInFrame(if(session_no >= 50 AND fast_ma > slow_ma, 1, 0), 1)
            OVER (ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS long_prior,
        lagInFrame(px, 1)
            OVER (ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS px_prior
    FROM averaged
)
SELECT
    toYear(d)                                                                   AS year,
    round((exp(sum(log(if(long_prior = 1, px / px_prior, 1.0)))) - 1) * 100, 1) AS rule_pct,
    round((exp(sum(log(px / px_prior))) - 1) * 100, 1)                          AS hold_pct,
    countIf(long_today != long_prior)                                           AS crossover_count
FROM positioned
WHERE px_prior > 0
  AND toYear(d) >= 2017
GROUP BY year
ORDER BY year
Run this yourself

একটি অপরিবর্তিত rule, 9 বার আলাদাভাবে পরিমাপ করা হয়েছে। অন্য কিছু পড়ার আগে দুইটি percent column ওপর থেকে নিচে পড়ুন। 2017-এ rule-টি বছর শেষ করেছে 16%-এ, যেখানে একই সময়ে SPY ধরে রাখার ফল ছিল 19.4%। 2025-এ একই দুইটি column-এর মান যথাক্রমে 10.4% এবং 16.4%। দুইটি row-তেই code অভিন্ন। শুধু window পরিবর্তিত হয়েছে।

Crossover column দেখায় underlying evidence কতটা সীমিত। 4 position change 2025-এর মধ্যে হয়েছে। অর্থাৎ equity curve-এর একটি পুরো বছর মাত্র কয়েকটি সিদ্ধান্তের ওপর নির্ভর করছে। এটিকে ফলাফল বলার জন্য এটি খুবই ছোট sample।

অন্যান্য শেয়ারেও কি একই নিয়ম একইভাবে কাজ করে?

তারিখের সময়সীমা পরিবর্তন করে একটি নির্দিষ্ট কার্ভ বিশ্লেষণ করা যায়। আরেকটি উপায় হলো বিনিয়োগযোগ্য শেয়ারের পরিসর পরিবর্তন করা। নিচের প্যানেলে প্যারামিটার অপরিবর্তিত রেখে 2021 থেকে 2025—এই পাঁচ ক্যালেন্ডার বছরে পাঁচটি লিকুইড শেয়ারে একই নিয়ম প্রয়োগ করা হয়েছে।

কুয়েরিপাঁচটি লিকুইড নামের ক্ষেত্রে একই 20/50 নিয়ম, 2021 থেকে 2025
প্রতিটি সংখ্যার পেছনের সঠিক SQL
WITH daily AS
(
    SELECT
        ticker,
        toDate(toTimeZone(window_start, 'America/New_York')) AS d,
        toFloat64(argMax(close, window_start))               AS px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker IN ('SPY', 'QQQ', 'AAPL', 'MSFT', 'KO')
      AND window_start >= '2020-07-01 00:00:00'
      AND window_start <  '2026-01-01 05:00:00'
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) >= 570
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) < 960
    GROUP BY ticker, d
),
averaged AS
(
    SELECT
        ticker,
        d,
        px,
        avg(px) OVER (PARTITION BY ticker ORDER BY d ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) AS fast_ma,
        avg(px) OVER (PARTITION BY ticker ORDER BY d ROWS BETWEEN 49 PRECEDING AND CURRENT ROW) AS slow_ma,
        row_number() OVER (PARTITION BY ticker ORDER BY d)                                      AS session_no
    FROM daily
),
positioned AS
(
    SELECT
        ticker,
        d,
        px,
        lagInFrame(if(session_no >= 50 AND fast_ma > slow_ma, 1, 0), 1)
            OVER (PARTITION BY ticker ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS long_prior,
        lagInFrame(px, 1)
            OVER (PARTITION BY ticker ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS px_prior
    FROM averaged
)
SELECT
    ticker                                                                      AS symbol,
    round((exp(sum(log(if(long_prior = 1, px / px_prior, 1.0)))) - 1) * 100, 1) AS rule_pct,
    round((exp(sum(log(px / px_prior))) - 1) * 100, 1)                          AS hold_pct,
    round(avg(long_prior) * 100, 0)                                             AS days_long_pct
FROM positioned
WHERE px_prior > 0
  AND d >= toDate('2021-01-01')
GROUP BY symbol
ORDER BY rule_pct DESC
Run this yourself

প্যানেলের শীর্ষে রয়েছে QQQ, যার ফলাফল 40.2%। নিচের সারিতে থাকা KO-এর ফলাফল 3.8%। days_long_pct কলামে দেখানো হয়েছে, প্রতিটি সংস্করণ মোট সময়সীমার কতটা অংশ কোনো পজিশন ধরে রেখেছিল; শীর্ষ সারির ক্ষেত্রে তা 67%। একটি প্যারামিটার সেট, পাঁচটি বিনিয়োগযোগ্য শেয়ারের পরিসর, এবং ফলাফলের এত বিস্তৃত পার্থক্য—পরে বিজয়ী বেছে নিলে এমন কোনো ট্রেড সম্পর্কে কিছুই জানা যায় না, যা আপনি এখনও করেননি।

এগুলো crossover ট্রেড করার সুপারিশ নয়। Backtest-এ crossover কেবল একটি পরিমাপক। আর আমরা যে বিষয়টি পরিমাপ করছি, সেটি হলো backtest।

ওয়েটস ব্যাকটেস্টে look-ahead bias কোথায় ঢুকে পড়ে

Weights-ভিত্তিক engine একটি signal-কে target weights-এ রূপান্তর করে, ওই weights-এর ভিত্তিতে fills সিমুলেট করে, তারপর ফলস্বরূপ positions থেকে portfolio value পুনর্গঠন করে। ত্রুটিটি signal এবং weight-এর সংযোগস্থলে লুকিয়ে থাকে। যদি আজকের weight আজকের close থেকে নির্ধারিত হয় এবং তারপর সেই weight আজকের return অর্জন করে, তাহলে backtest এমন তথ্যের ভিত্তিতে ট্রেড করেছে যা order দেওয়ার সময় বিদ্যমান ছিল না। এটিই look-ahead bias। এতে কোনো error দেখা যায় না। শুধু সব ফলাফলকে বাস্তবের চেয়ে ভালো মনে হয়।

নিচের panel-এ একই SPY history ব্যবহার করে একটি rule-এর উভয় সংস্করণ চালানো হয়েছে।

কুয়েরিএকই নিয়ম, পূর্ববর্তী সেশনের সংকেত বনাম একই সেশনের সংকেত, বছরভিত্তিক SPY
প্রতিটি সংখ্যার পেছনের সঠিক SQL
WITH daily AS
(
    SELECT
        toDate(toTimeZone(window_start, 'America/New_York')) AS d,
        toFloat64(argMax(close, window_start))               AS px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker = 'SPY'
      AND window_start >= '2016-01-01 00:00:00'
      AND window_start <  '2026-01-01 05:00:00'
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) >= 570
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) < 960
    GROUP BY d
),
averaged AS
(
    SELECT
        d,
        px,
        avg(px) OVER (ORDER BY d ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) AS fast_ma,
        avg(px) OVER (ORDER BY d ROWS BETWEEN 49 PRECEDING AND CURRENT ROW) AS slow_ma,
        row_number() OVER (ORDER BY d)                                      AS session_no
    FROM daily
),
positioned AS
(
    SELECT
        d,
        px,
        if(session_no >= 50 AND fast_ma > slow_ma, 1, 0) AS long_today,
        lagInFrame(if(session_no >= 50 AND fast_ma > slow_ma, 1, 0), 1)
            OVER (ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS long_prior,
        lagInFrame(px, 1)
            OVER (ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS px_prior
    FROM averaged
)
SELECT
    toYear(d)                                                                   AS year,
    round((exp(sum(log(if(long_prior = 1, px / px_prior, 1.0)))) - 1) * 100, 1) AS next_bar_pct,
    round((exp(sum(log(if(long_today = 1, px / px_prior, 1.0)))) - 1) * 100, 1) AS same_bar_pct,
    round(abs(exp(sum(log(if(long_today = 1, px / px_prior, 1.0))))
              - exp(sum(log(if(long_prior = 1, px / px_prior, 1.0))))) * 100, 1) AS gap_pp
FROM positioned
WHERE px_prior > 0
  AND toYear(d) >= 2017
GROUP BY year
ORDER BY year
Run this yourself

2017-এ prior-session সংস্করণে 16% প্রিন্ট হয়েছে, আর একই-session সংস্করণে 17.1% প্রিন্ট হয়েছে; দুটির ব্যবধান ছিল 1.1 percentage points। 2025-এ দুই মাপের ব্যবধান ছিল 1.6 percentage points। যে machine session কোথায় বন্ধ হবে তা আগে থেকে জানত না, তার output হিসেবে এই column দুটির মধ্যে কেবল একটিই তৈরি হতে পারে। তাদের পার্থক্য নিছক accounting-এর ফল; এর পেছনে কোনো ধারণা, দক্ষতা বা বাস্তব trade নেই।

Timing নিয়ে এই engine নিজেই একটি অবস্থান জানায়। সেটি কোনো প্রতিশ্রুতির চেয়ে বেশি নির্ভরযোগ্য:

Open-এ fills-এর ক্ষেত্রে range-sensitive slippage কেবল আগের সম্পূর্ণ হওয়া bar দেখে, এবং volume capacity lagged observations থেকে forecast করা হয়। Engine ওই দিনের পরবর্তী high, low, close বা পুরো দিনের volume ব্যবহার করে না।
Source: quantjourney-bt README, version 0.12.4, read 6 August 2026.

নথিভুক্ত assumption আপনি ইতিমধ্যে ইনস্টল করা source-এর সঙ্গে মিলিয়ে যাচাই করতে পারেন। অনথিভুক্ত assumption কেবল একটি অনুমান।

ওয়াক-ফরোয়ার্ড অতিরিক্ত অংশটি কেন আছে

WF01 থেকে WF05 পর্যন্ত walk-forward উদাহরণগুলোর সঙ্গে [wf] অতিরিক্ত অংশ এবং এর Optuna dependency রয়েছে। Walk-forward পদ্ধতিতে ইতিহাসের একটি অংশের ওপর parameter fit করা হয়, পরবর্তী অংশে সেগুলোর ফল মাপা হয়, তারপর জোড়াটি সামনে এগিয়ে নিয়ে একই প্রক্রিয়া পুনরাবৃত্তি করা হয়। Rolling এবং expanding variant-এর পার্থক্য হলো, এগিয়ে যাওয়ার সময় fitting window তার সবচেয়ে পুরোনো data বাদ দেয় কি না। আরেকটি উদাহরণ প্রতিটি boundary-তে purge এবং embargo যোগ করে। এতে seam-এর কাছাকাছি observations বাদ পড়ে, যাতে fit করা slice যে slice-এ মাপা হচ্ছে সেখানে leak করতে না পারে।

এর কোনোটিই দুর্বল ধারণাকে কার্যকর কৌশলে পরিণত করে না। এটি একটি মাত্র সংখ্যার বদলে এমন একটি সংখ্যাবণ্টন দেয়, যার ভিত্তিতে আপনি ফল নিয়ে প্রশ্ন তুলতে পারেন। উন্নতিটা মূলত এখানেই। এর পরের ধাপও live money নয়: real money ব্যবহারের আগে paper trading backtest কাঠামোগতভাবে যা দেখতে পারে না, তা মাপে। প্রথমেই দেখা হয়, simulator যে price ধরে নিয়েছিল তার কাছাকাছি আপনার order আদৌ fill হয় কি না। আর trading bot-এর জন্য circuit breaker সেই দিন আপনার code কী করে, যেদিন code প্রত্যাশামতো কাজ করে না, তা সামলায়। এর পেছনের statistics সম্পর্কে আরও গভীরে যেতে আমাদের open-source quant trading book-এর নোট দেখুন।

প্রায়শই জিজ্ঞাসিত প্রশ্ন

API key ছাড়া কি কোনো কৌশলের backtest করা যায়?

হ্যাঁ। quantjourney-bt-এর সঙ্গে --sample-data flag-এর অধীনে একটি sample dataset অন্তর্ভুক্ত থাকে। উদাহরণ কৌশলগুলো কোনো account বা credentials ছাড়াই ওই dataset-এর ওপর চালানো যায়। Dataset-টি ছোট এবং উদাহরণনির্ভর। তাই এই run-কে কোনো কৌশল সম্পর্কে প্রমাণ হিসেবে নয়, বরং installation এবং pipeline পরীক্ষা হিসেবে বিবেচনা করুন।

Python backtesting package-এর version pin করা হয় কেন?

0.x version-এর package-এ minor release-এর মধ্যে compatibility-এর কোনো নিশ্চয়তা থাকে না। Default পরিবর্তন বা metric-এর নাম বদলে গেলেও তা আলাদাভাবে জানানো নাও হতে পারে। pip install quantjourney-bt==0.12.4 ব্যবহার করে version pin করা এবং requirements file-এ environment-এর তথ্য সংরক্ষণ করা হলে আজ তৈরি করা ফলাফল আগামী বছরও একই ফলাফল তৈরি করা engine-এ পুনর্নির্মাণ করা যায়।

quantjourney-bt কোন licence-এর অধীনে প্রকাশিত?

Apache License 2.0। এটি commercial use এবং modification অনুমোদন করে। যেকোনো redistribution-এর সঙ্গে licence এবং notice file রাখতে হয়। এতে contributors-দের পক্ষ থেকে একটি explicit patent grant-ও রয়েছে। Version 0.12.4 21 July 2026-এ প্রকাশিত হয়েছে এবং Python 3.11 বা তার পরের version প্রয়োজন।

শক্তিশালী backtest result কি প্রমাণ করে যে কৌশলটি কাজ করে?

না। Backtest হলো একটি নির্দিষ্ট window, একটি নির্দিষ্ট universe এবং একটি নির্দিষ্ট সময়পর্বে করা একটি পরিমাপ। উপরের panel-গুলো দেখায়, কোনো পরিবর্তন না করা একই rule একটি ticker-এ বিভিন্ন বছরে খুব ভিন্ন ফল দিতে পারে। আবার পাঁচটি নামের মধ্যেও ফলাফল অনেক আলাদা হতে পারে। Walk-forward validation এবং out-of-sample testing এই পার্থক্যই প্রকাশ করার জন্য ব্যবহৃত হয়।

উপরের panel-গুলোর হিসাব কীভাবে করা হয়েছে

প্রতিটি তারিখের জন্য daily close নেওয়া হয়েছে New York clock time অনুযায়ী 9:30 a.m. থেকে 4:00 p.m.-এর মধ্যে হওয়া regular session-এর শেষ minute print হিসেবে। এর ফলে session length hardcode না করেও early-close half day সঠিকভাবে অন্তর্ভুক্ত হয়। Fast average 20টি session এবং slow average 50টি session নিয়ে হিসাব করা হয়েছে। দুটিই simple average। প্রতিটি series-এর প্রথম 49টি session warm-up হিসেবে ধরা হয়েছে, যেখানে কোনো position রাখা হয়নি। Yearly figure-এ rule long থাকা session-গুলোর close-to-close movement compound করা হয়েছে। তুলনার জন্য hold column-এ একই বছরের প্রতিটি session compound করা হয়েছে। Cross-section-এর পাঁচটি নাম এমন continuous history-এর ভিত্তিতে বাছাই করা হয়েছে, যার window-এর মধ্যে কোনো split নেই। তাই close series-এ adjustment প্রয়োজন হয়নি। Window-গুলো অতীতের নির্দিষ্ট সময়পর্বে স্থির করা আছে। তাই প্রতিবার regeneration করলে এই panel-গুলো একই number দেখায়।


এখানকার প্রতিটি panel-এর নিচে সম্পূর্ণ SQL দেওয়া আছে। ফলে pinned install-এর মতো এই page-এর number-গুলোও পুনরায় run করা যায়। কোনো backtest code লেখার আগে নিজের পছন্দের window-এ একটি rule পরিমাপ করতে Strasmore terminal-এ plain English-এ প্রশ্নটি করুন।

#backtesting#python#open source#reproducibility#quantjourney