Jinsi ya kujenga wakala wa utafiti wa hisa kwenye VPS
Jifunze kuunda wakala wa utafiti wa hisa unaojiendesha kwenye VPS kwa kutumia DuckDB na Python. Mwongozo huu unaelezea usanidi wa systemd timer na LLM kwa uchambuzi wa soko.
Wakala wa utafiti wa hisa unaojiendesha (self-hosted)
Wakala wa utafiti wa hisa unaojiendesha ni programu ndogo kwenye seva unayomiliki inayovuta data ya soko kulingana na ratiba, kuihifadhi kwenye database ya ndani, kuichuja, na kuiuliza large language model (LLM) kuandika mabadiliko yaliyotokea. Programu hii husoma na kuchuja. Haifanyi biashara, na hakuna chochote katika mwongozo huu kinachopaswa kuchukuliwa kama ushauri wa kifedha.
Watu wawili wanaojenga mfumo huu kuanzia mwanzo watachagua maktaba tofauti lakini bado wataishia na sehemu nne zilezile: feed inayotoa bei na misingi ya kifedha, hifadhi ya ndani inayotunza kila safu uliyowahi kuivuta, kazi (job) inayohuisha hifadhi hiyo kwa kutumia timer, na safu ya LLM inayobadilisha safu zilizobaki kuwa sentensi. Mwongozo huu unajenga muundo huo kwa kutumia Python, DuckDB, systemd timer na Claude API (application programming interface). Utekelezaji wa biashara ni kazi tofauti yenye njia zake za kufeli, na inapaswa kuwekwa kwenye VPS iliyosanidiwa kwa ajili ya roboti za biashara badala yake.
Sehemu nne, na kazi ya kila moja
Feed ndiyo sehemu pekee inayowasiliana na ulimwengu wa nje. Inajua jinsi ya kuomba ticker na masafa ya tarehe, na jinsi ya kurejesha safu za data. Kila kitu kinachofuata husoma database yako badala ya feed, kwa hivyo hitilafu ya feed inakugharimu siku moja ya data mpya badala ya skrini iliyoharibika.
Store ndiyo lengo kuu la zoezi hili lote. Bei ya kufunga ya kila siku ambayo hukurekodi mara nyingi inaweza kuchukuliwa tena baadaye. Nukuu ya ndani ya siku, makadirio kabla hayajarekebishwa, au takwimu za msingi kabla hazijabadilishwa haziwezi kupatikana tena. Store ndiyo njia unayotumia kujenga rekodi ya kile data ilichosema kweli katika siku iliyosemwa.
Scheduler huamua wakati wa kusasisha data. Kwenye seva, hii ni systemd timer, ambayo ndiyo sababu VPS ni muhimu zaidi hapa kuliko msimbo (code) wenyewe.
LLM layer husoma kizuizi kifupi cha maandishi kilichozalishwa na SQL yako, na kuandika muhtasari wake. Haiunganishwi kamwe na database na haijengi swali (query) la SQL. Ikiwa modeli itaandika SQL, token moja mbaya inakuwa namba isiyo sahihi ndani ya sentensi fasaha, bila kitu cha kuilinganisha nayo. Ikiwa SQL inazalisha namba, modeli inaweza kukosea tu kwenye lugha, na unaweza kukagua lugha hiyo dhidi ya safu ulizotuma.
Kwa nini uendeshe kwenye VPS badala ya kompyuta ya mkononi
Kipanga ratiba (scheduler) ndiyo sababu. Soko la hisa la Marekani likifungwa saa 16:00 kwa saa za New York, ni saa 22:00 jijini Berlin na saa 04:00 asubuhi iliyofuata jijini Jakarta. Kompyuta ya mkononi huwa imelala katika nyakati zote mbili. Kazi inayokosa kutekelezwa ina gharama kubwa kuliko noti iliyochelewa: data za kila siku (daily bars) mara nyingi zinaweza kurejeshwa baadaye, lakini chochote kinachofanyiwa marekebisho hakiwezi kurejeshwa, hivyo pengo katika rekodi zako huwa la kudumu. Hoja hiyo hiyo inatumika kwa wakala yeyote ambaye ratiba yake na hali iliyohifadhiwa (stored state) lazima viendelee kuwepo baada ya reboot, jambo ambalo ndilo msingi wa kuendesha KiroCrew kama wakala anayewaka muda wote kwenye VPS yako.
Sababu ya pili ni ndogo lakini bado ni ya kweli. Seva inashikilia API key moja, kwenye faili moja, inayomilikiwa na mtumiaji mmoja wa mfumo (system user) asiye na login shell, inayotumiwa na kazi moja. Hilo ni gumu zaidi kulipanga kwenye kompyuta ya mkononi unayotumia pia kuvinjari mtandao. Weka key nje ya code na nje ya input ya model, jambo ambalo ndilo mada ya kuepuka kuweka API keys ndani ya wakala wa AI.
Ukubwa wa rasilimali: diski, RAM na tokeni
Diski ni sehemu rahisi. Mstari mmoja wa kila siku ni safu moja kwa kila ticker kwa siku ya biashara, na mwaka wa biashara wa Marekani una takriban siku 252.
The data behind this chart
[
{
"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"
}
]Ticker ishirini ni 5,040 safu baada ya mwaka mmoja. Mstari wa 500 tickers unafikia 1,260,000 safu baada ya miaka kumi. Kila safu ina tarehe na namba chache za aina ya double, na DuckDB huhifadhi safu wima zikiwa zimefinyazwa, kwa hivyo huu ni ukubwa wa makumi ya megabytes badala ya gigabytes. Usiamini makadirio hayo, ikiwemo yangu. Endesha du -h /opt/research/data/market.duckdb baada ya backfill yako ya kwanza na utumie namba yako mwenyewe.
RAM ndipo VPS ndogo inapopata changamoto. DuckDB kwa chaguo-msingi huchukua sehemu kubwa ya kumbukumbu ya mashine na cores zote kwa ajili ya query moja, jambo ambalo ni sahihi kwenye seva ya uchanganuzi lakini si sahihi kwenye mashine ya 2 GB inayofanya kazi nyingine. Aggregation moja juu ya jedwali zima la bei husababisha mchakato huo kusitishwa na kernel, na systemd huripoti Main process exited, code=killed, status=9/KILL wakati journalctl -k ikionyesha kuwa mchakato umesitishwa kwa sababu ya kuishiwa na kumbukumbu (OOM kill). Weka memory_limit na threads wazi na query itakuwa polepole badala ya kufa.
Tokeni zinapaswa kupimwa, si kukadiriwa. Kila jibu la Messages API hubeba kitu cha usage chenye input_tokens na output_tokens. Andika vyote kwenye jedwali kila unapopiga simu, na baada ya wiki moja utajua kiasi chako halisi, ambacho unakizidisha kwa bei inayoorodheshwa na modeli yako siku unayokagua. Mambo mawili ni thabiti vya kutosha kupangia. Tokeni za pato (output) zina bei ya juu kuliko tokeni za ingizo (input) kwenye kila modeli ya Claude, kwa hivyo kupunguza ujumbe hadi maneno 200 kunapunguza gharama zaidi kuliko kupunguza data unayotuma. Na prompt caching haisaidii kazi inayofanyika mara moja kwa siku, kwa sababu muda wa kuishi wa cache hupimwa kwa dakika: kufikia wakati wa run inayofuata, block iliyohifadhiwa kwenye cache itakuwa imekwisha muda wake na utalipa bei kamili ya ingizo tena. Caching inalipa wakati run moja inapopiga simu nyingi juu ya block moja kubwa ya maandishi.
Sakinisha vipengele
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/dataKagua usakinishaji kabla ya kuandika msimbo wowote:
sudo -u research /opt/research/venv/bin/python -c 'import duckdb, yfinance, anthropic; print("ok")'Hiyo huchapisha ok. Ikiwa uliruka mazingira ya virtual na kuendesha pip install dhidi ya Python ya mfumo, Ubuntu 24.04 itakuzuia kwa error: externally-managed-environment, kwa sababu usambazaji unamiliki /usr/lib/python3 na unakataa kuruhusu pip kuandika hapo. Venv si suala la adabu. Ni saraka pekee ambayo pip inaruhusiwa kuigusa.
Ufunguo wa API huwekwa kwenye faili ambayo mtumiaji wa huduma anaweza kuisoma na hakuna mwingine anayeweza:
sudo install -d -m 755 /etc/research
sudo install -m 640 -o root -g research /dev/null /etc/research/env
sudoedit /etc/research/envWeka mstari mmoja ndani yake, bila alama za kunukuu na bila export, kwa sababu systemd huchakata faili hii yenyewe badala ya kuipitisha kwenye shell:
ANTHROPIC_API_KEY=sk-ant-your-key-hereHifadhi: jedwali mbili
# /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 conUfunguo msingi (primary key) kwenye (ticker, day) ndio unaofanya uboreshaji kuwa salama kurudiwa. INSERT OR REPLACE hufuta na kuandika upya safu iliyopo kwa ajili ya ticker hiyo na siku hiyo, kwa hivyo kuendesha backfill mara mbili hakuzidishi jedwali. Bila ufunguo huo, kurudia operesheni baada ya hitilafu kutasababisha nakala mbili za kila bar, na kila wastani utakaokokotoa baadaye utakuwa na makosa bila ujumbe wowote wa hitilafu kukuonya.
DuckDB inaruhusu mchakato mmoja pekee kushikilia faili ikiwa wazi kwa ajili ya kuandika. Mwandishi wa pili anashindwa mara moja na Could not set lock on file, ikifuatiwa na PID inayoshikilia faili hiyo, ambayo kwa kawaida ni shell ya duckdb uliyoiacha wazi kwenye terminal nyingine. Wasomaji hupita read_only=True, ndiyo sababu connect inatumia flag hiyo. Ikiwa michakato kadhaa inahitaji kuandika kwa wakati mmoja, hiyo ni kazi ya injini nyingine: SQLite katika hali ya WAL inaruhusu wasomaji kufanya kazi wakati mwandishi mmoja akifanya commit, na muda wa kusubiri (busy timeout) huwafanya waandishi wengine kusubiri badala ya kushindwa. DuckDB dhidi ya SQLite kwa mzigo wa kazi wa seva inalinganisha mifumo hiyo miwili, na kuendesha SQLite katika uzalishaji kwenye VPS inashughulikia mipangilio inayofanya WAL ifanye kazi ipasavyo.
Kazi ya kusasisha (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()Iendeshe kwa mikono mara moja:
sudo -u research /opt/research/venv/bin/python /opt/research/refresh.pyUendeshaji wa kwanza hujaza data ya miaka iliyopita na kuchapisha mstari mmoja kwa kila ticker ikiwa na idadi ya maelfu ya safu. Iendeshe tena baada ya dakika moja na kila mstari utaonyesha safu 1 au 2, kwa sababu kazi hiyo huanza kutoka siku ya mwisho iliyohifadhiwa tayari. Uendeshaji huo wa pili ndio jaribio la kweli: kama idadi bado iko katika maelfu, max(day) hairejeshi chochote na kitendo cha kuingiza data (insert) kinaandika upya historia yako yote kila usiku.
Ukaguzi wa df.empty ndio mstari muhimu zaidi katika faili. Ticker.history() haitoi kosa (raise) kwa alama (symbol) isiyo sahihi au iliyoondolewa kwenye soko. Inachapisha onyo kuhusu alama hiyo ikiwezekana kuondolewa kwenye soko bila data ya bei kupatikana (maneno kamili hubadilika kati ya matoleo ya maktaba) na kurejesha DataFrame tupu. Kazi isiyo na ukaguzi huo haitaandika chochote, itatoka kwa exit code 0, na systemd itaonyesha uendeshaji wenye afya (kijani) wakati jedwali linaacha kukua kimyakimya. Utagundua wiki kadhaa baadaye, kutoka kwenye skrini inayorejesha safu zilezile kila siku.
Ushughulikiaji wa timestamp una sababu pia. Index inayorejeshwa na feed inaweza kubeba timezone ya soko la hisa, na kuondoa offset si sawa na kubadilisha kwenda UTC. Kikao cha Tokyo kilichowekwa alama ya saa sita usiku kwa saa za huko hubadilika na kuwa siku ya kalenda iliyotangulia katika UTC, kwa hivyo ubadilishaji wa UTC hubadilisha kimyakimya kila bar ya Kijapani kurudi nyuma siku moja na kuharibu primary key. tz_localize(None) huhifadhi tarehe ya kikao cha soko lenyewe, ambayo ndiyo maana ya bar ya kila siku.
Kipima muda kinachofanya kazi wakati soko linafungwa
Soko la Marekani hufungwa saa 16:00 kwa saa za New York, na rekodi za mwisho za miamala huchukua dakika chache kukamilika, kwa hivyo kazi hii huendeshwa saa 16:20. Iandike hiyo kwa saa za New York, si kwa UTC. New York iko UTC minus 5 wakati wa baridi na UTC minus 4 wakati wa kiangazi, kwa hivyo kipima muda kilichoandikwa kwa saa ya UTC isiyobadilika kitayumba kwa saa moja mara mbili kwa mwaka na kuanza kufanya kazi kabla ya soko kufungwa. systemd 252 na matoleo mapya zaidi hukubali eneo la saa (timezone) moja kwa moja ndani ya OnCalendar, na Ubuntu 24.04 inakuja na toleo la 255. Thibitisha toleo lako kwa kutumia 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.targetType=oneshot ndiyo aina pekee ya huduma inayokubali zaidi ya ExecStart moja, na huiendesha kwa mpangilio, ikisimama ikiwa moja itatoka na msimbo usio sifuri (non-zero). Hiyo ndiyo tabia unayotaka hasa: usasishaji ulioshindwa haupaswi kufuatiwa na onyesho la data ya zamani. Persistent=true ni muhimu kwenye VPS inayowaka upya (reboot) kwa ajili ya masasisho ya kernel. Ikiwa reboot itatokea saa 16:15 bila mpangilio huu, kazi hiyo itapotea; ukiwa nao, kazi hiyo itaendeshwa mara tu mashine itakapokuwa tayari.
sudo systemctl daemon-reload
sudo systemctl enable --now research-refresh.timer
systemctl list-timers research-refresh.timerlist-timers inapaswa kuonyesha safu ya NEXT inayoshikilia muda wa kazi ya siku ya kazi inayofuata, iliyogeuzwa kuwa saa ya ndani ya seva. Orodha tupu inamaanisha kuwa kipima muda hakijawashwa, au unit inakosa sehemu yake ya [Install], kwa hivyo enable haikuwa na kitu cha kuunganisha kwenye timers.target.
Skrini: SQL kwanza, modeli mwisho
SQL ni sahihi na haina gharama kwa kila uendeshaji. Modeli si mojawapo ya hayo. Kwa hivyo, hoja (query) hupunguza upeo, na ni yale matokeo yaliyosalia pekee ndiyo yanayotumwa kwenye modeli. Hifadhi hii kama /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;Kichujio cha n > 50 si pambo. ROWS BETWEEN 49 PRECEDING AND CURRENT ROW hupiga wastani wa safu zozote zilizopo, kwa hivyo safu ya tatu ya ticker hurejesha wastani wa siku tatu na bado huiita ma50. Linganisha hilo na ma20 na utaunda crossover mwanzoni mwa historia ya kila ticker ambayo haikutokea kamwe. Kuchuja kulingana na nambari ya safu huondoa safu ambazo dirisha lake halikuwa limejaa.
Kile ambacho modeli hukiona, na kile ambacho haikioni kamwe
# /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,
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()Kikomo cha maneno katika system prompt hupunguza gharama kubwa ya matumizi. Maelekezo ya kutumia safu mlalo (rows) ulizopewa pekee ndiyo jambo unalopaswa kulithibitisha badala ya kuliweka kwenye imani: futa safu wima (column) moja kutoka kwenye block, iendeshe tena, na usome matokeo. Ikiwa namba ya safu hiyo bado inaonekana, basi modeli imejaza pengo hilo, na prompt yako haijakazwa vya kutosha. Jaribio hilo huchukua dakika 2 na ndiyo njia pekee ya kweli ya kujua.
Modeli haioni kamwe API key, haioni kamwe njia ya database (database path), na haiendeshi query yoyote. Inapokea safu mlalo na kurejesha maandishi. Mpaka huo ndio unaofanya matokeo yaweze kukaguliwa, kwa sababu kila namba katika dokezo inapaswa pia kuonekana katika block uliyotuma, na unaweza kuzilinganisha mstari kwa mstari. Kwa maelezo zaidi kuhusu upande wa prompting wa jambo hili, kutumia Claude kwa uchambuzi wa kifedha inaelezea zaidi kuhusu kile ambacho modeli ina uwezo mzuri wa kukisoma.
Utekelezaji mmoja, kuanzia mwanzo hadi mwisho
Saa 16:20 kwa saa za New York, kipima muda huanzisha huduma. refresh.py huomba data ya kila ticker kuanzia siku ya mwisho iliyohifadhiwa, huandika pau moja au mbili mpya kwa kila moja, na kuchapisha mstari mmoja kwa kila ticker. screen.py hufungua faili hiyo hiyo, huendesha hoja ya wastani wa mjongeo (moving average), na kupata safu chache. Safu hizo huwa kizuizi cha maandishi cha token mia chache. Wito mmoja wa API huibadilisha kuwa dokezo fupi, dokezo hilo huenda kwenye jarida, na safu moja hutua katika runs ikiwa na idadi ya token na idadi ya matokeo yaliyopatikana.
journalctl -u research-refresh.service -n 50 --no-pagerLogi yenye afya huwa na mstari mmoja kwa kila ticker, ikifuatiwa na dokezo, kisha research-refresh.service: Deactivated successfully. Baada ya wiki moja, soma gharama yako mwenyewe kutoka kwenye hifadhidata:
SELECT count(*) AS runs,
sum(input_tokens) AS in_tokens,
sum(output_tokens) AS out_tokens
FROM runs;Zidisha jumla hizo kwa bei za kila milioni ya token ambazo modeli yako inaziorodhesha siku unayozisoma. Hiyo inakupa takwimu halisi badala ya makadirio ya mtu mwingine. Bei zilizochapishwa hubadilika. Hesabu haibadiliki.
Kwa nini backtests hupata overfit, na jinsi ya kufuatilia hilo
Andika upya screen kama utendaji wa urefu wa madirisha mawili, pitia gridi ya jozi, na uzipange kulingana na faida. Jozi bora itaonekana kuwa nzuri sana. Hilo ndilo tatizo, si matokeo. Gridi ya jozi 200 ni majaribio 200, na umehifadhi ile iliyopata bahati zaidi.
Unaweza kuona hilo likitokea ndani ya dakika kumi. Gawanya hifadhi yako mara mbili kulingana na tarehe. Pitia gridi kwenye nusu ya kwanza pekee na uandike mshindi. Pitia gridi hiyo hiyo kwenye nusu ya pili. Ikiwa washindi wawili wako mbali sana, vigezo hivyo vinafit kelele (noise), na jozi inayoshinda kwenye nusu uliyoiwekea vigezo haisemi chochote kuhusu kesho.
Survivorship ni mbaya zaidi kuliko overfitting kwa sababu kurekebisha vigezo hakuwezi kuiondoa. Orodha yako ya ticker ni wanachama wa index wa leo, kwa hivyo inajumuisha tu kampuni zilizosalia. Ikiwa utaiomba feed ticker iliyoondolewa kwenye orodha mwaka 2019, itakupa data tupu, jambo linalomaanisha kuwa kampuni hiyo haijawahi kuingia kwenye hifadhi yako na haijawahi kuingia kwenye jaribio lako. Kila backtest unayoendesha imeshawatenga wale waliofeli.
Restated fundamentals huvunja mpangilio wa muda. Takwimu ya mapato ambayo API inatoa leo kwa robo ya mwaka ya 2019 si lazima iwe takwimu iliyochapishwa mwaka 2019. Screen inayochanganya fundamentals za leo na bei za 2019 inatumia taarifa ambazo hazikuwepo wakati huo. Bei kwa kawaida ni salama hapa. Fundamentals kwa kawaida si salama.
Adjusted prices hubadilika chini yako. Kwa auto_adjust=True bei za kufunga (closes) hurekebishwa nyuma kwa ajili ya gawio na migawanyo ya hisa (splits), kwa hivyo swali lilelile likiendeshwa mwezi ujao litarudisha historia tofauti kidogo. Kuhifadhi safu ulizotumia kihalisi ndiko kunakofanya matokeo yaweze kurudiwa, na ndiyo sababu nyingine ya kuwepo kwa hifadhi ya ndani (local store).
Backtest pia hupuuza kamisheni na slippage, na inadhani kuwa oda yako haibadilishi bei. Mambo hayo yanahusu utekelezaji, ambao uko nje ya wigo wa hapa na yamejadiliwa katika kuendesha trading bots kwenye VPS.
Njia za kufeli na ujumbe utakaouona
error: externally-managed-environment wakati pip inapoendeshwa. Upo nje ya virtual environment. Iite /opt/research/venv/bin/pip kwa kutumia njia kamili (full path).
Could not set lock on file, ikifuatiwa na PID. Mchakato mwingine umeshikilia faili la DuckDB kwa ajili ya kuandika, kwa kawaida ni interactive shell uliyosahau. Ifunge, au fungua muunganisho wa pili kwa kutumia read_only=True.
Main process exited, code=killed, status=9/KILL katika systemctl status. Kernel imesitisha kazi hiyo kwa sababu ya ukosefu wa kumbukumbu (memory). Thibitisha kwa journalctl -k | grep -i oom, kisha punguza memory_limit katika store.py.
Uendeshaji uliofanikiwa (green run) ambao hauandiki chochote. systemctl status inasoma active (exited) na jedwali halijakua. Feed imerejesha fremu tupu. Hitilafu hii hufichika kwa muda mrefu zaidi, kwa hivyo fanya kazi hiyo itoke (exit) kwa status isiyo ya sifuri wakati kila ticker inaporejea ikiwa tupu.
Timer imewaka wakati wa likizo ya soko. systemd haijui kalenda ya soko la hisa, kwa hivyo Mon-Fri inajumuisha siku za likizo. Uendeshaji hutokea, feed haina jipya, na kazi inapaswa kuchukulia hilo kama hali ya kawaida badala ya hitilafu.
429 kutoka kwa API. Umezidi kikomo cha kasi (rate limit). Anthropic SDK hujaribu tena kwa kutumia backoff yenyewe, na Anthropic(max_retries=5) huongeza idadi ya majaribio. Ikiwa bado inafeli kila siku, kazi hiyo inaomba data nyingi mno kwa wakati mmoja.
Hiki si kitu gani
Hii ni zana ya kusaidia utafiti. Mfano wa AI unaofanya muhtasari wa faili hutoa tafsiri ya faili hiyo, na unaweza kukosea kwa kujiamini kuhusu namba iliyoandikwa kwenye matini. Hii ndiyo sababu kila takwimu katika dokezo hili lazima irejelee mstari ulioutuma. Ichukulie matokeo haya kama orodha fupi ya mambo ya kusoma wewe mwenyewe. Hakuna kitu hapa ambacho ni ushauri wa kifedha, na hakuna hata kimoja kinachoweza kuchukuliwa kama ishara ya biashara.
Backtests ni muhimu kwa kukataa mawazo mabaya, lakini ni dhaifu katika kuthibitisha mawazo mazuri. Mkakati unaofeli kwenye data yako mwenyewe umekufa kweli. Mkakati unaofaulu umepona tu kwenye data yako, jambo ambalo ni madai madogo sana kuliko inavyoweza kuhisiwa saa 7 usiku.
Utekelezaji wa amri umewekwa nje ya wigo huu kwa makusudi. Amri na vitambulisho vya broker hubeba wasifu wa hatari tofauti na kisanduku cha utafiti cha kusoma pekee (read-only), na kuvichanganya huweka funguo za biashara kwenye mashine moja na LLM prompt. Ikiwa unataka kuona mahali ambapo muundo huu unakaa kando ya mambo mengine yanayofaa kuendeshwa kwenye seva yako mwenyewe, mawakala wa AI wanaojiendesha wenyewe wanaofaa kuendeshwa ndio mwongozo mpana zaidi.
FAQ
Je, nahitaji huduma ya kulipia ya data ya soko?
Si kwa ajili ya prototype. Huduma ya bure isiyo rasmi inafaa kwa kujifunza muundo wa mfumo, lakini itafeli, kwa sababu inategemea tovuti ambayo haina wajibu wowote kwako. Kwa kawaida hufeli kwa kutoa fremu tupu badala ya kutoa exception, kwa hivyo kazi yako lazima ikague idadi ya safu (row counts). Hamia kwenye huduma ya kulipia yenye API iliyoandikwa vizuri na anwani ya usaidizi pindi data inapoanza kutumika kufanya maamuzi. Hifadhi (store) ndiyo inayofanya mabadiliko hayo kuwa rahisi: ni kazi ya kuchota data (fetch function) pekee inayobadilika, huku ratiba, schema na skrini zikibaki kama zilivyo.
Je, niweke bei kwenye SQLite au DuckDB?
DuckDB ni ya safu wima (columnar) na imeundwa kwa ajili ya kuchanganua safu nyingi ili kukokotoa jumla, jambo ambalo ndilo hasa wastani wa miondoko (moving average) ya miaka kumi ya data. SQLite inaelekea kwenye safu mlalo (row oriented) na ni bora zaidi kwa usomaji na uandishi mdogo mdogo kutoka kwa michakato mingi kwa wakati mmoja. Kwa kazi moja iliyoratibiwa inayoongeza mamia ya safu na kisha kuchanganua mamilioni, DuckDB inafaa zaidi. Ikiwa michakato mingi lazima iandike kwa wakati mmoja, SQLite katika hali ya WAL inaruhusu wasomaji kufanya kazi wakati mwandishi mmoja akifanya commit, na busy timeout huwafanya waandishi wengine kusubiri badala ya kufeli moja kwa moja.
Gharama za simu za LLM kwa mwezi ni kiasi gani?
Ingiza usage.input_tokens na usage.output_tokens kutoka kwa kila jibu kwenye jedwali, kisha zidisha jumla ya kila wiki kwa bei ya kila token milioni ambayo modeli yako inaorodhesha siku unayokagua. Hiyo ndiyo namba pekee inayobaki kuwa sahihi katika robo ijayo. Uendeshaji mmoja wa kila siku kwenye skrini fupi ni idadi ndogo ya simu, na urefu wa dokezo ndiyo sehemu unayoweza kuidhibiti: kupunguza muhtasari usizidi maneno 200 huokoa zaidi kuliko kutuma safu chache, kwa sababu token za pato (output tokens) zina bei ya juu kuliko token za ingizo (input tokens) kwenye kila modeli ya Claude.
Kwa nini kazi yangu ilifanikiwa lakini haikuandika safu mpya?
Kuna sababu mbili za kawaida. Soko lilikuwa limefungwa, kwa sababu ratiba ya systemd Mon-Fri inajumuisha likizo za soko la hisa. Au huduma ya data ilirejesha fremu tupu kwa kila ticker, jambo ambalo maktaba za mteja mara nyingi huripoti kama onyo lililochapishwa badala ya exception, kwa hivyo mchakato bado unamalizika kwa exit 0 na systemd bado inaonyesha uendeshaji wa kijani. Zitofautishe kwa kulinganisha SELECT max(day) FROM prices dhidi ya siku ya mwisho ya kweli ya biashara, na ufanye kazi hiyo imalizike kwa exit isiyo sifuri (non zero) wakati kila ticker inaporejea tupu.
Je, wakala (agent) anaweza kuamua nini cha kununua?
Hapana, na kujaribu kumjenga ili afanye hivyo ndiko miradi hii inakosea. Modeli haina ufikiaji wa soko, haina mtazamo wa nafasi yako au hali yako ya kodi, na haina njia ya kukagua namba zake dhidi ya kitu chochote. Inachoweza kufanya vizuri ni kusoma kiasi kikubwa cha maandishi na kukuambia ni vitu vichache vipi vinavyostahili umakini wako leo. Hakuna chochote inachoandika ambacho ni ushauri wa kifedha, na uamuzi, pamoja na wajibu wake, unabaki kwako.