Files
crypto_trading/backend/tests/test_portfolio.py
T
matitos 9c3bee87ac
CI/CD / backend (push) Successful in 5m18s
CI/CD / frontend (push) Successful in 5m2s
CI/CD / build (push) Successful in 34s
CI/CD / cleanup (push) Successful in 6s
CI/CD / deploy (push) Successful in 22s
Crypto trading bot: engine, brokers, backtester, dashboard, deploy
FastAPI backend with a single-strategy async engine, paper and Binance
brokers, pure-python indicators, a backtester that shares the engine's
portfolio math, and a Vue dashboard with a live WebSocket feed. Adds the
Gitea Actions CI/CD workflow that deploys to Traefik via docker_default.
2026-09-18 17:02:33 +00:00

60 lines
2.4 KiB
Python

from __future__ import annotations
import pytest
from app.engine.portfolio import (
account_equity,
apply_close,
apply_open,
order_qty,
realized_pnl,
risk_exit,
unrealized_pnl,
)
from app.models import PositionSide
def test_apply_open_long_and_short() -> None:
assert apply_open(1000.0, PositionSide.LONG, 1.0, 100.0, 1.0) == pytest.approx(899.0)
assert apply_open(1000.0, PositionSide.SHORT, 1.0, 100.0, 1.0) == pytest.approx(1099.0)
def test_apply_close_long_and_short() -> None:
assert apply_close(100.0, PositionSide.LONG, 1.0, 120.0, 1.0) == pytest.approx(219.0)
assert apply_close(2100.0, PositionSide.SHORT, 1.0, 90.0, 1.0) == pytest.approx(2009.0)
def test_realized_pnl_includes_fees() -> None:
assert realized_pnl(PositionSide.LONG, 100, 110, 2, 1, 1) == pytest.approx(18.0)
assert realized_pnl(PositionSide.SHORT, 100, 90, 2, 1, 1) == pytest.approx(18.0)
assert realized_pnl(PositionSide.LONG, 100, 90, 2, 1, 1) == pytest.approx(-22.0)
def test_unrealized_pnl() -> None:
assert unrealized_pnl(PositionSide.LONG, 100, 110, 3) == pytest.approx(30.0)
assert unrealized_pnl(PositionSide.SHORT, 100, 110, 3) == pytest.approx(-30.0)
def test_account_equity_long_and_short() -> None:
assert account_equity(100, PositionSide.LONG, 2, 150) == pytest.approx(400.0)
assert account_equity(300, PositionSide.SHORT, 2, 80) == pytest.approx(140.0)
def test_order_qty_caps_longs_by_cash_and_scales_with_size() -> None:
assert order_qty(10_000, 1.0, PositionSide.LONG, 10_000, 100) == pytest.approx(99.0)
assert order_qty(10_000, 0.5, PositionSide.SHORT, 10_000, 100) == pytest.approx(50.0)
assert order_qty(10_000, 1.0, PositionSide.SHORT, 0, 100) == pytest.approx(100.0)
assert order_qty(10_000, 1.0, PositionSide.LONG, 10_000, 0) == 0.0
def test_risk_exit_long() -> None:
assert risk_exit(PositionSide.LONG, 95, 120, high=101, low=94) == ("stop loss", 95)
assert risk_exit(PositionSide.LONG, 95, 120, high=121, low=100) == ("take profit", 120)
assert risk_exit(PositionSide.LONG, 95, 120, high=110, low=100) is None
def test_risk_exit_short() -> None:
assert risk_exit(PositionSide.SHORT, 110, 90, high=111, low=100) == ("stop loss", 110)
assert risk_exit(PositionSide.SHORT, 110, 90, high=100, low=89) == ("take profit", 90)
assert risk_exit(PositionSide.SHORT, None, None, high=100, low=95) is None