feat: update environment configuration and improve repository handling
CI / lint-test-build (push) Failing after 11s
CI / lint-test-build (push) Failing after 11s
- Added PG_PASSWORD to .env.example for database connection. - Removed unnecessary imports and streamlined code in various modules. - Enhanced error handling in ConfigSettingRepository and ConfigPairingRepository. - Updated test files to remove unused imports and improve clarity.
This commit is contained in:
@@ -1,6 +1,11 @@
|
|||||||
APP_ENV=dev
|
APP_ENV=dev
|
||||||
APP_HOST=0.0.0.0
|
APP_HOST=0.0.0.0
|
||||||
APP_PORT=8000
|
APP_PORT=8000
|
||||||
|
PG_HOST=192.168.88.35
|
||||||
|
PG_PORT=5432
|
||||||
|
PG_DATABASE=arbitrade
|
||||||
|
PG_USER=arbitrade
|
||||||
|
PG_PASSWORD=arbitrade
|
||||||
LOG_LEVEL=INFO
|
LOG_LEVEL=INFO
|
||||||
LOG_JSON=true
|
LOG_JSON=true
|
||||||
ALERTS_ENABLED=true
|
ALERTS_ENABLED=true
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ Current stack:
|
|||||||
- Native Kraken WebSocket planned for market-data hot path
|
- Native Kraken WebSocket planned for market-data hot path
|
||||||
- Gitea Actions + Gitea container registry
|
- Gitea Actions + Gitea container registry
|
||||||
|
|
||||||
Project plan lives in [PLAN.md](PLAN.md).
|
|
||||||
Task checklist lives in [.github/instructions/TODO.md](.github/instructions/TODO.md).
|
Task checklist lives in [.github/instructions/TODO.md](.github/instructions/TODO.md).
|
||||||
Coolify deployment runbooks live in [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md).
|
Coolify deployment runbooks live in [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md).
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,15 @@ from arbitrade.market_data.feed_builder import (
|
|||||||
)
|
)
|
||||||
from arbitrade.metrics import MetricsCalculator
|
from arbitrade.metrics import MetricsCalculator
|
||||||
from arbitrade.runtime.lifecycle import graceful_shutdown, restore_runtime_state
|
from arbitrade.runtime.lifecycle import graceful_shutdown, restore_runtime_state
|
||||||
from arbitrade.storage.pg_store import PgStore
|
|
||||||
from arbitrade.storage.market_snapshots import AsyncMarketSnapshotWriter
|
from arbitrade.storage.market_snapshots import AsyncMarketSnapshotWriter
|
||||||
from arbitrade.storage.opportunities import AsyncOpportunityWriter
|
from arbitrade.storage.opportunities import AsyncOpportunityWriter
|
||||||
from arbitrade.storage.repositories import AuditRepository, RuntimeStateRepository, MarketSnapshotRepository, OpportunityRepository
|
from arbitrade.storage.pg_store import PgStore
|
||||||
|
from arbitrade.storage.repositories import (
|
||||||
|
AuditRepository,
|
||||||
|
MarketSnapshotRepository,
|
||||||
|
OpportunityRepository,
|
||||||
|
RuntimeStateRepository,
|
||||||
|
)
|
||||||
|
|
||||||
_LOG = structlog.get_logger(__name__)
|
_LOG = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -47,13 +47,15 @@ class TriangularExecutionSequencer:
|
|||||||
rest_client: SupportsOrderPlacement,
|
rest_client: SupportsOrderPlacement,
|
||||||
*,
|
*,
|
||||||
available_pairs: Sequence[str],
|
available_pairs: Sequence[str],
|
||||||
volume_for_leg: Callable[[OpportunityEvent, ExecutionLeg, int], float] | None = None,
|
volume_for_leg: Callable[[OpportunityEvent,
|
||||||
|
ExecutionLeg, int], float] | None = None,
|
||||||
execution_writer: AsyncExecutionWriter | None = None,
|
execution_writer: AsyncExecutionWriter | None = None,
|
||||||
alert_notifier: SupportsAlerts | None = None,
|
alert_notifier: SupportsAlerts | None = None,
|
||||||
audit_repository: AuditRepository | None = None,
|
audit_repository: AuditRepository | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._rest_client = rest_client
|
self._rest_client = rest_client
|
||||||
self._available_pairs = {self._normalize_pair(pair) for pair in available_pairs}
|
self._available_pairs = {self._normalize_pair(
|
||||||
|
pair) for pair in available_pairs}
|
||||||
self._volume_for_leg = volume_for_leg or self._default_volume_for_leg
|
self._volume_for_leg = volume_for_leg or self._default_volume_for_leg
|
||||||
self._execution_writer = execution_writer
|
self._execution_writer = execution_writer
|
||||||
self._alert_notifier = alert_notifier
|
self._alert_notifier = alert_notifier
|
||||||
@@ -100,12 +102,15 @@ class TriangularExecutionSequencer:
|
|||||||
raise ValueError(f"No tradable pair for leg {from_cur}->{to_cur}")
|
raise ValueError(f"No tradable pair for leg {from_cur}->{to_cur}")
|
||||||
|
|
||||||
def _build_legs(self, event: OpportunityEvent) -> tuple[ExecutionLeg, ...]:
|
def _build_legs(self, event: OpportunityEvent) -> tuple[ExecutionLeg, ...]:
|
||||||
currencies = [part.strip().upper() for part in event.cycle.split("->") if part.strip()]
|
currencies = [part.strip().upper()
|
||||||
|
for part in event.cycle.split("->") if part.strip()]
|
||||||
if len(currencies) < 4 or currencies[0] != currencies[-1]:
|
if len(currencies) < 4 or currencies[0] != currencies[-1]:
|
||||||
raise ValueError("cycle must be a closed triangular path like A->B->C->A")
|
raise ValueError(
|
||||||
|
"cycle must be a closed triangular path like A->B->C->A")
|
||||||
|
|
||||||
if len(currencies) != 4:
|
if len(currencies) != 4:
|
||||||
raise ValueError("cycle must contain exactly three unique currencies")
|
raise ValueError(
|
||||||
|
"cycle must contain exactly three unique currencies")
|
||||||
|
|
||||||
legs: list[ExecutionLeg] = []
|
legs: list[ExecutionLeg] = []
|
||||||
for idx in range(3):
|
for idx in range(3):
|
||||||
@@ -120,7 +125,8 @@ class TriangularExecutionSequencer:
|
|||||||
)
|
)
|
||||||
volume = self._volume_for_leg(event, placeholder_leg, idx)
|
volume = self._volume_for_leg(event, placeholder_leg, idx)
|
||||||
if volume <= 0.0:
|
if volume <= 0.0:
|
||||||
raise ValueError("volume_for_leg must return a positive volume")
|
raise ValueError(
|
||||||
|
"volume_for_leg must return a positive volume")
|
||||||
legs.append(self._resolve_leg(from_currency, to_currency, volume))
|
legs.append(self._resolve_leg(from_currency, to_currency, volume))
|
||||||
|
|
||||||
return tuple(legs)
|
return tuple(legs)
|
||||||
@@ -158,7 +164,7 @@ class TriangularExecutionSequencer:
|
|||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
if self._audit_repository is not None:
|
if self._audit_repository is not None:
|
||||||
self._audit_repository.insert(
|
await self._audit_repository.insert(
|
||||||
AuditRecord(
|
AuditRecord(
|
||||||
occurred_at=datetime.now(UTC),
|
occurred_at=datetime.now(UTC),
|
||||||
actor="execution_engine",
|
actor="execution_engine",
|
||||||
@@ -209,7 +215,8 @@ class TriangularExecutionSequencer:
|
|||||||
responses.append(response)
|
responses.append(response)
|
||||||
|
|
||||||
if self._execution_writer is not None:
|
if self._execution_writer is not None:
|
||||||
order_ref = self._order_ref_from_response(response, f"leg-{idx}")
|
order_ref = self._order_ref_from_response(
|
||||||
|
response, f"leg-{idx}")
|
||||||
await self._execution_writer.enqueue(
|
await self._execution_writer.enqueue(
|
||||||
OrderRecord(
|
OrderRecord(
|
||||||
trade_ref=trade_ref,
|
trade_ref=trade_ref,
|
||||||
@@ -265,7 +272,7 @@ class TriangularExecutionSequencer:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if self._audit_repository is not None:
|
if self._audit_repository is not None:
|
||||||
self._audit_repository.insert(
|
await self._audit_repository.insert(
|
||||||
AuditRecord(
|
AuditRecord(
|
||||||
occurred_at=datetime.now(UTC),
|
occurred_at=datetime.now(UTC),
|
||||||
actor="execution_engine",
|
actor="execution_engine",
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ class MarketDataFeed:
|
|||||||
opportunity_writer: AsyncOpportunityWriter | None = None,
|
opportunity_writer: AsyncOpportunityWriter | None = None,
|
||||||
paper_trading_mode: bool = True,
|
paper_trading_mode: bool = True,
|
||||||
opportunity_executor: (
|
opportunity_executor: (
|
||||||
Callable[[OpportunityEvent], Awaitable[ExecutionOutcome | float | None]] | None
|
Callable[[OpportunityEvent],
|
||||||
|
Awaitable[ExecutionOutcome | float | None]] | None
|
||||||
) = None,
|
) = None,
|
||||||
trade_capital: float = 1.0,
|
trade_capital: float = 1.0,
|
||||||
max_trade_capital: float | None = None,
|
max_trade_capital: float | None = None,
|
||||||
@@ -92,7 +93,8 @@ class MarketDataFeed:
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
start = currencies[0]
|
start = currencies[0]
|
||||||
exposure_assets = {currency for currency in currencies[1:] if currency != start}
|
exposure_assets = {
|
||||||
|
currency for currency in currencies[1:] if currency != start}
|
||||||
return {asset: event.allocated_capital for asset in exposure_assets}
|
return {asset: event.allocated_capital for asset in exposure_assets}
|
||||||
|
|
||||||
async def run(self) -> None:
|
async def run(self) -> None:
|
||||||
@@ -144,7 +146,7 @@ class MarketDataFeed:
|
|||||||
symbol=delta.symbol,
|
symbol=delta.symbol,
|
||||||
)
|
)
|
||||||
if self._audit_repository is not None:
|
if self._audit_repository is not None:
|
||||||
self._audit_repository.insert(
|
await self._audit_repository.insert(
|
||||||
AuditRecord(
|
AuditRecord(
|
||||||
occurred_at=datetime.now(UTC),
|
occurred_at=datetime.now(UTC),
|
||||||
actor="risk_manager",
|
actor="risk_manager",
|
||||||
@@ -172,7 +174,7 @@ class MarketDataFeed:
|
|||||||
|
|
||||||
for event in opportunities:
|
for event in opportunities:
|
||||||
if self._audit_repository is not None:
|
if self._audit_repository is not None:
|
||||||
self._audit_repository.insert(
|
await self._audit_repository.insert(
|
||||||
AuditRecord(
|
AuditRecord(
|
||||||
occurred_at=datetime.now(UTC),
|
occurred_at=datetime.now(UTC),
|
||||||
actor="detector",
|
actor="detector",
|
||||||
@@ -207,7 +209,7 @@ class MarketDataFeed:
|
|||||||
net_pct=event.net_pct,
|
net_pct=event.net_pct,
|
||||||
)
|
)
|
||||||
if self._audit_repository is not None:
|
if self._audit_repository is not None:
|
||||||
self._audit_repository.insert(
|
await self._audit_repository.insert(
|
||||||
AuditRecord(
|
AuditRecord(
|
||||||
occurred_at=datetime.now(UTC),
|
occurred_at=datetime.now(UTC),
|
||||||
actor="execution_engine",
|
actor="execution_engine",
|
||||||
@@ -228,7 +230,7 @@ class MarketDataFeed:
|
|||||||
updated_pair=event.updated_pair,
|
updated_pair=event.updated_pair,
|
||||||
)
|
)
|
||||||
if self._audit_repository is not None:
|
if self._audit_repository is not None:
|
||||||
self._audit_repository.insert(
|
await self._audit_repository.insert(
|
||||||
AuditRecord(
|
AuditRecord(
|
||||||
occurred_at=datetime.now(UTC),
|
occurred_at=datetime.now(UTC),
|
||||||
actor="execution_engine",
|
actor="execution_engine",
|
||||||
@@ -250,7 +252,7 @@ class MarketDataFeed:
|
|||||||
reason=self._kill_switch.reason,
|
reason=self._kill_switch.reason,
|
||||||
)
|
)
|
||||||
if self._audit_repository is not None:
|
if self._audit_repository is not None:
|
||||||
self._audit_repository.insert(
|
await self._audit_repository.insert(
|
||||||
AuditRecord(
|
AuditRecord(
|
||||||
occurred_at=datetime.now(UTC),
|
occurred_at=datetime.now(UTC),
|
||||||
actor="risk_manager",
|
actor="risk_manager",
|
||||||
@@ -275,7 +277,7 @@ class MarketDataFeed:
|
|||||||
reason=self._stop_conditions_guard.halted_reason,
|
reason=self._stop_conditions_guard.halted_reason,
|
||||||
)
|
)
|
||||||
if self._audit_repository is not None:
|
if self._audit_repository is not None:
|
||||||
self._audit_repository.insert(
|
await self._audit_repository.insert(
|
||||||
AuditRecord(
|
AuditRecord(
|
||||||
occurred_at=datetime.now(UTC),
|
occurred_at=datetime.now(UTC),
|
||||||
actor="risk_manager",
|
actor="risk_manager",
|
||||||
@@ -298,7 +300,7 @@ class MarketDataFeed:
|
|||||||
reason=self._loss_limit_guard.halted_reason,
|
reason=self._loss_limit_guard.halted_reason,
|
||||||
)
|
)
|
||||||
if self._audit_repository is not None:
|
if self._audit_repository is not None:
|
||||||
self._audit_repository.insert(
|
await self._audit_repository.insert(
|
||||||
AuditRecord(
|
AuditRecord(
|
||||||
occurred_at=datetime.now(UTC),
|
occurred_at=datetime.now(UTC),
|
||||||
actor="risk_manager",
|
actor="risk_manager",
|
||||||
@@ -313,7 +315,8 @@ class MarketDataFeed:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if self._pre_trade_validator is not None and self._balance_provider is not None:
|
if self._pre_trade_validator is not None and self._balance_provider is not None:
|
||||||
required_balances = {self._quote_balance_asset: event.allocated_capital}
|
required_balances = {
|
||||||
|
self._quote_balance_asset: event.allocated_capital}
|
||||||
balances = {
|
balances = {
|
||||||
asset.upper(): amount
|
asset.upper(): amount
|
||||||
for asset, amount in self._balance_provider().items()
|
for asset, amount in self._balance_provider().items()
|
||||||
@@ -329,7 +332,7 @@ class MarketDataFeed:
|
|||||||
required_by_asset=required_balances,
|
required_by_asset=required_balances,
|
||||||
)
|
)
|
||||||
if self._audit_repository is not None:
|
if self._audit_repository is not None:
|
||||||
self._audit_repository.insert(
|
await self._audit_repository.insert(
|
||||||
AuditRecord(
|
AuditRecord(
|
||||||
occurred_at=datetime.now(UTC),
|
occurred_at=datetime.now(UTC),
|
||||||
actor="risk_manager",
|
actor="risk_manager",
|
||||||
@@ -358,7 +361,7 @@ class MarketDataFeed:
|
|||||||
exposure_by_asset=exposure_by_asset,
|
exposure_by_asset=exposure_by_asset,
|
||||||
)
|
)
|
||||||
if self._audit_repository is not None:
|
if self._audit_repository is not None:
|
||||||
self._audit_repository.insert(
|
await self._audit_repository.insert(
|
||||||
AuditRecord(
|
AuditRecord(
|
||||||
occurred_at=datetime.now(UTC),
|
occurred_at=datetime.now(UTC),
|
||||||
actor="risk_manager",
|
actor="risk_manager",
|
||||||
@@ -381,7 +384,8 @@ class MarketDataFeed:
|
|||||||
outcome = await self._opportunity_executor(event)
|
outcome = await self._opportunity_executor(event)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
if self._trade_limits_guard is not None:
|
if self._trade_limits_guard is not None:
|
||||||
self._trade_limits_guard.close_trade(exposure_by_asset)
|
self._trade_limits_guard.close_trade(
|
||||||
|
exposure_by_asset)
|
||||||
|
|
||||||
dispatch_alert_nowait(
|
dispatch_alert_nowait(
|
||||||
self._alert_notifier,
|
self._alert_notifier,
|
||||||
@@ -420,7 +424,7 @@ class MarketDataFeed:
|
|||||||
updated_pair=event.updated_pair,
|
updated_pair=event.updated_pair,
|
||||||
)
|
)
|
||||||
if self._audit_repository is not None:
|
if self._audit_repository is not None:
|
||||||
self._audit_repository.insert(
|
await self._audit_repository.insert(
|
||||||
AuditRecord(
|
AuditRecord(
|
||||||
occurred_at=datetime.now(UTC),
|
occurred_at=datetime.now(UTC),
|
||||||
actor="execution_engine",
|
actor="execution_engine",
|
||||||
@@ -447,7 +451,8 @@ class MarketDataFeed:
|
|||||||
realized_pnl = outcome
|
realized_pnl = outcome
|
||||||
|
|
||||||
if realized_pnl is not None and self._loss_limit_guard is not None:
|
if realized_pnl is not None and self._loss_limit_guard is not None:
|
||||||
self._loss_limit_guard.register_realized_pnl(realized_pnl)
|
self._loss_limit_guard.register_realized_pnl(
|
||||||
|
realized_pnl)
|
||||||
if self._loss_limit_guard.is_halted:
|
if self._loss_limit_guard.is_halted:
|
||||||
_LOG.warning(
|
_LOG.warning(
|
||||||
"loss_limit_halt_triggered",
|
"loss_limit_halt_triggered",
|
||||||
@@ -459,7 +464,7 @@ class MarketDataFeed:
|
|||||||
self._trade_limits_guard.close_trade(exposure_by_asset)
|
self._trade_limits_guard.close_trade(exposure_by_asset)
|
||||||
|
|
||||||
if self._audit_repository is not None:
|
if self._audit_repository is not None:
|
||||||
self._audit_repository.insert(
|
await self._audit_repository.insert(
|
||||||
AuditRecord(
|
AuditRecord(
|
||||||
occurred_at=datetime.now(UTC),
|
occurred_at=datetime.now(UTC),
|
||||||
actor="execution_engine",
|
actor="execution_engine",
|
||||||
|
|||||||
@@ -521,7 +521,11 @@ class ConfigSettingRepository:
|
|||||||
""",
|
""",
|
||||||
key,
|
key,
|
||||||
)
|
)
|
||||||
return result != "DELETE 0"
|
if result is None:
|
||||||
|
return False
|
||||||
|
elif isinstance(result, str):
|
||||||
|
return result != "DELETE 0"
|
||||||
|
return False
|
||||||
|
|
||||||
async def list_settings(self, section: str | None = None) -> list[ConfigSetting]:
|
async def list_settings(self, section: str | None = None) -> list[ConfigSetting]:
|
||||||
"""List all configuration settings, optionally filtered by section."""
|
"""List all configuration settings, optionally filtered by section."""
|
||||||
@@ -567,7 +571,7 @@ class ConfigSettingRepository:
|
|||||||
ts = row["latest_updated_at"]
|
ts = row["latest_updated_at"]
|
||||||
if isinstance(ts, str):
|
if isinstance(ts, str):
|
||||||
return datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
return datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
||||||
return ts
|
return ts # type: ignore[no-any-return]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -663,7 +667,11 @@ class ConfigPairingRepository:
|
|||||||
""",
|
""",
|
||||||
base_asset, quote_asset,
|
base_asset, quote_asset,
|
||||||
)
|
)
|
||||||
return result != "DELETE 0"
|
if result is None:
|
||||||
|
return False
|
||||||
|
elif isinstance(result, str):
|
||||||
|
return result != "DELETE 0"
|
||||||
|
return False
|
||||||
|
|
||||||
async def upsert_pairing(self, pairing: ConfigPairing) -> ConfigPairing:
|
async def upsert_pairing(self, pairing: ConfigPairing) -> ConfigPairing:
|
||||||
"""Insert or update a currency pairing (upsert on base_asset, quote_asset)."""
|
"""Insert or update a currency pairing (upsert on base_asset, quote_asset)."""
|
||||||
@@ -996,4 +1004,8 @@ class BacktestJobRepository:
|
|||||||
"DELETE FROM backtest_jobs WHERE id = $1",
|
"DELETE FROM backtest_jobs WHERE id = $1",
|
||||||
job_id,
|
job_id,
|
||||||
)
|
)
|
||||||
return result != "DELETE 0"
|
if result is None:
|
||||||
|
return False
|
||||||
|
elif isinstance(result, str):
|
||||||
|
return result != "DELETE 0"
|
||||||
|
return False
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from contextlib import asynccontextmanager
|
|||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
|
||||||
|
|
||||||
from arbitrade.config.settings import get_settings
|
from arbitrade.config.settings import get_settings
|
||||||
from arbitrade.storage.pg_store import PgStore
|
from arbitrade.storage.pg_store import PgStore
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import pytest
|
|||||||
from arbitrade.config.settings import get_settings
|
from arbitrade.config.settings import get_settings
|
||||||
from arbitrade.detection.engine import OpportunityEvent
|
from arbitrade.detection.engine import OpportunityEvent
|
||||||
from arbitrade.execution.sequencer import TriangularExecutionSequencer
|
from arbitrade.execution.sequencer import TriangularExecutionSequencer
|
||||||
from arbitrade.storage.pg_store import PgStore
|
|
||||||
from arbitrade.storage.executions import AsyncExecutionWriter
|
from arbitrade.storage.executions import AsyncExecutionWriter
|
||||||
|
from arbitrade.storage.pg_store import PgStore
|
||||||
from arbitrade.storage.repositories import OrderRepository, PnLRepository, TradeRepository
|
from arbitrade.storage.repositories import OrderRepository, PnLRepository, TradeRepository
|
||||||
|
|
||||||
pytestmark = pytest.mark.integration
|
pytestmark = pytest.mark.integration
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from contextlib import asynccontextmanager
|
|||||||
from datetime import UTC, datetime, timedelta
|
from datetime import UTC, datetime, timedelta
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
|
||||||
|
|
||||||
from arbitrade.config.settings import get_settings
|
from arbitrade.config.settings import get_settings
|
||||||
from arbitrade.metrics import MetricsCalculator
|
from arbitrade.metrics import MetricsCalculator
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import pytest
|
|||||||
|
|
||||||
from arbitrade.config.settings import get_settings
|
from arbitrade.config.settings import get_settings
|
||||||
from arbitrade.detection.engine import OpportunityEvent
|
from arbitrade.detection.engine import OpportunityEvent
|
||||||
from arbitrade.storage.pg_store import PgStore
|
|
||||||
from arbitrade.storage.opportunities import AsyncOpportunityWriter
|
from arbitrade.storage.opportunities import AsyncOpportunityWriter
|
||||||
|
from arbitrade.storage.pg_store import PgStore
|
||||||
from arbitrade.storage.repositories import OpportunityRepository
|
from arbitrade.storage.repositories import OpportunityRepository
|
||||||
|
|
||||||
pytestmark = pytest.mark.integration
|
pytestmark = pytest.mark.integration
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from contextlib import asynccontextmanager
|
|||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
|
|
||||||
from arbitrade.config.settings import Settings, get_settings
|
from arbitrade.config.settings import get_settings
|
||||||
from arbitrade.storage.pg_store import PgStore
|
from arbitrade.storage.pg_store import PgStore
|
||||||
|
|
||||||
pytestmark = pytest.mark.integration
|
pytestmark = pytest.mark.integration
|
||||||
@@ -332,6 +332,7 @@ async def test_audit_list_recent(pg: PgStore) -> None:
|
|||||||
"""AuditRepository.list_recent returns records in desc order."""
|
"""AuditRepository.list_recent returns records in desc order."""
|
||||||
await pg.migrate()
|
await pg.migrate()
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
from arbitrade.storage.repositories import AuditRecord, AuditRepository
|
from arbitrade.storage.repositories import AuditRecord, AuditRepository
|
||||||
|
|
||||||
repo = AuditRepository(pg)
|
repo = AuditRepository(pg)
|
||||||
|
|||||||
Reference in New Issue
Block a user