SSD Nodes Learn Hosting plans →

VPS-ல் சொந்தமாக Stock Research Agent உருவாக்குவது எப்படி?

Python, DuckDB மற்றும் systemd timer பயன்படுத்தி உங்கள் சொந்த Stock Research Agent உருவாக்குங்கள். சந்தை தரவுகளை சேமித்து LLM மூலம் பகுப்பாய்வு செய்யும் முறையை அறியுங்கள்.

சுய-வழங்கப்பட்ட (self-hosted) பங்குச் சந்தை ஆராய்ச்சி முகவர் என்றால் என்ன

சுய-வழங்கப்பட்ட பங்குச் சந்தை ஆராய்ச்சி முகவர் என்பது நீங்கள் சொந்தமாக வைத்திருக்கும் server-ல் இயங்கும் ஒரு சிறிய நிரலாகும். இது குறிப்பிட்ட கால இடைவெளியில் சந்தை தரவுகளைப் பெற்று, அவற்றை local database-ல் சேமித்து, அதன் மீது ஒரு திரையிடலை (screen) இயக்கி, என்ன மாற்றங்கள் நிகழ்ந்துள்ளன என்பதை விளக்குமாறு ஒரு பெரிய மொழி மாதிரியிடம் (LLM) கேட்கிறது. இது தரவுகளை வாசித்து வடிகட்டுகிறது. இது வர்த்தகம் செய்வதில்லை, மேலும் இந்த வழிகாட்டியில் உள்ள எதுவும் நிதி ஆலோசனையல்ல.

இதை புதிதாக உருவாக்கும் இருவர் வெவ்வேறு libraries-ஐத் தேர்ந்தெடுத்தாலும், இறுதியில் ஒரே மாதிரியான நான்கு பகுதிகளைக் கொண்டிருப்பார்கள்: விலைகள் மற்றும் அடிப்படைத் தரவுகளை வழங்கும் ஒரு feed, நீங்கள் இதுவரை பெற்ற ஒவ்வொரு வரிசையையும் வைத்திருக்கும் ஒரு local store, குறிப்பிட்ட கால இடைவெளியில் store-ஐப் புதுப்பிக்கும் ஒரு job, மற்றும் எஞ்சியிருக்கும் வரிசைகளை வாக்கியங்களாக மாற்றும் ஒரு LLM அடுக்கு. இந்த வழிகாட்டி Python, DuckDB, systemd timer மற்றும் Claude API (application programming interface) ஆகியவற்றைப் பயன்படுத்தி அந்த அமைப்பை உருவாக்குகிறது. இது ஒரு நிலையான சுழற்சியைக் கொண்ட குறுகிய நோக்கமுடைய முகவர். எனவே, நீங்கள் பொதுவான பதிப்பை முதலில் தெரிந்துகொள்ள விரும்பினால், AI முகவர்களைக் கற்றுக்கொள்வதற்கான படிநிலை பாதை (the staged path for learning AI agents) சுழற்சிகள், கருவிகள் மற்றும் நினைவகம் ஆகியவற்றை ஒரு குறிப்பிட்ட துறை சார்ந்த சிக்கலாக மாறுவதற்கு முன்பே வரிசையாக விளக்குகிறது. வர்த்தகச் செயல்பாடுகள் தனித்தனி வேலைகளாகவும், தனித்தனி தோல்வி முறைகளைக் கொண்டவையாகவும் இருப்பதால், அவை வர்த்தக பாட்களுக்காக அமைக்கப்பட்ட VPS (a VPS set up for trading bots)-ல் மட்டுமே இருக்க வேண்டும்.

நான்கு பகுதிகள் மற்றும் அவற்றின் செயல்பாடுகள்

The feed என்பது வெளி உலகத்துடன் தொடர்பு கொள்ளும் ஒரே பகுதியாகும். இது ஒரு ticker மற்றும் தேதி வரம்பை (date range) எவ்வாறு கேட்பது என்பதையும், தரவு வரிசைகளை (rows) எவ்வாறு திருப்பித் தருவது என்பதையும் அறியும். இதற்குக் கீழே உள்ள அனைத்துப் பகுதிகளும் feed-ஐ விட உங்கள் database-ஐயே வாசிக்கின்றன. எனவே, feed செயலிழந்தால், ஒரு நாள் புதிய தரவு மட்டுமே கிடைக்காது; மாறாக, திரை (screen) செயலிழக்காது.

The store என்பது இந்த முழுச் செயல்பாட்டின் முக்கிய நோக்கமாகும். நீங்கள் பதிவு செய்யாத ஒரு daily close தரவை, பொதுவாகப் பிற்காலத்தில் மீண்டும் பெற்றுக்கொள்ள முடியும். ஆனால், ஒரு intraday quote, திருத்தப்படுவதற்கு முந்தைய மதிப்பீடு அல்லது மறுசீரமைக்கப்படுவதற்கு முந்தைய அடிப்படைத் தரவு (fundamentals figure) ஆகியவற்றை மீண்டும் பெற முடியாது. தரவு வழங்கப்பட்ட நாளில் அது என்ன கூறியது என்பதற்கான பதிவை உருவாக்குவதே the store-ன் பணியாகும்.

The scheduler என்பது தரவு புதுப்பித்தல் எப்போது நடக்க வேண்டும் என்பதைத் தீர்மானிக்கிறது. ஒரு server-ல் இது systemd timer மூலம் இயங்குகிறது; இதனால்தான் குறியீட்டை (code) விட VPS இங்கு முக்கியத்துவம் பெறுகிறது.

The LLM layer என்பது உங்கள் SQL உருவாக்கிய ஒரு சிறிய உரைத் தொகுதியை (text block) வாசித்து, அதன் சுருக்கத்தை எழுதுகிறது. இது ஒருபோதும் database-உடன் இணைக்கப்படுவதில்லை மற்றும் query-ஐ உருவாக்குவதில்லை. ஒருவேளை model-லே SQL-ஐ எழுதினால், ஒரு தவறான token, சரளமான வாக்கியத்திற்குள் தவறான எண்ணாக மாறிவிடும்; அதை ஒப்பிட்டுப் பார்க்க எதுவுமிருக்காது. SQL தரவுகளை உருவாக்கினால், model உரைநடையை (prose) மட்டுமே தவறாக எழுத முடியும்; நீங்கள் அனுப்பிய தரவு வரிசைகளை வைத்து அந்த உரைநடையைச் சரிபார்க்க முடியும்.

ஏன் ஒரு மடிக்கணினிக்கு பதிலாக VPS-ல் இயக்க வேண்டும்

இதற்கான காரணம் scheduler ஆகும். நியூயார்க் நேரப்படி மாலை 16:00 மணிக்கு அமெரிக்க சந்தை முடிவடையும் நேரம், பெர்லினில் 22:00 மணி மற்றும் ஜகார்த்தாவில் அடுத்த நாள் காலை 04:00 மணி ஆகும். இந்த இரண்டு நேரங்களிலும் மடிக்கணினி உறக்க நிலையில் (asleep) இருக்கும். ஒருமுறை இயக்கம் தவறுவது, ஒரு குறிப்பு தாமதமாவதை விட அதிக இழப்பை ஏற்படுத்தும்: தினசரி தரவுகளை (daily bars) பின்னர் மீண்டும் பெற்றுக்கொள்ள முடியும், ஆனால் திருத்தப்படும் எந்தவொரு தரவையும் மீண்டும் பெற முடியாது, எனவே உங்கள் பதிவுகளில் ஏற்படும் இடைவெளி நிரந்தரமானதாகிவிடும். ஒரு முகவரின் (agent) கால அட்டவணையும் சேமிக்கப்பட்ட நிலையும் (stored state) கணினி மறுதொடக்கம் (reboot) செய்யப்பட்ட பிறகும் நீடிக்க வேண்டும் என்ற அதே தர்க்கம் தான், KiroCrew-ஐ உங்கள் சொந்த VPS-ல் எப்போதும் இயங்கும் முகவராக இயக்குதல் என்பதன் அடிப்படையாகும்.

இரண்டாவது காரணம் சிறியது, ஆனால் உண்மையானது. ஒரு server-ல் ஒரே ஒரு API key மட்டுமே இருக்கும், அது ஒரே ஒரு கோப்பில் இருக்கும், login shell இல்லாத ஒரே ஒரு system user-க்கு சொந்தமானதாக இருக்கும், மேலும் ஒரே ஒரு பணிக்கு மட்டுமே பயன்படுத்தப்படும். நீங்கள் இணையத்தில் உலாவப் பயன்படுத்தும் மடிக்கணினியில் இத்தகைய சூழலை அமைப்பது மிகவும் கடினம். குறியீட்டிற்குள்ளும் (code) மற்றும் மாதிரியின் உள்ளீட்டிற்குள்ளும் (model's input) key-ஐ வைக்காதீர்கள்; இதுவே AI முகவரிடமிருந்து API keys-ஐ பாதுகாத்தல் என்பதன் மையப்பொருள்.

வளங்களை அளவிடுதல்: disk, RAM மற்றும் tokens

Disk-ஐக் கணக்கிடுவது எளிது. ஒரு நாளைக்கு ஒரு bar என்பது, ஒரு ticker-க்கு ஒரு trading day-க்கு ஒரு row ஆகும். அமெரிக்க பங்குச்சந்தை ஆண்டுக்கு சுமார் 252 நாட்கள் செயல்படும்.

விளக்கப்படம்Rows in the prices table by watchlist size, at 252 trading days a year
இந்த விளக்கப்படத்தின் தரவு
[
  {
    "label": "20 tickers",
    "rows_after_1y": "5,040",
    "rows_after_10y": "50,400"
  },
  {
    "label": "100 tickers",
    "rows_after_1y": "25,200",
    "rows_after_10y": "252,000"
  },
  {
    "label": "500 tickers",
    "rows_after_1y": "126,000",
    "rows_after_10y": "1,260,000"
  }
]

இருபது tickers-க்கு, ஒரு வருடத்திற்குப் பிறகு 5,040 rows இருக்கும். 500 tickers வரிசையானது பத்து ஆண்டுகளுக்குப் பிறகு 1,260,000 rows-ஐ எட்டும். ஒவ்வொரு row-லும் ஒரு தேதி மற்றும் சில doubles மதிப்புகள் இருக்கும். DuckDB நெடுவரிசைகளை (columns) சுருக்கிச் சேமிப்பதால், இது gigabytes அளவில் இல்லாமல், tens of megabytes அளவில் மட்டுமே இருக்கும். இந்த மதிப்பீட்டை, என்னுடையது உட்பட, அப்படியே நம்ப வேண்டாம். உங்கள் முதல் backfill-க்குப் பிறகு du -h /opt/research/data/market.duckdb-ஐ இயக்கி, உங்கள் சொந்த தரவுகளைப் பயன்படுத்தவும்.

சிறிய VPS-களில் RAM ஒரு சிக்கலாக இருக்கும். DuckDB இயல்பாகவே ஒரு query-க்காக machine-ன் நினைவகத்தில் பெரும்பகுதியையும், அனைத்து cores-களையும் எடுத்துக்கொள்ளும். இது ஒரு analytics server-க்குச் சரியாக இருக்கும், ஆனால் மற்ற பணிகளைச் செய்யும் 2 GB box-க்கு இது தவறானது. முழு prices table-ஐயும் ஒருமுறை aggregation செய்யும்போது, kernel அந்த process-ஐக் கொன்றுவிடும் (kill). அப்போது systemd Main process exited, code=killed, status=9/KILL என்று காட்டும், journalctl -k கட்டளை மூலம் அது out of memory காரணமாகக் கொல்லப்பட்டது என்பதை உறுதிப்படுத்தலாம். memory_limit மற்றும் threads ஆகியவற்றைத் தெளிவாக அமைப்பதன் மூலம், query செயலிழப்பதற்குப் பதிலாக மெதுவாக இயங்கும்.

Tokens-ஐ மதிப்பீடு செய்யாமல், அளவிட வேண்டும். ஒவ்வொரு Messages API பதிலிலும் input_tokens மற்றும் output_tokens ஆகியவற்றை உள்ளடக்கிய ஒரு usage object இருக்கும். ஒவ்வொரு அழைப்பின்போதும் இவை இரண்டையும் ஒரு table-ல் எழுதவும். ஒரு வாரத்திற்குப் பிறகு உங்கள் உண்மையான பயன்பாடு தெரிந்துவிடும். அதை நீங்கள் பயன்படுத்தும் model-ன் அன்றைய விலைப்பட்டியலுடன் பெருக்கிக்கொள்ளலாம். திட்டமிடுவதற்கு இரண்டு விஷயங்கள் நிலையானவை. அனைத்து Claude model-களிலும் input tokens-ஐ விட output tokens-க்கு விலை அதிகம். எனவே, குறிப்புகளை 200 words-க்குள் கட்டுப்படுத்துவது, நீங்கள் அனுப்பும் தரவைக் குறைப்பதை விட அதிகச் செலவைக் குறைக்கும். மேலும், ஒரு நாளைக்கு ஒருமுறை நடக்கும் பணிக்கு prompt caching உதவாது. cache இயல்பாக 5 நிமிடங்கள் மட்டுமே இருக்கும். Anthropic வழங்கும் அதிகபட்ச கால அளவு 1 மணிநேரம் ஆகும், இதற்கு அடிப்படை input விலையை விட 2x கட்டணம் வசூலிக்கப்படும் (5 நிமிட cache-க்கு 1.25x). எப்படியிருந்தாலும், அடுத்த நாள் பணி தொடங்கும் போது cache காலாவதியாகிவிடும், நீங்கள் மீண்டும் முழு input விலையையும் செலுத்த வேண்டியிருக்கும். ஒரே பெரிய தரவுத் தொகுப்பைப் பயன்படுத்தி பலமுறை அழைப்புகளைச் செய்யும் பணிகளுக்கு மட்டுமே caching பயனுள்ளதாக இருக்கும்.

கூறுகளை நிறுவுதல்

sudo apt update
sudo apt install -y python3-venv
sudo useradd --system --create-home --home-dir /opt/research --shell /usr/sbin/nologin research
sudo -u research python3 -m venv /opt/research/venv
sudo -u research /opt/research/venv/bin/pip install duckdb pandas yfinance anthropic
sudo install -d -o research -g research -m 750 /opt/research/data

எந்தக் குறியீட்டையும் (code) எழுதும் முன் நிறுவலைச் சரிபார்க்கவும்:

sudo -u research /opt/research/venv/bin/python -c 'import duckdb, yfinance, anthropic; print("ok")'

அது ok-ஐ அச்சிடும். நீங்கள் virtual environment-ஐத் தவிர்த்துவிட்டு, system Python-ல் pip install-ஐ இயக்கினால், Ubuntu 24.04 உங்களைத் தடுக்கும் error: externally-managed-environment, ஏனெனில் /usr/lib/python3-ன் உரிமையாளர் அந்த distribution ஆகும், மேலும் அங்கு pip எழுத அது அனுமதிப்பதில்லை. Venv என்பது ஒரு விருப்பத்தேர்வு அல்ல. Pip-க்கு அனுமதி உள்ள ஒரே கோப்பகம் (directory) அது மட்டுமே.

API key-ஐ service user மட்டும் வாசிக்கக்கூடிய, மற்றவர்கள் அணுக முடியாத ஒரு கோப்பில் வைக்கவும்:

sudo install -d -m 755 /etc/research
sudo install -m 640 -o root -g research /dev/null /etc/research/env
sudoedit /etc/research/env

அதில் மேற்கோள் குறிகள் (quotes) மற்றும் export இல்லாமல் ஒரே வரியை மட்டும் உள்ளிடவும். ஏனெனில் systemd இதை shell-க்கு அனுப்பாமல், தானே நேரடியாகப் பகுப்பாய்வு (parse) செய்கிறது:

ANTHROPIC_API_KEY=sk-ant-your-key-here

சேமிப்பகம்: இரண்டு அட்டவணைகள்

# /opt/research/store.py
import duckdb

DB = '/opt/research/data/market.duckdb'

SCHEMA = [
    """
    CREATE TABLE IF NOT EXISTS prices (
      ticker VARCHAR,
      day    DATE,
      open   DOUBLE,
      high   DOUBLE,
      low    DOUBLE,
      close  DOUBLE,
      volume BIGINT,
      PRIMARY KEY (ticker, day)
    )
    """,
    """
    CREATE TABLE IF NOT EXISTS runs (
      started_at    TIMESTAMPTZ,
      model         VARCHAR,
      input_tokens  BIGINT,
      output_tokens BIGINT,
      hits          BIGINT
    )
    """,
]

def connect(read_only=False):
    con = duckdb.connect(DB, read_only=read_only)
    con.execute("SET memory_limit='512MB'")
    con.execute('SET threads=2')
    if not read_only:
        for statement in SCHEMA:
            con.execute(statement)
    return con

(ticker, day)-ல் உள்ள primary key, refresh செயல்பாட்டை மீண்டும் மீண்டும் பாதுகாப்பாகச் செய்ய உதவுகிறது. INSERT OR REPLACE, அந்த ticker மற்றும் அந்த நாளுக்கு ஏற்கனவே உள்ள வரிசையை (row) மேலெழுதும் (overwrite). எனவே, backfill-ஐ இருமுறை இயக்கினாலும் அட்டவணையில் தரவுகள் இரட்டிப்பாகாது. இந்த key இல்லையென்றால், ஒருமுறை crash ஆன பிறகு மீண்டும் இயக்கும்போது ஒவ்வொரு bar-ம் தானாகவே நகலெடுக்கப்படும். அதன் பிறகு நீங்கள் கணக்கிடும் ஒவ்வொரு சராசரியும் (average) தவறாக இருக்கும், ஆனால் இது குறித்து எந்த பிழைச் செய்தியும் (error message) கிடைக்காது.

DuckDB-ல் ஒரு கோப்பை எழுதுவதற்காகத் திறக்க ஒரே ஒரு process-க்கு மட்டுமே அனுமதி உண்டு. இரண்டாவது writer முயற்சிக்கும்போது Could not set lock on file பிழையுடன் அது தோல்வியடையும், அதைத் தொடர்ந்து அந்த கோப்பை வைத்திருக்கும் PID-ம் காட்டப்படும். நடைமுறையில், நீங்கள் மற்றொரு terminal-ல் திறந்து வைத்திருக்கும் interactive duckdb shell-ஆகவே அது இருக்கும். Read-only connection மூலம் இதைத் தவிர்க்க முடியாது. DuckDB-ன் concurrency ஆவணங்கள் இரண்டு முறைகளை விளக்குகின்றன: ஒன்று, வாசிக்கும் மற்றும் எழுதும் ஒரே ஒரு process; மற்றொன்று, வாசிக்க மட்டுமே செய்யும் பல process-கள். இவை இரண்டையும் கலந்து பயன்படுத்த முடியாது. connect-ல் read_only flag-ஐப் பயன்படுத்தினால் பல reader-கள் கோப்பைப் பகிர்ந்து கொள்ளலாம், ஆனால் எந்த writer-ம் அதைத் திறக்காதபோது மட்டுமே இது வேலை செய்யும். பல process-கள் ஒரே நேரத்தில் எழுத வேண்டிய அவசியம் இருந்தால், அது வேறு ஒரு engine-ன் வேலை: SQLite-ன் WAL mode-ல், ஒரு writer commit செய்யும்போது reader-கள் வேலை செய்ய முடியும், மேலும் busy timeout வசதி மற்ற writer-களைத் தோல்வியடையச் செய்யாமல் காத்திருக்க வைக்கும். server workload-க்கு DuckDB vs SQLite இவை இரண்டையும் ஒப்பிடுகிறது, மேலும் VPS-ல் SQLite-ஐ production-ல் இயக்குதல் WAL mode-ஐச் சரியாகச் செயல்பட வைக்கும் அமைப்புகளை விளக்குகிறது.

Refresh job

# /opt/research/refresh.py
import sys
import pandas as pd
import yfinance as yf
from store import connect

TICKERS = ['AAPL', 'MSFT', 'KO', 'SAP', 'TSM']
FIRST_DAY = '2016-01-01'
COLS = ['ticker', 'day', 'open', 'high', 'low', 'close', 'volume']

def fetch(ticker, start):
    df = yf.Ticker(ticker).history(start=start, auto_adjust=True)
    if df.empty:
        return None
    df = df.reset_index()
    stamps = pd.to_datetime(df['Date'])
    if stamps.dt.tz is not None:
        stamps = stamps.dt.tz_localize(None)
    df['day'] = stamps.dt.date
    df['ticker'] = ticker
    df = df.rename(columns={'Open': 'open', 'High': 'high', 'Low': 'low',
                            'Close': 'close', 'Volume': 'volume'})
    return df[COLS]

def main():
    con = connect()
    empty = 0
    for ticker in TICKERS:
        last = con.execute('SELECT max(day) FROM prices WHERE ticker = ?',
                           [ticker]).fetchone()[0]
        rows_df = fetch(ticker, str(last) if last else FIRST_DAY)
        if rows_df is None:
            print(ticker + ': feed returned no rows', file=sys.stderr)
            empty += 1
            continue
        con.register('rows_df', rows_df)
        con.execute('INSERT OR REPLACE INTO prices '
                    'SELECT ticker, day, open, high, low, close, volume FROM rows_df')
        print(ticker + ': ' + str(len(rows_df)) + ' rows')
    con.close()
    if empty == len(TICKERS):
        print('every ticker returned nothing: the feed is broken', file=sys.stderr)
        sys.exit(1)

main()

இதை ஒருமுறை கைமுறையாக இயக்கவும்:

sudo -u research /opt/research/venv/bin/python /opt/research/refresh.py

முதல்முறை இயக்கும்போது, பல ஆண்டுகளுக்கான தரவுகள் நிரப்பப்பட்டு, ஒவ்வொரு ticker-க்கும் ஆயிரக்கணக்கான வரிசைகள் கொண்ட ஒரு வரி அச்சிடப்படும். ஒரு நிமிடம் கழித்து மீண்டும் இயக்கினால், ஒவ்வொரு வரியிலும் 1 அல்லது 2 வரிசைகள் மட்டுமே இருக்கும், ஏனெனில் ஏற்கனவே சேமிக்கப்பட்ட கடைசி நாளிலிருந்து இந்த job தொடங்குகிறது. அந்த இரண்டாவது இயக்கமே உண்மையான சோதனை: எண்ணிக்கை இன்னும் ஆயிரக்கணக்கில் இருந்தால், max(day) எதையும் திரும்பத் தரவில்லை என்று அர்த்தம், மேலும் ஒவ்வொரு இரவும் உங்கள் முழு வரலாற்றையும் insert மீண்டும் எழுதுகிறது.

df.empty சரிபார்ப்புதான் கோப்பில் உள்ள மிக முக்கியமான வரியாகும். தவறான அல்லது delist செய்யப்பட்ட குறியீட்டிற்கு (symbol) Ticker.history() பிழையை (raise) ஏற்படுத்தாது. இது அந்த குறியீடு delist செய்யப்பட்டிருக்கலாம் அல்லது விலை தரவு கிடைக்கவில்லை என்ற எச்சரிக்கையை மட்டும் அச்சிடும் (இந்த வாசகம் library பதிப்புகளுக்கு ஏற்ப மாறும்) மற்றும் காலியான DataFrame-ஐத் திரும்பத் தரும். அந்தச் சரிபார்ப்பு இல்லாத ஒரு job எதையும் எழுதாது, 0 என்ற exit code-ஐத் தரும், மேலும் systemd அந்த run-ஐ வெற்றிகரமாக (green) காட்டும், ஆனால் அட்டவணை மெதுவாக வளர்வதை நிறுத்திவிடும். பல வாரங்களுக்குப் பிறகு, ஒவ்வொரு நாளும் ஒரே வரிசைகளைத் தரும் திரையைப் பார்க்கும்போதுதான் நீங்கள் இதைக் கண்டறிவீர்கள்.

Timestamp கையாளுதலுக்கும் ஒரு காரணம் உள்ளது. feed திரும்பத் தரும் index, exchange-ன் timezone-ஐக் கொண்டிருக்கலாம், மேலும் offset-ஐ நீக்குவது UTC-க்கு மாற்றுவதற்குச் சமமல்ல. நள்ளிரவு உள்ளூர் நேரத்தைக் கொண்ட ஒரு Tokyo session, UTC-யில் முந்தைய நாட்காட்டி நாளாக மாறும், எனவே UTC மாற்றம் ஒவ்வொரு Japanese bar-ஐயும் அமைதியாக ஒரு நாள் பின்னோக்கி நகர்த்தி primary key-ஐச் சிதைத்துவிடும். tz_localize(None) அந்தந்த exchange-ன் session தேதியையே வைத்திருக்கிறது, இதுதான் ஒரு daily bar-ன் உண்மையான அர்த்தமாகும்.

சந்தை முடிவடையும் நேரத்தில் இயங்கும் timer

அமெரிக்கச் சந்தை நியூயார்க் நேரப்படி 16:00 மணிக்கு முடிவடைகிறது. கடைசி வர்த்தகப் பதிவுகள் (last prints) முடிவடையச் சில நிமிடங்கள் ஆவதால், இந்த job 16:20 மணிக்கு இயங்குகிறது. இதை UTC-ல் குறிப்பிடாமல், நியூயார்க் நேரத்திலேயே எழுதவும். குளிர்காலத்தில் நியூயார்க் நேரம் UTC-5 ஆகவும், கோடையில் UTC-4 ஆகவும் இருக்கும். எனவே, நிலையான UTC நேரத்தை மட்டும் பயன்படுத்தினால், ஆண்டுக்கு இருமுறை நேரம் ஒரு மணிநேரம் மாறி, சந்தை முடிவதற்கு முன்பே job இயங்கத் தொடங்கிவிடும். systemd 235 மற்றும் அதற்குப் பிந்தைய பதிப்புகள் OnCalendar-ல் நேரடியாக timezone-ஐ ஏற்றுக்கொள்கின்றன. Ubuntu 24.04 பதிப்பு 255-ஐக் கொண்டுள்ளது. உங்கள் பதிப்பை systemctl --version மூலம் உறுதிப்படுத்தவும்.

# /etc/systemd/system/research-refresh.service
[Unit]
Description=Refresh market data and run the daily screen
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
User=research
Group=research
WorkingDirectory=/opt/research
EnvironmentFile=/etc/research/env
ExecStart=/opt/research/venv/bin/python /opt/research/refresh.py
ExecStart=/opt/research/venv/bin/python /opt/research/screen.py
# /etc/systemd/system/research-refresh.timer
[Unit]
Description=Run the refresh after the US market close

[Timer]
OnCalendar=Mon-Fri 16:20 America/New_York
Persistent=true
RandomizedDelaySec=180

[Install]
WantedBy=timers.target

Type=oneshot மட்டுமே ஒன்றுக்கும் மேற்பட்ட ExecStart-களை ஏற்கும் ஒரே service வகை ஆகும். இது அவற்றை வரிசையாக இயக்கும்; ஏதேனும் ஒன்று பூஜ்ஜியமற்ற (non-zero) நிலையில் வெளியேறினால், அடுத்ததை இயக்காது. இதுவே உங்களுக்குத் தேவையான செயல்பாடாகும்: தரவு புதுப்பித்தல் தோல்வியுற்றால், பழைய தரவைக் கொண்டு திரையை நிரப்பக் கூடாது. கர்னல் அப்டேட்களுக்காக VPS reboot செய்யப்படும்போது Persistent=true முக்கியமானது. இது இல்லையெனில், 16:15 மணிக்கு reboot நடந்தால் அந்த run இழக்கப்படும். இது இருந்தால், இயந்திரம் மீண்டும் இயங்கியவுடன் job தானாகவே இயங்கும். ஒரு agent இயங்கிக்கொண்டே இருக்க வேண்டும், அவ்வப்போது இயங்கி வெளியேறக் கூடாது என்றால், அதற்கு நேர்மாறான unit அமைப்பு தேவை. அதாவது, Restart= விதிகளைக் கொண்ட நீண்டகால process-க்கு timer தேவையில்லை. இது குறித்து VPS-ல் systemd மூலம் dsh-ஐ headless-ஆக இயக்குவது என்ற பகுதியில் விளக்கப்பட்டுள்ளது.

sudo systemctl daemon-reload
sudo systemctl enable --now research-refresh.timer
systemctl list-timers research-refresh.timer

list-timers கட்டளையை இயக்கும்போது, அடுத்த வேலைநாள் run-ஐக் காட்டும் NEXT நெடுவரிசை, சர்வரின் உள்ளூர் நேரத்திற்கு மாற்றப்பட்டுத் தெரிய வேண்டும். பட்டியல் காலியாக இருந்தால், timer enable செய்யப்படவில்லை அல்லது unit-ல் [Install] பகுதி விடுபட்டுள்ளது என்று அர்த்தம். இதனால் enable-ஆல் timers.target-ல் எதையும் இணைக்க முடியவில்லை.

திரை: முதலில் SQL, இறுதியில் model

SQL துல்லியமானது, அதை இயக்குவதற்கு செலவு ஏதுமில்லை. model அவ்வாறு இல்லை. எனவே, query தரவுகளின் அளவைக் குறைக்கிறது; எஞ்சிய தரவுகள் மட்டுமே model-க்கு அனுப்பப்படுகின்றன. இதை /opt/research/screen.sql ஆக சேமிக்கவும்.

WITH ma AS (
  SELECT ticker, day, close,
         avg(close) OVER w20 AS ma20,
         avg(close) OVER w50 AS ma50,
         row_number() OVER (PARTITION BY ticker ORDER BY day) AS n
  FROM prices
  WINDOW
    w20 AS (PARTITION BY ticker ORDER BY day ROWS BETWEEN 19 PRECEDING AND CURRENT ROW),
    w50 AS (PARTITION BY ticker ORDER BY day ROWS BETWEEN 49 PRECEDING AND CURRENT ROW)
)
SELECT ticker, day, close, round(ma20, 2) AS ma20, round(ma50, 2) AS ma50
FROM ma
WHERE n > 50 AND ma20 > ma50
ORDER BY day DESC, ticker
LIMIT 20;

n > 50 filter என்பது அலங்காரத்திற்காக அல்ல. ROWS BETWEEN 49 PRECEDING AND CURRENT ROW என்பது இருக்கும் அனைத்து வரிசைகளின் சராசரியைக் கணக்கிடுகிறது. எனவே, ஒரு ticker-ன் மூன்றாவது வரிசை மூன்று நாட்களின் சராசரியைக் காட்டி, அதை ma50 என்று அழைக்கும். இதை ma20 உடன் ஒப்பிடும்போது, ஒவ்வொரு ticker-ன் வரலாற்றின் தொடக்கத்திலும் நடக்காத ஒரு crossover-ஐ நீங்கள் உருவாக்குகிறீர்கள். வரிசை எண்ணை (row number) வைத்து filter செய்வதன் மூலம், window முழுமையடையாத வரிசைகள் நீக்கப்படுகின்றன.

மாதிரி எதைப் பார்க்கிறது, எதைப் பார்க்காது

# /opt/research/screen.py
from anthropic import Anthropic
from store import connect

SYSTEM = (
    'You are a research assistant. Use only the rows in the message. '
    'If a number is not in the rows, say that it is not available. '
    'Do not give investment advice, price targets or buy and sell calls. '
    'Write at most 200 words.'
)

con = connect()
sql = open('/opt/research/screen.sql').read()
rows = con.execute(sql).fetchall()
block = '\n'.join(' / '.join(str(v) for v in row) for row in rows)

client = Anthropic(max_retries=5)   # reads ANTHROPIC_API_KEY from the environment
resp = client.messages.create(
    model='claude-sonnet-5',
    max_tokens=600,
    thinking={'type': 'disabled'},
    system=SYSTEM,
    messages=[{'role': 'user', 'content': 'Screen hits, ticker / day / close / ma20 / ma50:\n' + block}],
)

print(resp.content[0].text)
con.execute('INSERT INTO runs VALUES (now(), ?, ?, ?, ?)',
            ['claude-sonnet-5', resp.usage.input_tokens,
             resp.usage.output_tokens, len(rows)])
con.close()

இந்த மாதிரியில் thinking வரி முக்கியமானது. Claude Sonnet 5 இயல்பாகவே சிந்திக்கும் திறன் கொண்டது, அதன் சிந்தனை டோக்கன்கள் (thinking tokens) வெளியீடாகக் கணக்கிடப்பட்டு max_tokens-ல் சேர்க்கப்படுகின்றன, மேலும் அவை உரைக்கு முன்னால் ஒரு தொகுதியாக வருகின்றன. அந்த வரி இல்லையென்றால், resp.content[0] சிந்தனைத் தொகுதியாக இருக்கலாம், மேலும் .text தேடல் தோல்வியடையும். Claude Sonnet 5 இப்போது ஒரு பழைய (legacy) மாதிரி. அதன் அடுத்த பதிப்பான claude-sonnet-5-5, disabled-ஐ நிராகரிக்கிறது, அங்கு பயன்படுத்த வேண்டிய அமைப்பு thinking={'type': 'between_tools'} ஆகும், இது பதிலுக்கு முன்னால் சிந்தனையை முடக்குகிறது.

கட்டளைத் தூண்டுதலில் (system prompt) உள்ள வார்த்தை வரம்பு, கட்டணத்தின் அதிக செலவுள்ள பாதியை கட்டுப்படுத்துகிறது. கொடுக்கப்பட்ட வரிசைகளை மட்டுமே பயன்படுத்த வேண்டும் என்ற அறிவுறுத்தலை நீங்கள் நம்புவதை விட சரிபார்க்க வேண்டும்: தொகுதியிலிருந்து ஒரு நெடுவரிசையை நீக்கிவிட்டு, மீண்டும் இயக்கி, வெளியீட்டைப் படிக்கவும். அந்த நெடுவரிசைக்கான எண் இன்னும் தோன்றினால், மாதிரி அந்த இடைவெளியை நிரப்பியுள்ளது என்று அர்த்தம், உங்கள் தூண்டுதல் போதுமான அளவு துல்லியமாக இல்லை. அந்தச் சோதனையைச் செய்ய இரண்டு நிமிடங்கள் ஆகும், இதுவே உண்மையை அறிய ஒரே நேர்மையான வழி.

மாதிரி ஒருபோதும் API key-ஐப் பார்ப்பதில்லை, database path-ஐப் பார்ப்பதில்லை, எந்த வினவலையும் (query) இயக்குவதில்லை. அது வரிசைகளைப் பெற்று உரைநடையைத் திருப்பித் தருகிறது. அந்த எல்லைதான் வெளியீட்டைச் சரிபார்க்கக்கூடியதாக மாற்றுகிறது, ஏனெனில் குறிப்பில் உள்ள ஒவ்வொரு எண்ணும் நீங்கள் அனுப்பிய தொகுதியிலும் இருக்க வேண்டும், அவற்றை நீங்கள் வரி வரியாக ஒப்பிடலாம். இணையத்திலிருந்து எடுக்கப்பட்ட பக்கங்களைச் சேர்க்க உள்ளீட்டை விரிவுபடுத்தும் தருணத்தில், அந்த எல்லையின் வடிவம் மாறுகிறது, ஏனெனில் ஒரு தலைப்புச் செய்தி அல்லது மன்றப் பதிவு (forum post) உங்களுக்குத் தேவையான உண்மைகளுக்குப் பதிலாக மாதிரிக்குத் தேவையான அறிவுறுத்தல்களைக் கொண்டிருக்கலாம், இதுவே SearXNG search backend-ஐ ஒரு முகவருக்கு வழங்குதல் உருவாக்கும் பாதுகாப்பு இடைவெளி. இது குறித்த கூடுதல் தூண்டுதல் நுட்பங்களுக்கு, நிதி ஆய்வுக்கு Claude-ஐப் பயன்படுத்துதல் மாதிரி எதைப் படிப்பதில் சிறந்தது என்பதை மேலும் விளக்குகிறது.

ஒரு முழுமையான இயக்கம், இறுதி முதல் இறுதி வரை

நியூயார்க் நேரப்படி 16:20 மணிக்கு டைமர் சேவையைத் தொடங்குகிறது. refresh.py கடைசியாகச் சேமிக்கப்பட்ட நாளிலிருந்து ஒவ்வொரு ticker-க்கும் feed-ஐக் கேட்டு, ஒவ்வொன்றிற்கும் ஒன்று அல்லது இரண்டு புதிய bars-களை எழுதி, ஒவ்வொரு ticker-க்கும் ஒரு வரியை அச்சிடுகிறது. screen.py அதே கோப்பைத் திறந்து, moving average வினவலை (query) இயக்கி, சில வரிசைகளைப் பெறுகிறது. அந்த வரிசைகள் சில நூறு tokens கொண்ட உரைத் தொகுதியாக மாறுகின்றன. ஒரு API அழைப்பு அவற்றை ஒரு சிறு குறிப்பாக மாற்றுகிறது, அந்தக்குறிப்பு journal-க்குச் செல்கிறது, மேலும் runs-ல் token எண்ணிக்கை மற்றும் hits எண்ணிக்கையுடன் ஒரு வரி சேமிக்கப்படுகிறது.

journalctl -u research-refresh.service -n 50 --no-pager

சரியான log கோப்பில் ஒவ்வொரு ticker-க்கும் ஒரு வரி, அதைத் தொடர்ந்து குறிப்பு, பின்னர் research-refresh.service: Deactivated successfully இருக்கும். ஒரு Claude Code session சர்வரில் அந்த log-ஐ வாசிக்கும்போது, மற்றொரு session screen query-ல் வேலை செய்தால், இரண்டு session-களும் தங்களுக்குள் உரையைப் பரிமாறிக்கொள்ள முடியும்; நீங்கள் terminal-களுக்கு இடையே வரிகளை நகலெடுக்க வேண்டியதில்லை. ஒரு வாரத்திற்குப் பிறகு, தரவுத்தளத்திலிருந்து உங்கள் சொந்த செலவுத் தொகையை வாசிக்கவும்:

SELECT count(*) AS runs,
       sum(input_tokens)  AS in_tokens,
       sum(output_tokens) AS out_tokens
FROM runs;

அந்த மொத்தத் தொகையை, நீங்கள் வாசிக்கும் நாளில் உங்கள் model பட்டியலிடும் ஒரு மில்லியன் tokens-க்கான விலையுடன் பெருக்கவும். இது மற்றவர்களின் மதிப்பீட்டிற்குப் பதிலாக உண்மையான தொகையை உங்களுக்கு வழங்கும். வெளியிடப்பட்ட விலைகள் மாறலாம். கணக்கீடு மாறாது.

Backtests ஏன் overfit ஆகின்றன மற்றும் அதை எவ்வாறு கண்காணிப்பது

இரண்டு window நீளங்களின் செயல்பாடாக screen-ஐ மாற்றி, ஜோடிகளின் grid-ஐ sweep செய்து, அவற்றை வருவாயின் அடிப்படையில் வரிசைப்படுத்தவும். சிறந்த ஜோடி மிகச்சிறப்பாகத் தோன்றும். இதுவே சிக்கல், முடிவு அல்ல. 200 ஜோடிகளைக் கொண்ட ஒரு grid என்பது 200 சோதனைகள் ஆகும், அதில் நீங்கள் அதிர்ஷ்டமான ஒன்றைத் தேர்ந்தெடுத்துள்ளீர்கள்.

இதை பத்து நிமிடங்களில் நீங்கள் கண்காணிக்கலாம். தரவு சேமிப்பகத்தை (store) தேதியின் அடிப்படையில் பாதியாகப் பிரிக்கவும். முதல் பாதியில் மட்டும் grid-ஐ sweep செய்து வெற்றியாளரைக் குறித்துக்கொள்ளவும். அதே grid-ஐ இரண்டாம் பாதியில் sweep செய்யவும். இரண்டு வெற்றியாளர்களும் வெகு தொலைவில் இருந்தால், parameters இரைச்சலை (noise) பொருத்துகின்றன (fitting) என்று அர்த்தம். நீங்கள் tune செய்த பாதியில் மட்டும் வெற்றி பெறும் ஜோடி, நாளை என்ன நடக்கும் என்பதைப் பற்றி எதையும் கூறாது.

Survivorship என்பது overfitting-ஐ விட மோசமானது, ஏனெனில் tuning மூலம் இதைச் சரிசெய்ய முடியாது. உங்கள் ticker பட்டியல் இன்றைய index உறுப்பினர்களைக் கொண்டது, எனவே இது தப்பிப்பிழைத்த நிறுவனங்களை மட்டுமே கொண்டுள்ளது. 2019-ல் நீக்கப்பட்ட (delisted) ஒரு ticker-ஐ feed-இடம் கேட்டால், அது காலியான frame-ஐத் தரும். அதாவது அந்த நிறுவனம் உங்கள் store-க்குள் நுழையாது, உங்கள் சோதனையிலும் நுழையாது. நீங்கள் இயக்கும் ஒவ்வொரு backtest-ம் தோல்வியுற்ற நிறுவனங்களை ஏற்கனவே தவிர்த்துவிடுகிறது.

Restated fundamentals காலவரிசையைச் சிதைக்கின்றன. 2019-ம் ஆண்டின் காலாண்டுக்கு API இன்று வழங்கும் வருவாய் புள்ளிவிவரம், 2019-ல் வெளியிடப்பட்ட புள்ளிவிவரம் அல்ல. இன்றைய fundamentals-ஐ 2019-ன் விலைகளுடன் கலக்கும் ஒரு screen, அப்போது இல்லாத தகவலைப் பயன்படுத்துகிறது. இதில் விலைகள் பொதுவாகப் பாதுகாப்பானவை. Fundamentals பொதுவாகப் பாதுகாப்பானவை அல்ல.

Adjusted prices உங்களுக்குத் தெரியாமலேயே மாறுகின்றன. auto_adjust=True மூலம், dividend மற்றும் split-களுக்காக closes பின்னோக்கி சரிசெய்யப்படுகின்றன. எனவே அடுத்த மாதம் அதே query-ஐ இயக்கினால் சற்று மாறுபட்ட வரலாறு கிடைக்கும். நீங்கள் உண்மையில் பயன்படுத்திய rows-ஐச் சேமித்து வைப்பதுதான் ஒரு முடிவை மீண்டும் உருவாக்கக்கூடியதாக (reproducible) மாற்றுகிறது. உள்ளூர் store இருப்பதற்கான மற்றொரு காரணமும் இதுவே.

Backtest என்பது commissions மற்றும் slippage-ஐப் புறக்கணிக்கிறது, மேலும் உங்கள் order விலையை மாற்றாது என்றும் கருதுகிறது. இவை execution சார்ந்தவை, அவை இங்கு விவாதத்திற்கு அப்பாற்பட்டவை மற்றும் VPS-ல் trading bots-ஐ இயக்குதல் என்பதில் விளக்கப்பட்டுள்ளன.

தோல்வி முறைகள் மற்றும் நீங்கள் காணும் சரங்கள் (strings)

error: externally-managed-environment என்பது pip இயங்கும்போது தோன்றும். நீங்கள் virtual environment-க்கு வெளியே இருக்கிறீர்கள். /opt/research/venv/bin/pip-ஐ அதன் முழுப் பாதையைக் (full path) கொண்டு அழைக்கவும்.

Could not set lock on file, அதைத் தொடர்ந்து ஒரு PID இருக்கும். வேறொரு process, DuckDB கோப்பை எழுதும் அனுமதியுடன் (write access) திறந்து வைத்துள்ளது; பெரும்பாலும் நீங்கள் கவனிக்காமல் விட்ட interactive shell-ஆக இருக்கலாம். அதை மூடவும். அந்த writer திறந்திருக்கும்போது read_only=True மூலம் இரண்டாவது இணைப்பைத் திறப்பது உதவாது, ஏனெனில் DuckDB பல process-களைப் படிக்க அனுமதிக்கிறது அல்லது ஒரே ஒரு process-ஐ மட்டுமே எழுத அனுமதிக்கிறது, இரண்டையும் ஒரே நேரத்தில் செய்யாது.

Main process exited, code=killed, status=9/KILL என்பது systemctl status-ல் தோன்றும். நினைவகப் பற்றாக்குறையால் (memory) kernel அந்த வேலையை நிறுத்திவிட்டது. journalctl -k | grep -i oom மூலம் இதை உறுதிப்படுத்தவும், பின்னர் store.py-ல் உள்ள memory_limit-ன் அளவைக் குறைக்கவும்.

எதையும் எழுதாத ஒரு வெற்றிகரமான இயக்கம் (Green run). systemctl status ஆனது active (exited)-ஐப் படிக்கிறது, ஆனால் அட்டவணை (table) வளரவில்லை. அந்த feed காலியான frames-ஐத் திருப்பி அனுப்பியுள்ளது. இந்தத் தோல்வி நீண்ட நேரம் கண்டறியப்படாமல் இருக்கும், எனவே ஒவ்வொரு ticker-ம் காலியாக வரும்போது அந்த job-ஐ non-zero exit code-உடன் முடிவடையச் செய்யவும்.

சந்தை விடுமுறை நாளில் timer இயங்குதல். systemd-க்கு பங்குச் சந்தை நாட்காட்டி தெரியாது, எனவே Mon-Fri விடுமுறை நாட்களையும் உள்ளடக்கும். அந்த இயக்கம் நடக்கும், feed-ல் புதிய தரவுகள் இருக்காது, இதை ஒரு பிழையாகக் கருதாமல் இயல்பான ஒன்றாக அந்த job கையாள வேண்டும்.

API-லிருந்து 429. நீங்கள் rate limit-ஐத் தாண்டிவிட்டீர்கள். Anthropic SDK தானாகவே backoff முறையைப் பயன்படுத்தி மீண்டும் முயற்சிக்கும், மேலும் Anthropic(max_retries=5) முயற்சி எண்ணிக்கையை (attempt count) அதிகரிக்கும். இது தினமும் தோல்வியடைந்தால், அந்த job ஒரே நேரத்தில் அதிகப்படியான கோரிக்கைகளை முன்வைக்கிறது என்று அர்த்தம்.

இது எதைக் குறிக்கவில்லை

இது ஒரு ஆராய்ச்சி உதவியாளர். ஒரு கோப்பைச் சுருக்கித் தரும் மாதிரி, அந்த ஆவணத்தின் வாசிப்பை மட்டுமே வழங்குகிறது. அதில் உள்ள எண்களைத் தவறாகக் குறிப்பிட வாய்ப்புள்ளது, எனவே குறிப்பில் உள்ள ஒவ்வொரு எண்ணையும் நீங்கள் அனுப்பிய தரவுகளுடன் சரிபார்க்க வேண்டும். இந்த வெளியீட்டை நீங்கள் நீங்களே வாசிக்க வேண்டிய விஷயங்களின் பட்டியலாகக் கருதவும். இது நிதி ஆலோசனையோ அல்லது வர்த்தக சிக்னலோ அல்ல.

Backtests என்பது ஒரு யோசனையை நிராகரிக்கப் பயன்படுமே தவிர, அதை உறுதிப்படுத்தப் போதுமானதல்ல. உங்கள் சொந்தத் தரவில் தோல்வியடையும் ஒரு உத்தி முற்றிலும் பயனற்றது. உங்கள் தரவில் வெற்றிபெறும் ஒரு உத்தி, அந்தத் தரவை மட்டுமே கடந்து வந்துள்ளது; இது நள்ளிரவில் தோன்றுவது போலப் பெரிய சாதனை அல்ல.

Execution என்பது திட்டமிட்டே இந்த வரம்பிற்குள் சேர்க்கப்படவில்லை. Orders மற்றும் broker credentials ஆகியவை read-only ஆராய்ச்சி சூழலை விட அதிக ஆபத்து கொண்டவை. இவற்றை ஒன்றாக இணைப்பது, LLM prompt இயங்கும் அதே கணினியில் வர்த்தகக் குறியீடுகளை (trading keys) வைப்பதற்குச் சமம். உங்கள் server-ல் இயக்க வேண்டிய பிற பயனுள்ள விஷயங்களுடன் இந்த அமைப்பு எவ்வாறு பொருந்துகிறது என்பதை அறிய, இயக்க வேண்டிய self-hosted AI agents குறித்த விரிவான வழிகாட்டியைப் பார்க்கவும்.

FAQ

எனக்குக் கட்டண சந்தை தரவு (market data feed) தேவையா?

முன்மாதிரிக்கு (prototype) தேவையில்லை. கணினியின் அமைப்பைக் கற்றுக்கொள்ள ஒரு இலவச அதிகாரப்பூர்வமற்ற feed போதுமானது. இது செயலிழக்கக்கூடும், ஏனெனில் இது உங்களுக்கு எந்தப் பொறுப்பும் இல்லாத ஒரு இணையதளத்தைச் சார்ந்துள்ளது. இது பெரும்பாலும் பிழையாக (exception) வராமல், காலி தரவுகளாகவே (empty frames) வரும். எனவே, உங்கள் பணி வரி எண்ணிக்கையை (row counts) சரிபார்க்க வேண்டும். தரவு முடிவெடுக்கும் நிலைக்கு வரும்போது, ஆவணப்படுத்தப்பட்ட API மற்றும் ஆதரவு முகவரி கொண்ட கட்டண feed-க்கு மாறவும். Store-ஐப் பயன்படுத்துவதால் இந்த மாற்றம் எளிதாகிறது: fetch function மட்டுமே மாறும்; schedule, schema மற்றும் screen ஆகியவை அப்படியே இருக்கும்.

விலைகளை SQLite-ல் வைக்க வேண்டுமா அல்லது DuckDB-ல் வைக்க வேண்டுமா?

DuckDB என்பது columnar வகை, இது பல வரிசைகளை ஸ்கேன் செய்து கூட்டுத்தொகையைக் கணக்கிட உருவாக்கப்பட்டது. பத்து ஆண்டுகால தரவுகளின் நகரும் சராசரியைக் (moving average) கணக்கிட இதுவே சிறந்தது. SQLite என்பது row oriented வகை, இது ஒரே நேரத்தில் பல செயல்முறைகளிலிருந்து (processes) சிறிய அளவிலான தரவுகளை வாசிக்கவும் எழுதவும் சிறந்தது. சில நூறு வரிசைகளைச் சேர்த்து, மில்லியன் கணக்கான வரிசைகளை ஸ்கேன் செய்யும் ஒரு scheduled job-க்கு DuckDB சிறந்த தேர்வாகும். பல செயல்முறைகள் ஒரே நேரத்தில் எழுத வேண்டியிருந்தால், SQLite-ன் WAL mode-ஐப் பயன்படுத்தவும். இது ஒரு writer commit செய்யும்போது மற்றவர்கள் வாசிக்க அனுமதிக்கும், மேலும் busy timeout மற்ற writers தோல்வியடையாமல் காத்திருக்கச் செய்யும்.

LLM அழைப்புகளுக்கு மாதத்திற்கு எவ்வளவு செலவாகும்?

ஒவ்வொரு பதிலிலிருந்தும் usage.input_tokens மற்றும் usage.output_tokens ஆகியவற்றை ஒரு அட்டவணையில் பதிவு செய்யவும். பின்னர், உங்கள் வாராந்திர மொத்தத்தை, நீங்கள் சரிபார்க்கும் நாளில் உங்கள் model பட்டியலிடும் ஒரு மில்லியன் டோக்கன்களுக்கான விலையுடன் பெருக்கவும். அடுத்த காலாண்டிலும் உண்மையாக இருக்கும் ஒரே புள்ளிவிவரம் இதுதான். ஒரு குறுகிய screen-ல் தினசரி ஒருமுறை இயக்கும்போது அழைப்புகளின் எண்ணிக்கை குறைவாகவே இருக்கும். குறிப்பின் நீளத்தை நீங்கள் கட்டுப்படுத்தலாம்: சுருக்கத்தை 200 சொற்களுக்குள் வைப்பது, குறைவான வரிசைகளை அனுப்புவதை விட அதிக சேமிப்பைத் தரும். ஏனெனில் ஒவ்வொரு Claude model-லும் output tokens-ன் விலை input tokens-ஐ விட அதிகமாக இருக்கும்.

எனது பணி வெற்றி பெற்றும் ஏன் புதிய வரிசைகள் எழுதப்படவில்லை?

இதற்கு இரண்டு பொதுவான காரணங்கள் உள்ளன. சந்தை மூடப்பட்டிருக்கலாம், ஏனெனில் systemd Mon-Fri schedule-ல் பங்குச் சந்தை விடுமுறைகளும் அடங்கும். அல்லது, feed அனைத்து ticker-களுக்கும் காலி தரவுகளை அனுப்பியிருக்கலாம். Client libraries இதை பிழையாகக் கருதாமல், பெரும்பாலும் வெறும் எச்சரிக்கையாகவே அச்சிடும். எனவே, செயல்முறை 0 என்ற நிலையிலேயே முடிந்து, systemd பச்சை நிறத்தைக் காட்டும். SELECT max(day) FROM prices-ஐ கடைசி உண்மையான வர்த்தக நாளுடன் ஒப்பிட்டுப் பார்த்து, ஒவ்வொரு ticker-ம் காலியாக வரும்போது பணியை non-zero நிலையில் முடிக்கச் செய்யவும்.

முகவர் (agent) எதை வாங்க வேண்டும் என்று முடிவு செய்ய முடியுமா?

முடியாது. அதைச் செய்ய முயற்சிப்பதுதான் இத்தகைய திட்டங்கள் தோல்வியடைவதற்குக் காரணமாகிறது. இந்த model-க்கு சந்தை அணுகல் கிடையாது, உங்கள் முதலீடு அல்லது வரி நிலை குறித்த பார்வை கிடையாது, மேலும் அதன் எண்களை எதனுடனும் ஒப்பிட்டுச் சரிபார்க்கும் வசதியும் இல்லை. இது அதிகப்படியான உரையை வாசித்து, இன்று உங்களுக்கு எந்தெந்த விஷயங்கள் முக்கியம் என்று சொல்வதில் மட்டுமே சிறந்தது. இது எழுதும் எதுவும் நிதி ஆலோசனை அல்ல; முடிவெடுக்கும் பொறுப்பு உங்களுடையதே.