VPS'te self-hosted hisse araştırma aracı
VPS üzerinde self-hosted hisse araştırma aracı kurun: piyasa verisi feed'i, DuckDB store'u, kapanışta çalışan systemd timer ve LLM taraması tek yapıda.
Self-hosted hisse araştırma aracının ne olduğu
Self-hosted hisse araştırma aracı, sahibi olduğunuz bir sunucuda çalışan küçük bir programdır. Belirli aralıklarla piyasa verilerini çeker, bu verileri yerel bir veritabanında saklar, üzerinde bir filtreleme işlemi yürütür ve değişen noktaları açıklamak üzere bir large language model'e (LLM) yazdırır. Verileri okur ve filtreler. Alım satım yapmaz. Bu kılavuzdaki hiçbir bilgi finansal danışmanlık değildir.
Bu sistemi sıfırdan geliştiren iki kişi farklı kütüphaneleri seçebilir. Buna rağmen sonuçta aynı dört bileşen ortaya çıkar: fiyat ve temel verileri sağlayan bir feed, şimdiye kadar alınan her satırı saklayan yerel bir store, store'u zamanlayıcıyla yenileyen bir job ve kalan satırları cümlelere dönüştüren bir LLM katmanı. Bu kılavuzda bu yapı Python, DuckDB, bir systemd timer ve Claude API (application programming interface) kullanılarak oluşturulur. Execution ayrı bir job'dır ve farklı hata türlerine sahiptir. Bu nedenle trading botları için yapılandırılmış bir VPS üzerinde çalıştırılmalıdır.
Dört bölüm ve her birinin yaptığı iş
Feed, dış dünyayla iletişim kuran tek bölümdür. Ticker ve tarih aralığı istemeyi, ardından satırları döndürmeyi bilir. Bundan sonraki tüm bileşenler feed yerine veritabanınızı okur. Bu nedenle feed kesintisi, bozuk bir ekran yerine yalnızca bir günlük yeni veri kaybına yol açar.
Store, tüm çalışmanın temel amacıdır. Kaydetmediğiniz günlük kapanış verisini genellikle daha sonra yeniden alabilirsiniz. Ancak gün içi bir fiyat teklifi, revize edilmeden önceki bir tahmin veya yeniden düzenlenmeden önceki bir temel analiz verisi yeniden alınamayabilir. Store, verinin söylendiği gün gerçekte ne söylediğine ilişkin bir kayıt oluşturmanızı sağlar.
Scheduler, yenilemenin ne zaman yapılacağına karar verir. Bir sunucuda bu görev systemd timer tarafından yürütülür. VPS'nin burada koddan daha önemli olmasının nedeni budur.
LLM katmanı, SQL tarafından üretilen kısa bir metin bloğunu okur ve bunun özetini yazar. Veritabanına hiçbir zaman bağlanmaz ve sorguyu hiçbir zaman oluşturmaz. SQL sorgusunu model yazarsa, tek bir hatalı token akıcı bir cümlenin içinde yanlış bir sayıya dönüşür ve bunu karşılaştırabileceğiniz hiçbir kaynak kalmaz. Sayıları SQL üretirse model yalnızca metni hatalı oluşturabilir. Siz de metni gönderdiğiniz satırlarla karşılaştırabilirsiniz.
Bir laptop yerine neden VPS üzerinde çalıştırılmalı
Zamanlayıcı bunun temel nedenidir. ABD piyasalarının New York saatiyle 16:00'da kapanması, Berlin'de 22:00'ye ve Jakarta'da ertesi sabah 04:00'e denk gelir. Laptop bu saatlerin ikisinde de uyku modunda olabilir. Kaçırılan bir çalıştırmanın maliyeti, gecikmiş bir nottan daha yüksektir: günlük bar verileri genellikle daha sonra yeniden alınabilir, ancak revize edilen veriler yeniden alınamaz. Bu nedenle kayıtlarınızdaki boşluk kalıcı olur.
İkinci neden daha küçük olsa da gerçektir. Sunucuda tek bir API key, tek bir dosyada, login shell'i olmayan tek bir system user tarafından sahiplenilir ve tek bir job tarafından kullanılır. Web'de gezinmek için de kullandığınız bir laptop'ta aynı düzeni kurmak çok daha zordur. Key'i kodun dışında ve modelin girdisinin dışında tutun. Bu konu API key'lerini bir AI agent'ın dışında tutma başlığında ele alınır.
Disk, RAM ve token kullanımı
Disk en kolay kısımdır. Günlük bir bar, her işlem günü ve her ticker için bir satırdır. ABD'de bir 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 ticker, bir yılın sonunda 5,040 satıra ulaşır. 500 tickers satırı, on yılın sonunda 1,260,000 satıra ulaşır. Her satırda bir tarih ve birkaç double değer bulunur. DuckDB sütunları sıkıştırarak depoladığı için bu boyut gigabaytlar değil, onlarca megabayt düzeyindedir. Benim tahminime bile güvenilmemelidir. İlk backfill işleminden sonra du -h /opt/research/data/market.duckdb komutunu çalıştırın ve kendi ölçümünüzü kullanın.
Küçük bir VPS'te sınırlayıcı unsur RAM'dir. DuckDB, varsayılan olarak tek bir sorgu için makinenin belleğinin ve tüm CPU çekirdeklerinin büyük bir bölümünü kullanır. Bu, bir analytics sunucusu için uygundur. Ancak aynı makinede başka servislerin de çalıştığı 2 GB RAM'li bir sunucu için uygun değildir. Bunun sonucunda prices tablosunun tamamı üzerinde yapılan tek bir aggregation işlemi, kernel tarafından sürecin sonlandırılmasına neden olur. systemd Main process exited, code=killed, status=9/KILL bildirirken, journalctl -k bellek yetersizliği nedeniyle gerçekleştirilen sonlandırmayı gösterir. memory_limit ve threads değerlerini açıkça ayarlayın. Böylece sorgu sonlandırılmak yerine daha yavaş çalışır.
Token kullanımı 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 iki değeri bir tabloya yazın. Bir hafta sonra gerçek hacim öğrenilir. Bu değer, kontrol günü modeliniz için listelenen fiyatla çarpılabilir. Planlama açısından iki konu yeterince sabittir. Tüm Claude modellerinde output token fiyatı input token fiyatından yüksektir. Bu nedenle notu 200 kelimeyle sınırlamak, gönderilen veriyi azaltmaktan daha fazla maliyet düşürür. Prompt caching, günde bir kez çalışan bir iş için fayda sağlamaz. Bunun nedeni cache ömrünün dakikalarla ölçülmesidir. Bir sonraki çalıştırmada cache'e alınan blok süresini doldurmuş olur ve input için tam fiyat ödenir. Caching, tek bir çalıştırma aynı büyük metin bloğu üzerinden çok sayıda çağrı yaptığında maliyet avantajı sağlar.
Kurulum bileşenlerini yükleme
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 işlem ok çıktısını verir. Sanal ortamı atlayıp pip install komutunu sistem Python'ı üzerinde çalıştırırsanız Ubuntu 24.04 işlemi error: externally-managed-environment ile durdurur. Bunun nedeni, dağıtımın /usr/lib/python3 sahibi olması ve pip'in buraya yazmasına izin vermemesidir. venv bir nezaket uygulaması değildir. pip'in yazmasına izin verilen tek dizin venv dizinidir.
API anahtarı, servis kullanıcısının okuyabildiği ve başka hiçbir kullanıcının erişemediği bir dosyada tutulmalıdır:
sudo install -d -m 755 /etc/research
sudo install -m 640 -o root -g research /dev/null /etc/research/env
sudoedit /etc/research/envDosyaya tırnak işareti ve export olmadan tek bir satır ekleyin. Bunun nedeni, systemd'nin bu dosyayı bir shell'e aktarmak yerine kendisinin ayrıştırmasıdır:
ANTHROPIC_API_KEY=sk-ant-your-key-hereMağaza: 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 primary key, yenileme işleminin güvenli biçimde tekrarlanabilmesini sağlar. INSERT OR REPLACE, aynı ticker ve gün için zaten mevcut olan satırın üzerine yazar. Bu nedenle backfill işleminin iki kez çalıştırılması tabloda yinelenen kayıtlar oluşturmaz. Bu anahtar olmadan, bir çökme sonrasında yapılan her yeniden çalıştırma tüm barları sessizce çoğaltır. Sonrasında hesaplanan her ortalama yanlış olur ve bunu bildirecek hiçbir hata mesajı oluşmaz.
DuckDB, dosyanın yazma amacıyla aynı anda açık tutulmasına yalnızca bir sürecin izin verir. İkinci yazıcı, dosyayı tutan PID ile birlikte hemen Could not set lock on file hatasıyla başarısız olur. Uygulamada bu süreç, başka bir terminalde açık bıraktığınız etkileşimli duckdb kabuğudur. Okuyucular read_only=True üzerinden çalışır. Bu nedenle connect flag'ini alır. Birden fazla sürecin gerçekten aynı anda yazması gerekiyorsa farklı bir veritabanı motoru kullanılmalıdır: WAL modundaki SQLite, bir yazıcı commit işlemini gerçekleştirirken okuyucuların çalışmasına izin verir. busy timeout ayarı da diğer yazıcıların başarısız olmak yerine beklemesini sağlar. Sunucu iş yükü açısından DuckDB ve SQLite karşılaştırması iki sistemi karşılaştırır. Bir VPS üzerinde SQLite'ı production ortamında çalıştırma ise WAL'ın düzgün çalışması için gereken ayarları açıklar.
Yenileme görevi
# /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()Bir kez elle çalıştırın:
sudo -u research /opt/research/venv/bin/python /opt/research/refresh.pyİlk çalıştırma yıllar boyunca eksik verileri tamamlar ve her hisse senedi sembolü için binlerle ifade edilen sayıda satır yazdırır. Bir dakika sonra tekrar çalıştırıldığında her satırda 1 veya 2 kayıt görünür; çünkü görev, veritabanında zaten bulunan son günden başlar. Asıl test ikinci çalıştırmadır: sayılar hâlâ binlerle ifade ediliyorsa max(day) hiçbir veri döndürmüyordur ve INSERT işlemi her gece tüm geçmiş verilerinizi yeniden yazıyordur.
df.empty kontrolü dosyadaki en önemli satırdır. Ticker.history() hatalı veya borsadan kaldırılmış bir sembol için hata üretmez. Bunun yerine sembolün borsadan kaldırılmış olabileceğini ve fiyat verisi bulunamadığını belirten bir uyarı yazdırır; uyarının tam metni library sürümleri arasında değişebilir ve boş bir DataFrame döndürür. Bu kontrolün bulunmadığı bir görev hiçbir veri yazmadan 0 durum koduyla çıkar. systemd ise tablo arka planda büyümeyi durdurmuşken çalıştırmayı sağlıklı olarak gösterir. Bu durum, her gün aynı satırları döndüren bir ekran üzerinden haftalar sonra fark edilir.
Zaman damgası işlemenin de bir nedeni vardır. Feed tarafından döndürülen index, borsa zaman dilimini taşıyabilir. Offset değerini kaldırmak, zamanı UTC'ye dönüştürmekle aynı değildir. Yerel saatle gece yarısı olarak zaman damgası eklenen bir Tokyo seansı UTC'ye dönüştürüldüğünde önceki takvim gününe kayar. Bu nedenle UTC dönüşümü her Japon barını sessizce bir gün geriye taşır ve primary key değerini bozar. tz_localize(None) borsanın kendi seans tarihini korur. Günlük barın anlamı da budur.
Piyasa kapanışında tetiklenen zamanlayıcı
ABD piyasası New York saatine göre 16:00'da kapanır. Son işlemlerin kesinleşmesi birkaç dakika sürdüğü için iş 16:20'de çalıştırılır. Bu zamanlamayı UTC olarak değil, New York saatine göre yazın. New York kışın UTC'den 5 saat, yazın ise 4 saat geridedir. Bu nedenle sabit bir UTC saatiyle yazılan zamanlayıcı yılda iki kez 1 saat kayar ve kapanıştan önce çalışmaya başlar. systemd 252 ve sonraki sürümler OnCalendar içinde doğrudan bir zaman dilimini kabul eder. Ubuntu 24.04 sürümü 255 ile birlikte gelir. Kullandığınız sürümü 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. Bunları sırayla çalıştırır ve herhangi biri 0 olmayan bir çıkış koduyla sonlanırsa durur. İstenen davranış tam olarak budur: başarısız bir yenilemenin ardından eski verilerle bir ekran oluşturulmamalıdır. Persistent=true, kernel güncellemeleri nedeniyle yeniden başlatılan bir VPS için önemlidir. Bu seçenek olmadan 16:15'te yeniden başlatma yapılırsa çalıştırma tamamen kaybolur. Bu seçenekle iş, makine yeniden kullanılabilir duruma gelir gelmez çalıştırılır.
sudo systemctl daemon-reload
sudo systemctl enable --now research-refresh.timer
systemctl list-timers research-refresh.timerlist-timers, bir sonraki hafta içi çalıştırma zamanını sunucunun kendi yerel saatine dönüştürülmüş biçimde içeren bir NEXT sütunu göstermelidir. Boş bir liste, zamanlayıcının etkin olmadığını veya unit dosyasında [Install] bölümünün bulunmadığını gösterir. Bu durumda enable, timers.target içine bağlanacak bir unit bulamaz.
Ekran: Önce SQL, en son model
SQL kesin sonuç verir ve her çalıştırmada maliyet oluşturmaz. Model ise böyle değildir. Bu nedenle sorgu olası kayıt kümesini daraltır ve yalnızca kalan kayıtlar 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 yalnızca görsel amaçlı değildir. ROWS BETWEEN 49 PRECEDING AND CURRENT ROW mevcut satırların tamamı üzerinden ortalama hesaplar. Bu nedenle bir ticker'ın üçüncü satırı üç günün ortalamasını döndürür ve bunu yine ma50 olarak adlandırır. Bu sonucu ma20 ile karşılaştırmak, her ticker geçmişinin başlangıcında gerçekte gerçekleşmemiş bir kesişme oluşmasına neden olur. Satır numarasına göre filtreleme, pencerenin tam olarak dolmadığı satırları çıkarır.
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()System prompt içindeki kelime sınırı, maliyetin yüksek olan yarısını sınırlar. Yalnızca verilen satırların kullanılmasına ilişkin talimat ise güvenmek yerine doğrulanmalıdır: bloktan bir sütunu silin, komutu yeniden çalıştırın ve çıktıyı okuyun. Bu sütuna ait bir değer hâlâ görünüyorsa model eksik kısmı kendisi doldurmuştur ve prompt yeterince kısıtlayıcı değildir. Bu test iki dakika sürer ve gerçeği öğrenmenin tek güvenilir yoludur.
Model API key değerini görmez, veritabanı yolunu görmez ve sorgu çalıştırmaz. Satırları alır ve düzyazı çıktı üretir. Bu sınır, çıktının denetlenebilmesini sağlar; çünkü nottaki her sayı gönderilen blokta da bulunmalıdır ve bunlar satır satır karşılaştırılabilir. Prompt oluşturma tarafı hakkında daha fazla bilgi için finans analizi için Claude kullanımı, modelin hangi bilgileri okumakta başarılı olduğunu daha ayrıntılı ele alır.
Uçtan uca tek çalıştırma
New York saatine göre 16:20'de timer servisi başlatır. refresh.py, son kaydedilen günden başlayarak her ticker için feed'i sorgular, her biri için bir veya iki yeni bar yazar ve her ticker için bir satır yazdırır. screen.py aynı dosyayı açar, hareketli ortalama sorgusunu çalıştırır ve birkaç satır sonuç alır. Bu satırlar birkaç yüz token içeren bir metin bloğuna dönüştürülür. Tek bir API çağrısı bunları kısa bir nota dönüştürür. Not journal'a yazılır. Token sayıları ve isabet sayısıyla birlikte bir satır runs içine eklenir.
journalctl -u research-refresh.service -n 50 --no-pagerSağlıklı bir log dosyasında her ticker için bir satır, ardından not ve sonra research-refresh.service: Deactivated successfully bulunur. Bir hafta sonra gerçek maliyeti veritabanından okuyun:
SELECT count(*) AS runs,
sum(input_tokens) AS in_tokens,
sum(output_tokens) AS out_tokens
FROM runs;Bu toplamları, modelinizin okuduğunuz gün listelediği milyon token başına fiyatlarla çarpın. Böylece başkasının tahmini yerine gerçek tutarı elde edersiniz. Yayınlanan fiyatlar değişir. Hesaplama değişmez.
Backtest'lerin neden aşırı uyum sağladığı ve bunun nasıl gözlemleneceği
Ekranı iki pencere uzunluğunun fonksiyonu olarak yeniden yazın, bir çiftler ızgarasını tarayın ve çiftleri getirilerine göre sıralayın. En iyi çift mükemmel görünecektir. Sorun da sonuç değil, budur. 200 çiftten oluşan bir ızgara, 200 deneme anlamına gelir ve en şanslı sonucu seçmiş olursunuz.
Bunun gerçekleştiğini on dakika içinde gözlemleyebilirsiniz. Store'u tarihe göre ikiye bölün. Izgarayı yalnızca ilk yarıda tarayın ve kazananı not edin. Aynı ızgarayı ikinci yarıda tarayın. İki kazanan birbirinden çok farklıysa parametreler gürültüye uyum sağlamıştır. Yalnızca üzerinde ayar yaptığınız yarıda kazanan bir çift, yarın hakkında hiçbir şey söylemez.
Hayatta kalanları seçme yanlılığı, aşırı uyum sağlamadan daha kötüdür; çünkü ayarlama bunu düzeltemez. Ticker listeniz güncel endeks üyelerinden oluşur ve yalnızca ayakta kalan şirketleri içerir. Feed'den 2019'da listeden çıkarılmış bir ticker istediğinizde boş bir frame döner. Bu, söz konusu şirketin store'a ve testinize hiç girmediği anlamına gelir. Çalıştırdığınız her backtest, başarısız olanları zaten dışarıda bırakmıştır.
Yeniden düzenlenmiş temel finansal veriler zaman çizelgesini bozar. API'nin bugün 2019'daki bir çeyrek için döndürdüğü gelir rakamı, 2019'da yayımlanan rakamla her zaman aynı değildir. Güncel temel finansal verileri 2019 fiyatlarıyla birleştiren bir ekran, o tarihte mevcut olmayan bilgileri kullanır. Fiyatlar bu açıdan genellikle güvenlidir. Temel finansal veriler ise genellikle güvenli değildir.
Düzeltilmiş fiyatlar altınızdan kayar. auto_adjust=True ile kapanış fiyatları temettüler ve bölünmeler için geriye dönük olarak düzeltilir. Bu nedenle gelecek ay aynı sorgu çalıştırıldığında biraz farklı bir geçmiş dönebilir. Gerçekte kullandığınız satırları saklamak, sonucu yeniden üretilebilir hale getirir. Yerel store'un var olmasının nedenlerinden biri de budur.
Backtest ayrıca komisyonları ve kaymayı dikkate almaz. Emrinizin fiyatı hareket ettirmediğini varsayar. Bunlar execution kapsamındadır ve burada ele alınmaz; ayrıntılar VPS üzerinde trading bot'larını çalıştırma bölümünde açıklanır.
Hata durumları ve görülecek mesajlar
pip çalışırken error: externally-managed-environment. Sanal ortamın dışındasınız. /opt/research/venv/bin/pip komutunu tam yolu ile çağırın.
Could not set lock on file, ardından bir PID gelir. Başka bir süreç DuckDB dosyasını yazmak üzere açık tutuyor. Genellikle kapatmayı unuttuğunuz etkileşimli bir kabuktur. Bu süreci kapatın veya ikinci bağlantıyı read_only=True ile açın.
Main process exited, code=killed, status=9/KILL, systemctl status içinde. Kernel işi bellek yetersizliği nedeniyle sonlandırdı. journalctl -k | grep -i oom ile doğrulayın, ardından store.py içindeki memory_limit değerini düşürün.
Başarılı görünen ancak hiçbir şey yazmayan çalışma. systemctl status, active (exited) değerini okur ve tablo büyümemiştir. Veri akışı boş veri çerçeveleri döndürmüştür. Bu hata en zor fark edilen durumdur. Bu nedenle tüm ticker değerleri boş döndüğünde işin sıfır olmayan bir çıkış koduyla sonlanması sağlanmalıdır.
Zamanlayıcının piyasa tatilinde çalışması. systemd borsa takvimini bilmez. Bu nedenle Mon-Fri tatil günlerini de içerir. İş çalışır, veri akışında yeni veri bulunmaz ve iş bunu hata olarak değil, normal durum olarak ele almalıdır.
API’den 429. Rate limit sınırını aştınız. Anthropic SDK kendi başına backoff ile yeniden deneme yapar ve Anthropic(max_retries=5) deneme sayısını artırır. Hata her gün devam ediyorsa iş tek bir burst içinde gereğinden fazla istek gönderiyor demektir.
Bunun kapsamı nedir?
Bu bir araştırma asistanıdır. Bir model, bir bildirimi özetlerken o bildirimin yorumunu üretir ve metinde yazılı bir sayı hakkında güvenle yanlış sonuca varabilir. Bu nedenle nottaki her rakam, gönderilen bir satıra kadar izlenebilir olmalıdır. Çıktıyı, kendiniz okumanız gereken içeriklerin kısa listesi olarak değerlendirin. Buradaki hiçbir şey finansal tavsiye değildir ve bunların hiçbiri bir sinyal değildir.
Backtest'ler fikirleri elemek için kullanışlıdır, ancak fikirleri doğrulama konusunda zayıftır. Kendi verilerinizde başarısız olan bir strateji gerçekten geçersizdir. Başarılı olan bir strateji ise yalnızca kendi verilerinizden geçmiştir. Bu, gece 1am'de olduğundan daha küçük bir iddiadır.
Execution özellikle kapsam dışında tutulur. Emirler ve broker kimlik bilgileri, yalnızca okuma amaçlı bir araştırma sunucusundan farklı bir risk profiline sahiptir. Bunları aynı makinede kullanmak, trading key'lerini bir LLM prompt'u ile aynı sisteme koyar. Bu yaklaşımın kendi sunucunuzda çalıştırmaya değer diğer seçeneklerin yanında nerede konumlandığını görmek istiyorsanız, çalıştırmaya değer self-hosted AI agent'ları daha geniş bir genel bakış sunar.
FAQ
Ücretli bir piyasa verisi akışına ihtiyacım var mı?
Prototip için gerekmez. Ücretsiz, resmi olmayan bir akış sistemin yapısını öğrenmek için yeterlidir. Ancak bu akış, size karşı herhangi bir yükümlülüğü olmayan bir web sitesine bağlı olduğu için bozulacaktır. Genellikle exception yerine boş frame döndürerek başarısız olur. Bu nedenle işiniz satır sayılarını denetlemelidir. Veri bir kararı etkilemeye başladığında, belgelenmiş bir API ve destek adresi sunan ücretli bir akışa geçilmelidir. Bu geçişi ucuz hale getiren store yapısıdır: yalnızca fetch işlevi değişir; schedule, schema ve ekran aynı kalır.
Fiyatları SQLite veya DuckDB içinde mi tutmalıyım?
DuckDB columnar yapıdadır ve aggregate hesaplamak için çok sayıda satırı taramak üzere tasarlanmıştır. Bu, on yıllık barlar üzerinde moving average hesaplamakla tam olarak örtüşür. SQLite row oriented yapıdadır ve aynı anda birden fazla process tarafından yapılan çok sayıda küçük okuma ve yazma işlemi için daha uygundur. Bir scheduled job birkaç yüz satır ekliyor ve ardından milyonlarca satırı tarıyorsa DuckDB daha uygun seçimdir. Aynı anda birden fazla process yazma işlemi yapacaksa WAL mode içindeki SQLite, tek bir writer commit işlemi yaparken reader süreçlerinin çalışmasına izin verir. busy timeout ise diğer writer süreçlerinin doğrudan başarısız olmak yerine beklemesini sağlar.
LLM çağrılarının aylık maliyeti ne kadar?
Her response içindeki usage.input_tokens ve usage.output_tokens değerlerini bir tabloya kaydedin. Ardından haftalık toplamları, kontrol ettiğiniz gün modelinizin belirttiği million token fiyatıyla çarpın. Gelecek çeyrekte de geçerliliğini koruyacak tek değer budur. Kısa bir screen üzerinde günde bir kez çalıştırılan job az sayıda çağrı üretir. Kontrol edebileceğiniz asıl unsur note uzunluğudur: summary uzunluğunu 200 kelimeyle sınırlamak, daha az satır göndermekten daha fazla tasarruf sağlar. Bunun nedeni, her Claude modelinde output token fiyatının input token fiyatından yüksek olmasıdır.
Job başarılı olduğu halde neden yeni satır yazmadı?
Bunun iki yaygın nedeni vardır. Borsa kapalı olabilir; çünkü bir systemd Mon-Fri schedule exchange tatillerini içerir. Alternatif olarak feed, her ticker için boş bir frame döndürmüş olabilir. Client library'ler bu durumu çoğu zaman exception yerine yazdırılmış bir warning olarak bildirir. Bu nedenle process 0 durum koduyla çıkar ve systemd çalışmayı başarılı gösterir. Ayrımı yapmak için SELECT max(day) FROM prices değerini son gerçek işlem günüyle karşılaştırın. Ayrıca tüm ticker değerleri boş geldiğinde job non zero durum koduyla çıkmalıdır.
Agent ne satın alınacağına karar verebilir mi?
Hayır. Bu yönde tasarlamak, bu projelerin yanlış sonuçlanmasına neden olan temel noktadır. Modelin piyasaya erişimi, pozisyonunuz veya vergi durumunuz hakkında bilgisi ve kendi hesaplamalarını herhangi bir kaynakla karşılaştırma imkanı yoktur. Güçlü olduğu alan, büyük miktarda metni okuyarak o gün dikkatinizi hak eden birkaç öğeyi belirlemektir. Yazdığı hiçbir şey finansal tavsiye değildir. Karar ve bunun sorumluluğu size aittir.