Skip to content

Commit be0fd12

Browse files
committed
refactor: migrate to src/ monorepo structure
- Restructure packages/pytrade, packages/pytrade-backtest, packages/pytrade-oanda into src/pytrade, src/pytrade-backtest, src/pytrade-oanda - Merge test suites from PyTrade, PyTradeBacktest, oanda-client into tests/ - Fix defects: remove erroneous @AbstractMethod decorators from Oanda, move crossover() to pytrade.indicator, clean up misplaced runtime dependencies - Rewrite Makefile with dynamic src/ package discovery pattern"
1 parent 667faff commit be0fd12

86 files changed

Lines changed: 218742 additions & 1543 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- "**"
7+
8+
jobs:
9+
test:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: Checkout repository
14+
uses: actions/checkout@v4
15+
16+
- name: Set up Python
17+
uses: actions/setup-python@v5
18+
with:
19+
python-version: "3.12"
20+
21+
- name: Install Poetry
22+
uses: snok/install-poetry@v1
23+
with:
24+
version: "1.8.3"
25+
virtualenvs-create: true
26+
virtualenvs-in-project: true
27+
28+
- name: Cache virtualenv
29+
uses: actions/cache@v4
30+
id: cache-venv
31+
with:
32+
path: .venv
33+
key: venv-${{ runner.os }}-${{ hashFiles('poetry.lock') }}
34+
35+
- name: Install dependencies
36+
if: steps.cache-venv.outputs.cache-hit != 'true'
37+
run: poetry install --no-interaction
38+
39+
- name: Run unit tests
40+
run: poetry run pytest -s --tb=native --durations=10 --ignore=tests/integration tests

.gitignore

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,39 @@
1+
# Python
2+
__pycache__/
3+
*.py[cod]
4+
*.pyo
5+
*.pyd
6+
*.egg-info/
7+
*.egg
8+
dist/
9+
build/
10+
.eggs/
11+
12+
# Virtual environments
13+
.venv/
14+
venv/
15+
env/
16+
17+
# Coverage
118
.coverage
2-
.venv
3-
**/__pycache__/*
4-
**/*.log
19+
htmlcov/
20+
.coverage.*
21+
coverage.xml
22+
23+
# Pytest
24+
.pytest_cache/
25+
26+
# Mypy
27+
.mypy_cache/
28+
29+
# IDEs
30+
.vscode/
31+
.idea/
32+
*.code-workspace
33+
34+
# OS
35+
.DS_Store
36+
Thumbs.db
37+
38+
# Poetry
39+
poetry.lock

.vscode/settings.json

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
{
2-
"python.defaultInterpreterPath": ".venv/bin/python",
2+
"python.testing.pytestArgs": [
3+
"tests"
4+
],
35
"python.testing.unittestEnabled": false,
4-
"python.testing.pytestEnabled": true,
5-
"python.terminal.activateEnvironment": true,
6-
"python.terminal.activateEnvInCurrentTerminal": true,
7-
"python.analysis.exclude": [
8-
"**/.venv",
9-
"**/docker",
10-
"**/mypy_cache",
11-
"**/pytest_cache"
12-
]
6+
"python.testing.pytestEnabled": true
137
}

Makefile

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,32 @@
11
SHELL := /bin/bash -e -o pipefail
2+
export PATH := $(HOME)/.local/bin:$(PATH)
3+
24
PROJECT ?= pytrade
3-
BRANCH_NAME ?= local
5+
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD | tr '/' '-')
46
BUILD_NUMBER ?= 0
5-
IMAGE ?= ${PROJECT}:${BRANCH_NAME}-${BUILD_NUMBER}
6-
COMPOSE_FILE=docker/docker-compose.yaml
7-
COMPOSE_BASE_FILE=docker/docker-compose.base.yaml
8-
DC=docker compose -p ${PROJECT} -f ${COMPOSE_FILE} -f ${COMPOSE_BASE_FILE}
9-
SERVICE := pytrade
10-
POETRY ?= "poetry"
11-
VERSION := $(shell head VERSION | grep -Eo "\d+.\d+.\d+")
7+
POETRY ?= poetry
8+
SRC_DIR := src
9+
10+
# Auto-discover packages under src/
11+
PACKAGES := $(shell ls $(SRC_DIR))
12+
13+
# Package filter: "make <cmd> pytrade" selects one package
14+
PKG_FILTER := $(filter $(PACKAGES),$(MAKECMDGOALS))
15+
SELECTED_PKGS := $(or $(PKG_FILTER),$(PACKAGES))
16+
17+
# Convert hyphenated package name to Python import name (e.g. pytrade-backtest -> pytradebacktest)
18+
pkg_to_import = $(subst -,,$(1))
19+
20+
# Computed paths used by sub-makefiles
21+
SRC_DIRS := $(foreach pkg,$(SELECTED_PKGS),$(SRC_DIR)/$(pkg)/$(call pkg_to_import,$(pkg)))
22+
LINT_DIRS := $(SRC_DIRS) tests
23+
COV_ARGS := $(foreach pkg,$(SELECTED_PKGS),--cov=$(call pkg_to_import,$(pkg)))
24+
MYPY_ARGS := $(foreach pkg,$(SELECTED_PKGS),-p $(call pkg_to_import,$(pkg)))
25+
26+
# Allow package names as make goals (no-op targets)
27+
ifneq ($(PKG_FILTER),)
28+
$(PKG_FILTER): @:
29+
endif
1230

1331
.DEFAULT_GOAL := help
1432

@@ -18,4 +36,11 @@ include makefiles/development.mk
1836
include makefiles/help.mk
1937
include makefiles/lint.mk
2038
include makefiles/local.mk
21-
include makefiles/test.mk
39+
include makefiles/test.mk
40+
41+
.PHONY: list-packages
42+
list-packages: ##@other List all packages in src/
43+
@echo "Available packages:"
44+
@for pkg in $(PACKAGES); do echo " - $$pkg"; done
45+
@echo ""
46+
@echo "Usage: make <target> [package]"

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# pytrade
2+
3+
Public monorepo containing the core PyTrade trading framework packages.
4+
5+
## Packages
6+
7+
| Package | Path | Description |
8+
|---|---|---|
9+
| `pytrade` | `src/pytrade/` | Core abstractions — strategies, indicators, instruments, broker interfaces |
10+
| `pytrade-backtest` | `src/pytrade-backtest/` | Backtesting engine for running strategies against historical CSV data |
11+
| `pytrade-oanda` | `src/pytrade-oanda/` | Oanda brokerage adapter implementing `IClient` |
12+
13+
## Local Development
14+
15+
```bash
16+
# Install all packages in editable mode
17+
poetry install
18+
19+
# Run unit tests
20+
make test
21+
22+
# Run a specific test suite
23+
poetry run pytest tests/unit/pytrade
24+
poetry run pytest tests/unit/backtest
25+
poetry run pytest tests/unit/oanda
26+
27+
# Lint
28+
make lint
29+
30+
# Auto-format
31+
make reformat
32+
```
33+
34+
## Building Sub-Packages
35+
36+
Each sub-package can be built and published independently:
37+
38+
```bash
39+
make build-pytrade
40+
make build-pytrade-backtest
41+
make build-pytrade-oanda
42+
# or all at once:
43+
make build-all
44+
```
45+
46+
## Dependency Graph
47+
48+
```
49+
src/pytrade (standalone — numpy, pandas)
50+
src/pytrade-backtest (depends on: pytrade, plotly, progressbar2)
51+
src/pytrade-oanda (depends on: pytrade, v20-python, pyyaml)
52+
```

makefiles/development.mk

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,16 @@ venv: ##@development Set up virtual environment
33
venv:
44
${POETRY} install
55

6-
.PHONY: build
7-
buid: ##@development Build the docker images
8-
build: prod_image ?= ${PROJECT}:${BRANCH_NAME}-${BUILD_NUMBER}
9-
build: dev_image ?= ${PROJECT}_development:${BRANCH_NAME}-${BUILD_NUMBER}
10-
build: args ?= -f docker/Dockerfile --build-arg PROJECT_DIR=. --network=host --build-arg BUILDKIT_INLINE_CACHE=1
11-
build:
12-
DOCKER_BUILDKIT=1 docker build --progress=plain --target production -t ${prod_image} ${args} .
13-
DOCKER_BUILDKIT=1 docker build --progress=plain --target development -t ${dev_image} --cache-from ${prod_image} ${args} .
14-
156
.PHONY: infrastructure
167
infrastructure: ##@development Set up infrastructure for tests
178
infrastructure:
18-
echo "Skipping..."
9+
@echo "Skipping..."
1910

20-
.PHONY: client
11+
.PHONY: clean
2112
clean: ##@development Clean up any dependencies
2213
clean:
23-
echo "Skipping..."
14+
@echo "Skipping..."
2415

25-
.PHONY: config
16+
.PHONY: ci
2617
ci: ##@development Run CI pipeline
27-
ci: clean build infrastructure lint test clean
18+
ci: clean infrastructure lint test clean

makefiles/help.mk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ HELP_FUN = \
1919

2020
.PHONY: help
2121
help: ##@other Show this help.
22-
@perl -e '$(HELP_FUN)' $(MAKEFILE_LIST)
22+
@perl -e '$(HELP_FUN)' $(MAKEFILE_LIST)

makefiles/lint.mk

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,47 @@
11
.PHONY: bandit
2-
bandit: ##@lint Run bandit
3-
bandit: files ?= ${SERVICE}
2+
bandit: ##@lint Run bandit security scan
43
bandit:
5-
${DC} run --rm --no-deps bandit -r ${files}
4+
${POETRY} run bandit -r -c .bandit $(SRC_DIRS)
65

76
.PHONY: black
8-
black: ##@lint Run black
9-
black: files ?= ${SERVICE} tests
7+
black: ##@lint Run black formatter (check mode)
108
black:
11-
${DC} run --rm --no-deps black ${files}
9+
${POETRY} run black --check $(LINT_DIRS)
10+
11+
.PHONY: black-fix
12+
black-fix: ##@lint Run black formatter (write mode)
13+
black-fix:
14+
${POETRY} run black $(LINT_DIRS)
1215

1316
.PHONY: flake8
1417
flake8: ##@lint Run flake8
15-
flake8: files ?= ${SERVICE} tests
1618
flake8:
17-
${DC} run --rm --no-deps flake8 --config .flake8 ${files}
19+
${POETRY} run flake8 --config .flake8 $(LINT_DIRS)
1820

1921
.PHONY: isort
20-
isort: ##@lint Run isort
21-
isort: files ?= ${SERVICE} tests
22-
isort: args ?= --diff --check-only --quiet -rc ${files}
22+
isort: ##@lint Run isort (check mode)
2323
isort:
24-
${DC} run --rm --no-deps isort ${args}
24+
${POETRY} run isort --diff --check-only --quiet $(LINT_DIRS)
25+
26+
.PHONY: isort-fix
27+
isort-fix: ##@lint Run isort (write mode)
28+
isort-fix:
29+
${POETRY} run isort $(LINT_DIRS)
2530

2631
.PHONY: mypy
27-
mypy: ##@lint Run mypy
28-
mypy: args ?= -p ${SERVICE}
32+
mypy: ##@lint Run mypy type checker
2933
mypy:
30-
${DC} run --rm --no-deps mypy ${args}
34+
${POETRY} run mypy $(MYPY_ARGS)
3135

3236
.PHONY: lint
33-
lint: ##@lint Run lint tools
34-
lint: bandit black flake8 isort mypy
37+
lint: ##@lint Run all lint tools
38+
lint: bandit black flake8 isort mypy
39+
40+
.PHONY: clean-imports
41+
clean-imports: ##@lint Remove unused imports
42+
clean-imports:
43+
${POETRY} run autoflake --in-place --remove-all-unused-imports --recursive $(LINT_DIRS)
44+
45+
.PHONY: reformat
46+
reformat: ##@lint Auto-fix imports, isort, and black
47+
reformat: clean-imports isort-fix black-fix

makefiles/local.mk

Lines changed: 7 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,7 @@
1-
.PHONY: bandit-local
2-
bandit-local: ##@lint Run bandit
3-
bandit-local: files ?= ${SERVICE}
4-
bandit-local:
5-
${POETRY} run bandit -r ${files}
6-
7-
.PHONY: black-local
8-
black-local: ##@lint Run black
9-
black-local: files ?= ${SERVICE} tests
10-
black-local:
11-
${POETRY} run black ${files}
12-
13-
.PHONY: flake8-local
14-
flake8-local: ##@lint Run flake8
15-
flake8-local: files ?= ${SERVICE} tests
16-
flake8-local:
17-
${POETRY} run flake8 --config .flake8 ${files}
18-
19-
.PHONY: isort-local
20-
isort-local: ##@lint Run isort
21-
isort-local: files ?= ${SERVICE} tests
22-
isort-local: args ?= --diff --check-only --quiet -rc ${files}
23-
isort-local:
24-
${POETRY} run isort ${args}
25-
26-
.PHONY: mypy-local
27-
mypy-local: ##@lint Run mypy
28-
mypy-local: args ?= -p ${SERVICE}
29-
mypy-local:
30-
${POETRY} run mypy ${args}
31-
32-
.PHONY: lint-local
33-
lint-local: ##@lint Run lint tools
34-
lint-local: bandit-local black-local flake8-local isort-local mypy-local
35-
36-
.PHONY: clean-imports
37-
clean-imports: ##@local Remove unused imports
38-
clean-imports:
39-
autoflake --in-place --remove-all-unused-imports --recursive pytrade tests
40-
41-
.PHONY: reformat
42-
reformat: ##@local Reformat module
43-
reformat: files ?= ${SERVICE} tests
44-
reformat: clean-imports
45-
${POETRY} run isort --overwrite-in-place ${files}
46-
${POETRY} run black ${files}
47-
48-
PHONY: test-local
49-
test-local: ##@local Run test suite
50-
test-local: venv
51-
${POETRY} run pytest -s --tb=native --durations=5 --cov=${SERVICE} --cov-report=term-missing tests
52-
${POETRY} run coverage report --fail-under=90
1+
.PHONY: build
2+
build: ##@local Build selected package(s) with poetry
3+
build:
4+
@for pkg in $(SELECTED_PKGS); do \
5+
echo "Building $$pkg..."; \
6+
cd $(SRC_DIR)/$$pkg && ${POETRY} build && cd -; \
7+
done

0 commit comments

Comments
 (0)