veto
# veto
A checklist for a long call. It answers `pass`, `fail`, or `watchlist`, and
it does not recommend a trade.
The rules came out of a week of checking the same names twice and finding
that the second check disagreed with the first. The useful part was not the
names. It was the refusals: an entry mode that was "near" its number, an
analyst target copied from a stale snippet, a spread whose short leg broke a
rule the long leg had already satisfied. Those are tests now. The names are
examples of a reading, not ideas.
Nothing to install beyond Python 3.9.
## Try it
```sh
pip install veto-check
veto check examples/se.txt
python3 -m veto check examples/se.txt
python3 -m veto check examples/clear.txt
python3 -m veto check examples/spread.txt
```
A reading is one `name: value` per line. A missing value fails the rule that
needs it. There is no "close enough": RSI of 30.01 does not pass a rule
written as 30.
`examples/se.txt` fails both entry modes, so the result is `watchlist`. Not a
smaller trade. `examples/bmy.txt` fails the analyst check first, at 8.3%
against a rule that wants 20%. `examples/spread.txt` passes an entry mode and
then fails because the short leg is too short, too far from the money, and
expires after earnings.
## The rules
Applied in this order. The first one is first because it removes a name
before the rest of the numbers are worth collecting.
| Rule | Passes when |
|---|---|
| analyst upside | target is at least 20% above the price |
| mode A | RSI is 30 or below, and the MACD histogram is positive |
| mode B | RSI is from 45 to 65, and a catalyst falls inside 30 days |
| iv rank | below 30 |
| FOMC | the decision is more than 48 hours away, or already past |
| gap | the open is not more than 5% |
| earnings | a hold-or-exit decision is already written down |
| diagonal | only when the reading is a spread: each leg 60 to 120 days, at the money or one strike out, and the short leg expires before earnings |
One of the two modes has to pass. The other rules all have to pass. Both
modes failing is `watchlist`. A mode passing while another rule fails is
`fail`.
## As an MCP server
```sh
python3 -m veto serve
```
This speaks [MCP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports)
over stdin and stdout: one JSON-RPC message per line, logs on stderr, nothing
else on stdout. It implements `initialize`, `tools/list`, `tools/call`, and
`ping`. The one tool is `check`, and its arguments are the same fields as the
text file.
A host that already runs other servers adds this beside them. The decision
does not change because it arrived over the protocol: `serve` calls the same
function `check` does. A client that would rather not speak MCP can shell out
to `python3 -m veto check` and read the text.
```json
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"example","version":"0"}}}
```
## What it will not do
It does not fetch a price, an analyst target, or an option chain. Those
numbers go stale, and a tool that fetches them starts pretending the fetch
cannot be wrong. You bring the figures. It also does not size a position,
pick a strike, or tell you to buy anything. The output is whether the reading
cleared rules you can read in `veto/rules.py`.
## Tests
```sh
python3 -m unittest discover -s tests
```
TDQS
Scored across 1 tool
There is only one tool, so there is no possibility of selecting among overlapping or confusing alternatives. Its purpose—returning pass, fail, or watchlist for a long-call reading against fixed rules—is clearly stated.
With a single tool, there is no cross-tool naming inconsistency. The name 'check' is concise and verb-based, though generic and not especially descriptive of the veto/rule-checking domain.
The server appears narrowly scoped to one validation decision, so one tool can be sufficient. However, a single-tool surface is thin for a standalone server and leaves little room for related operations such as rule inspection or explanation.
The core decision operation is covered, including pass/fail/watchlist outcomes and a clear statement that missing numbers fail applicable rules. Minor gaps remain around diagnostics, such as explaining which rule failed or exposing the fixed rule set.