LLM কি alpha factor খুঁজে পেতে পারে?
একটি LLM এক ঘণ্টায় একশোটি alpha factor লিখতে পারে। 10 বছরের real price-এ 240টি coin-flip factor-এর ফল দেখুন এবং টিকে থাকা factor পরীক্ষা করার পদ্ধতি জানুন।
একটি LLM সারাদিন alpha factor প্রস্তাব করতে পারে। একটি সক্ষম মডেলকে data dictionary এবং scoring harness দিন, আর দুপুরের আগেই এটি একশোটি বিশ্বাসযোগ্য factor expression লিখে ফেলবে। কঠিন প্রশ্নটি এর নিচেই রয়েছে: এগুলোর কোনোটি সত্যিই কার্যকর কি না, তা কেউ কীভাবে জানবে, যখন যে search এটি তৈরি করেছে সেটিই noise থেকে বিজয়ী বানানোর যন্ত্র?
alpha factor কী?
একটি factor হলো এমন একটি নিয়ম, যা market data-কে প্রতিটি তারিখে প্রতিটি stock-এর জন্য একটি সংখ্যায় রূপান্তর করে। 12-month price change একটি factor। Debt-to-equity ratio-ও তাই। কোনো factor-কে universe অনুযায়ী rank করে শীর্ষ অংশ কেনা, নিম্ন অংশ বিক্রি করা এবং নির্দিষ্ট সময়সূচিতে portfolio rebalance করলে সেটি strategy হয়ে যায়। Alpha হলো সাধারণ market exposure থেকে স্বাভাবিকভাবে পাওয়া return বাদ দেওয়ার পর অবশিষ্ট return।
প্রার্থীদের Sharpe ratio দিয়ে মূল্যায়ন করা হয়: average return-কে সেই return-এর standard deviation দিয়ে ভাগ করে এক বছরে scale করা হয়। এটি প্রতি unit volatility-র বিপরীতে return মাপে। Live strategy-তে দীর্ঘমেয়াদি Sharpe প্রায় 1 হলে তা সম্মানজনক। পরের বার কোনো backtest 3 Sharpe দেখালে এই বিষয়টি মনে রাখুন।
LLM factor research বাস্তবে কীভাবে কাজ করে
এই ক্ষেত্রের প্রতিটি project একই ধরনের একটি loop চালায়।
- Model এমন factor expression লেখে, যা harness একটি ছোট language-এ evaluate করতে পারে।
- Backtester prices এবং fundamentals-এর নির্দিষ্ট history-র ওপর প্রতিটি expression-এর score নির্ধারণ করে।
- নির্ধারিত threshold-এর বেশি score পাওয়া expression রাখা হয়। বাকিগুলো বাদ দেওয়া হয়।
- রাখা expression-গুলো worked example হিসেবে আবার model-এর context-এ ফেরত যায়, তারপর loop পুনরায় চলে।
Multi-agent trading systems এই কাজগুলোকে আলাদা role-এ ভাগ করে। একজন প্রস্তাব করে, অন্যজন পরীক্ষা করে। এই plumbing সত্যিই কার্যকর। একজন মানুষের যে market data skills an AI agent needs, সেগুলোই এখানে প্রয়োজন।
এই loop-এর কোনো অংশ অসৎ নয়। Research করার একটি পদ্ধতিই হলো search। সমস্যা arithmetic-এ, এবং step 2 কয়েকবারের বেশি চললেই তা দেখা দেয়।
কেন LLM alpha factor search বিজয়ী তৈরি করে
একটি price history। হাজারো সস্তা hypothesis। প্রতিটি hypothesis একই সীমিত sample-এর বিপরীতে score করা হয়, আর সেই sample-এ প্রচুর randomness থাকে। যথেষ্ট নিয়ম পরীক্ষা করলে কিছু নিয়ম সেই randomness-এর সঙ্গে ঘনিষ্ঠভাবে fit করবেই। Score আপনাকে বলতে পারে না কোন ধরনের fit হয়েছে। কারণ noise-এর সঙ্গে মিলে যাওয়া নিয়ম এবং market movement-এর সঙ্গে মিলে যাওয়া নিয়ম একই সংখ্যা print করে।
এখানে null hypothesis-টি 240 বার আঁকা হয়েছে। নিচের প্রতিটি "factor" একটি coin flip: ticker, month এবং trial number-এর hash ব্যবহার করে প্রতি মাসে 40টি বড় US নামকে দুটি অর্ধে ভাগ করা হয়েছে। Strategy-টি এক অর্ধে long এবং অন্য অর্ধে short position ধরে। গঠনগতভাবেই এতে কোনো তথ্য নেই। January 2016 থেকে June 2021 পর্যন্ত বাস্তব month-end return দিয়ে score করলে 240টি trial-এর ফলাফল এমন হয়।
প্রতিটি সংখ্যার পেছনের সঠিক SQL
WITH month_end AS (
SELECT ticker,
toStartOfMonth(toDate(toTimeZone(window_start, 'America/New_York'))) AS month_start,
argMax(toFloat64(close), window_start) AS close_px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('AAPL','ADBE','AMZN','BA','CAT','COST','CRM','CSCO','CVX','DE',
'DUK','GE','GOOGL','HD','HON','IBM','INTC','JNJ','JPM','KO',
'LMT','MCD','MMM','MRK','MSFT','NKE','NVDA','ORCL','PEP','PFE',
'PG','QCOM','SO','T','TGT','TXN','UNP','VZ','WMT','XOM')
AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2015-12-01')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2021-06-30')
AND toDayOfMonth(toTimeZone(window_start, 'America/New_York')) >= 22
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY ticker, month_start
),
lagged AS (
SELECT ticker,
month_start,
close_px,
lagInFrame(close_px) OVER (PARTITION BY ticker ORDER BY month_start
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_px
FROM month_end
),
monthly_return AS (
SELECT ticker, month_start, close_px / prev_px - 1 AS ret
FROM lagged
WHERE prev_px > 0
AND month_start >= toDate('2016-01-01')
),
trial AS (
SELECT arrayJoin(range(1, 241)) AS n
),
factor_month AS (
SELECT t.n AS trial_id,
m.month_start AS month_start,
avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1)
- avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) AS long_short_ret
FROM monthly_return AS m
CROSS JOIN trial AS t
GROUP BY trial_id, month_start
HAVING countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1) > 0
AND countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) > 0
),
scored AS (
SELECT trial_id,
avg(long_short_ret) / stddevSamp(long_short_ret) * sqrt(12) AS sharpe
FROM factor_month
GROUP BY trial_id
HAVING stddevSamp(long_short_ret) > 0
)
SELECT multiIf(sharpe < -1.2, 'below -1.2',
sharpe < -0.8, '-1.2 to -0.8',
sharpe < -0.4, '-0.8 to -0.4',
sharpe < 0.0, '-0.4 to 0.0',
sharpe < 0.4, '0.0 to 0.4',
sharpe < 0.8, '0.4 to 0.8',
sharpe < 1.2, '0.8 to 1.2',
'1.2 and above') AS sharpe_bucket,
count() AS factor_count,
round(100 * count() / 240, 1) AS share_pct
FROM scored
GROUP BY sharpe_bucket
ORDER BY min(sharpe)এই spread-ই মূল বিষয়। ওই chart-এর কিছুই কোনো কিছুর পূর্বাভাস দেয় না। তবু 1টি trial শীর্ষ band-এ (1.2 and above), search-এর 0.4% অংশে, এবং 1টি নিম্ন band-এ (below -1.2) পড়েছে। কোনো researcher যদি একটি ভাগ্যবান trial চালিয়ে থেমে যেতেন, তাঁর হাতে একটি chart এবং একটি Sharpe ratio থাকত। কিন্তু কোনোটিই discovery কি না, তা বোঝার উপায় থাকত না। এখানে return month-end close থেকে পরের month-end close পর্যন্ত মাপা হয়েছে। মাসিক return কীভাবে মাপা হয়-এ সেই arithmetic ব্যাখ্যা করা আছে।
গুরুত্বপূর্ণ সংখ্যা হলো আপনি কতগুলো পরীক্ষা করেছেন
একটি backtest একা report করলে তার denominator অনুপস্থিত থাকে। একই 240টি trial-কে এমন একটি search হিসেবে দেখুন, যা ক্রমশ বিস্তৃত হচ্ছে। প্রতিটি ধাপে board-এর সর্বোচ্চ score-কে এ পর্যন্ত চেষ্টা করা সব score-এর average-এর পাশে দেখানো হয়েছে।
প্রতিটি সংখ্যার পেছনের সঠিক SQL
WITH month_end AS (
SELECT ticker,
toStartOfMonth(toDate(toTimeZone(window_start, 'America/New_York'))) AS month_start,
argMax(toFloat64(close), window_start) AS close_px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('AAPL','ADBE','AMZN','BA','CAT','COST','CRM','CSCO','CVX','DE',
'DUK','GE','GOOGL','HD','HON','IBM','INTC','JNJ','JPM','KO',
'LMT','MCD','MMM','MRK','MSFT','NKE','NVDA','ORCL','PEP','PFE',
'PG','QCOM','SO','T','TGT','TXN','UNP','VZ','WMT','XOM')
AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2015-12-01')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2021-06-30')
AND toDayOfMonth(toTimeZone(window_start, 'America/New_York')) >= 22
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY ticker, month_start
),
lagged AS (
SELECT ticker,
month_start,
close_px,
lagInFrame(close_px) OVER (PARTITION BY ticker ORDER BY month_start
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_px
FROM month_end
),
monthly_return AS (
SELECT ticker, month_start, close_px / prev_px - 1 AS ret
FROM lagged
WHERE prev_px > 0
AND month_start >= toDate('2016-01-01')
),
trial AS (
SELECT arrayJoin(range(1, 241)) AS n
),
factor_month AS (
SELECT t.n AS trial_id,
m.month_start AS month_start,
avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1)
- avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) AS long_short_ret
FROM monthly_return AS m
CROSS JOIN trial AS t
GROUP BY trial_id, month_start
HAVING countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1) > 0
AND countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) > 0
),
scored AS (
SELECT trial_id,
avg(long_short_ret) / stddevSamp(long_short_ret) * sqrt(12) AS sharpe
FROM factor_month
GROUP BY trial_id
HAVING stddevSamp(long_short_ret) > 0
),
ladder AS (
SELECT arrayJoin([1, 2, 5, 10, 25, 50, 100, 160, 240]) AS n
)
SELECT l.n AS factors_tried,
round(max(s.sharpe), 2) AS best_sharpe,
round(avg(s.sharpe), 2) AS average_sharpe
FROM ladder AS l
CROSS JOIN scored AS s
WHERE s.trial_id <= l.n
GROUP BY factors_tried
ORDER BY factors_triedRunning maximum কেবল বাড়তে পারে। এটাই ফাঁদ। প্রথম পরীক্ষা করা নিয়মটির score ছিল 0.44। 240টি trial-এর পর board-এর সেরা score দাঁড়ায় 1.59, আর সব trial-এর average থাকে 0.01-এ। কোনো নিয়ম উন্নত না হলেও headline উন্নত হয়েছে। যে harness 10,000টি expression evaluate করে, সেটি এখানে দেখানো সীমার অনেক ডান দিকে এই curve চালাচ্ছে। সেটি যে সংখ্যা report করে, তা হলো ওই curve-এর সর্বোচ্চ বিন্দু।
Holdout period বিজয়ীদের সঙ্গে কী করে
সাধারণ প্রতিরোধ ব্যবস্থা হলো holdout: একটি period-এ score করা, তারপর search যে পরবর্তী period কখনো দেখেনি সেখানে টিকে থাকা নিয়মগুলোকে আবার score করা। Training window-এ সেরা 12টি coin flip নিন এবং পরের 5 বছর, July 2021 থেকে June 2026 পর্যন্ত, একই নিয়মে চালান।
প্রতিটি সংখ্যার পেছনের সঠিক SQL
WITH month_end AS (
SELECT ticker,
toStartOfMonth(toDate(toTimeZone(window_start, 'America/New_York'))) AS month_start,
argMax(toFloat64(close), window_start) AS close_px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('AAPL','ADBE','AMZN','BA','CAT','COST','CRM','CSCO','CVX','DE',
'DUK','GE','GOOGL','HD','HON','IBM','INTC','JNJ','JPM','KO',
'LMT','MCD','MMM','MRK','MSFT','NKE','NVDA','ORCL','PEP','PFE',
'PG','QCOM','SO','T','TGT','TXN','UNP','VZ','WMT','XOM')
AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2015-12-01')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-06-30')
AND toDayOfMonth(toTimeZone(window_start, 'America/New_York')) >= 22
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY ticker, month_start
),
lagged AS (
SELECT ticker,
month_start,
close_px,
lagInFrame(close_px) OVER (PARTITION BY ticker ORDER BY month_start
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_px
FROM month_end
),
monthly_return AS (
SELECT ticker, month_start, close_px / prev_px - 1 AS ret
FROM lagged
WHERE prev_px > 0
AND month_start >= toDate('2016-01-01')
),
trial AS (
SELECT arrayJoin(range(1, 241)) AS n
),
factor_month AS (
SELECT t.n AS trial_id,
m.month_start AS month_start,
avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1)
- avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) AS long_short_ret
FROM monthly_return AS m
CROSS JOIN trial AS t
GROUP BY trial_id, month_start
HAVING countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1) > 0
AND countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) > 0
),
scored AS (
SELECT trial_id,
avgIf(long_short_ret, month_start < toDate('2021-07-01'))
/ stddevSampIf(long_short_ret, month_start < toDate('2021-07-01')) * sqrt(12) AS in_sample_sharpe,
avgIf(long_short_ret, month_start >= toDate('2021-07-01'))
/ stddevSampIf(long_short_ret, month_start >= toDate('2021-07-01')) * sqrt(12) AS out_of_sample_sharpe
FROM factor_month
GROUP BY trial_id
HAVING countIf(month_start < toDate('2021-07-01')) >= 24
AND countIf(month_start >= toDate('2021-07-01')) >= 24
)
SELECT concat('trial ', toString(trial_id)) AS factor_label,
round(in_sample_sharpe, 2) AS in_sample_sharpe,
round(out_of_sample_sharpe, 2) AS out_of_sample_sharpe
FROM scored
ORDER BY in_sample_sharpe DESC
LIMIT 12প্রতিটি bar pair একটি নিয়মের প্রতিনিধিত্ব করে। বাম bar হলো report-এ স্থান পাওয়ার জন্য নিয়মটি যে score অর্জন করেছিল। ডান bar হলো পরবর্তী 5 বছরে একই নিয়মের score। শীর্ষ-ranked trial training-এ 1.59 score পেয়েছিল এবং পরে -0.51; দ্বাদশ trial পেয়েছিল 0.74 এবং পরে 0.49।
12টি নিয়ম নিজেই একটি ছোট sample। Training score অনুযায়ী সব 240টি নিয়মকে পাঁচটি সমান অংশে ভাগ করে প্রতিটি অংশের holdout score-এর average নিলে আরও পরিষ্কার চিত্র পাওয়া যায়।
প্রতিটি সংখ্যার পেছনের সঠিক SQL
WITH month_end AS (
SELECT ticker,
toStartOfMonth(toDate(toTimeZone(window_start, 'America/New_York'))) AS month_start,
argMax(toFloat64(close), window_start) AS close_px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('AAPL','ADBE','AMZN','BA','CAT','COST','CRM','CSCO','CVX','DE',
'DUK','GE','GOOGL','HD','HON','IBM','INTC','JNJ','JPM','KO',
'LMT','MCD','MMM','MRK','MSFT','NKE','NVDA','ORCL','PEP','PFE',
'PG','QCOM','SO','T','TGT','TXN','UNP','VZ','WMT','XOM')
AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2015-12-01')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-06-30')
AND toDayOfMonth(toTimeZone(window_start, 'America/New_York')) >= 22
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY ticker, month_start
),
lagged AS (
SELECT ticker,
month_start,
close_px,
lagInFrame(close_px) OVER (PARTITION BY ticker ORDER BY month_start
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_px
FROM month_end
),
monthly_return AS (
SELECT ticker, month_start, close_px / prev_px - 1 AS ret
FROM lagged
WHERE prev_px > 0
AND month_start >= toDate('2016-01-01')
),
trial AS (
SELECT arrayJoin(range(1, 241)) AS n
),
factor_month AS (
SELECT t.n AS trial_id,
m.month_start AS month_start,
avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1)
- avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) AS long_short_ret
FROM monthly_return AS m
CROSS JOIN trial AS t
GROUP BY trial_id, month_start
HAVING countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1) > 0
AND countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) > 0
),
scored AS (
SELECT trial_id,
avgIf(long_short_ret, month_start < toDate('2021-07-01'))
/ stddevSampIf(long_short_ret, month_start < toDate('2021-07-01')) * sqrt(12) AS in_sample_sharpe,
avgIf(long_short_ret, month_start >= toDate('2021-07-01'))
/ stddevSampIf(long_short_ret, month_start >= toDate('2021-07-01')) * sqrt(12) AS out_of_sample_sharpe
FROM factor_month
GROUP BY trial_id
HAVING countIf(month_start < toDate('2021-07-01')) >= 24
AND countIf(month_start >= toDate('2021-07-01')) >= 24
),
ranked AS (
SELECT trial_id,
in_sample_sharpe,
out_of_sample_sharpe,
row_number() OVER (ORDER BY in_sample_sharpe DESC) AS in_sample_rank
FROM scored
)
SELECT multiIf(in_sample_rank <= 48, 'best fifth in training',
in_sample_rank <= 96, 'second fifth',
in_sample_rank <= 144, 'middle fifth',
in_sample_rank <= 192, 'fourth fifth',
'worst fifth in training') AS training_group,
round(avg(in_sample_sharpe), 2) AS avg_in_sample_sharpe,
round(avg(out_of_sample_sharpe), 2) AS avg_out_of_sample_sharpe
FROM ranked
GROUP BY training_group
ORDER BY min(in_sample_rank)Training-এ শীর্ষ group-এর 0.66 থেকে নিম্ন group-এর -0.63 পর্যন্ত ফলাফল রয়েছে। এটি একটি বিস্তৃত এবং সম্পূর্ণ সুশৃঙ্খল ladder। কারণ group-গুলো ওই score দিয়েই তৈরি করা হয়েছে। Holdout-এ একই দুই প্রান্তের average হলো 0.01 এবং 0.13। Ladder সমতল হয়ে যায়। Holdout হলো pipeline-এর একমাত্র অংশ, যেটিকে optimize করা হয়নি। তাই এটি সংযতভাবে ব্যবহার করা মূল্যবান।
যে প্রতিরোধ ব্যবস্থা সত্যিই কাজ করে
একবার ব্যবহার করার জন্য রাখা holdout। Holdout-এ প্রতিবার তাকালে সেটি training data-তে পরিণত হয়। Walk-forward testing-এ window এগিয়ে যায়, এবং প্রতিটি score fit-এর পরের data থেকে আসে। বারবার ব্যবহারের পরও টিকে থাকে এই পদ্ধতিই।
Multiple-testing adjustment। Bailey এবং López de Prado 2014 সালে প্রবর্তিত deflated Sharpe ratio কোনো observed Sharpe-কে discount করে। এটি বিবেচনা করে কতগুলো trial চালানো হয়েছে, sample কত দীর্ঘ, return কতটা skewed এবং tail কতটা fat। সঠিক trial count এবং 10,000-expression search থেকে পাওয়া headline Sharpe দিলে তা প্রায়ই শূন্যে নেমে আসে।
পরীক্ষা করা প্রতিটি expression-এর audit trail, বাদ দেওয়া expression-সহ। এটিই সবচেয়ে গুরুত্বপূর্ণ বিষয়। তাই factor-research project-এর বর্ণনায় "auditable" শব্দটি গুরুত্বপূর্ণ। Deflation-এর জন্য trial count দরকার। যে pipeline কেবল বিজয়ীদের log করে, সেটি নিজের correction-এর input নষ্ট করে। বাদ দেওয়া draft, পরিত্যক্ত parameter sweep, researcher's নিজের restart এবং scoring code-এর প্রতিটি আগের version—সবই ওই সংখ্যার অন্তর্ভুক্ত।
Score বিশ্বাস করার আগে cost এবং look-ahead bias পরীক্ষা। কোনো factor যদি vendor data load করার তারিখে stamp করা fundamentals field দিয়ে rank করা হয়, market যখন তথ্যটি জানতে পেরেছিল সেই তারিখে নয়, তাহলে backtest চমৎকার দেখাবে কিন্তু বাস্তব trading খারাপ হবে।
"Auditable" শব্দটি কীভাবে পড়বেন
এই ক্ষেত্রের নতুন repository প্রায় প্রতি সপ্তাহেই দেখা যায়। কয়েক ডজন star থাকা project track record নয়, prototype। Star count code-এর চেয়ে দ্রুত বদলায়। তাই এই page কোনো একটি project নয়, pattern মূল্যায়ন করে। যে project সামনে আসবে, সেটিতে প্রথমে যা দেখবেন:
- প্রতিটি candidate-এর expression এবং score timestamp-সহ log করা হয়, নাকি কেবল রাখা candidate-গুলো?
- Holdout কি harness প্রয়োগ করে, নাকি researcher's self-discipline-এর ওপর নির্ভর করে?
- Report করা প্রতিটি score-এর পাশে trial count থাকে কি?
- এটি কোন market-এর জন্য তৈরি? China A-shares-এর জন্য tuned library-তে daily price limit এবং একই session-এ কেনা share বিক্রির নিষেধাজ্ঞা থাকে। এই নিয়মের অধীনে কোনো factor-এর আচরণ US equities-এ একইভাবে প্রযোজ্য নয়।
- আপনি কি এটি আবার চালিয়ে একই সংখ্যা reproduce করতে পারবেন? আপনি যে exact commit পড়েছেন সেটি pin করুন। কারণ এই পর্যায়ের project-এ weekend থেকে weekend-এর মধ্যে scoring code বদলে যেতে পারে।
এর কোনোটিই factor research-এ LLM-কে অকার্যকর করে না। Hypothesis তৈরি করা একটি বাস্তব bottleneck, এবং model এতে ভালো। পরিবর্তন হলো দায়িত্বের অবস্থান: কতগুলো hypothesis পরীক্ষা করে বাদ দেওয়া হয়েছে, তার হিসাব রাখার দায়িত্ব। কোনো ফল live order book-এ পৌঁছানোর আগে backtest এবং বাস্তব fill-এর ব্যবধান paper trading-এ দৃশ্যমান হয়।
LLM alpha factor FAQ
LLM কি alpha factor খুঁজে পেতে পারে?
এটি হাজার হাজার factor প্রস্তাব করতে পারে। কিন্তু proposal এবং finding এক জিনিস নয়। Claim তৈরি হয় scoring step-এ। বিস্তৃত search থেকে পাওয়া score-এর সঙ্গে এমন একটি selection problem থাকে, যা score নিজে দেখতে পারে না। Expression-এর আগে holdout discipline এবং recorded trial count মূল্যায়ন করুন।
Deflated Sharpe ratio কী?
এটি এমন একটি correction, যা observed Sharpe ratio-কে এই সম্ভাবনায় রূপান্তর করে যে কোনো বাস্তব edge না থাকলেও ওই আকারের search একই ফল তৈরি করতে পারত কি না। Bailey এবং López de Prado এটি 2014 সালে প্রকাশ করেন। এর কেন্দ্রীয় input হলো trial-এর সংখ্যা। Unaudited research loop ঠিক এই সংখ্যাটিই দিতে পারে না।
কতগুলো backtest অতিরিক্ত?
কোনো নির্দিষ্ট threshold নেই। কেবল একটি adjustment আছে, যা প্রয়োগ করতেই হবে। একটি backtest-এ 1.0 score পাওয়া এবং 10,000টি backtest-এর মধ্যে সেরা score 1.0 হওয়া—বিশ্ব সম্পর্কে দুটি ভিন্ন দাবি। উপরের coin flip-গুলো কোনো data information ছাড়াই 1.59-এ পৌঁছেছিল, মোট 240টি trial-এর মধ্যে।
প্রকাশের পর published factor দুর্বল হয়ে যায় কেন?
Academic research কোনো anomaly প্রকাশের পরবর্তী বছরগুলোতে তার decay নথিভুক্ত করেছে। Crowding একটি সম্ভাব্য mechanism। নিজের sample-এর সঙ্গে overfit হওয়া original result আরেকটি mechanism। দুটির chart একই রকম দেখায়। efficient market hypothesis প্রথম mechanism-টির কাঠামো ব্যাখ্যা করে, আর উপরের trial-গুলো দ্বিতীয়টি দেখায়।
এখানের প্রতিটি panel বাস্তব month-end price-এর ওপর চালানো একটি stored query। এর নিচে SQL উন্মুক্ত রয়েছে। একটি copy করুন, trial count বাড়ান, এবং Strasmore terminal-এ সেরা সংখ্যাটি উঠতে দেখুন।