complete paper trading: auto-close on stop/target + My Trades realized record
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user