From Pine Script to a Python Trading Platform

TickersWikipediaPricesbatched yfinanceScannerconfig-drivenvectorbtSL/TP backtestsBroker APIpaper bracket orders

This one started on TradingView. I’d been writing Pine Script indicators for years, custom divergence detection and trend filters, and they worked well enough that I kept bumping into the limits of what a charting platform lets you do. You can draw signals on a chart all day, but you can’t scan 500 tickers with them, and you can’t honestly test whether your idea would have made money. So I rebuilt the whole thing in Python. The strategy logic itself stays private; the plumbing is the useful part to share anyway.

Data first

The library I’d been using for ticker lists went unmaintained, so the S&P 500 list now comes straight from Wikipedia and prices download in batches through yfinance:

import pandas as pd
import yfinance as yf

tickers = pd.read_html(
    "https://en.wikipedia.org/wiki/List_of_S%26P_500_companies"
)[0]["Symbol"].str.replace(".", "-", regex=False).tolist()

# batched download, one request per chunk instead of 500
for i in range(0, len(tickers), 50):
    chunk = tickers[i:i+50]
    data = yf.download(chunk, period="2y", group_by="ticker",
                       auto_adjust=True, threads=True)

Two real-world notes there: Wikipedia uses dots in tickers like BRK.B where Yahoo wants dashes, and you want auto_adjust=True so splits and dividends don’t manufacture fake signals in your history.

Config-driven scanning

The scanner runs the strategy across the whole index and flags setups. Entry conditions like trend filters are config driven, so ideas turn on and off without touching code:

scan:
  universe: sp500
  min_price: 5.00
  min_avg_volume: 500000
filters:
  trend_filter: true
  regime: "any"        # or bull / bear presets
risk:
  stop_loss_pct: 0.05
  take_profit_pct: 0.10

Backtesting without lying to yourself

Backtests run through vectorbt with stop-loss and take-profit modeled, and every result gets benchmarked against just buying and holding the same ticker over the same window:

import vectorbt as vbt

pf = vbt.Portfolio.from_signals(
    close, entries, exits,
    sl_stop=0.05, tp_stop=0.10,
    fees=0.0005, slippage=0.0005,
    init_cash=10_000,
)
bench = vbt.Portfolio.from_holding(close, init_cash=10_000)
print(pf.total_return(), "vs buy-and-hold", bench.total_return())

Note the fees and slippage arguments. A backtest with free, instant fills is fiction. And the benchmark line matters more than people think: a strategy can look great in isolation and still lose to doing nothing. If you don’t test against buy-and-hold, you’re grading your own homework.

Paper trading bridge

The newest piece is a bridge to a brokerage API for paper trading. It places bracket orders, entry plus stop plus target as one unit, with position sizes computed from risk instead of gut feel:

risk_per_trade = account_value * 0.01          # risk 1% per position
shares = int(risk_per_trade / (entry - stop))  # size from stop distance

It has a dry-run mode that logs what it would have done without sending anything, and that mode ran for weeks before the paper account saw its first order. I also ported the core strategy to NinjaScript along the way, which was a good exercise in finding out which parts of the logic were actually portable and which were quietly depending on platform behavior.

To be clear, this is an engineering project, not financial advice. For me it’s the same instinct as putting a scanner on a car instead of guessing from the sound. Data beats hunches, and if the tool to get that data doesn’t exist or costs too much, I’d rather build it.