Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a3e5acd765 | |||
| 49c91e5d85 | |||
| 400bd41e56 | |||
| 1d1ebd385e | |||
| c5ed951b2a | |||
| c77377f67e | |||
| 8ccebf81f5 | |||
| dc38176529 | |||
| 3f29717b64 | |||
| ecc1c1de5d |
+16
-4
@@ -3,10 +3,22 @@ __pycache__/
|
||||
__OLD__/
|
||||
.specstory/
|
||||
.history/
|
||||
.cursorindexingignore
|
||||
.vscode/
|
||||
*.py[cod]
|
||||
.ipynb_checkpoints/
|
||||
.pytest_cache/
|
||||
|
||||
# Local environments
|
||||
.venv/
|
||||
venv/
|
||||
|
||||
# Local test data and generated analysis results
|
||||
data/*
|
||||
!data/.gitkeep
|
||||
results/*
|
||||
!results/.gitkeep
|
||||
|
||||
data
|
||||
|
||||
cvttpy
|
||||
# SpecStory explanation file
|
||||
.specstory/.what-is-this.md
|
||||
results/
|
||||
tmp/
|
||||
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
PYTHONPATH=/home/oleg/develop
|
||||
Vendored
-133
@@ -1,133 +0,0 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
|
||||
{
|
||||
"name": "Python Debugger: Current File",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"python": "/home/oleg/.pyenv/python3.12-venv/bin/python",
|
||||
"program": "${file}",
|
||||
"console": "integratedTerminal",
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceFolder}/lib:${workspaceFolder}/.."
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "-------- VECM --------",
|
||||
},
|
||||
{
|
||||
"name": "CRYPTO VECM BACKTEST (optimized)",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"python": "/home/oleg/.pyenv/python3.12-venv/bin/python",
|
||||
"program": "${workspaceFolder}/research/backtest.py",
|
||||
"args": [
|
||||
"--config=http://cloud16.cvtt.vpn:6789/apps/pairs_trading/backtest",
|
||||
"--instruments=CRYPTO:BNBSPOT:PAIR-ADA-USDT,CRYPTO:BNBSPOT:PAIR-SOL-USDT",
|
||||
"--date_pattern=20250911",
|
||||
"--result_db=${workspaceFolder}/research/results/crypto/%T.vecm-opt.ADA-SOL.20250605.crypto_results.db",
|
||||
],
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceFolder}/..",
|
||||
"CONFIG_SERVICE": "cloud16.cvtt.vpn:6789",
|
||||
"MODEL_CONFIG": "vecm-opt"
|
||||
},
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
// {
|
||||
// "name": "EQUITY VECM (rolling)",
|
||||
// "type": "debugpy",
|
||||
// "request": "launch",
|
||||
// "python": "/home/oleg/.pyenv/python3.12-venv/bin/python",
|
||||
// "program": "${workspaceFolder}/research/backtest.py",
|
||||
// "args": [
|
||||
// "--config=${workspaceFolder}/configuration/vecm.cfg",
|
||||
// "--instruments=COIN:EQUITY:ALPACA,MSTR:EQUITY:ALPACA",
|
||||
// "--date_pattern=20250605",
|
||||
// "--result_db=${workspaceFolder}/research/results/equity/%T.vecm.COIN-MSTR.20250605.equity_results.db",
|
||||
// ],
|
||||
// "env": {
|
||||
// "PYTHONPATH": "${workspaceFolder}/lib"
|
||||
// },
|
||||
// "console": "integratedTerminal"
|
||||
// },
|
||||
// {
|
||||
// "name": "EQUITY-CRYPTO VECM (rolling)",
|
||||
// "type": "debugpy",
|
||||
// "request": "launch",
|
||||
// "python": "/home/oleg/.pyenv/python3.12-venv/bin/python",
|
||||
// "program": "${workspaceFolder}/research/backtest.py",
|
||||
// "args": [
|
||||
// "--config=${workspaceFolder}/configuration/vecm.cfg",
|
||||
// "--instruments=COIN:EQUITY:ALPACA,BTC-USDT:CRYPTO:BNBSPOT",
|
||||
// "--date_pattern=20250605",
|
||||
// "--result_db=${workspaceFolder}/research/results/intermarket/%T.vecm.COIN-BTC.20250601.equity_results.db",
|
||||
// ],
|
||||
// "env": {
|
||||
// "PYTHONPATH": "${workspaceFolder}/lib"
|
||||
// },
|
||||
// "console": "integratedTerminal"
|
||||
// },
|
||||
{
|
||||
"name": "-------- B a t c h e s --------",
|
||||
},
|
||||
{
|
||||
"name": "CRYPTO OLS Batch (rolling)",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"python": "/home/oleg/.pyenv/python3.12-venv/bin/python",
|
||||
"program": "${workspaceFolder}/research/backtest.py",
|
||||
"args": [
|
||||
"--config=${workspaceFolder}/configuration/ols.cfg",
|
||||
"--instruments=ADA-USDT:CRYPTO:BNBSPOT,SOL-USDT:CRYPTO:BNBSPOT",
|
||||
"--date_pattern=2025060*",
|
||||
"--result_db=${workspaceFolder}/research/results/crypto/%T.ols.ADA-SOL.2025060-.crypto_results.db",
|
||||
],
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceFolder}/lib"
|
||||
},
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "CRYPTO VECM Batch (rolling)",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"python": "/home/oleg/.pyenv/python3.12-venv/bin/python",
|
||||
"program": "${workspaceFolder}/research/backtest.py",
|
||||
"args": [
|
||||
"--config=${workspaceFolder}/configuration/vecm.cfg",
|
||||
"--instruments=ADA-USDT:CRYPTO:BNBSPOT,SOL-USDT:CRYPTO:BNBSPOT",
|
||||
"--date_pattern=2025060*",
|
||||
"--result_db=${workspaceFolder}/research/results/crypto/%T.vecm.ADA-SOL.2025060-.crypto_results.db",
|
||||
],
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceFolder}/lib"
|
||||
},
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "-------- Viz Test --------",
|
||||
},
|
||||
{
|
||||
"name": "Viz Test",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"python": "/home/oleg/.pyenv/python3.12-venv/bin/python",
|
||||
"program": "${workspaceFolder}/tests/viz_test.py",
|
||||
"args": [
|
||||
"--config=${workspaceFolder}/configuration/ols.cfg",
|
||||
"--instruments=ADA-USDT:CRYPTO:BNBSPOT,SOL-USDT:CRYPTO:BNBSPOT",
|
||||
"--date_pattern=20250605",
|
||||
],
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceFolder}/lib"
|
||||
},
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
-10
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": ".."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"workbench.colorTheme": "Dracula Theme"
|
||||
}
|
||||
}
|
||||
Vendored
-19
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"python.testing.pytestEnabled": true,
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestArgs": [
|
||||
"unittests"
|
||||
],
|
||||
"python.testing.cwd": "${workspaceFolder}",
|
||||
"python.testing.autoTestDiscoverOnSaveEnabled": true,
|
||||
"python.testing.pytestPath": "python3",
|
||||
"python.analysis.extraPaths": [
|
||||
"${workspaceFolder}",
|
||||
"${workspaceFolder}/..",
|
||||
"${workspaceFolder}/unittests"
|
||||
],
|
||||
"python.envFile": "${workspaceFolder}/.env",
|
||||
"python.testing.debugPort": 3000,
|
||||
"python.testing.promptToConfigure": false,
|
||||
"python.defaultInterpreterPath": "/home/oleg/.pyenv/python3.12-venv/bin/python"
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
# Agent Instructions
|
||||
|
||||
## Repository purpose
|
||||
|
||||
This repository analyzes test results with Jupyter notebooks and Python or
|
||||
Bash scripts. Inputs are commonly SQLite databases containing time-series data
|
||||
and JSON columns, but analyses may use other test-result formats.
|
||||
|
||||
Ignore `__SAV__/`. It is unrelated legacy material, is not part of the active
|
||||
project, and must not be read, edited, moved, or used as a source of conventions
|
||||
unless the user explicitly requests it.
|
||||
|
||||
## Active layout
|
||||
|
||||
- `notebooks/`: exploratory and report-oriented Jupyter notebooks.
|
||||
- `scripts/`: reusable Python and Bash analysis utilities.
|
||||
- `data/`: local input data. Contents are ignored except for `.gitkeep`.
|
||||
- `results/`: generated tables, figures, exports, and reports. Contents are
|
||||
ignored except for `.gitkeep`.
|
||||
- `requirements.txt`: Python dependencies needed to reproduce repository work.
|
||||
|
||||
Keep reusable logic in `scripts/` and use notebooks to orchestrate analysis,
|
||||
explain decisions, and present results. Do not create a separate `analysis/`
|
||||
tree.
|
||||
|
||||
## Python environment
|
||||
|
||||
The intended virtual environment is `~/.pyenv/python3.12-venv`.
|
||||
|
||||
```bash
|
||||
source ~/.pyenv/python3.12-venv/bin/activate
|
||||
python -m pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Agents may install packages in this environment when needed. Whenever a package
|
||||
is installed for repository work, update `requirements.txt` in the same change
|
||||
with a suitable direct dependency declaration. Use `python -m pip`, not bare
|
||||
`pip`, in documented commands.
|
||||
|
||||
Do not create an in-repository virtual environment unless the user asks for
|
||||
one.
|
||||
|
||||
## Data handling
|
||||
|
||||
- Treat files in `data/` as local, potentially large, and potentially
|
||||
sensitive.
|
||||
- Do not commit SQLite databases, raw test results, or generated results.
|
||||
- Do not modify source data in place. Write transformed data and exports under
|
||||
`results/`.
|
||||
- Use parameterized SQL for values. Do not construct SQL by interpolating
|
||||
untrusted data.
|
||||
- Parse JSON columns defensively and preserve missing, malformed, and unexpected
|
||||
values unless the analysis explicitly defines another policy.
|
||||
- State assumptions about timestamps, time zones, ordering, units, and duplicate
|
||||
observations in the notebook or script that relies on them.
|
||||
- Avoid loading entire databases into memory when a filtered query or chunked
|
||||
read is practical.
|
||||
|
||||
## Notebook conventions
|
||||
|
||||
- A notebook must run from a fresh kernel, top to bottom, without relying on
|
||||
hidden interactive state.
|
||||
- Set random seeds where nondeterminism affects results.
|
||||
- Keep data paths relative to the repository root and avoid machine-specific
|
||||
absolute paths.
|
||||
- Move logic that is reused or substantial enough to test into `scripts/`.
|
||||
- Clear cell outputs before committing notebooks. Never commit embedded source
|
||||
data, credentials, or bulky generated output.
|
||||
- Keep concise Markdown context near analyses: purpose, input assumptions,
|
||||
method, and interpretation.
|
||||
|
||||
## Scripts
|
||||
|
||||
- Python scripts should expose reusable functions and use a guarded CLI entry
|
||||
point when executable.
|
||||
- Bash scripts must start with `#!/usr/bin/env bash` and use
|
||||
`set -euo pipefail`.
|
||||
- Prefer explicit CLI arguments over hard-coded paths or parameters.
|
||||
- Fail with actionable error messages when required data, tables, columns, or
|
||||
configuration are missing.
|
||||
|
||||
## Verification
|
||||
|
||||
Verification should be proportional to the change. At minimum:
|
||||
|
||||
- Run `pytest` for Python script changes.
|
||||
- Add or update tests for reusable parsing, transformation, query, and
|
||||
calculation logic.
|
||||
- Execute changed notebooks from a fresh kernel with `nbmake`.
|
||||
- Run changed Bash scripts against a safe fixture or exercise their
|
||||
non-destructive validation/help path.
|
||||
- Clear notebook outputs after execution and before committing.
|
||||
|
||||
Useful commands:
|
||||
|
||||
```bash
|
||||
python -m pytest
|
||||
python -m pytest --nbmake notebooks
|
||||
jupyter nbconvert --ClearOutputPreprocessor.enabled=True --inplace path/to/notebook.ipynb
|
||||
```
|
||||
|
||||
If verification cannot be run, report exactly what was skipped and why.
|
||||
|
||||
## Release rules
|
||||
|
||||
- Update `CHANGELOG.md` for every release with the release version, release
|
||||
date, Git tag, and a concise summary of notable changes.
|
||||
- Keep an `Unreleased` section at the top of `CHANGELOG.md` for changes that
|
||||
have not been included in a tagged release yet.
|
||||
- Move relevant entries from `Unreleased` into the dated release section when
|
||||
creating a release, and leave `Unreleased` present for future changes.
|
||||
- Use release headers in `YYYY-MM-DD vMAJOR.MINOR.PATCH` form.
|
||||
- Use version numbers in `MAJOR.MINOR.PATCH` form. Start this repository at
|
||||
`0.0.1`.
|
||||
- Use Git tags in `vMAJOR.MINOR.PATCH` form, matching the changelog version
|
||||
exactly. For example, version `0.0.1` must be tagged as `v0.0.1`.
|
||||
- Create the Git tag only after the changelog and any release-related version
|
||||
changes are complete.
|
||||
- When the user requests creating a release, treat that as explicit permission
|
||||
to commit the release changes, create the matching Git tag, and push both the
|
||||
branch and tag.
|
||||
- Do not push release commits or tags unless the user explicitly requests it.
|
||||
|
||||
## Mandatory background review
|
||||
|
||||
Changes to Python scripts, Bash scripts, or notebook code cells require approval
|
||||
from a separate background reviewer agent before the implementing agent may
|
||||
declare the work complete.
|
||||
|
||||
The implementing agent must:
|
||||
|
||||
1. Finish the implementation and run the relevant verification.
|
||||
2. Ask a separate background agent to review the diff for correctness,
|
||||
reproducibility, data safety, and test coverage.
|
||||
3. Address every material finding, rerun affected checks, and request follow-up
|
||||
review when the fix materially changes the code.
|
||||
4. Report the reviewer outcome in the final response.
|
||||
|
||||
The reviewer must inspect the actual diff and relevant surrounding files; a
|
||||
self-review does not satisfy this requirement. Documentation-only,
|
||||
configuration-only, dependency-only, and ignore-rule-only changes do not
|
||||
require background approval unless they also alter Python, Bash, or notebook
|
||||
code cells.
|
||||
|
||||
If no background reviewer is available, complete all other work but do not
|
||||
claim reviewer approval. End the handoff with the exact status:
|
||||
|
||||
`review pending`
|
||||
|
||||
## Change discipline
|
||||
|
||||
- Preserve user changes and avoid unrelated cleanup.
|
||||
- Do not edit or commit generated files from `data/` or `results/`.
|
||||
- Do not push or commit unless the user explicitly requests it. The `master`
|
||||
branch being unprotected does not imply permission to push directly.
|
||||
- Keep changes focused and explain any new assumptions or dependencies.
|
||||
@@ -0,0 +1,71 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project are documented in this file.
|
||||
|
||||
## Unreleased
|
||||
|
||||
No unreleased changes yet.
|
||||
|
||||
## 2026-07-29 v1.0.2
|
||||
|
||||
- Added a Panel application for single-day SPBT result analysis with result-file
|
||||
selection, minimum TARGET-change input, pair TheoRet table, pair selector,
|
||||
selected-pair execution table, and market/trade chart.
|
||||
- Added a launcher script for the Panel application.
|
||||
- Changed notebook and Panel pair analysis to use per-row Analyze actions from
|
||||
the Pair TheoRet grid, deferring selected-pair calculations until clicked.
|
||||
- Adjusted Panel sizing so key controls use compact widths and Pair TheoRet uses
|
||||
content width with vertical scrolling instead of full-width paginated layout.
|
||||
- Added a FastListTemplate shell to the Panel application for sidebar controls
|
||||
and configurable app color accents.
|
||||
- Made Plotly chart panes use all available horizontal space.
|
||||
|
||||
## 2026-07-28 v1.0.1
|
||||
|
||||
- Added the `spbt_day` notebook for interactive single-day backtest result
|
||||
analysis, including SQLite result file selection from the local data
|
||||
directory.
|
||||
- Added selector-pair loading and dense ranking by `mr_score.final`, preserving
|
||||
rows with invalid score JSON for inspection.
|
||||
- Added theoretical return calculation for ranked pairs from
|
||||
`trading_instructions`, including reusable helper functions and tests.
|
||||
- Added a Plotly histogram for visual analysis of total theoretical return by
|
||||
pair.
|
||||
- Moved notebook support code into reusable `scripts/spbt_day.py` helpers.
|
||||
- Adjusted notebook table outputs to show all relevant rows and reduce
|
||||
redundant intermediate displays.
|
||||
- Added an alphabetically sorted pair selector for individual pair analysis.
|
||||
- Added selected-pair theoretical execution tables and aligned TheoRet
|
||||
calculations with target-delta trade generation.
|
||||
- Added per-asset `strength` values to selected-pair theoretical execution
|
||||
tables.
|
||||
- Corrected theoretical execution size to use
|
||||
`10000 * strength / reference_price`.
|
||||
- Removed `:USD` quote suffixes from displayed pair names in notebook tables,
|
||||
chart hovers, and the pair selector dropdown while preserving full internal
|
||||
pair keys for calculations.
|
||||
- Added `num_trades` to pair TheoRet summaries, counting asset-level theoretical
|
||||
trades from effective `TARGET` and `CLOSE` instructions.
|
||||
- Added sortable interactive grids for the pair TheoRet and selected-pair
|
||||
theoretical execution tables.
|
||||
- Styled interactive dataframe grids with black text on white backgrounds for
|
||||
readability across notebook themes.
|
||||
- Added a selected-pair Plotly chart that overlays theoretical BUY/SELL
|
||||
executions on relative 1-minute market close data for both instruments.
|
||||
- Anchored the selected-pair market chart at trading-day midnight and normalized
|
||||
relative prices to each instrument's close at that timestamp.
|
||||
- Added a `min_pctg_change` threshold for ranked pair TheoRet calculations to
|
||||
skip small target-strength changes after a position is acquired.
|
||||
- Added a notebook input field for the minimum TARGET strength-change threshold.
|
||||
|
||||
## 2026-07-25 v0.0.9
|
||||
|
||||
- Added contributing guidance and Python dependency declarations.
|
||||
- Added placeholder files for active project directories.
|
||||
- Updated ignore rules for local data, generated results, caches, and local
|
||||
environments.
|
||||
- Documented unreleased changelog handling and release push behavior.
|
||||
|
||||
## 2026-07-25 v0.0.1
|
||||
|
||||
- Established the initial repository structure and project guidance.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Contributing
|
||||
|
||||
## Setup
|
||||
|
||||
Use the shared Python 3.12 virtual environment:
|
||||
|
||||
```bash
|
||||
source ~/.pyenv/python3.12-venv/bin/activate
|
||||
python -m pip install -r requirements.txt
|
||||
```
|
||||
|
||||
If you install another package for repository work, add its direct dependency
|
||||
to `requirements.txt`.
|
||||
|
||||
## Repository layout
|
||||
|
||||
- Put notebooks in `notebooks/`.
|
||||
- Put reusable Python and Bash utilities in `scripts/`.
|
||||
- Put local input files in `data/`.
|
||||
- Put generated artifacts in `results/`.
|
||||
|
||||
The contents of `data/` and `results/` are ignored. Do not force-add test
|
||||
databases, raw test results, generated exports, or notebook outputs.
|
||||
|
||||
`__SAV__/` is unrelated legacy material and is outside the active project.
|
||||
|
||||
## Working with notebooks
|
||||
|
||||
Notebooks must execute from top to bottom in a fresh kernel. Use relative paths,
|
||||
document data assumptions, and move reusable logic into tested scripts.
|
||||
|
||||
Before handing off a change:
|
||||
|
||||
```bash
|
||||
python -m pytest
|
||||
python -m pytest --nbmake notebooks
|
||||
jupyter nbconvert --ClearOutputPreprocessor.enabled=True --inplace path/to/notebook.ipynb
|
||||
```
|
||||
|
||||
Run only the checks relevant to the files present in the repository, and report
|
||||
anything that could not be run.
|
||||
|
||||
## Review requirement
|
||||
|
||||
Python scripts, Bash scripts, and notebook code-cell changes require review and
|
||||
approval by a separate background agent. Address material findings and rerun
|
||||
affected checks before completion. If a reviewer is unavailable, the change may
|
||||
be handed off only with the status `review pending`.
|
||||
|
||||
Documentation, dependency declarations, and ignore rules do not require this
|
||||
background review when no Python, Bash, or notebook code cells changed.
|
||||
|
||||
The `master` branch is not protected. That does not remove the review
|
||||
requirement or authorize an agent to commit or push without an explicit request.
|
||||
@@ -0,0 +1,2 @@
|
||||
## 2026-02-09 (v0.0.9)
|
||||
- related to the changes made in *cvttpy_tools 1.4.7*
|
||||
@@ -0,0 +1 @@
|
||||
0.0.9
|
||||
@@ -13,19 +13,19 @@ from statsmodels.tsa.stattools import adfuller, coint # type: ignore
|
||||
from statsmodels.tsa.vector_ar.vecm import coint_johansen # type: ignore
|
||||
|
||||
|
||||
from cvttpy_tools.app import App
|
||||
from cvttpy_tools.base import NamedObject
|
||||
from cvttpy_tools.config import Config, CvttAppConfig
|
||||
from cvttpy_tools.logger import Log
|
||||
from cvttpy_tools.timeutils import NanoPerSec, SecPerHour, current_nanoseconds
|
||||
from cvttpy_tools.web.rest_client import RESTSender
|
||||
from cvttpy_tools.web.rest_service import RestService
|
||||
from cvttpy_tools.base.app import App
|
||||
from cvttpy_tools.base.base import NamedObject
|
||||
from cvttpy_tools.base.config import Config, CvttAppConfig
|
||||
from cvttpy_tools.base.logger import Log
|
||||
from cvttpy_tools.base.timeutils import NanoPerSec, SecPerHour, current_nanoseconds
|
||||
from cvttpy_tools.comm.web.rest_service import RestService
|
||||
|
||||
from cvttpy_trading.trading.exchange_config import ExchangeAccounts
|
||||
from cvttpy_trading.trading.instrument import ExchangeInstrument
|
||||
from cvttpy_trading.trading.mkt_data.md_summary import MdTradesAggregate, MdSummary
|
||||
|
||||
from pairs_trading.apps.pair_selector.renderer import HtmlRenderer
|
||||
from pairs_trading.lib.live.rest import RESTSender
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -3,9 +3,9 @@ from __future__ import annotations
|
||||
from typing import Any, Dict, List
|
||||
|
||||
|
||||
from cvttpy_tools.app import App
|
||||
from cvttpy_tools.base import NamedObject
|
||||
from cvttpy_tools.config import CvttAppConfig
|
||||
from cvttpy_tools.base.app import App
|
||||
from cvttpy_tools.base.base import NamedObject
|
||||
from cvttpy_tools.base.config import CvttAppConfig
|
||||
|
||||
|
||||
class HtmlRenderer(NamedObject):
|
||||
@@ -4,13 +4,13 @@ import asyncio
|
||||
from typing import Callable, Coroutine, Dict, List
|
||||
import aiohttp.web as web
|
||||
|
||||
from cvttpy_tools.app import App
|
||||
from cvttpy_tools.config import Config
|
||||
from cvttpy_tools.base import NamedObject
|
||||
from cvttpy_tools.config import CvttAppConfig
|
||||
from cvttpy_tools.logger import Log
|
||||
from cvttpy_tools.base.app import App
|
||||
from cvttpy_tools.base.config import Config
|
||||
from cvttpy_tools.base.base import NamedObject
|
||||
from cvttpy_tools.base.config import CvttAppConfig
|
||||
from cvttpy_tools.base.logger import Log
|
||||
from cvttpy_tools.settings.cvtt_types import BookIdT
|
||||
from cvttpy_tools.web.rest_service import RestService
|
||||
from cvttpy_tools.comm.web.rest_service import RestService
|
||||
|
||||
# ---
|
||||
from cvttpy_trading.trading.instrument import ExchangeInstrument
|
||||
@@ -5,19 +5,19 @@ from typing import Dict, Any, List, Optional, Set
|
||||
|
||||
import requests
|
||||
|
||||
from cvttpy_tools.base import NamedObject
|
||||
from cvttpy_tools.logger import Log
|
||||
from cvttpy_tools.config import Config
|
||||
from cvttpy_tools.timer import Timer
|
||||
from cvttpy_tools.timeutils import NanosT, current_seconds
|
||||
from cvttpy_tools.base.base import NamedObject
|
||||
from cvttpy_tools.base.logger import Log
|
||||
from cvttpy_tools.base.config import Config
|
||||
from cvttpy_tools.base.timer import Timer
|
||||
from cvttpy_tools.base.timeutils import NanosT, current_seconds
|
||||
from cvttpy_tools.settings.cvtt_types import InstrumentIdT, IntervalSecT
|
||||
from cvttpy_tools.web.rest_client import RESTSender
|
||||
# ---
|
||||
from cvttpy_trading.trading.instrument import ExchangeInstrument
|
||||
from cvttpy_trading.trading.accounting.exch_account import ExchangeAccountNameT
|
||||
from cvttpy_trading.trading.mkt_data.md_summary import MdTradesAggregate, MdSummary, MdSummaryCallbackT
|
||||
from cvttpy_trading.trading.exchange_config import ExchangeAccounts
|
||||
# ---
|
||||
from pairs_trading.lib.live.rest import RESTSender
|
||||
|
||||
|
||||
# class MdSummary(HistMdBar):
|
||||
@@ -1,14 +1,14 @@
|
||||
```python
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict
|
||||
from typing import Dict, Optional
|
||||
import time
|
||||
|
||||
import requests
|
||||
|
||||
from cvttpy_tools.base import NamedObject
|
||||
from cvttpy_tools.base.base import NamedObject
|
||||
|
||||
class RESTSender(NamedObject):
|
||||
# Synchronous request sernder
|
||||
session_: requests.Session
|
||||
base_url_: str
|
||||
|
||||
@@ -26,35 +26,35 @@ class RESTSender(NamedObject):
|
||||
except requests.exceptions.RequestException:
|
||||
return False
|
||||
|
||||
def send_post(self, endpoint: str, post_body: Dict) -> requests.Response:
|
||||
|
||||
while not self.is_ready():
|
||||
print("Waiting for FrontGateway to start...")
|
||||
time.sleep(5)
|
||||
def send_post(
|
||||
self, endpoint: str, post_body: Dict, headers: Optional[Dict[str, str]] = None
|
||||
) -> requests.Response:
|
||||
|
||||
if not headers:
|
||||
headers = {"Content-Type": "application/json"}
|
||||
url = f"{self.base_url_}/{endpoint}"
|
||||
try:
|
||||
return self.session_.request(
|
||||
method="POST",
|
||||
url=url,
|
||||
json=post_body,
|
||||
headers={"Content-Type": "application/json"},
|
||||
headers=headers,
|
||||
)
|
||||
except requests.exceptions.RequestException as excpt:
|
||||
raise ConnectionError(
|
||||
f"Failed to send status={excpt.response.status_code} {excpt.response.text}" # type: ignore
|
||||
) from excpt
|
||||
|
||||
def send_get(self, endpoint: str) -> requests.Response:
|
||||
while not self.is_ready():
|
||||
print("Waiting for FrontGateway to start...")
|
||||
time.sleep(5)
|
||||
|
||||
def send_get(
|
||||
self, endpoint: str, headers: Optional[Dict[str, str]] = None
|
||||
) -> requests.Response:
|
||||
if not headers:
|
||||
headers = {}
|
||||
url = f"{self.base_url_}/{endpoint}"
|
||||
try:
|
||||
return self.session_.request(method="GET", url=url)
|
||||
return self.session_.request(method="GET", url=url, headers=headers)
|
||||
except requests.exceptions.RequestException as excpt:
|
||||
raise ConnectionError(
|
||||
f"Failed to send status={excpt.response.status_code} {excpt.response.text}" # type: ignore
|
||||
) from excpt
|
||||
```
|
||||
|
||||
@@ -3,14 +3,14 @@ from enum import Enum
|
||||
import requests
|
||||
|
||||
# import aiohttp
|
||||
from cvttpy_tools.base import NamedObject
|
||||
from cvttpy_tools.config import Config
|
||||
from cvttpy_tools.logger import Log
|
||||
from cvttpy_tools.web.rest_client import RESTSender
|
||||
from cvttpy_tools.base.base import NamedObject
|
||||
from cvttpy_tools.base.config import Config
|
||||
from cvttpy_tools.base.logger import Log
|
||||
# ---
|
||||
from cvttpy_trading.trading.trading_instructions import TradingInstructions
|
||||
# ---
|
||||
from pairs_trading.apps.pair_trader import PairTrader
|
||||
from pairs_trading.lib.live.rest import RESTSender
|
||||
|
||||
|
||||
class TradingInstructionsSender(NamedObject):
|
||||
+5
-5
@@ -5,12 +5,12 @@ from typing import Any, Dict, List, Optional
|
||||
import pandas as pd
|
||||
|
||||
# ---
|
||||
from cvttpy_tools.base import NamedObject
|
||||
from cvttpy_tools.app import App
|
||||
from cvttpy_tools.config import Config
|
||||
from cvttpy_tools.base.base import NamedObject
|
||||
from cvttpy_tools.base.app import App
|
||||
from cvttpy_tools.base.config import Config
|
||||
from cvttpy_tools.settings.cvtt_types import IntervalSecT
|
||||
from cvttpy_tools.timeutils import NanosT, SecPerHour, current_nanoseconds, NanoPerSec, format_nanos_utc
|
||||
from cvttpy_tools.logger import Log
|
||||
from cvttpy_tools.base.timeutils import NanosT, SecPerHour, current_nanoseconds, NanoPerSec, format_nanos_utc
|
||||
from cvttpy_tools.base.logger import Log
|
||||
|
||||
# ---
|
||||
from cvttpy_trading.trading.instrument import ExchangeInstrument
|
||||
@@ -8,7 +8,7 @@ from typing import Any, Dict, Optional, cast
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from cvttpy_tools.config import Config
|
||||
from cvttpy_tools.base.config import Config
|
||||
|
||||
@dataclass
|
||||
class DataWindowParams:
|
||||
@@ -6,8 +6,8 @@ from typing import Any, Dict, List, Optional
|
||||
import pandas as pd
|
||||
|
||||
# ---
|
||||
from cvttpy_tools.base import NamedObject
|
||||
from cvttpy_tools.config import Config
|
||||
from cvttpy_tools.base.base import NamedObject
|
||||
from cvttpy_tools.base.config import Config
|
||||
from cvttpy_tools.settings.cvtt_types import JsonDictT
|
||||
|
||||
# ---
|
||||
@@ -4,7 +4,7 @@ from abc import ABC, abstractmethod
|
||||
from typing import Any, Dict, cast
|
||||
|
||||
# ---
|
||||
from cvttpy_tools.config import Config
|
||||
from cvttpy_tools.base.config import Config
|
||||
# ---
|
||||
from pairs_trading.lib.pt_strategy.prediction import Prediction
|
||||
from pairs_trading.lib.pt_strategy.trading_pair import TradingPair
|
||||
@@ -4,7 +4,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
import pandas as pd
|
||||
# ---
|
||||
from cvttpy_tools.config import Config
|
||||
from cvttpy_tools.base.config import Config
|
||||
# ---
|
||||
from cvttpy_trading.trading.instrument import ExchangeInstrument
|
||||
# ---
|
||||
@@ -5,7 +5,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
import pandas as pd
|
||||
# ---
|
||||
from cvttpy_tools.config import Config
|
||||
from cvttpy_tools.base.config import Config
|
||||
# ---
|
||||
from cvttpy_trading.trading.instrument import ExchangeInstrument
|
||||
# ---
|
||||
@@ -8,8 +8,8 @@ from typing import Any, Dict, List
|
||||
import pandas as pd
|
||||
|
||||
# ---
|
||||
from cvttpy_tools.base import NamedObject
|
||||
from cvttpy_tools.config import Config
|
||||
from cvttpy_tools.base.base import NamedObject
|
||||
from cvttpy_tools.base.config import Config
|
||||
# ---
|
||||
from cvttpy_trading.trading.instrument import ExchangeInstrument
|
||||
# ---
|
||||
@@ -2,7 +2,7 @@ import hjson
|
||||
from typing import Dict
|
||||
from datetime import datetime
|
||||
# ---
|
||||
from cvttpy_tools.config import Config
|
||||
from cvttpy_tools.base.config import Config
|
||||
|
||||
|
||||
def load_config(config_path: str) -> Config:
|
||||
@@ -2,7 +2,7 @@ import os
|
||||
import glob
|
||||
from typing import Dict, List, Tuple
|
||||
# ---
|
||||
from cvttpy_tools.config import Config
|
||||
from cvttpy_tools.base.config import Config
|
||||
# ---
|
||||
from cvttpy_trading.trading.instrument import ExchangeInstrument
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
aiohttp>=3.8.4
|
||||
aiosignal>=1.3.1
|
||||
async-timeout>=4.0.2
|
||||
attrs>=21.2.0
|
||||
beautifulsoup4>=4.10.0
|
||||
black>=23.3.0
|
||||
flake8>=6.0.0
|
||||
certifi>=2020.6.20
|
||||
chardet>=4.0.0
|
||||
charset-normalizer>=3.1.0
|
||||
click>=8.0.3
|
||||
colorama>=0.4.4
|
||||
configobj>=5.0.6
|
||||
cryptography>=3.4.8
|
||||
distro>=1.7.0
|
||||
docker>=5.0.3
|
||||
dockerpty>=0.4.1
|
||||
docopt>=0.6.2
|
||||
eyeD3>=0.8.10
|
||||
filelock>=3.6.0
|
||||
frozenlist>=1.3.3
|
||||
grpcio>=1.30.2
|
||||
hjson>=3.0.2
|
||||
html5lib>=1.1
|
||||
httplib2>=0.20.2
|
||||
idna>=3.3
|
||||
ipython>=8.18.1
|
||||
ipywidgets>=8.1.1
|
||||
ifaddr>=0.1.7
|
||||
IMDbPY>=2021.4.18
|
||||
ipykernel>=6.29.5
|
||||
jeepney>=0.7.1
|
||||
jsonschema>=3.2.0
|
||||
jupyter>=1.0.0
|
||||
keyring>=23.5.0
|
||||
launchpadlib>=1.10.16
|
||||
lazr.restfulclient>=0.14.4
|
||||
lazr.uri>=1.0.6
|
||||
lxml>=4.8.0
|
||||
Mako>=1.1.3
|
||||
Markdown>=3.3.6
|
||||
MarkupSafe>=2.0.1
|
||||
matplotlib>=3.10.3
|
||||
more-itertools>=8.10.0
|
||||
multidict>=6.0.4
|
||||
mypy>=0.942
|
||||
mypy-extensions>=0.4.3
|
||||
nbformat>=5.10.2
|
||||
netaddr>=0.8.0
|
||||
######### netifaces>=0.11.0
|
||||
numpy>=1.26.4,<2.3.0
|
||||
oauthlib>=3.2.0
|
||||
packaging>=23.1
|
||||
pandas>=2.2.3
|
||||
pathspec>=0.11.1
|
||||
pexpect>=4.8.0
|
||||
Pillow>=9.0.1
|
||||
platformdirs>=3.2.0
|
||||
plotly>=5.19.0
|
||||
protobuf>=3.12.4
|
||||
psutil>=5.9.0
|
||||
ptyprocess>=0.7.0
|
||||
pycurl>=7.44.1
|
||||
pyelftools>=0.27
|
||||
Pygments>=2.11.2
|
||||
pyparsing>=2.4.7
|
||||
pyrsistent>=0.18.1
|
||||
python-debian>=0.1.43 #+ubuntu1.1
|
||||
python-dotenv>=0.19.2
|
||||
python-magic>=0.4.24
|
||||
python-xlib>=0.29
|
||||
pyxdg>=0.27
|
||||
PyYAML>=6.0
|
||||
reportlab>=3.6.8
|
||||
requests>=2.25.1
|
||||
requests-file>=1.5.1
|
||||
scipy<1.13.0
|
||||
seaborn>=0.13.2
|
||||
SecretStorage>=3.3.1
|
||||
setproctitle>=1.2.2
|
||||
simpleeval>=1.0.3
|
||||
six>=1.16.0
|
||||
soupsieve>=2.3.1
|
||||
ssh-import-id>=5.11
|
||||
statsmodels>=0.14.4
|
||||
texttable>=1.6.4
|
||||
tldextract>=3.1.2
|
||||
tomli>=1.2.2
|
||||
######## typed-ast>=1.4.3
|
||||
types-aiofiles>=0.1
|
||||
types-annoy>=1.17
|
||||
types-appdirs>=1.4
|
||||
types-atomicwrites>=1.4
|
||||
types-aws-xray-sdk>=2.8
|
||||
types-babel>=2.9
|
||||
types-backports-abc>=0.5
|
||||
types-backports.ssl-match-hostname>=3.7
|
||||
types-beautifulsoup4>=4.10
|
||||
types-bleach>=4.1
|
||||
types-boto>=2.49
|
||||
types-braintree>=4.11
|
||||
types-cachetools>=4.2
|
||||
types-caldav>=0.8
|
||||
types-certifi>=2020.4
|
||||
types-characteristic>=14.3
|
||||
types-chardet>=4.0
|
||||
types-click>=7.1
|
||||
types-click-spinner>=0.1
|
||||
types-colorama>=0.4
|
||||
types-commonmark>=0.9
|
||||
types-contextvars>=0.1
|
||||
types-croniter>=1.0
|
||||
types-cryptography>=3.3
|
||||
types-dataclasses>=0.1
|
||||
types-dateparser>=1.0
|
||||
types-DateTimeRange>=0.1
|
||||
types-decorator>=0.1
|
||||
types-Deprecated>=1.2
|
||||
types-docopt>=0.6
|
||||
types-docutils>=0.17
|
||||
types-editdistance>=0.5
|
||||
types-emoji>=1.2
|
||||
types-entrypoints>=0.3
|
||||
types-enum34>=1.1
|
||||
types-filelock>=3.2
|
||||
types-first>=2.0
|
||||
types-Flask>=1.1
|
||||
types-freezegun>=1.1
|
||||
types-frozendict>=0.1
|
||||
types-futures>=3.3
|
||||
types-html5lib>=1.1
|
||||
types-httplib2>=0.19
|
||||
types-humanfriendly>=9.2
|
||||
types-ipaddress>=1.0
|
||||
types-itsdangerous>=1.1
|
||||
types-JACK-Client>=0.1
|
||||
types-Jinja2>=2.11
|
||||
types-jmespath>=0.10
|
||||
types-jsonschema>=3.2
|
||||
types-Markdown>=3.3
|
||||
types-MarkupSafe>=1.1
|
||||
types-mock>=4.0
|
||||
types-mypy-extensions>=0.4
|
||||
types-mysqlclient>=2.0
|
||||
types-oauthlib>=3.1
|
||||
types-orjson>=3.6
|
||||
types-paramiko>=2.7
|
||||
types-Pillow>=8.3
|
||||
types-polib>=1.1
|
||||
types-prettytable>=2.1
|
||||
types-protobuf>=3.17
|
||||
types-psutil>=5.8
|
||||
types-psycopg2>=2.9
|
||||
types-pyaudio>=0.2
|
||||
types-pycurl>=0.1
|
||||
types-pyfarmhash>=0.2
|
||||
types-Pygments>=2.9
|
||||
types-PyMySQL>=1.0
|
||||
types-pyOpenSSL>=20.0
|
||||
types-pyRFC3339>=0.1
|
||||
types-pysftp>=0.2
|
||||
types-pytest-lazy-fixture>=0.6
|
||||
types-python-dateutil>=2.8
|
||||
types-python-gflags>=3.1
|
||||
types-python-nmap>=0.6
|
||||
types-python-slugify>=5.0
|
||||
types-pytz>=2021.1
|
||||
types-pyvmomi>=7.0
|
||||
types-PyYAML>=5.4
|
||||
types-redis>=3.5
|
||||
types-requests>=2.25
|
||||
types-retry>=0.9
|
||||
types-seaborn>0.13.2
|
||||
types-selenium>=3.141
|
||||
types-Send2Trash>=1.8
|
||||
types-setuptools>=57.4
|
||||
types-simplejson>=3.17
|
||||
types-singledispatch>=3.7
|
||||
types-six>=1.16
|
||||
types-slumber>=0.7
|
||||
types-stripe>=2.59
|
||||
types-tabulate>=0.8
|
||||
types-termcolor>=1.1
|
||||
types-toml>=0.10
|
||||
types-toposort>=1.6
|
||||
types-ttkthemes>=3.2
|
||||
types-typed-ast>=1.4
|
||||
types-tzlocal>=0.1
|
||||
types-ujson>=0.1
|
||||
types-vobject>=0.9
|
||||
types-waitress>=0.1
|
||||
types-Werkzeug>=1.0
|
||||
types-xxhash>=2.0
|
||||
typing-extensions>=3.10.0.2
|
||||
Unidecode>=1.3.3
|
||||
urllib3>=1.26.5
|
||||
wadllib>=1.3.6
|
||||
webencodings>=0.5.1
|
||||
websocket-client>=1.2.3
|
||||
yarl>=1.9.1
|
||||
zipp>=1.0.0
|
||||
@@ -4,9 +4,9 @@ import os
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
# ---
|
||||
from cvttpy_tools.app import App
|
||||
from cvttpy_tools.base import NamedObject
|
||||
from cvttpy_tools.config import CvttAppConfig
|
||||
from cvttpy_tools.base.app import App
|
||||
from cvttpy_tools.base.base import NamedObject
|
||||
from cvttpy_tools.base.config import CvttAppConfig
|
||||
|
||||
# ---
|
||||
from cvttpy_trading.trading.instrument import ExchangeInstrument
|
||||
+1
-1
@@ -182,7 +182,7 @@
|
||||
"import os\n",
|
||||
"import importlib\n",
|
||||
"\n",
|
||||
"from cvttpy_tools.config import Config\n",
|
||||
"from cvttpy_tools.base.config import Config\n",
|
||||
"\n",
|
||||
"def load_config_from_file() -> Optional[Dict]:\n",
|
||||
" global DB_TABLE_NAME\n",
|
||||
@@ -1,43 +0,0 @@
|
||||
{
|
||||
"market_data_loading": {
|
||||
"CRYPTO": {
|
||||
"data_directory": "./data/crypto",
|
||||
"db_table_name": "md_1min_bars",
|
||||
"instrument_id_pfx": "PAIR-",
|
||||
},
|
||||
"EQUITY": {
|
||||
"data_directory": "./data/equity",
|
||||
"db_table_name": "md_1min_bars",
|
||||
"instrument_id_pfx": "STOCK-",
|
||||
}
|
||||
},
|
||||
|
||||
# ====== Funding ======
|
||||
"funding_per_pair": 2000.0,
|
||||
# ====== Trading Parameters ======
|
||||
"stat_model_price": "close",
|
||||
"execution_price": {
|
||||
"column": "vwap",
|
||||
"shift": 1,
|
||||
},
|
||||
"dis-equilibrium_open_trshld": 2.0,
|
||||
"dis-equilibrium_close_trshld": 0.5,
|
||||
"training_size": 120,
|
||||
"model_class": "pairs_trading.lib.pt_strategy.models.OLSModel",
|
||||
"model_data_policy_class": "pairs_trading.lib.pt_strategy.model_data_policy.ExpandingWindowDataPolicy",
|
||||
|
||||
# ====== Stop Conditions ======
|
||||
"stop_close_conditions": {
|
||||
"profit": 2.0,
|
||||
"loss": -0.5
|
||||
}
|
||||
|
||||
# ====== End of Session Closeout ======
|
||||
"close_outstanding_positions": true,
|
||||
# "close_outstanding_positions": false,
|
||||
"trading_hours": {
|
||||
"timezone": "America/New_York",
|
||||
"begin_session": "7:30:00",
|
||||
"end_session": "18:30:00",
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"market_data_loading": {
|
||||
"CRYPTO": {
|
||||
"data_directory": "./data/crypto",
|
||||
"db_table_name": "md_1min_bars",
|
||||
"instrument_id_pfx": "PAIR-",
|
||||
},
|
||||
"EQUITY": {
|
||||
"data_directory": "./data/equity",
|
||||
"db_table_name": "md_1min_bars",
|
||||
"instrument_id_pfx": "STOCK-",
|
||||
}
|
||||
},
|
||||
|
||||
# ====== Funding ======
|
||||
"funding_per_pair": 2000.0,
|
||||
# ====== Trading Parameters ======
|
||||
"stat_model_price": "close",
|
||||
"execution_price": {
|
||||
"column": "vwap",
|
||||
"shift": 1,
|
||||
},
|
||||
"dis-equilibrium_open_trshld": 1.75,
|
||||
"dis-equilibrium_close_trshld": 0.9,
|
||||
"model_class": "pairs_trading.lib.pt_strategy.models.OLSModel",
|
||||
|
||||
# "model_data_policy_class": "pairs_trading.lib.pt_strategy.model_data_policy.EGOptimizedWndDataPolicy",
|
||||
# "model_data_policy_class": "pairs_trading.lib.pt_strategy.model_data_policy.ADFOptimizedWndDataPolicy",
|
||||
"model_data_policy_class": "pairs_trading.lib.pt_strategy.model_data_policy.JohansenOptdWndDataPolicy",
|
||||
"min_training_size": 60,
|
||||
"max_training_size": 150,
|
||||
|
||||
# ====== Stop Conditions ======
|
||||
"stop_close_conditions": {
|
||||
"profit": 2.0,
|
||||
"loss": -0.5
|
||||
}
|
||||
|
||||
# ====== End of Session Closeout ======
|
||||
"close_outstanding_positions": true,
|
||||
# "close_outstanding_positions": false,
|
||||
"trading_hours": {
|
||||
"timezone": "America/New_York",
|
||||
"begin_session": "7:30:00",
|
||||
"end_session": "18:30:00",
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"market_data_loading": {
|
||||
"CRYPTO": {
|
||||
"data_directory": "./data/crypto",
|
||||
"db_table_name": "md_1min_bars",
|
||||
"instrument_id_pfx": "PAIR-",
|
||||
},
|
||||
"EQUITY": {
|
||||
"data_directory": "./data/equity",
|
||||
"db_table_name": "md_1min_bars",
|
||||
"instrument_id_pfx": "STOCK-",
|
||||
}
|
||||
},
|
||||
|
||||
# ====== Funding ======
|
||||
"funding_per_pair": 2000.0,
|
||||
# ====== Trading Parameters ======
|
||||
"stat_model_price": "close",
|
||||
"execution_price": {
|
||||
"column": "vwap",
|
||||
"shift": 1,
|
||||
},
|
||||
"dis-equilibrium_open_trshld": 1.75,
|
||||
"dis-equilibrium_close_trshld": 0.9,
|
||||
"model_class": "pairs_trading.lib.pt_strategy.models.OLSModel",
|
||||
|
||||
"training_size": 120,
|
||||
"model_data_policy_class": "pairs_trading.lib.pt_strategy.model_data_policy.RollingWindowDataPolicy",
|
||||
# "model_data_policy_class": "pairs_trading.lib.pt_strategy.model_data_policy.OptimizedWindowDataPolicy",
|
||||
# "min_training_size": 60,
|
||||
# "max_training_size": 150,
|
||||
|
||||
# ====== Stop Conditions ======
|
||||
"stop_close_conditions": {
|
||||
"profit": 2.0,
|
||||
"loss": -0.5
|
||||
}
|
||||
|
||||
# ====== End of Session Closeout ======
|
||||
"close_outstanding_positions": true,
|
||||
# "close_outstanding_positions": false,
|
||||
"trading_hours": {
|
||||
"timezone": "America/New_York",
|
||||
"begin_session": "7:30:00",
|
||||
"end_session": "18:30:00",
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"market_data_loading": {
|
||||
"CRYPTO": {
|
||||
"data_directory": "./data/crypto",
|
||||
"db_table_name": "md_1min_bars",
|
||||
"instrument_id_pfx": "PAIR-",
|
||||
},
|
||||
"EQUITY": {
|
||||
"data_directory": "./data/equity",
|
||||
"db_table_name": "md_1min_bars",
|
||||
"instrument_id_pfx": "STOCK-",
|
||||
}
|
||||
},
|
||||
|
||||
# ====== Funding ======
|
||||
"funding_per_pair": 2000.0,
|
||||
|
||||
# ====== Trading Parameters ======
|
||||
"stat_model_price": "close", # "vwap"
|
||||
"execution_price": {
|
||||
"column": "vwap",
|
||||
"shift": 1,
|
||||
},
|
||||
"dis-equilibrium_open_trshld": 1.75,
|
||||
"dis-equilibrium_close_trshld": 1.0,
|
||||
"model_class": "pairs_trading.lib.pt_strategy.models.VECMModel",
|
||||
|
||||
"training_size": 120,
|
||||
"model_data_policy_class": "pairs_trading.lib.pt_strategy.model_data_policy.RollingWindowDataPolicy",
|
||||
# "model_data_policy_class": "pairs_trading.lib.pt_strategy.model_data_policy.OptimizedWindowDataPolicy",
|
||||
# "min_training_size": 60,
|
||||
# "max_training_size": 150,
|
||||
|
||||
# ====== Stop Conditions ======
|
||||
"stop_close_conditions": {
|
||||
"profit": 2.0,
|
||||
"loss": -0.5
|
||||
}
|
||||
|
||||
# ====== End of Session Closeout ======
|
||||
"close_outstanding_positions": true,
|
||||
# "close_outstanding_positions": false,
|
||||
"trading_hours": {
|
||||
"timezone": "America/New_York",
|
||||
"begin_session": "7:30:00",
|
||||
"end_session": "18:30:00",
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "single-day-title",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Single-Day Backtest Result Analysis\n",
|
||||
"\n",
|
||||
"This notebook analyzes the result of one single-day backtest stored in a SQLite database. Development is staged; Step 1 only selects the database file that later sections will read.\n",
|
||||
"\n",
|
||||
"Input assumptions for Step 1:\n",
|
||||
"\n",
|
||||
"- The default data directory is `data/` at the repository root.\n",
|
||||
"- SQLite result files usually use `.db`, `.sqlite`, or `.sqlite3` extensions.\n",
|
||||
"- The directory can be changed interactively if the result file lives elsewhere."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "imports-and-paths",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from pathlib import Path\n",
|
||||
"import importlib\n",
|
||||
"import sys\n",
|
||||
"\n",
|
||||
"from IPython.display import display\n",
|
||||
"import ipywidgets as widgets\n",
|
||||
"import pandas as pd\n",
|
||||
"import panel as pn\n",
|
||||
"\n",
|
||||
"START_DIR = Path.cwd().resolve()\n",
|
||||
"for candidate in (START_DIR, *START_DIR.parents):\n",
|
||||
" if (candidate / \"scripts\" / \"spbt_day.py\").exists():\n",
|
||||
" if str(candidate) not in sys.path:\n",
|
||||
" sys.path.insert(0, str(candidate))\n",
|
||||
" break\n",
|
||||
"\n",
|
||||
"import scripts.spbt_day as spbt_day\n",
|
||||
"\n",
|
||||
"spbt_day = importlib.reload(spbt_day)\n",
|
||||
"pn.extension(\"tabulator\", \"plotly\")\n",
|
||||
"\n",
|
||||
"ANALYZE_BUTTON_COLUMN = spbt_day.ANALYZE_BUTTON_COLUMN\n",
|
||||
"SELECTED_PAIR_EXECUTION_DISPLAY_COLUMNS = spbt_day.SELECTED_PAIR_EXECUTION_DISPLAY_COLUMNS\n",
|
||||
"add_total_pnl = spbt_day.add_total_pnl\n",
|
||||
"calculate_pair_theo_executions = spbt_day.calculate_pair_theo_executions\n",
|
||||
"calculate_ranked_pairs_theo_ret = spbt_day.calculate_ranked_pairs_theo_ret\n",
|
||||
"create_database_file_selector = spbt_day.create_database_file_selector\n",
|
||||
"create_pair_theo_ret_analyze_grid = spbt_day.create_pair_theo_ret_analyze_grid\n",
|
||||
"create_pair_trades_market_plot = spbt_day.create_pair_trades_market_plot\n",
|
||||
"create_selected_pair_executions_grid = spbt_day.create_selected_pair_executions_grid\n",
|
||||
"create_total_pnl_histogram = spbt_day.create_total_pnl_histogram\n",
|
||||
"find_repo_root = spbt_day.find_repo_root\n",
|
||||
"format_pair_name_for_display = spbt_day.format_pair_name_for_display\n",
|
||||
"format_pair_names_for_display = spbt_day.format_pair_names_for_display\n",
|
||||
"format_pair_theo_ret_for_analyze_grid = spbt_day.format_pair_theo_ret_for_analyze_grid\n",
|
||||
"infer_trading_day_start_ns = spbt_day.infer_trading_day_start_ns\n",
|
||||
"load_selector_pair_rankings = spbt_day.load_selector_pair_rankings\n",
|
||||
"load_pair_market_data = spbt_day.load_pair_market_data\n",
|
||||
"load_trading_instructions = spbt_day.load_trading_instructions\n",
|
||||
"pair_name_from_analyze_event = spbt_day.pair_name_from_analyze_event\n",
|
||||
"show_interactive_dataframe = spbt_day.show_interactive_dataframe\n",
|
||||
"\n",
|
||||
"REPO_ROOT = find_repo_root()\n",
|
||||
"DEFAULT_DATA_DIR = REPO_ROOT / \"data\"\n",
|
||||
"\n",
|
||||
"REPO_ROOT, DEFAULT_DATA_DIR"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "database-file-selector",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"db_selector = create_database_file_selector(\n",
|
||||
" default_data_dir=DEFAULT_DATA_DIR,\n",
|
||||
" repo_root=REPO_ROOT,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"display(db_selector.widget)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "selected-database-helpers",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"selected_database_path = db_selector.selected_database_path\n",
|
||||
"connect_selected_database = db_selector.connect_selected_database\n",
|
||||
"\n",
|
||||
"# Later notebook sections can call selected_database_path() or connect_selected_database()."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "selector-pair-rankings-context",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Selector Pair Rankings\n",
|
||||
"\n",
|
||||
"Load `selector_pairs.pair_name` and `selector_pairs.mr_score` from the selected SQLite database. The JSON field `mr_score.final` is parsed as a numeric score and ranked descending with dense ranks, so tied scores share the same rank and the next distinct score gets the next rank.\n",
|
||||
"\n",
|
||||
"Rows with missing, malformed, non-numeric, or non-finite `mr_score.final` values are preserved, sorted after ranked rows, and marked in `mr_score_parse_status`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "load-selector-pair-rankings",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"conn = connect_selected_database()\n",
|
||||
"try:\n",
|
||||
" selector_pair_rankings = load_selector_pair_rankings(conn)\n",
|
||||
"finally:\n",
|
||||
" conn.close()\n",
|
||||
"\n",
|
||||
"selector_pair_rankings_display = format_pair_names_for_display(\n",
|
||||
" selector_pair_rankings[[\"pair_rank\", \"pair_name\", \"mr_score_final\"]]\n",
|
||||
")\n",
|
||||
"with pd.option_context(\"display.max_rows\", None):\n",
|
||||
" display(selector_pair_rankings_display)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "theoretical-return-context",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Theoretical Return by Pair\n",
|
||||
"\n",
|
||||
"Load `trading_instructions` and calculate theoretical return for each ranked pair. Each pair starts from a fixed `$10,000` theoretical USD base. `TARGET` trades from the current theoretical position to the new target position, where target size is `10000 * strength / reference_price`; `CLOSE` liquidates the open position at the close row's `reference_price`; `HOLD` is ignored.\n",
|
||||
"\n",
|
||||
"`MIN_TARGET_STRENGTH_CHANGE_PCTG` can be raised above `0.0` to skip `TARGET` updates whose absolute percentage strength change is smaller than the threshold since the position was acquired. `num_trades` counts asset-level theoretical trades caused by effective `TARGET` and `CLOSE` rows. `realized_pnl` and `unrealized_pnl` are percentage returns relative to `$10,000`. The displayed dataframe is sorted by total return (`realized_pnl + unrealized_pnl`) ascending."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "target-change-threshold-input",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"min_target_change_input = widgets.FloatText(\n",
|
||||
" value=0.0,\n",
|
||||
" description=\"Mininal TARGET change (%)\",\n",
|
||||
" step=1.0,\n",
|
||||
" layout=widgets.Layout(width=\"420px\"),\n",
|
||||
" style={\"description_width\": \"190px\"},\n",
|
||||
")\n",
|
||||
"display(min_target_change_input)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "load-trading-instructions",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"conn = connect_selected_database()\n",
|
||||
"try:\n",
|
||||
" trading_instructions = load_trading_instructions(conn)\n",
|
||||
"finally:\n",
|
||||
" conn.close()\n",
|
||||
"\n",
|
||||
"print(f\"Loaded {len(trading_instructions):,} trading instruction rows.\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "calculate-pair-theoretical-returns",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"MIN_TARGET_STRENGTH_CHANGE_PCTG = float(min_target_change_input.value)\n",
|
||||
"\n",
|
||||
"pair_theo_ret = add_total_pnl(\n",
|
||||
" calculate_ranked_pairs_theo_ret(\n",
|
||||
" selector_pair_rankings,\n",
|
||||
" trading_instructions,\n",
|
||||
" min_pctg_change=MIN_TARGET_STRENGTH_CHANGE_PCTG,\n",
|
||||
" )\n",
|
||||
").sort_values(\n",
|
||||
" [\"total_pnl\", \"pair_name\"],\n",
|
||||
" ascending=[True, True],\n",
|
||||
" kind=\"mergesort\",\n",
|
||||
").drop(columns=\"total_pnl\").reset_index(drop=True)\n",
|
||||
"\n",
|
||||
"pair_theo_ret_display = format_pair_theo_ret_for_analyze_grid(pair_theo_ret)\n",
|
||||
"pair_theo_ret_grid = create_pair_theo_ret_analyze_grid(\n",
|
||||
" pair_theo_ret_display,\n",
|
||||
" height=520,\n",
|
||||
")\n",
|
||||
"display(pair_theo_ret_grid)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "theoretical-return-histogram-context",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Total Theoretical Return Distribution\n",
|
||||
"\n",
|
||||
"Plot the distribution of total theoretical return, calculated as `realized_pnl + unrealized_pnl`. Plotly chooses histogram bins automatically."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "plot-total-theoretical-return-histogram",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"total_pnl_histogram = create_total_pnl_histogram(pair_theo_ret)\n",
|
||||
"total_pnl_histogram_pane = pn.pane.Plotly(\n",
|
||||
" total_pnl_histogram,\n",
|
||||
" height=360,\n",
|
||||
" sizing_mode=\"stretch_width\",\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"display(total_pnl_histogram_pane)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "individual-pair-analysis-context",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Individual Pair Analysis\n",
|
||||
"\n",
|
||||
"Click the Analyze button in the Pair TheoRet grid to load detailed follow-up analysis for that row. The selected-pair execution table and market/trade chart are not calculated until an Analyze button is clicked."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "individual-pair-analysis",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"selected_pair_name = None\n",
|
||||
"selected_pair_theo_executions = pd.DataFrame()\n",
|
||||
"selected_pair_theo_executions_display = pd.DataFrame(\n",
|
||||
" columns=SELECTED_PAIR_EXECUTION_DISPLAY_COLUMNS\n",
|
||||
")\n",
|
||||
"selected_pair_market_data = pd.DataFrame()\n",
|
||||
"selected_pair_market_trades_plot = None\n",
|
||||
"\n",
|
||||
"selected_pair_message = pn.pane.Markdown(\n",
|
||||
" \"Click Analyze in the Pair TheoRet grid to load individual-pair details.\"\n",
|
||||
")\n",
|
||||
"selected_pair_theo_executions_grid = create_selected_pair_executions_grid(\n",
|
||||
" selected_pair_theo_executions_display,\n",
|
||||
" height=360,\n",
|
||||
")\n",
|
||||
"selected_pair_market_trades_plot_pane = pn.pane.Plotly(\n",
|
||||
" None,\n",
|
||||
" height=520,\n",
|
||||
" sizing_mode=\"stretch_width\",\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def analyze_pair_click(event):\n",
|
||||
" global selected_pair_name\n",
|
||||
" global selected_pair_theo_executions\n",
|
||||
" global selected_pair_theo_executions_display\n",
|
||||
" global selected_pair_market_data\n",
|
||||
" global selected_pair_market_trades_plot\n",
|
||||
"\n",
|
||||
" try:\n",
|
||||
" selected_pair_name = pair_name_from_analyze_event(pair_theo_ret_grid, event)\n",
|
||||
" selected_pair_message.object = (\n",
|
||||
" f\"Selected pair: **{format_pair_name_for_display(selected_pair_name)}**\"\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" selected_pair_theo_executions = calculate_pair_theo_executions(\n",
|
||||
" selected_pair_name,\n",
|
||||
" trading_instructions,\n",
|
||||
" min_pctg_change=MIN_TARGET_STRENGTH_CHANGE_PCTG,\n",
|
||||
" )\n",
|
||||
" selected_pair_theo_executions_display = selected_pair_theo_executions.reindex(\n",
|
||||
" columns=SELECTED_PAIR_EXECUTION_DISPLAY_COLUMNS\n",
|
||||
" )\n",
|
||||
" selected_pair_theo_executions_grid.value = selected_pair_theo_executions_display\n",
|
||||
"\n",
|
||||
" trading_day_start_ns = infer_trading_day_start_ns(trading_instructions)\n",
|
||||
" conn = connect_selected_database()\n",
|
||||
" try:\n",
|
||||
" selected_pair_market_data = load_pair_market_data(\n",
|
||||
" conn,\n",
|
||||
" selected_pair_name,\n",
|
||||
" trading_day_start_ns=trading_day_start_ns,\n",
|
||||
" )\n",
|
||||
" finally:\n",
|
||||
" conn.close()\n",
|
||||
"\n",
|
||||
" selected_pair_market_trades_plot = create_pair_trades_market_plot(\n",
|
||||
" selected_pair_name,\n",
|
||||
" selected_pair_market_data,\n",
|
||||
" selected_pair_theo_executions,\n",
|
||||
" )\n",
|
||||
" selected_pair_market_trades_plot_pane.object = selected_pair_market_trades_plot\n",
|
||||
" except Exception as exc:\n",
|
||||
" selected_pair_message.object = f\"**Error:** {exc}\"\n",
|
||||
" selected_pair_market_trades_plot_pane.object = None\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"pair_theo_ret_grid.on_click(analyze_pair_click, column=ANALYZE_BUTTON_COLUMN)\n",
|
||||
"\n",
|
||||
"display(\n",
|
||||
" pn.Column(\n",
|
||||
" selected_pair_message,\n",
|
||||
" \"### Theoretical Executions\",\n",
|
||||
" selected_pair_theo_executions_grid,\n",
|
||||
" \"### Trades on Market Data\",\n",
|
||||
" selected_pair_market_trades_plot_pane,\n",
|
||||
" )\n",
|
||||
")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "python3.12-venv (3.12.13.final.0)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.13"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
"""Panel application for single-day SPBT result analysis."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
import pandas as pd
|
||||
import panel as pn
|
||||
|
||||
|
||||
APP_DIR = Path(__file__).resolve().parent
|
||||
REPO_ROOT = APP_DIR.parent
|
||||
if str(REPO_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(REPO_ROOT))
|
||||
|
||||
from scripts import spbt_day
|
||||
|
||||
|
||||
pn.extension("tabulator", "plotly")
|
||||
|
||||
|
||||
PAIR_THEO_RET_SORT_COLUMNS = ["total_pnl", "pair_name"]
|
||||
PAIR_THEO_RET_DISPLAY_DROP_COLUMNS = ["total_pnl"]
|
||||
APP_TITLE = "SPBT Day Analysis"
|
||||
APP_ACCENT_COLOR = "#226c67"
|
||||
APP_HEADER_COLOR = "#184c47"
|
||||
|
||||
|
||||
class SpbtDayPanelApp:
|
||||
"""Stateful Panel UI for single-day SPBT analysis."""
|
||||
|
||||
def __init__(self, repo_root: Path | None = None) -> None:
|
||||
self.repo_root = (repo_root or spbt_day.find_repo_root(REPO_ROOT)).resolve()
|
||||
self.selector_pair_rankings = pd.DataFrame()
|
||||
self.trading_instructions = pd.DataFrame()
|
||||
self.pair_theo_ret = pd.DataFrame()
|
||||
self.selected_pair_theo_executions = pd.DataFrame()
|
||||
self.selected_pair_name: str | None = None
|
||||
self.min_pctg_change = 0.0
|
||||
|
||||
self.directory_input = pn.widgets.TextInput(
|
||||
label="Directory",
|
||||
value=str(self.repo_root / "data"),
|
||||
)
|
||||
self.show_all_files = pn.widgets.Checkbox(label="Show all files", value=False)
|
||||
self.refresh_button = pn.widgets.Button(label="Refresh")
|
||||
self.file_select = pn.widgets.Select(
|
||||
label="SQLite result file",
|
||||
options={},
|
||||
width=360,
|
||||
)
|
||||
self.min_pctg_change_input = pn.widgets.FloatInput(
|
||||
label="Mininal TARGET change (%)",
|
||||
value=0.0,
|
||||
step=1.0,
|
||||
width=220,
|
||||
)
|
||||
self.calculate_button = pn.widgets.Button(
|
||||
label="Calculate",
|
||||
color="primary",
|
||||
width=110,
|
||||
)
|
||||
|
||||
self.status = pn.pane.Markdown("")
|
||||
self.pair_theo_ret_table = spbt_day.create_pair_theo_ret_analyze_grid(
|
||||
pd.DataFrame(),
|
||||
height=420,
|
||||
)
|
||||
self.total_pnl_histogram = pn.pane.Plotly(
|
||||
None,
|
||||
height=360,
|
||||
sizing_mode="stretch_width",
|
||||
)
|
||||
self.selected_pair_message = pn.pane.Markdown(
|
||||
"Click Analyze in the Pair TheoRet grid to load individual-pair details."
|
||||
)
|
||||
self.selected_pair_executions_table = spbt_day.create_selected_pair_executions_grid(
|
||||
height=320,
|
||||
)
|
||||
self.selected_pair_market_plot = pn.pane.Plotly(
|
||||
None,
|
||||
height=520,
|
||||
sizing_mode="stretch_width",
|
||||
)
|
||||
|
||||
self.refresh_button.on_click(self.refresh_files)
|
||||
self.calculate_button.on_click(self.calculate)
|
||||
self.directory_input.param.watch(self.refresh_files, "value")
|
||||
self.show_all_files.param.watch(self.refresh_files, "value")
|
||||
self.pair_theo_ret_table.on_click(
|
||||
self.analyze_pair_click,
|
||||
column=spbt_day.ANALYZE_BUTTON_COLUMN,
|
||||
)
|
||||
self.refresh_files()
|
||||
|
||||
def set_status(self, message: str, *, error: bool = False) -> None:
|
||||
"""Update visible status text."""
|
||||
prefix = "**Error:** " if error else ""
|
||||
self.status.object = f"{prefix}{message}" if message else ""
|
||||
|
||||
def selected_database_path(self) -> Path:
|
||||
"""Return the selected result database path."""
|
||||
if not self.file_select.value:
|
||||
raise ValueError("Select a SQLite result file before calculating.")
|
||||
db_path = Path(str(self.file_select.value)).resolve()
|
||||
if not db_path.exists():
|
||||
raise FileNotFoundError(f"Selected database does not exist: {db_path}")
|
||||
if not db_path.is_file():
|
||||
raise ValueError(f"Selected database path is not a file: {db_path}")
|
||||
return db_path
|
||||
|
||||
def refresh_files(self, *_events: Any) -> None:
|
||||
"""Refresh selectable SQLite files from the configured directory."""
|
||||
try:
|
||||
directory = spbt_day.normalize_directory(
|
||||
self.directory_input.value,
|
||||
self.repo_root,
|
||||
)
|
||||
candidates = spbt_day.list_candidate_files(
|
||||
directory,
|
||||
show_all=self.show_all_files.value,
|
||||
)
|
||||
except Exception as exc:
|
||||
self.file_select.options = {}
|
||||
self.file_select.value = None
|
||||
self.set_status(str(exc), error=True)
|
||||
return
|
||||
|
||||
options = {path.name: str(path) for path in candidates}
|
||||
previous_value = self.file_select.value
|
||||
self.file_select.options = options
|
||||
if previous_value in options.values():
|
||||
self.file_select.value = previous_value
|
||||
elif options:
|
||||
self.file_select.value = next(iter(options.values()))
|
||||
else:
|
||||
self.file_select.value = None
|
||||
|
||||
if options:
|
||||
self.set_status(f"Found {len(options):,} file(s) in {directory}.")
|
||||
else:
|
||||
self.set_status(f"No selectable files found in {directory}.")
|
||||
|
||||
def calculate(self, *_events: Any) -> None:
|
||||
"""Load selected data and calculate all-pair TheoRet."""
|
||||
self.calculate_button.loading = True
|
||||
try:
|
||||
db_path = self.selected_database_path()
|
||||
self.min_pctg_change = float(self.min_pctg_change_input.value)
|
||||
|
||||
conn = spbt_day.connect_sqlite_read_only(db_path)
|
||||
try:
|
||||
self.selector_pair_rankings = spbt_day.load_selector_pair_rankings(conn)
|
||||
self.trading_instructions = spbt_day.load_trading_instructions(conn)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
self.pair_theo_ret = (
|
||||
spbt_day.add_total_pnl(
|
||||
spbt_day.calculate_ranked_pairs_theo_ret(
|
||||
self.selector_pair_rankings,
|
||||
self.trading_instructions,
|
||||
min_pctg_change=self.min_pctg_change,
|
||||
)
|
||||
)
|
||||
.sort_values(
|
||||
PAIR_THEO_RET_SORT_COLUMNS,
|
||||
ascending=[True, True],
|
||||
kind="mergesort",
|
||||
)
|
||||
.drop(columns=PAIR_THEO_RET_DISPLAY_DROP_COLUMNS)
|
||||
.reset_index(drop=True)
|
||||
)
|
||||
self.pair_theo_ret_table.value = spbt_day.format_pair_theo_ret_for_analyze_grid(
|
||||
self.pair_theo_ret
|
||||
)
|
||||
self.total_pnl_histogram.object = spbt_day.create_total_pnl_histogram(
|
||||
self.pair_theo_ret
|
||||
)
|
||||
self.clear_selected_pair_analysis()
|
||||
|
||||
self.set_status(
|
||||
f"Calculated {len(self.pair_theo_ret):,} pair row(s) from {db_path.name}."
|
||||
)
|
||||
except Exception as exc:
|
||||
self.set_status(str(exc), error=True)
|
||||
finally:
|
||||
self.calculate_button.loading = False
|
||||
|
||||
def clear_selected_pair_analysis(self) -> None:
|
||||
"""Clear individual-pair outputs until a row Analyze button is clicked."""
|
||||
self.selected_pair_name = None
|
||||
self.selected_pair_theo_executions = pd.DataFrame()
|
||||
self.selected_pair_message.object = (
|
||||
"Click Analyze in the Pair TheoRet grid to load individual-pair details."
|
||||
)
|
||||
self.selected_pair_executions_table.value = pd.DataFrame(
|
||||
columns=spbt_day.SELECTED_PAIR_EXECUTION_DISPLAY_COLUMNS
|
||||
)
|
||||
self.selected_pair_market_plot.object = None
|
||||
|
||||
def analyze_pair_click(self, event: Any) -> None:
|
||||
"""Run selected-pair analysis from a Pair TheoRet Analyze button click."""
|
||||
self.update_selected_pair(
|
||||
spbt_day.pair_name_from_analyze_event(self.pair_theo_ret_table, event)
|
||||
)
|
||||
|
||||
def analyze_pair_row(self, row: int) -> None:
|
||||
"""Run selected-pair analysis for a Pair TheoRet table row."""
|
||||
event = type("AnalyzeEvent", (), {"row": row})()
|
||||
self.analyze_pair_click(event)
|
||||
|
||||
def update_selected_pair(self, pair_name: str) -> None:
|
||||
"""Calculate selected-pair executions and market plot."""
|
||||
if self.trading_instructions.empty:
|
||||
self.clear_selected_pair_analysis()
|
||||
return
|
||||
|
||||
self.selected_pair_name = pair_name
|
||||
self.selected_pair_message.object = (
|
||||
f"Selected pair: **{spbt_day.format_pair_name_for_display(pair_name)}**"
|
||||
)
|
||||
self.selected_pair_theo_executions = spbt_day.calculate_pair_theo_executions(
|
||||
pair_name,
|
||||
self.trading_instructions,
|
||||
min_pctg_change=self.min_pctg_change,
|
||||
)
|
||||
self.selected_pair_executions_table.value = (
|
||||
self.selected_pair_theo_executions.reindex(
|
||||
columns=spbt_day.SELECTED_PAIR_EXECUTION_DISPLAY_COLUMNS
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
trading_day_start_ns = spbt_day.infer_trading_day_start_ns(
|
||||
self.trading_instructions
|
||||
)
|
||||
conn = spbt_day.connect_sqlite_read_only(self.selected_database_path())
|
||||
try:
|
||||
selected_pair_market_data = spbt_day.load_pair_market_data(
|
||||
conn,
|
||||
pair_name,
|
||||
trading_day_start_ns=trading_day_start_ns,
|
||||
)
|
||||
finally:
|
||||
conn.close()
|
||||
self.selected_pair_market_plot.object = spbt_day.create_pair_trades_market_plot(
|
||||
pair_name,
|
||||
selected_pair_market_data,
|
||||
self.selected_pair_theo_executions,
|
||||
)
|
||||
except Exception as exc:
|
||||
self.selected_pair_market_plot.object = None
|
||||
self.set_status(str(exc), error=True)
|
||||
|
||||
@property
|
||||
def view(self) -> pn.template.FastListTemplate:
|
||||
"""Return the app layout."""
|
||||
controls = pn.Column(
|
||||
"## Inputs",
|
||||
pn.Row(self.directory_input, self.refresh_button),
|
||||
self.show_all_files,
|
||||
self.file_select,
|
||||
self.min_pctg_change_input,
|
||||
self.calculate_button,
|
||||
self.status,
|
||||
width=400,
|
||||
)
|
||||
main = pn.Column(
|
||||
"## Pair TheoRet",
|
||||
self.pair_theo_ret_table,
|
||||
self.total_pnl_histogram,
|
||||
"## Individual Pair",
|
||||
self.selected_pair_message,
|
||||
"### Theoretical Executions",
|
||||
self.selected_pair_executions_table,
|
||||
"### Trades on Market Data",
|
||||
self.selected_pair_market_plot,
|
||||
)
|
||||
return pn.template.FastListTemplate(
|
||||
title=APP_TITLE,
|
||||
sidebar=[controls],
|
||||
main=[main],
|
||||
sidebar_width=430,
|
||||
accent_base_color=APP_ACCENT_COLOR,
|
||||
header_background=APP_HEADER_COLOR,
|
||||
main_layout=None,
|
||||
)
|
||||
|
||||
|
||||
app_controller = SpbtDayPanelApp()
|
||||
app = app_controller.view
|
||||
app.servable(title=APP_TITLE)
|
||||
+13
-201
@@ -1,201 +1,13 @@
|
||||
aiohttp>=3.8.4
|
||||
aiosignal>=1.3.1
|
||||
async-timeout>=4.0.2
|
||||
attrs>=21.2.0
|
||||
beautifulsoup4>=4.10.0
|
||||
black>=23.3.0
|
||||
flake8>=6.0.0
|
||||
certifi>=2020.6.20
|
||||
chardet>=4.0.0
|
||||
charset-normalizer>=3.1.0
|
||||
click>=8.0.3
|
||||
colorama>=0.4.4
|
||||
configobj>=5.0.6
|
||||
cryptography>=3.4.8
|
||||
distro>=1.7.0
|
||||
docker>=5.0.3
|
||||
dockerpty>=0.4.1
|
||||
docopt>=0.6.2
|
||||
eyeD3>=0.8.10
|
||||
filelock>=3.6.0
|
||||
frozenlist>=1.3.3
|
||||
grpcio>=1.30.2
|
||||
hjson>=3.0.2
|
||||
html5lib>=1.1
|
||||
httplib2>=0.20.2
|
||||
idna>=3.3
|
||||
ipython>=8.18.1
|
||||
ipywidgets>=8.1.1
|
||||
ifaddr>=0.1.7
|
||||
IMDbPY>=2021.4.18
|
||||
ipykernel>=6.29.5
|
||||
jeepney>=0.7.1
|
||||
jsonschema>=3.2.0
|
||||
jupyter>=1.0.0
|
||||
keyring>=23.5.0
|
||||
launchpadlib>=1.10.16
|
||||
lazr.restfulclient>=0.14.4
|
||||
lazr.uri>=1.0.6
|
||||
lxml>=4.8.0
|
||||
Mako>=1.1.3
|
||||
Markdown>=3.3.6
|
||||
MarkupSafe>=2.0.1
|
||||
matplotlib>=3.10.3
|
||||
more-itertools>=8.10.0
|
||||
multidict>=6.0.4
|
||||
mypy>=0.942
|
||||
mypy-extensions>=0.4.3
|
||||
nbformat>=5.10.2
|
||||
netaddr>=0.8.0
|
||||
######### netifaces>=0.11.0
|
||||
numpy>=1.26.4,<2.3.0
|
||||
oauthlib>=3.2.0
|
||||
packaging>=23.1
|
||||
pandas>=2.2.3
|
||||
pathspec>=0.11.1
|
||||
pexpect>=4.8.0
|
||||
Pillow>=9.0.1
|
||||
platformdirs>=3.2.0
|
||||
plotly>=5.19.0
|
||||
protobuf>=3.12.4
|
||||
psutil>=5.9.0
|
||||
ptyprocess>=0.7.0
|
||||
pycurl>=7.44.1
|
||||
pyelftools>=0.27
|
||||
Pygments>=2.11.2
|
||||
pyparsing>=2.4.7
|
||||
pyrsistent>=0.18.1
|
||||
python-debian>=0.1.43 #+ubuntu1.1
|
||||
python-dotenv>=0.19.2
|
||||
python-magic>=0.4.24
|
||||
python-xlib>=0.29
|
||||
pyxdg>=0.27
|
||||
PyYAML>=6.0
|
||||
reportlab>=3.6.8
|
||||
requests>=2.25.1
|
||||
requests-file>=1.5.1
|
||||
scipy<1.13.0
|
||||
seaborn>=0.13.2
|
||||
SecretStorage>=3.3.1
|
||||
setproctitle>=1.2.2
|
||||
simpleeval>=1.0.3
|
||||
six>=1.16.0
|
||||
soupsieve>=2.3.1
|
||||
ssh-import-id>=5.11
|
||||
statsmodels>=0.14.4
|
||||
texttable>=1.6.4
|
||||
tldextract>=3.1.2
|
||||
tomli>=1.2.2
|
||||
######## typed-ast>=1.4.3
|
||||
types-aiofiles>=0.1
|
||||
types-annoy>=1.17
|
||||
types-appdirs>=1.4
|
||||
types-atomicwrites>=1.4
|
||||
types-aws-xray-sdk>=2.8
|
||||
types-babel>=2.9
|
||||
types-backports-abc>=0.5
|
||||
types-backports.ssl-match-hostname>=3.7
|
||||
types-beautifulsoup4>=4.10
|
||||
types-bleach>=4.1
|
||||
types-boto>=2.49
|
||||
types-braintree>=4.11
|
||||
types-cachetools>=4.2
|
||||
types-caldav>=0.8
|
||||
types-certifi>=2020.4
|
||||
types-characteristic>=14.3
|
||||
types-chardet>=4.0
|
||||
types-click>=7.1
|
||||
types-click-spinner>=0.1
|
||||
types-colorama>=0.4
|
||||
types-commonmark>=0.9
|
||||
types-contextvars>=0.1
|
||||
types-croniter>=1.0
|
||||
types-cryptography>=3.3
|
||||
types-dataclasses>=0.1
|
||||
types-dateparser>=1.0
|
||||
types-DateTimeRange>=0.1
|
||||
types-decorator>=0.1
|
||||
types-Deprecated>=1.2
|
||||
types-docopt>=0.6
|
||||
types-docutils>=0.17
|
||||
types-editdistance>=0.5
|
||||
types-emoji>=1.2
|
||||
types-entrypoints>=0.3
|
||||
types-enum34>=1.1
|
||||
types-filelock>=3.2
|
||||
types-first>=2.0
|
||||
types-Flask>=1.1
|
||||
types-freezegun>=1.1
|
||||
types-frozendict>=0.1
|
||||
types-futures>=3.3
|
||||
types-html5lib>=1.1
|
||||
types-httplib2>=0.19
|
||||
types-humanfriendly>=9.2
|
||||
types-ipaddress>=1.0
|
||||
types-itsdangerous>=1.1
|
||||
types-JACK-Client>=0.1
|
||||
types-Jinja2>=2.11
|
||||
types-jmespath>=0.10
|
||||
types-jsonschema>=3.2
|
||||
types-Markdown>=3.3
|
||||
types-MarkupSafe>=1.1
|
||||
types-mock>=4.0
|
||||
types-mypy-extensions>=0.4
|
||||
types-mysqlclient>=2.0
|
||||
types-oauthlib>=3.1
|
||||
types-orjson>=3.6
|
||||
types-paramiko>=2.7
|
||||
types-Pillow>=8.3
|
||||
types-polib>=1.1
|
||||
types-prettytable>=2.1
|
||||
types-protobuf>=3.17
|
||||
types-psutil>=5.8
|
||||
types-psycopg2>=2.9
|
||||
types-pyaudio>=0.2
|
||||
types-pycurl>=0.1
|
||||
types-pyfarmhash>=0.2
|
||||
types-Pygments>=2.9
|
||||
types-PyMySQL>=1.0
|
||||
types-pyOpenSSL>=20.0
|
||||
types-pyRFC3339>=0.1
|
||||
types-pysftp>=0.2
|
||||
types-pytest-lazy-fixture>=0.6
|
||||
types-python-dateutil>=2.8
|
||||
types-python-gflags>=3.1
|
||||
types-python-nmap>=0.6
|
||||
types-python-slugify>=5.0
|
||||
types-pytz>=2021.1
|
||||
types-pyvmomi>=7.0
|
||||
types-PyYAML>=5.4
|
||||
types-redis>=3.5
|
||||
types-requests>=2.25
|
||||
types-retry>=0.9
|
||||
types-seaborn>0.13.2
|
||||
types-selenium>=3.141
|
||||
types-Send2Trash>=1.8
|
||||
types-setuptools>=57.4
|
||||
types-simplejson>=3.17
|
||||
types-singledispatch>=3.7
|
||||
types-six>=1.16
|
||||
types-slumber>=0.7
|
||||
types-stripe>=2.59
|
||||
types-tabulate>=0.8
|
||||
types-termcolor>=1.1
|
||||
types-toml>=0.10
|
||||
types-toposort>=1.6
|
||||
types-ttkthemes>=3.2
|
||||
types-typed-ast>=1.4
|
||||
types-tzlocal>=0.1
|
||||
types-ujson>=0.1
|
||||
types-vobject>=0.9
|
||||
types-waitress>=0.1
|
||||
types-Werkzeug>=1.0
|
||||
types-xxhash>=2.0
|
||||
typing-extensions>=3.10.0.2
|
||||
Unidecode>=1.3.3
|
||||
urllib3>=1.26.5
|
||||
wadllib>=1.3.6
|
||||
webencodings>=0.5.1
|
||||
websocket-client>=1.2.3
|
||||
yarl>=1.9.1
|
||||
zipp>=1.0.0
|
||||
# Interactive analysis
|
||||
ipykernel>=6.29,<7
|
||||
ipywidgets>=8.1,<9
|
||||
itables>=2.2,<3
|
||||
jupyter>=1.1,<2
|
||||
nbformat>=5.10,<6
|
||||
pandas>=2.2,<3
|
||||
panel>=1.5,<2
|
||||
plotly>=5.24,<7
|
||||
|
||||
# Verification
|
||||
nbmake>=1.5,<2
|
||||
pytest>=8,<9
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$repo_root"
|
||||
|
||||
panel serve panel/spbt_day_panel.py --show "$@"
|
||||
+1262
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,218 @@
|
||||
import importlib.util
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def load_panel_app_module():
|
||||
module_path = Path("panel/spbt_day_panel.py").resolve()
|
||||
spec = importlib.util.spec_from_file_location("spbt_day_panel_app", module_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
assert spec.loader is not None
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def create_panel_fixture_db(db_path: Path) -> None:
|
||||
trading_day_start_ns = pd.Timestamp("2026-06-17T00:00:00Z").value
|
||||
conn = sqlite3.connect(db_path)
|
||||
try:
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE selector_pairs (
|
||||
time_ns INTEGER,
|
||||
tstamp TEXT,
|
||||
pair_name TEXT,
|
||||
instrument_a TEXT,
|
||||
instrument_b TEXT,
|
||||
mr_score TEXT
|
||||
)
|
||||
"""
|
||||
)
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE trading_instructions (
|
||||
time_ns INTEGER,
|
||||
tstamp TEXT,
|
||||
book_id TEXT,
|
||||
strategy_id TEXT,
|
||||
type TEXT,
|
||||
data TEXT
|
||||
)
|
||||
"""
|
||||
)
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE ohlcv_1min (
|
||||
tstamp TEXT,
|
||||
tstamp_ns INTEGER,
|
||||
exch_acct TEXT,
|
||||
exchange_id TEXT,
|
||||
instrument_id TEXT,
|
||||
interval_sec INTEGER,
|
||||
open REAL,
|
||||
high REAL,
|
||||
low REAL,
|
||||
close REAL,
|
||||
volume REAL,
|
||||
vwap REAL,
|
||||
num_trades INTEGER
|
||||
)
|
||||
"""
|
||||
)
|
||||
conn.execute(
|
||||
"INSERT INTO selector_pairs VALUES (?, ?, ?, ?, ?, ?)",
|
||||
(
|
||||
10,
|
||||
"2026-06-17T00:00:00Z",
|
||||
"AAA:USD-BBB:USD",
|
||||
"EXCH:PAIR-AAA-USD",
|
||||
"EXCH:PAIR-BBB-USD",
|
||||
'{"final":"0.5"}',
|
||||
),
|
||||
)
|
||||
conn.executemany(
|
||||
"INSERT INTO trading_instructions VALUES (?, ?, ?, ?, ?, ?)",
|
||||
[
|
||||
(
|
||||
trading_day_start_ns,
|
||||
"2026-06-17T00:00:00Z",
|
||||
"book",
|
||||
"strategy-AAA:USD-BBB:USD",
|
||||
"TARGET_POSITION",
|
||||
(
|
||||
'{"action":"TARGET","quote_asset":"USD","assets":'
|
||||
'{"AAA":{"reference_price":"100","strength":"0.5"},'
|
||||
'"BBB":{"reference_price":"50","strength":"-0.5"}}}'
|
||||
),
|
||||
),
|
||||
(
|
||||
trading_day_start_ns + 60_000_000_000,
|
||||
"2026-06-17T00:01:00Z",
|
||||
"book",
|
||||
"strategy-AAA:USD-BBB:USD",
|
||||
"CLOSE_POSITION",
|
||||
(
|
||||
'{"action":"CLOSE","quote_asset":"USD","assets":'
|
||||
'{"AAA":{"reference_price":"110"},'
|
||||
'"BBB":{"reference_price":"45"}}}'
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
conn.executemany(
|
||||
"INSERT INTO ohlcv_1min VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
[
|
||||
(
|
||||
"2026-06-17T00:00:00Z",
|
||||
trading_day_start_ns,
|
||||
"EXCH",
|
||||
"EXCH",
|
||||
"PAIR-AAA-USD",
|
||||
60,
|
||||
100.0,
|
||||
100.0,
|
||||
100.0,
|
||||
100.0,
|
||||
1.0,
|
||||
100.0,
|
||||
1,
|
||||
),
|
||||
(
|
||||
"2026-06-17T00:00:00Z",
|
||||
trading_day_start_ns,
|
||||
"EXCH",
|
||||
"EXCH",
|
||||
"PAIR-BBB-USD",
|
||||
60,
|
||||
50.0,
|
||||
50.0,
|
||||
50.0,
|
||||
50.0,
|
||||
1.0,
|
||||
50.0,
|
||||
1,
|
||||
),
|
||||
],
|
||||
)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def test_pair_analyze_grid_keeps_clean_labels_and_full_pair_values():
|
||||
module = load_panel_app_module()
|
||||
pair_theo_ret = pd.DataFrame(
|
||||
{
|
||||
"pair_name": ["BTC:USD-ETH:USD", "ADA:USD-BTC:USD"],
|
||||
"mr_ranking": [2, 1],
|
||||
"realized_pnl": [0.0, 0.0],
|
||||
"unrealized_pnl": [0.0, 0.0],
|
||||
}
|
||||
)
|
||||
|
||||
formatted = module.spbt_day.format_pair_theo_ret_for_analyze_grid(pair_theo_ret)
|
||||
|
||||
assert formatted["pair_name"].tolist() == ["BTC-ETH", "ADA-BTC"]
|
||||
assert formatted[module.spbt_day.PAIR_NAME_VALUE_COLUMN].tolist() == [
|
||||
"BTC:USD-ETH:USD",
|
||||
"ADA:USD-BTC:USD",
|
||||
]
|
||||
|
||||
|
||||
def test_panel_app_uses_fast_list_template(tmp_path):
|
||||
module = load_panel_app_module()
|
||||
app = module.SpbtDayPanelApp(repo_root=tmp_path)
|
||||
view = app.view
|
||||
|
||||
assert isinstance(view, module.pn.template.FastListTemplate)
|
||||
assert view.title == module.APP_TITLE
|
||||
assert view.sidebar_width == 430
|
||||
assert view.accent_base_color == module.APP_ACCENT_COLOR
|
||||
assert view.header_background == module.APP_HEADER_COLOR
|
||||
assert len(view.sidebar) == 1
|
||||
assert len(view.main) == 1
|
||||
|
||||
|
||||
def test_panel_app_calculates_pairs_and_selected_pair_outputs(tmp_path):
|
||||
module = load_panel_app_module()
|
||||
data_dir = tmp_path / "data"
|
||||
data_dir.mkdir()
|
||||
db_path = data_dir / "20260617.spbt_results.db"
|
||||
create_panel_fixture_db(db_path)
|
||||
|
||||
app = module.SpbtDayPanelApp(repo_root=tmp_path)
|
||||
app.directory_input.value = str(data_dir)
|
||||
app.refresh_files()
|
||||
app.min_pctg_change_input.value = 0.0
|
||||
|
||||
app.calculate()
|
||||
|
||||
assert app.file_select.value == str(db_path)
|
||||
assert app.file_select.width == 360
|
||||
assert app.min_pctg_change_input.width == 220
|
||||
assert app.calculate_button.width == 110
|
||||
assert app.total_pnl_histogram.sizing_mode == "stretch_width"
|
||||
assert app.selected_pair_market_plot.sizing_mode == "stretch_width"
|
||||
assert app.pair_theo_ret_table.pagination is None
|
||||
assert app.pair_theo_ret_table.layout == "fit_data_table"
|
||||
assert app.pair_theo_ret_table.value["pair_name"].tolist() == ["AAA-BBB"]
|
||||
assert (
|
||||
app.pair_theo_ret_table.value[module.spbt_day.PAIR_NAME_VALUE_COLUMN].tolist()
|
||||
== ["AAA:USD-BBB:USD"]
|
||||
)
|
||||
assert app.selected_pair_name is None
|
||||
assert app.selected_pair_executions_table.value.empty
|
||||
assert app.selected_pair_market_plot.object is None
|
||||
|
||||
app.analyze_pair_row(0)
|
||||
|
||||
assert app.selected_pair_name == "AAA:USD-BBB:USD"
|
||||
assert app.selected_pair_executions_table.value["action"].tolist() == [
|
||||
"TARGET",
|
||||
"TARGET",
|
||||
"CLOSE",
|
||||
"CLOSE",
|
||||
]
|
||||
assert app.selected_pair_market_plot.object is not None
|
||||
Reference in New Issue
Block a user