VPS üzerinde self-hosted borsa araştırma ajanı kurulumu
Python, DuckDB ve systemd kullanarak kendi borsa araştırma ajanınızı oluşturun. Market verilerini çeken, yerel depolayan ve LLM ile analiz eden sistemin kurulum rehberi.
Self-hosted borsa araştırma ajanı nedir
Self-hosted borsa araştırma ajanı, kendi sunucunuzda çalışan ve piyasa verilerini belirli bir zaman çizelgesine göre çeken, bu verileri yerel bir veritabanında tutan, üzerinde tarama yapan ve büyük bir dil modeline (LLM) nelerin değiştiğini raporlatan küçük bir programdır. Bu ajan verileri okur ve filtreler. Alım satım işlemi yapmaz ve bu kılavuzdaki hiçbir bilgi finansal tavsiye niteliği taşımaz.
Bu sistemi sıfırdan kuran iki kişi farklı kütüphaneler seçse bile sonuçta aynı dört bileşene ulaşacaktır: fiyatları ve temel verileri sağlayan bir akış, şimdiye kadar çektiğiniz tüm satırları saklayan yerel bir depo, depoyu zamanlayıcı ile güncelleyen bir iş ve kalan satırları cümlelere dönüştüren bir LLM katmanı. Bu kılavuz; Python, DuckDB, systemd zamanlayıcısı ve Claude API (uygulama programlama arayüzü) kullanarak bu yapıyı inşa eder. Yürütme işlemi, kendine has hata modlarına sahip ayrı bir iştir ve bunun yerine işlem botları için yapılandırılmış bir VPS üzerinde çalıştırılmalıdır.
Dört bölüm ve her birinin işlevi
Feed (Besleme), dış dünya ile iletişim kuran tek bölümdür. Bir borsa kodu ve tarih aralığı için nasıl istekte bulunacağını ve satırları nasıl geri döndüreceğini bilir. Akışın devamındaki her şey feed yerine veritabanınızı okur; bu sayede bir feed kesintisi, ekranın bozulması yerine yalnızca bir günlük yeni veri kaybına neden olur.
Store (Depo), tüm bu çalışmanın temel amacıdır. Kaydetmediğiniz bir günlük kapanış verisi genellikle daha sonra tekrar alınabilir. Ancak gün içi bir fiyat teklifi, revize edilmeden önceki bir tahmin veya yeniden düzenlenmeden önceki temel bir finansal veri sonradan geri getirilemez. Store, verinin söylendiği gün tam olarak ne ifade ettiğine dair bir kayıt oluşturmanızı sağlar.
Scheduler (Zamanlayıcı), yenilemenin ne zaman gerçekleşeceğine karar verir. Bir sunucuda bu, systemd timer yapısıdır; VPS'in burada koddan daha önemli olmasının nedeni de budur.
LLM katmanı, SQL çıktınızın ürettiği kısa bir metin bloğunu okur ve bunun bir özetini yazar. Veritabanına asla bağlanmaz ve sorguyu asla oluşturmaz. Eğer SQL'i model yazarsa, hatalı tek bir token, akıcı bir cümle içinde yanlış bir sayıya dönüşür ve bunu karşılaştırabileceğiniz hiçbir şey kalmaz. Sayıları SQL ürettiğinde, model yalnızca metin kısmında hata yapabilir; siz de bu metni gönderdiğiniz satırlarla karşılaştırarak doğrulayabilirsiniz.
Neden dizüstü bilgisayar yerine VPS üzerinde çalıştırılmalı
Zamanlayıcı bunun temel nedenidir. New York saatiyle 16:00'daki ABD piyasa kapanışı, Berlin'de 22:00, Cakarta'da ise ertesi sabah 04:00'tür. Dizüstü bilgisayar her iki saatte de uyku modundadır. Kaçırılan bir çalışma, geç kalan bir nottan daha maliyetlidir: günlük çubuklar genellikle daha sonra tekrar çekilebilir ancak revize edilen hiçbir veri geri getirilemez; bu nedenle kayıtlarınızdaki boşluk kalıcı olur. Aynı mantık, zamanlaması ve kayıtlı durumu bir yeniden başlatmadan sağ çıkması gereken tüm aracılar için geçerlidir; KiroCrew'u kendi VPS'nizde her zaman açık bir aracı olarak çalıştırmak tam olarak bunun üzerine kuruludur.
İkinci neden daha küçük olsa da hala geçerlidir. Sunucu, giriş kabuğu olmayan tek bir sistem kullanıcısına ait, tek bir iş tarafından kullanılan ve tek bir dosyada tutulan tek bir API anahtarı barındırır. Bunu, internette gezindiğiniz dizüstü bilgisayarınızda düzenlemek çok daha zordur. Anahtarı kodun ve modelin girdisinin dışında tutun; bu konu API anahtarlarını bir yapay zeka aracısının dışında tutmak başlığında ele alınmıştır.
Boyutlandırma: disk, RAM ve tokenlar
Disk kısmı en kolay olanıdır. Günlük bir çubuk, her borsa kodu (ticker) için işlem günü başına bir satırdır ve bir ABD işlem yılı yaklaşık 252 gündür.
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"
}
]Yirmi borsa kodu, bir yılın sonunda 5,040 satır eder. 500 tickers satırı, on yılın sonunda 1,260,000 satıra ulaşır. Her satır bir tarih ve birkaç double değerinden oluşur; DuckDB sütunları sıkıştırılmış halde sakladığı için bu miktar gigabaytlar yerine onlarca megabayt seviyesindedir. Benimki de dahil olmak üzere hiçbir tahmine güvenmeyin. İlk toplu veri yüklemenizden (backfill) sonra du -h /opt/research/data/market.duckdb komutunu çalıştırın ve kendi verilerinizi kullanın.
RAM, küçük bir VPS için zorlayıcı olabilir. DuckDB varsayılan olarak, tek bir sorgu için makinenin belleğinin büyük bir kısmını ve tüm çekirdeklerini kullanır; bu durum bir analitik sunucusu için doğru olsa da, başka işlerin de çalıştığı 2 GB'lık bir makine için yanlıştır. Tüm fiyat tablosu üzerinde yapılan bir toplulaştırma işlemi, sürecin çekirdek (kernel) tarafından sonlandırılmasına neden olur; systemd Main process exited, code=killed, status=9/KILL hatasını bildirirken journalctl -k komutu belleğin yetersiz kalması nedeniyle sürecin öldürüldüğünü (OOM kill) gösterir. memory_limit ve threads değerlerini açıkça ayarladığınızda, sorgu ölmek yerine yavaş çalışacaktır.
Tokenlar tahmin edilmemeli, ölçülmelidir. Her Messages API yanıtı, input_tokens ve output_tokens değerlerini içeren bir usage nesnesi taşır. Her çağrıda bu değerleri bir tabloya yazın; bir hafta sonra gerçek hacminizi öğrenmiş olursunuz. Bu hacmi, kontrol ettiğiniz gün modelinizin listelediği fiyatlarla çarpın. Planlama yaparken iki husus oldukça kararlıdır. Çıktı tokenları her Claude modelinde girdi tokenlarından daha pahalıdır; bu nedenle notu 200 kelimeyle sınırlamak, gönderdiğiniz veriyi kırpmaktan daha fazla tasarruf sağlar. Prompt önbellekleme (caching) ise günde bir kez yapılan işlerde yardımcı olmaz, çünkü önbellek ömrü dakikalarla ölçülür: bir sonraki çalıştırmada önbelleğe alınan blok süresi dolmuş olur ve girdi ücretini tekrar tam olarak ödersiniz. Önbellekleme, tek bir çalıştırmanın aynı büyük metin bloğu üzerinde birçok çağrı yaptığı durumlarda kazanç sağlar.
Bileşenlerin kurulumu
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/dataHerhangi bir kod yazmadan önce kurulumu doğrulayın:
sudo -u research /opt/research/venv/bin/python -c 'import duckdb, yfinance, anthropic; print("ok")'Bu komut ok çıktısını verir. Eğer sanal ortamı atlayıp pip install komutunu sistem Python'ı üzerinde çalıştırdıysanız, Ubuntu 24.04 sizi error: externally-managed-environment hatası ile durduracaktır; çünkü dağıtım /usr/lib/python3 dizininin sahibidir ve pip'in buraya yazmasına izin vermez. Sanal ortam (venv) bir nezaket kuralı değildir. Pip'in dokunmasına izin verilen tek dizindir.
API anahtarı, yalnızca servis kullanıcısının okuyabileceği ve başka kimsenin erişemeyeceği bir dosyaya yerleştirilmelidir:
sudo install -d -m 755 /etc/research
sudo install -m 640 -o root -g research /dev/null /etc/research/env
sudoedit /etc/research/envDosyanın içine, tırnak işareti veya export kullanmadan tek bir satır ekleyin; çünkü systemd bu dosyayı bir kabuğa (shell) aktarmak yerine doğrudan kendisi ayrıştırır:
ANTHROPIC_API_KEY=sk-ant-your-key-hereDepo: iki tablo
# /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) üzerindeki birincil anahtar, yenileme işleminin güvenli bir şekilde tekrarlanabilmesini sağlar. INSERT OR REPLACE, ilgili borsa kodu ve gün için zaten mevcut olan bir satırın üzerine yazar; bu nedenle geçmiş veriyi doldurma (backfill) işlemini iki kez çalıştırmak tabloyu ikiye katlamaz. Anahtar olmadan, bir çökme sonrası yapılan yeniden çalıştırma her çubuğu sessizce kopyalar ve sonrasında hesapladığınız her ortalama, herhangi bir hata mesajı olmaksızın hatalı sonuç verir.
DuckDB, dosyanın yazma amacıyla açık tutulmasına yalnızca tek bir sürecin izin vermesini sağlar. İkinci bir yazıcı, Could not set lock on file hatasıyla ve ardından dosyayı elinde tutan PID ile hemen başarısız olur; bu PID pratikte başka bir terminalde açık bıraktığınız etkileşimli duckdb kabuğudur. Okuyucular read_only=True ile geçer, bu yüzden connect bu bayrağı alır. Eğer birden fazla sürecin aynı anda yazması gerekiyorsa, bu farklı bir motorun işidir: SQLite, WAL modunda bir yazıcı commit yaparken okuyucuların çalışmasına izin verir ve meşguliyet zaman aşımı (busy timeout), diğer yazıcıların hata vermek yerine beklemesini sağlar. Sunucu iş yükü için DuckDB ve SQLite karşılaştırması bu ikisini kıyaslar ve VPS üzerinde üretim ortamında SQLite çalıştırma WAL modunun düzgün çalışmasını sağlayan ayarları ele alır.
Yenileme işi
# /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()İşi bir kez manuel olarak çalıştırın:
sudo -u research /opt/research/venv/bin/python /opt/research/refresh.pyİlk çalıştırma yıllar öncesine ait verileri doldurur ve her borsa kodu (ticker) için binlerce satırlık bir çıktı üretir. Bir dakika sonra tekrar çalıştırdığınızda her satır 1 veya 2 kayıt gösterir; çünkü iş, halihazırda depolanmış olan son günden itibaren başlar. Bu ikinci çalıştırma gerçek testtir: Eğer kayıt sayıları hala binler seviyesindeyse, max(day) hiçbir sonuç döndürmüyor demektir ve insert işlemi her gece tüm geçmişinizi yeniden yazıyordur.
df.empty kontrolü, dosyadaki en önemli satırdır. Ticker.history(), hatalı veya borsadan çıkarılmış bir sembol için hata (raise) vermez. Bunun yerine, sembolün borsadan çıkarılmış olabileceğine ve fiyat verisi bulunamadığına dair bir uyarı yazdırır (tam ifade kütüphane sürümleri arasında değişiklik gösterebilir) ve boş bir DataFrame döndürür. Bu kontrolün olmadığı bir iş hiçbir şey yazmaz, 0 koduyla çıkar ve systemd, tablo sessizce büyümeyi durdurmuşken sağlıklı ve yeşil bir çalışma durumu gösterir. Durumu haftalar sonra, her gün aynı satırları döndüren bir ekrandan fark edersiniz.
Zaman damgası işlemenin de bir nedeni vardır. Akışın döndürdüğü indeks, borsanın saat dilimini taşıyabilir ve ofseti kaldırmak, UTC'ye dönüştürmekle aynı şey değildir. Yerel saatle gece yarısı damgalanmış bir Tokyo seansı, UTC'de bir önceki takvim gününe dönüşür; bu nedenle UTC dönüşümü, her Japonya barını sessizce bir gün geriye kaydırır ve birincil anahtarı (primary key) bozar. tz_localize(None), günlük barın gerçek anlamı olan borsanın kendi seans tarihini korur.
Piyasa kapanışında tetiklenen zamanlayıcı
ABD piyasası New York saatiyle 16:00'da kapanır ve son işlemlerin sisteme işlenmesi birkaç dakika sürer; bu nedenle görev 16:20'de çalıştırılır. Bunu UTC yerine New York saatiyle yazın. New York kışın UTC eksi 5, yazın ise UTC eksi 4 saat dilimindedir; bu yüzden sabit bir UTC saati olarak yazılan zamanlayıcı yılda iki kez bir saat kayar ve kapanıştan önce tetiklenmeye başlar. systemd 252 ve sonraki sürümler, OnCalendar içinde doğrudan saat dilimi kabul eder ve Ubuntu 24.04, 255 sürümüyle gelir. Kendi sürümünüzü systemctl --version ile doğrulayın.
# /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, birden fazla ExecStart kabul eden tek servis türüdür ve bunları sırayla çalıştırarak, herhangi biri sıfırdan farklı bir değerle çıkış yaparsa durur. Tam olarak istediğiniz davranış budur: başarısız bir yenileme işleminin ardından eski verilerle ekran tazelenmemelidir. Persistent=true, çekirdek güncellemeleri için yeniden başlatılan bir VPS üzerinde önem kazanır. Bu ayar olmadan 16:15'te yapılan bir yeniden başlatmada görev çalışmaz; bu ayar mevcut olduğunda ise makine açılır açılmaz görev yürütülür.
sudo systemctl daemon-reload
sudo systemctl enable --now research-refresh.timer
systemctl list-timers research-refresh.timerlist-timers, sunucunun yerel saatine dönüştürülmüş bir sonraki hafta içi çalışma zamanını içeren bir NEXT sütunu göstermelidir. Boş bir liste, zamanlayıcının etkinleştirilmediği veya birimin [Install] bölümünün eksik olduğu, dolayısıyla enable öğesinin timers.target içine bağlayacak bir şeyi olmadığı anlamına gelir.
Ekran: Önce SQL, sonra model
SQL kesindir ve her çalıştırmada maliyet oluşturmaz. Model ise ne kesindir ne de ücretsizdir. Bu nedenle sorgu, veri kümesini daraltır ve yalnızca hayatta kalan veriler modele gönderilir. Bunu /opt/research/screen.sql olarak kaydedin.
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 filtresi bir süsleme değildir. ROWS BETWEEN 49 PRECEDING AND CURRENT ROW, mevcut olan tüm satırların ortalamasını alır; bu nedenle bir borsa kodunun üçüncü satırı, üç günün ortalamasını döndürür ve buna yine de ma50 denir. Bunu ma20 ile karşılaştırdığınızda, her borsa kodunun geçmişinin başında, aslında hiç gerçekleşmemiş bir kesişim (crossover) icat etmiş olursunuz. Satır numarasına göre filtreleme yapmak, pencerenin tam dolmadığı satırları eler.
Modelin gördükleri ve asla görmedikleri
# /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()Sistem istemindeki kelime sınırı, faturanın maliyetli kısmını kısıtlar. Yalnızca verilen satırların kullanılması talimatı, güvenmek yerine doğrulamanız gereken bir kısıtlamadır: bloktan bir sütunu silin, işlemi tekrar çalıştırın ve çıktıyı okuyun. Eğer o sütuna ait bir rakam hala görünüyorsa, model boşluğu kendi doldurmuş demektir ve isteminiz yeterince sıkı değildir. Bu test iki dakika sürer ve gerçeği öğrenmenin tek dürüst yoludur.
Model, API anahtarını asla görmez, veritabanı yolunu asla görmez ve hiçbir sorguyu çalıştırmaz. Yalnızca satırları alır ve metin döndürür. Bu sınır, çıktının denetlenebilir olmasını sağlar; çünkü nottaki her rakam gönderdiğiniz blokta da yer almalıdır ve bunları satır satır karşılaştırabilirsiniz. Bu konunun istem tarafı hakkında daha fazla bilgi için finansal analiz için Claude kullanımı bölümü, modelin okuma konusunda nelerde başarılı olduğuna daha derinlemesine değinmektedir.
Uçtan uca tek bir çalıştırma
New York saatiyle 16:20'de zamanlayıcı servisi başlatır. refresh.py, beslemeden son kayıtlı günden itibaren her bir borsa kodu (ticker) için veri ister, her biri için bir veya iki yeni çubuk yazar ve her borsa kodu için bir satır çıktı verir. screen.py aynı dosyayı açar, hareketli ortalama sorgusunu çalıştırır ve birkaç satırlık sonuç alır. Bu satırlar birkaç yüz token'lık bir metin bloğuna dönüşür. Tek bir API çağrısı bunları kısa bir nota dönüştürür, not günlüğe kaydedilir ve token sayıları ile isabet sayısı runs içine bir satır olarak eklenir.
journalctl -u research-refresh.service -n 50 --no-pagerSağlıklı bir log dosyası, her borsa kodu için bir satır, ardından not ve son olarak research-refresh.service: Deactivated successfully içeriğini barındırır. Bir hafta sonra, kendi maliyetinizi veritabanından şu şekilde okuyun:
SELECT count(*) AS runs,
sum(input_tokens) AS in_tokens,
sum(output_tokens) AS out_tokens
FROM runs;Bu toplamları, okuma yaptığınız gün modelinizin listelediği milyon token başına fiyatlarla çarpın. Bu işlem, başkalarının tahminleri yerine size gerçek rakamı verir. Yayınlanan fiyatlar değişir. Matematiksel hesaplama ise değişmez.
Backtest'ler neden aşırı uyum (overfit) sağlar ve bu süreç nasıl izlenir
Ekranı iki pencere uzunluğunun bir fonksiyonu olarak yeniden yazın, bir çift ızgarasını (grid) tarayın ve bunları getiriye göre sıralayın. En iyi çift mükemmel görünecektir. Sorun budur, sonuç değil. 200 çiftlik bir ızgara 200 deney demektir ve siz en şanslı olanı elinizde tuttunuz.
Bunun gerçekleştiğini on dakika içinde izleyebilirsiniz. Veri deposunu tarihe göre ikiye bölün. Izgarayı sadece ilk yarıda tarayın ve kazananı not edin. Aynı ızgarayı ikinci yarıda tarayın. Eğer iki kazanan birbirinden çok uzaksa, parametreler gürültüye uyum sağlıyordur ve sadece üzerinde ayarlama yaptığınız yarıda kazanan bir çift, yarın hakkında hiçbir şey söylemez.
Survivorship (hayatta kalma yanlılığı), aşırı uyumdan daha kötüdür çünkü ayarlama bunu düzeltemez. Ticker listeniz bugünün endeks üyeleridir, bu yüzden sadece hayatta kalan şirketleri içerir. Veri akışından 2019'da listeden çıkarılan bir ticker isterseniz, size boş bir çerçeve döndürür; bu da o şirketin deponuza hiç girmediği ve testinize hiç dahil olmadığı anlamına gelir. Çalıştırdığınız her backtest, başarısız olanları zaten dışarıda bırakmıştır.
Yeniden ifade edilen temeller (restated fundamentals) zaman çizelgesini bozar. API'nin 2019'un bir çeyreği için bugün döndürdüğü gelir rakamı, her zaman 2019'da yayınlanan rakam değildir. Bugünkü temelleri 2019 fiyatlarıyla karıştıran bir ekran, o zaman var olmayan bilgileri kullanıyordur. Fiyatlar burada genellikle güvenlidir. Temeller ise genellikle değildir.
Düzeltilmiş fiyatlar altınızda hareket eder. auto_adjust=True ile kapanışlar temettüler ve bölünmeler için geriye dönük olarak düzeltilir, bu nedenle gelecek ay çalıştırılan aynı sorgu biraz farklı bir geçmiş döndürür. Aslında kullandığınız satırları saklamak, bir sonucu tekrarlanabilir kılan şeydir ve yerel deponun var olmasının bir diğer nedenidir.
Backtest ayrıca komisyonları ve kaymaları (slippage) görmezden gelir ve emrinizin fiyatı hareket ettirmediğini varsayar. Bunlar yürütme (execution) kapsamına girer, bu konu burada ele alınmamıştır ve VPS üzerinde işlem botları çalıştırma bölümünde açıklanmıştır.
Hata modları ve karşılaşacağınız dizgeler
error: externally-managed-environment pip çalıştırıldığında görülür. Sanal ortamın dışındasınız. /opt/research/venv/bin/pip komutunu tam yolunu belirterek çağırın.
Could not set lock on file, ardından bir PID numarası ile gelir. Başka bir süreç, DuckDB dosyasını yazma modunda açık tutuyordur; bu genellikle açık unutulan etkileşimli bir kabuktur. İlgili süreci kapatın veya ikinci bağlantıyı read_only=True ile açın.
Main process exited, code=killed, status=9/KILL ve systemctl status içinde görülür. Çekirdek, bellek yetersizliği nedeniyle işi sonlandırmıştır. Durumu journalctl -k | grep -i oom ile doğrulayın, ardından store.py içindeki memory_limit değerini düşürün.
Hiçbir şey yazmayan başarılı bir çalışma. systemctl status, active (exited) dosyasını okur ancak tablo büyümemiştir. Veri akışı boş çerçeveler döndürmüştür. Bu hata türü en uzun süre fark edilmeyen hatadır; bu nedenle her veri akışı boş döndüğünde işin sıfır olmayan bir çıkış kodu üretmesini sağlayın.
Zamanlayıcının piyasa tatilinde tetiklenmesi. systemd borsa takvimini bilmez, bu yüzden Mon-Fri tatil günlerini de kapsar. Çalışma gerçekleşir, veri akışında yeni bir bilgi yoktur; iş, bu durumu bir hata olarak değil, normal bir durum olarak ele almalıdır.
API'den 429 hatası. Hız sınırını (rate limit) aştınız. Anthropic SDK, geri çekilme (backoff) mekanizmasıyla kendi kendine yeniden deneme yapar ve Anthropic(max_retries=5) deneme sayısını artırır. Eğer hata her gün devam ediyorsa, iş tek bir istekte çok fazla veri talep ediyordur.
Bu ne değildir
Bu bir araştırma asistanıdır. Bir dosyalama özetleyen model, o dosyalamanın bir okumasını üretir ve metinde basılı bir sayı konusunda kendinden emin bir şekilde hatalı olabilir; bu nedenle nottaki her rakam, gönderdiğiniz bir satıra dayandırılmalıdır. Çıktıyı, bizzat okumanız gereken maddelerin kısa bir listesi olarak değerlendirin. Burada yer alan hiçbir şey finansal tavsiye değildir ve hiçbiri bir sinyal niteliği taşımaz.
Geriye dönük testler (backtest), fikirleri reddetmek için yararlıdır ancak doğrulamak için zayıftır. Kendi verileriniz üzerinde başarısız olan bir strateji kesinlikle ölüdür. Başarılı olan bir strateji ise yalnızca verilerinizden sağ çıkmıştır; bu, gece saat 01:00'de hissedildiğinden çok daha küçük bir iddiadır.
İşlem yürütme, kasıtlı olarak kapsam dışında tutulmuştur. Emirler ve aracı kurum kimlik bilgileri, salt okunur bir araştırma kutusundan farklı bir risk profili taşır; bunları karıştırmak, işlem anahtarlarını bir LLM istemiyle aynı makineye koymak anlamına gelir. Bu modelin kendi sunucunuzda çalıştırmaya değer diğer şeylerin yanında nerede durduğunu görmek isterseniz, çalıştırmaya değer self-hosted yapay zeka ajanları daha geniş bir tur sunar.
FAQ
Ücretli bir piyasa verisi akışına ihtiyacım var mı?
Prototip aşamasında gerekmez. Ücretsiz ve resmi olmayan bir akış, sistemin yapısını öğrenmek için yeterlidir; ancak bu akışlar size karşı hiçbir sorumluluğu olmayan web sitelerine bağlı olduğundan kesintiye uğrayacaktır. Genellikle hata fırlatmak yerine boş veri çerçeveleri döndürdükleri için, görevinizin satır sayılarını kontrol etmesi gerekir. Veriler bir karar mekanizmasını beslemeye başladığında, belgelenmiş bir API'ye ve destek adresine sahip ücretli bir akışa geçin. Bu değişimi kolaylaştıran şey veri deposudur: yalnızca veri çekme işlevi değişir; zamanlama, şema ve ekran aynı kalır.
Fiyatları SQLite içinde mi yoksa DuckDB içinde mi tutmalıyım?
DuckDB sütun tabanlıdır ve toplu hesaplamalar için çok sayıda satırı taramak üzere tasarlanmıştır; on yıllık çubuk verileri üzerinden hareketli ortalama hesaplamak tam olarak buna örnektir. SQLite satır tabanlıdır ve aynı anda birden fazla süreçten gelen çok sayıda küçük okuma ve yazma işlemi için daha uygundur. Birkaç yüz satır ekleyip ardından milyonlarca satırı tarayan tek bir zamanlanmış görev için DuckDB daha iyi bir tercihtir. Eğer birden fazla sürecin aynı anda yazması gerekiyorsa, SQLite'ın WAL modu bir yazıcı commit işlemi yaparken okuyucuların çalışmaya devam etmesine izin verir; busy_timeout ayarı ise diğer yazıcıların hata vermek yerine beklemesini sağlar.
LLM çağrıları aylık ne kadar tutar?
Her yanıttan usage.input_tokens ve usage.output_tokens değerlerini bir tabloya kaydedin, ardından haftalık toplamlarınızı kontrol ettiğiniz gün modelinizin listelediği milyon token başına fiyatla çarpın. Gelecek çeyrekte geçerli kalacak tek rakam budur. Günlük yapılan kısa bir tarama az sayıda çağrı içerir; notun uzunluğu ise kontrol edebileceğiniz kısımdır: özeti 200 kelimeyle sınırlamak, daha az satır göndermekten daha fazla tasarruf sağlar çünkü her Claude modelinde çıktı tokenları, girdi tokenlarından daha pahalıdır.
Görevim neden başarıyla tamamlandı ancak yeni satır yazmadı?
Bunun iki yaygın nedeni vardır. Piyasa kapalı olabilir, çünkü bir systemd Mon-Fri zamanlaması borsa tatillerini kapsar. Ya da veri akışı her borsa kodu için boş bir çerçeve döndürmüş olabilir; istemci kütüphaneleri bunu genellikle bir istisna yerine yazdırılan bir uyarı olarak rapor eder, bu nedenle süreç 0 koduyla çıkar ve systemd çalışmayı başarılı (yeşil) olarak gösterir. Bu durumu, SELECT max(day) FROM prices değerini son gerçek işlem günüyle karşılaştırarak ayırt edin ve her borsa kodu boş döndüğünde görevin sıfır olmayan bir kodla çıkmasını sağlayın.
Ajan ne alacağına karar verebilir mi?
Hayır; bu tür projelerin başarısız olduğu nokta, ajanı buna zorlamaya çalışmaktır. Modelin piyasa erişimi, pozisyonunuz veya vergi durumunuz hakkında bilgisi yoktur ve kendi sayılarını doğrulayabileceği bir mekanizması bulunmaz. Modelin başarılı olduğu alan, büyük miktarda metni okuyup bugün dikkatinizi hak eden birkaç öğeyi size bildirmektir. Yazdığı hiçbir şey finansal tavsiye niteliğinde değildir; karar ve bunun getirdiği sorumluluk size aittir.