complete paper trading: auto-close on stop/target + My Trades realized record
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 38s
Deploy / deploy (push) Successful in 25s

resolve_open_trades walks the daily bars after each open trade and closes it at
the target (target hit) or stop (stop/ambiguous), leaving undecided trades open.
Runs nightly inside the outcome evaluator (so it's coordinated with fresh OHLCV)
and on its manual trigger. New "My Trades" section at the top of Signals → Track
Record shows realized hit-rate, expectancy (avg R), total R, total P&L, and a
closed-trades table — your actual results, separate from the theoretical signal
record below it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 08:49:28 +02:00
parent 7e87a15a12
commit fb3b8d18d7
6 changed files with 220 additions and 3 deletions
+44 -1
View File
@@ -2,7 +2,7 @@
from __future__ import annotations
from datetime import date, datetime, timezone
from datetime import date, datetime, timedelta, timezone
import pytest
@@ -81,3 +81,46 @@ async def test_double_close_rejected(session):
await svc.close_trade(session, 1, trade.id)
with pytest.raises(ValidationError):
await svc.close_trade(session, 1, trade.id)
async def _add_bars(session, ticker_id: int, highs_lows: list[tuple[float, float]], start: date) -> None:
for i, (hi, lo) in enumerate(highs_lows):
mid = (hi + lo) / 2
session.add(OHLCVRecord(ticker_id=ticker_id, date=start + timedelta(days=i + 1),
open=mid, high=hi, low=lo, close=mid, volume=1))
await session.commit()
async def test_resolve_closes_on_target(session):
tid = await _seed(session, "AAA", close=100.0)
trade = await svc.create_trade(session, 1, symbol="AAA", direction="long",
entry_price=100.0, shares=10, stop_loss=95.0, target=110.0)
# later bars: a day that trades up through 110
await _add_bars(session, tid, [(103, 101), (111, 108)], start=date.today())
closed = await svc.resolve_open_trades(session)
assert closed == 1
await session.refresh(trade)
assert trade.status == "closed"
assert trade.close_price == 110.0 # closed at target
async def test_resolve_closes_on_stop(session):
tid = await _seed(session, "AAA", close=100.0)
trade = await svc.create_trade(session, 1, symbol="AAA", direction="long",
entry_price=100.0, shares=10, stop_loss=95.0, target=110.0)
await _add_bars(session, tid, [(101, 94)], start=date.today()) # low pierces stop
closed = await svc.resolve_open_trades(session)
assert closed == 1
await session.refresh(trade)
assert trade.close_price == 95.0 # closed at stop
async def test_resolve_leaves_open_when_neither_hit(session):
tid = await _seed(session, "AAA", close=100.0)
await svc.create_trade(session, 1, symbol="AAA", direction="long",
entry_price=100.0, shares=10, stop_loss=95.0, target=110.0)
await _add_bars(session, tid, [(103, 98), (104, 99)], start=date.today()) # range-bound
closed = await svc.resolve_open_trades(session)
assert closed == 0
rows = await svc.list_trades(session, 1, status="open")
assert len(rows) == 1