MSTock
A scanner that checks about 75 stocks, futures, and crypto markets for technical setups. It logs every signal it finds, follows each one forward to see how it turned out, and can place the trades in an Alpaca paper account. It only trades on paper.
- Status
- Paper trading only
- Stack
- Python, pandas and numpy, Streamlit, Alpaca paper trading, pytest
- Tests
- 168 pytest tests, no network needed, run in CI on every push
Why I built it
I wanted to know whether the setups I was taking by hand actually had an edge, and looking at charts after the fact is not a way to find out.
So the design puts measurement ahead of automation. Every signal is logged when it is made and then checked against the real bars that came after it. The hit rate and average R come from trades the system actually called, not from a curve fit after the fact. Position sizing only reacts to that record once there is enough of it to mean anything.
How it works
No database. Everything is written to append-only CSV files. The indicators are written directly in pandas and numpy, because pandas-ta would not install on Python 3.14.
- Watchlist
- config/watchlist.py holds every instrument as one record. No other file keeps its own list of tickers.
- Data
- One DataSource interface, with versions for stocks and futures (yfinance), crypto (Binance), and intraday bars (Alpaca). All of them return price data in the same shape.
- Filters
- Stocks within 3 trading days of earnings are dropped before any strategy runs. If the earnings calendar is missing, the stock goes through with a warning instead of being dropped without a word.
- Strategies
- Trend pullback, Supertrend, and Donchian breakout. Each strategy sees only fully closed bars and returns a signal or nothing.
- Logging
- Every signal is written to a CSV with an entry, a stop, a target, and a position size. Running the scan twice in one day does not log the same signal twice.
- Resolution
- A resolver walks each signal forward bar by bar. Entry is the open of the next bar, never the signal bar. If one bar touches both the stop and the target, it checks 1 minute or 5 minute bars to see which came first, and counts a loss if it still cannot tell.
- Sizing
- Risk per trade comes from quarter Kelly on each asset class's own results, held between 0.25% and 1.5%. Below 30 closed trades it uses a fixed default.
- Execution
- Stock signals go to Alpaca as bracket orders. Alpaca has no bracket orders for crypto, so the code manages the stop and target itself. Each order uses the signal's ID, so matching orders back to signals is safe to run again.
- Dashboard
- A Streamlit app shows scan results, open positions, backtests, and a kill switch for each trading loop.
The scanner, the backtester, and the tests all run with no credentials. Only paper trading needs Alpaca keys.
Known limitations
These are real gaps in the code today.
The backtest has no slippage or commissions
Live fills on large stocks usually come in 0.05 to 0.15R worse per trade, so backtest results look better than they should by about that much.
A tie goes to the stop
A price bar cannot say whether the stop or the target came first. When finer data is missing, the loss is counted. That pushes measured results down, which is the safe direction, but it is not the truth.
Kelly sizing is not active yet
It needs 30 closed trades per asset class, and the log does not have that many yet. Until then every trade uses the default risk, so that part is written and tested but not proven.
Intraday volume is a sample
Alpaca's IEX feed is about 2 to 4% of all trading, so any filter built on volume is an estimate.
Intraday backtests are short
Alpaca only has so much intraday history, so windows run about 30 days. One news-driven week can swing the results.
Live trading takes a code change, on purpose
paper=True is hardcoded in the broker client, with no setting to flip it. A config mistake cannot send real money.